本文主要是閱讀Thinking in C++ 第一卷的一些筆記。主要是一些注意點
- Thinking in C++ Chapter 2
- Translator:
- 編譯器編譯程序步驟:
- 函數或者變量的聲明與定義
- 連接
- Thinking in C++ Chapter 3
- 函數返回說明
- 通常函數庫
- 關於for循環的說明
- switch說明
- 說明符(specifier)
- void*指針說明
- 變量的有效作用域
- 全局變量
- 靜態(static)變量
- 連接(linkage種類)
- 預處理宏
- typedef語法說明
- enum說明:
- Thinking in C++ Chapter 5
- 友元
- 嵌套友元
- struct其他
- Thinking in C++ Chapter 6
- 構造函數相應說明
- delete void*
- 默認構造函數
- Thinking in C++ Chapter 7
- overload
- 類型安全連接(type-safe linkage)
- Union
- 默認參數使用規則:
- 占位符參數
- Thinking in C++ Chapter 8
- const說明
- const指針
- 賦值和類型檢查
- 臨時變量
- 傳遞和返回地址
- 標準參數傳遞
- 類內數據成員為const初始化
- 編譯期間的常量
- const對象和成員函數
- Thinking in C++ Chapter 9
- 內聯函數
- 構造函數和析構函數隱藏行為
- 預處理器
- Thinking in C++ Chapter 10
- static
- static對象
- 內部連結
- others
- stactic/const/stctic const
- 靜態成員函數
- 指針數組/數組指針
- 函數指針
- 函數地址(Function Address)
- 函數指針
- 調用函數指針
- 函數指針數組
- typedef簡化工作量
- namespace
- namespace
- 未命名的名字空間
- using directive& using declaration
- new/malloc/delete/free
- new/malloc
- delete/free
- 重載全局new和delete
- 類重載new和delete
- 為數組重載new和delete
- 定位new/delete
- 繼承與組合(Inheritance&Composition)
- 初始化表達式
- 構造函數和析構函數調用的順序
- 非自動繼承的函數
- 繼承與靜態成員函數
- 私有繼承
- 向上類型轉換和拷貝構造函數
- 運算符重載
- +=,-=,*=,/=,=等一類運算符重載
- 函數參數和返回值說明
- Prefix ++ & Postfix ++
- 常量傳值返回與返回值優化
- operator[]
- operator->(指針間接引用運算符)
- operator->*
- 運算符成員函數基本方針
- copy-on-write
- 自動類型轉換
Thinking in C++ Chapter 2
Translator:
- 解釋器(interpreter)
- 編譯器(complier)
編譯器編譯程序步驟:
- 預處理器(preprocessor)處理預處理指令
- 編譯分為兩遍,第一遍解析預處理代碼生成節點數,在進行第二步之前進行全局優化(global optimizer),第二遍代碼生成器(code generator)解析代碼樹生成機器語言或者彙編語言
靜態類型檢查在第一遍中進行
函數或者變量的聲明與定義
void function();//聲明void function(){}//定義extern int a;//聲明int a;//定義
連接
編譯過程的最後階段,把編譯器生成的目標模塊連接成可執行文件(作業系統可以識別)
Thinking in C++ Chapter 3
函數返回說明
return語句退出函數,返回到函數調用後的那點,聯想棧的操作
通常函數庫
由庫管理器來管理對象模塊,這個庫管理器就是管理.lib/.a文件的
關於for循環的說明
for(initialization;conditional;step)
for循環首先執行initialization,其次判斷conditional,滿足則進入循環,執行完循環進行step步驟
switch說明
switch(selector){
case integral-value:statement;break; ...
defualt:statement;
}
switch中的selector必須為整數值,integral-value必須為整形數值
selector也可以為enum類型值
說明符(specifier)
用於改變基本內建類型的含義並將基本類型擴展成一個更大的集合
1. int: short int / int / long int(使用short和long時,int關鍵字可以省略)
2. float/double: 沒有long float只有long double,即:float / double /long double
3. signed/unsigned:符號位(適用於整形和字符型)
void*指針說明
void*指針可以賦值為任何類型的地址,但是會丟失類型信息,不恰當的類型轉換會導致程序崩潰
變量的有效作用域
從定義點開始,到和定義變量之前最鄰近的開括號配對的第一個閉括號,也就是說作用域由變量所在的最近一對括號確定。
全局變量
全局變量的生命周期一直到程序結束,可以使用extern關鍵字來使用另一個文件中的全局變量
靜態(static)變量
static變量優點是在函數範圍之外它是不可用的,不可以輕易改變,使錯誤局部化,當應用static於函數名和所有函數外部變量時,它的意思是「在文件的外部不可以使用該名字」,即擁有文件作用域如
//file1.cppstatic int fs;int main(){
fs = 1;
}//file2.cppextern int fs;//編譯器不會找到file1.cpp文件中的fs,即文件作用域void function(){
fs = 100;
}
extern static int i;int main(){ cout << i << endl;
}static int i = 0;
將出現error,即全局靜態變量聲明是有文件作用域的,編譯器將會產生錯誤
連接(linkage種類)
- 內部連接:只對正在編譯的文件穿件存儲空間,為每一個標識符創建單獨的存儲空間,內部連接由關鍵字static指定
- 外部連接:對所有編譯過的文件創建一個單獨的存儲空間,即將所有變量和函數包含在該空間中
自動(局部)變量只是臨時存在於堆棧中,連接器不知道自動變量,所以這些變量沒有連接
預處理宏
#define PRINT(STR,VAR) \\
cout << STR << VAR << endl; \\
cout << VAR << STR <即可以像調用函數一樣調用PRINT,這裡預處理宏分行使用'\\',宏只是展開,並替換
#define PRINT(STR,VAR) \\
cout << #STR << VAR << endl
這裡'#'表示STR字符串化(stringizing),比如:int i = 0;PRINT(i,i); //這裡輸出應該是 i:0
typedef語法說明
typedef existing-type-description alias-name
- typedef unsinged long ulong;
- typedef int* intPtr
- typedef struct MyStruct{
- //這裡是C常營的結構定義
- } MyStruct;
- typedef int (*fun)(int,int) //函數指針別名為fun
enum說明:
c++中對enum的檢查更為嚴格,c中允許a++(a為color型枚舉),但是c++中不允許,因為a++做了兩次轉換,首先將color類型轉換為int,然後自增1之後,將該值在轉換成color,第二次轉換時非法的
Thinking in C++ Chapter 5
友元
使用friend關鍵字可以訪問內部私有成員變量或者成員函數
struct X;struct Y{ void f(X*);
};struct X{ private: int i; public: void initialize(); friend void g(X*,int); //Global friend
friend void Y::f(X*); //struct member friend
friend struct z; //Entire struct is a friend
friend void h();
}
Y::f(X*)引用了一個X對象的地址,編譯器知道如何傳遞一個地址,不管被傳遞的是什麼對象,地址具有固定大小,當試圖傳遞整個對象時,編譯器必須知道X的全部定義以確定它的大小以及如何傳遞,使用不完全類型說明(incomplete type specification),即在struct Y之前聲明struct X;
嵌套友元
嵌套結構不能自動獲得訪問private成員權限,可以使用如下方法訪問
- 聲明嵌套結構
- 聲明該結構是全局範圍內使用的一個friend
- 定義該結構
const in sz = 20;struct Holder{ private: int a[sz]; public: void initialize(); struct Pointer;
friend Pointer; struct Pointer{ private:
Holder* h; int* p; public: void initialize(Holder* h); void next(); void previous(); void top(); void end(); int read(); void set(int i);
};
};void Holder::initialize(){
memset(a,0,sz*sizeof(int));
}void Holder::Pointer::initialize(Holder* rv){
h = rv;
p = rv->a;
}
...int main(){
Hodler h;
Holder::Pointer hp; int i;
h.initialize();
hp.initialize(&h);
...
}
struct其他
//: Hadler.h
class Handler{
struct Cheshire;
Cheshire* smile;
public: ...};
//:~
//:Handler.cpp
struct Handler::Cheshire{
int i; ...}...
Handler.h文件中struct Cheshire是一個不完全的類型說明或者類聲明,具體類定義放在了實現文件中
Thinking in C++ Chapter 6
構造函數相應說明
class Object{public: Object(int number = 0);private: int m_number;
};//:~//: mainint main(int argc, char *argv[])
{ int i = 0; switch(i){ case 0: Object obj1{1}; break; case 1: //error: cannot jump from switch statement to this case label "case 1:"
Object obj2{2}; //jump bypasses variable initialization "Object obj1{1};"
break;
} return 0;
}
上述代碼報錯
switch回跳過構造函數的的序列點,甚至構造函數沒有被調用時,這個對象也會在後面的 程序塊中程序塊中起作用,這裡產生錯誤
是確保對象在產生的同時被初始化。goto也會產生這樣的錯誤。
delete void*
當void*指向一個非內建類型的對象時,只會釋放內存,不會執行析構函數
默認構造函數
class Object{public: Object(int number);private: int m_number;
};Object object[2] = {Object{1}};
Object沒有默認構造函數,數組聲明初始化時將報錯,object[1]必須有默認構造函數進行初始化,否則報錯若且唯若沒有構造函數時編譯器會自動創建一個默認構造函數
Thinking in C++ Chapter 7
overload
使用範圍和參數可以進行重載
void f();class X{void f();};
類型安全連接(type-safe linkage)
1.cppvoid functin(int);2.cppvoid function(char);int main(){ function(1); //cause a linker error;
return 0;
}
編譯成功,在C中連接成功,但是在C++中連接出錯,這是C++中的一種機制:類型安全連接
Union
class SuperVar{ enum{
character,
integer,
floating_point
} vartype; union{ char c; int i; float f;
}; public:
SuperVal(char ch);
SuperVal(int ii);
SuperVal(float ff); void print();
};
SuperVal::SuperVali(char ch){
vartype = character;
c = ch;
}
SuperVal::SuperVali(int ii){
vartype = integer;
i = ii;
}
SuperVal::SuperVali(float ff){
vartype = floating_type;
f = ff;
}void SuperVal::print(){ switch(vartype){ case character: cout << "character:" << c <}
}int main(){
SuperVar A('c'),B(12),C(1.44f);
A.print();
B.print();
C.print(); return 0;
}
- enum沒有類型名,因為後面沒有必要涉及美劇的類型名稱,所以枚舉類型名可選,非必須
- union沒有類型名和標識符,稱為匿名聯合(anonymous union),不需要使用標識符和以點操作符方式訪問這個union的元素
- 訪問一個匿名聯合成員就像訪問普通變量一樣,唯一區別在於:該聯合的兩個變量占用同一內存空間,如果匿名union在文件作用域內(在所有函數和類之外),則它必須聲明為static,以使它有內部的連接
默認參數使用規則:
- 只有參數列表的後部參數才可以是默認的
- 一旦在一個函數調用中開始使用默認參數,那麼這個參數後面的所有參數都必須為默認的
占位符參數
void f(int i, int = 0, float = 1.1); //version 1void f(int i ,int , float flt); // version 2
其中version 2除了i,flt之外中間參數就是占位符參數
Thinking in C++ Chapter 8
const說明
C++中const默認為內部連接,僅在const被定義的文件中才可見,在連接時不能被其它編譯單元看見,當定義一個const時必須賦值給它,除非使用extern進行說明
extern const int bufsize;
通常c++不為const創建空間,將其定義保存在符號表內,但是上面的extern進行了強制內存空間分配,另外如取const的地址也是需要存儲空間的分配。
對於複雜的結構,編譯器建立存儲,阻止常量摺疊。在C中const默認為外部連接,C++默認為內部連接.出現在所有函數外部的const作用域是整個文件,默認為內部連接
const指針
- const修飾指針正指向的對象 const int* a;
- const修飾在指針中的地址 int* const a;
賦值和類型檢查
- const對象地址不可以賦值給一個非const指針,但是可以吧一個非const對象地址賦值給一個const指針
- 字符數據的字面值:
char * cp = "howdy";char cp[] = "howdy";
指針cp指向一個常量值,即常量字符數組,數組cp的寫法允許對howdy進行修改
臨時變量
在求表達式值期間,編譯器必須創建零時變量,編譯器為所有的臨時變量自動生成為const
傳遞和返回地址
void t(int*) {}void u(const int* clip){ //*clip = 2; error
int i = *clip; //int * ip2 = clip error;}const char* v(){ return "result of functin 0";
}const int * const w(){ static int i; return &i;
}int main(){ int x= 0; int * ip = &x; const int * cip = &x;
t(ip); //ok
//t(cip); not ok;
u(ip); //ok
u(cip);//ok
//char * cp = v(); not ok
const char* ccp = v();//ok
//int * ip2 = w(); not ok
const int * const ccip = w();// ok
const int* cip2 = w();//ok
//*w() = 1; not ok}
- const指針不可以賦值給非const指針,但是非const指針可以賦值給const指針
- 函數v()返回一個從字符數組的字面值中建立的const char *,在編譯器建立了它並把它存儲在靜態存儲區之後,該聲明實際上產生該字符數組的字面值的地址
- 函數w()返回值要求這個指針以及這個指針所指向的對象均為常量,與函數v()類似,因為i是靜態的,所以函數返回後返回值仍然有效
- const int* const w()只有在作左值時第二個const才能顯現作用,所以w()返回值可以賦值給const int *
標準參數傳遞
可以將臨時對象傳遞給const引用,但不能將一個臨時對象傳遞給接收指針的函數,對於指針必須明確接受地址(臨時變量總是const)
class X
{public: X() {}
};
X f(){return X();}void g1(X&){ }void g2(const X&){ }int main(int argc, char *argv[])
{
g1(f()); //error!
g2(f()); return 0;
}
類內數據成員為const初始化
必須在構造函數的初始化列表中進行初始化
編譯期間的常量
- 一個內建類型的static const可以看成編譯期間的常量,但是該static const必須在定義的地方進行初始化
- 無標記enum也可以看成為編譯期間常量,一個枚舉在編譯期間必須有值
class X{
enum {size = 1000}; //same as static const
static const int size = 1000; int i[size];
}
const對象和成員函數
- 若將一個成員函數聲明為const,則該成員函數可以被const對象調用
- const成員函數可以調用非const成員和const成員,非const成員函數同樣可以使用const成員
- const對象只能調用const成員函數,非const對象調用非const成員函數
Thinking in C++ Chapter 9
內聯函數
內聯函數與普通函數一樣執行,但是內聯函數在適當的地方像宏一樣展開,不需要函數調用的開銷(壓棧,出棧),任何在類內部定義的函數自動成為內聯函數
- 內聯函數體過大時,編譯器將放棄使用內聯
- 當取函數地址時,編譯器也將放棄內聯
- 一個內聯函數在類中向前引用一個還沒有聲明的函數時,是可以的,因為C++規定只有在類聲明結束後,其中的內聯函數才會被計算
class Forward{ int i; public: Forward():i(0){} int f() const {return g()+i;} int g() const {return i;}
}
構造函數和析構函數隱藏行為
class X
{ int i,j,k;public:
X(int x = 0):i(x),j(x),k(x) { cout << "X" <~X(){cout << "~X" < };class Y{
X q,r,s; int i;public:
Y(int ii):i(ii){cout << "Y" <~Y(){ cout << "~Y" < }
};int main(int argc, char *argv[])
{
Y y(1); return 0;
}//:~//:outputX
X
X
Y
~Y
~X
~X
~X
- 類中包含子對象,構造函數先調用子對象的構造函數,如果沒有默認構造函數,則必須在初始化列表中進行初始化,然後在調用類的構造函數
- 類中包含子對象,先調用類的析構函數在調用子類的析構函數
預處理器
1. #define DEBUG(x) cout << #x "=" << x<
Thinking in C++ Chapter 10
static
- 在固定地址上進行存儲分配,在一個特殊的靜態數據區上創建,不是在堆棧上產生
- 對一個特定編譯單位來說是局部的,static可以控制名字的可見性,該名字在這個單元或者類以外是不可見的
static對象
- 如果沒有為一個內建類型的靜態變量提供一個初始化值,編譯器會確保在程序開始時它被初始化為零(轉化成適當的類型)
- 如果在定義一個靜態對象時沒有指定構造參數時,該類必須有默認的構造函數
內部連結
常量、內聯函數默認情況下為內部連結
others
- 所有全局對象隱含為靜態存儲
- 對static函數意味著只在本單元可見,成為文件靜態(file static)
stactic/const/stctic const
- static成員必須在類外初始化,如果不初始化,則編譯器不會進行默認初始化,對於非內建類型,可以使用構造函數初始化代替「=」操作符
- 類內const成員必須在構造函數的初始化列表中進行初始化
- static const變量(內建類型)必須在聲明的地方就初始化
- static對象數組,包括const和非const數組必須在類外部初始化
- 自定義class類聲明為stctic,不管其為const或者非const都必須在類外初始化
- 類的靜態成員必須進行初始化後才可以使用
靜態成員函數
- 類的靜態成員函數不能訪問一般數據成員或者函數,只能訪問靜態數據成員,也能調用其他靜態成員函數
- 靜態成員函數沒有this指針
指針數組/數組指針
優先級低的先讀
*p[] 指針數組
(*p)[] 數組指針,即指向一個數組
函數指針
函數地址(Function Address)
函數的地址:函數名後不跟參數
void fun(){}fun即為函數地址fun()為函數的調用
函數指針
double pam(int); //prototypedouble (*pf)(int); //function pointerpf=pam;//pf now points to the pam();
調用函數指針
double x=pf(5);double x=(*pf)(5);
函數指針數組
const double* f1(const double ar[],int n);const double* f2(const double [],int);const double* f3(coanst double *,int);//f1,f2,f3函數聲明本質一樣const double* (*pa[3])(const double * , int) = {f1,f2,f3}; //[]優先級高於*,所以表示pa是個數組,數組中包含三個指針auto pb = pa;const double * px = pa[0](av,3);const double * py = (*pb[1])(av,3);double x = *pa[0](av,3);double y = *(pb[1])(av,3);
指向整個數組的指針,即是一個指針,而不是一個數組,優先級低的先讀
*p[] 指針數組
(*p)[] 數組指針,即指向一個數組
const double* (*(*pd)[3])(const double* , int) = &pa;->等價形式 auto pd = &pa;
pd指向數組,*pd就是數組,而(*pd)[i]是數組中的元素,即函數指針
函數調用:
(*pd)[i](av,3)->此處返回const double *
*(*pd)[i](av,3)->此處返回 double
另外一種函數調用略複雜(*(*pd)[i])(av,3)->返回const double *
*(*(*pd)[i])(av,3)->返回 double
typedef簡化工作量
typedef const double* (*p_fun)(const double* ,int);
p_fun p1 = f1;
p_fun pa[3] = {f1,f2,f3};
p_fun (*pd)[3] = &pa;
namespace
namespace
- namespace只能在全局範圍內定義,但是可以相互嵌套
- 在namespace定義的結尾,右花括號後面不必跟一個分號
- 可以按類的語法來定義一個namespace,定義的內容可以在多個頭文件中延續,就好像重複定義這個namespace
//:header1.h
namespace MyLib{
extern int x;
void f();
//...}
//:~
//:header2.h
namespace MyLib{
extern int y;
void g();
//...}
- 一個namespace的名字可以用另一個名字來作為它的別名
- namespace lib = MyLib;
- 不能像類一樣創建一個名字空間的實例
未命名的名字空間
namespace {
class A{};
class B{};
int i,j,k;
//...}
將局部名字放在一個未命名的名字空間中,不需要加上static就可以作為內部連接
using directive& using declaration
using directive:using namespace xxusing declaration:using xx::f;
new/malloc/delete/free
new/malloc
new計算內存大小,並調用構造函數,malloc需要手工計算內存大小,不調用構造函數
delete/free
delete先執行析構函數,在清空內存,free直接清空內存
delete用於void*時將不會調用析構函數,直接清空內存
重載全局new和delete
- 重載的new必須有一個size_t參數,該參數由編譯器產生並傳遞給我們,分配內存的長度,必須返回一個指向等於該長度的對象的指針,如果沒有找到存儲單元,則返回一個0,然而如果找不到存儲單元,不能僅僅返回0,還應該有new-handler或產生一個異常信息
- new返回void*,而不是指向任何特定類型的指針,只需要完成內存分配,而不是完成對象的創建,直到構造函數調用才能完成對象的創建,調用構造函數是編譯器完成的
- delete參數是由new分配的void*指針,該參數是在調用析構函數後得到的指針,析構函數從存儲單元中移去對象
#include#include using namespace std;void* operator new(size_t sz){ printf("operator new:%d Bytes\\n",sz); void* m = malloc(sz); if(!m) puts("out of memry"); return m;
}void operator delete(void* m){ puts("operator delete"); free(m);
}class S{ int i[100];public:
S(){puts("S::S()");}
~S(){puts("S::~S()");}
};int main(int argc, char *argv[])
{ int * p = new int(47); delete p;
S* s = new S; delete s;
S* sa = new S[3]; delete [] sa; return 0;
}//:outputoperator new:4 Bytesoperator deleteoperator new:400 Bytes
S::S()
S::~S()operator deleteoperator new:1208 Bytes
S::S()
S::S()
S::S()
S::~S()
S::~S()
S::~S()operator delete
這裡使用printf(),puts()等函數,而不是iostreams,因為使用iostreams對象時(全局對象cin,cout,cerr),調用new分配內存,printf不會進入死鎖狀態,它不調用new來初始化自身
類重載new和delete
//p328
主要思想:使用static數組以及一個bool數組,返回static數組的下標地址,進行new,得到static下標數組進行delete
void* operator new(size_t) throw(bad_alloc);
void operator delete(void*);
為數組重載new和delete
//p331
定位new/delete
//p333
主要思想:使用new運算符重載,但是不對delete運算符重載,指定內存位置new
void* operator new(size_t,void*);
繼承與組合(Inheritance&Composition)
初始化表達式
- 成員對象如果沒有默認的構造函數,必須顯示的進行初始化,即在構造函數初始化列表中進行初始化
- 只有執行成員類型初始化之後,才會進入構造函數
- 沒有對所有成員以及基類對象的構造函數調用之前,若基類沒有默認構造函數則必須初始化,即在初始化列表中進行初始化,否則無法進入構造函數體
構造函數和析構函數調用的順序
- 構造函數調用的順序:首先調用基類構造函數,然後調用成員對象的構造函數,調用成員對象構造函數的順序是按照成員對象在類中聲明的順序執行,最後調用自己的構造函數
- 析構函數調用次序與構造函數調用次序相反,先調用自己的析構函數,在調用成員函數的析構函數,最後調用基類析構函數
- 對於多重繼承,構造函數調用順序為繼承時的聲明順序
非自動繼承的函數
- 構造函數
- 析構函數
- operator=
繼承與靜態成員函數
靜態成員函數與非靜態成員函數的共同特點:
1. 均可以被繼承到派生類中
2. 重新定義一個靜態成員,所有基類中的其他重載函數會被隱藏
3. 如果我們改變了基類中的函數的特徵,所有使用該函數名字的基類版本將會被隱藏。
4. 靜態成員函數不可以是虛函數
私有繼承
- 使用私有繼承是為了不允許該對象的處理像一個基類對象,一般private更適合於組合
- 私有繼承成員公有化
class Base{ public: Base(){}
~Base(){} void name() {cout << "Base name" <}
class Derived:private Base{ public: Derived(){}
~Derived(){} using Base::name; //私有繼承公有化}
向上類型轉換和拷貝構造函數
class Base{ public: Base(){}
Base(const Base&){}
~Base(){} void name() {cout << "Base name" <}
class Derived:public Base{ public: Derived(){}
Derived(const Derived& d):Base(d){ }//這裡是調用基類的拷貝構造函數
~Derived(){}
}
基類拷貝構造函數的調用將一個Derived引用向上類型轉換成一個Base引用,並且使用它來執行拷貝構造函數,向上類型轉換是安全的
運算符重載
+=,-=,*=,/=,=等一類運算符重載
首先進行自我檢查(是否對自身賦值),即this == &val,在進行運算,「=」運算符只允許作為成員函數進行重載
函數參數和返回值說明
- 對於任何函數參數,如果僅需要從參數中讀而不改變它,默認地應當做const引用傳遞。普通算數運算符(像「+」,「-」)和bool運算符不會改變參數,所以const引用為主要傳遞方式,當函數時成員函數時,就轉換為const成員函數。只有會改變左側參數的運算符賦值(如+=)和operator=,左側參數不是常量,但因參數將被改變,所以參數仍然按地址傳遞
- 返回值類型取決於運算符的具體含義,如果使用該運算符產生一個新值,就需要產生一個作為返回對象的新對象。例如operator+必須生成一個操作數之和的對象,該對象作為一個常量通過返回值返回,所以作為一個左值不會被改變
- 所有賦值運算符均改變左值,為了使賦值結果能用於鏈式表達式(如a=b=c),應該能夠返回一個剛剛改變了的左值的引用。但該引用並非一定要是常量引用,如(a=b).f(),這裡b賦值給a,a調用成員函數f,因此所有賦值運算符的返回值對於左值應該是非常量引用(如果是常量引用,則f成員函數必須為const函數,否則無法調用該函數,這與願望相違背)
- 對於邏輯運算符,人們至少希望得到一個int返回值,或者最好是bool值
Prefix ++ & Postfix ++
- 成員函數
const Object& operator++(){}const Object operator++(int){}
- 友元函數
const Object& operator++(Object& obj){}const Object operator++(Object& obj,int){}
對於友元函數重載來說,因為傳入的Object對象被改變,所以使用非常量引用
前綴通過引用返回,後綴通過值(臨時對象)返回,因為後綴返回臨時對象,所以後綴通過常量值返回,前綴返回引用,如果希望可以繼續改變對象則返回引用,否則通過常量引用返回比較合適,這樣與後綴保持了一致性
常量傳值返回與返回值優化
- 作為常量通常通過傳值方式返回。考慮二元運算符+,假設在表達式f(a+b)中使用,a+b的結果變為一個臨時對象(Object),該對象被f()調用,因為它為臨時的,所以自動被定義為常量,所以無論是否返回值為常量都沒有關係。但是如果使用(a+b).f(),這裡設返回值為常量規定了對於返回值只有常量成員函數才可以調用
- 返回值優化通過傳值方式返回要創建的的新對象時,注意使用的形式,如operator+
version 1:return Object(lObj.i+rObj.i);
version 2:
Object tmp(lObj.i+rObj.i);return tmp;
version 2將會發生三件事,首先創建tmp對象,然後調用拷貝構造函數把tmp拷貝到外部返回值的存儲單元中,最後當tmp在作用域的結尾時調用析構函數
version 1編譯器直接將該對象創建在外部返回值的內存單元,不是整的創建一個局部變量所以僅需要一個普通的構造函數調用(不需要拷貝構造函數),且不會調用析構函數,效率高。這種方式被稱為返回值優化。
operator[]
該運算符必須是成員函數,而且只接受一個參數,可以返回一個引用,可以用於等號左側。
operator->(指針間接引用運算符)
該運算符一定是一個成員函數,它必須返回一個對象(或者引用),該對象也有一個指針間接引用運算符;或者必須返回一個指針,被用於選擇指針間接引用運算符箭頭所指的內容
class Obj{ static int i,j; public: void f() const {cout<};int Obj::i = 47;int Obj::j = 11;class ObjContainer{ vectora; public: void add(Obj* obj) {a.push_back(obj);} friend class SmartPointer;
};class SmartPointer{
ObjContainer& oc; int index; public:
SmartPointer(ObjContainer & obj):oc(obj){
index = 0;
} bool operator++(){ if(index >= oc.a.size()) return false; if(oc.a[++index] == 0) return false; return true;
} bool operator++(int){ return operator++();
}
Obj* operator->() const{ return oc.a[index];
}
};
指針間接引用運算符自動的為用SmartPointer::operator->返回的Obj*調用成員函數
class Obj{ static int i,j; public: void f() const {cout<};int Obj::i = 47;int Obj::j = 11;class ObjContainer{ vectora; public: void add(Obj* obj) {a.push_back(obj);} class SmartPointer; //聲明友元之前必須告知該類存在
friend SmartPointer; class SmartPointer{
ObjContainer& oc; int index; public:
SmartPointer(ObjContainer & obj):oc(obj){
index = 0;
} bool operator++(){ if(index >= oc.a.size()) return false; if(oc.a[++index] == 0) return false; return true;
} bool operator++(int){ return operator++();
}
Obj* operator->() const{ return oc.a[index];
}
};
SmartPointer begin(){ return SmartPointer(*this);
}
};
operator->*
//p294
運算符成員函數基本方針
- 所有一元運算符建議為成員
- =()[]->->*必須為成員
- += -= /= *= ^= &= |= %= >>= <<=建議為成員
- 所有其他二元運算符為非成員
copy-on-write
//p301引用計數
自動類型轉換
- 構造函數轉換:構造函數能把另外一個類型對象(或者引用)作為它的單個參數,該構造函數允許編譯器執行自動類型轉換
- 運算符轉換:運算符重載,創建一個成員函數,該函數通過關鍵字operator後跟隨想要轉換的類型的方法,將當前類型轉換為希望的類型,自動類型轉換隻發生在函數調用值中,而不在成員選擇期間
class Three{ int i; public: Three(int ii = 0,int = 0):i(ii){}
};
class Four{ int x; public: Four(int xx):x(xx){} operator Three() const {return Three(x);}
};void g(Three){}int main(){
Four four(1);
g(four);
g(1);
}
- 二義性錯誤
class Orange;
class Apple{ public: operator Orange() const;
};
class Orange{ public: Orange(Apple);
};void f(Orange){}int main(){
Apple a; // f(a); error:二義性錯誤}
- 扇出錯誤:提供不止一種類型的自動轉換
class Orange{};class Pear{};class Apple{
public:
operator Orange() const;
operator Pear() const;
};void eat(Orange);void eat(Apple);int main(){
Apple a; //eat(a); error:扇出錯誤}
相關文章:
【讀書筆記】精通CSS 第二版
《深入理解bootstrap》讀書筆記:第一章 入門準備
相關視頻:
李炎恢PHP視頻教程第一季
以上就是Thinking in C++ 第一卷閱讀全書筆記重點總結的詳細內容,更多請關注其它相關文章!
更多技巧請《轉發 + 關注》哦!