問題01:如何初始化成員變量?
你應該總是在構造函數的初始化列表中初始化成員變量,並且避免在構造函數體中進行成員變量的初始化工作。如果這個成員變量是一個類,在初始化列表中進行初始化,只需要一次構造操作;如果在函數體中進行初始化,則需要一次構造和一次賦值操作。此外,初始化列表還可以使你獲得自動的異常處理。
引用變量的初始化必須使用初始化列表。根據標準,一個引用必須一直指向一個簡單變量,並且不能被改變指向另一個變量。對於非成員變量,編譯器要求引用變量在進行定義時必須初始化它,使得它指向某個對象。而對於成員變量,大部分編譯都可以接受它直到你創建一個這個類的實例爲止。
問題02:如何使用構造函數和析構函數管理資源
在構造函數中分配資源或獲取資源,並在析構函數中釋放資源,這種技術常常稱爲資源獲取初始化(RAII)。採用這種技術可以減少用戶的異常處理代碼。
問題03:在不需要類的用戶做任何特別處理的情況下,如何在一個容器中存儲該類的所有實例
使用一個靜態的鏈表來存儲指向對象的指針。當一個對象被創建時,把它的地址加到這個鏈表中;而當這個對象被銷燬時,從這個鏈表中刪除它。
- #include <iostream>
- #include <list>
- #include <algorithm>
- using namespace std;
- class MyClass {
- public:
- MyClass(int val);
- ~MyClass();
- static void showList();
- protected:
- static list<MyClass *> instances_;
- private:
- int val_;
- };
- MyClass::MyClass(int val) :
- val_(val) {
- instances_.push_back(this);
- }
- MyClass::~MyClass() {
- list<MyClass*>::iterator p = find(instances_.begin(), instances_.end(), this);
- if(p != instances_.end())
- instances_.erase(p);
- }
- void MyClass::showList() {
- list<MyClass*>::iterator p = instances_.begin();
- for( ; p != instances_.end(); ++p)
- cout << (*p)->val_ << endl;
- }
- list<MyClass *> MyClass::instances_;
問題04:在運行時,如何動態地查詢某個類對象的類型
使用運行時類型標識(RTTI)來查詢一個對象的地址以得到這個地址指向的對象類型。不過,RTTI是增加系統開銷的。
- #include <iostream>
- #include <typeinfo>
- using namespace std;
- struct Base {};
- struct Derived : public Base {};
- int main()
- {
- Base b;
- Derived d;
- cout << boolalpha << bool(typeid(b) == typeid(d)) << endl;
- cout << typeid(b).name() << endl;
- cout << typeid(d).name() << endl;
- return 0;
- }
問題05:如何確定某個對象的類是否是另一個類的子類
使用dynamic_cast操作符來獲取一個類型到另外一個類型的繼承關係,dynamic_cast帶一個指針或引用參數,並且企圖把它轉換成它的一個派生類的指針或者引用(downcast)。如果downcast是安全的(也就說,如果基類指針或者引用確實指向一個派生類對象)這個運算符會傳回適當轉型過的指針。如果downcast不安全,這個運算符會傳回空指針(也就是說,基類指針或者引用沒有指向一個派生類對象)。
- #include <iostream>
- #include <typeinfo>
- using namespace std;
- struct Base {};
- struct Derived : public Base {};
- int main()
- {
- Derived d;
- if(dynamic_cast<Base*>(&d))
- cout << "d is derived from Base" << endl;
- else
- cout << "d is not derived from Base" << endl;
- return 0;
- }
問題06:如何實現一個只能被實例化一次的類,即單例模式
創建一個靜態成員變量並且這個成員是一個指向當前類的指針,通過private私有修飾符來限制構造函數的使用來創建這個類的對象,並且提供一個公有的靜態成員函數來訪問這個唯一的實例。
- #include <iostream>
- using namespace std;
- class Singleton {
- public:
- static Singleton* getInstance();
- void setValue(int val);
- int getValue();
- protected:
- int val_;
- private:
- Singleton();
- ~Singleton();
- static Singleton* instance_;
- };
- Singleton* Singleton::getInstance() {
- if(instance_ == NULL)
- instance_ = new Singleton();
- return instance_;
- }
- void Singleton::setValue(int val) { val_ = val;}
- int Singleton::getValue() { return val_;}
- Singleton::Singleton() : val_(0) {}
- Singleton::~Singleton() {}
- Singleton* Singleton::instance_ = NULL;
- int main()
- {
- Singleton* p1 = Singleton::getInstance();
- p1->setValue(1);
- Singleton* p2 = Singleton::getInstance();
- cout << p2->getValue() << endl;
- return 0;
- }
問題07:如何定義一個子類將來可以實現的接口
通過創建一個抽象基類(常常用ABC來稱呼)來定義這個接口,客戶代碼能夠用不同的實現來繼承這個抽象基類從而保證這個共同的接口。
一個抽象基類是一個不能被實例化的類,因此它也就是起到一個接口的作用。如果一個類聲明瞭最少一個純虛函數或者它繼承了一個純虛函數但沒有它的實現的話,那麼這個類就是抽象類。因此,如果這個ABC的一個子類要實例的話,它不得不實現ABC類中所有的虛函數。
最後,如果在你的基類中提供了虛的析構函數,你需要給它提供一個函數體。這是因爲子類的析構函數需要自動調用基類的析構函數。
- template <class T>
- class ABC {
- public:
- virtual ~ABC() {}
- virtual void fun1() const = 0;
- virtual void fun2(const T& val) = 0;
- private:
- T val_;
- };
問題08:如何實現一個簡單成員函數,這個成員函數可以帶一個任意類型的參數
使用成員函數模板並且把這個模板的參數聲明成這個成員函數的參數對象類型。調用示例中的get1()函數需要提供模板參數,而調用get2()函數則不需要提供模板參數。因爲編譯器只能通過形參來推導出模板參數類型,get1()函數沒有形參,所以編譯器無法自動推導出返回值類型。
- class MyClass {
- public:
- template <typename T>
- T* get1() { return (new T);}
- template <typename T>
- void get2(T*& p) { p = new T;}
- };
問題09:如何調用一個特定類的超類中的函數,這個函數在子類被重寫了
使用你的目標基類來修飾你的成員函數名
- Derived *pd;
- pd->Base::fun();