本文主要是閱讀Thinking in C++ 第一卷的一些筆記。主要是一些注意點
Translator:
編譯器編譯程序步驟:
靜態類型檢查在第一遍中進行
函數或者變量的聲明與定義
void function();//聲明void function(){}//定義extern int a;//聲明int a;//定義
連接
編譯過程的最後階段,把編譯器生成的目標模塊連接成可執行文件(作業系統可以識別)
函數返回說明
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種類)
自動(局部)變量只是臨時存在於堆棧中,連接器不知道自動變量,所以這些變量沒有連接
預處理宏
#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
enum說明:
c++中對enum的檢查更為嚴格,c中允許a++(a為color型枚舉),但是c++中不允許,因為a++做了兩次轉換,首先將color類型轉換為int,然後自增1之後,將該值在轉換成color,第二次轉換時非法的
友元
使用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成員權限,可以使用如下方法訪問
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是一個不完全的類型說明或者類聲明,具體類定義放在了實現文件中
構造函數相應說明
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]必須有默認構造函數進行初始化,否則報錯若且唯若沒有構造函數時編譯器會自動創建一個默認構造函數
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;
}
默認參數使用規則:
占位符參數
void f(int i, int = 0, float = 1.1); //version 1void f(int i ,int , float flt); // version 2
其中version 2除了i,flt之外中間參數就是占位符參數
const說明
C++中const默認為內部連接,僅在const被定義的文件中才可見,在連接時不能被其它編譯單元看見,當定義一個const時必須賦值給它,除非使用extern進行說明
extern const int bufsize;
通常c++不為const創建空間,將其定義保存在符號表內,但是上面的extern進行了強制內存空間分配,另外如取const的地址也是需要存儲空間的分配。
對於複雜的結構,編譯器建立存儲,阻止常量摺疊。在C中const默認為外部連接,C++默認為內部連接.出現在所有函數外部的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)
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初始化
必須在構造函數的初始化列表中進行初始化
編譯期間的常量
class X{
enum {size = 1000}; //same as static const
static const int size = 1000; int i[size];
}
const對象和成員函數
內聯函數
內聯函數與普通函數一樣執行,但是內聯函數在適當的地方像宏一樣展開,不需要函數調用的開銷(壓棧,出棧),任何在類內部定義的函數自動成為內聯函數
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<
static
static對象
內部連結
常量、內聯函數默認情況下為內部連結
others
stactic/const/stctic const
靜態成員函數
優先級低的先讀
*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
//:header1.h
namespace MyLib{
extern int x;
void f();
//...}
//:~
//:header2.h
namespace MyLib{
extern int y;
void g();
//...}
未命名的名字空間
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
new計算內存大小,並調用構造函數,malloc需要手工計算內存大小,不調用構造函數
delete/free
delete先執行析構函數,在清空內存,free直接清空內存
delete用於void*時將不會調用析構函數,直接清空內存
重載全局new和delete
#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*);
初始化表達式
構造函數和析構函數調用的順序
非自動繼承的函數
繼承與靜態成員函數
靜態成員函數與非靜態成員函數的共同特點:
1. 均可以被繼承到派生類中
2. 重新定義一個靜態成員,所有基類中的其他重載函數會被隱藏
3. 如果我們改變了基類中的函數的特徵,所有使用該函數名字的基類版本將會被隱藏。
4. 靜態成員函數不可以是虛函數
私有繼承
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,在進行運算,「=」運算符只允許作為成員函數進行重載
函數參數和返回值說明
Prefix ++ & Postfix ++
const Object& operator++(){}const Object operator++(int){}
const Object& operator++(Object& obj){}const Object operator++(Object& obj,int){}
對於友元函數重載來說,因為傳入的Object對象被改變,所以使用非常量引用
前綴通過引用返回,後綴通過值(臨時對象)返回,因為後綴返回臨時對象,所以後綴通過常量值返回,前綴返回引用,如果希望可以繼續改變對象則返回引用,否則通過常量引用返回比較合適,這樣與後綴保持了一致性
常量傳值返回與返回值優化
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引用計數
自動類型轉換
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++ 第一卷閱讀全書筆記重點總結的詳細內容,更多請關注其它相關文章!
更多技巧請《轉發 + 關注》哦!