代碼疑云(1)-掌握初始化列表
代碼:
- #include<iostream>??
- using?namespace?std;??
- class?A??
- {??
- private:??
- ??int?x1;??
- ??int?x2;??
- public:??
- ??A():x2(1),x1(x2++){}?//初始化列表??
- ??void?print()??
- ??{??
- ????cout<<"x1="<<x1<<endl??
- ????????<<"x2="<<x2<<endl;??
- ??}??
- };??
- int?main()??
- {??
- ??A?a;??
- ??a.print();??
- ??
- ??return?0;??
- }??
疑:x1,x2最終被輸出什么值呢?為什么?
解答:上機調試下會發現輸出的結果是:x1是一個隨機數,x2是1。為什么?因為在初始化列表中在給x1賦值為x2++時,這個x2并未初始化,也就是說x2里面什么東西也沒裝。也許你會問我不是在前面已經給x2賦值了嗎,沒錯,但是有一個問題你忽略了,那就是初始化列表的賦值順序是依照x1和x2的聲明順序的順序來初始化的,也就是說在代碼中,程序是先給x1賦值為x2++(此時x2并未初始化),再給x2賦值為1。還有初始化列表里的賦值,是在變量被聲明時進行的,所以多使用初始化列表是可以提升程序效率的。
!!代碼疑云系列由本人在天天唯C論壇下首發
代碼疑云(2)-c函數調用約定
代碼:
- #include<iostream>??
- using?namespace?std;??
- void?foo(int?p1,int?p2,int?p3)??
- {??
- ??cout<<"p1="<<p1<<endl??
- ??????<<"p2="<<p2<<endl??
- ??????<<"p3="<<p3<<endl;??
- }??
- int?main()??
- {??
- ??int?i;??
- ??cout<<"first?call:"<<endl;??
- ??i=0;??
- ??foo(++i,++i,++i);??
- ??cout<<"second?call:"<<endl;??
- ??i=0;??
- ??foo(++i,i++,i++);??
- ??return?0;??
- }??
疑:兩次調用foo函數分別輸出了什么,為什么?
解答:按照cedel函數調用的約定,編譯器使參數從左到右的入棧。第一次調用為什么p1,p2,p3的值全是3呢,原因在此,在foo被call之前三++i 操作將先被操作也就是連續自增了3次,最終結果i 的值是3,然后是編譯器push(i),push(i),push(i)三次入棧,然后call到foo定義處依次出棧并相應地復制給了形參。第二次調用foo時,一開始與第一次一樣先是計算三次++操作,但是所不同的是最后兩個是i++,i++ 剛沒說到它們的計算順序,編譯器計算這些的順序是由右到左的,也就是先i++,再i++,最后是++i,而運算i++是先取值再自增的,編譯器會先把i (這時為0)存入寄存器(cpu中的存儲器),再加1,然后計算下一個i++ 與前一次一樣,所不同的是這次 i 的值是1,因為前面已加1 ,最后++i 。
!!代碼疑云系列由本人在天天唯C論壇下首發
代碼疑云(3)-靜態字符串
代碼:
- #include<iostream>??
- using?namespace?std;??
- int?main()??
- {??
- ??char?*str1?=?"string";??
- ??char?*str2?=?"string";??
- ??if(str1?==?str2)??
- ????cout<<"str1?is?same?as?str2";??
- }??
疑:str1 的值是否等于 str2 而輸出字符串“str1 is same as str2”呢,為什么? 解答:是的 “str1 is same as srr2”,也就是說str1與str2指向了相同的內存地址,因為"string"是靜態對象,是由編譯器分配給他的內存空間,在代碼中出現了兩次,編譯器并不會給他們分別分配空間,因為如果這樣將會造成不必要的浪費。
代碼疑云(4)-類的sizeof值
代碼:
- #include<iostream>??
- using?namespace?std;??
- class?A??
- {??
- ??
- };??
- class?B??
- {??
- ??char?a;??
- ??int?b;??
- };??
- class?C??
- {??
- ??void?foo(){};??
- };??
- class?D??
- {??
- ??virtual?void?foo(){};??
- };??
- int?main()??
- {??
- ??cout<<sizeof(A)<<sizeof(B)<<sizeof(C)<<sizeof(D);??
- ??return?0;??
- }??
疑:結果是什么,為什么呢?
解答:sizeof(A)為1,而不是0,雖然空類沒有任何成員變量,但其實體提供取地址操作,所以其內存空間不能為0。sizeof(B)為8,編譯器在計算類體或結構體變量的地址時是通過字節對齊的方式進行的,也就是通過加多少偏移量來確認是那個變量。類的偏移量為其最大內存空間的類型的成員變量的長度,其最大內存空間類型成員是int,所以偏移量為4字節,即char a成員需要補上3個字節,才能讓編譯器準確高效地尋址,再加上int的4字節,sizeof(B)就是8了。sizeof(C)為1,類的成員函數在編譯器編譯時,其函數地址就已自動存放,所以不必為其分配額外的空間來存放他,所以它的長度跟空類一樣為1。sizeof(D)為4,因為類D需要構造一虛函數列表來存放函數指針以實現動態調用,一個指針的長度占用4個字節,所以為4.
代碼疑云(5)-類成員函數的thiscall約定
代碼:
- #include<iostream>??
- using?namespace?std;??
- ??
- class?A??
- {??
- private:??
- ??int?value;??
- public:??
- ??A()??
- ??{??
- ????value=0;??
- ??}??
- ??void?coutHello()??
- ??{??
- ????cout<<"hello"<<endl;??
- ??}??
- ??void?coutValue()??
- ??{??
- ????cout<<value<<endl;??
- ??}??
- };??
- int?main()??
- {??
- ??A?*pA=NULL;?//空指針,所指向的內容不可訪問存取??
- ??
- ??pA->coutHello();??
- ??pA->coutValue();??
- ??
- ??return?0;??
- }??
(感謝網友提供的題目)
疑:調用coutHello和coutValue方法有什么問題?解答: 成員函數的地址在編譯器編譯時給出的,所以是已知的,根據thiscall約定,類的成員函數在編譯時編譯器會傳入一個this指針,通過this指針指向成員變量,在調用couthello時并未用到this指針所以調用正常,而調用coutvalue時,value需要用到this指針,因為此時this是NULL指針,所以會發生內存報錯。
代碼疑云(6)-頭文件的正確定義
代碼:
頭文件print_tools.h
- #include<stdio.h>??
- void?printStr(const?char?*pStr)??
- {??
- ??printf("%s\n",pStr);??
- }??
- void?printtInt(const?int?i)??
- {??
- ??printf("%d\n",i);??
- }??
頭文件counter.h
- #include"print_tools.h"??
- static?int?sg_value;??
- void?counter_init()??
- {??
- ??sg_value=0;??
- }??
- void?counter_count()??
- {??
- ??sg_value++;??
- }??
- void?counter_out_result()??
- {??
- ??printStr("the?result?is:");??
- ??printtInt(sg_value);??
- }??
main.cpp
- #include?"print_tools.h"??
- #include?"counter.h"??
- int?main()??
- {??
- ??char?ch;??
- ??counter_init();??
- ??printStr("please?input?some?charactors:");??
- ??while((ch=getchar())!='$')??
- ????{??
- ??????if(ch=='A')??
- <span?style="white-space:pre">????</span>counter_count();??
- ????}??
- ??counter_out_result();??
- }??
疑:以上代碼編譯時有何問題嗎,是什么導致的呢?
解答:void printStr(const char*)和'void printtInt(int) 函數redefine了,道理很簡單,因為在counter.h中已包含了print_tools.h,在main.cpp中又包含了print_tools.h頭文件,也就是兩次包含了print_toosl.h,所以也就發生了重定義錯誤,這是初學者常見問題,要避免這個問題,我們必須采取防范措施,就是使用預編譯命令,如下作修改:
頭文件print_tools.h
- #ifndef?PRINT_TOOLS_H?//如果未定義?PRINT_TOOLS_H??
- #define?PRINT_TOOLS_H?//則定義?然后編譯以下源代碼??
- #include<stdio.h>??
- void?printStr(const?char?*pStr)??
- {??
- ??printf("%s\n",pStr);??
- }??
- void?printtInt(const?int?i)??
- {??
- ??printf("%d\n",i);??
- }??
- #endif?//結束?//如此改頭文件就只被編譯器編譯一次了,起到了防范重定義錯誤的作用??
頭文件counter.h
- #ifndef?COUNTER_H??//??
- #define?COUNTER_H??//??
- #include"print_tools.h"??
- static?int?sg_value;??
- void?counter_init()??
- {??
- ??sg_value=0;??
- }??
- void?counter_count()??
- {??
- ??sg_value++;??
- }??
- void?counter_out_result()??
- {??
- ??printStr("the?result?is:");??
- ??printtInt(sg_value);??
- }??
- #endif?//??
代碼疑云(7)-構造函數在類繼承時
代碼:
- #include?<iostream>??
- using?namespace?std;??
- class?A??
- {??
- public:??
- ??A()??
- ??{??
- ????Print();??
- ??}??
- ??virtual?void?Print()??
- ??{??
- ????cout<<"A?is?constructed.\n";??
- ??}??
- };??
- ??
- class?B:?public?A??
- {??
- public:??
- ??B()??
- ??{??
- ????Print();??
- ??}??
- ??
- ??virtual?void?Print()??
- ??{??
- ????cout<<"B?is?constructed.\n";??
- ??}??
- };??
- ??
- int?main()??
- {??
- ??A*?pA?=?new?B();??
- ??delete?pA;??
- ??
- ??return?0;??
- }??
疑:以上代碼輸出結果是?
輸出結果如下:
A is constructed.
B is constructed.
解釋:當new B()時,因為B繼承了A,所以編譯器首先調用A的構造函數,A的構造函數里是調用A類里的Print(),所以首先輸出A is constructed ,然后調用的是B的構造函數,此時Print虛函數指針已指向B的void print(),所以輸出B is constructed。