遇到就更新 C C 中一些容易犯錯的小傢伙

2021-08-06 03:59:36 字數 3834 閱讀 7313

想要造輪子,c/c++功力不可少,不往高處談設計,先把腳底下的坑填了再說。這些小細節,遇到就更新,你上你也行!

先來點個題,與本文其他內容無關。這可能是宇宙中最難以回答的問題了,它似乎牽扯到了哲學和倫理。不管怎樣,c++起初都被認為是帶類的c(c with classes),那他起碼也得比c語言更厲害一點,那似乎應該是1。真的是這樣嗎?回到**本身:

int c = 0;

printf("%d", c++);

這個……好像還是0!

假設foo是乙個類,問以下**中有多少個物件被建立:

foo foo1();

foo foo2(2);

foo foo3[3];

foo* foo4[3];

foo& foo5 = foo2;

如果你被這個語法看花了眼,那一定是5個,因為你把第一行當成了用foo類的預設構造版本建立物件,其實他只是乙個返回值是foo型別的無參函式的宣告。注意,不同於new乙個物件,new foo;和new foo();是等價的,在棧中用預設建構函式建立物件的話請務必去掉這對圓括號。

問以下**執行完後,b的值是多少:

int a = 2, b = 1, c = 0;

bool result = a > 0 || ++b && c;

如果你整天把&&、|| 短路短路求值的特性記得滾瓜爛熟而忘了他倆的優先順序這個最根本的東西,肯定會不假思索的回答1!

老實說這個就是筆試題中的老鼠屎,你還得夾起來扔掉,要不然壞一鍋粥,看**:

class foo 

};int main()

正常人都以為這個會crash的,正常人誰會寫這樣的**,但一碼歸一碼,這**是沒問題的,因為這個func方法並沒有引用到類例項的任何東西,他僅僅是接受乙個引數,輸出之。我們都知道,c++類成員函式在編譯時其實就是加了乙個類型別指標的普通c函式,對於上面的func成員函式,大概是這樣的:

void func(foo* instance, int a);
在這樣呼叫時:pfoo->func(10086); 被編譯器替換成了func(pfoo, 10086);,而func函式內並沒用引用到pfoo的任何記憶體例項,所以自然就不會crash。

大家都知道,c/c++中陣列作為函式的引數時會退化為同型別指標,所以無法在函式內直接通過這個指標獲取陣列的長度(c風格字串除外),這也就是為什麼那些c庫裡邊運算元組的函式都得再傳乙個代表陣列長度的引數,如qsort等。但是,如果給這個陣列引數指定了長度呢?就像下面:

void foo(int a[10])
這得從哲學的角度好好想一想,傳int a 和 int* a時因為不知道陣列在記憶體中的邊界故而不清楚陣列的長度,那麼a退化成指標(即sizeof(a) == 4 or 8)似乎是乙個不錯的辦法,讓程式設計師自己想辦法解決大小問題;但是現在指定了陣列大小,是不是就意味著我了解了這個陣列引數真正的、確切的長度,那麼sizeof(a)是不是就是4 * 10 == 40呢?似乎從哲學上說的過去,那麼事實真的是這樣嗎?很遺憾,並不是,a依然只是個int*,引數裡的10在這裡好像並沒有什麼用,除了讓你自我感覺良好一點:看!我限制了傳入陣列的長度!但事實是你依然可以如此呼叫:
int a[3];

foo(a);

int* p = a;

foo(p);

// 甚至……

foo(null);

就算你把整個天傳進去,在函式體內sizeof(a)永遠只是乙個指標的大小!如果真的想限制陣列引數的長度,那麼有兩種辦法:

1. 傳指向指定大小陣列的指標

void foo(int (*a)[10]);

2. 傳指定大小的陣列引用

void foo(int (&a)[10]);

完美解決你的問題!

int a[3];

int* p1 = a;

int* p2 = &a;

void foo() {};

//...

void(*f1)() = foo;

void(*f2)() = &foo;

void(*p3)() = &&&&&foo;

但是,對於陣列來說,情況就完全不同了。

