C/C++求職寶典21個重點筆記(常考筆試面試點)

Alexia(minmin)發表於2014-11-28

這是我之前準備找工作時看《C/C++求職寶典》一書做的筆記,都是一些筆試面試中常考的重點難點問題,但比較基礎,適合初學者看。

 

1. char c = '\72'; 中的\72代表一個字元,72是八進位制數,代表ASCII碼字元“:”。

2. 10*a++ 中a先進行乘法運算再自增(筆試中經常喜歡出這類運算子優先順序容易混淆的輸出問題)。

3. const和static的作用
太常見的問題了,下面給出一個較詳細的參考答案:
static關鍵字:
1)函式體內static變數的作用範圍為函式體。不同於auto變數。該變數的記憶體只被分配一次。因此其值在下次呼叫時仍維持上次的值。
2)在模組內的static全域性變數可以被模組內的所有函式訪問。但不能被模組外的其他函式訪問。
3)在模組內的static函式只可被這一模組內的其它函式呼叫。這個函式的使用範圍被限制在宣告它的模組內。
4)在類中的static成員變數屬於整個類所有,對類的所有物件只有一份複製。
5)在類中的static成員函式屬於整個類所有,這個函式不接受this指標,因而只能訪問類的static成員變數。
const關鍵字:
1)欲阻止一個變數被改變,可以使用const關鍵字。在定義該const變數時,通常需要對它進行初始化。因為以後就沒有機會再改變它了。
2)對指標來說,可以指定指標的本身為const,也可以指定指標所指向的數為const。或二者同時為const。
3)在一個函式的宣告中,const可以修飾形參,表明它是一個輸入引數。在函式內不能改變其值。
4)對於類的成員函式,若指定其為const型別。則表明其是一個常量函式。不能修改類的成員變數。
5)對於類的成員函式,有時候必須指定其返回值為const型別。以使得其返回值不為“左值”。
 
4. 注意sizeof不是函式而是運算子,所以在計算變數所佔用空間大小時,括號是可以省略的,但在計算型別大小時括號則不能省略,比如int i = 0; 則sizeof int是錯誤的。
 
5. 有1,2,…,n的無序陣列,求排序演算法,並且要求時間複雜度為O(n),空間複雜度O(1),使用交換,而且一次只能交換兩個數。
#include <stdio.h>
int main() {
    int a[] = {10, 6, 9, 5, 2, 8, 4, 7, 1, 3};
    int i, tmp;
    int len = sizeof(a) / sizeof(a[0]);
    for(i = 0; i < len;) {
        tmp = a[a[i] - 1];
        a[a[i] - 1] = a[i];
        a[i] = tmp;
        if(a[i] == i + 1) i++;
    }
    for(i = 0; i < len; ++i)
        printf("%d ", a[i]);
    printf("\n");
    return 0;
}

 

6. 易誤解:如果int a[5], 那麼a與&a是等價的,因為兩者地址相同。
解答:一定要注意a與&a是不一樣的,雖然兩者地址相同,但意義不一樣,&a是整個陣列物件的首地址,而a是陣列首地址,也就是a[0]的地址,a的型別是int[5],a[0]的型別是int,因此&a+1相當於a的地址值加上sizeof(int) * 5,也就是a[5],下一個物件的地址,已經越界了,而a+1相當於a的地址加上sizeof(int),即a[1]的地址。
 
7. 如何將一個小數分解成整數部分和小數部分?
要記得利用標頭檔案中的庫函式modf,下面是函式原型(記住一些實用的庫函式,避免自己重寫):
double modf(double num, double *i); // 將num分解為整數部分*i和小數部分(返回值決定)

 

8. 可作為函式過載判斷依據的有:引數個數、引數型別、const修飾符;
   不可以作為過載判斷依據的有:返回型別。
 
9. 程式輸出題:
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p = &(a + 1)[3];
printf("%d\n", *p);

輸出:5

