參考鏈接:
-在設(shè)計(jì)類的時(shí)候,一個(gè)原則就是對(duì)于不改變數(shù)據(jù)成員的成員函數(shù)都要在后面加 const,而對(duì)于改變數(shù)據(jù)成員的成員函數(shù)不能加 const。所以 const 關(guān)鍵字對(duì)成員函數(shù)的行為作了更加明確的限定:有 const 修飾的成員函數(shù)(指 const 放在函數(shù)參數(shù)表的后面,而不是在函數(shù)前面或者參數(shù)表內(nèi)),只能讀取數(shù)據(jù)成員,不能改變數(shù)據(jù)成員;沒有 const 修飾的成員函數(shù),對(duì)數(shù)據(jù)成員則是可讀可寫的。
除此之外,在類的成員函數(shù)后面加 const 還有什么好處呢?
“獲得能力:可以操作常量對(duì)象”,其實(shí)應(yīng)該是常量(即 const)對(duì)象可以調(diào)用 const 成員函數(shù),而不能調(diào)用非const修飾的函數(shù)。
對(duì)于const成員函數(shù),"不能修改類的數(shù)據(jù)成員,不能在函數(shù)中調(diào)用其他不是const的函數(shù)",這是由const的屬性決定的 。
static靜態(tài)成員變量不能在類的內(nèi)部初始化。在類的內(nèi)部只是聲明,定義必須在類定義體的外部,通常在類的實(shí)現(xiàn)文件中初始化。
由于聲明為static的變量只被初始化一次,因?yàn)樗鼈冊(cè)趩为?dú)的靜態(tài)存儲(chǔ)中分配了空間,因此類中的靜態(tài)變量由對(duì)象共享。對(duì)于不同的對(duì)象,不能有相同靜態(tài)變量的多個(gè)副本。也是因?yàn)檫@個(gè)原因,靜態(tài)變量不能使用構(gòu)造函數(shù)初始化。
類中的靜態(tài)變量應(yīng)由用戶使用類外的類名和范圍解析運(yùn)算符顯式初始化
靜態(tài)對(duì)象的范圍是貫穿程序的生命周期
一個(gè)對(duì)象的this指針并不是對(duì)象本身的一部分,不會(huì)影響sizeof(對(duì)象)的結(jié)果。
在類的非靜態(tài)成員函數(shù)中返回類對(duì)象本身的時(shí)候,直接使用 return *this。
當(dāng)參數(shù)與成員變量名相同時(shí),如this->n = n (不能寫成n = n)。
this在成員函數(shù)的開始執(zhí)行前構(gòu)造,在成員的執(zhí)行結(jié)束后清除。
this類型為const A* const。A為類。
內(nèi)聯(lián)能提高函數(shù)效率,但并不是所有的函數(shù)都定義成內(nèi)聯(lián)函數(shù)!內(nèi)聯(lián)是以代碼膨脹(復(fù)制)為代價(jià),僅僅省去了函數(shù)調(diào)用的開銷,從而提高函數(shù)的執(zhí)行效率。
虛函數(shù)可以是內(nèi)聯(lián)函數(shù),內(nèi)聯(lián)是可以修飾虛函數(shù)的,但是當(dāng)虛函數(shù)表現(xiàn)多態(tài)性的時(shí)候不能內(nèi)聯(lián)。
內(nèi)聯(lián)是在編譯器建議編譯器內(nèi)聯(lián),而虛函數(shù)的多態(tài)性在運(yùn)行期,編譯器無法知道運(yùn)行期調(diào)用哪個(gè)代碼,因此虛函數(shù)表現(xiàn)為多態(tài)性時(shí)(運(yùn)行期)不可以內(nèi)聯(lián)。
inline virtual 唯一可以內(nèi)聯(lián)的時(shí)候是:編譯器知道所調(diào)用的對(duì)象是哪個(gè)類(如 Base::who()),這只有在編譯器具有實(shí)際對(duì)象而不是對(duì)象的指針或引用時(shí)才會(huì)發(fā)生。
sizeof: 普通繼承,派生類繼承了所有基類的函數(shù)與成員,要按照字節(jié)對(duì)齊來計(jì)算大小
sizeof:虛函數(shù)繼承,不管是單繼承還是多繼承,都是繼承了基類的vptr。(32位操作系統(tǒng)4字節(jié),64位操作系統(tǒng) 8字節(jié))!
sizeof: 靜態(tài)變量不影響類的大小
sizeof:對(duì)于包含虛函數(shù)的類,不管有多少個(gè)虛函數(shù),只有一個(gè)虛指針,vptr的大小。
sizeof: 派生類虛繼承多個(gè)虛函數(shù),會(huì)繼承所有虛函數(shù)的vptr。
抽象類中:在成員函數(shù)內(nèi)可以調(diào)用純虛函數(shù),在構(gòu)造函數(shù)/析構(gòu)函數(shù)內(nèi)部不能使用純虛函數(shù)。
如果一個(gè)類從抽象類派生而來,它必須實(shí)現(xiàn)了基類中的所有純虛函數(shù),才能成為非抽象類。
抽象類至少包含一個(gè)純虛函數(shù)
不能創(chuàng)建抽象類的對(duì)象
抽象類的指針和引用 指向 由抽象類派生出來的類的對(duì)象
派生類沒有實(shí)現(xiàn)純虛函數(shù),那么派生類也會(huì)變?yōu)槌橄箢悾荒軇?chuàng)建抽象類的對(duì)象
虛函數(shù)的調(diào)用取決于指向或者引用的對(duì)象的類型,而不是指針或者引用自身的類型。
默認(rèn)參數(shù)是靜態(tài)綁定的,虛函數(shù)是動(dòng)態(tài)綁定的。 默認(rèn)參數(shù)的使用需要看指針或者引用本身的類型,而不是對(duì)象的類型。
靜態(tài)函數(shù)不可以聲明為虛函數(shù),同時(shí)也不能被const 和 volatile關(guān)鍵字修飾
為什么構(gòu)造函數(shù)不可以為虛函數(shù)?
解:盡管虛函數(shù)表vtable是在編譯階段就已經(jīng)建立的,但指向虛函數(shù)表的指針vptr是在運(yùn)行階段實(shí)例化對(duì)象時(shí)才產(chǎn)生的。 如果類含有虛函數(shù),編譯器會(huì)在構(gòu)造函數(shù)中添加代碼來創(chuàng)建vptr。 問題來了,如果構(gòu)造函數(shù)是虛的,那么它需要vptr來訪問vtable,可這個(gè)時(shí)候vptr還沒產(chǎn)生。 因此,構(gòu)造函數(shù)不可以為虛函數(shù)。虛函數(shù)可以被私有化,但有一些細(xì)節(jié)需要注意。
1 基類指針指向繼承類對(duì)象,則調(diào)用繼承類對(duì)象的函數(shù);
2 int main()必須聲明為Base類的友元,否則編譯失敗。 編譯器報(bào)錯(cuò): ptr無法訪問私有函數(shù)。
3 當(dāng)然,把基類聲明為public, 繼承類為private,該問題就不存在了。volatile 關(guān)鍵字聲明的變量,每次訪問時(shí)都必須從內(nèi)存中取出值(沒有被 volatile 修飾的變量,可能由于編譯器的優(yōu)化,從 CPU 寄存器中取值)
const 可以是 volatile (如只讀的狀態(tài)寄存器)
指針可以是 volatile
斷言,是宏,而非函數(shù)。assert 宏的原型定義在 (C)、(C++)中,其作用是如果它的條件返回錯(cuò)誤,則終止程序執(zhí)行??梢酝ㄟ^定義 NDEBUG 來關(guān)閉 assert,但是需要在源代碼的開頭,include 之前。
斷言主要用于檢查邏輯上不可能的情況。
它們可用于檢查代碼在開始運(yùn)行之前所期望的狀態(tài),或者在運(yùn)行完成后檢查狀態(tài)。與正常的錯(cuò)誤處理不同,斷言通常在運(yùn)行時(shí)被禁用。
忽略斷言:在代碼開頭加上:#define NDEBUG // 加上這行,則 assert 不可用
extern "C"全部都放在于cpp程序相關(guān)文件或其頭文件中。
在C中struct只單純的用作數(shù)據(jù)的復(fù)合類型,也就是說,在結(jié)構(gòu)體聲明中只能將數(shù)據(jù)成員放在里面,而不能將函數(shù)放在里面。
在C結(jié)構(gòu)體聲明中不能使用C++訪問修飾符,如:public、protected、private 而在C++中可以使用。
在C中定義結(jié)構(gòu)體變量,如果使用了下面定義必須加struct。
C的結(jié)構(gòu)體不能繼承(沒有這一概念)。
若結(jié)構(gòu)體的名字與函數(shù)名相同,可以正常運(yùn)行且正常的調(diào)用!例如:可以定義與 struct Base 不沖突的 void Base() {}。
與C對(duì)比如下:
1.C++結(jié)構(gòu)體中不僅可以定義數(shù)據(jù),還可以定義函數(shù)。
2.C++結(jié)構(gòu)體中可以使用訪問修飾符,如:public、protected、private 。
3.C++結(jié)構(gòu)體使用可以直接使用不帶struct。
4.C++繼承若結(jié)構(gòu)體的名字與函數(shù)名相同,可以正常運(yùn)行且正常的調(diào)用!但是定義結(jié)構(gòu)體變量時(shí)候只用帶struct的!union
1.默認(rèn)訪問控制符為 public
2.可以含有構(gòu)造函數(shù)、析構(gòu)函數(shù)
3.不能含有引用類型的成員
4.不能繼承自其他類,不能作為基類
5.不能含有虛函數(shù)
6.匿名 union 在定義所在作用域可直接訪問 union 成員
7.匿名 union 不能包含 protected 成員或 private 成員
8.全局匿名聯(lián)合必須是靜態(tài)(static)的-
.C實(shí)現(xiàn)多態(tài)
- 封裝
C語言中是沒有class類這個(gè)概念的,但是有struct結(jié)構(gòu)體,我們可以考慮使用struct來模擬;
使用函數(shù)指針把屬性與方法封裝到結(jié)構(gòu)體中。 - 繼承
結(jié)構(gòu)體嵌套 - 多態(tài)、
類與子類方法的函數(shù)指針不同
在C語言的結(jié)構(gòu)體內(nèi)部是沒有成員函數(shù)的,如果實(shí)現(xiàn)這個(gè)父結(jié)構(gòu)體和子結(jié)構(gòu)體共有的函數(shù)呢?我們可以考慮使用函數(shù)指針來模擬。但是這樣處理存在一個(gè)缺陷就是:父子各自的函數(shù)指針之間指向的不是類似C++中維護(hù)的虛函數(shù)表而是一塊物理內(nèi)存,如果模擬的函數(shù)過多的話就會(huì)不容易維護(hù)了。
模擬多態(tài),必須保持函數(shù)指針變量對(duì)齊(在內(nèi)容上完全一致,而且變量對(duì)齊上也完全一致)。否則父類指針指向子類對(duì)象,運(yùn)行崩潰!
#include <stdio.h>
// 重定義一個(gè)函數(shù)指針類型
typedef void (*pf) ();
/**
父類
*/
typedef struct _A
{
pf _f;
}A;
/**
子類
*/
typedef struct _B
{
A _b; // 在子類中定義一個(gè)基類的對(duì)象即可實(shí)現(xiàn)對(duì)父類的繼承。
}B;
void FunA()
{
printf("%s\n","Base A::fun()");
}
void FunB()
{
printf("%s\n","Derived B::fun()");
}
int main()
{
A a;
B b;
a._f = FunA;
b._b._f = FunB;
A *pa = &a;
pa->_f();
pa = (A *)&b; // 讓父類指針指向子類的對(duì)象,由于類型不匹配所以要進(jìn)行強(qiáng)轉(zhuǎn)
pa->_f();
return 0;
}
- explicit 修飾構(gòu)造函數(shù)時(shí),可以防止隱式轉(zhuǎn)換和復(fù)制初始化
- explicit 修飾轉(zhuǎn)換函數(shù)時(shí),可以防止隱式轉(zhuǎn)換,但按語境轉(zhuǎn)換除外
#include <iostream>
using namespace std;
struct A
{
A(int) { }
operator bool() const { return true; }
};
struct B
{
explicit B(int) {}
explicit operator bool() const { return true; }
};
void doA(A a) {}
void doB(B b) {}
int main()
{
A a1(1); // OK:直接初始化
A a2 = 1; // OK:復(fù)制初始化
A a3{ 1 }; // OK:直接列表初始化
A a4 = { 1 }; // OK:復(fù)制列表初始化
A a5 = (A)1; // OK:允許 static_cast 的顯式轉(zhuǎn)換
doA(1); // OK:允許從 int 到 A 的隱式轉(zhuǎn)換
if (a1); // OK:使用轉(zhuǎn)換函數(shù) A::operator bool() 的從 A 到 bool 的隱式轉(zhuǎn)換
bool a6(a1); // OK:使用轉(zhuǎn)換函數(shù) A::operator bool() 的從 A 到 bool 的隱式轉(zhuǎn)換
bool a7 = a1; // OK:使用轉(zhuǎn)換函數(shù) A::operator bool() 的從 A 到 bool 的隱式轉(zhuǎn)換
bool a8 = static_cast<bool>(a1); // OK :static_cast 進(jìn)行直接初始化
B b1(1); // OK:直接初始化
// B b2 = 1; // 錯(cuò)誤:被 explicit 修飾構(gòu)造函數(shù)的對(duì)象不可以復(fù)制初始化
B b3{ 1 }; // OK:直接列表初始化
// B b4 = { 1 }; // 錯(cuò)誤:被 explicit 修飾構(gòu)造函數(shù)的對(duì)象不可以復(fù)制列表初始化
B b5 = (B)1; // OK:允許 static_cast 的顯式轉(zhuǎn)換
// doB(1); // 錯(cuò)誤:被 explicit 修飾構(gòu)造函數(shù)的對(duì)象不可以從 int 到 B 的隱式轉(zhuǎn)換
if (b1); // OK:被 explicit 修飾轉(zhuǎn)換函數(shù) B::operator bool() 的對(duì)象可以從 B 到 bool 的按語境轉(zhuǎn)換
bool b6(b1); // OK:被 explicit 修飾轉(zhuǎn)換函數(shù) B::operator bool() 的對(duì)象可以從 B 到 bool 的按語境轉(zhuǎn)換
// bool b7 = b1; // 錯(cuò)誤:被 explicit 修飾轉(zhuǎn)換函數(shù) B::operator bool() 的對(duì)象不可以隱式轉(zhuǎn)換
bool b8 = static_cast<bool>(b1); // OK:static_cast 進(jìn)行直接初始化
return 0;
}
- 友元提供了一種 普通函數(shù)或者類成員函數(shù) 訪問另一個(gè)類中的私有或保護(hù)成員 的機(jī)制。也就是說有兩種形式的友元:
(1)友元函數(shù):普通函數(shù)對(duì)一個(gè)訪問某個(gè)類中的私有或保護(hù)成員。
(2)友元類:類A中的成員函數(shù)訪問類B中的私有或保護(hù)成員
優(yōu)點(diǎn):提高了程序的運(yùn)行效率。
缺點(diǎn):破壞了類的封裝性和數(shù)據(jù)的透明性。
總結(jié): - 能訪問私有成員 - 破壞封裝性 - 友元關(guān)系不可傳遞 - 友元關(guān)系的單向性 - 友元聲明的形式及數(shù)量不受限制
在類聲明的任何區(qū)域中聲明,而定義則在類的外部。
friend <類型><友元函數(shù)名>(<參數(shù)表>);
注意,友元函數(shù)只是一個(gè)普通函數(shù),并不是該類的類成員函數(shù),它可以在任何地方調(diào)用,友元函數(shù)中通過對(duì)象名來訪問該類的私有或保護(hù)成員。
#include <iostream>
using namespace std;
class A
{
public:
A(int _a):a(_a){};
friend int geta(A &ca); ///< 友元函數(shù)
private:
int a;
};
int geta(A &ca)
{
return ca.a;
}
int main()
{
A a(3);
cout<<geta(a)<<endl;
return 0;
}
友元類的聲明在該類的聲明中,而實(shí)現(xiàn)在該類外。
friend class <友元類名>;
類B是類A的友元,那么類B可以直接訪問A的私有成員。
#include <iostream>
using namespace std;
class A
{
public:
A(int _a):a(_a){};
friend class B;
private:
int a;
};
class B
{
public:
int getb(A ca) {
return ca.a;
};
};
int main()
{
A a(3);
B b;
cout<<b.getb(a)<<endl;
return 0;
}
友元關(guān)系沒有繼承性 假如類B是類A的友元,類C繼承于類A,那么友元類B是沒辦法直接訪問類C的私有或保護(hù)成員。
友元關(guān)系沒有傳遞性 假如類B是類A的友元,類C是類B的友元,那么友元類C是沒辦法直接訪問類A的私有或保護(hù)成員,也就是不存在“友元的友元”這種關(guān)系。
- using
#include <iostream>
#define isNs1 1
//#define isGlobal 2
using namespace std;
void func()
{
cout<<"::func"<<endl;
}
namespace ns1 {
void func()
{
cout<<"ns1::func"<<endl;
}
}
namespace ns2 {
#ifdef isNs1
using ns1::func; /// ns1中的函數(shù)
#elif isGlobal
using ::func; /// 全局中的函數(shù)
#else
void func()
{
cout<<"other::func"<<endl;
}
#endif
}
int main()
{
/**
* 這就是為什么在c++中使用了cmath而不是math.h頭文件
*/
ns2::func(); // 會(huì)根據(jù)當(dāng)前環(huán)境定義宏的不同來調(diào)用不同命名空間下的func()函數(shù)
return 0;
}
class Base{
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};
class Derived : private Base {
public:
using Base::size;
protected:
using Base::n;
};
在繼承過程中,派生類可以覆蓋重載函數(shù)的0個(gè)或多個(gè)實(shí)例,一旦定義了一個(gè)重載版本,那么其他的重載版本都會(huì)變?yōu)椴豢梢姟?/p>
如果對(duì)于基類的重載函數(shù),我們需要在派生類中修改一個(gè),又要讓其他的保持可見,必須要重載所有版本,這樣十分的繁瑣。
#include <iostream>
using namespace std;
class Base{
public:
void f(){ cout<<"f()"<<endl;
}
void f(int n){
cout<<"Base::f(int)"<<endl;
}
};
class Derived : private Base {
public:
using Base::f;
void f(int n){
cout<<"Derived::f(int)"<<endl;
}
};
int main()
{
Base b;
Derived d;
d.f();
d.f(1);
return 0;
}
如上代碼中,在派生類中使用using聲明語句指定一個(gè)名字而不指定形參列表,所以一條基類成員函數(shù)的using聲明語句就可以把該函數(shù)的所有重載實(shí)例添加到派生類的作用域中。此時(shí),派生類只需要定義其特有的函數(shù)就行了,而無需為繼承而來的其他函數(shù)重新定義。
C中常用typedef A B這樣的語法,將B定義為A類型,也就是給A類型一個(gè)別名B
對(duì)應(yīng)typedef A B,使用using B=A可以進(jìn)行同樣的操作。
typedef vector<int> V1;
using V2 = vector<int>;
全局作用域符(::name):用于類型名稱(類、類成員、成員函數(shù)、變量等)前,表示作用域?yàn)槿置臻g
類作用域符(class::name):用于表示指定類型的作用域范圍是具體某個(gè)類的
命名空間作用域符(namespace::name):用于表示指定類型的作用域范圍是具體某個(gè)命名空間的
-
enum:
傳統(tǒng)問題:- 作用域不受限,會(huì)容易引起命名沖突。例如下面無法編譯通過的:
#include <iostream>
using namespace std;
enum Color {RED,BLUE};
enum Feeling {EXCITED,BLUE};
int main()
{
return 0;
}
- 會(huì)隱式轉(zhuǎn)換為int
- 用來表征枚舉變量的實(shí)際類型不能明確指定,從而無法支持枚舉類型的前向聲明。
解決作用域不受限帶來的命名沖突問題的一個(gè)簡單方法是,給枚舉變量命名時(shí)加前綴,如上面例子改成 COLOR_BLUE 以及 FEELING_BLUE。
namespace Color
{
enum Type
{
RED=15,
YELLOW,
BLUE
};
};
這樣之后就可以用 Color::Type c = Color::RED; 來定義新的枚舉變量了。如果 using namespace Color 后,前綴還可以省去,使得代碼簡化。不過,因?yàn)槊臻g是可以隨后被擴(kuò)充內(nèi)容的,所以它提供的作用域封閉性不高。在大項(xiàng)目中,還是有可能不同人給不同的東西起同樣的枚舉類型名。
更“有效”的辦法是用一個(gè)類或結(jié)構(gòu)體來限定其作用域,例如:定義新變量的方法和上面命名空間的相同。不過這樣就不用擔(dān)心類在別處被修改內(nèi)容。這里用結(jié)構(gòu)體而非類,一是因?yàn)楸旧硐M@些常量可以公開訪問,二是因?yàn)樗话瑪?shù)據(jù)沒有成員函數(shù)。
struct Color1
{
enum Type
{
RED=102,
YELLOW,
BLUE
};
};
C++11 標(biāo)準(zhǔn)中引入了“枚舉類”(enum class),可以較好地解決上述問題。
- 新的enum的作用域不在是全局的
- 不能隱式轉(zhuǎn)換成其他類型
/**
* @brief C++11的枚舉類
* 下面等價(jià)于enum class Color2:int
*/
enum class Color2
{
RED=2,
YELLOW,
BLUE
};
r2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl; //必須轉(zhuǎn)!
- 可以指定用特定的類型來存儲(chǔ)enum
enum class Color3:char; // 前向聲明
// 定義
enum class Color3:char
{
RED='r',
BLUE
};
char c3 = static_cast<char>(Color3::RED);
class Person{
public:
typedef enum {
BOY = 0,
GIRL
}SexType;
};
//訪問的時(shí)候通過,Person::BOY或者Person::GIRL來進(jìn)行訪問。
枚舉常量不會(huì)占用對(duì)象的存儲(chǔ)空間,它們?cè)诰幾g時(shí)被全部求值。
枚舉常量的缺點(diǎn)是:它的隱含數(shù)據(jù)類型是整數(shù),其最大值有限,且不能表示浮點(diǎn)。
- 這里的括號(hào)是必不可少的,decltype的作用是“查詢表達(dá)式的類型”,因此,上面語句的效果是,返回 expression 表達(dá)式的類型。注意,decltype 僅僅“查詢”表達(dá)式的類型,并不會(huì)對(duì)表達(dá)式進(jìn)行“求值”。
decltype (expression)
int i = 4;
decltype(i) a; //推導(dǎo)結(jié)果為int。a的類型為int。
using size_t = decltype(sizeof(0));//sizeof(a)的返回值為size_t類型
using ptrdiff_t = decltype((int*)0 - (int*)0);
using nullptr_t = decltype(nullptr);
vector<int >vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin; i != vec.end(); i++)
{
//...
}
//在C++中,我們有時(shí)候會(huì)遇上一些匿名類型,如:
struct
{
int d ;
doubel b;
}anon_s;
//而借助decltype,我們可以重新使用這個(gè)匿名的結(jié)構(gòu)體:
decltype(anon_s) as ;//定義了一個(gè)上面匿名的結(jié)構(gòu)體
泛型編程中結(jié)合auto,用于追蹤函數(shù)的返回值類型
這也是decltype最大的用途了。
template <typename T>
auto multiply(T x, T y)->decltype(x*y)
{
return x*y;
}
對(duì)于decltype(e)而言,其判別結(jié)果受以下條件的影響:
如果e是一個(gè)沒有帶括號(hào)的標(biāo)記符表達(dá)式或者類成員訪問表達(dá)式,那么的decltype(e)就是e所命名的實(shí)體的類型。此外,如果e是一個(gè)被重載的函數(shù),則會(huì)導(dǎo)致編譯錯(cuò)誤。 否則 ,假設(shè)e的類型是T,如果e是一個(gè)將亡值,那么decltype(e)為T&& 否則,假設(shè)e的類型是T,如果e是一個(gè)左值,那么decltype(e)為T&。 否則,假設(shè)e的類型是T,則decltype(e)為T。
標(biāo)記符指的是除去關(guān)鍵字、字面量等編譯器需要使用的標(biāo)記之外的程序員自己定義的標(biāo)記,而單個(gè)標(biāo)記符對(duì)應(yīng)的表達(dá)式即為標(biāo)記符表達(dá)式。例如:
int arr[4]
則arr為一個(gè)標(biāo)記符表達(dá)式,而arr[3]+0不是。
int i = 4;
int arr[5] = { 0 };
int *ptr = arr;
struct S{ double d; }s ;
void Overloaded(int);
void Overloaded(char);//重載的函數(shù)
int && RvalRef();
const bool Func(int);
//規(guī)則一:推導(dǎo)為其類型
decltype (arr) var1; //int 標(biāo)記符表達(dá)式
decltype (ptr) var2;//int * 標(biāo)記符表達(dá)式
decltype(s.d) var3;//doubel 成員訪問表達(dá)式
//decltype(Overloaded) var4;//重載函數(shù)。編譯錯(cuò)誤。
//規(guī)則二:將亡值。推導(dǎo)為類型的右值引用。
decltype (RvalRef()) var5 = 1;
//規(guī)則三:左值,推導(dǎo)為類型的引用。
decltype ((i))var6 = i; //int&
decltype (true ? i : i) var7 = i; //int& 條件表達(dá)式返回左值。
decltype (++i) var8 = i; //int& ++i返回i的左值。
decltype(arr[5]) var9 = i;//int&. []操作返回左值
decltype(*ptr)var10 = i;//int& *操作返回左值
decltype("hello")var11 = "hello"; //const char(&)[9] 字符串字面常量為左值,且為const左值。
//規(guī)則四:以上都不是,則推導(dǎo)為本類型
decltype(1) var12;//const int
decltype(Func(1)) var13=true;//const bool
decltype(i++) var14 = i;//int i++返回右值
-
右值引用可實(shí)現(xiàn)轉(zhuǎn)移語義(Move Sementics)和精確傳遞(Perfect Forwarding),它的主要目的有兩個(gè)方面:
1.消除兩個(gè)對(duì)象交互時(shí)不必要的對(duì)象拷貝,節(jié)省運(yùn)算存儲(chǔ)資源,提高效率。
2.能夠更簡潔明確地定義泛型函數(shù)。
引用折疊
-
X& &、X& &&、X&& &可折疊成X& -
X&& &&可折疊成X&&
C++的引用在減少了程序員自由度的同時(shí)提升了內(nèi)存操作的安全性和語義的優(yōu)美性。
private 是完全私有的,只有當(dāng)前類中的成員能訪問到.
protected 是受保護(hù)的,只有當(dāng)前類的成員與繼承該類的類才能訪問.
- 在使用new的時(shí)候做了兩件事:
1、調(diào)用operator new分配空間
2、調(diào)用構(gòu)造函數(shù)初始化對(duì)象
- 在使用delete的時(shí)候也做了兩件事:
1、調(diào)用析構(gòu)函數(shù)清理對(duì)象
2、調(diào)用operator delete函數(shù)釋放空間
- 在使用new[N]的時(shí)候也做了兩件事:
1、調(diào)用operator new分配空間
2、調(diào)用N次構(gòu)造函數(shù)初始化N個(gè)對(duì)象
- 在使用delete[]的時(shí)候也做了兩件事:
1、調(diào)用N次析構(gòu)函數(shù)清理N個(gè)對(duì)象
2、調(diào)用operator delete函數(shù)釋放空間
-
1.1 字符串化操作符(#)
在一個(gè)宏中的參數(shù)前面使用一個(gè)#,預(yù)處理器會(huì)把這個(gè)參數(shù)轉(zhuǎn)換為一個(gè)字符數(shù)組,換言之就是:#是“字符串化”的意思,出現(xiàn)在宏定義中的#是把跟在后面的參數(shù)轉(zhuǎn)換成一個(gè)字符串。
-
1.2 符號(hào)連接操作符(##)
“##”是一種分隔連接方式,它的作用是先分隔,然后進(jìn)行強(qiáng)制連接。將宏定義的多個(gè)形參轉(zhuǎn)換成一個(gè)實(shí)際參數(shù)名。
注意事項(xiàng):
(1)當(dāng)用##連接形參時(shí),##前后的空格可有可無。
(2)連接后的實(shí)際參數(shù)名,必須為實(shí)際存在的參數(shù)名或是編譯器已知的宏定義。
(3)如果##后的參數(shù)本身也是一個(gè)宏的話,##會(huì)阻止這個(gè)宏的展開。
-
1.3 續(xù)行操作符(\
當(dāng)定義的宏不能用一行表達(dá)完整時(shí),可以用”\”表示下一行繼續(xù)此宏的定義。
注意 \ 前留空格。
template<typename ...Args>
class A {
private:
int size = 0; // c++11 支持類內(nèi)初始化
public:
A() {
size = sizeof...(Args);
cout << size << endl;
}
};
A<int, string, vector<int>> a; // 類型任意
// Tuple就是利用這個(gè)特性(變長參數(shù)模板)
tuple<int, string> t = make_tuple(1, "hha");
單例模式
//簡單實(shí)現(xiàn),只適用于單線程下。
//懶漢 線程不安全
class singleton {
private:
singleton() {}
static singleton *p;
public:
static singleton *instance();
};
singleton *singleton::p = nullptr;
singleton* singleton::instance() {
if (p == nullptr)
p = new singleton();
return p;
}
//餓漢 這個(gè)是線程安全的
class singleton {
private:
singleton() {}
static singleton *p;
public:
static singleton *instance();
};
singleton *singleton::p = new singleton();
singleton* singleton::instance() {
return p;
}
//多線程下單例模式
class singleton {
private:
singleton() {}
static singleton *p;
static mutex lock_;
public:
static singleton *instance();
};
singleton *singleton::p = nullptr;
singleton* singleton::instance() {
lock_guard<mutex> guard(lock_);
if (p == nullptr)
p = new singleton();
return p;
}
//雙重檢查鎖+自動(dòng)回收
class singleton {
private:
singleton() {}
static singleton *p;
static mutex lock_;
public:
singleton *instance();
// 實(shí)現(xiàn)一個(gè)內(nèi)嵌垃圾回收類
class CGarbo
{
public:
~CGarbo()
{
if(singleton::p)
delete singleton::p;
}
};
static CGarbo Garbo; // 定義一個(gè)靜態(tài)成員變量,程序結(jié)束時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用它的析構(gòu)函數(shù)從而釋放單例對(duì)象
};
singleton *singleton::p = nullptr;
singleton::CGarbo Garbo;
singleton* singleton::instance() {
if (p == nullptr) {
lock_guard<mutex> guard(lock_);
if (p == nullptr)
p = new singleton();
}
return p;
}
5.memory barrier指令
DCLP問題在C++11中,這個(gè)問題得到了解決。
因?yàn)樾碌腃++11規(guī)定了新的內(nèi)存模型,保證了執(zhí)行上述3個(gè)步驟的時(shí)候不會(huì)發(fā)生線程切換,相當(dāng)這個(gè)初始化過程是“原子性”的的操作,DCL又可以正確使用了,不過在C++11下卻有更簡潔的多線程singleton寫法了,這個(gè)留在后面再介紹。
C++11之前解決方法是barrier指令。要使其正確執(zhí)行的話,就得在步驟2、3直接加上一道m(xù)emory barrier。強(qiáng)迫CPU執(zhí)行的時(shí)候按照1、2、3的步驟來運(yùn)行。
第一種實(shí)現(xiàn):
基于operator new+placement new,遵循1,2,3執(zhí)行順序依次編寫代碼。
// method 1 operator new + placement new
singleton *instance() {
if (p == nullptr) {
lock_guard<mutex> guard(lock_);
if (p == nullptr) {
singleton *tmp = static_cast<singleton *>(operator new(sizeof(singleton)));
new(p)singleton();
p = tmp;
}
}
return p;
}
**第二種實(shí)現(xiàn):**
基于直接嵌入ASM匯編指令mfence,uninx的barrier宏也是通過該指令實(shí)現(xiàn)的。
#define barrier() __asm__ volatile ("lwsync")
singleton *singleton::instance() {
if (p == nullptr) {
lock_guard<mutex> guard(lock_);
barrier();
if (p == nullptr) {
p = new singleton();
}
}
return p;
}
通常情況下是調(diào)用cpu提供的一條指令,這條指令的作用是會(huì)阻止cpu將該指令之前的指令交換到該指令之后,這條指令也通常被叫做barrier。 上面代碼中的asm表示這個(gè)是一條匯編指令,volatile是可選的,如果用了它,則表示向編譯器聲明不允許對(duì)該匯編指令進(jìn)行優(yōu)化。lwsync是POWERPC提供的barrier指令。
- Scott Meyer在《Effective C++》中提出了一種簡潔的singleton寫法
singleton *singleton::instance() {
static singleton p;
return &p;
}
mutex singleton::lock_;
atomic<singleton *> singleton::p;
/*
* std::atomic_thread_fence(std::memory_order_acquire);
* std::atomic_thread_fence(std::memory_order_release);
* 這兩句話可以保證他們之間的語句不會(huì)發(fā)生亂序執(zhí)行。
*/
singleton *singleton::instance() {
singleton *tmp = p.load(memory_order_relaxed);
atomic_thread_fence(memory_order_acquire);
if (tmp == nullptr) {
lock_guard<mutex> guard(lock_);
tmp = p.load(memory_order_relaxed);
if (tmp == nullptr) {
tmp = new singleton();
atomic_thread_fence(memory_order_release);
p.store(tmp, memory_order_relaxed);
}
}
return p;
}
值得注意的是,上述代碼使用兩個(gè)比較關(guān)鍵的術(shù)語,獲得與釋放:
- 獲得是一個(gè)對(duì)內(nèi)存的讀操作,當(dāng)前線程的任何后面的讀寫操作都不允許重排到這個(gè)操作的前面去。
- 釋放是一個(gè)對(duì)內(nèi)存的寫操作,當(dāng)前線程的任何前面的讀寫操作都不允許重排到這個(gè)操作的后面去。
如果是在unix平臺(tái)的話,除了使用atomic operation外,在不適用C++11的情況下,還可以通過pthread_once來實(shí)現(xiàn)Singleton。
原型如下:
int pthread_once(pthread_once_t once_control, void (init_routine) (void));
class singleton {
private:
singleton(); //私有構(gòu)造函數(shù),不允許使用者自己生成對(duì)象
singleton(const singleton &other);
//要寫成靜態(tài)方法的原因:類成員函數(shù)隱含傳遞this指針(第一個(gè)參數(shù))
static void init() {
p = new singleton();
}
static pthread_once_t ponce_;
static singleton *p; //靜態(tài)成員變量
public:
singleton *instance() {
// init函數(shù)只會(huì)執(zhí)行一次
pthread_once(&ponce_, &singleton::init);
return p;
}
};
- C++ tr1全稱Technical Report 1,是針對(duì)C++標(biāo)準(zhǔn)庫的第一次擴(kuò)展。即將到來的下一個(gè)版本的C++標(biāo)準(zhǔn)c++0x會(huì)包括它,以及一些語言本身的擴(kuò)充。tr1包括大家期待已久的smart pointer,正則表達(dá)式以及其他一些支持范型編程的內(nèi)容。草案階段,新增的類和模板的名字空間是std::tr1。
#include <tr1/array>
std::tr1::array<int ,10> a;//第一個(gè)類型,第二個(gè)大小
-
deque與vector最大的差異就是:
1.deque允許于常數(shù)時(shí)間內(nèi)對(duì)頭端進(jìn)行插入或刪除元素;
2.deque是分段連續(xù)線性空間,隨時(shí)可以增加一段新的空間;
-
用戶看起來deque使用的是連續(xù)空間,實(shí)際上是分段連續(xù)線性空間。為了管理分段空間deque容器引入了map,稱之為中控器,map是一塊連續(xù)的空間,其中每個(gè)元素是指向緩沖區(qū)的指針,緩沖區(qū)才是deque存儲(chǔ)數(shù)據(jù)的主體。
image
在上圖中,buffer稱為緩沖區(qū),顯示map size的一段連續(xù)空間就是中控器。
中控器包含了map size,指向buffer的指針,deque的開始迭代器與結(jié)尾迭代器。
_Tp **_M_map;
size_t _M_map_size;
iterator _M_start;
iterator _M_finish;
deque是使用基類_Deque_base來完成內(nèi)存管理與中控器管理
在stack的源碼中我們關(guān)注兩點(diǎn): - 默認(rèn)_Sequence為deque - 內(nèi)部函數(shù)實(shí)現(xiàn)是調(diào)用_Sequence對(duì)應(yīng)容器的函數(shù)。
對(duì)于stack來說,底層容器可以是vector、deque、list,但不可以是map、set。 由于編譯器不會(huì)做全面性檢查,當(dāng)調(diào)用函數(shù)不存在的時(shí)候,就編譯不通過,所以對(duì)于像set雖然不能作為底層容器,但如果具有某些函數(shù),調(diào)用仍然是成功的,直到調(diào)用的函數(shù)不存在。
在queue的源碼中我們關(guān)注兩點(diǎn): - 默認(rèn)_Sequence為deque - 內(nèi)部函數(shù)實(shí)現(xiàn)是調(diào)用_Sequence對(duì)應(yīng)容器的函數(shù)。
優(yōu)先隊(duì)列則是使用vector作為默認(rèn)容器。
對(duì)于queue底層容器可以是deque,也可以是list,但不能是vector,map,set,使用默認(rèn)的deque效率在插入方面比其他容器作為底層要快!
對(duì)于優(yōu)先隊(duì)列來說,測(cè)試結(jié)果發(fā)現(xiàn),采用deque要比默認(rèn)的vector插入速度快! 底層支持vector、deque容器,但不支持list、map、set。
stack、queue、priority_queue不被稱為容器, 把它稱為容器配接器。
vector的數(shù)據(jù)安排以及操作方式,與array非常相似。兩者的唯一差別在于空間的運(yùn)用的靈活性,array是靜態(tài)的,一旦配置了就不能改變,而 vector是動(dòng)態(tài)空間,隨著元素的加入,它的內(nèi)部機(jī)制會(huì)自行擴(kuò)充空間以容納新元素。
-
類作用域
在類外部訪問類中的名稱時(shí),可以使用類作用域操作符,形如MyClass::name的調(diào)用通常存在三種:靜態(tài)數(shù)據(jù)成員、靜態(tài)成員函數(shù)和嵌套類型:
struct MyClass {
static int A; //靜態(tài)成員
static int B(){cout<<"B()"<<endl; return 100;} //靜態(tài)函數(shù)
typedef int C; //嵌套類型
struct A1 { //嵌套類型
static int s;
};
};

一種能夠順序訪問容器中每個(gè)元素的方法,使用該方法不能暴露容器內(nèi)部的表達(dá)方式。而類型萃取技術(shù)就是為了要解決和 iterator 有關(guān)的問題的。
總結(jié):通過定義內(nèi)嵌類型,我們獲得了知曉 iterator 所指元素類型的方法,通過 traits 技法,我們將函數(shù)模板對(duì)于原生指針和自定義 iterator 的定義都統(tǒng)一起來,我們使用 traits 技法主要是為了解決原生指針和自定義 iterator 之間的不同所造成的代碼冗余,這就是 traits 技法的妙處所在。
因?yàn)榭疹愅瑯涌梢员粚?shí)例化,每個(gè)實(shí)例在內(nèi)存中都有一個(gè)獨(dú)一無二的地址,為了達(dá)到這個(gè)目的,編譯器往往會(huì)給一個(gè)空類隱含的加一個(gè)字節(jié),這樣空類在實(shí)例化后在內(nèi)存得到了獨(dú)一無二的地址.所以上述大小為1.
兩個(gè)不同對(duì)象的地址不同。
基類為空,通過繼承方式來獲得基類的功能,并沒有產(chǎn)生額外大小的優(yōu)化稱之為EBO(空基類優(yōu)化)。
第一種方式的內(nèi)存管理:嵌入一個(gè)內(nèi)存管理類
template<class T, class Allocator>
class MyContainerNotEBO {
T *data_ = nullptr;
std::size_t capacity_;
Allocator allocator_; // 嵌入一個(gè)MyAllocator
public:
MyContainerNotEBO(std::size_t capacity)
: capacity_(capacity), allocator_(), data_(nullptr) {
std::cout << "alloc malloc" << std::endl;
data_ = reinterpret_cast<T *>(allocator_.allocate(capacity * sizeof(T))); // 分配內(nèi)存
}
~MyContainerNotEBO() {
std::cout << "MyContainerNotEBO free malloc" << std::endl;
allocator_.deallocate(data_);
}
};
- 第二種方式:采用空基類優(yōu)化,繼承來獲得內(nèi)存管理功能
template<class T, class Allocator>
class MyContainerEBO
: public Allocator { // 繼承一個(gè)EBO
T *data_ = nullptr;
std::size_t capacity_;
public:
MyContainerEBO(std::size_t capacity)
: capacity_(capacity), data_(nullptr) {
std::cout << "alloc malloc" << std::endl;
data_ = reinterpret_cast<T *>(this->allocate(capacity * sizeof(T)));
}
~MyContainerEBO() {
std::cout << "MyContainerEBO free malloc" << std::endl;
this->deallocate(data_);
}
};
int main() {
MyContainerNotEBO<int, MyAllocator> notEbo = MyContainerNotEBO<int, MyAllocator>(0);
std::cout << "Using Not EBO Test sizeof is " << sizeof(notEbo) << std::endl;
MyContainerEBO<int, MyAllocator> ebo = MyContainerEBO<int, MyAllocator>(0);
std::cout << "Using EBO Test sizeof is " << sizeof(ebo) << std::endl;
return 0;
}
//結(jié)果
alloc malloc
Using Not EBO Test sizeof is 24
alloc malloc
Using EBO Test sizeof is 16
MyContainerEBO free malloc
MyContainerNotEBO free malloc
- 采用EBO的設(shè)計(jì)確實(shí)比嵌入設(shè)計(jì)好很多。
? 二分查找的速度比簡單查找快得多。
? O(log n)比O(n)快。需要搜索的元素越多,前者比后者就快得越多。
? 算法運(yùn)行時(shí)間并不以秒為單位。
? 算法運(yùn)行時(shí)間是從其增速的角度度量的。
? 算法運(yùn)行時(shí)間用大O表示法表示。
set/multiset以rb_tree為底層結(jié)構(gòu),因此有元素自動(dòng)排序特性。排序的依據(jù)是key,而set/multiset元素的value和key合二為一:value就是key。
第一個(gè)問題:key是value,value也是key。
第二個(gè)問題:無法使用迭代器改變?cè)刂怠?/p>
第三個(gè)問題:插入是唯一的key。
cout<<"flag: "<<itree._M_insert_unique(5).second<<endl; // 學(xué)習(xí)返回值
typedef pair<int ,bool> _Res; // 也來用一下typedef后的pair
cout<<_Res(1,true).first<<endl; // 直接包裹
_Res r=make_pair(2,false); // 定義新對(duì)象
cout<<r.first<<endl; // 輸出結(jié)果
- map的key為key,value為key+data,與set是不同的,set是key就是value,value就是key。
- map的key不可修改,map與multimap的插入調(diào)用函數(shù)不同,影響了其key是否對(duì)應(yīng)value。
- initializer_list使用
- map有[]操作符,而multimap沒有[]操作符。
insert的幾種方法:
(1) 插入 pair
std::pair<iterator, bool> insert(const value_type& __x)
{ return _M_t._M_insert_unique(__x); }
map里面
(2) 在指定位置,插入pair
iterator insert(iterator __position, const value_type& __x)
{ return _M_t._M_insert_equal_(__position, __x); }
(3) 從一個(gè)范圍進(jìn)行插入
template<typename _InputIterator>
void
insert(_InputIterator __first, _InputIterator __last)
{ _M_t._M_insert_equal(__first, __last); }
(4)從list中插入
void
insert(initializer_list<value_type> __l)
{ this->insert(__l.begin(), __l.end()); }
針對(duì)最后一個(gè)insert,里面有個(gè)initializer_list,舉個(gè)例子大家就知道了。
- 結(jié)論1:undered_map與undered_set不允許key重復(fù),而帶multi的則允許key重復(fù);
- 結(jié)論2:undered_map與undered_multimap采用的迭代器是iterator,而undered_set與undered_multiset采用的迭代器是const_iterator。
- 結(jié)論3:undered_map與undered_multimap的key是key,value是key+value;而undered_set與undered_multiset的key是Value,Value也是Key。
五種創(chuàng)建線程的方式
- 函數(shù)指針
- Lambda函數(shù)吧
- Functor(仿函數(shù))
- 非靜態(tài)成員函數(shù)
- 靜態(tài)成員函數(shù)
2.1 函數(shù)指針
// 1.函數(shù)指針
void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
// 調(diào)用
std::thread t1(fun, 10);
t1.join();
2.2 Lambda函數(shù)
// 注意:如果我們創(chuàng)建多線程 并不會(huì)保證哪一個(gè)先開始
int main() {
// 2.Lambda函數(shù)
auto fun = [](int x) {
while (x-- > 0) {
cout << x << endl;
}
};
// std::1.thread t1(fun, 10);
// 也可以寫成下面:
std::thread t1_1([](int x) {
while (x-- > 0) {
cout << x << endl;
}
}, 11);
// std::1.thread t2(fun, 10);
// t1.join();
t1_1.join();
// t2.join();
return 0;
}
2.3 仿函數(shù)
// 3.functor (Funciton Object)
class Base {
public:
void operator()(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};
// 調(diào)用
thread t(Base(), 10);
t.join();
2.4 非靜態(tài)成員函數(shù)
// 4.Non-static member function
class Base {
public:
void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};
// 調(diào)用
thread t(&Base::fun,&b, 10);
t.join();
2.5 靜態(tài)成員函數(shù)
// 4.Non-static member function
class Base {
public:
static void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};
// 調(diào)用
thread t(&Base::fun, 10);
t.join();
join
- 一旦線程開始,我們要想等待線程完成,需要在該對(duì)象上調(diào)用join()
- 雙重join將導(dǎo)致程序終止
- 在join之前我們應(yīng)該檢查顯示是否可以被join,通過使用joinable()
void run(int count) {
while (count-- > 0) {
cout << count << endl;
}
std::this_thread::sleep_for(chrono::seconds(3));
}
int main() {
thread t1(run, 10);
cout << "main()" << endl;
t1.join();
if (t1.joinable()) {
t1.join();
}
cout << "main() after" << endl;
return 0;
}
detach
- 這用于從父線程分離新創(chuàng)建的線程
- 在分離線程之前,請(qǐng)務(wù)必檢查它是否可以joinable,否則可能會(huì)導(dǎo)致兩次分離,并且雙重detach()將導(dǎo)致程序終止
- 如果我們有分離的線程并且main函數(shù)正在返回,那么分離的線程執(zhí)行將被掛起
void run(int count) {
while (count-- > 0) {
cout << count << endl;
}
std::this_thread::sleep_for(chrono::seconds(3));
}
int main() {
thread t1(run, 10);
cout << "main()" << endl;
t1.detach();
if(t1.joinable())
t1.detach();
cout << "main() after" << endl;
return 0;
-
增加變量(i ++)的過程分三個(gè)步驟:
1.將內(nèi)存內(nèi)容復(fù)制到CPU寄存器。 load 2.在CPU中增加該值。 increment 3.將新值存儲(chǔ)在內(nèi)存中。 store
- 如果只能通過一個(gè)線程訪問該內(nèi)存位置(例如下面的變量i),則不會(huì)出現(xiàn)爭用情況,也沒有與i關(guān)聯(lián)的臨界區(qū)。 但是sum變量是一個(gè)全局變量,可以通過兩個(gè)線程進(jìn)行訪問。 兩個(gè)線程可能會(huì)嘗試同時(shí)增加變量。
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
int sum = 0; //shared
mutex m;
void *countgold() {
int i; //local to each thread
for (i = 0; i < 10000000; i++) {
sum += 1;
}
return NULL;
}
int main() {
thread t1(countgold);
thread t2(countgold);
//Wait for both threads to finish
t1.join();
t2.join();
cout << "sum = " << sum << endl;
return 0;
}
初始化列表與賦值
- const成員的初始化只能在構(gòu)造函數(shù)初始化列表中進(jìn)行
- 引用成員的初始化也只能在構(gòu)造函數(shù)初始化列表中進(jìn)行
- 對(duì)象成員(對(duì)象成員所對(duì)應(yīng)的類沒有默認(rèn)構(gòu)造函數(shù))的初始化,也只能在構(gòu)造函數(shù)初始化列表中進(jìn)行
類之間嵌套
第一種: 使用初始化列表。
class Animal {
public:
Animal() {
std::cout << "Animal() is called" << std::endl;
}
Animal(const Animal &) {
std::cout << "Animal (const Animal &) is called" << std::endl;
}
Animal &operator=(const Animal &) {
std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
return *this;
}
~Animal() {
std::cout << "~Animal() is called" << std::endl;
}
};
class Dog {
public:
Dog(const Animal &animal) : __animal(animal) {
std::cout << "Dog(const Animal &animal) is called" << std::endl;
}
~Dog() {
std::cout << "~Dog() is called" << std::endl;
}
private:
Animal __animal;
};
int main() {
Animal animal;
std::cout << std::endl;
Dog d(animal);
std::cout << std::endl;
return 0;
}
第二種:構(gòu)造函數(shù)賦值來初始化對(duì)象。
Dog(const Animal &animal) {
__animal = animal;
std::cout << "Dog(const Animal &animal) is called" << std::endl;
}
- 類中包含其他自定義的class或者struct,采用初始化列表,實(shí)際上就是創(chuàng)建對(duì)象同時(shí)并初始化
- 而采用類中賦值方式,等價(jià)于先定義對(duì)象,再進(jìn)行賦值,一般會(huì)先調(diào)用默認(rèn)構(gòu)造,在調(diào)用=操作符重載函數(shù)。
無默認(rèn)構(gòu)造函數(shù)的繼承關(guān)系中
class Animal {
public:
Animal(int age) {
std::cout << "Animal(int age) is called" << std::endl;
}
Animal(const Animal & animal) {
std::cout << "Animal (const Animal &) is called" << std::endl;
}
Animal &operator=(const Animal & amimal) {
std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
return *this;
}
~Animal() {
std::cout << "~Animal() is called" << std::endl;
}
};
class Dog : Animal {
public:
Dog(int age) : Animal(age) {
std::cout << "Dog(int age) is called" << std::endl;
}
~Dog() {
std::cout << "~Dog() is called" << std::endl;
}
};
- 由于在Animal中沒有默認(rèn)構(gòu)造函數(shù),所以報(bào)錯(cuò),遇到這種問題屬于災(zāi)難性的,我們應(yīng)該盡量避免,可以通過初始化列表給基類的構(gòu)造初始化。
類中const數(shù)據(jù)成員、引用數(shù)據(jù)成員
- 特別是引用數(shù)據(jù)成員,必須用初始化列表初始化,而不能通過賦值初始化!
class Animal {
public:
Animal(int age, std::string name) : age_(age), name_(name) {
std::cout << "Animal(int age) is called" << std::endl;
}
private:
int &age_;
const std::string name_;
};
// enum class
enum class EntityType {
Ground = 0,
Human,
Aerial,
Total
};
void foo(EntityType entityType)
{
if (entityType == EntityType::Ground) {
/*code*/
}
}
- 可以指定對(duì)象具有構(gòu)造函數(shù)和析構(gòu)函數(shù),這些構(gòu)造函數(shù)和析構(gòu)函數(shù)在適當(dāng)?shù)臅r(shí)候由
- 編譯器自動(dòng)調(diào)用,這為管理給定對(duì)象的內(nèi)存提供了更為方便的方法。
- 資源在析構(gòu)函數(shù)中被釋放
- 該類的實(shí)例是堆棧分配的
- 資源是在構(gòu)造函數(shù)中獲取的。
RAII代表“資源獲取是初始化”。常見的例子有:
文件操作
智能指針
互斥量
為了使用copy-swap,我們需要三件事:
1.一個(gè)有效的拷貝構(gòu)造函數(shù)
2.一個(gè)有效的析構(gòu)函數(shù)(兩者都是任何包裝程序的基礎(chǔ),因此無論如何都應(yīng)完整)以及交換功能。
