鍍金池/ 問答/ C++問答

我覺著你可能不是相對路徑,相對路徑默認的圖片路徑都會被編譯

檢查以下是不是圖片路徑寫的是/img/pic1.jpg

假灑脫 回答

PHP腳本當接到POST請求時,$_POST數(shù)組就會被自動創(chuàng)建并裝入請求的參數(shù)。所以在整個腳本,甚至其中引用的腳本,都能夠訪問到同樣的請求參數(shù)。如果提示Undefined index有可能是前端傳入的參數(shù)中并不包含password這個字段。你可以在腳本一開始查看一下file_get_contents('php://input')這條語句的返回值,里面包含了請求參數(shù)的原始內(nèi)容,是不是少了password

夏木 回答
  1. 不會,
  2. 時間太小了,哪有你這么測試的。。

你先不用5個數(shù),我給你個測試數(shù)據(jù):[2,1,3]
你自己在紙上跟著程序單步跑一跑你就明白你問題出來在哪了。

款爺 回答

二級指針, 詳見我以前的這篇博文, 看完了你應(yīng)該就有答案了http://czxyl.me/2017/05/14/In...

以防被墻, 在這里貼出原文.


title: Introduction to double pointers---part1
date: 2017-05-14 19:48:03
tags:
thumbnail: https://d3nmt5vlzunoa1.cloudf...
categories: [c/c++, tricky]

bgimage: https://d3nmt5vlzunoa1.cloudf...

[TOC]

這篇文章試圖從最簡單的例子入手展開指針的實用的技能. 考慮到這篇文章的某些讀者可能不是很熟悉c語言或者對指針仍然不是很熟悉, 所以我們先來看3個非常簡單的例子來熱下身, 不過為了增加點樂趣, 這里也會列出常見的一些錯誤. 不過為了保證文章的流暢性, 關(guān)于指針的語法細節(jié)我不會多涉及. 雖然很多例子都可以改用reference而不是pointers, 但是這里用pointers能更好的講解, 有興趣的同學(xué)也可以自己嘗試用reference改寫例子, 畢竟多動手時提高的唯一方式啊... 本文采取的書寫方式主要是用問答式, 并且盡量將問題進行分解, 也就是說每次提問的答案都很簡短, 但是問題會因此增多, 也會反復(fù)強調(diào)些要點. 所以希望大家看到一個問題先做思考. 當然如果大家發(fā)現(xiàn)我的答案和我的提問不匹配時希望能留言或者發(fā)郵件至czxyl@protonmail.com給我一個改正的機會, 謝謝.

warm up

case0: preface

int main()
{
    int *x = new int(1);
    int **x_address = &x;
}

Q0.1: 這里的x和x_address代表什么?
answer: x 代表1這個rvalue的地址, 也就是說它的值是就是一個整數(shù)的地址 或者說x這個地址上存儲著一個int 1, 如果我們對x進行dereference, 得到的值就是1了 x_address代表著x這個地址的地址. 如果我們對x_address進行dereference, 那么得到的值就是x, 也就是一個整數(shù)的地址了. 如果再進一步dereference得到的就是1了. 所以本文就是圍繞著這么個最簡單的知識展開的.

case1: swap

swap: version A

void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

swap: version B

void swap(int *a, int *b)
{
    int *temp = a;
    a = b;
    b = temp;
}

swap: version C

void swap(int **a, int **b)
{
    int ** temp = a;
    a = b;
    b = temp;
}

swap: version D

void swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}
Q1.1: 哪個version是對/錯的?

answer: A是對的, B, C, D是錯的.

Q1.2: A和B的區(qū)別是什么?

answer: 很明顯, A交換了a和b指向的address之上的value. 而B交換的是兩個address本身,
然而其上的value依然沒有變.

Q1.3: version C錯在哪里(以后會省略前綴version)?

answer: 懂了B的錯誤后, 很輕松的就能發(fā)現(xiàn)C里面交換的只是地址的地址. 所以最頂層的value依然是不變的. 這里提這個形式的例子主要是為了引出主題(double pointer)

case2: allocate

allocate: version A

void alloc1(int* p) {
   p = (int*)malloc(sizeof(int));
   *p = 10;
}
int main(){
   int *p = NULL;
   alloc1(p);
   printf("%d ",*p);
   free(p);
   return 0;
}