說明:因為a+1指向a的第二個元素,[3]表示再向後移動3個元素。
 
10. 程式輸出題:
 char str1[] = "abc";
 char str2[] = "abc";
 const char str3[] = "abc";
 const char str4[] = "abc";
 const char *str5 = "abc";
 const char *str6 = "abc";
 char *str7 = "abc";
 char *str8 = "abc";
 cout << (str1 == str2) << endl;
 cout << (str3 == str4) << endl;
 cout << (str5 == str6) << endl;
 cout << (str7 == str8) << endl;

輸出:0 0 1 1

說明:輸出str1~str8的地址為:
0x23aa80
0x23aa70
0x23aa60
0x23aa50
0x23aa48
0x23aa40
0x23aa38
0x23aa30
輸出str1~str8內容“abc”的儲存地址為:
0x23aa80
0x23aa70
0x23aa60
0x23aa50
0x100403030
0x100403030
0x100403030
0x100403030
可以發現str1~str4中的內容是存在棧上,地址各不相同,而str5~str8的內容都是儲存在常量區,所以地址都相同。
 
注意:
char *str = "abc";
printf("%p\n", str1);
cout << &str1 << endl;

上面列印的是字串 “abc”的地址,下面列印的是 str1 變數的地址。

 
11. C的結構體和C++結構體的區別
(1)C的結構體內不允許有函式存在,C++允許有內部成員函式,且允許該函式是虛擬函式。所以C的結構體是沒有建構函式、解構函式、和this指標的。
(2)C的結構體對內部成員變數的訪問許可權只能是public,而C++允許public,protected,private三種。
(3)C語言的結構體是不可以繼承的,C++的結構體是可以從其他的結構體或者類繼承過來的。
 
以上都是表面的區別,實際區別就是程式導向和麵向物件程式設計思路的區別:
C的結構體只是把資料變數給包裹起來了,並不涉及演算法。
而C++是把資料變數及對這些資料變數的相關演算法給封裝起來,並且給對這些資料和類不同的訪問許可權。
C語言中是沒有類的概念的,但是C語言可以通過結構體內建立函式指標實現物件導向思想。
 
12. 如何在類中定義常量成員併為其初始化?
解答:只能在初始化列表裡對const成員初始化,像下面這樣:
class CBook {
public:
    const double m_price;
    CBook() :m_price(8.8) { }
};

下面的做法是錯誤的:

class CBook {
public:
    const double m_price;
    CBook() {
        m_price = 8.8;
    }
};

而下面的做法雖未報錯,但有個warning,也不推薦:

class CBook {
public:
    const double m_price = 8.8; // 注意這裡若沒有const則編譯出錯
    CBook() { }
};

 

13. 在定義類的成員函式時使用mutable關鍵字的作用是什麼?
解答:當需要在const方法中修改物件的資料成員時,可以在資料成員前使用mutable關鍵字,防止出現編譯出錯。例子如下:
class CBook {
public:
    mutable double m_price; // 如果不加就會出錯
    CBook(double price) :m_price(price) { }
    double getPrice() const; // 定義const方法
};
double CBook::getPrice() const {
    m_price = 9.8;
    return m_price;
}

 

14. 建構函式、拷貝建構函式、解構函式的呼叫點和順序問題,如下面這個例子輸出是什麼?
class CBook {
public:
    CBook() {
        cout << "constructor is called.\n";
    }
    ~CBook() {
        cout << "destructor is called.\n";
    }
};
 
void invoke(CBook book) { // 物件作為函式引數,如果這裡加了個&就不是了,因為加了&後是引用方式傳遞,形參和實參指向同一塊地
                          // 址,就不需要建立臨時物件,也就不需要呼叫拷貝建構函式了
    cout << "invoke is called.\n";
}
 
int main() {
    CBook c;
    invoke(c);
}

解答:注意拷貝建構函式在物件作為函式引數傳遞時被呼叫,注意是物件例項而不是物件引用。因此該題輸出如下:

