1. 程式人生 > 實用技巧 >C/C++刷題知識點總結

C/C++刷題知識點總結

1、C++ const 常量摺疊

所謂的常量摺疊是編譯器的一種優化技術,也就是程式碼編譯時時 const 常量表達式直接替換成立即數。

不過需要注意的時,const 常量仍然會分配記憶體空間。

  1. #include <iostream>
  2. using namespace std;
  3. int main(void)
  4. {
  5. const int a = 10;
  6. int * p = (int *)(&a);
  7. *p = 20;
  8. cout<<"a = "<<a<<", *p = "<<*p<<endl;
  9. int& c = const_cast<int&>(a);
  10. c = 20;
  11. cout << a<<" "<< c<<endl;
  12. return 0;
  13. }

輸出的結果為:a的值是10,*p的值和c的值都是20。


2、柔性陣列

所謂的柔性陣列,是指結構體的最後一個成員的為長度為0的陣列,這個陣列本身不佔結構體的記憶體,只是一個地址標記,相當於一個指標,標記結構體的結束地址和陣列的起始地址。結合malloc 在堆上動態分配記憶體,相當於在結構體的末尾分配了動態陣列,此之謂柔性。

  1. struct XXX{
  2. int size;
  3. void data[0];
  4. }

malloc(sizeof(XXX) + buff_len) , 便相當於結構體的末尾分配了一個 data[buff_len] 的陣列。


3、不對陣列使用多型

對於函式 func(Base*p, int n), 但是當傳入的函式的引數的型別是派生類的陣列時,func() 函式中通過索引 p[i] 或者 *(p+i) 來訪問物件時,一次跳過的記憶體位元組數(步長)是 sizeof(Base) 大小,此時便會出現記憶體的混亂。

對於陣列元素的訪問和對物件的成員的訪問都是通過轉化為基地址加上偏移量的形式進行的,而偏移量在編譯器就是確定的。

4、虛解構函式

如果基類的解構函式是非虛的,無論基類指標或引用指向的是基類物件還是派生類物件,也不論此時派生類物件的解構函式是否是虛擬函式,delete 基類指標只會呼叫基類的解構函式。

5、迭代器失效

序列容器的 .erase()成員函式

iterator erase (iterator position);
iterator erase (iterator first, iterator last);

注意返回值是一個迭代器,iter = container.erase(iter) 指向刪除位置的下一個位置,所以如果此時再++運算,就會跳過一個元素。

關聯容器的.erase()成員函式

(1)
     void erase (iterator position);
(2)
size_type erase (const key_type& k); //若key存在有刪除 返回1   沒有刪除 返回0               
(3)
     void erase (iterator first, iterator last);

返回值是void,要想刪除之後的迭代器指向刪除位置的下一個位置,需要這樣container.erase(iter++); iter即就是指向了刪除位置的下一個位置。

6、無符號和有符號的比較

  1. int i = -1;
  2. unsigned j = 1;
  3. j < i; //表示式為真

如果表示式包含signed和unsigned int,signed會被轉換為unsigned。-1 轉化為unsigned是ffffffff.

  1. char i = -1;
  2. unsigned char j = 1;
  3. j < i; //表示式為假

算術轉換通常的是做整形提升(integral promotion),對於所有比int小的整形,包括char、signed char、unsigned char、short和unsigned short,如果該型別的所有可能的值都能包含在int內,它們就會被提升為int,否則被提升為unsigned int。如果將bool值提升為int,則false轉換為0,true轉換為1。

所以這裡i 和 j 都會被提升為int 型別, 不過i仍然是int型別的 -1, j 為int型別的1; 仍然有 j > i.

做整形提升時,

unsigned char c = 0xe0;
char d = c;

前邊的位置會補充符號為,所以這裡c提升為int, 將是一個正整數,而d 提升為int, 將是一個負數。雖然d 和 c的二進位制位是一樣的。所以在作比較時,由於需要整形提升,所以將會是 c > d 為真。

7、建構函式和解構函式中呼叫虛擬函式

一般不要這麼做,無論在基類還是派生類的建構函式和解構函式中呼叫虛擬函式,呼叫的都只會是基類的虛擬函式。原因,任何一個正在構建和正在析構的派生類物件只是一個基類物件。(見C++程式設計慣用法p77).

8、關鍵字和關鍵字出現的次數構成的pair來構造map可以模擬multiset

9、折半法查詢

折半法查詢判斷迴圈結束的條件一定是low <= high, 一定要有=;最後才能從兩個元素中鎖定要high的那個,否則只能夠鎖定到low。


10、引數的入棧順序

this指標不入棧,其他引數從右往左入棧,由於棧是向下增長的,所以可以通過左邊的引數的地址推算右邊引數的地址,這也是C語言中可變引數的原理。函式內部,按申明的順序入棧,所以先定義的是大地址。


