概述#

好的面試官不會要求你用一個你不熟悉的語言編寫程式碼。如果 C++ 列在你的履歷上,你才可能被問到這類題目。即使記不住所有 API,大多數面試官也不會太在意,但建議熟悉基本的 C++ 語法。


類別與繼承(Classes and Inheritance)#

C++ 的類別與其他語言類似,但有一些語法差異。以下範例展示基本類別實作與繼承:

#include <iostream>
using namespace std;

#define NAME_SIZE 50 // Defines a macro

class Person {
    int id;           // all members are private by default
    char name[NAME_SIZE];

  public:
    void aboutMe() {
        cout << "I am a person.";
    }
};

class Student : public Person {
  public:
    void aboutMe() {
        cout << "I am a student.";
    }
};

int main() {
    Student * p = new Student();
    p->aboutMe(); // prints "I am a student."
    delete p;     // Important! Make sure to delete allocated memory.
    return 0;
}

C++ 中,所有資料成員和方法預設是 private。需要使用 public 關鍵字才能對外開放存取。


建構子與解構子(Constructors and Destructors)#

建構子(Constructor) 在物件建立時自動呼叫。若未定義,編譯器會自動產生預設建構子。

初始化基本型別的兩種寫法:

// 寫法一:在建構子主體中賦值
Person(int a) {
    id = a;
}

// 寫法二:使用初始化列表(Initializer List)
Person(int a) : id(a) {
    ...
}

初始化列表(id(a) 的寫法)會在物件建立前、建構子主體執行前完成賦值。當欄位為 const 或類別型別時,必須使用此寫法。

解構子(Destructor) 在物件銷毀時自動呼叫,負責釋放資源。解構子不接受參數:

~Person() {
    delete obj; // free any memory allocated within class
}

虛擬函式(Virtual Functions)#

考慮以下情境:

// 使用 Student* 型別
Student * p = new Student();
p->aboutMe(); // 呼叫 Student::aboutMe()

// 使用 Person* 型別
Person * p = new Student();
p->aboutMe(); // 呼叫 Person::aboutMe() ← 這不是我們想要的!

當使用父類別指標指向子類別物件時,若未宣告 virtual,方法呼叫會在編譯期決定(static binding),呼叫的是父類別的實作。

使用 virtual 關鍵字可以啟用動態綁定(dynamic binding)

class Person {
    ...
    virtual void aboutMe() {
        cout << "I am a person.";
    }
};

class Student : public Person {
  public:
    void aboutMe() {
        cout << "I am a student.";
    }
};

純虛擬函式(Pure Virtual Function)#

若父類別的方法沒有合理的預設實作,可以將其宣告為純虛擬函式

class Person {
    int id;
    char name[NAME_SIZE];
  public:
    virtual void aboutMe() {
        cout << "I am a person." << endl;
    }
    virtual bool addCourse(string s) = 0; // 純虛擬函式
};

宣告純虛擬函式後,Person 成為抽象類別,無法直接實例化。子類別必須提供實作。


虛擬解構子(Virtual Destructor)#

當使用父類別指標指向子類別物件,並刪除該物件時,若解構子不是 virtual,只有父類別的解構子會被呼叫,導致子類別的資源無法被清理。

有問題的版本:

class Person {
  public:
    ~Person() {
        cout << "Deleting a person." << endl;
    }
};

class Student : public Person {
  public:
    ~Student() {
        cout << "Deleting a student." << endl;
    }
};

int main() {
    Person * p = new Student();
    delete p; // 只印出 "Deleting a person.",Student 的記憶體可能未被清理
}

正確做法:將父類別解構子宣告為 virtual:

class Person {
  public:
    virtual ~Person() {
        cout << "Deleting a person." << endl;
    }
};

這樣 delete p 會依序輸出:

Deleting a student.
Deleting a person.

預設值(Default Values)#

函式可以為參數指定預設值。所有有預設值的參數必須位於參數列表的右側:

int func(int a, int b = 3) {
    x = a;
    y = b;
    return a + b;
}

w = func(4);    // b 使用預設值 3,w = 7
z = func(4, 5); // z = 9

運算子多載(Operator Overloading)#

運算子多載讓我們可以對自訂物件使用 + 等運算子:

BookShelf BookShelf::operator+(BookShelf &other) { ... }

指標與參考(Pointers and References)#

指標(Pointer)#

指標保存一個變數的記憶體位址,可以用來存取和修改該變數:

int * p = new int;
*p = 7;
int * q = p;
*p = 8;
cout << *q; // prints 8(p 和 q 指向同一記憶體位址)

指標的大小在 32 位元機器上為 32 bits,64 位元機器上為 64 bits。面試中可能會問某個資料結構佔用多少空間。

參考(Reference)#

參考是已存在物件的別名,不佔有自己的記憶體:

int a = 5;
int & b = a;
b = 7;
cout << a; // prints 7(修改 b 就是修改 a)

參考與指標的主要差異:

  • 參考不能為 null
  • 參考一旦初始化,不能指向另一個物件

指標算術(Pointer Arithmetic)#

int * p = new int[2];
p[0] = 0;
p[1] = 1;
p++;
cout << *p; // Outputs 1

p++ 會讓指標向前移動 sizeof(int) 個 bytes。如果指標型別不同,則跳過的 bytes 也不同。


模板(Templates)#

模板可以讓同一段程式碼適用於不同的資料型別,實現程式碼重用:

template <class T>class ShiftedList {
    T* array;
    int offset, size;
  public:
    ShiftedList(int sz) : offset(0), size(sz) {
        array = new T[size];
    }

    ~ShiftedList() {
        delete [] array;
    }

    void shiftBy(int n) {
        offset = (offset + n) % size;
    }

    T getAt(int i) {
        return array[convertIndex(i)];
    }

    void setAt(T item, int i) {
        array[convertIndex(i)] = item;
    }

  private:
    int convertIndex(int i) {
        int index = (i - offset) % size;
        while (index < 0) index += size;
        return index;
    }
};

面試題目列表#

題號題目
12.1Last K Lines:用 C++ 印出輸入檔案的最後 K 行
12.2Reverse String:實作 void reverse(char* str) 反轉字串
12.3Hash Table vs. STL Map:比較 Hash Table 和 STL Map 的差異
12.4Virtual Functions:virtual function 在 C++ 中如何運作?
12.5Shallow vs. Deep Copy:深拷貝與淺拷貝的差異及使用時機
12.6Volatile:C 中 volatile 關鍵字的意義
12.7Virtual Base Class:為何基底類別的解構子需要宣告為 virtual?
12.8Copy Node:實作方法,複製含有兩個 Node 指標的資料結構
12.9Smart Pointer:實作一個 smart pointer 類別
12.10Malloc:實作對齊的 malloc,使回傳的記憶體位址能被指定的 2 的冪次整除
12.112D Alloc:用 C 實作 my2DAlloc,以最少的 malloc 呼叫分配二維陣列