1. 程式人生 > >C++基礎知識總結

C++基礎知識總結

1

面試C++程式設計師的時候一般都是3板斧,先是基礎問答,然後一頓虛擬函式、虛擬函式表、純虛擬函式、抽象類、虛擬函式和解構函式、虛擬函式和建構函式。接著拷貝建構函式、操作符過載、下面是STL,最後是智慧指標。
       都能挺過去那基本知識這關應該算是過了,下面就是專案背景和演算法了。

     1,C++和C相比最大的特點
                   1)面向物件:封裝,繼承,多型。
                   2)引入引用代替指標。
                   3)const /inline/template替代巨集常量。
                   4)namespace解決重名的問題。
                   5)STL提供高效的資料結構和演算法
     
     2,你知道虛擬函式嗎
     答案:實現多型所必須,父類型別的指標指向子類的例項,執行的時候會執行之類中定義的函式。
     
     3,解構函式可以是虛擬函式嗎?
     答案: 如果有子類的話,解構函式必須是虛擬函式。否則析構子類型別的指標時,解構函式有可能不會被呼叫到。

     4,多型的實現。
     答案:簡而言之編譯器根據虛擬函式表找到恰當的虛擬函式。對於一個父類的物件指標型別變數,如果給他賦父類物件的指標,那麼他就呼叫父類中的函式,如果給他賦子類物件的指標,他就呼叫子類中的函式。函式執行之前查表。

     5,虛擬函式表是針對類還是針對物件的?
     答案:虛擬函式表是針對類的,一個類的所有物件的虛擬函式表都一樣。
     
     6,純虛擬函式和虛擬函式有什麼區別
     答案:純虛擬函式就是定義了一個虛擬函式但並沒有實現,原型後面加"=0"。包含純虛擬函式的類都是抽象類,不能生成例項。

     7,建構函式可以是虛擬函式嗎?
     答案:每個物件的虛擬函式表指標是在建構函式中初始化的,因為建構函式沒執行完,所以虛擬函式表指標還沒初始化好,建構函式的虛擬函式不起作用。

     8,建構函式中可以呼叫虛擬函式嗎?
     答案:就算呼叫虛擬函式也不起作用,呼叫虛擬函式同調用一般的成員函式一樣。

     9,解構函式中可以呼叫虛擬函式嗎?
     答案:解構函式中呼叫虛擬函式也不起作用,呼叫虛擬函式同調用一般的成員函式一樣。解構函式的順序是先派生類後基類,有可能內容已經被析構沒了,所以虛擬函式不起作用。

     10,虛繼承和虛基類?
     答案:虛繼承是為了解決多重繼承出現菱形繼承時出現的問題。例如:類B、C分別繼承了類A。類D多重繼承類B和C的時候,類A中的資料就會在類D中存在多份。通過宣告繼承關係的時候加上virtual關鍵字可以實現虛繼承。

2

實現一個自己的String類是一道考驗C++基礎知識的好題。

至少要能實現以下:建構函式,解構函式,拷貝建構函式(copy constructor),過載賦值操作符(copy assignment operator),。

首先是至少能夠準確的寫出這幾個函式的宣告。

class String {
public: 
       String();
       String(const char *);
       //舊寫法:
       //String(const String& rhs);
       //String& operator=(const String& rhs);
       //新寫法:
       String(String rhs);
       String& operator=(String rhs);
       ~String();
private:
       char* data_;
}

其次,老版本的拷貝建構函式和過載賦值操作符時:有幾點需要注意的是:判斷自己賦值給自己 和 異常安全性。
通過使用swap可以簡化方法。
下面是老版本的拷貝建構函式的實現,new的時候有可能會丟擲異常。

String::String(const String& rhs) {
    if (&rhs!=this) {
        delete [] data_;
        data_ = new char[rhs.size() + 1];
        memcpy(data_, rhs.c_str(), rhs.size());
    }
    return *this;
}
 
String::~String() {
    delete [] data_;
}


//使用swap的拷貝建構函式,通過swap將臨時變數rhs中的資料儲存到了data_中,同時data_中的資料拷貝到了臨時變數中,在函式返回時會被自動釋放。
一舉兩得,也不用擔心有異常發生了。
 

String::String(String rhs) {
      std::swap(data_, rhs.data_);
}
 
String::String& operator=(String rhs) {
      std::swap(data_, rhs.data_);
      return *this;
}
 
String::String() : data_ = new char[1]{
        *data_ = '\0';
}

