1. 運行下面的C++代碼,得到的結果是什么?
#include "stdafx.h" #include<iostream> using namespace std; class A { private: int m_value; public: A(int value) { m_value = value; } void Print1() { printf("hello world"); } void Print2() { printf("%d", m_value); } virtual void Print3() { printf("hello world"); } }; int _tmain(int argc, _TCHAR* argv[]) { A* pA; pA->Print1(); pA->Print2(); pA->Print3(); return 0; }
答案是:Print1呼叫正常,列印出hello world,但運行至Print2時,程式崩潰,
呼叫Print1時,并不需要pA的地址,因為Print1的函式地址是固定的,編譯器會給Print1傳入一個this指標,該指標為NULL,但在Print1中該this指標并沒有用到,
只要程式運行時沒有訪問不該訪問的記憶體就不會出錯,因此運行正常,
在運行print2時,需要this指標才能得到m_value的值,由于此時this指標為NULL,因此程式崩潰了,
對于Print3這一虛函式,C++在呼叫虛函式的時候,要根據實體(即this指標指向的實體)中虛函式表指標得到虛函式表,再從虛函式表中找到函式的地址,由于這一步需要訪問實體的地址(即this指標),而此時this指標為空指標,因此導致記憶體訪問出錯,
2. 運行下面的C++代碼,得到的結果是什么?
#include<iostream> using namespace std; class A { public: A() { Print(); } virtual void Print() { cout<<"A is constructed."<<endl; } }; class B: public A { public: B() { Print(); } virtual void Print() { cout<<"B is constructed."<<endl; } }; int main() { A* pA = new B(); delete pA; return 0; }
先后列印出兩行:A is constructed. B is constructed. 呼叫B的建構式時,先會呼叫B的基類A的建構式,
然后在A的建構式里呼叫Print,由于此時實體的型別B的部分還沒有構造好,本質上它只是A的一個實體,他的虛函式表指標指向的是型別A的虛函式表,
因此此時呼叫的Print是A::Print,接著呼叫型別B的建構式,并呼叫Print,此時已經開始構造B,并且虛函式表的指標已指向類B的虛函式表地址,因此此時呼叫的Print是B::Print,
3. 運行下面的C++代碼,輸出是什么?
class A{ private: int n1; int n2; public: A(): n2(0), n1(n2 + 2) { } void Print() { std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl; } }; int _tmain(int argc, _TCHAR* argv[]) { A a; a.Print(); return 0; }
輸出n1是一個隨機的數字,n2為0,
在C++中,成員變數的初始化順序與變數在型別中的申明順序相同,而與它們在建構式的初始化串列中的順序無關,
因此首先初始化n1,而初始n1的引數n2還沒有初始化,是一個隨機值,初始化n2時,根據引數0對其初始化,故n2=0,
4. 編譯運行下面的C++代碼,結果是什么?
(A)編譯錯誤;(B)編譯成功,運行時程式崩潰;(C)編譯運行正常,輸出10,請選擇正確答案并分析原因,
class A{ private: int value; public: A(int n) { value = n; } A(A other) { value = other.value; } void Print() { std::cout << value << std::endl; } }; int _tmain(int argc, _TCHAR* argv[]) { A a = 10; A b = a; b.Print(); return 0; }
編譯錯誤,在復制建構式中傳入的引數是A的一個實體,
由于是傳值,把形參拷貝到實參會呼叫復制建構式,因此如果允許復制建構式傳值,那么會形成永無休止的遞回并造成堆疊溢位,因此C++的標準不允許復制建構式傳值引數,而必須是傳參考或者常量參考,
復制建構式的引數需要改為:const A& other,
5. 運行如下的C++代碼,輸出是什么?
class A{ public: virtual void Fun(int number = 10) { std::cout << "A::Fun with number " << number; } }; class B:public A { public: virtual void Fun(int number = 20) { std::cout << "B::Fun with number " << number; } }; int main() { B b; A &a = b; a.Fun(); return 0; }
輸出 B::Fun with number 10,
由于a是一個指向B實體的參考,因此在運行的時候會呼叫B::Fun,但預設引數是在編譯期決定的,在編譯的時候,編譯器只知道a是一個型別a的參考,具體指向什么型別在編譯期是不能確定的,因此會按照A::Fun的宣告把預設引數number設為10,
這一題的關鍵在于理解確定預設引數的值是在編譯的時候,但確定參考、指標的虛函式呼叫哪個型別的函式是在運行的時候,
6. 運行如下的C代碼,輸出是什么?
char* GetString1() { char p[] = "Hello World";//指向臨時分配的桟空間,當運行至函式體外時,空間將被釋放 return p; } char* GetString2() { char *p = "Hello World";//指向全域常量區 return p; } int _tmain(int argc, _TCHAR* argv[]) { printf("GetString1 returns: %s. \n", GetString1()); printf("GetString2 returns: %s. \n", GetString2()); return 0; }
輸出兩行,第一行GetString1 returns: 后面跟的是一串隨機的內容,而第二行GetString2 returns: Hello World.
兩個函式的區別在于GetString1中是一個陣列,而GetString2中是一個指標,運行到GetString1時,p是一個陣列,會開辟一塊記憶體,并拷貝"Hello World"初始化該陣列,
接著回傳陣列的首地址并退出該函式,由于p是GetString1內的一個區域變數,當運行到這個函式外面的時候,這個陣列的記憶體會被釋放掉,
因此在_tmain函式里再去訪問這個陣列的內容時,結果是隨機的,運行到GetString2時,p是一個指標,它指向的是字串常量區的一個常量字串,該常量字串是一個全域的,并不會因為退出函式GetString2而被釋放掉,
7. 運行下圖中C代碼,輸出的結果是什么?
int _tmain(int argc, _TCHAR* argv[]) { char str1[] = "hello world";//桟空間 char str2[] = "hello world";//桟空間,臨時分配,地址不同 char* str3 = "hello world";//常量區 char* str4 = "hello world";//指向同一塊全域常量區 if(str1 == str2) printf("str1 and str2 are same.\n"); else printf("str1 and str2 are not same.\n"); if(str3 == str4) printf("str3 and str4 are same.\n"); else printf("str3 and str4 are not same.\n"); return 0; }
這個題目與上一題目類似,str1和str2是兩個字串陣列,我們會為它們分配兩個長度為12個位元組的空間,并把"hello world"的內容分別拷貝到陣列中去,
這是兩個初始地址不同的陣列,因此比較str1和str2的值,會不相同,str3和str4是兩個指標,我們無需為它們分配記憶體以存盤字串的內容,而只需要把它們指向"hello world“在記憶體中的地址就可以了,
由于"hello world”是常量字串,它在記憶體中只有一個拷貝,因此str3和str4指向的是同一個地址,因此比較str3和str4的值,會是相同的,
8. 運行Test,輸出結果是什么?
void Test() { class B { public: B(void) { cout<<"B\t"; } ~B(void) { cout<<"~B\t"; } }; struct C { C(void) { cout<<"C\t"; } ~C(void) { cout<<"~C\t"; } }; struct D : B { D() { cout<<"D\t"; } ~D() { cout<<"~D\t"; } private: C c; }; D d; }
運行結果:B C D ~D ~ C ~B,
當實體化D物件時,由于繼承自B,因而首先呼叫B的建構式,之后初始化私有成員C,完成父類的構造與私有成員的初始化后再進入D的建構式體內;之后,按照相反順序完成物件的析構操作,
初始化與賦值是不同的,一般初始化是在初始化串列完成的,建構式體中進行的是賦值操作,
9. 下列程式輸出結果是什么?
class A { public: int a;//4位元組 char b;//1位元組 double c;//8位元組,以此為基本單位進行位元組對齊,上面的兩個變數對齊后共為8位元組,加上當前位元組數,共為8+8=16位元組, virtual void print()//虛函式,構建虛函式表,虛函式表指標需要4位元組,位元組對其,擴充為8位元組 { cout<<"this is father's function!"<<endl; } virtual void print1()//地址存于虛函式表 { cout<<"this is father's function1!"<<endl; } virtual void print2()//無需分配記憶體 { cout<<"this is father's function2!"<<endl; } private: float d;//4位元組,位元組對其,擴充為8位元組 }; class B : A//首先承載A的大小:32位元組 { public: virtual void print()//修改虛函式表地址 { cout<<"this is children's function!"<<endl; } void print1()//僅存有函式入口地址,無需分配記憶體 { cout<<"this is children's function1!"<<endl; } private: char e;//1位元組,位元組對齊,擴充為8位元組(可以發現,繼承后,位元組對齊單位也放生變化) }; int main(void) { cout<<sizeof(A)<<" "<<sizeof(B)<<endl; system("pause"); return 0; }
運行結果:32,40.
這個題目解決的關鍵在于掌握位元組對齊的相關知識點,具體見上面注釋,
10. 以下程式,在編譯與運行時或發生什么?
class A { public: virtual void foo() { } }; class B { public: virtual void foo() { } }; class C : public A , public B { public: virtual void foo() { } }; void bar1(A *pa) { B *pc = dynamic_cast<B*>(pa);//運行期遍歷繼承樹 } void bar2(A *pa) { B *pc = static_cast<B*>(pa);//兩個類無關,編譯出錯 } void bar3() { C c; A *pa = &c; B *pb = static_cast<B*>(static_cast<C*>(pa));//存在繼承關系,編譯正確 }
對于bar1,dynamic_cast是在運行時遍歷繼承樹,所以,在編譯時不會報錯,
但是因為A和B無繼承關系,所以運行時報錯,static_cast:編譯器隱式執行的任何型別轉換都可由它顯示完成,
其中對于:
(1)基本型別,如可以將int轉換為double(編譯器會執行隱式轉換),但是不能將int用它轉換到double(沒有此隱式轉換),
(2)對于用戶自定義型別,如果兩個類無關,則會出錯,如果存在繼承關系,則可以在基類和派生類之間進行任何轉型,在編譯期間不會出錯,所以bar3可以通過編譯,
11. 執行下列程式,會發生什么?
class A { public: string a; void f1() { printf("Hello World"); } void f2() { a = "Hello World"; printf("%s",a.c_str()); } virtual void f3() { a = "Hello World"; printf("%s",a.c_str()); } static void f4() { printf("Hello World"); } }; int main(void) { A *aptr = NULL; //創建一個A物件,物件指標為空,意味著物件僅有空殼,無法借助指標訪問成員變數 aptr->f1(); //運行成功,呼叫f1函式僅需函式入口地址,無需訪問物件中的成員變數 aptr->f2(); //運行失敗,呼叫f2需訪問成員變數 aptr->f3(); //運行失敗,同上 aptr->f4(); //靜態成員不屬于任何物件,運行成功 return 0; }
此題解答如程式注釋所示,
12. 下列函式運行情況如何?
int func() { char b[2]={0}; strcpy(b,"aaa"); }
Debug版崩潰,Release版正常,因為在Debug中有ASSERT斷言保護,所以要崩潰,而在Release中就會刪掉ASSERT,所以正常運行,但是不推薦這么做,因為這樣會覆寫不屬于自己的記憶體,
看到這里是不是又學到了很多新知識呢~
如果你很想學編程,小編推薦我的C語言/C++編程學習基地【點擊進入】!
都是學編程小伙伴們,帶你入個門還是簡簡單單啦,一起學習,一起加油~
還有許多學習資料和視頻,相信你會喜歡的!
涉及:游戲開發、常用軟體開發、編程基礎知識、課程設計、黑客等等......


轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/596.html
標籤:其他
上一篇:全面復工后的就業難!招聘會汗水總結:計算機專業大學生求職秘籍
下一篇:2020面試Java要點
