下面這些題目都是我之前準備筆試面試過程中積累的,大部分都是知名公司的筆試題,C++基礎薄弱的很容易栽進去。我從中選了10道簡單的題,C++初學者可以進來挑戰下,C++大牛也可以作為娛樂玩下(比如下面的第6題)。為了便於大家思考,將題目與答案分開,不過無論題目本身如何,我覺得後面的解析過程更值得學習,因為涉及很多我們學習C++過程中必知必會的小知識點 。
第一部分:題目
-
如下函式,在32 bit系統foo(2^31-3)的值是:()
int foo(int x) { return x&-x; }
A:0 B: 1 C: 2 D: 4
-
運算子優先順序
unsigned char i=0x80;
printf("0x%x\n", ~i>>3+1);
輸出什麼? -
靜態物件是否呼叫建構函式?
#include <iostream> using namespace std; class A { public: A() { cout << "A's Constructor Called " << endl; } }; class B { static A a; public: B() { cout << "B's Constructor Called " << endl; } }; int main() { B b; return 0; }
-
union問題
#include <stdio.h> union { int i; char x[2]; }a; int main() { a.x[0] = 10; a.x[1] = 1; printf("%d",a.i); return 0; }
-
下面程式碼會報錯嗎?為什麼?
class A { public: int m; void print() { cout << "A\n"; } }; A *pa = 0; pa->print();
-
下面程式碼的輸出是什麼?(非常考基礎水平的一道題)
char *c[] = {"ENTER","NEW","POINT","FIRST"}; char **cp[] = { c + 3 , c + 2 , c + 1 , c}; char ***cpp = cp; int main(void) { printf("%s",**++cpp); printf("%s",*--*++cpp+3); printf("%s",*cpp[-2]+3); printf("%s\n",cpp[-1][-1]+1); return 0; }
-
結構體
#include <stdio.h> struct data { int a; unsigned short b; }; int main(void) { data mData; mData.b = 0x0102; char *pData = (char *)&mData; printf("%d %d", sizeof(pData), (int)(*(pData + 4))); return 0; }
-
改變string變數的值?
#include <iostream> #include <string> using namespace std; void chg_str(string str) { str = "ichgit"; } int main() { string s = "sarrr"; chg_str(s); printf("%s\n", s.c_str()); cout << s << endl; return 0; }
-
靜態變數的輸出
#include <stdio.h> int sum(int a) { int c = 0; static int b = 3; // 只執行一次 c++; b += 2; return (a + b + c); } int main() { int i; int a = 2; for(i = 0; i < 5; ++i) { printf("%d\n", sum(a)); } return 0; }
-
返回值加const修飾的必要性
你覺得下面兩種寫法有區別嗎?int GetInt(void) const int GetInt(void)
如果是下面的呢?其中A 為使用者自定義的資料型別。
A GetA(void) const A GetA(void)
第二部分:答案詳細解析
-
如下函式,在32 bit系統foo(2^31-3)的值是:
int foo(int x) { return x&-x; }
A:0 B: 1 C: 2 D: 4
答案:C
解釋:我只想說注意運算子優先順序,注意^是異或而不是冪次方。 -
運算子優先順序
unsigned char i=0x80;
printf("0x%x\n", ~i>>3+1);
輸出什麼?
輸出:0xfffffff7(提示:+的優先順序優於>>)
如果將unsigned去掉,則輸出0x7。 -
靜態物件是否呼叫建構函式?
#include <iostream> using namespace std; class A { public: A() { cout << "A's Constructor Called " << endl; } }; class B { static A a; public: B() { cout << "B's Constructor Called " << endl; } }; int main() { B b; return 0; }
輸出:
B's Constructor Called
解釋:上面的程式只是呼叫了B的建構函式,沒有呼叫A的建構函式。因為靜態成員變數只是在類中宣告,沒有定義。靜態成員變數必須在類外使用作用域識別符號顯式定義。
如果我們沒有顯式定義靜態成員變數a,就試圖訪問它,編譯會出錯,比如下面的程式編譯出錯:#include <iostream> using namespace std; class A { int x; public: A() { cout << "A's constructor called " << endl; } }; class B { static A a; public: B() { cout << "B's constructor called " << endl; } static A getA() { return a; } }; int main() { B b; A a = b.getA(); return 0; }
輸出:
Compiler Error: undefined reference to `B::a
如果我們加上a的定義,那麼上面的程式可以正常執行,
注意:如果A是個空類,沒有資料成員x,則就算B中的a未定義也還是能執行成功的,即可以訪問A。#include <iostream> using namespace std; class A { int x; public: A() { cout << "A's constructor called " << endl; } }; class B { static A a; public: B() { cout << "B's constructor called " << endl; } static A getA() { return a; } }; A B::a; // definition of a int main() { B b1, b2, b3; A a = b1.getA(); return 0; }
輸出:
A's constructor called
B's constructor called
B's constructor called
B's constructor called上面的程式呼叫B的建構函式3次,但是隻呼叫A的建構函式一次,因為靜態成員變數被所有物件共享,這也是它被稱為類變數的原因。同時,靜態成員變數也可以通過類名直接訪問,比如下面的程式沒有通過任何類物件訪問,只是通過類訪問a。
int main() { // static member 'a' is accessed without any object of B A a = B::getA(); return 0; }
輸出:
A's constructor called
-
union問題
#include <stdio.h> union { int i; char x[2]; }a; int main() { a.x[0] = 10; a.x[1] = 1; printf("%d",a.i); return 0; }
輸出:266,自己畫個記憶體結構圖就知道了,注意union的存放順序是所有成員都從低地址開始存放。Union的大小為其內部所有變數的最大值,並且按照型別最大值的整數倍進行記憶體對齊。
-
下面程式碼會報錯嗎?為什麼?
class A { public: int m; void print() { cout << "A\n"; } }; A *pa = 0; pa->print();
答案:正常輸出。上面的程式碼可以這樣理解(這非常重要):
void print(A *this) { cout << "A\n"; } A *pa = 0; print_A();
也就是:並不是類沒有初始化就不能呼叫類的成員函式,如果成員函式只是簡單的列印個東西,沒有呼叫類成員啥的就不會報段錯誤。
-
下面程式碼的輸出是什麼?(非常考基礎水平的一道題)
char *c[] = {"ENTER","NEW","POINT","FIRST"}; char **cp[] = { c + 3 , c + 2 , c + 1 , c}; char ***cpp = cp; int main(void) { printf("%s",**++cpp); printf("%s",*--*++cpp+3); printf("%s",*cpp[-2]+3); printf("%s\n",cpp[-1][-1]+1); return 0; }
解答:
c是一個指標陣列,每個陣列元素都是char*型別的指標,值分別是那些字串(的首地址):c[0] = "ENTER"
c[1] = "NEW"
c[2] = "POINT"
c[3] = "FIRST"而[]和*是本質一樣的運算,即
c[i]=*(c+i)
。c和c+i都是char *[]型別,它可以退化成char **型別,再看cp,它正好是一個char **的陣列,來看它的值:
cp[0] = c + 3
cp[1] = c + 2
cp[2] = c + 1
cp[3] = c引用後就有:
cp[0][0]=*(c + 3)=c[3]="FIRST"
,以此類推。cp是char **[]型別,它可以退化成char ***型別,看最後的cpp,它正是char ***型別,它是一個指標變數,和上面兩個不同,上面兩個是陣列。
這樣分析過後,下面的解析就一目瞭然了:
printf("%s",**++cpp);
++cpp的值是cp+1,引用一次後是cp[1]再引用是*cp[1]=c[2]="POINT",第一句的輸出printf("%s",*--*++cpp+3);
再++cpp的值是cp+2,引用一次是cp[2]=c+1,再對這進行--,減後是c再引用是c[0]="ENTER"再+3,字串指標指到"ER",輸出是"ER"printf("%s",*cpp[-2]+3);
這時cpp的值是cp+2,cpp[-2]=*(cpp-2)=*(cp+2-2)=cp[0]=c+3,再引用是c[3]="FIRST",+3 字串指標指到"ST",輸出是"ST"printf("%s\n",cpp[-1][-1]+1);
cpp還是cp+2,cpp[-1]=*(cpp-1)=*(cp+2-1)=cp[1]=c+2,再[-1]得*(c+2-1)=c[1]="NEW",+1字串指標指到"EW",輸出是"EW"。
-
結構體
#include <stdio.h> struct data { int a; unsigned short b; }; int main(void) { data mData; mData.b = 0x0102; char *pData = (char *)&mData; printf("%d %d", sizeof(pData), (int)(*(pData + 4))); return 0; }
輸出:4 2
說明:一般變數都是從高到低分配記憶體地址,但對於結構體來說,結構體的成員在記憶體中順序存放,所佔記憶體地址依次增高,第一個成員處於低地址處,最後一個成員處於最高地址處,但結構體成員的記憶體分配不一定是連續的,編譯器會對其成員變數依據前面介紹的 “對齊”原則進行處理。
補充知識點:
除了棧以外,堆、只讀資料區、全域性變數地址增長方向都是從低到高的。
-
改變string變數的值?
#include <iostream> #include <string> using namespace std; void chg_str(string str) { str = "ichgit"; } int main() { string s = "sarrr"; chg_str(s); printf("%s\n", s.c_str()); cout << s << endl; return 0; }
輸出:仍為“sarrr”。
解釋:string是傳值引數,不能修改其值。要想改變string變數的值,可以改為傳地址方式:#include <iostream> #include <string> using namespace std; void chg_str(string *str) { *str = "ichgit"; } int main() { string s = "sarrr"; chg_str(&s); printf("%s\n", s.c_str()); cout << s << endl; return 0; }
-
靜態變數的輸出
#include <stdio.h> int sum(int a) { int c = 0; static int b = 3; // 只執行一次 c++; b += 2; return (a + b + c); } int main() { int i; int a = 2; for(i = 0; i < 5; ++i) { printf("%d\n", sum(a)); } return 0; }
輸出:8 10 12 14 16
解釋:儲存在靜態資料區的變數會在程式剛開始執行時就完成初始化,也是唯一的一次初始化,此後該初始化不再執行,相當於一次執行後就作廢,靜態區域性變數儲存了前次被呼叫後留下的值。 -
返回值加const修飾的必要性
你覺得下面兩種寫法有區別嗎?int GetInt(void) const int GetInt(void)
如果是下面的呢?其中A 為使用者自定義的資料型別。
A GetA(void) const A GetA(void)
答案:沒有任何區別。
解釋:如果函式返回值採用“值傳遞方式”,由於函式會把返回值複製到外部臨時的儲存單元中,加const 修飾沒有任何價值。所以,對於值傳遞來說,加const沒有太多意義。
所以:- 不要把函式int GetInt(void) 寫成const int GetInt(void)。
- 不要把函式A GetA(void) 寫成const A GetA(void)。
在程式設計中要儘可能多的使用const(比如函式引數採用const&修飾),這樣可以獲得編譯器的幫助,以便寫出健壯性的程式碼。