3

STL相關的各種問題
1,用過那些容器。
最常用的容器就是:vector, list, map, hash_map等等。

2,vector,list,deque的實現。
vector是一塊連續記憶體,當空間不足了會再分配。
list是雙向連結串列。
deque是雙端佇列可在頭和尾部插入、刪除元素。

3,hashmap和map有什麼區別。
一個是基於hash表實現,一個是基於紅黑樹實現。

4,紅黑樹有什麼特性

5,STL仿函式和指標的差別。

6,配接器

7,一元、二元仿函式

4

C++面試題(一)、(二)和(三)都搞定的話,恭喜你來到這裡,這基本就是c++面試題的最後一波了。
     1,你知道智慧指標嗎?智慧指標的原理。
     2,常用的智慧指標。
     3,智慧指標的實現。

  1答案:智慧指標是一個類,這個類的建構函式中傳入一個普通指標,解構函式中釋放傳入的指標。智慧指標的類都是棧上的物件,所以當函式(或程式)結束時會自動被釋放,

     2, 最常用的智慧指標: 

              1)std::auto_ptr,有很多問題。 不支援複製(拷貝建構函式)和賦值(operator =),但複製或賦值的時候不會提示出錯。因為不能被複制,所以不能被放入容器中。

              2) C++11引入的unique_ptr, 也不支援複製和賦值,但比auto_ptr好,直接賦值會編譯出錯。實在想賦值的話,需要使用:std::move。

               例如:

                    std::unique_ptr<int> p1(new int(5));
                    std::unique_ptr<int> p2 = p1; // 編譯會出錯
                    std::unique_ptr<int> p3 = std::move(p1); // 轉移所有權, 現在那塊記憶體歸p3所有, p1成為無效的指標.

              3) C++11或boost的shared_ptr,基於引用計數的智慧指標。可隨意賦值,直到記憶體的引用計數為0的時候這個記憶體會被釋放。

              4)C++11或boost的weak_ptr,弱引用。 引用計數有一個問題就是互相引用形成環,這樣兩個指標指向的記憶體都無法釋放。需要手動打破迴圈引用或使用weak_ptr。顧名思義,weak_ptr是一個弱引用,只引用,不計數。如果一塊記憶體被shared_ptr和weak_ptr同時引用,當所有shared_ptr析構了之後,不管還有沒有weak_ptr引用該記憶體,記憶體也會被釋放。所以weak_ptr不保證它指向的記憶體一定是有效的,在使用之前需要檢查weak_ptr是否為空指標。

     3, 智慧指標的實現

      下面是一個基於引用計數的智慧指標的實現,需要實現構造,析構,拷貝構造,=操作符過載,過載*-和>操作符。

template <typename T>
class SmartPointer {
public:
    //建構函式
    SmartPointer(T* p=0): _ptr(p), _reference_count(new size_t){
        if(p)
            *_reference_count = 1; 
        else
            *_reference_count = 0; 
    }
    //拷貝建構函式
    SmartPointer(const SmartPointer& src) {
        if(this!=&src) {
            _ptr = src._ptr;
            _reference_count = src._reference_count;
            (*_reference_count)++;
        }
    }
    //過載賦值操作符
    SmartPointer& operator=(const SmartPointer& src) {
        if(_ptr==src._ptr) {
            return *this;
        }
        releaseCount();
        _ptr = src._ptr;
        _reference_count = src._reference_count;
        (*_reference_count)++;
        return *this;
    }
 
    //過載操作符
    T& operator*() {
        if(ptr) {
            return *_ptr;
        }
        //throw exception
    }
    //過載操作符
    T* operator->() {
        if(ptr) {
            return _ptr;
        }
        //throw exception
    }
    //解構函式
    ~SmartPointer() {
        if (--(*_reference_count) == 0) {
            delete _ptr;
            delete _reference_count;
        }
    }
private:
    T *_ptr;
        size_t *_reference_count;
        void releaseCount() {
        if(_ptr) {
            (*_reference_count)--;
                if((*_reference_count)==0) {
                    delete _ptr;
                    delete _reference_count;
                }
        }
        }
};
 
int main() 
{
    SmartPointer<char> cp1(new char('a'));
    SmartPointer<char> cp2(cp1);
    SmartPointer<char> cp3;
    cp3 = cp2;
    cp3 = cp1;
    cp3 = cp3;
    SmartPointer<char> cp4(new char('b'));
    cp3 = cp4;
}