constructor is called.
invoke is called.
destructor is called. // 在invoke函式呼叫結束時還要釋放拷貝建構函式建立的臨時物件,因此這裡還呼叫了個解構函式
destructor is called.

 

引申:拷貝建構函式在哪些情況下被呼叫?
(1)函式的引數為類物件且引數採用值傳遞方式;
(2)將類物件做為函式的返回值。
 
15. C++中的explicit關鍵字有何作用?
解答:禁止將建構函式作為轉換函式,即禁止建構函式自動進行隱式型別轉換。
例如CBook中只有一個引數m_price,在構建物件時可以使用CBook c = 9.8這樣的隱式轉換,使用explicit防止這種轉換髮生。
 
16. 在C++中,如果確定了某一個建構函式的建立過程,在該建構函式中如果呼叫了其它過載的建構函式,它將不會執行其它建構函式的初始化列表部分程式碼,而是執行函式體程式碼,此時已經退化成普通函式了。例子說明如下:
class CBook {
public:
    double m_price;
    CBook() {
        CBook(8.8);
    }
    CBook(double price) : m_price(price) { }
};
int main() {
    CBook c;
    cout << c.m_price << endl; // 此時並不會輸出理想中的8.8
}

 

17. 靜態資料成員只能在全域性區域進行初始化,而不能在類體中進行(建構函式中初始化也不行),且靜態資料成員不涉及物件,因此不受類訪問限定符的限制。
例子說明如下:
class CBook {
public:
    static double m_price;
};
double CBook::m_price = 8.8; // 只能在這初始化,不能在CBook的建構函式或直接初始化

 

18. C++中可以過載的運算子:new/delete、new[]/delete[]、++等。
    不可以過載的運算子:、.、::、?:、sizeof、typeid、.、**、不能改變運算子的優先順序。
 
引申:過載++和–時是怎麼區分字首++和字尾++的?
例如當編譯器看到++a(先自增)時,它就呼叫operator++(a);
但當編譯器看到a++時,它就呼叫operator++(a, int)。即編譯器通過呼叫不同的函式區別這兩種形式。
 
19. C++的多型性分為靜態多型和動態多型。
靜態多型性:編譯期間確定具體執行哪一項操作,主要是通過函式過載和運算子過載來實現的;
動態多型性:執行時確定具體執行哪一項操作,主要是通過虛擬函式來實現的。
 
20. 虛擬函式原理考點,例如下面程式的輸出是什麼?
class A {
public:
    virtual void funa();
    virtual void funb();
    void func();
    static void fund();
    static int si;
private:
    int i;
    char c;
};

問:sizeof(A) = ?

解答:
關於類佔用的記憶體空間,有以下幾點需要注意:
(1)如果類中含有虛擬函式,則編譯器需要為類構建虛擬函式表,類中需要儲存一個指標指向這個虛擬函式表的首地址,注意不管有幾個虛擬函式,都只建立一張表,所有的虛擬函式地址都存在這張表裡,類中只需要一個指標指向虛擬函式表首地址即可。
(2)類中的靜態成員是被類所有例項所共享的,它不計入sizeof計算的空間
(3)類中的普通函式或靜態普通函式都儲存在棧中,不計入sizeof計算的空間
(4)類成員採用位元組對齊的方式分配空間
答案:12(32位系統)或16(64位系統)
 
21. 虛繼承的作用是什麼?
在多繼承中,子類可能同時擁有多個父類,如果這些父類還有相同的父類(祖先類),那麼在子類中就會有多份祖先類。例如,類B和類C都繼承與類A,如果類D派生於B和C,那麼類D中就會有兩份A。為了防止在多繼承中子類存在重複的父類情況,可以在父類繼承時使用虛擬函式,即在類B和類C繼承類A時使用virtual關鍵字,例如:
class B : virtual public A
class C : virtual public A
注:因為多繼承會帶來很多複雜問題,因此要慎用。

相關文章