歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 10道C++輸出易錯筆試題收集

10道C++輸出易錯筆試題收集

日期:2017/3/1 9:35:10   编辑:Linux編程

下面這些題目都是我之前准備筆試面試過程中積累的,大部分都是知名公司的筆試題,C++基礎薄弱的很容易栽進去。我從中選了10道簡單的題,C++初學者可以進來挑戰下,C++大牛也可以作為娛樂玩下(比如下面的第6題)。為了便於大家思考,將題目與答案分開,不過無論題目本身如何,我覺得後面的解析過程更值得學習,因為涉及很多我們學習C++過程中必知必會的小知識點 。

第一部分:題目

  1. 如下函數,在32 bit系統foo(2^31-3)的值是:()

     int foo(int x)
     {
         return x&-x;
     }

    A:0 B: 1 C: 2 D: 4

  2. 運算符優先級
    unsigned char i=0x80;
    printf("0x%x\n", ~i>>3+1);
    輸出什麼?

  3. 靜態對象是否調用構造函數?

     #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;
     }

  4. 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;
     }

  5. 下面代碼會報錯嗎?為什麼?

    class A {
    public:
      int m;
      void print() {  cout << "A\n";  } 
    };
    A *pa = 0;
    pa->print();

  6. 下面代碼的輸出是什麼?(非常考基礎水平的一道題)

    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;  
    }

  7. 結構體

    #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;
    }

  8. 改變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;
    }

  9. 靜態變量的輸出

    #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;
    }

  10. 返回值加const修飾的必要性
    你覺得下面兩種寫法有區別嗎?

    int GetInt(void) 
    const int GetInt(void)

    如果是下面的呢?其中A 為用戶自定義的數據類型。

    A GetA(void)
    const A GetA(void) 

第二部分:答案詳細解析
  1. 如下函數,在32 bit系統foo(2^31-3)的值是:

     int foo(int x)
     {
         return x&-x;
     }

    A:0 B: 1 C: 2 D: 4

    答案:C
    解釋:我只想說注意運算符優先級,注意^是異或而不是冪次方。

  2. 運算符優先級
    unsigned char i=0x80;
    printf("0x%x\n", ~i>>3+1);
    輸出什麼?

    輸出:0xfffffff7(提示:+的優先級優於>>)
    如果將unsigned去掉,則輸出0x7。

  3. 靜態對象是否調用構造函數?

    #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

  4. 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的大小為其內部所有變量的最大值,並且按照類型最大值的整數倍進行內存對齊。

  5. 下面代碼會報錯嗎?為什麼?

    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();

    也就是:並不是類沒有初始化就不能調用類的成員函數,如果成員函數只是簡單的打印個東西,沒有調用類成員啥的就不會報段錯誤。

  6. 下面代碼的輸出是什麼?(非常考基礎水平的一道題)

    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"。

  7. 結構體

    #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

    說明:一般變量都是從高到低分配內存地址,但對於結構體來說,結構體的成員在內存中順序存放,所占內存地址依次增高,第一個成員處於低地址處,最後一個成員處於最高地址處,但結構體成員的內存分配不一定是連續的,編譯器會對其成員變量依據前面介紹的 “對齊”原則進行處理。

    補充知識點:

    除了棧以外,堆、只讀數據區、全局變量地址增長方向都是從低到高的。

  8. 改變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;
    }

  9. 靜態變量的輸出

    #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
    解釋:存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化,此後該初始化不再執行,相當於一次執行後就作廢,靜態局部變量保存了前次被調用後留下的值。

  10. 返回值加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&修飾),這樣可以獲得編譯器的幫助,以便寫出健壯性的代碼

C++ Primer Plus 第6版 中文版 清晰有書簽PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm

讀C++ Primer 之構造函數陷阱 http://www.linuxidc.com/Linux/2011-08/40176.htm

讀C++ Primer 之智能指針 http://www.linuxidc.com/Linux/2011-08/40177.htm

讀C++ Primer 之句柄類 http://www.linuxidc.com/Linux/2011-08/40175.htm

將C語言梳理一下,分布在以下10個章節中:

  1. Linux-C成長之路(一):Linux下C編程概要 http://www.linuxidc.com/Linux/2014-05/101242.htm
  2. Linux-C成長之路(二):基本數據類型 http://www.linuxidc.com/Linux/2014-05/101242p2.htm
  3. Linux-C成長之路(三):基本IO函數操作 http://www.linuxidc.com/Linux/2014-05/101242p3.htm
  4. Linux-C成長之路(四):運算符 http://www.linuxidc.com/Linux/2014-05/101242p4.htm
  5. Linux-C成長之路(五):控制流 http://www.linuxidc.com/Linux/2014-05/101242p5.htm
  6. Linux-C成長之路(六):函數要義 http://www.linuxidc.com/Linux/2014-05/101242p6.htm
  7. Linux-C成長之路(七):數組與指針 http://www.linuxidc.com/Linux/2014-05/101242p7.htm
  8. Linux-C成長之路(八):存儲類,動態內存 http://www.linuxidc.com/Linux/2014-05/101242p8.htm
  9. Linux-C成長之路(九):復合數據類型 http://www.linuxidc.com/Linux/2014-05/101242p9.htm
  10. Linux-C成長之路(十):其他高級議題

Copyright © Linux教程網 All Rights Reserved