allocate: version B

void alloc2(int** p) {
   *p = (int*)malloc(sizeof(int));
   **p = 10;
}
int main(){
   int *p = NULL;
   alloc2(&p);
   printf("%d ",*p);
   free(p);
   return 0;
}
Q2.1: A與B的對錯?

answer: A錯B對

Q2.2: A會造成哪些問題?

answer: 1. 無法準確打印出*p的值. 2. 會有內(nèi)存泄漏

Q2.3: swap::version D 與 allocate::version A有什么相同的錯誤之處?

answer: 都妄圖通過傳value來改變value, 注意, 此處我定義的value是廣義上的, 就像allocate::version A傳入的是一個指針int *p, 同時接受的也是int *類型, 所以我在這里統(tǒng)一看做傳value. 與之相對的就是allocate::version B的傳參就是傳address了.

Q2.4: swap::version A 與 allocate::version B有什么相同的正確之處?

answer: 使用function來改變值時都傳入的是其地址.

Q2.5: 為什么無法準確打印出*p的值?

answer: 結(jié)合Q2.3, 可以看出其實p是作為一個值傳入的. 自然alloc1()里面的p再怎么分配malloc也和main()中的p沒有關(guān)系了. 同時, 由于alloc里面分配的內(nèi)存的所有權(quán)并沒有返回轉(zhuǎn)交到main()中, 所以會造成p = (int*)malloc(sizeof(int));出來的內(nèi)存就被拋棄了, 造成內(nèi)存泄漏

Q2.6: 如何確定真的出現(xiàn)了內(nèi)存泄漏了?

answer: 當然, 這里比較直觀, 所以能肉眼看出來, 但是程序一旦復(fù)雜, 想檢測這種內(nèi)存泄漏的情況可就不一定能一眼看出來了. 所以我們需要一些工具, 比如vs下的_CrtDumpMemoryLeaks或者linux下的valgrind.

Q2.7: 詳細闡述下version B的正確性?

answer: 首先, 傳入的是一個int*的地址, 也可以說成pointer to pointer. 然后在這個地址的值(本質(zhì)也是一個地址)上開辟了空間*p = (int*)malloc(sizeof(int));. 因此相當于在swap時需要往下探入一層(從value到address), 這里也是在地址上的基礎(chǔ)上再往下探入到地址的地址, 這樣地址能夠被在main()中的p保存. 因此allocate2()中對**p的修改也能在main()中反映.

Derive a conclusion from case1 and case2

我們想要通過函數(shù)修改任何一個值必須傳入它地址. 而不能僅僅傳入值, 即使它本身是一個地址, 而你想要修改的就是這個地址, 那么你需要做的就是傳入這個地址的地址. 不過寫到這里, 讀者可能還是不知道什么時候需要用到這種double pointers. 所以, 接下來我們通過各種例子來展示下此技巧的威力

Application for double pointers

Linkedlist

case 1: insert(tail)

這里的insert是在尾部插入結(jié)點, 并且為了講解的效果, 這個例子不使用返回值, 統(tǒng)統(tǒng)使用void. 并且以下某些結(jié)論也是在僅使用void得出的. 如果返回head的話就沒討論的意義了......

insert: version A(trival && singal pointer)

void insert(node *head, node *inserted_node) //假設(shè)head是鏈表頭, inserted_node是帶插入尾部的已分配空間的結(jié)點
{
    node *current_node = head;
    if (current_node == NULL)
    {
        head = inserted_node;
    }
    else
    {
        while (current_node->next)
        {
            current_node = current_node->next;
        }
        current_node->next = inserted_node;
    }
}

insert: version B(non-trival && double pointers)

void insert(node **head, node *inserted_node)
{
    node **current_node = head;
    while (*current_node)
    {
        current_node = &(*current_node)->next;
    }
    *current_node = inserted_node;
}

insert: version C(trival && singal pointer)

void insert(node *head, node *inserted_node) 
{
    node *current_node = head;
    while (current_node)
    {
        current_node = current_node->next;
    }
    current_node = inserted_node;
}
Q1.1: version A省略掉if部分的判斷會怎么樣?

answer: 插入第一個結(jié)點(頭結(jié)點)時會訪問到NULL->next, 非法內(nèi)存訪問.

Q1.2: version B為什么不需要這個version A的if條件?