不加&的話表示乙個指向陣列第乙個元素的指標,他的型別是int*,也就是老師常說的陣列名就是指標;而加上&的話,就代表「取整個陣列的位址」,他的型別不是普通的指標了,而是指向乙個特定大小的陣列的指標,對於上面來說,就是int(*)[3]了,對其解引用,得到的才是乙個int*,指向該陣列的第乙個元素。那麼問題來了,這個型別能不能賦值給int* 型別的變數呢?畢竟他們都是指標,最終都是指向int的,答案當然是不行!所以上面的int* p2 = &a;是根本不能通過編譯的,應該是int (*p2)[3] = &a;。

int*的意思大概是:記憶體裡有好多個int,我的任務就是指向其中乙個。

int(*)[3]的意思大概是:記憶體中有好多3個int連在一起的空間,我的任務就是指向其中乙個空間的開始。

這就好理解為什麼對int(*)[3]解引用後便是乙個普通的int指標,意思大概是:我int(*)[3]已經在記憶體中找到了3個int連續的空間了,確定到底要其中哪個int的任務就交給你int*吧!

上面這話的意思是這兩種型別步進的偏移不同:32位環境下,int* 的偏移就是int的大小,即4,int(*)[3]的偏移是3個int的大小,即12。知道了這兩種區別,就不會做錯下面這道面試題了:

int a[5] = ;

printf("%d,%d", *(a + 1), *(*(&a + 1) - 1));

*(a + 1)很好理解,就是陣列的第2個元素,即2;重點是*(*(&a + 1) - 1),先看&a + 1,前面說過了,&a代表指向有5個元素的陣列的指標,他的步進偏移是5 * 4 = 20,所以&a + 1可以看成是代表這個陣列後面的那個相同大小陣列的位址,比a的位址高20個位元組,然後給他解引用,前面說過,得到了指向int的指標,此時這個指標指向陣列a後面的那個陣列的第乙個元素,而實際情況是a後面並沒有陣列了,若是此時對這個指標進行解引用,那麼要麼得到乙個隨機值,要麼直接崩潰。接著,對解引用出來的int* 減了個1,int*的偏移是4,故向前偏移4個位元組,這不正好指向了陣列a的最後乙個元素嗎?解引用之,得到5。

你還會發現,這個型別不就是二維陣列的陣列名的型別嗎?像這樣:

int a[2][3];

int (*p)[3] = a;

而對二維陣列取位址,結果就又是另外一種情況了……好了,就此打住。

分析這段**的輸出:

class foo 

void print()

}// ...

foo foo;

foo.print();

如果不清楚c++類建構函式中成員變數的初始化順序,那麼很容易得出答案:n1是1,n2是0。事實上,建構函式中成員變數的初始化順序和初始化列表中指定的順序並無關係,只與成員在類中的宣告順序有關。

在上例類foo中,n1先於n2宣告,故在構造foo的物件時,n1也先於n2構造,即使初始化列表中試圖先構造n2。因為構造n1時:n1(n2 + 1),n2並沒有被構造,所以n1的值是未知的,n2的確被初始化為0。

C 中一些容易混淆的概念

指標函式 是指帶指標的函式,本身是函式,只是返回值是某乙個型別的指標。int fun int x 函式指標 指向函式的指標變數,即本質是乙個指標變數。int fun int x void fun 定義函式指標 void test int main void test typedef typedef ...

一些容易忘記的

important的使用者樣式 important的作者樣式 作者樣式 使用者樣式 使用者在瀏覽器裡自己寫配置檔案定義的樣式。瀏覽器定義的樣式 作者樣式中 元素內定樣式 內嵌樣式表 匯入樣式表 import 外鏈樣式表 復合選擇器 慎用,考慮瀏覽器相容支援問題。當用rgb百分比時,即使當值為0時也要...

selenium遇到的一些問題,持續更新

1.今天早上執行程式的時候,發現我在迴圈點選乙個元素的時候出現了錯誤 selenium.common.exceptions.staleelementreferenceexception message stale element reference element is not attached t...