一、const,&:
When:實參與const引用參數不匹配時,編譯器將創建臨時變量?
1、實參的類型正確,但不是左值;
2、實參類型不正確,但可轉換爲正確類型。
Why:引用形參儘可能聲明爲const?
1、可以避免無意中修改數據;
2、使函數能夠處理const和非const實參;
3、使用const引用可使函數能夠正確生成並使用臨時變量。
函數返回值應避免返回函數終止時不再存在的內存單元引用或指針。
避免措施:1、返回一個作爲參數傳遞給函數的引用;2、使用new
按引用傳遞對象:
1、提高效率(按值傳遞,複製構造函數,析構函數)。
2、在繼承使用虛函數時,被定義爲接收基類引用參數的函數可以接受派生類。
二、函數重載:
編譯器在檢查函數特徵標時,將類型引用和類型本身視爲同一個特徵標。
函數重載只有特徵標不同。
三、函數模板:
隱式實例化:
template <typename T>
void Swap(T &a, T &b){...}
顯式實例化:
template void Swap<int>(int ,int){...}
顯式具體化:
template < > void Swap<Job> (Job &j1, Job &j2){...}
關鍵字decltype:
1、 double a = 5.5; double &b = a; decltype(b) c; // c : double &
2、long function(int); decltype (function(3)) w; //w: long
3、decltype ( (a) ) a0 = a; //a0: double &
4、int n = 1; decltype(n+2) n0; // n0: int
5、template<class T1, class T2>
auto function(T1 x, T2 y) -> decltype(x + y){
...
return x + y;
}
四、定位new運算符:
#include <new>
未重載的new運算符工作原理:它返回傳遞給它的地址,並將其強制轉換爲void *,以便能夠賦給任何指針類型。
若定位new運算符使用了常規new分配的內存,需要顯式地爲定位new運算符創建的對象調用析構函數:p->~Test();
注意上一點調用析構函數的順序。
五、對象和類:
轉換構造函數:
接受一個參數的構造函數允許使用賦值語法將值賦給對象,使用explicit可關閉此特性。
轉換函數:
轉換函數必須是類方法,不能有返回類型和參數:operator double(){ return double(id); }
explicit operator int() const; //隱式不能調用,
When:調用析構函數:
1、如果創建的是靜態存儲類對象,其析構函數在程序結束時自動被調用。
2、如果創建的是自動存儲類對象,其析構函數將在程序執行完代碼塊自動被調用。
3、若對象是new創建的,則它將駐留在棧內存或自由存儲區中,當使用delete釋放內存時,其析構函數自動被調用。
4、對於臨時對象,程序將在結束對該對象的使用時自動調用其析構函數。
在類聲明中定義使用常量的方法:
1、enum { Months = 12 };
2、static const int Months = 12;
作用域內枚舉:
enum class : short egg{Small, ..., Large}; //使用:egg::Small
運算符重載
友元函數:
1、友元函數不是成員函數,不能使用成員運算符來調用;
2、但與成員函數的訪問權限相同;
3、不要在定義中使用friend;
4、T1 = T2.operator + ( T3 ); // 成員函數 運算符重載
T1 = operator + ( T2 + T3 ); // 非成員函數(友元函數),運算符重載
複製構造函數參數必須爲const &
何時使用複製構造函數?
1、新建一個對象並將其初始化爲同類現有對象時:
String test1( test0 );
String test2 = test0; //也可能調用賦值運算符
String test3 = String( test0 ); //也可能調用賦值運算符
String *pTest = new String( test0 );
2、每當程序生成了對象副本時:函數按值傳遞對象 或 函數返回對象,
3、編譯器生成臨時對象。
也可能調用賦值運算符
賦值運算符編寫:
String & String::operator= (const String &st){
if( this == &st )
return *this;
delete [] str;
len = st.len;
str = new char [len + 1];
std::strcpy(str, st.str);
return *this;
}
調用靜態成員函數使用作用域解析運算符(類限定符)
常見的兩種返回非const對象的情形:
1、重載賦值運算符:效率
2、重載與cout一起使用的<<運算符:必須,返回ostream &;
不能返回ostream的原因是ostream沒有公有的複製構造函數。
返回對象而不是引用的例子:重載算術運算符(因爲引用指向的對象不復存在;需調用複製析構)
成員初始化列表:
1、只有構造函數可使用
2、非靜態const類成員和&類成員,必須使用(因爲它們只能在被創建時初始化)
六、類繼承:
有兩種重要的機制可用於實現多態公有繼承:
1、在派生類中重新定義基類方法;
2、使用虛方法。
如果沒有使用關鍵字virtual,程序將根據引用類型或指針類型選擇方法;如果使用了virtual,程序將根據引用或
指針指向的對 象的類型來選擇方法。
如果在派生類中調用基類方法:
1、若是virtual方法,參照上一點;
2、類名::方法
爲何需要虛析構函數?
如果析構函數不是虛的,則將只調用對應於指針類型的析構函數;而對於指針指向的對象的析構函數不會調用。
使用虛析構函數可以確保正確的析構函數序列被調用。
將源代碼中的函數調用解釋爲執行特定的函數代碼塊稱爲函數名聯編。
靜態聯編:編譯時非虛方法完成聯編。
動態聯編:運行時編譯器選擇正確的虛方法的代碼。
●虛函數的工作原理:隱藏成員(指針),虛函數表,派生類定義新的虛函數的地址被添加到虛函數表中
●使用虛函數的成本:
1、每個對象都增大,增大量爲存儲地址的空間;
2、對於每個類,編譯器都創建一個虛函數地址表
3、對於每個函數調用,都需要執行一項額外的操作,即到表中查找地址
構造函數、友元函數不能是虛的,析構函數應當是虛函數。
1、若派生類更改且僅更改虛函數的特徵標,這不是重載,它會隱藏基類版本。
2、返回類型協變
3、如果基類聲明被重載了,則應在派生類中重新定義所有的基類版本;如果只重新定義一個版本,
其他版本將被隱藏。
純虛函數,抽象基類
基類和派生類都使用動態內存分配時,派生類的析構函數、複製構造函數、賦值運算符都必須使用相應的基類方法來處理
基類元素:對於析構,自動完成;對於構造,初始化成員列表中調用基類的複製構造函數來完成(若沒,會調用默認構造);
對於賦值,使用作用域解析運算符顯式地調用基類的賦值運算符來完成。
如何使用基類的友元函數?
將(友元的)參數強制類型轉換成基類
公有繼承中,什麼不能被繼承?
構造函數、析構函數、賦值運算符、友元函數
派生類引用不能自動引用基類對象,如何使其可引用?
1、轉換構造函數:派生類(const 基類 &);
2、定義一個將基類賦給派生類的賦值運算符;
3、顯式強制類型轉換。
/*********************************************************************************************/
私有繼承、包含兩者區別:
1、包含版本提供的是被顯式命名的對象成員,私有繼承提供的是無名稱的子對象成員;
2、構造函數成員初始化列表中,包含使用“基類對象名(參數)”形式構造基類;私有繼承使用“類名(參數)”形式構造基類。
3、包含使用對象來調用方法,私有繼承使用類名::來調用基類方法。
- 私有繼承如何使用基類對象本身,基類成員、方法? 強制類型轉換
- 在私有繼承中,在不進行顯示類型轉換的情況下,不能將指向派生類的引用或指針賦給基類引用或指針。
- 私有/保護繼承中,讓基類方法在派生類外面可用,2種方法:
1、定義一個使用該基類方法的派生類方法;
2、使用using聲明:在類聲明public中,using std::valarray<double>::min;
using聲明只適用於繼承,不適用於包含。
多重繼承,虛基類,成員初始化列表規則
類模板:
1、隱式實例化:
Array<double, 30> *pt;
pt = new Array<double 30>;
2、顯式實例化:
template class Array<string, 100>
3、顯式具體化:
template <> class SortedArray<const char *>{...}
4、部分具體化:
template <沒有被具體化的類型參數T1> class Pair<T1, int>{...}
類模板中表達式參數的限制,eg,ArrayTP<T, int>:
1、表達式參數可以是整型、枚舉、引用、指針:eg中int不能替換爲double,但可爲double *;
2、模板代碼不能修改參數的值,不能使用參數地址:eg中,不能n++、&n;
3、實例化模板時,用作表達式參數的值必須是常量表達式。
模板類的友元:
1、非模板友元
2、約束模板友元:模板函數前向聲明;類中將模板聲明爲友元;爲友元提供模板定義。
eg:
template <typename T> void counts();
template <typename T> void report(T &);
template <typename TT>
class Test{
...
public:
...
friend void counts<TT>();
friend void report<>(Test<TT> &);
};
template <typename T>
void counts(){ ... }
七、異常:
throw的工作過程?
函數調用原理?棧解退?兩者區別?
函數返回僅僅處理該函數放在棧中的對象(一次返回僅處理一個函數放在棧中的對象),
而throw語句則處理try塊和throw之間整個函數調用序列放在棧中的對象(一串)。
引發異常時編譯器總是創建一個臨時拷貝,即使異常規範和catch塊中指定的是引用。
<exception>
八、C++標準程序庫,STL
九、i/o
十、補充:
●鏈接性爲內部的靜態變量:
static int counts; <->
namespace{
int counts;
}
●auto:自動類型推斷
●類內成員初始化
●右值引用&&,移動語義:
Useless(Useless && f) : n(f.n){ //移動複製構造函數
++ct;
pc = f.pc;
f.pc = nullptr; //竊取
f.n = 0;
}
eg: Useless three(one + two); //若無移動複製構造函數,將調用複製構造函數
可能:移動賦值運算符和移動複製構造函數的參數不能是const &,因爲方法修改了源對象。
強制移動:#include <utility>
three = std::move(one);
●default可用於禁止編譯器使用特定方法,只用於6個特殊的成員函數:function = default;
delete可用於禁止編譯器使用特定方法:function = delete;
●委託:在一個構造函數的定義中使用另一個構造函數(成員初始化列表)。
●繼承構造函數:讓派生類能夠繼承基類構造函數: using BS::BS;
●override: 覆蓋一個虛函數,將其放在參數列表後面。
final:禁止派生類覆蓋特定的虛方法。
●lambda表達式:
1、count = std::count_if(numbers.begin(), numbers.end(),
[](int x){return x %3 == 0;});
2、僅當lambda表達式完全由一條返回語句組成時,自動類型推斷纔有用,否則:
[](double x) -> double{ int y = x; return x -y; }
3、lambda表達式可訪問作用域內的任何動態變量:
[&]按引用,[=]按值,
●模板和函數參數包:
void show(const T& value){...} //參數展開後,爲避免無窮遞歸,定義此函數
template <typename T, typename... Args>
void show(const T& value, const Args&... args){...}