answer: 想要回答這個問題, 只需要考慮version B在插入頭結(jié)點時的是如何處理的就行了. 與version A不同, version B是用*current_node做循環(huán)條件的, 而不是(*current_node)->next. 這樣做的好處是避免了NULL->next這樣的錯誤步驟出現(xiàn).

Q1.3: version A可以寫成while (current_node)來避免使用if條件, 也就是version C這樣的嗎?

answer: 我們來看下完整的代碼, 希望讀者能上機運行下

#include "stdafx.h"
class node
{
public:
    node *next;
    int elem;
    node(int e) : next{NULL}, elem{e} {}

};

void insert(node *head, node *inserted_node)
{
    node *current_node = head;
    while (current_node)
    {
        current_node = current_node->next;
    }
    current_node = inserted_node;
}

int main()
{
    node *head = NULL;
    node *inserted_node = new node(1);
    insert(head, inserted_node);
    inserted_node = new node(2);
    insert(head, inserted_node);
    return 0;
}

首先我們必須要搞清楚我們傳參的目的是什么, 以上面這份代碼為例, 我們的第一個insert其實是需要修改head的value, 但是, 我們我們傳入的其實也是head的value, 回想下文章開始的case1, case2, 毫無疑問, 這樣做的話, main()中的head依然是NULL, 得不到更新. 并且一直到return 0, head保持著NULL不變. 自然而然我們會想到把node *head = NULL替換為node head(you_choose_integer). 這樣做的話其實是和其它version邏輯稍有區(qū)別的, 即這里的頭結(jié)點是在main()里直接給出, 而不是insert中插入一個頭結(jié)點. 當然, 后面的邏輯依然是一致的. 下面給出完整代碼后我們繼續(xù)分析問題(依然沒有析構(gòu)僅供這里參考下)

#include "stdafx.h"
class node
{
public:
    node *next;
    int elem;
    node(int e) : next{NULL}, elem{e} {}

};

void insert(node *head, node *inserted_node) //假設(shè)head是鏈表頭, inserted_node是帶插入尾部的已分配空間的結(jié)點
{
    node *current_node = head;
    if (current_node == NULL)
    {
        head = inserted_node;
    }
    else
    {
        while (current_node->next)
        {
            current_node = current_node->next;
        }
        current_node->next = inserted_node;
    }
}
int main()
{
    node head(0);
    node *inserted_node = new node(1);
     insert(&head, inserted_node);
    inserted_node = new node(2);
    insert(&head, inserted_node);
    return 0;
}

好, 邏輯上終于修改完善了, 并且正確性大家上機測下很容易 我們以上面這份代碼來看Q1.3本身. 首先我們將代碼改為問題描述的樣子

#include "stdafx.h"
class node
{
public:
    node *next;
    int elem;
    node(int e) : next{NULL}, elem{e} {}

};

void insert(node *head, node *inserted_node) //假設(shè)head是鏈表頭, inserted_node是帶插入尾部的已分配空間的結(jié)點
{
    node *current_node = head;
    while (current_node->next)
    {
        current_node = current_node->next;
    }
    current_node->next = inserted_node;
}
int main()
{
    node head(0);
    node *inserted_node = new node(1);
     insert(&head, inserted_node);
    inserted_node = new node(2);
    insert(&head, inserted_node); 
    return 0;
}

首先看參數(shù)傳遞, 的確傳的是要修改的值的地址, 然后, 因為main()中初始時有一個非NULL的頭結(jié)點, 所以 while (current_node->next)不會出錯. 下面的問題我會進一步講解這種寫法可能出錯的地方. 但是這個其實是特例, 畢竟很少有這種把head一開始就定義的情況...不過下面的一些例子還是會讓大家看到有些情況double pointer能夠?qū)懗霰?code>singal pointer更簡練的代碼的.

Q1.4: 將Q1.3的answer里面while (current_node->next)改為while (current_node)會怎么樣?

answer: 這樣最后跳出時我們得到current_node
NULL, 看上去是合理的, 給那個NULL賦值inserted_node就行了. 但是又有一個新問題, 給這個NULL賦值后我們能確保prev->next = inserted_node嗎? 這里其實是很反直覺的, 答案是不能, 首先我們要知道了next實際上都指向?qū)嶋H的內(nèi)存地址, 而當current_node = NULL(循環(huán)終止條件)時, 我們再給它進行復(fù)制, prev結(jié)點的next依然是NULL, 因為在這里current_node只是一個獨立的指針, 與鏈表結(jié)構(gòu)已經(jīng)無關(guān)了. 如果我們一定要這么做, 必須維護著一個實際存在的prev結(jié)點.