11、拷貝建構函式只能傳引用,不能傳值

傳值,拷貝建構函式的呼叫時,實參到形參的傳遞就會呼叫拷貝建構函式,這樣會陷入無休止的遞迴呼叫當中。

12、++a的值就是a的值,a++是個臨時值(a本身的值再增加1).

引數的計算順序從右往左,跟入棧的輸入相同: 如果是臨時變數,直接用臨時變數的值代替臨時變數;如果是左值,需要全部計算完,帶入最終的值。

  1. int b = 0;
  2. printf("%d %d", b++, b++); //輸出為 1, 0

先計算右邊的b++的值,是個臨時變數,為0,直接用0替換,然後b變成1;左邊的b++也是個臨時變數,值為1,直接用1替換, 所以最後的輸出為 1,0

  1. int b = 0;
  2. printf("%d %d", ++b, ++b); //輸出為2, 2

先計算右邊++b, 值即為b的值,為1;然後計算左邊表示式,表示式的值為b,最後相當於輸出兩次b 的值, 但是此時b的值為2. 所以最後的值為2, 2.

  1. int b = 0;
  2. printf("%d %d", b++, ++b); //輸出為1 , 2 //先計算右邊表示式的值,是b,計算完後,b的值是1;然後計算b++ ,是個臨時變數,用1代替,b的值變為2

  1. int b = 0;
  2. printf("%d %d", ++b, b++); //輸出為2, 0 //右邊是個臨時變數,用0代替,右邊的值是b,b的值最後為2

13、C++冒號前邊的是標籤,包括訪問控制public、private、protect , case(case後的一定要是某種整形類的資料), 還有自定義的標籤 label,這是可以通過goto 語句跳轉過去的

http://www.taobao.com

所以上邊這句完全沒問題,http: 是自定義的標籤, // 後邊的被註釋掉了,也不會出錯!

14、C++11允許按照物件的值型別來過載

  1. struct Foo {
  2. void foo() & { std::cout << "lvalue" << std::endl; }
  3. void foo() && { std::cout << "rvalue" << std::endl; }
  4. };

15、C風格的兩個字串中的空白會被忽略,合成為一個字串。

char p[ ] = "hello"   "world";

16、列舉型別變數,實際是整形,需要佔一個1個位元組到sizeof(int) 的大小之間,和編譯器的實現有關。

  1. class A
  2. {
  3. int i;
  4. union U
  5. {
  6. char buff[13];
  7. int i;
  8. }u;
  9. void foo() { }
  10. typedef char* (*f)(void*);
  11. enum{red, green, blue} color;
  12. }a;

sizeof(a) 的值為 24,最後的color 列舉型別變數需要一個 int 的大小。注意巢狀的型別, 型別本身不佔記憶體, 是變數才佔記憶體.


17、map的operator [] 運算, 如果關鍵字存在, 返回值是value 型別, 如果關鍵字不存在, 會將關鍵字插入, 返回返回插入的value 的引用, 對於類型別, 呼叫預設建構函式來初始化這個value, 對於內建型別, 初始化為 0. 所以對於const 型別的map, 不能進行 operator[] 運算,防止修改資料.

注意map的元素是pair 型別, 且pair<const key_type,mapped_type>也就是 .first 成員是個const 型別, 不能被修改.

18、C++中的overload,override、overwrite

overload是過載,在相同的作用域;override指的派生類和基類之間的,是覆蓋,也就是虛擬函式(注意一定要函式簽名一樣,才能正確表現出多型),而且虛擬函式最好不要帶預設引數,否則某人蔘數總是基類中的(C++程式設計規範第38條);overwrite是重寫,指的是派生類和基類之間的相同函式名(除了多型,派生類和基類中使用相同的函式名(注意函式簽名可以不同)是非常不好的設計,基類中的同名函式將被隱藏),這時派生類物件和指標將無法訪問基類中的同名函式。另外用誰的指標將呼叫誰的函式。

19、this指標為空

一般的,類指標如果為空,可以呼叫類的成員函式,只要類的成員函式不訪問非靜態的成員變數,相當於給類的成員函式傳入了一個空的this 指標,完全沒有問題。


20、sizeof()只會求表示式的型別,不會對錶達式進行運算求值,而且sizeof()是編譯期求值。一般的賦值,是在執行時才進行的。所以這樣

sizeof(((MyClass*)0)->m_i)

來求一個成員的大小完全沒有問題。

21、assert 巨集的使用

assert 巨集是在執行時起作用,而且是條件為假時,終止程式,並給出定位到條件不滿足的地方。一旦除錯完,可以在 #include <assert.h> 語句之前,定義巨集 #define NDEBUG來取消 assert 巨集的作用。