Q1.5: 將Q1.3里面的最后一份代碼的第一步node head(0)改為node *head = new node(0); 會怎么樣?

answer:
其實這個問題的依舊隱藏著一個前提條件, 就是這樣修改依舊會實現(xiàn)在main()中定義好了一個頭結(jié)點, 這樣的話即使形參實參都是node*類型, 也就是說沒有傳地址的情況, 依舊是正確的. 因為它不需要改變head或者inserted_node, 只需要改變next就行了. 但是一旦寫成node *head = NULL, 就會犯NULL->next這樣的read access violation錯誤了.

Q1.6:有沒有辦法將version A改為可以設(shè)置頭結(jié)點在insert建立?

answer: 沒有, 我們討論兩者情況,

  1. node *head = NULL;
  2. node head{0};

法1的問題是想修改head(題目要求)但是傳入的是node *head, 自然不能成功
法2的問題是這樣必須在main()中建立了一個value是0的頭結(jié)點了, 所以已經(jīng)與題意不符了.

Q1.7: 驗證version B的正確性?

answer: 我們首先再回顧下(別怪我啰嗦, 我認為這是最重要的), 傳入的是要修改的head的地址(別被形參和實參名一樣所迷惑, 他們代表的是兩個不同的東西, 形參是實參的地址). 我們先看對頭結(jié)點的處理: 將NULL的地址傳入, 并且修改為了inserted_node. 然后看非head結(jié)點的建立, 此時就需要用到while了. 我們分析下這個while的含義, 循環(huán)條件是對current_node進行dereference, 判斷是不是NULL. 循環(huán)執(zhí)行語句是每次講current_node這個地址的地址下移(next). 邏輯還是很清楚的.

Q1.8: 本例中double pointers的優(yōu)勢在哪里?

answer: 可以任性的在main()中設(shè)置head, 比如node *head = NULL, 也可以node head(0), 也可以node *head = new node(0). 其他version就有著這樣那樣的限制.

case2: remove

這里remove是指移除滿足一定條件的結(jié)點, 不限重復(fù). 在下面的例子中就是移除num是奇數(shù)的結(jié)點. 沒有處理析構(gòu), 僅寫了兩份簡單代碼以便講解. 如果有不了解lambda的可以看version A, C, 不了解函數(shù)指針的可以看version B, D. 不過并不是c style的就是純粹的c了, 比如初始化列表什么的還是用的c++. 還有一點就是這里的single pointer不返回void了. 畢竟上面關(guān)于void的情況依舊討論了很多了, 沒必要繼續(xù)了. 雖燃insert的例子講了那么多關(guān)于值與地址, 但是僅僅為了更好的使讀者理解double pointers, 實際上在不使用void的情況下我們完全可以返回一個head來維護head, 即使傳入的是其value, 只要head = remove_if(...)來接受返回值就行了. 所以再接下來的講解中我們不過多關(guān)注value和address, 而是關(guān)注于代碼邏輯.

remove: version A(right && c style(not completely) && double pointers)

#include "stdafx.h"
#include <iostream>
typedef struct node
{
    struct node * next;
    int num;
    node(int x) : next{ NULL }, num{ x } {}
} node;

typedef bool(*remove_fn)(node const * v);

void remove_if(node ** head, remove_fn rm)
{
    for (node** curr = head; *curr; )
    {
        node * entry = *curr;
        if (rm(entry))
        {
            *curr = entry->next;
            delete entry;
        }
        else
            curr = &entry->next;
    }
}
bool find_odd(node const * x)
{
    if (x->num % 2 == 0)
    {
        return false;
    }
    return true;
}
int main()
{
    node *head = new node(1);
    head->next = new node(2);
    head->next->next = new node(3);
    head->next->next->next = new node(4);
    remove_fn rm = find_odd;
    remove_if(&head, rm);
    return 0;
}

remove: version B(right && c++ style && double pointers)

#include "stdafx.h"
#include <iostream>
struct node
{
    struct node * next;
    int num;
    node(int x) : next{ NULL }, num{ x } {}
};

template<typename remove_fn>
void remove_if(node ** head, remove_fn rm)
{
    for (node** curr = head; *curr != NULL; )
    {
        node * entry = *curr;
        if (rm(entry))
        {
            *curr = entry->next;
            delete entry;
        }
        else
            curr = &entry->next;
    }
}

int main()
{
    node *head = new node(1);
    head->next = new node(2);
    head->next->next = new node(3);
    head->next->next->next = new node(4);
    remove_if(&head, [&](node* temp) {return temp->num % 2 != 0; });
    return 0;
}

remove: version C(right && c style && single pointer)

#include "stdafx.h"
#include <iostream>
typedef struct node
{
    struct node * next;
    int num;
    node(int x) : next{ NULL }, num{ x } {}
} node;

typedef bool(*remove_fn)(node const * v);

node * remove_if(node * head, remove_fn rm)
{
    for (node * prev = NULL, *curr = head; curr != NULL; )
    {
        node * const next = curr->next;
        if (rm(curr))
        {
            if (prev)
                prev->next = next;
            else
                head = next;
            delete curr;
        }
        else
            prev = curr;
        curr = next;
    }
    return head;
}
bool find_odd(node const * x)
{
    if (x->num % 2 == 0)
    {
        return false;
    }
    return true;
}
int main()
{
    node *head = new node(1);
    head->next = new node(2);
    head->next->next = new node(3);
    head->next->next->next = new node(4);
    remove_fn rm = find_odd;
    head = remove_if(head, rm);
    return 0;
}

remove: version D(right && c++ style && single pointer)

#include "stdafx.h"
#include <iostream>
struct node
{
    struct node * next;
    int num;
    node(int x) : next{ NULL }, num{ x } {}
};

template<typename remove_fn>
node * remove_if(node * head, remove_fn rm)
{
    for (node * prev = NULL, *curr = head; curr != NULL; )
    {
        node * const next = curr->next;
        if (rm(curr))
        {
            if (prev)
                prev->next = next;
            else
                head = next;
            delete curr;
        }
        else
            prev = curr;
        curr = next;
    }
    return head;
}

int main()
{
    node *head = new node(1);
    head->next = new node(2);
    head->next->next = new node(3);
    head->next->next->next = new node(4);
    head = remove_if(head, [&](node *temp) {return temp->num % 2 != 0; });
    return 0;
}

remove: version E(wrong && c++ style && single pointer)

#include "stdafx.h"
#include <iostream>
struct node
{
    struct node * next;
    int num;
    node(int x) : next{ NULL }, num{ x } {}
};

template<typename remove_fn>
node * remove_if(node * head, remove_fn rm)
{
    for (node* curr = head; curr; )
    {
        node * entry = curr;
        if (rm(entry))
        {
            curr = entry->next;
            delete entry;
        }
        else
            curr = entry->next;
    }
    return head;
}

int main()
{
    node *head = new node(1);
    head->next = new node(2);
    head->next->next = new node(3);
    head->next->next->next = new node(4);
    head = remove_if(head, [&](node *temp) {return temp->num % 2 != 0; });
    return 0;
}
Q2.1: version E有哪些錯誤?

answer:

  1. 沒有維護好head
  2. 沒有維護好prev和next的羈絆(
Q2.2: version E該如何改正?

answer: refer to version C/D: 如果prev是NULL, 那么就該維護head了(head = next). 使用prev->next = next, prev = curr來維護羈絆. next還有一個作用就是在delete curr前保存著下一個結(jié)點, 如果貪心省略掉這個next 你還是需要一個temp來保存的, 反而變丑了. 這也是寫代碼時容易忽略的一點.

Q2.2: 分析這里兩個right version的邏輯差別?

answer: single pointer version都需要維護prev和next結(jié)點, 而double pointers僅需要維護一個entry結(jié)點(除curr, 并且本質(zhì)上entry起著prev的作用). single pointer需要多一個判斷head的操作

Q2.3: double pointers version不需要判斷head(prev == NULL)?

answer: 這個問題依然要回到之前反復(fù)強調(diào)的value-address了, 傳入的head在這里是可以被修改的, 所以能在if里面直接維護.

Q2.4: double pointers version是如何處理非head結(jié)點的?

answer: 這個問題也需要回到value-address來回答. 其實無論是head還是non-head, 處理方式都是一樣的----改變這個結(jié)點的地址為->next. 這樣是不會破壞與prev的羈絆的. 即使是NULL都無妨(ctrl/command f 任性, 參考這里).

case3: Insertion Sort for Singly Linked List

單鏈表的排序里面我覺得插排是最容易實現(xiàn)的, 所以在這里我使用它來講解 single pointer VS double pointers.
sort: version A(single pointer)
ListNode* linkedListSort(ListNode *head) {
    if (head == NULL || head->next == NULL)
    {
        return head;
    }
    struct ListNode *new_head = new struct ListNode(head->val); // 新鏈表的頭結(jié)點
    struct ListNode *current_node = head->next; // 遍歷舊鏈表
    struct ListNode *tail_node = new_head; // 新鏈表的最后一個結(jié)點
    while (current_node != NULL)
    {
        struct ListNode *temp_node = new_head; // 在新鏈表上進行查找
        if (current_node->val < tail_node->val) // 需要進行中間插入的情況, 為了維護tail_node, 這里不能是小于等于
        {
            if (new_head->val >= current_node->val) //比頭結(jié)點還小的情況
            {
                struct ListNode *new_new_head = new struct ListNode(current_node->val); // 新的頭結(jié)點
                new_new_head->next = new_head;
                new_head = new_new_head;
            }
            else if (new_head->next->val >= current_node->val) // 比頭結(jié)點的下一個結(jié)點小但比頭結(jié)點大
            {
                struct ListNode *after_new_head = new struct ListNode(current_node->val);
                after_new_head->next = new_head->next;
                new_head->next = after_new_head;
            }
            else // 比頭結(jié)點和第二個結(jié)點都大的情況
            {
                while (temp_node->next->val <= current_node->val)
                {
                    temp_node = temp_node->next;
                }
                struct ListNode *middle_insert_node = new struct ListNode(current_node->val);
                middle_insert_node->next = temp_node->next;
                temp_node->next = middle_insert_node;
            }
        }
        else //直接放尾部的情況
        {
            struct ListNode *new_tail_node = new struct ListNode(current_node->val);
            tail_node->next = new_tail_node;
            tail_node = new_tail_node;
        }
        current_node = current_node->next;
    }
    return new_head;
}
Q3.1: 將上面那份代碼改寫為double pointers?
我的這個鏈表插排的寫法看起來的確很長, 但是勝在邏輯比較清楚, 可讀性很強, 邊界條件也都處理的比較完善. 所以這也是我少有的用沒有智能提示的editor里寫一遍就能通過編譯的代碼, 如果讀者抱著學(xué)習(xí)的態(tài)度來看這篇文章但是上面那些例子都沒有自己動手寫, 那么希望能親自動手寫下這個問題, 為了方便大家盡快理解思路以免浪費時間, 我在關(guān)鍵位置都寫了詳細的注釋.

answer:

ListNode* linkedListSort(ListNode *head) {
  // 很清楚的可以看到是O(n)復(fù)雜度
  if(head == NULL || head->next == NULL)
  {
      return head;
  }
  struct ListNode * new_head = NULL;
  while (head != NULL)
  {
      struct ListNode *   copy_of_head  = head;
      struct ListNode ** pointer_to_new_head = &new_head;
      head = head->next;
      while (!(*pointer_to_new_head == NULL || copy_of_head->val < (*pointer_to_new_head)->val ))
      {
          pointer_to_new_head = &(*pointer_to_new_head)->next;
      }
      copy_of_head->next = *pointer_to_new_head;
      *pointer_to_new_head = copy_of_head;
  }
  return new_head;
}

In Summary

可能很多人認為指針是一個糟糕的特性, 也有很多人認為掌握指針的各種技巧在C++11 里各種封裝好的smart pointer前只是班門弄斧. 但是我想說, 世界上不存在神這種玩意, 所以永遠不要把一個人當做神, 即使他/她再強, 說的話也不應(yīng)該全盤被信任, 所以你必須做出自己的判斷, 這判斷不是為了判斷而判斷, 而是必須盡快的切換到學(xué)習(xí)知識而不是犯傻逼腦殘的所謂選擇困難癥的狀態(tài), 否則只會在一票大神面前永遠迷失方向, 今天某大v說學(xué)這個是沒有意義的, 明天另一個大v說學(xué)那個是有意義的. 如果你信任所有人, 就會陷入什么都想學(xué), 卻什么都沒心思學(xué)的困境, 解決問題的唯一途徑就是憑著自己的興趣走下去, 何必管邊上的人所說的話?

等這些天手邊的事忙完后我在再寫一些 double pointers的其它應(yīng)用, 比如鏈表的其他操作, 樹的各種......

怣痛 回答

cbegin和cend等是在c++14開始添加的, 並非c++11

TL;DR, 是爲了更方便的獲得常量迭代器.
在c++14以前, 你像要得到常量迭代器, 方法比較繁瑣:

typedef vector<MyType> vect;
typedef vect::const_iterator c_iter;
vect v;
// alternatives:
c_iter it = const_cast<vect const &>(v).begin(); // 1
c_iter it = static_cast<c_iter>( v.begin() ); // 2 (explicit)
c_iter it = v.begin(); // 2 (implicit)

雖然現(xiàn)在const有被濫用的趨勢, 但是該用的時候還是要用, 比如fold, 或者其在c++中的等價函數(shù), std::accumulate, 不需要懟迭代器指向的成員進行修改, 此時const-qualify有其合理性.

再者, 其實現(xiàn)也非常簡單, 利用現(xiàn)有設(shè)施很容易, 不會破壞core laungage:

const_iterator cbegin() const;
const_iterator cend () const;

最後, 它與c++0x的其它特性, 比如auto/decltype可以相互配合, 威力成倍增強, 比如如果沒有cbegin, auto只能得到non-const版本.

其實這種問題最好的解決方法就是去 wg21 看其在2004年的提案, http://www.open-std.org/jtc1/...

更新:

窩跟你說...這裏有人在c++14出臺之前抱怨爲什麼沒有const 版本的std::begin()抱怨, 然後草藥大叔親自登場了...

孤影 回答

多線程操縱map?

嫑吢丕 回答

應(yīng)該是框架規(guī)則中要加載的類 (AuthorizedAccessTokenController),跟實際代碼中期望被加載的類名不匹配。

  1. 看一下提示的 命名空間 下,類名是否有誤
  2. 引入相關(guān)類時,是否寫法有誤
笨笨噠 回答

今天把Vue的代碼和jQuery的代碼分開成兩個<script>,好像好了

在form上加上onsubmit="return false;"解決了,原來這個報錯不是
$.ajax({

            type: "POST",。。。。
           

導(dǎo)致的

久不遇 回答

使用線程執(zhí)行 demux_thread 任務(wù)時,主線程要等待它返回(可用 SDL_WaitThread()),否則 main() 函數(shù)返回時將強制結(jié)束其他線程。

解夏 回答
  1. cpu在做密集的計算才會導(dǎo)致100%,最常見的例子就執(zhí)行的循環(huán)次數(shù)過多,要不就是那種死循環(huán)。
  2. 回到你的代碼上來,你這個函數(shù)沒有邏輯問題,但是存在性能問題,你的外層map元素個數(shù)到達10萬乃至百萬時,循環(huán)需要執(zhí)行很多次才能退出。
  3. 優(yōu)化的方式:要嘛增加修改查詢條件,加一個userid,查詢只需要做兩次map的find操作;要嘛修改存數(shù)據(jù)結(jié)構(gòu)提高查詢效率。
九年囚 回答

undefined是php沒有接收到POST數(shù)據(jù),但是你頁面上已經(jīng)提交了數(shù)據(jù),這時候注意檢查你的“請求頭”信息是否正確,

萢萢糖 回答

找到了,toFixed()會自動四舍五入...

伐木累 回答

因為Demo.h中定義的全局變量demo可以不被初始化。

靜態(tài)存儲期的非局部變量的初始化在程序啟動時進行,通常在main函數(shù)執(zhí)行前完成,除非被推遲了。它們的初始化分成兩類,靜態(tài)初始化和動態(tài)初始化。其中動態(tài)初始化可能會被推遲,推遲與否取決于編譯器實現(xiàn)。而被推遲的動態(tài)初始化可能不會被執(zhí)行。這里demo的初始化屬于動態(tài)初始化,所以不同編譯器可以產(chǎn)生不同的結(jié)果。Deferred dynamic initialization

最佳實踐是避免程序依賴全局變量構(gòu)造函數(shù)的副作用。而在真的需要這樣一個可以全局訪問的對象時,請用單例模式。

憶往昔 回答

epoll不是“GCC的類庫”,它是Linux專有API,在Mac上無論你用什么編譯器都一樣用不了的。
Mac和FreeBSD有一個類似的功能叫kqueue。

安淺陌 回答

我是寫 Java 的,那么如果要用不那么 OO 的方式(我們一般喜歡用日志 slf4j 之類的),那么我會用下面兩種思路:

public static void printTimes(Object obj, int times) {
    String content = obj.toString();
    // 通過新建一個異常來獲取調(diào)用棧信息,不拋出即可。
    String where = new Exception().getStackTrace()[1].getClassName();
    // 后續(xù)省略
}

public interface Printer {

    // 此法需要 Java 8
    // 聲明默認方法,想要為某個類加上按次數(shù)打印功能時就 implements Printer
    default void printTimes(Object obj, int times) {
        String where = this.getClass().getSimpleName();
        // 后續(xù)省略
    }

}

事實上新建異常來獲取調(diào)用棧信息可以拿到非常完整的執(zhí)行環(huán)境信息:

所處類 | 所處方法 | 所在文件名稱 | 所在文件行數(shù)

歡迎討論。

尋仙 回答

你不是輸入0 0 0了嗎

夕顏 回答

ISO C 標準中寫到

6.3.1.3 Signed and unsigned integers

When a value with integer type is converted to another integer type
other than _Bool, if the value can be represented by the new type, it
is unchanged. Otherwise, if the new type is unsigned, the value is
converted by repeatedly adding or subtracting one more than the
maximum value that can be represented in the new type until the value
is in the range of the new type. Otherwise, the new type is signed and
the value cannot be represented in it; either the result is
implementation-defined or an implementation-defined signal is raised.

換句話說,unsigned char 的表示范圍是 [0, 255],不能表示 -1,于是將 -1 加上 256 得到 255。

如果是把 signed char 型 -1 轉(zhuǎn)成 unsigned int,則用 -1 加上 4294967296 得到 4294967295。

對硬件來說,從有符號到無符號的轉(zhuǎn)換就是簡單地在前面補符號位或者直接截斷。

慢半拍 回答

你的程序表明你開的是局部變量而不是全局變量(關(guān)于全局和局部變量你可以參考C++ 全局變量、局部變量、靜態(tài)全局變量、靜態(tài)局部變量的區(qū)別)。
所以你的數(shù)組是開在棧上的,這就涉及到編譯期限制棧大小的問題。如果你申請這么大的數(shù)組是會stackoverflow的,我記得我原來用devc++寫oj的時候開了一個100000的數(shù)組好像就爆棧了,但是現(xiàn)在換到osx的clion下面好像沒事了...
在一般情況下, 不同平臺默認棧大小如下(僅供參考)

SunOS/Solaris 8172K bytes (Shared Version)

Linux 10240K bytes

Windows 1024K bytes (Release Version)

AIX 65536K bytes

當然你可以修改你的默認棧大?。?

1.SunOS/Solaris系統(tǒng):
limit # 顯示當前用戶的棧大小
unlimit # 將當前用戶的棧大小改為不限制大小
setenv STACKSIZE 32768 #設(shè)置當前用戶的棧大小為 32M bytes

2.Linux系統(tǒng):
ulimit -a #顯示當前用戶的棧大小
ulimit -s 32768 #將當前用戶的棧大小設(shè)置為32M bytes

3.Windows (在編譯過程中的設(shè)置)

  • 選擇 "Project->Setting".
  • 選擇 "Link".
  • 選擇 "Category"中的 "Output".
  • 在 "Stack allocations"中的"Reserve:"中輸棧的大小,例如: 32768

在 Visual Studio 開發(fā)環(huán)境中設(shè)置此鏈接器選項

  • 打開此項目的“屬性頁”對話框。有關(guān)詳細信息,請參見設(shè)置 Visual C++ 項目屬性。
  • 單擊“鏈接器”文件夾。
  • 單擊“系統(tǒng)”屬性頁。
  • 修改下列任一屬性:
  • 堆棧提交大小
  • 堆棧保留大小

Reference