柚子快報(bào)邀請碼778899分享:標(biāo)準(zhǔn)C++
柚子快報(bào)邀請碼778899分享:標(biāo)準(zhǔn)C++
C++
98/03標(biāo)準(zhǔn)
名字空間
劃分邏輯單元 ,避免名字沖突
什么是名字空間
名字空間定義 namespace 名字空間名 {…} 名字空間合并 聲明定義分開 示例 // 名字空間:劃分更多的邏輯空間,有效避免名字沖突的問題
#include
namespace ICBC {
int g_money = 0;
void save( int money ) {
g_money += money;
}
}
namespace CCB {
int g_money = 0;
void save( int money ) { // 連 聲明 帶 定義
g_money += money;
}
void pay( int money ); // 聲明
}
void CCB::pay( int money ) { // 定義
g_money -= money;
}
namespace ICBC { // 編譯器將合并為一個(gè)名字空間
void pay( int money ) {
g_money -= money;
}
}
int main( void ) {
ICBC::save( 10000 );
ICBC::pay( 3000 );
std::cout << "工行卡余額:" << ICBC::g_money << std::endl;
CCB::save( 8000 );
CCB::pay( 3000 );
std::cout << "建行卡余額:" << CCB::g_money << std::endl;
return 0;
}
怎樣用名字空間
–作用域限定符 :: 名字空間指令
示例 // 名字空間指令
#include
using namespace std;
namespace ns {
int g_value = 0;
}
//int g_value = 0;
//using namespace ns;//從這行代碼開始,ns中的內(nèi)容在當(dāng)前作用域 可見
int main( void ) {
// int g_value = 0;
using namespace ns;//從這行代碼開始,ns中的內(nèi)容在當(dāng)前作用域 可見
g_value = 100;
/*std::*/cout << "ns::g_value=" << ns::g_value << /*std::*/endl;
return 0;
}
名字空間聲明
示例 // 名字空間聲明
#include
using namespace std;
namespace ns {
int g_value = 0;
}
//int g_value = 0;
//using ns::g_value;//從這行代碼開始,ns中的g_value引入當(dāng)前作用域(相當(dāng)于定義)
int main( void ) {
// int g_value = 0;
// using ns::g_value;//從這行代碼開始,ns中的g_value引入當(dāng)前作用域(相當(dāng)于定義)
g_value = 100;
cout << "ns::g_value=" << ns::g_value << endl;
return 0;
}
名字空間指令和名字空間聲明差別示例 // 名字空間聲明 和 名字空間指令 的差別
#include
using namespace std;
namespace ns1 {
int g_value = 0;
int g_other = 0;
}
namespace ns2 {
int g_value = 0;
int g_other = 0;
}
int main( void ) {
using namespace ns1;//名字空間指令,ns1中的所有內(nèi)容在當(dāng)前作用域可見(可見表)
using ns2::g_value; //名字空間聲明,ns2中的g_value引入當(dāng)前作用域(定義表),僅僅只有g(shù)_value出現(xiàn)在定義表中
g_value = 666; // ns2
cout << "ns1::g_value=" << ns1::g_value << ", ns2::g_value=" << ns2::g_value << endl;
g_other = 888; // ns1
cout << "ns1::g_other=" << ns1::g_other << ", ns2::g_other=" << ns2::g_other << endl;
return 0;
}
名字空間嵌套
內(nèi)層標(biāo)識符與外層同名標(biāo)識符為隱藏關(guān)系嵌套的名字空間需要逐層分解 名字空間別名
–可通過名字空間別名簡化書寫 namespace ns_four = ns1::ns2::ns3::ns4; 示例 // 名字空間的嵌套
#include
using namespace std;
namespace ns1 {
int g_value = 100;
namespace ns2 {
int g_value = 200;
namespace ns3 {
int g_value = 300;
namespace ns4 {
int g_value = 400;
}
}
}
}
int main( void ) {
namespace ns_four = ns1::ns2::ns3::ns4; // 名字空間別名可以簡化書寫
cout << ns_four::g_value << endl;
return 0;
}
C++復(fù)合類型
結(jié)構(gòu)、聯(lián)合和枚舉
C++的結(jié)構(gòu)
定義結(jié)構(gòu)型的變量時(shí),可以省略struct關(guān)鍵字 可以定義成員函數(shù),在結(jié)構(gòu)體中的成員函數(shù)內(nèi)部可以直接訪問本結(jié)構(gòu)體的成員,無需通過".“或”->" C++的聯(lián)合
定義聯(lián)合型的變量時(shí),可以省略union關(guān)鍵字支持匿名聯(lián)合 C++的枚舉
定義枚舉型的變量時(shí),可以省略enum關(guān)鍵字獨(dú)立的類型,和整型之間不能隱式轉(zhuǎn)換 布爾類型
表示布爾量的數(shù)據(jù)類型:bool布爾類型的字面值常量:true表示真,false表示假布爾類型的本質(zhì):單字節(jié)整數(shù),用1和0表示真和假任何基本類型都可以被隱式轉(zhuǎn)換為布爾類型:非0即真,0即假 示例 // C++的復(fù)合類型
#include
#include
using namespace std;
void TestStruct( ) {
struct Student {
int m_age; // 成員變量
char m_name[256]; // 成員變量
void getinfo() { // 成員函數(shù)(可以直接訪問本結(jié)構(gòu)體的成員)
cout << "getinfo:" << m_name << ":" << m_age << endl;
}
};
/*struct*/ Student s;
s.m_age = 22;
strcpy( s.m_name,"張飛" );
cout << "姓名:" << s.m_name << ", 年齡:" << s.m_age << endl;
s.getinfo();
}
void TestUnion( ) {
union { // 匿名聯(lián)合體:主要體現(xiàn)的為內(nèi)存的排布方式(共用同一塊內(nèi)存空間)
int i;
char c[4];
};
i = 0x12345678; // 小端字節(jié)序 : 低數(shù)位 占 低地址
cout << hex << (int)c[0] << ' ' << (int)c[1] << ' '
<< (int)c[2] << ' ' << (int)c[3] << endl;
}
void TestEnum( ) {
enum Color { red, green, blue };
/*enum*/ Color c = red; // 0-error
cout << "c=" << c << endl;
}
void TestBool( ) {
bool a = ""; // "fds"; // 0.000000001; // 123; // true;
bool b = NULL; // 0.000000000; // 0; // false;
cout << boolalpha << "a=" << a << ", b=" << b << endl;
}
int main( void ) {
TestBool( );
// TestEnum( );
// TestUnion( );
// TestStruct( );
return 0;
}
函數(shù)關(guān)系–重載
重載關(guān)系
同一作用域內(nèi),函數(shù)名相同,參數(shù)表不同 根據(jù)實(shí)參類型和形參的類型進(jìn)行匹配,調(diào)用最匹配的函數(shù)
示例 // 函數(shù)之間的關(guān)系--重載(1.同一作用域內(nèi) 2.函數(shù)名必須相同 3.形參表必須不同)
// 函數(shù)的形參表是否相同 和 形參名沒有關(guān)系,和 形參的個(gè)數(shù) 以及 每個(gè)"對應(yīng)"形參的類型有關(guān)
#include
using namespace std;
void foo( char* c, short s ) {
cout << "1. foo(char*,short)" << endl;
}
void foo( int i, double d ) {
cout << "2. foo(int,double)" << endl;
}
void foo( const char* c, short s ) {
cout << "3. foo(const char*,short)" << endl;
}
void foo( double i, int d ) {
cout << "4. foo(double,int)" << endl;
}
// int foo( double d, int i ) {} // error
int main( void ) {
char* c; short s;
foo( c, s ); // 1
const char* cc;
foo( cc, s ); // 3
int i; double d;
foo( i, d ); // 2
foo( d, i ); // 4
return 0;
}
只有同一作用域內(nèi)的同名函數(shù)才涉及重載的關(guān)系,不同作用域的同名函數(shù)涉及的是隱藏關(guān)系
示例 // 詳談 同一作用域內(nèi) (具體要看站在哪個(gè)點(diǎn)來說,而且具體情況具體分析)
#include
using namespace std;
namespace ns1 {
void foo( char* c, short s ) {
cout << "1. foo(char*,short)" << endl;
}
void foo( int i, double d ) {
cout << "2. foo(int,double)" << endl;
}
}
namespace ns2 {
void foo( const char* c, short s ) {
cout << "3. foo(const char*,short)" << endl;
}
void foo( double i, int d ) {
cout << "4. foo(double,int)" << endl;
}
}
int main( void ) {
using namespace ns1; // 名字空間指令,ns1中的foo出現(xiàn)在可見表中
using ns2::foo; // 名字空間聲明,ns2中的foo出現(xiàn)在定義表中
char* c; short s;
foo( c, s ); // 3
return 0;
}
重載解析
完全匹配>常量轉(zhuǎn)換>升級轉(zhuǎn)換>標(biāo)準(zhǔn)轉(zhuǎn)換>自定義轉(zhuǎn)換>省略號匹配 函數(shù)指針的類型決定其調(diào)用的重載函數(shù)的版本 示例 // 重載匹配(解析)優(yōu)先級
#include
using namespace std;
void foo( char* c, short s ) { // _Z3fooPcs
cout << "1. foo 完全匹配" << endl;
}
void foo( const char* c, short s ) { // _Z3fooPKcs
cout << "2. foo 常量轉(zhuǎn)換" << endl;
}
void foo( char* c, int s ) { // _Z3fooPci
cout << "3. foo 升級轉(zhuǎn)換" << endl;
}
void foo( char* c, char s ) { // _Z3fooPcc
cout << "4. foo 標(biāo)準(zhǔn)轉(zhuǎn)換" << endl;
}
void foo( ... ) { // _Z3fooz
cout << "5. foo 可變長參數(shù)" << endl;
}
int main( void ) {
char* c; short s;
foo( c, s ); // _Z3fooPcs(c,s);
// 普通方式調(diào)用,根據(jù) 實(shí)參和形參的類型 來確定調(diào)用哪個(gè)foo
void(*pfunc)(const char*, short) = foo; // _Z3fooPKcs
pfunc( c, s );// 函數(shù)指針方式調(diào)用,根據(jù)函數(shù)指針本身的類型 來確定調(diào)用哪個(gè)foo
return 0;
}
重載的本質(zhì)
重載是通過C++換名機(jī)制來實(shí)現(xiàn)的 通過extern "C"可以要求C++編譯器按照C方式編譯函數(shù),即不做換名,當(dāng)然也就無法重載 示例 extern "C" {
int sum( int a, int b ) {
return a + b;
}
int sub( int a, int b ) {
return a - b;
}
}
啞元函數(shù)
只指定形參類型而不指定形參名稱的函數(shù),謂之啞元
保證函數(shù)的向下兼容形成函數(shù)的重載版本 示例 // 啞元函數(shù)
#include
using namespace std;
void foo( int ) {
// 高精尖人工智能算法,不需要利用用戶提供基準(zhǔn)數(shù)據(jù)也能得到正確結(jié)果
// 函數(shù)內(nèi)部 不能獲取 用戶傳遞的實(shí)參數(shù)據(jù)
cout << "foo(int)" << endl;
}
void foo() {
cout << "foo()" << endl;
}
int main( void ) {
foo( 10 );
foo();
return 0;
}
缺?。J(rèn))參數(shù)
可以為函數(shù)的形參指定缺省(默認(rèn))值,當(dāng)調(diào)用該函數(shù)時(shí)若未指定實(shí)參,則使用形參的缺?。J(rèn))值 如果函數(shù)的某一個(gè)形參具有缺省(默認(rèn))值,那么該形參后面的所有形參必須都具有缺?。J(rèn))值 盡量避免因?yàn)槭褂萌笔?shù)而導(dǎo)致重載匹配歧義 函數(shù)形參的缺?。J(rèn))值只能在函數(shù)聲明中指定 示例 // 缺省參數(shù): 帶默認(rèn)值的形參( 默認(rèn)值不是初始值 )
#include
using namespace std;
void foo( int a, double b=3.14, float c=3.1, short d=2, char e='A' ); // 聲明
void foo( int a ) { // 連 聲明 帶 定義
}
int main( void ) {
foo( 3, 3.14, 3.1, 2 );
foo( 3, 3.14, 3.1, 2, 'B' );
// foo( 10 );
return 0;
}
void foo( int a, double b, float c, short d, char e ) { // 定義
cout << "e=" << e << endl;
}
內(nèi)聯(lián)函數(shù)
調(diào)用普通函數(shù)的問題:每個(gè)普通函數(shù)調(diào)用語句都需要發(fā)生跳轉(zhuǎn)操作,這種跳轉(zhuǎn)操作會(huì)帶來時(shí)間開銷 內(nèi)聯(lián)就是用函數(shù)已被編譯好的二進(jìn)制代碼,替換對該函數(shù)的調(diào)用指令 內(nèi)聯(lián)在保證函數(shù)特性的同時(shí),避免了函數(shù)調(diào)用的時(shí)間開銷 內(nèi)聯(lián)會(huì)使可執(zhí)行文件的體積和進(jìn)程代碼的內(nèi)存變大,因此只有頻繁調(diào)用的簡單函數(shù)才適合內(nèi)聯(lián),稀少被調(diào)用的復(fù)雜函數(shù)和遞歸函數(shù)都不適合內(nèi)聯(lián) inline關(guān)鍵字僅表示期望該函數(shù)被優(yōu)化為內(nèi)聯(lián),但是否適合內(nèi)聯(lián)則完全由編譯器決定 示例 // 內(nèi)聯(lián)函數(shù) - 編譯器的優(yōu)化策略
#include
using namespace std;
void foo( int x ) { // 普通函數(shù)
cout << "foo(int):" << x << endl;
}
inline void bar( int x ) { // 內(nèi)聯(lián)函數(shù)
cout << "bar(int):" << x << endl;
}
int main( void ) {
foo(10); // 生成跳轉(zhuǎn)指令
// ...
foo(20); // ...
// ...
foo(30); // ...
bar(10); // 將此處替換為bar函數(shù)編譯后產(chǎn)生的二進(jìn)制指令集
// ...
bar(20); // ...
// ...
bar(30); // ...
return 0;
}
動(dòng)態(tài)內(nèi)存分配
可以繼續(xù)使用標(biāo)準(zhǔn)C庫函數(shù)malloc/free 更建議使用new/delete操作符在堆中分配/釋放內(nèi)存 int* pi = new int; delete pi; 在分配內(nèi)存的同時(shí)初始化 int* pi = new int (100); 以數(shù)組方式new的也要以數(shù)組方式delete int* pi = new int [4] {10, 20, 30, 40}; delete[] pi; 通過new操作符分配N維數(shù)組,返回N-1維數(shù)組指針 int (*prow) [4] = new int [3][4]; int (*ppage) [4][5] = new int [3][4][5]; 不能通過delete操作符釋放已釋放過的內(nèi)存 delete野指針后果未定義,delete空指針安全 new操作符申請內(nèi)存失敗,將拋出異常 示例 // 動(dòng)態(tài)(堆)內(nèi)存分配
#include
#include
using namespace std;
int main( void ) {
int* pm = (int*)malloc(4);
cout << "*pm=" << *pm << endl;
free( pm );//當(dāng)這行代碼執(zhí)行結(jié)束,pm指向的堆內(nèi)存被釋放,進(jìn)而pm變?yōu)橐?懸空)指針
pm = NULL;
free( pm );//若給free傳遞的為野指針,釋放野指針后果很嚴(yán)重,釋放空指針是安全的
int* pn = new int(100);
cout << "*pn=" << *pn << endl;
delete pn;//當(dāng)這行代碼執(zhí)行結(jié)束,pn指向的堆內(nèi)存被釋放,進(jìn)而pn變?yōu)橐?懸空)指針
pn = NULL;
delete pn;//若給delete傳遞的為野指針,釋放野指針后果很嚴(yán)重,釋放空指針是安全的
int* parr = new int[4]{10,20,30,40}; // 以數(shù)組方式new一塊內(nèi)存,“永遠(yuǎn)”返回第一個(gè)(首)元素的地址
for( int i=0; i<4; i++ ) {
cout << parr[i] << ' ';
}
cout << endl;
delete[] parr;
// 不管幾維數(shù)組,都應(yīng)該當(dāng)做一維數(shù)組看待
int(*p)[4] = new int[3][4];
delete[] p;
try {
new int[0xFFFFFFFF];
}
catch( ... ) {
}
return 0;
}
引用
引用即內(nèi)存的別名 int a = 10; int& b = a; 引用本身不占內(nèi)存,并非實(shí)體, 對引用的所有操作都是在對目標(biāo)內(nèi)存進(jìn)行操作 引用必須初始化,且不能更換目標(biāo) int c = 20; b = c;//僅僅是在對引用的目標(biāo)內(nèi)存進(jìn)行賦值 不存在引用的引用 引用的常屬性須和目標(biāo)的常屬性“一致”, const int e = 10; int& f = e;//ERROR const int& g = e;//OK 但可以限定更加嚴(yán)格 int a = 10; const int& h = a;//OK 示例 // 引用:就是一塊內(nèi)存的別名
#include
#include
using namespace std;
int main( void ) {
int a = 10;
int& b = a;//這里不要理解為利用a給b賦值,而應(yīng)該理解為 引用b是目標(biāo)內(nèi)存(a)的別名
b = 20; // 表面看是在給引用b賦值,其實(shí)是在給引用b的目標(biāo)內(nèi)存(a)賦值
cout << "a=" << a << ", b=" << b << endl; // 表面看是在讀取b的值,其實(shí)讀取的為引用b的目標(biāo)內(nèi)存(a)的值
cout << "&b:" << &b << ", &a:" << &a << endl; // 取引用b的地址,其實(shí)取的是引用b的目標(biāo)內(nèi)存(a)的地址
int c = 30;
b = c; // 僅僅是利用c給引用b的目標(biāo)內(nèi)存(a)賦值
cout << "a=" << a << endl;
cout << "&a:" << &a << ", &b:" << &b << ", &c:" << &c << endl;
int& d = b; // 不要理解為d是b別名,而應(yīng)該理解為b和d都是目標(biāo)內(nèi)存(a)的別名
cout << "&a:" << &a << ", &b:" << &b << ", &d:" << &d << endl;
const int e = 10;
// int& f = e; // err, 別名不可以比真名限定的更加寬松
const int& g = e; // ok
const int& h = a; // ok, 別名可以比真名限定的更加嚴(yán)格
return 0;
}
左值和右值
左值:能夠取地址(&);右值:不能夠取地址
示例 // 左值 和 右值
#include
#include
using namespace std;
int foo() {
int m = 30;
return m;
}
int main( void ) {
// 當(dāng)前作用域的生命期
// 具名內(nèi)存--->能夠取址--->左值|非常左值(無const修飾)
// |常左值 (有const修飾)
int a = 10;
&a; // ok
a = 15; // ok
const int b = 10;
&b; // ok
// b = 15; // err
// 語句級生命期
// 匿名內(nèi)存--->不能取址--->右值|C++98/03標(biāo)準(zhǔn)認(rèn)為直接更改右值毫無意義
//
10;
// &10; // err
// 10 = 15; // err
/*|30|*/ foo(); // (1)分配一塊無名內(nèi)存空間 (2)生成跳轉(zhuǎn)指令
// &foo(); // err
// foo() = 15; // err
return 0;
}
引用可以延長右值的生命周期 常引用 即 萬能引用
常引用 即 萬能引用( 可以引用 非常左值、常左值、右值) int a = 10; const int& cra = a; // 但如果常引用 引用的為 非常左值,那么通過常引用將喪失修改目標(biāo)內(nèi)存的權(quán)限 常指針 即 萬能指針( 可以指向 非常左值、常左值、右值) const int* pra = &a; // 但如果常指針 指向的為 非常左值,那么通過常指針將喪失修改目標(biāo)內(nèi)存的權(quán)限 引用的生命周期不能長于目標(biāo) 示例 // 左值/右值 和 引用
#include
#include
using namespace std;
int foo() {
int m = 30;
return m;
}
int main( void ) {
// 當(dāng)前作用域的生命期
// 具名內(nèi)存--->能夠取址--->左值|非常左值(無const修飾)
// |常左值 (有const修飾)
int a = 10;
int& ra = a; // ok
const int& cra = a; // ok
const int b = 10;
// int& rb = b; // err
const int& crb = b; // ok
// 語句級生命期 (引用可以延長右值的生命期)
// 匿名內(nèi)存--->不能取址--->右值|C++98/03標(biāo)準(zhǔn)認(rèn)為直接更改右值毫無意義
// |C++11標(biāo)準(zhǔn)不是這樣認(rèn)為的????
const int& ri = 10; // ok
const int& rf = /*|30|*/ foo(); // (1)分配一塊無名內(nèi)存空間 (2)生成跳轉(zhuǎn)指令
return 0;
}
引用的應(yīng)用
引用型參數(shù),函數(shù)的形參是實(shí)參的別名,避免對象復(fù)制的開銷
非常引用型參數(shù)
在函數(shù)中修改實(shí)參值 常引用型參數(shù)
防止對實(shí)參的意外修改接受常量型實(shí)參 示例 // 引用型形參 : 書寫簡潔,提高傳參效率
// 當(dāng)我們在設(shè)計(jì)一個(gè)函數(shù)時(shí),只要能夠保證函數(shù)內(nèi)部絕對不修改實(shí)參,那么就大膽加上const
#include
using namespace std;
void Swap( int& x, int& y ) { // 非常引用型 形參:可以在函數(shù)內(nèi)部直接修改實(shí)參數(shù)據(jù)
int z = x;
x = y;
y = z;
}
void Swap( int* x, int* y ) {
int z = *x;
*x = *y;
*y = z;
}
void Print( const int& x, const int& y ) { // 常引用型 形參:防止函數(shù)內(nèi)部意外修改實(shí)參
// x = 8888; // 不小心寫的,編譯器將報(bào)錯(cuò)提示
cout << x << ' ' << y << endl;
}
int main( void ) {
int a=10, b=20;
Swap( a, b );
// Swap( &a, &b ); // 這樣書寫很不直觀
cout << "a=" << a << ", b=" << b << endl;
Print( a, b );
Print( 888, 666 );
return 0;
}
引用型的返回值,從函數(shù)中返回引用,一定要保證在函數(shù)返回以后,該引用的目標(biāo)依然有效
可以返回全局、靜態(tài)變量的引用 可以返回成員變量的引用 可以返回在堆中動(dòng)態(tài)創(chuàng)建的對象的引用 可以返回調(diào)用對象自身的引用 可以返回引用型參數(shù)本身 不能返回局部變量的引用 非常引用型返回值
通過引用可以修改目標(biāo) 常引用型返回值
通過引用不能修改目標(biāo) 示例 // 引用型返回值
#include
using namespace std;
int g_value = 0;
int& foo( ) { // 非常引用型 返回值:可以利用引用修改目標(biāo)
return g_value;
}
const int& fooo( ) { // 常引用型 返回值:利用引用不能修改目標(biāo)
return g_value;
}
int& bar( ) {
static int s_value = 0;//這行代碼是程序啟動(dòng)就執(zhí)行,而且只執(zhí)行一次,不是每次調(diào)用bar函數(shù)執(zhí)行
cout << "s_value=" << s_value << endl;
return s_value;
}
int& hum( ) {
int* p = new int;
return *p;
}
int& fun( int& x ) {
return x; // 返回 引用型參數(shù) 本身
}
/*
int& boo( ) {
int m = 0;
return m; // 返回局部變量的引用
}
*/
int main( void ) {
foo() = 100;
cout << "g_value=" << g_value << endl;
// fooo() = 8888; // err,通過常引用不能修改目標(biāo)
bar() = 200;
bar();
hum() = 300;
int a_value = 0;
fun(a_value) = 400;
cout << "a_value=" << a_value << endl;
// boo();
return 0;
}
在實(shí)現(xiàn)層面,引用就是指針,但在C++語言層面,引用不是實(shí)體類型,因此C++語言層面引用與指針存在明顯的差別
指針可以不初始化,而引用必須初始化 指針的目標(biāo)可在初始化后隨意變更(除非是指針常量),而引用一旦初始化就無法變更其目標(biāo) 存在空指針,不存在空引用 存在指向指針的指針,不存在引用的引用 存在指針的引用,不存在引用的指針 存在指針數(shù)組,不存在引用數(shù)組,但存在數(shù)組引用 示例 // 引用 和 指針 的差別
#include
using namespace std;
int main( void ) {
int a=10, b=20;
// 指針可以不做初始化,也可以做初始化
int* pa = &a;
// 指針的目標(biāo)內(nèi)存可以變更
pa = &b;
// 引用必須做初始化
int& ra = a;
// 引用的目標(biāo)內(nèi)存不可以變更
ra = b; // 這句代碼并不會(huì)更改引用ra的目標(biāo)內(nèi)存
// 存在空指針
pa = NULL;
// 不存在空引用
// ra = NULL; // error
// 存在二級指針
int** ppa = &pa;
// 不存在二級引用
// int&& rra = ra; // error
// 存在指針的引用
int * & rpa = pa;
// 不存在引用的指針
// int & * pra = &ra; // error
// 存在指針的數(shù)組
int* parr[2] = {&a, &b};
// 不存在引用的數(shù)組
// int& rarr[2] = {a,b}; // error
// 存在數(shù)組的引用
int arr[3];
int(&rr)[3] = arr;
return 0;
}
類型強(qiáng)轉(zhuǎn)
顯式類型轉(zhuǎn)換
C風(fēng)格的顯式類型轉(zhuǎn)換
(目標(biāo)類型)源類型變量 C++風(fēng)格的顯式類型轉(zhuǎn)換
目標(biāo)類型(源類型變量) 靜態(tài)類型轉(zhuǎn)換
static_cast<目標(biāo)類型> (源類型變量)隱式類型轉(zhuǎn)換的逆轉(zhuǎn)換自定義類型轉(zhuǎn)換 動(dòng)態(tài)類型轉(zhuǎn)換
dynamic_cast<目標(biāo)類型> (源類型變量)多態(tài)父子類指針或引用之間的轉(zhuǎn)換 常類型轉(zhuǎn)換
const_cast<目標(biāo)類型> (源類型變量)去除指針或引用上的const屬性 重解釋類型轉(zhuǎn)換
reinterpret_cast<目標(biāo)類型> (源類型變量)任意類型的指針之間的轉(zhuǎn)換或引用之間的轉(zhuǎn)換任意類型的指針和整型之間的轉(zhuǎn)換 示例 // 顯式(強(qiáng)制)類型轉(zhuǎn)換
#include
using namespace std;
int main( void ) {
int a; double b; float c; short d; char e;
// 所有基本類型的變量之間都可以 隱式轉(zhuǎn)換
a = b = c = d = e;
e = d = c = b = a;
// 任何其他類型的指針 到 void* 都可以 隱式轉(zhuǎn)換
void* pv = &a; // int*-->void*
pv = &b;
pv = &c;
pv = &d;
pv = &e;
// void* 到 任何其他類型的指針 必須強(qiáng)轉(zhuǎn)***************************
int* pa = static_cast
double* pb = static_cast
float* pc = static_cast
short* pd = static_cast
char* pe = static_cast
// 任何類型 非常指針 到 同類型 常指針 都可以 隱式轉(zhuǎn)換
const int* cpa = pa; // int*-->const int*
const double* cpb = pb;
const float* cpc = pc;
const short* cpd = pd;
const char* cpe = pe;
// 任何類型 常指針 到 同類型 非常指針 必須強(qiáng)轉(zhuǎn)**********************
pa = const_cast
pb = const_cast
pc = const_cast
pd = const_cast
pe = const_cast
// 除了void*外,任何其他類型的指針之間都 必須強(qiáng)轉(zhuǎn)*******************
pb = reinterpret_cast
pb = reinterpret_cast
return 0;
}
面向?qū)ο?/p>
什么是對象
萬物皆對象,這是人類面對世界最樸素,最自然的認(rèn)知、感覺對象擁有足夠的智能,能夠理解來自其它對象的信息,并以適當(dāng)?shù)男袨樽鞒龇磻?yīng)對象能夠從高層對象繼承屬性和行為,并允許低層對象從自己繼承屬性和行為等–面向?qū)ο蟮娜笠悍庋b、繼承、多態(tài) 為什么要面向?qū)ο?/p>
相比于分而治之的結(jié)構(gòu)化程序設(shè)計(jì),強(qiáng)調(diào)大處著眼的面向?qū)ο蟪绦蛟O(shè)計(jì)思想,更適合于開發(fā)大型軟件 得益于代碼復(fù)用等面向?qū)ο蟮墓逃刑卣鳎浖_發(fā)的效率獲得極大地提升,成本卻大幅降低 怎樣面向?qū)ο?/p>
至少掌握一種面向?qū)ο蟮某绦蛟O(shè)計(jì)語言深入理解封裝、繼承和多態(tài)等面向?qū)ο蟮闹匾拍顚W(xué)習(xí)設(shè)計(jì)模式
類和對象
類是抽象事物的一套規(guī)則
類是一種用戶自定義的復(fù)合數(shù)據(jù)類型,即包括表達(dá)屬性的成員變量,也包括表達(dá)行為的成員函數(shù)類可用于表達(dá)那些不能直接與內(nèi)置基本類型建立自然映射關(guān)系的邏輯抽象類是現(xiàn)實(shí)世界的抽象,對象是類在虛擬世界的實(shí)例 類的一般形式 類的定義—訪問控制限定符
public:公有成員,誰都可以訪問protected:保護(hù)成員,只有自己和子類可以訪問private:私有成員,只有自己可以訪問在C++中,類(class)和結(jié)構(gòu)(struct)已沒有本質(zhì)性的差別,唯一的不同在于
類的缺省訪問控制屬性為私有(private)結(jié)構(gòu)的缺省訪問控制屬性為公有(public) 訪問控制限定符僅作用于類,而非作用于對象。對不同成員的訪問控制屬性加以區(qū)分,體現(xiàn)了C++作為面向?qū)ο蟪绦蛟O(shè)計(jì)語言的封裝特性 示例 // 類:抽取事物特征的規(guī)則
#include
#include
using namespace std;
// struct
class Human {
public:
void setinfo( int age=0, const char* name="無名" ) { // 橋梁函數(shù)
if( !strcmp(name,"憨憨") ) {
cout << "你才憨呢" << endl;
return;
}
m_age = age;
strcpy( m_name, name );
}
void getinfo( ) {
cout << "姓名:" << m_name << ", 年齡:" << m_age << endl;
}
private:
int m_age; // 聲明
char m_name[256]; // 聲明
};
// 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類)
// ---------------------------------
// 以下代碼模擬類的使用者(用戶)
int main( void ) {
Human h; // 定義h(給h分配內(nèi)存空間)
// 在h所占內(nèi)存空間中 定義m_age(給m_age分配內(nèi)存空間)初值為隨機(jī)數(shù)
// 在h所占內(nèi)存空間中 定義m_name(給m_name分配內(nèi)存空間)初值為隨機(jī)數(shù)
cout << "h的大小:" << sizeof(h) << endl; // 260
h.setinfo( 22, "張飛" );
h.setinfo( 22, "憨憨" );
h.getinfo();
// h.m_age = 22;
// strcpy( h.m_name, "張飛" );
// strcpy( h.m_name, "憨憨" );
// cout << "姓名:" << h.m_name << ", 年齡:" << h.m_age << endl;
return 0;
}
成員函數(shù)參數(shù)–this
C++對象模型
同一個(gè)類的不同對象各自擁有一份獨(dú)立的成員變量 同一個(gè)類的不同對象彼此共享同一份成員函數(shù) C++成員函數(shù)模型
類的每個(gè)成員函數(shù)(除靜態(tài)成員函數(shù)外),都有一個(gè)隱藏的指針型參數(shù),形參名為 this,指向調(diào)用該成員函數(shù)的對象,這就是this指針 在類的成員函數(shù)中(除靜態(tài)成員函數(shù)外),對所有成員的訪問,都是通過this指針進(jìn)行的 示例 // 成員函數(shù)的形參 - this
#include
#include
using namespace std;
// 當(dāng)前程序中有兩個(gè)對象(h/h2),每個(gè)對象內(nèi)部各擁有一份成員變量(m_age/m_name),共有兩份成員變量
class Human {
public:
void setinfo( /* Human* this */ int age=0, const char* name="無名" ) { // _ZN5Human7setinfoEiPKc
this->m_age = age;
strcpy( this->m_name, name );
}
void getinfo( /* Human* this */ ) { // _ZN5Human7getinfoEv
cout << "姓名:" << this->m_name << ", 年齡:" << this->m_age << endl;
}
private:
int m_age; // 聲明
char m_name[256]; // 聲明
};
// 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類)
// ---------------------------------
// 以下代碼模擬類的使用者(用戶)
int main( void ) {
Human h; // 定義h(給h分配內(nèi)存空間)
// 在h所占內(nèi)存空間中 定義m_age(給m_age分配內(nèi)存空間)初值為隨機(jī)數(shù)
// 在h所占內(nèi)存空間中 定義m_name(給m_name分配內(nèi)存空間)初值為隨機(jī)數(shù)
cout << "h的大小:" << sizeof(h) << endl; // 260
h.setinfo( 22, "zhangfei" ); // _ZN5Human7setinfoEiPKc( &h, ...);
h.getinfo(); // _ZN5Human7getinfoEv( &h )
Human h2; // 定義h2(給h2分配內(nèi)存空間)
// 在h2所占內(nèi)存空間中 定義m_age(給m_age分配內(nèi)存空間)初值為隨機(jī)數(shù)
// 在h2所占內(nèi)存空間中 定義m_name(給m_name分配內(nèi)存空間)初值為隨機(jī)數(shù)
cout << "h2的大小:" << sizeof(h2) << endl;
h2.setinfo( 20, "zhaoyun" ); // _ZN5Human7setinfoEiPKc( &h2, ...);
h2.getinfo(); // _ZN5Human7getinfoEv( &h2 )
return 0;
}
this指針的應(yīng)用
多數(shù)情況下,程序并不需要顯式地使用this指針 有時(shí)為了方便,將類的成員變量與該類成員函數(shù)的參數(shù)取相同標(biāo)識符,這時(shí)在成員函數(shù)內(nèi)部,可通過this指針將二者加以區(qū)分 返回基于this指針的自引用,以支持串連調(diào)用 將this指針作為函數(shù)的參數(shù),以實(shí)現(xiàn)對象交互 示例 // 必須自己寫this的情況
#include
using namespace std;
class Integer {
public:
void setinfo( int i ) {
this->i = i; // (1)必須自己寫this的情況
}
void getinfo( /* Integerr* this */ ) {
cout << /*this->*/i << endl; // 編譯器會(huì)補(bǔ)this
foo( this ); // (3)必須自己寫this的情況
}
Integer& increment( /* Integer* this */ ) {
++/*this->*/i; // 編譯器會(huì)補(bǔ)this
return *this; // 返回基于this指針的自引用 (2)必須自己寫this的情況
}
private:
int i;
};
void foo( Integer* v ) {
// ...
}
// 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類)
// ---------------------------------
// 以下代碼模擬類的使用者(用戶)
int main( void ) {
Integer ix;
ix.setinfo( 1000 );
ix.getinfo();
ix.increment().increment().increment(); // 串聯(lián)調(diào)用
ix.getinfo();
return 0;
}
常函數(shù)與常對象
常對象:被const關(guān)鍵字修飾的對象、對象指針或?qū)ο笠?,統(tǒng)稱為常對象 常函數(shù):在類成員函數(shù)的形參表之后,函數(shù)體之前加上const關(guān)鍵字,該成員函數(shù)的this指針即具有常屬性,這樣的成員函數(shù)被稱為常函數(shù) 原型相同的成員函數(shù),常版本和非常版本構(gòu)成重載
非常對象優(yōu)先選擇非常版本,如果沒有非常版本,也能選擇常版本常對象只能選擇常版本 在常函數(shù)內(nèi)部無法修改成員變量的值,除非該成員變量被mutable關(guān)鍵字修飾 示例 // 常對象(被const修飾的 對象/指針/引用) 和 非常對象(沒有被const修飾的 對象/指針/引用)
// 常函數(shù)(編譯器補(bǔ)充的this指針有const限定) 和 非常函數(shù)(...)
#include
using namespace std;
class Integer {
public:
void setinfo( /* Integer* this */ int i ) {
m_i = i;
}
void getinfo( /* Integer* this */ ) { // 非常函數(shù)
cout << "非常函數(shù)getinfo:" << m_i << endl;
}
void getinfo( /* const Integer* this */ ) const { // 常函數(shù)
const_cast
cout << "常函數(shù)getinfo:" << m_i << endl;
}
private:
/*mutable*/ int m_i;
};
// 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類)
// ---------------------------------
// 以下代碼模擬類的使用者(用戶)
int main( void ) {
Integer ix; // ix是 非常對象
ix.setinfo( 1000 );
ix.getinfo(); // getinfo( &ix )-->實(shí)參的類型為Integer*
const Integer cix = ix; // cix是 常對象
cix.getinfo(); // getinfo( &cix )-->實(shí)參的類型為const Integer*
return 0;
}
類的定義與實(shí)例化
構(gòu)造函數(shù)
函數(shù)名必須與類名相同,且沒有返回值類型 構(gòu)造函數(shù)調(diào)用時(shí)間
在定義對象同時(shí)自動(dòng)被調(diào)用,且僅被調(diào)用一次
對象定義語句new操作符 構(gòu)造函數(shù)的作用
定義對象的各個(gè)成員變量并賦初值。設(shè)置對象的初始狀態(tài) 在對象定義之初想實(shí)現(xiàn)的任何操作 示例 // 構(gòu)造函數(shù):1.函數(shù)名必須和類名相同 2.沒有返回值類型
#include
#include
using namespace std;
class Human {
public:
Human( /* Human* this */ int age=0, const char* name="無名" ) {
// 在this所指向的內(nèi)存空間中 定義m_age(給m_age分配內(nèi)存空間)初值為隨機(jī)數(shù)
// 在this所指向的內(nèi)存空間中 定義m_name(給m_name分配內(nèi)存空間)初值為隨機(jī)數(shù)
cout << "Human類的構(gòu)造函數(shù)被調(diào)用" << endl;
m_age = age;
strcpy( m_name, name );
}
// void setinfo( /* Human* this */ int age=0, const char* name="無名" ) {
// this->m_age = age;
// strcpy( this->m_name, name );
// }
void getinfo( /* Human* this */ ) {
cout << "姓名:" << this->m_name << ", 年齡:" << this->m_age << endl;
}
private:
int m_age; // 聲明
char m_name[256]; // 聲明
};
// 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類)
// ---------------------------------
// 以下代碼模擬類的使用者(用戶)
int main( void ) {
Human h(22,"張飛"); // 定義h(給h分配內(nèi)存空間),利用 h.Human(22,"張飛")
cout << "h對象創(chuàng)建完畢" << endl;
// h.setinfo( 22, "zhangfei" );
h.getinfo();
return 0;
}
對象的定義過程
為整個(gè)對象分配內(nèi)存空間以構(gòu)造實(shí)參調(diào)用構(gòu)造函數(shù)
定義成員變量執(zhí)行構(gòu)造代碼 類的聲明與實(shí)現(xiàn)可以分開
示例 // 類的聲明 和 實(shí)現(xiàn)(定義) 分開
#include
#include
using namespace std;
class Human {
public:
Human( /* Human* this */ int age=0, const char* name="無名" ); // 聲明
void getinfo( /* Human* this */ ); // 聲明
private:
int m_age; // 聲明
char m_name[256]; // 聲明
};
Human::Human( /* Human* this */ int age, const char* name ) { // 定義
// 在this所指向的內(nèi)存空間中 定義m_age(給m_age分配內(nèi)存空間)初值為隨機(jī)數(shù)
// 在this所指向的內(nèi)存空間中 定義m_name(給m_name分配內(nèi)存空間)初值為隨機(jī)數(shù)
cout << "Human類的構(gòu)造函數(shù)被調(diào)用" << endl;
m_age = age;
strcpy( m_name, name );
}
void Human::getinfo( /* Human* this */ ) { // 定義
cout << "姓名:" << m_name << ", 年齡:" << m_age << endl;
}
// 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類)
// ---------------------------------
// 以下代碼模擬類的使用者(用戶)
int main( void ) {
Human h(22,"張飛"); // 定義h(給h分配內(nèi)存空間),利用 h.Human(22,"張飛")
h.getinfo();
return 0;
}
將類的聲明、實(shí)現(xiàn)與使用分別放在不同的文件里 對象的定義與銷毀
在棧中定義單個(gè)對象
類名 對象; // 注意不要加空括號類名 對象 (實(shí)參表); 在棧中定義對象數(shù)組
類名 對象數(shù)組[元素個(gè)數(shù)];類名 對象數(shù)組[元素個(gè)數(shù)] = {類名 (實(shí)參表), …};類名 對象數(shù)組[] = {類名 (實(shí)參表), …}; 在堆中定義/銷毀單個(gè)對象
–類名* 對象指針 = new 類名;類名* 對象指針 = new 類名 ();類名* 對象指針 = new 類名 (實(shí)參表);delete 對象指針; 在堆中定義/銷毀對象數(shù)組
類名* 對象數(shù)組指針 = new 類名[元素個(gè)數(shù)];類名* 對象數(shù)組指針 = new 類名[元素個(gè)數(shù)] {類名 (實(shí)參表), …};// 需要編譯器支持C++11標(biāo)準(zhǔn)delete[] 對象數(shù)組指針; 示例 // 定義類對象的11種方法
#include
#include
using namespace std;
class Human {
public:
Human( /* Human* this */ int age=0, const char* name="無名" ); // 聲明
void getinfo( /* Human* this */ ); // 聲明
private:
int m_age; // 聲明
char m_name[256]; // 聲明
};
Human::Human( /* Human* this */ int age, const char* name ) { // 定義
// 在this所指向的內(nèi)存空間中 定義m_age(給m_age分配內(nèi)存空間)初值為隨機(jī)數(shù)
// 在this所指向的內(nèi)存空間中 定義m_name(給m_name分配內(nèi)存空間)初值為隨機(jī)數(shù)
cout << "Human類的構(gòu)造函數(shù)被調(diào)用" << endl;
m_age = age;
strcpy( m_name, name );
}
void Human::getinfo( /* Human* this */ ) { // 定義
cout << "姓名:" << m_name << ", 年齡:" << m_age << endl;
}
// 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類)
// ---------------------------------
// 以下代碼模擬類的使用者(用戶)
int main( void ) {
Human(32,"馬超").getinfo(); // 定義 匿名Human類對象,利用 匿名Human類對象.Human(32,"馬超")
Human h(22,"張飛"); // 定義h(給h分配內(nèi)存空間),利用 h.Human(22,"張飛")
h.getinfo();
Human h2; // 定義h2,利用h2.Human()
h2.getinfo();
Human h3[3]; // 定義3個(gè)Human類對象,并分別利用這3個(gè)Human類對象.Human()
for( int i=0; i<3; i++ ) {
h3[i].getinfo();
}
Human h4[3] = { Human(22,"張飛"), Human(20,"趙云"), Human(25,"關(guān)羽") };
for( int i=0; i<3; i++ ) {
h4[i].getinfo();
}
Human h5[] = { Human(22,"張飛"), Human(20,"趙云"), Human(25,"關(guān)羽"), Human(45,"黃忠") };
for( int i=0; i h5[i].getinfo(); } Human* ph = new Human; // 定義 Human類堆對象,利用 Human類堆對象.Human() (*ph).getinfo(); // ph->getinfo() delete ph; ph = NULL; Human* ph2 = new Human(); // 定義 Human類堆對象,利用 Human類堆對象.Human() (*ph2).getinfo(); delete ph2; ph2 = NULL; Human* ph3 = new Human(18,"武松"); // 定義 Human類堆對象,利用 Human類堆對象.Human(18,"武松") (*ph3).getinfo(); delete ph3; ph3 = NULL; Human* ph4 = new Human[3]; // 定義 3個(gè)Human類堆對象,分別利用這3個(gè)Human類堆對象.Human() for( int i=0; i<3; i++ ) { ph4[i].getinfo(); } delete[] ph4; ph4 = NULL; Human* ph5 = new Human[3]{ Human(18,"武松"), Human(20,"林沖"), Human(19,"魯達(dá)") }; for( int i=0; i<3; i++ ) { ph5[i].getinfo(); } delete[] ph5; ph5 = NULL; return 0; } C++標(biāo)準(zhǔn)庫–string類 示例 // string類的使用 #include using namespace std; // C++標(biāo)準(zhǔn)庫中設(shè)計(jì)的string類-->char* m_psz int main( void ) { string s1("hello"); // 定義s1,利用s1.string("hello")-->s1維護(hù)的字符串為"hello" cout << "s1: " << s1 << endl; // 如果在做初始化,并且"="兩邊的類型完全一致,那么=xxx和(xxx)無差別 string s2(s1); //= s1; 定義s2,利用s2.string(s1)-->s2維護(hù)的字符串和s1維護(hù)的字符串 內(nèi)容相同 cout << "s2: " << s2 << endl; string s3; // 定義s3,利用s3.string()-->s3維護(hù)的字符串為"\0" cout << "s3被賦值前: " << s3 << endl; // 如果在做賦值,并且"="兩邊的類型完全一致,那么將觸發(fā)operator=函數(shù)的調(diào)用 s3 = s2; // s3.operator=(s2)-->s3維護(hù)的字符串 和 s2維護(hù)的字符串 內(nèi)容相同 cout << "s3被賦值后: " << s3 << endl; // 無論是初始化還是賦值,只要"="兩邊的類型不一致,都會(huì)觸發(fā)類型轉(zhuǎn)換操作 string s4 = "hello"; // 定義 匿名string類對象,利用 匿名string類對象.string("hello")-->匿名對象維護(hù)"hello" // string s4 = 匿名string類對象-->s4維護(hù)的字符串 和 匿名string類對象維護(hù)的字符串內(nèi)容相同 // -->s4維護(hù)的字符串為"hello" cout << "s4: " << s4 << endl; string s5; cout << "s5被賦值前:" << s5 << endl; s5 = "hello"; // 定義 匿名string類對象,利用 匿名string類對象.string("hello")-->匿名對象維護(hù)"hello" // s5 = 匿名string類對象-->s5維護(hù)的字符串 和 匿名string類對象維護(hù)的字符串 內(nèi)容相同 // -->s5維護(hù)的字符串為"hello" cout << "s5被賦值后: " << s5 << endl; return 0; } 構(gòu)造函數(shù) 構(gòu)造函數(shù)可以重載 構(gòu)造函數(shù)也可以通過參數(shù)表的差別化形成重載 重載的構(gòu)造函數(shù),通過構(gòu)造函數(shù)的實(shí)參類型進(jìn)行匹配 不同的構(gòu)造函數(shù),表示對象的不同創(chuàng)建方式 使用缺省參數(shù)可以減少構(gòu)造函數(shù)重載的數(shù)量 示例 // 構(gòu)造函數(shù)的重載 #include using namespace std; class Human { public: /* Human() { cout << "1. Human()--"; m_age = 0; m_name = "無名"; } Human( int age ) { cout << "2. Human(int)--"; m_age = age; m_name = "無名"; } */ Human( int age=0, const char* name="無名" ) { cout << "3. Human(int,const char*)--"; m_age = age; m_name = name; } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } private: int m_age; // 聲明 string m_name; // 聲明 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human h; // 定義h,利用h.Human() h.getinfo(); Human h2(22); // 定義h2,利用h2.Human(22) h2.getinfo(); Human h3(22,"張飛"); // 定義h3,利用h3.Human(22,"張飛") h3.getinfo(); return 0; } 構(gòu)造函數(shù)分類 多參構(gòu)造函數(shù):按多參方式構(gòu)造無參(缺省)構(gòu)造函數(shù):按無參方式構(gòu)造類型轉(zhuǎn)換構(gòu)造函數(shù):利用不同類型的對象構(gòu)造拷貝構(gòu)造函數(shù):利用相同類型的對象構(gòu)造 無參構(gòu)造函數(shù) 無參構(gòu)造函數(shù)亦稱缺省構(gòu)造函數(shù),但其未必真的沒有任何參數(shù),為一個(gè)有參構(gòu)造函數(shù)的每個(gè)參數(shù)都提供一個(gè)缺省值,同樣可以達(dá)到無參構(gòu)造函數(shù)的效果 如果一個(gè)類沒有定義任何構(gòu)造函數(shù),那么編譯器會(huì)為其提供一個(gè)無參構(gòu)造函數(shù) 對基本類型的成員變量進(jìn)行定義,并初始化為隨機(jī)數(shù) 對類類型的成員變量進(jìn)行定義,調(diào)用相應(yīng)類型的無參構(gòu)造函數(shù) 如果一個(gè)類定義了構(gòu)造函數(shù),無論這個(gè)構(gòu)造函數(shù)是否帶有參數(shù),編譯器都不會(huì)再為這個(gè)類再提供無參構(gòu)造函數(shù) 示例 // 無參(缺省)構(gòu)造函數(shù) #include using namespace std; class Human { public: // 如果類沒有提供任何構(gòu)造函數(shù),編譯器將提供一個(gè)無參構(gòu)造函數(shù) /* Human() { 【int m_age;】定義m_age,初值為隨機(jī)數(shù) 【string m_name;】定義m_name,利用m_name.string() }*/ Human(int age=0, const char* name="無名") { //【int m_age;】定義m_age,初值為隨機(jī)數(shù) //【string m_name;】定義m_name,利用m_name.string() cout << "Human類的缺省構(gòu)造函數(shù)被調(diào)用" << endl; m_age = age; m_name = name; } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } private: int m_age; // 基本類型成員變量 string m_name; // 類類型成員變量 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human h; // 定義h,利用h.Human() h.getinfo(); Human h2(22,"張飛"); // 定義h2,利用 h2.Human(22,"張飛") h2.getinfo(); return 0; } 有時(shí)必須為一個(gè)類提供無參的構(gòu)造函數(shù),僅僅因?yàn)樗赡茏鳛榱硗庖粋€(gè)類的類類型成員變量 示例 // 建議大家設(shè)計(jì)一個(gè)類時(shí)候 提供無參(缺省)構(gòu)造函數(shù) #include using namespace std; class A { // 當(dāng)前A類 有 無參構(gòu)造函數(shù) public: A( int i=0 ) { m_i = i; } private: int m_i; }; class Human { public: Human(int age=0, const char* name="無名") { //【int m_age;】定義m_age,初值為隨機(jī)數(shù) //【string m_name;】定義m_name,利用m_name.string() //【A m_a;】定義m_a,利用m_a.A() cout << "Human類的缺省構(gòu)造函數(shù)被調(diào)用" << endl; m_age = age; m_name = name; } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } private: int m_age; // 基本類型成員變量 string m_name; // 類類型成員變量 A m_a; // 類類型成員變量 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human h; // 定義h,利用h.Human() h.getinfo(); Human h2(22,"張飛"); // 定義h2,利用 h2.Human(22,"張飛") h2.getinfo(); return 0; } 拷貝構(gòu)造函數(shù) 形如 class 類名 { 類名 (const 類名& that) { … } }; 的構(gòu)造函數(shù)被稱為拷貝構(gòu)造函數(shù)。 用于,利用一個(gè)已定義的對象,來定義其同類型的副本對象,即對象克隆 如果一個(gè)類沒有定義拷貝構(gòu)造函數(shù),那么編譯器會(huì)為其提供一個(gè)默認(rèn)拷貝構(gòu)造函數(shù) 對基本類型成員變量進(jìn)行定義,并賦初值(按字節(jié)復(fù)制)對類類型成員變量進(jìn)行定義,并調(diào)用相應(yīng)類型的拷貝構(gòu)造函數(shù) 如果自己定義了拷貝構(gòu)造函數(shù),編譯器將不再提供默認(rèn)拷貝構(gòu)造函數(shù),這時(shí)所有與成員復(fù)制有關(guān)的操作,都必須在自定義拷貝構(gòu)造函數(shù)中自己編寫代碼完成 若默認(rèn)拷貝構(gòu)造函數(shù)不能滿足要求,則需自己定義 示例 // 拷貝構(gòu)造函數(shù) #include using namespace std; class Human { public: // 如果類沒有提供任何構(gòu)造函數(shù),編譯器將提供一個(gè)無參構(gòu)造函數(shù) /* Human() { 【int m_age;】定義m_age,初值為隨機(jī)數(shù) 【string m_name;】定義m_name,利用m_name.string() }*/ Human(int age=0, const char* name="無名") { //【int m_age;】定義m_age,初值為隨機(jī)數(shù) //【string m_name;】定義m_name,利用m_name.string() cout << "Human類的缺省構(gòu)造函數(shù)被調(diào)用" << endl; m_age = age; m_name = name; } // 如果類沒有提供拷貝構(gòu)造函數(shù),編譯器將提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù) /* Human( const Human& that ) { 【int m_age=that.m_age;】定義m_age,初值為that.m_age 【string m_name(that.m_name);】定義m_name,利用m_name.string(that.m_name)-->string類的拷貝構(gòu)造 }*/ Human( const Human& that ) { // 后面將提升效率??? //【int m_age;】定義m_age,初值為隨機(jī)數(shù) //【string m_name;】定義m_name,利用m_name.string() cout << "Human類的拷貝構(gòu)造函數(shù)被調(diào)用" << endl; m_age = that.m_age; m_name = that.m_name; } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } private: int m_age; // 基本類型成員變量 string m_name; // 類類型成員變量 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human h; // 定義h,利用h.Human() h.getinfo(); Human h2(22,"張飛"); // 定義h2,利用 h2.Human(22,"張飛") h2.getinfo(); Human h3(h2); //= h2; 定義h3,利用 h3.Human(h2)-->拷貝構(gòu)造函數(shù) h3.getinfo(); return 0; } 拷貝構(gòu)造函數(shù)的調(diào)用時(shí)機(jī) 用已定義對象作為同類型對象的構(gòu)造實(shí)參 以對象的形式向函數(shù)傳遞參數(shù) 從函數(shù)中返回對象 注意:某些拷貝構(gòu)造過程會(huì)因編譯優(yōu)化而被省略 示例 // 拷貝構(gòu)造函數(shù)被調(diào)用的時(shí)間 #include using namespace std; class Human { public: Human(int age=0, const char* name="無名") { //【int m_age;】定義m_age,初值為隨機(jī)數(shù) //【string m_name;】定義m_name,利用m_name.string() m_age = age; m_name = name; } Human( const Human& that ) { //【int m_age;】定義m_age,初值為隨機(jī)數(shù) //【string m_name;】定義m_name,利用m_name.string() cout << "Human類的拷貝構(gòu)造函數(shù)被調(diào)用" << endl; m_age = that.m_age; m_name = that.m_name; } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } private: int m_age; // 基本類型成員變量 string m_name; // 類類型成員變量 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) void foo( Human v ) {} Human bar() { Human m; return m; } int main( void ) { Human h2(22,"張飛"); Human h3(h2); //= h2; 定義h3,利用 h3.Human(h2)-->觸發(fā)拷貝構(gòu)造函數(shù) (1) foo( h3 ); // -->觸發(fā)拷貝構(gòu)造函數(shù) (2) Human h4 = /*|...|*/ bar(); // 觸發(fā)2次拷貝構(gòu)造函數(shù)(被編譯器優(yōu)化) // -fno-elide-constructors return 0; } 自定義構(gòu)造函數(shù)和編譯器定義構(gòu)造函數(shù) 自定義構(gòu)造函數(shù)編譯器定義構(gòu)造函數(shù)無缺省構(gòu)造函數(shù);缺省拷貝構(gòu)造函數(shù)除拷貝構(gòu)造函數(shù)以外的任何構(gòu)造函數(shù)缺省拷貝構(gòu)造函數(shù)拷貝構(gòu)造函數(shù)無所有編譯器定義的構(gòu)造函數(shù),其訪問控制屬性均為公有(public) 類型轉(zhuǎn)換構(gòu)造函數(shù) 形如: class 目標(biāo)類型 { 目標(biāo)類型 (const 源類型& src) { … } }; 的構(gòu)造函數(shù)被稱為類型轉(zhuǎn)換構(gòu)造函數(shù) 用于 利用一個(gè)已定義的對象, 來定義另一個(gè)不同類型的對象 實(shí)現(xiàn)從源類型到目標(biāo)類型的隱式類型轉(zhuǎn)換的目的 通過explicit關(guān)鍵字,可以強(qiáng)制這種通過類型轉(zhuǎn)換構(gòu)造函數(shù)實(shí)現(xiàn)的類型轉(zhuǎn)換必須通過靜態(tài)轉(zhuǎn)換顯式地進(jìn)行 class 目標(biāo)類型 { explicit 目標(biāo)類型 (const 源類型& src) { … } }; 示例 // 類型轉(zhuǎn)換構(gòu)造函數(shù) #include using namespace std; class Cat { public: explicit Cat( const char* name ) : m_name(name) { // 類型轉(zhuǎn)換構(gòu)造函數(shù) //【string m_name(name);】 cout << "Cat類的類型轉(zhuǎn)換構(gòu)造函數(shù)被調(diào)用" << endl; } void talk() { cout << m_name << ": 喵喵~~~" << endl; } private: string m_name; friend class Dog; // 友元聲明 }; class Dog { public: Dog( const char* name ) : m_name(name) { // 類型轉(zhuǎn)換構(gòu)造函數(shù) //【string m_name(name);】 } explicit Dog( const Cat& c ) { // 類型轉(zhuǎn)換構(gòu)造函數(shù)( 定制了 Cat->Dog的轉(zhuǎn)換規(guī)則 ) m_name = c.m_name; cout << "Dog類的類型轉(zhuǎn)換構(gòu)造函數(shù)被調(diào)用" << endl; } void talk() { cout << m_name << ": 汪汪~~~" << endl; } private: string m_name; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { // Cat smallwhite("小白"); // 定義smallwhite,利用smallwhite.Cat("小白")-->類型轉(zhuǎn)換構(gòu)造函數(shù) // Cat smallwhite = "小白"; // 定義 匿名Cat類對象,利用 匿名Cat類對象.Cat("小白")-->類型轉(zhuǎn)換構(gòu)造函數(shù) Cat smallwhite = static_cast // 定義 匿名Cat類對象,利用 匿名Cat類對象.Cat("小白")-->類型轉(zhuǎn)換構(gòu)造函數(shù) smallwhite.talk(); // Dog bigyellow( smallwhite ); // 定義bigyellow,利用bigyellow.Dog( smallwhite )-->類型轉(zhuǎn)換構(gòu)造 // Dog bigyellow = smallwhite; // 定義 匿名Dog類對象,利用 匿名Dog類對象.Dog(smallwhite)-->類型轉(zhuǎn)換構(gòu)造 // Dog bigyellow = 匿名Dog類對象 Dog bigyellow = static_cast // 定義 匿名Dog類對象,利用 匿名Dog類對象.Dog(smallwhite)-->類型轉(zhuǎn)換構(gòu)造 // Dog bigyellow = 匿名Dog類對象 bigyellow.talk(); return 0; } 拷貝賦值函數(shù) 形如 class 類名 { 類名& operator= (const 類名& that) { … } }; 的函數(shù)被稱為拷貝賦值函數(shù),用于一個(gè)已定義的對象給同類型的對象賦值,即對象賦值 如果一個(gè)類沒有定義拷貝賦值函數(shù),那么編譯器會(huì)為其提供一個(gè)默認(rèn)拷貝賦值函數(shù) 對基本類型成員變量,值傳遞(按字節(jié)復(fù)制)對類類型成員變量,調(diào)用相應(yīng)類型的拷貝賦值函數(shù) 如果自己定義了拷貝賦值函數(shù),編譯器將不再提供默認(rèn)拷貝賦值函數(shù),這時(shí)所有與成員復(fù)制有關(guān)的操作,都必須在自定義拷貝賦值函數(shù)中自己編寫代碼完成 若默認(rèn)拷貝賦值函數(shù)不能滿足要求時(shí),則需自己定義 示例 // 拷貝賦值函數(shù):用于對象之間的賦值 #include using namespace std; class Human { public: // 如果類沒有提供任何構(gòu)造函數(shù),編譯器將提供一個(gè)無參構(gòu)造函數(shù) /* Human() { 【int m_age;】定義m_age,初值為隨機(jī)數(shù) 【string m_name;】定義m_name,利用m_name.string() }*/ Human(int age=0, const char* name="無名") { //【int m_age;】定義m_age,初值為隨機(jī)數(shù) //【string m_name;】定義m_name,利用m_name.string() cout << "Human類的缺省構(gòu)造函數(shù)被調(diào)用" << endl; m_age = age; m_name = name; } // 如果類沒有提供拷貝構(gòu)造函數(shù),編譯器將提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù) /* Human( const Human& that ) { 【int m_age=that.m_age;】定義m_age,初值為that.m_age 【string m_name(that.m_name);】定義m_name,利用m_name.string(that.m_name)-->string類的拷貝構(gòu)造 }*/ Human( const Human& that ) { // 后面將提升效率??? //【int m_age;】定義m_age,初值為隨機(jī)數(shù) //【string m_name;】定義m_name,利用m_name.string() cout << "Human類的拷貝構(gòu)造函數(shù)被調(diào)用" << endl; m_age = that.m_age; m_name = that.m_name; } // 如果類沒有提供拷貝賦值函數(shù),編譯器將提供一個(gè)默認(rèn)的拷貝賦值函數(shù) /* Human& operator=( const Human& that ) { this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string類拷貝賦值函數(shù) return *this; }*/ Human& operator=( const Human& that ) { // 編譯器不會(huì)在拷貝賦值函數(shù)中塞操作 cout << "Human類的拷貝賦值函數(shù)被調(diào)用" << endl; this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string類拷貝賦值函數(shù) return *this; } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } private: int m_age; // 基本類型成員變量 string m_name; // 類類型成員變量 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human h; // 定義h,利用h.Human() h.getinfo(); Human h2(22,"張飛"); // 定義h2,利用 h2.Human(22,"張飛") h2.getinfo(); Human h3(h2); //= h2; 定義h3,利用 h3.Human(h2)-->拷貝構(gòu)造函數(shù) h3.getinfo(); Human h4; // 定義h,利用h.Human() cout << "h4被賦值前--"; h4.getinfo(); h4 = h3; // h4.operator=(h3)-->拷貝賦值函數(shù) cout << "h4被賦值后--"; h4.getinfo(); return 0; } 初始化表 通過在類的構(gòu)造函數(shù)中使用初始化表,可以通知編譯器該類的成員變量如何被初始化 示例 // 初始化表 #include using namespace std; class Human { public: // 如果類沒有提供任何構(gòu)造函數(shù),編譯器將提供一個(gè)無參構(gòu)造函數(shù) /* Human() { 【int m_age;】定義m_age,初值為隨機(jī)數(shù) 【string m_name;】定義m_name,利用m_name.string() }*/ Human(int age=0, const char* name="無名") : m_age(age),m_name(name) { //【int m_age=age;】定義m_age,初值為age //【string m_name(name);】定義m_name,利用m_name.string(name) cout << "Human類的缺省構(gòu)造函數(shù)被調(diào)用" << endl; } // 如果類沒有提供拷貝構(gòu)造函數(shù),編譯器將提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù) /* Human( const Human& that ) { 【int m_age=that.m_age;】定義m_age,初值為that.m_age 【string m_name(that.m_name);】定義m_name,利用m_name.string(that.m_name)-->string類的拷貝構(gòu)造 }*/ Human( const Human& that ) : m_age(that.m_age), m_name(that.m_name) { //【int m_age=that.m_age;】定義m_age,初值為that.m_age //【string m_name(that.m_name);】定義m_name,利用m_name.string(that.m_name) cout << "Human類的拷貝構(gòu)造函數(shù)被調(diào)用" << endl; } // 如果類沒有提供拷貝賦值函數(shù),編譯器將提供一個(gè)默認(rèn)的拷貝賦值函數(shù) /* Human& operator=( const Human& that ) { this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string類拷貝賦值函數(shù) return *this; }*/ Human& operator=( const Human& that ) { // 編譯器不會(huì)在拷貝賦值函數(shù)中塞操作 cout << "Human類的拷貝賦值函數(shù)被調(diào)用" << endl; this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string類拷貝賦值函數(shù) return *this; } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } private: int m_age; // 基本類型成員變量 string m_name; // 類類型成員變量 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human h; // 定義h,利用h.Human() h.getinfo(); Human h2(22,"張飛"); // 定義h2,利用 h2.Human(22,"張飛") h2.getinfo(); Human h3(h2); //= h2; 定義h3,利用 h3.Human(h2)-->拷貝構(gòu)造函數(shù) h3.getinfo(); Human h4; // 定義h,利用h.Human() cout << "h4被賦值前--"; h4.getinfo(); h4 = h3; // h4.operator=(h3)-->拷貝賦值函數(shù) cout << "h4被賦值后--"; h4.getinfo(); return 0; } 類中的基本類型成員變量,最好在初始化表中顯式指明如何初始化,否則初值不確定 類中的類類型成員變量,也最好在初始化表中顯式指明如何初始化,否則將調(diào)動(dòng)相應(yīng)類型的無參構(gòu)造函數(shù) 類的常量型和引用型成員變量,必須在初始化表中顯式初始化 *類的成員變量*按其在類中的聲明順序依次被初始化,而與其在初始化表中的順序無關(guān)。所以在初始化成員變量時(shí)最好不要涉及其他成員變量 示例 // 必須使用初始化表的情況 #include #include using namespace std; class Human { public: Human(int age=0, const char* name="無名", float score=0.0) : m_age(age),m_name(name),m_score(score),m_len(strlen(name) ) { //【int m_len = strlen(name);】 //【int m_age=age;】定義m_age,初值為age //【string m_name(name);】定義m_name,利用m_name.string(name) //【const float m_score = score;】 cout << "Human類的缺省構(gòu)造函數(shù)被調(diào)用" << endl; } Human( const Human& that ) : m_age(that.m_age), m_name(that.m_name), m_score(that.m_score), m_len(that.m_len) { //【int m_len = that.m_len;】 //【int m_age=that.m_age;】定義m_age,初值為that.m_age //【string m_name(that.m_name);】定義m_name,利用m_name.string(that.m_name) //【const float m_score = that.m_score;】 cout << "Human類的拷貝構(gòu)造函數(shù)被調(diào)用" << endl; } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age << ", 成績:" << m_score << ", 名字長度:" << m_len << endl; } private: int m_len; // 保存名字字符串的長度 int m_age; string m_name; const float m_score; // 常量型的成員變量 // int m_len; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human h2(22,"張飛",88.5); // 定義h2,利用 h2.Human(22,"張飛",88.5) h2.getinfo(); Human h3(h2); //= h2; 定義h3,利用 h3.Human(h2)-->拷貝構(gòu)造函數(shù) h3.getinfo(); return 0; } 析構(gòu)函數(shù) 析構(gòu)函數(shù)的函數(shù)名就是在類名前面加“~”,沒有返回類型也沒有參數(shù),不能重載 示例 // 析構(gòu)函數(shù) #include using namespace std; class Human { public: // 如果類沒有提供任何構(gòu)造函數(shù),編譯器將提供一個(gè)無參構(gòu)造函數(shù) /* Human() { 【int m_age;】定義m_age,初值為隨機(jī)數(shù) 【string m_name;】定義m_name,利用m_name.string() }*/ Human(int age=0, const char* name="無名") : m_age(age),m_name(name) { //【int m_age=age;】定義m_age,初值為age //【string m_name(name);】定義m_name,利用m_name.string(name) cout << "Human類的缺省構(gòu)造函數(shù)被調(diào)用" << endl; } // 如果類沒有提供拷貝構(gòu)造函數(shù),編譯器將提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù) /* Human( const Human& that ) { 【int m_age=that.m_age;】定義m_age,初值為that.m_age 【string m_name(that.m_name);】定義m_name,利用m_name.string(that.m_name)-->string類的拷貝構(gòu)造 }*/ Human( const Human& that ) : m_age(that.m_age), m_name(that.m_name) { //【int m_age=that.m_age;】定義m_age,初值為that.m_age //【string m_name(that.m_name);】定義m_name,利用m_name.string(that.m_name) cout << "Human類的拷貝構(gòu)造函數(shù)被調(diào)用" << endl; } // 如果類沒有提供拷貝賦值函數(shù),編譯器將提供一個(gè)默認(rèn)的拷貝賦值函數(shù) /* Human& operator=( const Human& that ) { this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string類拷貝賦值函數(shù) return *this; }*/ Human& operator=( const Human& that ) { // 編譯器不會(huì)在拷貝賦值函數(shù)中塞操作 cout << "Human類的拷貝賦值函數(shù)被調(diào)用" << endl; this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string類拷貝賦值函數(shù) return *this; } // 如果類沒有提供析構(gòu)函數(shù),編譯器將提供一個(gè)默認(rèn)的析構(gòu)函數(shù) /* ~Human() { 對于基本類型的成員變量m_age,什么都不做 對于類類型的成員變量m_name,利用m_name.~string() 釋放 m_age/m_name 本身所占內(nèi)存空間 }*/ ~Human() { cout << "Human類的析構(gòu)函數(shù)被調(diào)用" << endl; // 對于類類型的成員變量m_name,利用m_name.~string() // 釋放 m_age/m_name 本身所占內(nèi)存空間 } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } private: int m_age; // 基本類型成員變量 string m_name; // 類類型成員變量 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human h; // 定義h,利用h.Human() h.getinfo(); Human h2(22,"張飛"); // 定義h2,利用 h2.Human(22,"張飛") h2.getinfo(); Human h3(h2); //= h2; 定義h3,利用 h3.Human(h2)-->拷貝構(gòu)造函數(shù) h3.getinfo(); Human h4; // 定義h,利用h.Human() cout << "h4被賦值前--"; h4.getinfo(); h4 = h3; // h4.operator=(h3)-->拷貝賦值函數(shù) cout << "h4被賦值后--"; h4.getinfo(); cout << "main will be over" << endl; return 0; } // (1) 利用h.~Human() h2.~Human() h3.~Human() h4.~Human() (2) 釋放h/h2/h3/h4本身所占內(nèi)存空間 在銷毀對象之前一刻自動(dòng)被調(diào)用,且僅被調(diào)用一次 對象離開作用域delete操作符 作用:銷毀對象的各個(gè)成員變量 如果一個(gè)類沒有定義析構(gòu)函數(shù),那么編譯器會(huì)為其提供一個(gè)默認(rèn)析構(gòu)函數(shù) 對基本類型的成員變量,什么也不做對類類型的成員變量,調(diào)用相應(yīng)類型的析構(gòu)函數(shù)銷毀對象的各個(gè)成員變量 對象的銷毀過程 調(diào)用析構(gòu)函數(shù) 執(zhí)行析構(gòu)函數(shù)的代碼調(diào)用成員變量的析構(gòu)函數(shù)釋放對象各成員變量所占內(nèi)存空間 釋放整個(gè)對象所占用的內(nèi)存空間 通常情況下,若對象在其生命周期的最終時(shí)刻,并不持有任何動(dòng)態(tài)分配的資源,可以不定義析構(gòu)函數(shù) 但若對象在其生命周期的最終時(shí)刻,持有動(dòng)態(tài)資源則必須自己定義析構(gòu)函數(shù),釋放對象所持有的動(dòng)態(tài)資源 示例 // 析構(gòu)函數(shù) #include using namespace std; class A { public: A(int i=0) : m_i(i),m_p(new int),m_f(open("./file",O_CREAT|O_RDWR,0644)) { //【int m_i=i;】定義m_i,初值為i //【int* m_p=new int;】定義m_p,初值為指向一塊堆內(nèi)存(動(dòng)態(tài)資源) //【int m_f=open(...);】定義m_f,初值為文件描述符-->文件表等內(nèi)核結(jié)構(gòu)(動(dòng)態(tài)資源) } ~A() { delete m_p; close( m_f ); // 釋放 m_i/m_p/m_f 本身所占內(nèi)存空間 } /* 默認(rèn)析構(gòu) ~A() { 釋放 m_i/m_p/m_f 本身所占內(nèi)存空間 } */ private: int m_i; int* m_p; int m_f; }; // 以上代碼模擬類的設(shè)計(jì)者 // ---------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { A a; // 定義a,利用a.A() return 0; } // a.~A() 釋放a本身所占內(nèi)存空間 析構(gòu)函數(shù)的功能并不局限在釋放資源上,它可以執(zhí)行我們希望在對象被釋放之前執(zhí)行的任何操作 深淺 拷貝構(gòu)造 與 拷貝賦值 如果類不提供拷貝構(gòu)造和拷貝賦值編譯器將提供默認(rèn)的拷貝構(gòu)造和拷貝賦值,而默認(rèn)的拷貝構(gòu)造和拷貝賦值函數(shù),對于指針型成員變量都是只復(fù)制地址,而并不是復(fù)制地址指向的數(shù)據(jù),這將導(dǎo)致淺拷貝問題 為了獲得完整意義上的對象副本,必須自己定義拷貝構(gòu)造和拷貝賦值,針對指針型成員變量做深拷貝 相對于拷貝構(gòu)造,拷貝賦值需要做更多的工作 避免自賦值分配新資源拷貝新內(nèi)容釋放舊資源返回自引用 無論是拷貝構(gòu)造還是拷貝賦值,其默認(rèn)實(shí)現(xiàn)對任何類型的指針成員都是簡單地復(fù)制地址,因此應(yīng)盡量避免使用指針型成員變量 出于具體原因的考慮,如果確實(shí)無法實(shí)現(xiàn)完整意義上的拷貝構(gòu)造和拷貝賦值,可將它們私有化,以防誤用 如果為一個(gè)類提供了自定義的拷貝構(gòu)造函數(shù),就沒有理由不提供相同邏輯的拷貝賦值運(yùn)算符函數(shù) 示例 // 默認(rèn)拷貝構(gòu)造 以及 默認(rèn)拷貝賦值,在某些特定場景(類中有指針型成員)有(淺拷貝)缺陷 #include #include using namespace std; // 模擬C++標(biāo)準(zhǔn)庫的string類,設(shè)計(jì)一個(gè)自己的String類 class String { public: String( const char* psz="" ) : m_psz(new char[strlen(psz)+1]) { //【char* m_psz=new char[...];】定義m_psz,初值為指向一塊堆內(nèi)存(動(dòng)態(tài)資源) strcpy( m_psz, psz ); } ~String( /* String* this */ ) { delete[] this->m_psz; this->m_psz = NULL; // 釋放 m_psz 本身所占內(nèi)存空間 } char* c_str() { return m_psz; } /* 默認(rèn)拷貝構(gòu)造函數(shù) String( const String& that ) { 【char* m_psz=that.m_psz;】只復(fù)制了地址,沒有復(fù)制地址指向的數(shù)據(jù)-->淺拷貝 } */ // 深拷貝構(gòu)造函數(shù) String( const String& that ) : m_psz(new char[strlen(that.m_psz)+1]) { //【char* m_psz=new char[...];】 strcpy( m_psz, that.m_psz ); // 復(fù)制了數(shù)據(jù),不復(fù)制地址-->深拷貝 cout << "String類深拷貝構(gòu)造函數(shù)被調(diào)用" << endl; } /* 默認(rèn)的拷貝賦值函數(shù) String& operator=( const String& that ) { this->m_psz = that.m_psz; // 只復(fù)制了地址,沒有復(fù)制地址指向的數(shù)據(jù)-->淺拷貝 return *this; }*/ // 深拷貝賦值函數(shù) String& operator=( /* String* this */ const String& that ) { cout << "String類的深拷貝賦值函數(shù)被調(diào)用" << endl; if( this!=&that ) { // 防止自賦值 delete[] this->m_psz; // 釋放舊資源 this->m_psz = new char[strlen(that.m_psz)+1]; // 分配新資源 strcpy( this->m_psz, that.m_psz ); // 拷貝新內(nèi)容 } return *this; // 返回自引用 } private: char* m_psz; }; // 以上代碼模擬類的設(shè)計(jì)者 // ---------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { String s1("hello"); // 定義s1,利用s1.String() cout << "s1維護(hù)的字符串:" << s1.c_str() << ", s1維護(hù)堆內(nèi)存首地址:" << (void*)s1.c_str() << endl; String s2 = s1; // 定義s2,利用s2.String(s1)-->觸發(fā)拷貝構(gòu)造函數(shù) cout << "s2維護(hù)的字符串:" << s2.c_str() << ", s2維護(hù)堆內(nèi)存首地址:" << (void*)s2.c_str() << endl; String s3; // 定義s3,利用s3.String()-->s3維護(hù)空串(占一個(gè)字節(jié)的堆內(nèi)存) s3 = s2; // s3.operator=(s2)-->觸發(fā)拷貝賦值函數(shù) cout << "s3維護(hù)的字符串:" << s3.c_str() << ", s3維護(hù)堆內(nèi)存首地址:" << (void*)s3.c_str() << endl; return 0; } // s1.~String() s2.~String() s3.~String() 類的靜態(tài)成員 靜態(tài)成員變量 靜態(tài)成員變量屬于類,而不屬于對象 靜態(tài)成員變量不包含在對象中,進(jìn)程級生命期 靜態(tài)成員變量的定義和初始化,只能在類的外部而不能在構(gòu)造函數(shù)中進(jìn)行 靜態(tài)成員變量依然受類作用域和訪問控制限定符的約束 訪問靜態(tài)成員變量,既可以通過類也可以通過對象 靜態(tài)成員變量為該類的所有對象實(shí)例所共享 示例 // 靜態(tài)成員變量 #include using namespace std; // 普通成員變量:屬于對象,對象的生命期 靜態(tài)成員變量:不屬于對象,進(jìn)程級生命期 class A { public: A() { //【int m_i;】定義 } int m_i; // 聲明 static int m_si; // 聲明 }; int A::m_si = 0; // 全局域中定義 -- 進(jìn)程級生命期 // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { A a, b; // 靜態(tài)成員不在對象中保存 -- 不屬于對象 cout << "a對象的大小:" << sizeof(a) << endl; // 4 cout << "b對象的大小:" << sizeof(b) << endl; // 4 A::m_si = 100; //受到類作用域的約束,也受到訪問控制限定符的約束 -- 屬于類 a.m_si = 666; // A::m_si = 666; cout << "b.m_si=" << b.m_si << endl; // A::m_si // 靜態(tài)成員變量 被該類所有對象 共享 return 0; } 靜態(tài)成員函數(shù) 靜態(tài)成員函數(shù)屬于類,而不屬于對象 靜態(tài)成員函數(shù)沒有this指針,也沒有常屬性 靜態(tài)成員函數(shù)依然受類作用域和訪問控制限定符的約束 訪問靜態(tài)成員函數(shù),既可以通過類也可以通過對象 靜態(tài)成員函數(shù)只能訪問靜態(tài)成員,而非靜態(tài)成員函數(shù)既可以訪問靜態(tài)成員,也可以訪問非靜態(tài)成員 示例 // 靜態(tài)成員變量 和 靜態(tài)成員函數(shù) #include using namespace std; // 普通成員函數(shù)屬于對象:普通成員函數(shù)必須利用對象來調(diào)用 // 靜態(tài)成員函數(shù)不屬于對象:靜態(tài)成員函數(shù)不用對象調(diào)用 class A { public: int m_i; // 非靜態(tài)成員變量 void foo( /* const A* this */ ) const { // 非靜態(tài)成員函數(shù) cout << "foo()" << endl; cout << m_i << endl; // ok cout << m_si << endl;// ok bar(); // ok // 以上三行代碼證明:非靜態(tài)普通成員函數(shù)既可訪問非靜態(tài)普成員也可訪問靜態(tài)成員 } static int m_si; // 靜態(tài)成員變量 static void bar( /*無this參數(shù)*/ ) /*const*/ { // 靜態(tài)成員函數(shù) cout << "bar()" << endl; cout << m_si << endl; // ok // cout << m_i << endl; // err // foo(); // err // 以上三行代碼證明:靜態(tài)成員函數(shù)只能訪問靜態(tài)成員,不能訪問非靜態(tài)普通成員 } }; int A::m_si = 0; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { A a, b; a.foo(); // foo( &a ) b.foo(); // foo( &b ) A::bar(); // 受到類作用域和訪問控制限定符的約束 -- 屬于類 a.bar(); // A::bar(); b.bar(); // A::bar(); return 0; } 事實(shí)上,類的靜態(tài)成員變量和靜態(tài)成員函數(shù),更象是普通的全局變量和全局函數(shù),只是多了一層類作用域和訪問控制限定符的約束,相當(dāng)于具有成員訪問屬性的全局變量和全局函數(shù) 單例模式 一個(gè)類僅有一個(gè)實(shí)例(對象) 空類對象保存 1個(gè)字節(jié)的垃圾數(shù)據(jù) 示例 #include using namespace std; /* class A { // 空類 }; int main( void ) { A a; // 空類對象保存 1個(gè)字節(jié)的垃圾數(shù)據(jù) cout << "a的大小:" << sizeof(a) << endl; return 0; } */ class A { public: int m_i; // A m_a; // 類中 不能包含一個(gè)本類的普通對象作為成員變量 static A m_sa; // 類中 能包含一個(gè)本類的靜態(tài)對象作為成員變量 }; int main( void ) { A a; // 定義a(給a分配內(nèi)存空間) return 0; } 將包括,類的拷貝構(gòu)造函數(shù)在內(nèi)的所有構(gòu)造函數(shù)私有化 , 防止使用者在類的外部創(chuàng)建對象 公有靜態(tài)成員函數(shù)getInstance()是獲取對象實(shí)例的唯一渠道 餓漢式:無論用不用,程序啟動(dòng)即創(chuàng)建 示例 // 單例模式 - 設(shè)計(jì)一個(gè)類,當(dāng)用戶使用這個(gè)類時(shí)只能出現(xiàn)一個(gè)對象 #include using namespace std; // 餓漢式單例 class Singleton { public://4 - 為什么函數(shù)需要static :不需要用戶利用對象調(diào)用 //5 - 為什么引用型返回值:不允許出現(xiàn)匿名對象 static Singleton& getInstance() { // 讓用戶可以得到唯一的對象 return s_instance; } private: Singleton() {} // 1 - 讓用戶無法定義對象 Singleton( const Singleton& that ) {} // 6 - 不允許用戶克隆對象 static Singleton s_instance; // 2 - 類的設(shè)計(jì)者唯一的對象 }; Singleton Singleton::s_instance; // 3 - 靜態(tài)成員變量定義必須在全局域 // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Singleton& s1 = Singleton::getInstance(); Singleton& s2 = Singleton::getInstance(); Singleton& s3 = Singleton::getInstance(); cout << "&s1:" << &s1 << ", &s2:" << &s2 << ", &s3:" << &s3 << endl; return 0; } 懶漢式:用的時(shí)候創(chuàng)建,不用了即銷毀 引用計(jì)數(shù) 示例 // 單例模式 - 設(shè)計(jì)一個(gè)類,當(dāng)用戶使用這個(gè)類時(shí)只能出現(xiàn)一個(gè)對象 #include using namespace std; // 懶漢式單例 class Singleton { public: static Singleton& getInstance() { if( s_instance==NULL ) { // 判斷是否是第一次被調(diào)用 s_instance = new Singleton; // 唯一的對象 cout << "創(chuàng)建了對象" << endl; } ++s_counter;//加減操作線程不安全,最好用互斥鎖包起來 return *s_instance; } void releaseInstance() { --s_counter; if( s_counter == 0 ) { delete s_instance; s_instance = NULL; cout << "銷毀了對象" << endl; } } private: Singleton() {} Singleton( const Singleton& that ) {} static Singleton *s_instance; // 并不是唯一對象 static int s_counter; // 計(jì)數(shù) }; Singleton* Singleton::s_instance = NULL; int Singleton::s_counter = 0; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Singleton& s1 = Singleton::getInstance();// 第一次調(diào)用getInstance函數(shù)時(shí),創(chuàng)建對象 Singleton& s2 = Singleton::getInstance();// 以后再調(diào)用就不要?jiǎng)?chuàng)建對象,返回原來創(chuàng)建對象 Singleton& s3 = Singleton::getInstance();// ... cout << "&s1:" << &s1 << ", &s2:" << &s2 << ", &s3:" << &s3 << endl; s1.releaseInstance(); // 將計(jì)數(shù)減1 s2.releaseInstance(); // 將計(jì)數(shù)減1 s3.releaseInstance(); // 最后一次調(diào)用releaseInstance函數(shù)時(shí),將對象銷毀 return 0; } 操作符重載 操作符標(biāo)記 單目操作符:-、++、–、*、->等雙目操作符:+、-、>、<、+=、-=、>>、<<等三目操作符:?: 操作符函數(shù) 在特定條件下,編譯器有能力把一個(gè)由操作數(shù)和操作符組成的表達(dá)式, 解釋為一個(gè)全局函數(shù)的調(diào)用解釋為一個(gè)成員函數(shù)的調(diào)用 ,該全局函數(shù)或成員函數(shù)被稱為操作符函數(shù) 通過定義操作符函數(shù),可以實(shí)現(xiàn)針對自定義類型的運(yùn)算法則,使之與內(nèi)置類型一樣參與各種操作符表達(dá)式 示例 // #include using namespace std; class Human { public: Human( int age=0, const char* name="無名" ) : m_age(age), m_name(name) { //【int m_age=age;】 //【string m_name(name);】 } void getinfo() { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } Human sum( /* Human* this */ Human that ) { return Human(this->m_age+that.m_age, (this->m_name+"+"+that.m_name).c_str() ); } Human sub( /* Human* this */ Human that ) { return Human(this->m_age-that.m_age, (this->m_name+"-"+that.m_name).c_str() ); } private: int m_age; string m_name; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human a(22,"張飛"), b(20,"趙云"), c(25,"關(guān)羽"), d(32,"馬超"); Human res = a.sum(b); // a + b; ==> a.operator+(b) 或 operator+(a,b) res.getinfo(); res = c.sub(d); // c - d; ==> c.operator-(d) 或 operator-(c,d) res.getinfo(); return 0; } 雙目操作符表達(dá)式:L#R 成員形式的操作符函數(shù)調(diào)用:L.operator# ? 左操作數(shù)是調(diào)用對象,右操作數(shù)是參數(shù)對象全局形式的操作符函數(shù)調(diào)用:operator# (L, R) 左操作數(shù)是第一參數(shù),右操作數(shù)是第二參數(shù) 單目操作符表達(dá)式:#O/O# 成員形式的操作符函數(shù)調(diào)用:O.operator# ()全局形式的操作符函數(shù)調(diào)用:operator# (O) 三目操作符表達(dá)式:F#S#T 無法重載 運(yùn)算類雙目操作符:+、-、*、/等 左操作數(shù)應(yīng)可以為非常左值、常左值或右值 右操作數(shù)應(yīng)可以為非常左值、常左值或右值 表達(dá)式的結(jié)果必須為右值 示例 // 運(yùn)算類雙目操作符 #include using namespace std; class Human { // 授權(quán)類 public: Human( int age=0, const char* name="無名" ) : m_age(age), m_name(name) { //【int m_age=age;】 //【string m_name(name);】 } void getinfo() { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } // 成員形式的操作符函數(shù) // Human operator+( /* const Human* this */ const Human& r ) const { // return Human(this->m_age+r.m_age, (this->m_name+"+"+r.m_name).c_str() ); // } private: int m_age; string m_name; friend Human operator+( const Human& l, const Human& r ); // 友元聲明 }; // 全局形式的操作符函數(shù) Human operator+( const Human& l, const Human& r ) { return Human(l.m_age+r.m_age, (l.m_name+"+"+r.m_name).c_str() ); } // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human a(22,"張飛"), b(20,"趙云"); // 非常左值 const Human c(25,"關(guān)羽"), d(32,"馬超"); // 常左值 Human res = a + b; // a.operator+(b) 或 operator+(a,b) res.getinfo(); res = c + d; // c.operator+(d) 或 operator+(c,d) res.getinfo(); res = Human(45,"黃忠") + Human(35,"劉備"); // Human(45,"黃忠").operator+(Human(35,"劉備")) 或 // operator+( Human(45,"黃忠"),Human(35,"劉備") ) res.getinfo(); /* int a=10, b=20; // 非常左值 const int c=30, d=40; // 常左值 |30|a + b; a + c; a + 5; c + b; 5 + b; */ return 0; } 友元 可以通過friend關(guān)鍵字,把一個(gè)全局函數(shù)、另一個(gè)類的成員函數(shù)或者另一個(gè)類整體,聲明為授權(quán)類的友元友元擁有訪問授權(quán)類任何非公有成員的特權(quán)友元聲明可以出現(xiàn)在授權(quán)類的公有、私有或者保護(hù)等任何區(qū)域,且不受訪問控制限定符的約束友元不是成員,其作用域并不隸屬于授權(quán)類,也不擁有授權(quán)類類型的this指針 典型雙目操作符的重載 賦值類雙目操作符:=、+=、-=、*=、/=等 右操作數(shù)應(yīng)可以為非常左值、常左值或右值 左操作數(shù)必須為非常左值 表達(dá)式結(jié)果必須為左操作數(shù)本身(而非副本) 示例 // 賦值類雙目操作符 #include using namespace std; class Human { // 授權(quán)類 public: Human( int age=0, const char* name="無名" ) : m_age(age), m_name(name) { //【int m_age=age;】 //【string m_name(name);】 } void getinfo() { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } // 成員形式的操作符函數(shù) Human& operator+=( /* Human* this */ const Human& that ) { this->m_age = this->m_age + that.m_age; this->m_name = this->m_name+"+"+that.m_name; return *this; } private: int m_age; string m_name; }; // 全局形式的操作符函數(shù) // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human a(22,"張飛"), b(20,"趙云"); // 非常左值 const Human c(25,"關(guān)羽"), d(32,"馬超"); // 常左值 ((a+=b)+=c)+=Human(45,"黃忠"); a.getinfo(); /* a += b; // a.operator+=(b) 或 operator+=(a,b) a.getinfo(); a += c; // a.operator+=(c) 或 operator+=(a,c) a.getinfo(); a += Human(45,"黃忠"); // a.operator+=(Human(45,"黃忠")) 或 operator+=(a,Human(45,"黃忠")) a.getinfo(); */ /* int a=10, b=20; // 非常左值 const int c=30, d=40; // 常左值 a = b; a = c; a = 5; c = b; // err 5 = b; // err */ return 0; } 比較類雙目操作符:>、<、==、<=、>=等 左操作數(shù)可以為非常左值、常左值或右值 右操作數(shù)可以為非常左值、常左值或右值 表達(dá)式結(jié)果必須為 bool 示例 // 比較類雙目操作符 #include using namespace std; class Human { // 授權(quán)類 public: Human( int age=0, const char* name="無名" ) : m_age(age), m_name(name) { //【int m_age=age;】 //【string m_name(name);】 } void getinfo() { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } // 成員形式的操作符函數(shù) bool operator==( /* const Human* this */ const Human& that ) const { return this->m_age==that.m_age && this->m_name==that.m_name; } bool operator!=( /* const Human* this */ const Human& that ) const { // return this->m_age!=that.m_age || this->m_name!=that.m_name; return !(*this==that); } private: int m_age; string m_name; }; // 全局形式的操作符函數(shù) // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human a(22,"張飛"), b(20,"趙云"); // 非常左值 const Human c(25,"關(guān)羽"), d(32,"馬超"); // 常左值 cout << boolalpha << (a == b) << endl; // a.operator==(b) 或 ... cout << boolalpha << (a != b) << endl; // a.operator!=(b) 或 ... cout << boolalpha << (c == d) << endl; // c.operator==(d) 或 ... cout << boolalpha << (c != d) << endl; // c.operator!=(d) 或 ... cout << boolalpha << (Human(45,"黃忠")==Human(35,"劉備")) << endl; // Human(45,"黃忠").operator==(Human(35,"劉備")) 或 .. cout << boolalpha << (Human(45,"黃忠")!=Human(35,"劉備")) << endl; // Human(45,"黃忠").operator!=(Human(35,"劉備")) 或 .. /* int a=10, b=20; // 非常左值 const int c=30, d=40; // 常左值 a == b; a == c; a == 5; c == b; 5 == b; */ return 0; } 典型單目操作符的重載 運(yùn)算類單目操作符:-、~、!等 唯一操作數(shù)為非常左值、常左值或右值 表達(dá)式的結(jié)果必須為右值 示例 // 運(yùn)算類單目操作符 #include using namespace std; class Human { // 授權(quán)類 public: Human( int age=0, const char* name="無名" ) : m_age(age), m_name(name) { //【int m_age=age;】 //【string m_name(name);】 } void getinfo() { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } // 成員形式的操作符函數(shù) Human operator-( /* const Human* this */ ) const { return Human(-this->m_age, this->m_name.c_str() ); } private: int m_age; string m_name; }; // 全局形式的操作符函數(shù) // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human a(22,"張飛"), b(20,"趙云"); // 非常左值 const Human c(25,"關(guān)羽"), d(32,"馬超"); // 常左值 Human res = -a; // a.operator-() 或 operator-(a) res.getinfo(); res = -c; // c.operator-() 或 operator-(c) res.getinfo(); res = -Human(45,"黃忠"); // Human(45,"黃忠").operator-() 或 operator-( Human(45,"黃忠") ) res.getinfo(); /* int a=10, b=20; // 非常左值 const int c=30, d=40; // 常左值 |-10| -a; |-30| -c; |-5| -5; */ return 0; } 前自增減類單目操作符:前++、前– 操作數(shù)為非常左值表達(dá)式的結(jié)果為操作數(shù)本身(而非副本) 后自增減類單目操作符:后++、后– 操作數(shù)為非常左值 表達(dá)式的結(jié)果為右值,且為自增減以前的值 示例 // 前++ 和 后++ #include using namespace std; class Human { // 授權(quán)類 public: Human( int age=0, const char* name="無名" ) : m_age(age), m_name(name) { //【int m_age=age;】 //【string m_name(name);】 } void getinfo() { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } // 成員形式的操作符函數(shù) Human& operator++( /* Human* this */ ) { this->m_age += 1; // 直接加1 return *this; } Human operator++( /* Human* this*/ int ) { Human old = *this; // 克隆一份b原來的值 this->m_age += 1; // 直接加1 return old; // 返回的為b原來的值 } private: int m_age; string m_name; }; // 全局形式的操作符函數(shù) // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human a(22,"張飛"), b(20,"趙云"); // 非常左值 const Human c(25,"關(guān)羽"), d(32,"馬超"); // 常左值 (++a).getinfo(); // a.operator++() 或 operator++(a) (/*|..|*/b++).getinfo(); // b.operator++(0) 或 operator++(b,0) b.getinfo(); /* int a=10, b=20; // 非常左值 const int c=30, d=40; // 常左值 ++a; |...| b++; */ return 0; } 輸入輸出操作符的重載 輸出操作符:<< 左操作數(shù)為非常左值形式的輸出流(ostream)對象,右操作數(shù)為左值或右值表達(dá)式的結(jié)果為左操作數(shù)本身(而非副本)左操作數(shù)的類型為ostream,若以成員函數(shù)形式重載該操作符,就應(yīng)將其定義為ostream類的成員,該類為標(biāo)準(zhǔn)庫提供,無法添加新的成員,因此只能以全局函數(shù)形式重載該操作符 ostream& operator<< (ostream& os, const RIGHT& right) { … } 輸入操作符:>> 左操作數(shù)為非常左值形式的輸入流(istream)對象,右操作數(shù)為非常左值表達(dá)式的結(jié)果為左操作數(shù)本身(而非副本)左操作數(shù)的類型為istream,若以成員函數(shù)形式重載該操作符,就應(yīng)將其定義為istream類的成員,該類為標(biāo)準(zhǔn)庫提供,無法添加新的成員,因此只能以全局函數(shù)形式重載該操作符 istream& operator>> (istream& is, RIGHT& right) { … } 示例 // 輸出操作符(<<) 和 輸入操作符(>>) #include using namespace std; class Human { // 授權(quán)類 public: Human( int age=0, const char* name="無名" ) : m_age(age), m_name(name) { //【int m_age=age;】 //【string m_name(name);】 } void getinfo() { cout << "姓名:" << m_name << ", 年齡:" << m_age << endl; } // 成員形式的操作符函數(shù) private: int m_age; string m_name; friend ostream& operator<<( ostream& os, const Human& r ) ; friend istream& operator>>( istream& is, Human& r ) ; }; // 全局形式的操作符函數(shù) ostream& operator<<( ostream& os, const Human& r ) { os << "姓名:" << r.m_name << ", 年齡:" << r.m_age; return os; // 返回引用型參數(shù)本身 } istream& operator>>( istream& is, Human& r ) { is >> r.m_name >> r.m_age; return is; } // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human a(22,"張飛"), b(20,"趙云"); // 非常左值 const Human c(25,"關(guān)羽"), d(32,"馬超"); // 常左值 cout << a << endl; // operator<<( cout, a ) cout << c << endl; // operator<<( cout, c ) cout << Human(45,"黃忠") << endl; // operator<<( cout, Human(45,"黃忠") ) cin >> a; // operator>>( cin, a ) cout << a << endl; /* int a=10, b=20; // 非常左值 const int c=30, d=40; // 常左值 cout << a << endl; cout << c; cout << 5; */ return 0; } 下標(biāo)操作符 下標(biāo)操作符:[] 常用于在容器類型中以下標(biāo)方式獲取數(shù)據(jù)元素 非常容器的元素為非常左值,常容器的元素為常左值 示例 // 簡易的棧容器 #include using namespace std; class Stack { public: Stack() : len(0) { //【int arr[20];】 //【int len=0;】 } void push( int data ) { arr[len++] = data; } int pop() { return arr[--len]; } int size() { return len; } const int& operator[]( /* const Stack* this */ size_t i) const { // 常函數(shù) return this->arr[i]; } int& operator[]( /* Stack* this */ size_t i) { // 非常函數(shù) return this->arr[i]; } private: int arr[20]; // 保存數(shù)據(jù) int len; // 保存數(shù)據(jù)個(gè)數(shù) }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Stack s; // s是非常容器, for( int i=0; i<20; i++ ) { s.push(1000+i); } cout << "壓棧后s容器中數(shù)據(jù)的個(gè)數(shù):" << s.size() << endl; s[5] = 888; // 元素是非常左值. s.operator[](5) for( int i=0; i<20; i++ ) { cout << s[i] << ' '; } cout << endl; cout << "讀數(shù)據(jù)后s容器中數(shù)據(jù)的個(gè)數(shù):" << s.size() << endl; const Stack cs = s; // cs是常容器, cs[5] = 666; // cs.operator[](5) 應(yīng)該讓編譯器報(bào)告 readonly 錯(cuò)誤 /* int s[20] = {....}; // s是非常容器, s[5] = 888; // 元素是非常左值. const int cs[20] = {...}; // cs是常容器, cs[5] = 666; // 元素是常左值 (編譯器將報(bào)告 readonly 錯(cuò)誤) */ return 0; } 解引用和間接成員訪問操作符 解引用和間接成員訪問操作符:*、-> 如果一個(gè)類重載了 “解引用”和 “間接成員訪問操作符”,那么該類的對象就可以被當(dāng)做指針來使用 應(yīng)用的體現(xiàn)(智能指針) 智能指針的本質(zhì)就是一個(gè)類對象,并且其維護(hù)一個(gè)指針型成員變量 智能指針 常規(guī)指針的缺點(diǎn) 當(dāng)一個(gè)常規(guī)指針離開它的作用域時(shí),只有該指針變量本身所占據(jù)的內(nèi)存空間(通常是4個(gè)字節(jié))會(huì)被釋放,而它所指向的動(dòng)態(tài)內(nèi)存并未得到釋放,必須自己手動(dòng)釋放 智能指針的優(yōu)點(diǎn) 智能指針是一個(gè)類對象(封裝了常規(guī)指針),當(dāng)它離開作用域時(shí),其析構(gòu)函數(shù)負(fù)責(zé)釋放該常規(guī)指針?biāo)赶虻膭?dòng)態(tài)內(nèi)存 智能指針與常規(guī)指針的一致性 為了使智能指針也能象常規(guī)指針一樣,通過“*”操作符解引用,通過“->”操作符訪問其目標(biāo)的成員,就需要對這兩個(gè)操作符進(jìn)行重載 智能指針與常規(guī)指針的不一致性 智能指針的默認(rèn)拷貝構(gòu)造和默認(rèn)拷貝賦值只是簡單復(fù)制堆對象的地址當(dāng)多個(gè)智能指針持有同一個(gè)堆對象的地址,該堆對象將在多個(gè)智能指針的析構(gòu)函數(shù)中被釋放多次(double free) 愚蠢的解決方法(11標(biāo)準(zhǔn)已解決) 只允許 一個(gè)智能指針 持有 堆對象 的地址自定義拷貝構(gòu)造和拷貝賦值,對智能指針?biāo)钟械亩褜ο蟮刂罚缘刂烽g的轉(zhuǎn)移代替復(fù)制智能指針的轉(zhuǎn)移語義與常規(guī)指針的復(fù)制語義不一致 示例 // 解引用 和 間接成員訪問 操作符 #include #include #include #include using namespace std; class A { public: A() : m_f(open("./file", O_CREAT|O_RDWR,0644)) { //【int m_f=open(...);】 cout << "A() is invoked - 打開了file文件" << endl; } void foo() { write( m_f, "hello file", 10 ); cout << "foo() is invoked - 寫了file" << endl; } ~A() { close( m_f ); cout << "~A() is invoked - 關(guān)閉了file文件" << endl; // 釋放 m_f 本身所占內(nèi)存空間 } private: int m_f; }; class Auto_ptr { // 智能指針類 public: Auto_ptr( A* a ) : m_a(a) { //【A* m_a=a;】 } ~Auto_ptr() { delete this->m_a; // 釋放m_a常規(guī)指針指向的內(nèi)存(用戶new的內(nèi)存) this->m_a = NULL; } A& operator*() { return *m_a; } A* operator->() { return m_a; } Auto_ptr( Auto_ptr& that ) { this->m_a = that.m_a; that.m_a = NULL; // 地址轉(zhuǎn)移 } private: A* m_a; // 常規(guī)指針(保存的是用戶new的內(nèi)存的地址) }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { // C++標(biāo)準(zhǔn)庫設(shè)計(jì)的智能指針 auto_ptr pau(new A); (*pau).foo(); pau->foo(); auto_ptr pbu = pau; (*pbu).foo(); pbu->foo(); // (*pau).foo(); // pau已經(jīng)失效 /* 自己設(shè)計(jì)的智能指針 Auto_ptr pau(new A); // 定義 pau,利用 pau.Auto_ptr(new A) (*pau).foo(); // pau.operator*().foo() pau->foo(); // pau.operator->()->foo() Auto_ptr pbu = pau; // 定義pbu,利用pbu.Auto_ptr(pau) (*pbu).foo(); pbu->foo(); */ // (*pau).foo(); // pau已經(jīng)失效 /* 常規(guī)指針 A* pa = new A; // 定義pa,初值為指向一塊堆內(nèi)存(A類堆對象) (*pa).foo(); pa->foo(); A* pb = pa; delete pa; // 利用A類堆對象.~A() 釋放A類堆對象本身所占內(nèi)存空間 */ return 0; } // pau.~Auto_ptr() pbu.~Auto_ptr() 釋放 pau/pbu 本身所占內(nèi)存空間 類型轉(zhuǎn)換操作符 若源類型是基本類型,目標(biāo)類型是類類型,則只能通過類型轉(zhuǎn)換構(gòu)造函數(shù)實(shí)現(xiàn)自定義類型轉(zhuǎn)換 ? class 目標(biāo)類型 { 目標(biāo)類型 (const 源類型& src) { … } ? }; 若目標(biāo)類型是基本類型,源類型是類類型,則只能通過類型轉(zhuǎn)換操作符函數(shù) 實(shí)現(xiàn)自定義類型轉(zhuǎn)換 ? class 源類型 { operator 目標(biāo)類型 (void) const { … } ? }; 示例 // 類型轉(zhuǎn)換操作符函數(shù) 和 類型轉(zhuǎn)換構(gòu)造函數(shù) #include using namespace std; class Integer { public: Integer( int i ) : m_i(i) { // 類型轉(zhuǎn)換構(gòu)造函數(shù) //【int m_i=i;】 cout << "Integer類的類型轉(zhuǎn)換構(gòu)造函數(shù)被調(diào)用" << endl; } operator int( /* const Integer* this */ ) const { cout << "Integer類的類型轉(zhuǎn)換操作符函數(shù)被調(diào)用" << endl; return this->m_i; } private: int m_i; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { int n = 10; // 類類型 基本類型 // Integer <--- int Integer ix = n; // 定義 匿名Integer類對象, 利用 匿名Integer類對象.Integer(n)-->類型轉(zhuǎn)換構(gòu)造 // Integer ix = n.operator Integer()->int類中沒有一個(gè)成員函數(shù)operator Integer(走不通) // 基本類型 類類型 // int<---Integer int m = ix; // 定義 匿名int類對象,利用 匿名int類對象.int(ix)->int類中沒有一個(gè)形參為Integer構(gòu)造函數(shù)(走不通) // int m = ix.operator int() ---> 類型轉(zhuǎn)換操作符函數(shù) return 0; } 類對象轉(zhuǎn)換為布爾示例 // 類對象 轉(zhuǎn)化為 布爾 #include using namespace std; class A { public: A( int a ) : m_a(a) { //【int m_a=a;】 } operator bool( /* const A* this */ ) const { cout << "A類中的類型轉(zhuǎn)換操作符函數(shù)被調(diào)用" << endl; return this->m_a; } private: int m_a; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { A a(888), b(0); bool c = a; // bool c = a.operator bool() cout << boolalpha << "c=" << c << endl; c = b; // c = b.operator bool() cout << boolalpha << "c=" << c << endl; if( a ) { // a.operator bool() cout << "if語句判定條件為true" << endl; } for( ; a ; ) { // a.operator bool() cout << "for循環(huán)的第二個(gè)判定條件為true" << endl; break; } while( a ) { // a.operator bool() cout << "while循環(huán)判定條件為true" << endl; break; } cout << !a << endl; // ! a.operator bool() return 0; } 自定義類型轉(zhuǎn)換 若源類型和目標(biāo)類型都是類類型(而非基本類型),則既可以通過類型轉(zhuǎn)換構(gòu)造函數(shù)也可以通過類型轉(zhuǎn)換操作符函數(shù)實(shí)現(xiàn)自定義類型轉(zhuǎn)換,但不要兩者同時(shí)使用,沒有必要 若源類型和目標(biāo)類型都是基本類型,則無法實(shí)現(xiàn)自定義類型轉(zhuǎn)換,基本類型間的類型轉(zhuǎn)換規(guī)則完全由編譯器內(nèi)置 示例 // 類型轉(zhuǎn)換構(gòu)造函數(shù) 和 類型轉(zhuǎn)換操作符函數(shù) #include using namespace std; class Dog; // 短式聲明 class Cat { public: Cat( const char* name ) : m_name(name) { // 類型轉(zhuǎn)換構(gòu)造函數(shù) //【string m_name(name);】 } operator Dog( /* const Cat* this */ ) const; void talk() { cout << m_name << ": 喵喵~~~" << endl; } private: string m_name; friend class Dog; // 友元聲明 }; class Dog { public: Dog( const char* name ) : m_name(name) { // 類型轉(zhuǎn)換構(gòu)造函數(shù) //【string m_name(name);】 } Dog( const Cat& c ) { // 類型轉(zhuǎn)換構(gòu)造函數(shù)( 定制了 Cat->Dog的轉(zhuǎn)換規(guī)則 ) m_name = c.m_name; cout << "Dog類的類型轉(zhuǎn)換構(gòu)造函數(shù)被調(diào)用" << endl; } void talk() { cout << m_name << ": 汪汪~~~" << endl; } private: string m_name; }; Cat::operator Dog( /* const Cat* this */ ) const { cout << "Cat類的類型轉(zhuǎn)換操作符函數(shù)被調(diào)動(dòng)" << endl; return Dog( this->m_name.c_str() ); } // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Cat smallwhite("小白"); Dog bigyellow = smallwhite; // 定義 匿名Dog類對象,利用 匿名Dog類對象.Dog(smallwhite)-->類型轉(zhuǎn)換構(gòu)造 // Dog bigyellow = smallwhite.operator Dog() -->類型轉(zhuǎn)換操作符函數(shù) return 0; } 操作符重載的限制 不是所有的操作符都能重載,以下操作符不能重載 作用域限定操作符( :: )直接成員訪問操作符( . )條件操作符( ?: )字節(jié)長度操作符( sizeof )類型信息操作符( typeid ) 無法重載所有操作數(shù)均為基本類型的操作符 1 + 1 = 8 ? 繼承 繼承的語法 class 子類 : 繼承方式1 基類1, 繼承方式2 基類2, … { … }; 繼承方式 公有繼承:public保護(hù)繼承:protected有繼承:private 三種繼承方式相同的基本特點(diǎn) 繼承所要達(dá)到的目的: 子類對象包含基類子對象(成員包含基類類型的對象)子類內(nèi)部可以直接訪問基類的所有非私有成員 繼承的本質(zhì): 基類的非私有成員在子類中僅僅為可見,而非擁有 示例 // 繼承最基本特點(diǎn): // (1) 子類對象 內(nèi)部包含 基類子對象 // (2) 子類內(nèi)部 可以直接訪問 基類的 非私有(公有/保護(hù))成員(變量/函數(shù)) #include using namespace std; class Base { public: int m_a; void foo() { cout << "Base::foo" << endl; } protected: int m_b; void bar() { cout << "Base::bar" << endl; } private: int m_c; void hum() { cout << "Base::hum" << endl; } }; //class Derived : public Base { //class Derived : protected Base { class Derived : private Base { public: void fun() { m_a = 100; // ok foo(); // ok m_b = 200; // ok bar(); // ok // m_c = 300; // err // hum(); // err } private: int m_d; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Base b; // 基類對象--> |m_a m_b m_c| cout << "基類對象b的大小:" << sizeof(b) << endl; // 12 Derived d; // 子類對象--> |基類子對象|m_d| --> |m_a m_b m_c|m_d| cout << "子類對象d的大小:" << sizeof(d) << endl; // 16 return 0; } 對于繼承切忌不要理解為基類的成員變?yōu)樽宇惖某蓡T,繼承不會(huì)改變類成員的作用域,基類的成員永遠(yuǎn)都是基類的成員,并不會(huì)因?yàn)槔^承而變成子類的成員 盡管基類的公有和保護(hù)成員在子類中直接可見,但仍然可以在子類中重新定義這些名字,子類中的名字會(huì)隱藏所有基類中的同名定義,如果需要在子類內(nèi)部訪問一個(gè)在基類中定義卻被子類標(biāo)識符所隱藏的名字,可以借助作用域限定操作符“::”實(shí)現(xiàn) 示例 #include using namespace std; class Base { public: int m_a; void foo() { cout << "Base::foo" << endl; } protected: int m_b; void bar() { cout << "Base::bar" << endl; } private: int m_c; void hum() { cout << "Base::hum" << endl; } }; class Derived : public Base { //class Derived : protected Base { //class Derived : private Base { public: void fun() { m_a = 100; // ok Base::foo(); // ok,子類的foo將基類的foo隱藏,但是可以通過作用域限定符要求使用基類的foo m_b = 200; // ok bar(); // ok, 子類的bar將基類的bar隱藏 // m_c = 300; // err // hum(); // err } private: int m_d; void foo() { cout << "Derived::foo" << endl; } void bar() { cout << "Derived::bar" << endl; } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Derived d; d.fun(); return 0; } 因?yàn)樽饔糜虻牟煌?,分別在子類和基類中定義的同名成員函數(shù)(包括靜態(tài)成員函數(shù)),并不構(gòu)成重載關(guān)系,相反是一種隱藏關(guān)系 任何時(shí)候,在子類的內(nèi)部,總可以通過作用域限定操作符“::”,顯式地調(diào)用那些在基類中定義卻被子類所隱藏的成員 三種繼承方式的差別 基類中的公有、保護(hù)和私有成員,在子類中將對這些基類成員的訪問控制限定進(jìn)行重新標(biāo)記,繼承方式?jīng)Q定了限定上限 基類中的在公有子類中標(biāo)記為在保護(hù)子類中標(biāo)記為在私有子類中標(biāo)記為公有成員公有成員保護(hù)成員私有成員保護(hù)成員保護(hù)成員保護(hù)成員私有成員私有成員私有成員私有成員私有成員 當(dāng)“通過”子類訪問其所繼承的基類的成員時(shí),需要考慮因繼承方式對訪問控制限定的影響 公有繼承 // 公有繼承 #include using namespace std; class Base { public: // 原始標(biāo)記 int m_a; void foo() { cout << "Base::foo" << endl; } protected: // 原始標(biāo)記 int m_b; void bar() { cout << "Base::bar" << endl; } private: // 原始標(biāo)記 int m_c; void hum() { cout << "Base::hum" << endl; } }; class Derived : public Base { // 子類將對基類的成員重新標(biāo)記訪控限定 m_a/foo是public m_b/bar是protected m_c/hum是private public: void fun() { // 在子類內(nèi)部訪問基類的成員時(shí),編譯器要查看這些成員在"基類中的原始標(biāo)記" m_a = 100; // ok foo(); // ok m_b = 200; // ok bar(); // ok // m_c = 300; // err // hum(); // err } private: int m_d; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Derived d; // 利用子類對象在類外 訪問 基類的成員時(shí),編譯器要查看這些成員在 "子類中的重新標(biāo)記" d.m_a = 100; // ok d.foo(); // ok // d.m_b = 200; // err // d.bar(); // err // d.m_c = 300; // err // d.hum(); // err return 0; } 保護(hù)繼承 // 保護(hù)繼承 #include using namespace std; class Base { public: // 原始標(biāo)記 int m_a; void foo() { cout << "Base::foo" << endl; } protected: // 原始標(biāo)記 int m_b; void bar() { cout << "Base::bar" << endl; } private: // 原始標(biāo)記 int m_c; void hum() { cout << "Base::hum" << endl; } }; class Derived : protected Base { // 子類將對基類的成員重新標(biāo)記訪控限定 m_a/foo是protected m_b/bar是protected m_c/hum是private public: void fun() { // 在子類內(nèi)部訪問基類的成員時(shí),編譯器要查看這些成員在"基類中的原始標(biāo)記" m_a = 100; // ok foo(); // ok m_b = 200; // ok bar(); // ok // m_c = 300; // err // hum(); // err } private: int m_d; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Derived d; // 利用子類對象在類外 訪問 基類的成員時(shí),編譯器要查看這些成員在 "子類中的重新標(biāo)記" // d.m_a = 100; // err // d.foo(); // err // d.m_b = 200; // err // d.bar(); // err // d.m_c = 300; // err // d.hum(); // err return 0; } 私有繼承 // 私有繼承 #include using namespace std; class Base { public: // 原始標(biāo)記 int m_a; void foo() { cout << "Base::foo" << endl; } protected: // 原始標(biāo)記 int m_b; void bar() { cout << "Base::bar" << endl; } private: // 原始標(biāo)記 int m_c; void hum() { cout << "Base::hum" << endl; } }; class Derived : private Base { // 子類將對基類的成員重新標(biāo)記訪控限定 m_a/foo是private m_b/bar是private m_c/hum是private public: void fun() { // 在子類內(nèi)部訪問基類的成員時(shí),編譯器要查看這些成員在"基類中的原始標(biāo)記" m_a = 100; // ok foo(); // ok m_b = 200; // ok bar(); // ok // m_c = 300; // err // hum(); // err } private: int m_d; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Derived d; // 利用子類對象在類外 訪問 基類的成員時(shí),編譯器要查看這些成員在 "子類中的重新標(biāo)記" // d.m_a = 100; // err // d.foo(); // err // d.m_b = 200; // err // d.bar(); // err // d.m_c = 300; // err // d.hum(); // err return 0; } 公有繼承獨(dú)有特點(diǎn) 子類對象在類外可以訪問基類公有成員(其他繼承方式不可以) 如果被子類同名標(biāo)識符隱藏也可以借助作用域限定符“::”指定訪問基類的公有成員 示例 // 公有繼承獨(dú)有的特點(diǎn) #include using namespace std; class Base { public: // 原始標(biāo)記 int m_a; void foo() { cout << "Base::foo" << endl; } protected: // 原始標(biāo)記 int m_b; void bar() { cout << "Base::bar" << endl; } private: // 原始標(biāo)記 int m_c; void hum() { cout << "Base::hum" << endl; } }; class Derived : public Base { // 子類將對基類的成員重新標(biāo)記訪控限定 m_a/foo是public m_b/bar是protected m_c/hum是private public: void foo() { cout << "Derived::foo" << endl; } private: int m_d; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Derived d; d.m_a = 100; // d.Base::foo(); // 以上兩行代碼證明:只有在公有繼承下,子類對象在類外可以訪問基類的公有成員(其他繼承不可以) return 0; } 子類類型的指針或引用 和 基類類型的指針或引用可以進(jìn)行轉(zhuǎn)換(其他繼承方式不可以) 子類類型的指針或者引用能隱式轉(zhuǎn)換為基類類型,編譯器認(rèn)為訪問范圍縮小是安全的 基類類型的指針或者引用不能隱式轉(zhuǎn)換為子類類型,編譯器認(rèn)為訪問范圍擴(kuò)大是危險(xiǎn)的 編譯器對類型安全的檢測僅僅基于指針或引用本身,基類指針或引用的實(shí)際目標(biāo),究竟是不是子類對象,完全由程序員自己判斷 示例 // 子類類型指針 和 基類類型指針 的轉(zhuǎn)換 #include using namespace std; #pragma pack(1) class Human { public: private: int m_age; string m_name; }; class Student : public Human { public: private: int m_no; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Human h; // |m_age m_name| cout << "基類對象h的大小:" << sizeof(h) << endl; // 36 Student s; // |基類子對象|m_no| --> |m_age m_name|m_no| cout << "子類對象s的大小:" << sizeof(s) << endl; // 40 Human* ph = &s; // Student*-->Human* (子類類型指針 轉(zhuǎn)換為 基類類型指針) Human& rh = s; // 編譯器認(rèn)為 以上兩個(gè)轉(zhuǎn)換訪問范圍縮小 是安全的 // Student* ps = static_cast // Student& rs = static_cast // 編譯器認(rèn)為 以上兩個(gè)轉(zhuǎn)換訪問范圍擴(kuò)大 是有風(fēng)險(xiǎn)的 // 雖然通過強(qiáng)轉(zhuǎn)可以成功,但是風(fēng)險(xiǎn)依然存在,極其不建議大家這樣做 Student* ps = static_cast Student& rs = static_cast // 以上兩種轉(zhuǎn)換毫無風(fēng)險(xiǎn),極其建議大家這么做 // 編譯器就是簡單而且粗暴 根據(jù)類型 來判斷是否存在風(fēng)險(xiǎn) return 0; } 子類的構(gòu)造、析構(gòu)和拷貝 子類的構(gòu)造 子類沒有定義構(gòu)造函數(shù) 編譯器為子類提供的默認(rèn)無參構(gòu)造函數(shù):定義基類子對象,并調(diào)用其基類的無參構(gòu)造函數(shù),構(gòu)造該子類對象中的基類子對象 子類定義構(gòu)造函數(shù)但沒有在初始化表中指明基類部分構(gòu)造方式 定義基類子對象,并調(diào)用其基類的無參構(gòu)造函數(shù),構(gòu)造該子類對象中的基類子對象 子類定義構(gòu)造函數(shù)并在初始化表中指明基類部分構(gòu)造方式 定義基類子對象并 調(diào)用指明的其基類的構(gòu)造函數(shù) 子類對象的構(gòu)造過程 構(gòu)造基類子對象->構(gòu)造成員變量->執(zhí)行構(gòu)造代碼 阻斷繼承 子類的構(gòu)造函數(shù)無論如何都會(huì)調(diào)用基類的構(gòu)造函數(shù),構(gòu)造子類對象中的基類子對象如果把基類的構(gòu)造函數(shù)定義為私有,那么該類的子類就永遠(yuǎn)無法被實(shí)例化為對象C++中可以用這種方法阻斷一個(gè)類被擴(kuò)展 子類的析構(gòu) 子類沒有定義析構(gòu)函數(shù) 編譯器將提供一個(gè)默認(rèn)析構(gòu)函數(shù):析構(gòu)完所有的成員變量以后,會(huì)自動(dòng)調(diào)用其基類的析構(gòu)函數(shù) 子類定義析構(gòu)函數(shù) 子類的析構(gòu)函數(shù)在執(zhí)行完自身析構(gòu)代碼,并析構(gòu)完所有的成員變量以后,會(huì)自動(dòng)調(diào)用其基類的析構(gòu)函數(shù) 子類對象的析構(gòu)過程 執(zhí)行析構(gòu)代碼->析構(gòu)成員變量->析構(gòu)基類子對象 子類的拷貝構(gòu)造 子類沒有定義拷貝構(gòu)造函數(shù) 編譯器為子類提供的默認(rèn)拷貝構(gòu)造函數(shù):定義基類子對象,并調(diào)用其基類的拷貝構(gòu)造函數(shù),構(gòu)造該子類對象中的基類子對象 子類定義了拷貝構(gòu)造函數(shù),但沒有在初始化表指明其基類部分的構(gòu)造方式 定義基類子對象,并調(diào)用其基類的無參構(gòu)造函數(shù),構(gòu)造該子類對象中的基類子對象 子類定義了拷貝構(gòu)造函數(shù),同時(shí)初始化表中指明了其基類部分以拷貝方式構(gòu)造 定義基類子類對象,并調(diào)用其基類的拷貝構(gòu)造函數(shù),構(gòu)造該子類對象中的基類子對象 子類的拷貝賦值 子類沒有定義拷貝賦值函數(shù) 編譯器為子類提供的缺省拷貝賦值函數(shù),會(huì)自動(dòng)調(diào)用其基類的拷貝賦值函數(shù),復(fù)制該子類對象中的基類子對象 子類定義了拷貝賦值函數(shù),但沒有顯式調(diào)用其基類的拷貝賦值函數(shù) 子類對象中的基類子對象將得不到復(fù)制 子類定義了拷貝賦值函數(shù),同時(shí)顯式調(diào)用了其基類的拷貝賦值函數(shù) 子類對象中的基類子對象將得到復(fù)制 示例 // 子類的構(gòu)造函數(shù) 和 析構(gòu)函數(shù) #include using namespace std; class Human { public: Human(int age=0, const char* name="無名") : m_age(age),m_name(name) { //【int m_age=age;】定義m_age,初值為age //【string m_name(name);】定義m_name,利用m_name.string(name) cout << "Human類的缺省構(gòu)造函數(shù)被調(diào)用" << endl; } Human( const Human& that ) : m_age(that.m_age), m_name(that.m_name) { //【int m_age=that.m_age;】定義m_age,初值為that.m_age //【string m_name(that.m_name);】定義m_name,利用m_name.string(that.m_name) cout << "Human類的拷貝構(gòu)造函數(shù)被調(diào)用" << endl; } Human& operator=( const Human& that ) { // 編譯器不會(huì)在拷貝賦值函數(shù)中塞操作 cout << "Human類的拷貝賦值函數(shù)被調(diào)用" << endl; this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string類拷貝賦值函數(shù) return *this; } ~Human() { cout << "Human類的析構(gòu)函數(shù)被調(diào)用" << endl; // 對于類類型的成員變量m_name,利用m_name.~string() // 釋放 m_age/m_name 本身所占內(nèi)存空間 } void getinfo( ) { cout << "姓名:" << m_name << ", 年齡:" << m_age; } private: int m_age; // 基本類型成員變量 string m_name; // 類類型成員變量 }; class Student : public Human { public: void getinfo() { Human::getinfo(); cout << ", 成績:" << m_score << ", 評語:" << m_remark << endl; } // 如果子類沒有提供任何構(gòu)造函數(shù),編譯器將提供一個(gè)無參的構(gòu)造函數(shù) /* Student() { 【Human();】定義基類子對象,利用基類子對象.Human() 【float m_score;】 【string m_remark;】 }*/ Student( int age=0, const char* name="無名", float score=0.0, const char* remark="沒有" ) : Human(age,name), m_score(score), m_remark(remark) { //【Human(age,name);】定義基類子對象,利用基類子對象.Human(age,name) //【float m_score=score;】 //【string m_remark(remark);】 cout << "Student類的缺省構(gòu)造函數(shù)被調(diào)用" << endl; } // 如果子類沒有提供析構(gòu)函數(shù),編譯器將提供一個(gè)默認(rèn)的析構(gòu)函數(shù) /* ~Student() { 對于m_remark,利用m_remark.~string() 對于基類子對象,利用基類子對象.~Human() 釋放 m_score/m_remark/基類子對象 本身所占內(nèi)存空間 }*/ ~Student() { cout << "Student類的析構(gòu)函數(shù)被調(diào)用" << endl; // 對于m_remark,利用m_remark.~string() // 對于基類子對象,利用基類子對象.~Human() // 釋放 m_score/m_remark/基類子對象 本身所占內(nèi)存空間 } // 如果子類沒有提供拷貝構(gòu)造函數(shù),編譯器將提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù) /* Student( const Student& that ) { 【Human(that);】定義 基類子對象,利用 基類子對象.Human(that)-->調(diào)用Human類的拷貝構(gòu)造函數(shù) 【float m_score=that.m_score;】 【string m_remark=that.m_remark;】 }*/ Student( const Student& that ) : Human(that), m_score(that.m_score),m_remark(that.m_remark) { //【Human(that);】定義 基類子對象,利用 基類子對象.Human(that)-->調(diào)用基類的拷貝構(gòu)造函數(shù) //【float m_score=that.m_score;】 //【string m_remark=that.m_remark;】 cout << "Student類的拷貝構(gòu)造函數(shù)被調(diào)用" << endl; } // 如果子類沒有提供拷貝賦值函數(shù),編譯器將提供一個(gè)默認(rèn)的拷貝賦值函數(shù) /* Student& operator=( const Student& that ) { Human& rh = *this; rh = that; // rh.operator=(that)-->調(diào)用Human類的operator=函數(shù) this->m_score = that.m_score; this->m_remark = that.m_remark; return *this; }*/ Student& operator=( const Student& that ) { // 編譯器不會(huì)在拷貝賦值函數(shù)中塞操作 cout << "Student類的拷貝賦值函數(shù)被調(diào)用" << endl; Human& rh = *this; rh = that; // rh.operator=(that)-->調(diào)用Human類的operator=函數(shù) this->m_score = that.m_score; this->m_remark = that.m_remark; return *this; } private: float m_score; string m_remark; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { cout << "--------------s1創(chuàng)建信息--------------" << endl; Student s1(22,"張飛",88.5,"良好"); // 定義s1,利用s1.Student(22,"張飛",88.5,"良好) s1.getinfo(); cout << "--------------s2創(chuàng)建信息--------------" << endl; Student s2 = s1;//(s1) 定義s2,利用s2.Student(s1) s2.getinfo(); cout << "--------------s3創(chuàng)建信息--------------" << endl; Student s3; cout << "s3被賦值前--"; s3.getinfo(); s3 = s2; // s3.operator=(s2) cout << "s3被賦值后--"; s3.getinfo(); cout << "-------------main will be over-------------" << endl; return 0; } // s1.~Student() 釋放s1本身所占內(nèi)存空間 多重繼承 一個(gè)類可以同時(shí)從多個(gè)基類繼承實(shí)現(xiàn)代碼 多重繼承的內(nèi)存布局 子類對象中的多個(gè)基類子對象,按照繼承表的順序依次被構(gòu)造,析構(gòu)的順序則與構(gòu)造嚴(yán)格相反,各個(gè)基類子對象按照從低地址到高地址排列 示例 // 多重繼承 #include using namespace std; class A { public: int m_a; A() { cout << "A()" << endl; } ~A() { cout << "~A()" << endl; } }; class B { public: int m_b; B() { cout << "B()" << endl; } ~B() { cout << "~B()" << endl; } }; class C { public: int m_c; C() { cout << "C()" << endl; } ~C() { cout << "~C()" << endl; } }; class D : public A, public B, public C { public: int m_d; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { D d; // |A基類子對象|B基類子對象|C基類子對象|m_d|-->|m_a|m_b|m_c|m_d| cout << "子類對象d的大小:" << sizeof(d) << endl; // 16 D* pd = &d; cout << "整個(gè)子類對象d的首地址 D* pd: " << pd << endl; cout << "A基類子對象的首地址: " << &d.m_a << endl; cout << "B基類子對象的首地址: " << &d.m_b << endl; cout << "C基類子對象的首地址: " << &d.m_c << endl; cout << "D類自己的成員 &m_d: " << &d.m_d << endl; return 0; } 多重繼承的類型轉(zhuǎn)換 將多重繼承的子類對象的指針,隱式轉(zhuǎn)換為它的基類類型,編譯器會(huì)根據(jù)各個(gè)基類子對象在子類對象中的內(nèi)存位置,進(jìn)行適當(dāng)?shù)钠朴?jì)算,以保證指針的類型與其所指向目標(biāo)對象的類型一致 反之,將任何一個(gè)基類類型的指針靜態(tài)轉(zhuǎn)換為子類類型,編譯器同樣會(huì)進(jìn)行適當(dāng)?shù)钠朴?jì)算 無論在哪個(gè)方向上,重解釋類型轉(zhuǎn)換(reinterpret_cast)都不進(jìn)行任何偏移計(jì)算 引用的情況與指針類似,因?yàn)橐玫谋举|(zhì)就是指針 示例 // 多重繼承 的 類型轉(zhuǎn)換 #include using namespace std; class A { public: int m_a; }; class B { public: int m_b; }; class C { public: int m_c; }; class D : public A, public B, public C { public: int m_d; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { D d; D* pd = &d; cout << "整個(gè)子類對象d的首地址 D* pd: " << pd << endl; cout << "A基類子對象的首地址: " << &d.m_a << endl; cout << "B基類子對象的首地址: " << &d.m_b << endl; cout << "C基類子對象的首地址: " << &d.m_c << endl; cout << "D類自己的成員 &m_d: " << &d.m_d << endl; cout << "-------------隱式轉(zhuǎn)換-----------------" << endl; A* pa = pd; cout << "D* pd--->A* pa: " << pa << endl; B* pb = pd; cout << "D* pd--->B* pb: " << pb << endl; C* pc = pd; cout << "D* pd--->C* pc: " << pc << endl; cout << "-------------靜態(tài)轉(zhuǎn)換-----------------" << endl; D* p1 = static_cast cout << "A* pa--->D* p1: " << p1 << endl; D* p2 = static_cast cout << "B* pb--->D* p2: " << p2 << endl; D* p3 = static_cast cout << "C* pc--->D* p3: " << p3 << endl; cout << "-------------重解釋轉(zhuǎn)換-----------------" << endl; pa = reinterpret_cast(pd); cout << "D* pd--->A* pa: " << pa << endl; pb = reinterpret_cast(pd); cout << "D* pd--->B* pb: " << pb << endl; pc = reinterpret_cast cout << "D* pd--->C* pc: " << pc << endl; return 0; } 圍繞多重繼承,歷來爭議頗多 現(xiàn)實(shí)世界中的實(shí)體本來就具有同時(shí)從多個(gè)來源共同繼承的特性,因此多重繼承有助于面向現(xiàn)實(shí)世界的問題域直接建立邏輯模型多重繼承可能會(huì)在大型程序中引入令人難以察覺的BUG,并極大地增加對類層次體系進(jìn)行擴(kuò)展的難度 名字沖突問題 如果在子類的多個(gè)基類中,存在同名的標(biāo)識符,那么任何試圖通過子類對象,或在子類內(nèi)部訪問該名字的操作,都將引發(fā)歧義 解決辦法 子類隱藏該標(biāo)識符通過作用域限定操作符“::”顯式指明所屬基類 示例 // 多重繼承 的 名字沖突問題 #include using namespace std; class A { // 學(xué)生類 public: int m_a; int m_c; // 成績 }; class B { // 老師類 public: int m_b; int m_c; // 工資 }; class D : public A, public B { // 助教類 public: int m_d; // int m_c; // 在業(yè)務(wù)上毫無意義, 僅僅將基類的m_c隱藏 void foo() { B::m_c = 8000; // } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { D d; // |A基類子對象|B基類子對象|m_d|-->|m_a m_c|m_b m_c|m_d| cout << "子類對象d的大小:" << sizeof(d) << endl; // 20 d.A::m_c = 100; // return 0; } 鉆石繼承 一個(gè)子類繼承自多個(gè)基類,而這些基類又源自共同的祖先,這樣的繼承結(jié)構(gòu)稱為鉆石繼承(菱形繼承) 鉆石繼承問題 公共基類子對象,在匯聚子類對象中,存在多個(gè)實(shí)例 在匯聚子類內(nèi)部,或通過匯聚子類對象,訪問公共基類的成員,會(huì)因繼承路徑的不同而導(dǎo)致匹配歧義 示例 // 鉆石繼承 #include using namespace std; class A { // 公共基類(人類) public: int m_a; // 年齡 }; class X : public A { // 中間子類(學(xué)生類) public: int m_x; }; class Y : public A { // 中間子類(老師類) public: int m_y; }; class Z : public X, public Y { // 匯聚子類(助教類) public: int m_z; void foo() { X::m_a = 20; // 歧義 } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Z z; // |X中間子類子對象|Y中間子類子對象|m_z|--> // |A公共基類子對象 m_x|A公共基類子對象 m_y|m_z|--> // |m_a m_x|m_a m_y|m_z| cout << "匯聚子類對象z的大小:" << sizeof(z) << endl; // 20 z.Y::m_a = 20; // 歧義 return 0; } 虛繼承 鉆石繼承問題解決方法 在繼承表中使用virtual關(guān)鍵字 虛繼承可以保證 公共虛基類子對象在匯聚子類對象中僅存一份實(shí)例 公共虛基類子對象被多個(gè)中間子類子對象所共享 虛繼承實(shí)現(xiàn)原理 匯聚子類對象中的每個(gè)中間子類子對象都持有一個(gè)指針,通過該指針可以獲取 中間子類子對象的首地址 到 公共虛基類子對象的首地址的 偏移量 示例 // 鉆石繼承缺陷的解決方法 --- 虛繼承 // 虛繼承能夠保證兩件事: // (1) 保證 公共虛基類(A類)子對象 在 匯聚子類(Z類)對象中只有一份 // (2) 公共虛基類子對象被多個(gè)中間子類子對象所共享 #include using namespace std; #pragma pack(1) class A { // 公共虛基類(人類) public: int m_a; // 年齡 }; class X : virtual public A { // 中間子類(學(xué)生類) public: int m_x; void setAge( /* X* this */ int age ) { this->m_a = age; // this-->X中間子類子對象-->指針1-->偏移量-->this+偏移量-->A公共虛基類子對象-->m_a } }; class Y : virtual public A { // 中間子類(老師類) public: int m_y; int getAge( /* Y* this */ ) { return this->m_a; // this->Y中間子類子對象-->指針2-->偏移量-->this+偏移量-->A公共虛基類子對象-->m_a } }; class Z : public X, public Y { // 匯聚子類(助教類) public: int m_z; void foo() { m_a = 20; // } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Z z; // |X中間子類子對象|Y中間子類子對象|m_z|A公共虛基類子對象|--> // |指針1 m_x|指針2 m_y|m_z|m_a| cout << "匯聚子類對象z的大小:" << sizeof(z) << endl; // 32 z.setAge( 28 ); // setAge( &z, 28 ) --> 實(shí)參類型為Z* cout << z.getAge() << endl; // getAge( &z ) --> 實(shí)參類型為Z* return 0; } 虛函數(shù) 對象的自恰性 對同樣的函數(shù)調(diào)用,各個(gè)類的對象都會(huì)做出恰當(dāng)?shù)捻憫?yīng)(是哪個(gè)類的對象,就優(yōu)先調(diào)哪個(gè)類的成員函數(shù)) 通過基類類型指針調(diào)用普通成員函數(shù)只能調(diào)用基類的成員函數(shù) 即便這個(gè)基類類型的指針指向子類對象,調(diào)用的也為基類的成員函數(shù)一旦調(diào)用子類所特有的成員函數(shù),將引發(fā)編譯錯(cuò)誤編譯器僅根據(jù)指針的類型確定調(diào)用哪個(gè)類的普通成員函數(shù) 示例 // 非虛的世界 -- 沒有虛函數(shù)的程序 #include using namespace std; class Shape { public: void Draw() { cout << "Shape::Draw" << endl; } int m_x; int m_y; }; class Rect : public Shape { public: void Draw() { cout << "Rect::Draw" << endl; } int m_rx; int m_ry; }; class Circle : public Shape { public: void Draw() { cout << "Circle::Draw" << endl; } int m_radius; void foo() {} }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { cout << "--------利用對象調(diào)用 非虛的成員函數(shù)------------" << endl; // 對象的自恰性:哪個(gè)類對象 就調(diào)用了 哪個(gè)類的 非虛的成員函數(shù) Shape s; s.Draw(); // Shape::Draw Rect r; r.Draw(); // Rect::Draw Circle c; c.Draw(); // Circle::Draw cout << "-------利用指針調(diào)用 非虛的成員函數(shù)-------------" << endl; // 基類類型指針 只能調(diào)用 基類的 非虛的成員函數(shù) Shape* ps = &s; ps->Draw(); // Shape::Draw // 即便 基類類型指針 指向的是 子類對象,調(diào)用的仍然為 基類的 非虛的成員函數(shù) ps = &r; ps->Draw(); // Shape::Draw ps = &c; ps->Draw(); // Shape::Draw // ps->foo(); // error // 編譯器簡單而且粗暴的 根據(jù) 指針本身的類型 來確定調(diào)用哪個(gè)類的非虛的成員函數(shù) return 0; } 虛函數(shù) 形如 class 類名 { virtual 返回類型 函數(shù)名 (形參表) { … } }; 的成員函數(shù),稱為虛函數(shù)或方法 覆蓋 如果子類的成員函數(shù)和基類的虛函數(shù)具有相同的函數(shù)簽名,那么該成員函數(shù)就也是虛函數(shù),無論其是否帶有virtual關(guān)鍵字,且與基類的虛函數(shù)構(gòu)成覆蓋關(guān)系 通過基類類型指針調(diào)用虛函數(shù) 如果基類型指針指向基類對象,調(diào)用基類的原始版本虛函數(shù) 如果基類型指針指向子類對象,調(diào)用子類的覆蓋版本虛函數(shù) 虛函數(shù)應(yīng)用-多態(tài) 多態(tài) 如果子類提供了對基類虛函數(shù)的有效覆蓋,那么通過一個(gè)基類型指針( 指向子類對象 ),或者基類型引用( 引用子類對象 ),調(diào)用該虛函數(shù),實(shí)際被調(diào)用的將是子類中的覆蓋版本,而非基類中的原始版本,這種現(xiàn)象稱為多態(tài) 多態(tài)的重要意義在于,一般情況下,調(diào)用哪個(gè)類的成員函數(shù)是由指針或引用本身的類型決定的,而當(dāng)多態(tài)發(fā)生時(shí),調(diào)用哪個(gè)類的成員函數(shù)是由指針或引用的實(shí)際目標(biāo)對象的類型決定的 示例 // 虛的世界 -- 有虛函數(shù)的程序 #include using namespace std; class Shape { public: virtual void Draw() { cout << "Shape::Draw" << endl; } // 原始版本虛函數(shù) int m_x; int m_y; }; class Rect : public Shape { public: void Draw() { cout << "Rect::Draw" << endl; } // 虛函數(shù),編譯器補(bǔ)virtual,與基類虛函數(shù)構(gòu)成覆蓋關(guān)系 int m_rx; int m_ry; }; class Circle : public Shape { public: virtual void Draw() { cout << "Circle::Draw" << endl; }// 虛函數(shù),編譯器不補(bǔ)virtual,與基類虛函數(shù)構(gòu)成覆蓋關(guān)系 int m_radius; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { cout << "--------利用對象調(diào)用 虛的成員函數(shù)------------" << endl; // 對象的自恰性:哪個(gè)類對象 就調(diào)用了 哪個(gè)類的 虛成員函數(shù) Shape s; s.Draw(); // Shape::Draw Rect r; r.Draw(); // Rect::Draw Circle c; c.Draw(); // Circle::Draw cout << "-------利用指針調(diào)用 虛的成員函數(shù)-------------" << endl; Shape* ps = &s; ps->Draw(); // Shape::Draw ps = &r; ps->Draw(); // Rect::Draw (多態(tài)) ps = &c; ps->Draw(); // Circle::Draw (多態(tài)) // 根據(jù) 指針指向的 對象的類型 來確定調(diào)用哪個(gè)類的 虛的成員函數(shù) return 0; } 具備以下兩個(gè)要件,多態(tài)才能表現(xiàn)出來 需要在基類中定義虛函數(shù),子類提供覆蓋版本必須借助基類型指針(指向子類對象)或者基類型引用(引用子類對象)調(diào)用該虛函數(shù) 調(diào)用虛函數(shù)的指針也可以是基類中的this指針,同樣能滿足多態(tài)的條件,但在構(gòu)造和析構(gòu)函數(shù)中除外 示例 // this指針 和 多態(tài) #include using namespace std; class Base { public: virtual void vfun() { cout << "Base::vfun()" << endl; } void foo( /* Base* this */ ) { cout << "foo函數(shù)中調(diào)用的為--"; this->vfun(); } Base( /* Base* this */ ) { cout << "構(gòu)造函數(shù)中調(diào)用的為--"; this->vfun(); } ~Base( /* Base* this */ ) { cout << "析構(gòu)函數(shù)中調(diào)用的為--"; this->vfun(); } }; class Derived : public Base { public: Derived() { //【Base();】定義 基類子對象,利用 基類子對象.Base() } ~Derived() { // 對于 基類子對象,利用 基類子對象.~Base() } void vfun() { cout << "Derived::vfun()" << endl; } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { Derived d; // 定義d,利用d.Derived() d.foo(); // foo( &d ) return 0; } // d.~Derived() 虛函數(shù)表 示例 // 多態(tài)揭秘 -- 虛函數(shù)表 #include using namespace std; class A { // 編譯器根據(jù)A類信息,將制作一張?zhí)摵瘮?shù)表 A::foo的地址 A::bar的地址 public: virtual void foo() { cout << "A::foo" << endl; } virtual void bar() { cout << "A::bar" << endl; } }; class B : public A { // 編譯器根據(jù)B類信息,將制作一張?zhí)摵瘮?shù)表 B::foo的地址 A::bar的地址 public: void foo() { cout << "B::foo" << endl; } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { A a; // |虛表指針|-->編譯器根據(jù)A類信息制作的虛函數(shù)表 cout << "a對象的大小:" << sizeof(a) << endl; // 8 B b; // |虛表指針|-->編譯器根據(jù)B類信息制作的虛函數(shù)表 cout << "b對象的大小:" << sizeof(b) << endl; // 8 void(**pfunc)() = *((void(***)())&a); // 將a中的數(shù)據(jù)(虛表指針)取出來,虛表指針是一個(gè)二級指針 pfunc[0](); // A::foo pfunc[1](); // A::bar void(**pfunc2)() = *((void(***)())&b); // 將b中的數(shù)據(jù)(虛表指針)取出來 pfunc2[0](); // B::foo pfunc2[1](); // A::bar A* pa = &b; pa->foo(); // 在編譯期間,編譯器并不知道調(diào)用哪個(gè)foo // 1. 利用pa找到b對象所占內(nèi)存空間 // 2. 從b對象所占內(nèi)存空間中獲取 虛表指針 // 3. 利用虛表指針 找到 編譯器根據(jù)B類信息制作的虛函數(shù)表 // 4. 從虛函數(shù)表中 獲取 虛函數(shù)的入口地址 // 5. 利用 虛函數(shù)的入口地址 調(diào)用 虛函數(shù) return 0; } 多態(tài)應(yīng)用示例 // 多態(tài)的應(yīng)用場景 #include #include #include #include using namespace std; // 設(shè)計(jì)一個(gè) "通用" 的線程類,能滿足全天下所有用戶開線程需求 class Thread { public: void start( /* Thread* this */ ) { pthread_create(&m_tid,NULL,threadrun,this); } static void* threadrun( void* arg ) {//編譯器不會(huì)給靜態(tài)函數(shù)補(bǔ)this指針 // 線程開啟后執(zhí)行的操作不應(yīng)該由類的設(shè)計(jì)者提供,應(yīng)該由用戶提供 // 類的設(shè)計(jì)者調(diào)用用戶提供的操作 Thread* p = (Thread*)arg; p->run(); } virtual void run() = 0; // 原始版本虛函數(shù) private: pthread_t m_tid; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) class MyThread : public Thread { public: MyThread( char ch, int sec ) : m_ch(ch),m_sec(sec) {} void run() { // 覆蓋版本虛函數(shù) for( ;; ) { usleep(1000*m_sec); cout << m_ch << flush; } } private: char m_ch; int m_sec; }; int main( void ) { MyThread t1('+',1000), t2('*',500); t1.start(); t2.start(); getchar(); return 0; } 動(dòng)態(tài)綁定 當(dāng)編譯器看到通過指針或引用調(diào)用虛函數(shù)的語句時(shí),并不急于生成有關(guān)函數(shù)調(diào)用的指令,相反它會(huì)用一段代碼替代該語句,這段代碼在運(yùn)行時(shí)才能被執(zhí)行,完成如下操作: 確定指針或引用的目標(biāo)對象所占內(nèi)存空間從目標(biāo)對象所占內(nèi)存空間中找到虛表指針利用虛表指針找到虛函數(shù)表從虛函數(shù)表中獲取所調(diào)用虛函數(shù)的入口地址根據(jù)入口地址,調(diào)用該函數(shù) 動(dòng)態(tài)綁定對性能的影響 虛函數(shù)表本身會(huì)增加進(jìn)程內(nèi)存空間的開銷與普通函數(shù)調(diào)用相比,虛函數(shù)調(diào)用要多出幾個(gè)步驟,會(huì)增加運(yùn)行時(shí)間的開銷動(dòng)態(tài)綁定會(huì)妨礙編譯器通過內(nèi)聯(lián)來優(yōu)化代碼只有在確實(shí)需要多態(tài)特性的場合才使用虛函數(shù),否則盡量使用普通函數(shù) 純虛函數(shù) 純虛函數(shù) 形如 class 類名 { virtual 返回類型 函數(shù)名 (形參表) = 0; }; 的虛函數(shù),稱為純虛函數(shù)或抽象方法 抽象類 擁有純虛函數(shù)的類稱為抽象類抽象類不能實(shí)例化為對象抽象類的子類如果不對基類中的全部純虛函數(shù)提供有效的覆蓋,那么該子類就也是抽象類 純抽象類 全部由純虛函數(shù)構(gòu)成的抽象類稱為純抽象類或接口 示例 // 純虛函數(shù) 和 抽象類 #include using namespace std; class A { // 抽象類 public: void bar() { } virtual void foo() = 0; // 純虛函數(shù) }; class B : public A { public: void foo() { // ... } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { // A a; // new A; B b; new B; return 0; } 虛函數(shù)的其他應(yīng)用 動(dòng)態(tài)類型轉(zhuǎn)換(dynamic_cast) 用于將基類類型的指針或引用轉(zhuǎn)換為其子類類型的指針或引用 前提是子類必須從基類多態(tài)繼承(即基類包含至少一個(gè)虛函數(shù)) 動(dòng)態(tài)類型轉(zhuǎn)換會(huì)對所需轉(zhuǎn)換的基類指針或引用做檢查,如果其指向的對象的類型與所要轉(zhuǎn)換的目標(biāo)類型一致,則轉(zhuǎn)換成功,否則轉(zhuǎn)換失敗 針對指針的動(dòng)態(tài)類型轉(zhuǎn)換,以返回空指針(NULL)表示失敗,針對引用的動(dòng)態(tài)類型轉(zhuǎn)換,以拋出bad_cast異常表示失敗 示例 // 動(dòng)態(tài)類型轉(zhuǎn)換 -- 將基類類型指針 轉(zhuǎn)換為 子類類型指針 // 將基類類型引用 轉(zhuǎn)換為 子類類型引用 #include using namespace std; class A { // 編譯器根據(jù)A類信息,將制作一張?zhí)摵瘮?shù)表 "A"...|A::foo的地址 virtual void foo() {} }; class B : public A { // 編譯器根據(jù)B類信息,將制作一張?zhí)摵瘮?shù)表 "B"...|A::foo的地址 }; class C : public B { // 編譯器根據(jù)C類信息,將制作一張?zhí)摵瘮?shù)表 "C"...|A::foo的地址 }; class D { // 編譯器根據(jù)D類信息 不會(huì)制作虛函數(shù)表 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { B b; // |虛表指針|-->編譯器根據(jù)B類信息制作的虛函數(shù)表 A* pa = &b; // B*-->A* (子類類型指針-->基類類型指針) cout << "---------dynamic_cast 不是編譯器檢查,是在運(yùn)行期間檢查并轉(zhuǎn)換---------------" << endl; B* pb = dynamic_cast(pa); // pa->b對象所占內(nèi)存空間->虛表指針->編譯器根據(jù)B類信息制作的虛函數(shù)表->"B" cout << "A* pa--->B* pb:" << pb << endl; C* pc = dynamic_cast cout << "A* pa--->C* pc:" << pc << endl; D* pd = dynamic_cast cout << "A* pa--->D* pd:" << pd << endl; cout << "---------static_cast 編譯器給我們檢查并轉(zhuǎn)換---------------" << endl; pb = static_cast(pa); // A*-->B*的反向B*-->A*可以隱式 cout << "A* pa--->B* pb:" << pb << endl; pc = static_cast cout << "A* pa--->C* pc:" << pc << endl; // pd = static_cast // cout << "A* pa--->D* pd:" << pd << endl; return 0; } typeid操作符 #include 返回type_info類型對象的常引用 type_info類的成員函數(shù)name(),返回類型名字符串type_info類支持“==”和“!=”操作符,可直接用于類型相同與否的判斷 當(dāng)其作用于基類類型的指針或引用的目標(biāo)對象時(shí) 若基類不包含虛函數(shù) typeid所返回類型信息由該指針或引用本身的類型決定若基類包含至少一個(gè)虛函數(shù),即存在多態(tài)繼承,typeid所返回類型信息由該指針或引用的實(shí)際目標(biāo)對象的類型決定 示例 // typeid操作符 : 無法獲取對象本身的常屬性信息 #include #include using namespace std; class A { // 編譯器根據(jù)A類信息,將制作一張?zhí)摵瘮?shù)表 "A"...|A::foo的地址 virtual void foo() {} }; class B : public A { // 編譯器根據(jù)B類信息,將制作一張?zhí)摵瘮?shù)表 "B"...|A::foo的地址 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { B b; // |虛表指針|-->編譯器根據(jù)B類信息制作的虛函數(shù)表 A* pa = &b; A& ra = b; cout << typeid(*pa).name() << endl;//pa->b對象所占內(nèi)存->虛表指針->編譯器根據(jù)B類信息制作的虛函數(shù)表->"B" cout << typeid(ra).name() << endl;//ra->b對象所占內(nèi)存->虛表指針->編譯器根據(jù)B類信息制作的虛函數(shù)表->"B" int m; const type_info& r = typeid(m); // 1. 獲取m的類型信息(類名,類大小,類版本...) // 2. 定義一個(gè)type_info類的對象 // 3. 將第一步獲取的類型信息 保存到 第二步創(chuàng)建type_info類對象的 各個(gè)"私有"成員變量中 // 4. 返回 上面創(chuàng)建的type_info類對象的常引用 string rn = r.name(); cout << "m的類型:" << rn << endl; const int n=m; cout << "n的類型:" << typeid(n).name() << endl; cout << (typeid(m)==typeid(n)) << endl; // type_info::operator== return 0; } 虛析構(gòu)函數(shù) delete一個(gè)基類指針(指向子類對象) 實(shí)際被調(diào)用的僅僅是基類的析構(gòu)函數(shù) 基類的析構(gòu)函數(shù)只負(fù)責(zé)析構(gòu)子類對象中的基類子對象 基類的析構(gòu)函數(shù)不會(huì)調(diào)用子類的析構(gòu)函數(shù) 在子類中分配的資源將無法得到釋放 如果將基類的析構(gòu)函數(shù)聲明為虛函數(shù),那么實(shí)際被調(diào)用的將是子類的析構(gòu)函數(shù) 子類的析構(gòu)函數(shù)將首先釋放子類對象自己的成員,然后再調(diào)用基類的析構(gòu)函數(shù)釋放該子類對象的基類部分,最終實(shí)現(xiàn)完美的資源釋放 示例 // 虛 析構(gòu)函數(shù) 能夠解決 delete一個(gè)基類指針(指向子類對象)能夠正確的調(diào)用子類析構(gòu)函數(shù) #include #include #include using namespace std; class A { public: A() : m_a(open("./cfg1", O_CREAT|O_RDWR,0644)) { //【int m_a=open(...);】 cout << "A() is invoked - 打開了cfg1文件" << endl; } virtual ~A() { close(m_a); cout << "~A() is invoked - 關(guān)閉了cfg1文件" << endl; // 釋放m_a本身所占內(nèi)存空間 } private: int m_a; }; class B : public A { public: B() : m_b(open("./cfg2", O_CREAT|O_RDWR, 0644)) { //【A();】 //【int m_b=open(...);】 cout << "B() is invoked - 打開了cfg2文件" << endl; } ~B() { close( m_b ); cout << "~B() is invoked - 關(guān)閉了cfg2文件" << endl; // 對于 基類子對象.~A() // 釋放 基類子對象/m_b 本身所占內(nèi)存空間 } private: int m_b; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { A* p = new B; // 定義 B類堆對象,利用 B類堆對象.B() // ... delete p; // p->虛析構(gòu)函數(shù)( ~B() ) 釋放 B類堆對象所占內(nèi)存空間 return 0; } // 釋放 p 指針本身所占內(nèi)存 空虛析構(gòu)函數(shù) 沒有分配任何動(dòng)態(tài)資源的類,無需定義析構(gòu)函數(shù)沒有定義析構(gòu)函數(shù)的類,編譯器會(huì)為其提供一個(gè)缺省析構(gòu)函數(shù),但缺省析構(gòu)函數(shù)并不是虛函數(shù)為了保證delete一個(gè)指向子類對象的基類指針時(shí),能夠正確調(diào)用子類的析構(gòu)函數(shù),就必須把基類的析構(gòu)函數(shù)定義為虛函數(shù),即使它是一個(gè)空函數(shù)任何時(shí)候,為基類定義一個(gè)虛析構(gòu)函數(shù)總是無害的 一個(gè)類中,除了構(gòu)造函數(shù)和靜態(tài)成員函數(shù)外,任何函數(shù)都可以被聲明為虛函數(shù) 異常 三種典型的錯(cuò)誤報(bào)告機(jī)制 通過返回值返回錯(cuò)誤信息 所有局部對象都能正確地被析構(gòu) 逐層判斷,流程繁瑣 示例 // 利用 return 報(bào)告異常 #include #include using namespace std; class A { public: A() { cout << "A() is invoked" << endl; } ~A() { cout << "~A() is invoked" << endl; } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int foo( ) { cout << "foo出錯(cuò)前的幾百行代碼" << endl; A a; FILE* pfile = fopen( "./cfg", "r" ); if( !pfile ) return -1; // (1)將數(shù)據(jù)返回給調(diào)用者 (2)跳轉(zhuǎn)至右花括號 cout << "foo出錯(cuò)后的幾百行代碼" << endl; return 0; } // a.~A() 釋放a本身所占內(nèi)存空間 int bar( ) { cout << "bar出錯(cuò)前的幾百行代碼" << endl; A b; if( foo()==-1 ) return -1; cout << "bar出錯(cuò)后的幾百行代碼" << endl; return 0; } // b.~A() 釋放b本身所占內(nèi)存空間 int hum( ) { cout << "hum出錯(cuò)前的幾百行代碼" << endl; A c; if( bar()==-1 ) return -1; cout << "hum出錯(cuò)后的幾百行代碼" << endl; return 0; } // c.~A() 釋放c本身所占內(nèi)存空間 int main( void ) { cout << "main出錯(cuò)前的幾百行代碼" << endl; A d; if( hum()==-1 ) return -1; cout << "main出錯(cuò)后的幾百行代碼" << endl; return 0; } // d.~A() 釋放d本身所占內(nèi)存空間 借助setjmp/longjmp遠(yuǎn)程跳轉(zhuǎn) 一步到位,流程簡單某些局部對象可能因此喪失被析構(gòu)的機(jī)會(huì) 拋出——捕獲異常對象 形式上一步到位,流程簡單實(shí)際上逐層析構(gòu)局部對象,避免內(nèi)存泄漏 C++異常處理 拋出異常 throw 異常對象; 可以拋出基本類型的對象,如: throw -1; throw “內(nèi)存分配失??!”; 也可以拋出類類型的對象,如: MemoryException ex; throw ex; throw MemoryException (); 但不要拋出局部對象的指針,如: MemoryException ex; throw &ex; // 錯(cuò)誤! 示例 // 利用 throw 報(bào)告異常 #include #include using namespace std; class A { public: A() { cout << "A() is invoked" << endl; } ~A() { cout << "~A() is invoked" << endl; } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) void foo( ) { cout << "foo出錯(cuò)前的幾百行代碼" << endl; A a; FILE* pfile = fopen( "./cfg", "r" ); if( !pfile ) throw -1; cout << "foo出錯(cuò)后的幾百行代碼" << endl; } // a.~A() 釋放a本身所占內(nèi)存空間 void bar( ) { cout << "bar出錯(cuò)前的幾百行代碼" << endl; A b; // try { foo(); // } // catch( int e ) { // cout << "bar中捕獲異常信息:" << e << endl; // } cout << "bar出錯(cuò)后的幾百行代碼" << endl; } // b.~A() 釋放b本身所占內(nèi)存空間 void hum( ) { cout << "hum出錯(cuò)前的幾百行代碼" << endl; A c; // try { bar(); // } // catch( int e ) { // cout << "hum中捕獲異常信息:" << e << endl; // } cout << "hum出錯(cuò)后的幾百行代碼" << endl; } // c.~A() 釋放c本身所占內(nèi)存空間 int main( void ) { cout << "main出錯(cuò)前的幾百行代碼" << endl; A d; try { hum(); } catch( int e ) { cout << "main中捕獲異常信息:" << e << endl; } cout << "main出錯(cuò)后的幾百行代碼" << endl; return 0; } // d.~A() 釋放d本身所占內(nèi)存空間 捕獲異常 try { 可能引發(fā)異常的語句; } catch (異常類型1& ex) { 針對異常類型1的異常處理; } catch (異常類型2& ex) { 針對異常類型2的異常處理; } … catch (異常類型n& ex) { 針對異常類型n的異常處理; } 未拋出異常時(shí)的流程(直線代表正常執(zhí)行,弧線代表跳過) 發(fā)生異常時(shí)的流程 捕獲異常 建議在catch子句中使用引用接收異常對象,避免因?yàn)榭截悩?gòu)造帶來性能損失 推薦以匿名臨時(shí)對象的形式拋出異常 異常對象必須允許被拷貝構(gòu)造和析構(gòu) 示例 // 關(guān)于捕獲異常的幾點(diǎn)建議 #include using namespace std; class A { public: A() {} A( const A& that ) { cout << "A類拷貝構(gòu)造函數(shù)被調(diào)用" << endl; } ~A() {} }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) void foo( ) { A a; throw A(); // 建議拋出匿名臨時(shí)對象(被編譯器優(yōu)化) } int main( void ) { try { foo(); } catch( A& e ) { // 建議 以引用方式 接收 異常對象 // ... } return 0; } 匹配順序 根據(jù)異常對象的類型自上至下順序匹配,而非最優(yōu)匹配,因此對子類類型異常的捕獲不要放在對基類類型異常的捕獲后面 示例 // 捕獲異常的匹配順序 : 順序匹配 而非 最優(yōu)匹配 #include using namespace std; class A {}; class B : public A {}; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) void foo( ) { throw B(); // 拋出的為子(B)類對象 } int main( void ) { try { foo(); } catch( B& e ) { // 對子類類型異常的捕獲放在前面 cout << "catch(B&)" << endl; } catch( A& e ) { // 對基類類型異常的捕獲放在后面 cout << "catch(A&)" << endl; } return 0; } 標(biāo)準(zhǔn)庫異常 異常說明 異常說明是函數(shù)原型的一部分,旨在說明函數(shù)可能拋出的異常類型 返回類型 函數(shù)名 (形參表) throw (異常類型1, 異常類型2, …) { 函數(shù)體; } 異常說明是一種承諾,承諾函數(shù)不會(huì)拋出異常說明以外的異常類型 如果函數(shù)拋出了異常說明以外的異常類型,那么該異常將無法被捕獲,并導(dǎo)致進(jìn)程中止 終止流程:std::unexpected() -> std::terminate() -> abort() 示例 // 異常說明 : 承諾調(diào)用者 函數(shù)內(nèi)部只可能拋出 哪些類型的異常 #include using namespace std; void foo() throw(int,double,const char*) { // 顯式拋出異常 throw "hello world"; // 3.14; // -1; } void bar() throw(int,double,const char*) { // 隱式拋出異常 foo(); // 調(diào)用了幾十個(gè)不同的函數(shù),而且每個(gè)函數(shù)都拋出異常,并且拋出的異常類型還都不一樣 } // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { try { bar(); // foo(); } catch( int& e ) { cout << "1. catch捕獲異常信息:" << e << endl; } catch( double& e ) { cout << "2. catch捕獲異常信息:" << e << endl; } catch( const char* e ) { cout << "3. catch捕獲異常信息:" << e << endl; } return 0; } 隱式拋出異常的函數(shù)也可以列出它的異常說明 異常說明可以沒有也可以為空 沒有異常說明,表示可能拋出任何類型的異常 void foo (void) { … } 異常說明為空,表示不會(huì)拋出任何類型的異常 void foo (void) throw () { … } 示例 // 沒有異常說明 和 異常說明為空 #include using namespace std; void foo() { // 沒有異常說明:承諾調(diào)用者,函數(shù)內(nèi)部可能拋出任何類型的異常 throw 3.14; // -1; // "hello world"; } void bar() throw() { // 異常說明為空:承諾調(diào)用者,函數(shù)內(nèi)部不會(huì)拋出任何類型的異常 throw -1; } void hum() throw(int,double); // 聲明 void hum() throw(int,double) { // 定義 } // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { try { // bar(); foo(); } catch( ... ) { // 捕獲異常,但不處理(忽略) } /* catch( int& e ) { cout << "1. catch捕獲異常信息:" << e << endl; } catch( double& e ) { cout << "2. catch捕獲異常信息:" << e << endl; } catch( const char* e ) { cout << "3. catch捕獲異常信息:" << e << endl; } */ return 0; } 異常說明在函數(shù)的聲明和定義中必須保持嚴(yán)格一致,否則將導(dǎo)致編譯錯(cuò)誤 異常處理模式 拋出基本類型的異常,根據(jù)異常對象的值分別處理拋出類類型的異常,根據(jù)異常對象的類型分別處理利用類類型的異常,攜帶更多診斷信息,以便查錯(cuò)可以在catch塊中繼續(xù)拋出所捕獲的異常,或其它異常任何未被捕獲的異常,都會(huì)由std::unexpected()函數(shù)處理,缺省的處理方式就是中止進(jìn)程忽略異常,不做處理 構(gòu)造函數(shù)中的異常 構(gòu)造函數(shù)可以拋出異常,某些時(shí)候還必須拋出異常 構(gòu)造過程中可能遇到各種錯(cuò)誤,比如內(nèi)存分配失敗構(gòu)造函數(shù)沒有返回值,無法通過返回值通知調(diào)用者 構(gòu)造函數(shù)拋出異常,對象將被不完整構(gòu)造,而一個(gè)被不完整構(gòu)造的對象,其析構(gòu)函數(shù)永遠(yuǎn)不會(huì)被執(zhí)行 所有對象形式的成員變量,在拋出異常的瞬間,都能得到正確地析構(gòu)(構(gòu)造函數(shù)的回滾機(jī)制)有動(dòng)態(tài)分配的資源,必須在拋出異常之前,自己手動(dòng)釋放,否則將形成資源的泄漏 示例 // 構(gòu)造函數(shù)中的異常 #include #include using namespace std; class A { public: A() { cout << "A() is invoked" << endl; } ~A() { cout << "~A() is invoked" << endl; } }; class C { public: C() : m_p(new int) { //【A m_a;】定義m_a,利用m_a.A() //【int* m_p = new int;】 cout << "C() is invoked" << endl; FILE* pfile = fopen("./cfg", "r"); if( !pfile ) { delete m_p; // 自己手動(dòng)釋放動(dòng)態(tài)資源 // 利用m_a.~A() // 釋放 m_a/m_p 本身所占內(nèi)存空間 throw -1; } // ... 后續(xù)代碼 ... } ~C() { cout << "~C() is invoked" << endl; delete m_p; // 利用m_a.~A() // 釋放 m_a/m_p 本身所占內(nèi)存空間 } private: A m_a; int* m_p; }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { try { C c; // 定義c,利用c.C() } // 如果c是完整構(gòu)造對象,將利用c.~C() , 但如果c是殘缺對象,將不調(diào)用~C() catch( ... ) { // ... } return 0; } 析構(gòu)函數(shù)中的異常 不要從析構(gòu)函數(shù)中主動(dòng)拋出異常 在兩種情況下,析構(gòu)函數(shù)會(huì)被調(diào)用 正常銷毀對象,離開作用域或顯式delete在異常傳遞的堆棧輾轉(zhuǎn)開解(stack-unwinding)過程中 對于第二種情況,異常正處于激活狀態(tài),而析構(gòu)函數(shù)又拋出了異常,這時(shí)C++將通過std::terminate()函數(shù),令進(jìn)程中止 對于可能引發(fā)異常的操作,盡量在析構(gòu)函數(shù)內(nèi)部消化 try { … } catch (…) { … } 示例 // 不要在析構(gòu)函數(shù)中 "主動(dòng)拋出" 異常 #include #include using namespace std; void foo() { throw "foo函數(shù)中拋出的異常"; } void bar() { throw "bar函數(shù)中拋出的異常"; } class A { public: ~A() { try { bar(); } catch( const char* e ) { cout << "析構(gòu)函數(shù)中捕獲異常信息:" << e << endl; } } }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { try { A a; foo(); // ... 其他代碼 ... } // a.~A() catch( const char* e ) { cout << "main中捕獲異常信息:" << e << endl; } return 0; } I/O流 I/O流的基本概念 流/流數(shù)據(jù)(Stream) 字節(jié)序列形式的數(shù)據(jù)(如:二進(jìn)制數(shù)據(jù)、文本字符、圖形圖像、音頻視頻,等等),猶如流水一般,從一個(gè)對象流向另一個(gè)對象 輸入流(Input Stream) 數(shù)據(jù)從表示輸入設(shè)備(如:鍵盤、磁盤文件等)的對象流向內(nèi)存對象。 例如:cin >> student; 輸出流(Output Stream) 數(shù)據(jù)從內(nèi)存對象流向表示輸出設(shè)備(如:顯示器、打印機(jī)、磁盤文件等)的對象。 例如:cout << student; 流緩沖(Stream Buffer) 介于各種I/O設(shè)備和內(nèi)存對象之間的內(nèi)存緩沖區(qū)當(dāng)從鍵盤輸入時(shí),數(shù)據(jù)首先進(jìn)入鍵盤緩沖區(qū),直到按下回車鍵,才將鍵盤緩沖區(qū)中的數(shù)據(jù)灌注到輸入流緩沖區(qū),之后再通過流操作符“>>”,進(jìn)入內(nèi)存對象當(dāng)向顯示器輸出時(shí),數(shù)據(jù)首先通過流操作符“<<”, 從內(nèi)存對象進(jìn)入輸出流緩沖區(qū),直到緩沖區(qū)滿或遇到換行符,才將其中的數(shù)據(jù)灌注到顯示器上顯示出來 流對象(Stream Object) 表示各種輸入輸出設(shè)備的對象,如鍵盤、顯示器、打印機(jī)、磁盤文件等,因其皆以流的方式接收或提供數(shù)據(jù),故稱為流對象向下訪問各種物理設(shè)備接口,向上與應(yīng)用程序交互,中間維護(hù)流緩沖區(qū)三個(gè)預(yù)定義的標(biāo)準(zhǔn)流對象 cin:標(biāo)準(zhǔn)輸入設(shè)備——鍵盤cout:標(biāo)準(zhǔn)輸出設(shè)備——顯示器cerr:標(biāo)準(zhǔn)錯(cuò)誤輸出設(shè)備——顯示器,不帶緩沖 流類(Stream Class) 用于實(shí)例化流對象的類cin和cout分別是istream_withassign和ostream_withassign類的對象 流類庫(Stream Class Library) C++以繼承的方式定義了一組流類,并將其作為標(biāo)準(zhǔn)C++庫的一部分提供給用戶基于流類庫可以構(gòu)建三種形式的流對象 面向控制臺(tái)的I/O流面向文件的I/O流向內(nèi)存的I/O流 I/O流類庫 目標(biāo)抽象輸入輸出輸入輸出抽象iosistreamostreamiostream控制臺(tái)——istream_ withassignostream_ withassigniostream_withassign文件fstreambaseifstreamofstreamfstream內(nèi)存strstreambaseistrstreamostrstreamstrstream 以上只有非抽象的9個(gè)類,針對具體目標(biāo)執(zhí)行具體操作 其中控制臺(tái)的三個(gè)類已經(jīng)預(yù)定義了cin/cout/cerr流對象 實(shí)際編程中主要使用文件和內(nèi)存的6個(gè)類實(shí)現(xiàn)針對文件和內(nèi)存的I/O 出于某些原因,所有I/O流類都不支持拷貝構(gòu)造和拷貝賦值 #include ios、istream、ostream、iostreamistream_withassign、ostream_withassign、iostream_withassign #include ifstream、ofstream、fstream #include istrstream、ostrstream、strstream #include istringstream、ostringstream、stringstream I/O流的打開與關(guān)閉 通過構(gòu)造函數(shù)打開I/O流 打開輸入流 ifstream(const char* filename,openmode mode);打開輸出流 ofstream(const char* filename,openmode mode);打開輸入輸出流 fstream(const char* filename,openmode mode); 其中filename表示文件路徑,mode表示打開模式 打開模式 ios::out 打開文件用于寫入,不存在則創(chuàng)建,存在則清空 適用于ofstream(缺省)/fstream 示例 #include #include using namespace std; // C++標(biāo)準(zhǔn)設(shè)計(jì)的類 -- ofstream(輸出文件流)類 // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { ofstream ofs1( "./ofs", ios::out ); if( !ofs1 ) { // ! ofs1.operator bool() cerr << "ofs1流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } ofs1 << 1234 << ' ' << 56.78 << ' ' << "hello" << '\n'; if( !ofs1 ) { // ! ofs1.operator bool() cerr << "ofs1流對象狀態(tài)錯(cuò)誤--寫文件失敗" << endl; } ofs1.close(); ofstream ofs2( "./ofs", ios::app ); if( !ofs2 ) { // ! ofs2.operator bool() cerr << "ofs2流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } ofs2 << "world" << endl; if( !ofs2 ) { // ! ofs2.operator bool() cerr << "ofs2流對象狀態(tài)錯(cuò)誤--寫文件失敗" << endl; } ofs2.close(); return 0; } ios::app 打開文件用于追加,不存在則創(chuàng)建,存在不清空適用于ofstream/fstream ios::trunc 打開時(shí)清空原內(nèi)容適用于ofstream/fstream ios::in 打開文件用于讀取,不存在則失敗,存在不清空 適用于ifstream(缺省)/fstream 示例 #include #include using namespace std; // C++標(biāo)準(zhǔn)設(shè)計(jì)的類 -- ifstream(輸入文件流)類 // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { cout << "------------第一次讀文件-----------------------" << endl; ifstream ifs1( "./ofs", ios::in ); if( !ifs1 ) { // ! ifs1.operator bool() cerr << "ifs1流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } int i; double d; string s1, s2; ifs1 >> i >> d >> s1 >> s2; if( !ifs1 ) { // ! ifs1.operator bool() cerr << "ifs1流對象狀態(tài)錯(cuò)誤--讀文件失敗" << endl; } cout << i << ' ' << d << ' ' << s1 << ' ' << s2 << endl; ifs1.close(); cout << "------------第二次讀文件-----------------------" << endl; ifstream ifs2( "./ofs", ios::ate ); if( !ifs2 ) { // ! ifs2.operator bool() cerr << "ifs2流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } ifs2.seekg(0, ios::beg); int ii; double dd; string ss1,ss2; ifs2 >> ii >> dd >> ss1 >> ss2; if( !ifs2 ) { // ! ifs2.operator bool() cerr << "ifs2流對象狀態(tài)錯(cuò)誤--讀文件失敗" << endl; } cout << ii << ' ' << dd << ' ' << ss1 << ' ' << ss2 << endl; ifs2.close(); return 0; } ios::ate 打開時(shí)定位文件尾適用于ifstream/fstream ios::binary 以二進(jìn)制模式讀寫適用于ifstream/ofstream/fstream I/O流對象的狀態(tài) I/O流類對象內(nèi)部保存當(dāng)前狀態(tài),其值為以下常量的位或 ios::goodbit:0,一切正常ios::badbit:1,發(fā)生致命錯(cuò)誤ios::eofbit:2,遇到文件尾ios::failbit:4,打開文件失敗或?qū)嶋H讀寫字節(jié)數(shù)未達(dá)預(yù)期 I/O流的狀態(tài)成員函數(shù) 狀態(tài)成員函數(shù)說明bool ios::good (void);流可用,狀態(tài)位全零,返回truebool ios::bad (void);badbit位是否被設(shè)置bool ios::eof (void);eofbit位是否被設(shè)置bool ios::fail (void);failbit位是否被設(shè)置iostate ios::rdstate (void);獲取當(dāng)狀態(tài)void ios::clear ( iostate s = ios::goodbit);設(shè)置(復(fù)位)流狀態(tài) I/O流類對象支持到bool類型的隱式轉(zhuǎn)換 發(fā)生1,2,4等情況,返回false,否則返回true將I/O流對象直接應(yīng)用到布爾上下文中,即可實(shí)現(xiàn)轉(zhuǎn)換 處于1或4狀態(tài)的流,在復(fù)位前無法工作 示例 #include #include using namespace std; // C++標(biāo)準(zhǔn)設(shè)計(jì)的類 -- ifstream(輸入文件流)類 // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { ifstream ifs2( "./ofs", ios::ate ); if( !ifs2 ) { // ! ifs2.operator bool() cerr << "ifs2流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } cout << "------------第一次讀文件-----------------------" << endl; int ii; double dd; string ss1,ss2; ifs2 >> ii >> dd >> ss1 >> ss2; if( !ifs2 ) { // ! ifs2.operator bool() cerr << "ifs2流對象狀態(tài)錯(cuò)誤--讀文件失敗" << endl; cerr << boolalpha << "ifs2是0狀態(tài)嗎?" << ifs2.good() << ", ifs2是1狀態(tài)嗎?" << ifs2.bad() << ", ifs2是2狀態(tài)嗎?" << ifs2.eof() << ", ifs2是4狀態(tài)嗎?" << ifs2.fail() << endl; cerr << "ifs2當(dāng)前具體狀態(tài)值:" << ifs2.rdstate() << endl; } cout << ii << ' ' << dd << ' ' << ss1 << ' ' << ss2 << endl; ifs2.clear(); ifs2.seekg(0, ios::beg); cout << "------------第二次讀文件-----------------------" << endl; ifs2 >> ii >> dd >> ss1 >> ss2; if( !ifs2 ) { // ! ifs2.operator bool() cerr << "ifs2流對象狀態(tài)錯(cuò)誤--讀文件失敗" << endl; cerr << boolalpha << "ifs2是0狀態(tài)嗎?" << ifs2.good() << ", ifs2是1狀態(tài)嗎?" << ifs2.bad() << ", ifs2是2狀態(tài)嗎?" << ifs2.eof() << ", ifs2是4狀態(tài)嗎?" << ifs2.fail() << endl; cerr << "ifs2當(dāng)前具體狀態(tài)值:" << ifs2.rdstate() << endl; } cout << ii << ' ' << dd << ' ' << ss1 << ' ' << ss2 << endl; ifs2.close(); return 0; } 非格式化I/O 寫入字符 ostream& ostream::put (char ch); 一次向輸出流寫入一個(gè)字符,返回流本身 刷輸出流 ostream& ostream::flush (void);將輸出流緩沖區(qū)中的數(shù)據(jù)刷到設(shè)備上,返回流本身 讀取字符 int istream::get (void); 成功返回讀到的字符,失敗或遇到文件尾返回EOF istream& istream::get (char& ch); 返回輸入流本身,其在布爾上下文中的值,成功為true,失敗或遇到文件尾為false 示例 #include #include using namespace std; // C++標(biāo)準(zhǔn)設(shè)計(jì)的類 -- ofstream(輸出文件流)類 / ifstream(輸入文件流)類 // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { ofstream ofs( "./noformat", ios::out ); if( !ofs ) { cerr << "ofs流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } for( char c=' '; c<='~'; c++ ) { ofs.put(c).flush(); } ofs.close(); ifstream ifs( "./noformat", ios::in ); if( !ifs ) { cerr << "ifs流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } char c; // 單參get while( 1 ) { ifs.get(c); if( ifs ) { cout << c; } else { break; // 利用ifs流對象狀態(tài)錯(cuò)誤來退循環(huán) } } cout << endl; ifs.clear(); ifs.seekg(0,ios::beg); // 無參get while( 1 ) { c = ifs.get(); if( c != EOF ) { cout << c; } else { break; } } cout << endl; ifs.close(); return 0; } 讀取行 istream& istream::getline (char* buffer,streamsize num, char delim = ‘\n’); 讀取字符(至定界符)到buffer中一旦讀取了num個(gè)字符還未讀取定界符,第num個(gè)字符設(shè)置為’\0’,返回(輸入流對象狀態(tài)為4)如果因?yàn)橛龅蕉ń绶ㄈ笔椤甛n’)返回(輸入流對象狀態(tài)為0)定界符被讀取并丟棄,追加結(jié)尾空字符‘\0’,讀指針停在該定界符的下一個(gè)位置遇到文件尾,返回 (輸入流對象狀態(tài)為6) 示例 #include #include using namespace std; // C++標(biāo)準(zhǔn)設(shè)計(jì)的類 -- ifstream(輸入文件流)類 // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { ifstream ifs( "./getline", ios::in ); if( !ifs ) { cerr << "ifs流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } char buf[256]; while( 1 ) { ifs.getline( buf, 256, '\n' ); if( ifs ) { cout << buf << endl; } else { break; } } /* ifs.getline( buf, 256, '\n' ); // aa\n cout << buf << endl; cout << "ifs流對象狀態(tài)值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); // bbbb\n cout << buf << endl; cout << "ifs流對象狀態(tài)值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); // cccccc\n cout << buf << endl; cout << "ifs流對象狀態(tài)值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); // dddddddd\n cout << buf << endl; cout << "ifs流對象狀態(tài)值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); // 0123456789\n cout << buf << endl; cout << "ifs流對象狀態(tài)值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); cout << buf << endl; cout << "ifs流對象狀態(tài)值:" << ifs.rdstate() << endl; */ ifs.close(); return 0; } 二進(jìn)制I/O 讀取二進(jìn)制數(shù)據(jù) istream& istream::read (char* buffer,streamsize num); 從輸入流中讀取num個(gè)字節(jié)到緩沖區(qū)buffer中返回流對象本身,其在布爾上下文中的值,成功(讀滿)為true,失敗(沒讀滿)為false如果沒讀滿num個(gè)字節(jié),函數(shù)就返回了,比如遇到文件尾,最后一次讀到緩沖區(qū)buffer中的字節(jié)數(shù),可以通過istream::gcount()函數(shù)獲得 獲取讀長度 streamsize istream::gcount (void); 返回最后一次從輸入流中讀取的字節(jié)數(shù) 寫入二進(jìn)制數(shù)據(jù) ostream& ostream::write (const char* buffer,streamsize num); 將緩沖區(qū)buffer中的num個(gè)字節(jié)寫入到輸出流中返回流本身,其在布爾上下文中的值,成功(寫滿)為true,失敗(沒寫滿)為false 示例 #include #include using namespace std; // C++標(biāo)準(zhǔn)設(shè)計(jì)的類 -- ifstream(輸入文件流)類 / ofstream(輸出文件流)類 // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { ifstream ifs( "./getline", ios::ate ); if( !ifs ) { cerr << "ifs流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } ofstream ofs( "./zjw", ios::out ); if( !ofs ) { cerr << "ofs流對象狀態(tài)錯(cuò)誤--打開文件失敗" << endl; } int size = ifs.tellg(); char buf[size]; ifs.seekg(0, ios::beg); ifs.read(buf,size); ofs.write(buf,size); ofs.close(); ifs.close(); return 0; } 讀寫指針與隨機(jī)訪問 設(shè)置讀/寫指針位置 istream& istream::seekg (off_type offset,ios::seekdir origin); ostream& ostream::seekp (off_type offset,ios::seekdir origin); origin表示偏移量offset的起點(diǎn)ios::beg:從文件的第一個(gè)字節(jié)ios::cur:從文件的當(dāng)前位置ios::end:從文件最后一個(gè)字節(jié)的下一個(gè)位置offset為負(fù)/正表示向文件頭/尾的方向偏移讀/寫指針被移到文件頭之前或文件尾之后,則失敗 獲取讀/寫指針位置 pos_type istream::tellg (void); pos_type ostream::tellp (void); 返回讀/寫指針當(dāng)前位置相對于文件頭的字節(jié)偏移量 字符串流 輸出字符串流 #include ostringstream oss; oss << 1234 << ' ' << 56.78 << ' ' << "ABCD"; string os = oss.str (); 輸入字符串流 #include string is ("1234 56.78 ABCD"); istringstream iss (is); int i; double d; string s; iss >> i >> d >> s; 格式化I/O 流函數(shù)(一組成員函數(shù)) I/O流類(ios)定義了一組用于控制輸入輸出格式的公有成員函數(shù),調(diào)用這些函數(shù)可以改變I/O流對象內(nèi)部的格式狀態(tài),進(jìn)而影響后續(xù)輸入輸出的格式化方式 流控制符 ( 一組全局函數(shù) ) 標(biāo)準(zhǔn)庫提供了一組特殊的全局函數(shù),它們有的帶有參數(shù)(在iomanip頭文件中聲明),有的不帶參數(shù)(在iostream頭文件中聲明)因其可被直接嵌入到輸入輸出表達(dá)式中,影響后續(xù)輸入輸出格式,故形象稱之為流控制符 I/O流格式化函數(shù) 格式化函數(shù)說明int ios::precision (int);設(shè)置浮點(diǎn)精度,返回原精度int ios::precision (void) const;獲取浮點(diǎn)精度int ios::width (int);設(shè)置顯示域?qū)?,返回原域?qū)抜nt ios::width (void) const;獲取顯示域?qū)抍har ios::fill (char);設(shè)置填充字符,返回原字符char ios::fill (void) const;獲取填充字符long ios::flags (long);設(shè)置格式標(biāo)志,返回原標(biāo)志long ios::flags (void) const;獲取格式標(biāo)志long ios::setf (long);添加格式標(biāo)志位,返回原標(biāo)志long ios::setf (long, long);添加格式標(biāo)志位,返回原標(biāo)志 先用第二個(gè)參數(shù)將互斥域清零long ios::unsetf (long);清除格式標(biāo)志位,返回原標(biāo)志 一般而言,對I/O流格式的改變都是持久的,即只要不再設(shè)置新格式,當(dāng)前格式將始終保持下去顯示域?qū)捠莻€(gè)例外,通過ios::width(int)所設(shè)置的顯示域?qū)?,只影響緊隨其后的第一次輸出,再往后的輸出又恢復(fù)到默認(rèn)狀態(tài) I/O流格式標(biāo)志 格式標(biāo)志位互斥域說明ios::leftios::adjustfield左對齊ios::rightios::adjustfield右對齊ios::internalios::adjustfield數(shù)值右對齊,符號左對齊ios::decios::basefield十進(jìn)制ios::octios::basefield八進(jìn)制ios::hexios::basefield十六進(jìn)制ios::fixedios::floatfield用定點(diǎn)小數(shù)表示浮點(diǎn)數(shù)ios::scientificios::basefield用科學(xué)計(jì)數(shù)法表示浮點(diǎn)數(shù) 格式標(biāo)志位說明ios::showpos正整數(shù)前面顯示+號ios::showbase顯示進(jìn)制前綴0或0xios::showpoint顯示小數(shù)點(diǎn)和尾數(shù)0ios::uppercase數(shù)中字母顯示為大寫ios::boolalpha用字符串表示布爾值ios::unitbuf每次插入都刷流緩沖ios::skipws以空白字符作分隔符 示例 cout.precision (10); cout << sqrt (200) << endl; // 14.14213562 cout << cout.precision () << endl; // 10 cout.setf (ios::scientific, ios::floatfield); cout << sqrt (200) << endl; // 1.4142135624e+01 cout.width (10); cout.fill ('-'); cout.setf (ios::internal, ios::adjustfield); cout.setf (ios::showpos); cout << 12345 << endl; // +----12345 I/O流格式化控制符 格式化控制符說明輸入輸出left左對齊√right右對齊√internal數(shù)值右對齊,符號左對齊√dec十進(jìn)制√√oct八進(jìn)制√√hex十六進(jìn)制√√fixed用定點(diǎn)小數(shù)表示浮點(diǎn)數(shù)√scientific用科學(xué)計(jì)數(shù)法表示浮點(diǎn)數(shù)√(no)showpos正整數(shù)前面(不)顯示+號√(no)showbase(不)顯示進(jìn)制前綴0或0x√(no)showpoint(不)顯示小數(shù)點(diǎn)和尾數(shù)0√(no)uppercase數(shù)中字母(不)顯示為大寫√(no)boolalpha(不)用字符串表示布爾值√√(no)unitbuf(不)每次插入都刷流緩沖√(no)skipws(不)以空白字符作分隔符√ws跳過前導(dǎo)空白字符√ends空字符√endl換行符,刷流緩沖√flush刷流緩沖√setprecision (int)設(shè)置浮點(diǎn)精度√setw (int)設(shè)置顯示域?qū)挕蘳etfill (int)設(shè)置填充字符√setiosflags (long)設(shè)置格式標(biāo)志√√resetiosflags (long)清除格式標(biāo)志√√ 示例 cout << setprecision (10) << sqrt (200) << endl; // 14.14213562 cout << cout.precision () << endl; // 10 cout << scientific << sqrt (200) << endl; // 1.4142135624e+01 cout << setw (10) << setfill ('-') << internal << showpos << 12345 << endl; // +----12345 C++11 C++語言各版本目標(biāo) C++98的目標(biāo):比C強(qiáng)、要抽象、OO、泛型C++03的目標(biāo):修補(bǔ)98的bugC++11的目標(biāo):庫、更簡單、效率更高 編譯時(shí)使用11標(biāo)準(zhǔn) g++ filename.cpp -std=c++11 類型推導(dǎo) auto關(guān)鍵字 C++98中,auto表示棧變量,通常省略不寫 void foo(void) { int i; auto int j;//表示在棧里分配的 } C++11中,給auto賦予新的語義,表示自動(dòng)類型推導(dǎo) 既根據(jù)對變量進(jìn)行初始化時(shí)所使用的數(shù)據(jù)的類型,由編譯器自動(dòng)推導(dǎo)出所定義變量的實(shí)際類型 例如: auto i=10;–> int i = 10; auto j=i; --> int j = i; auto類型推斷本質(zhì): auto1.cpp 按照定義獨(dú)立對象并根據(jù)初始化數(shù)據(jù)的類型進(jìn)行推導(dǎo) 無法自動(dòng)推斷 const,只能在auto的上下文顯示指明 如果給出的 初始化數(shù)據(jù)類型為常量指針,則可以自動(dòng)推導(dǎo)const 示例 // 類型推導(dǎo) 絕對不是 類型照抄 #include #include using namespace std; int main( void ) { int a = 10; const int b = 20; auto c = a; // auto: int c: int cout << "c的類型:" << typeid(c).name() << endl; // i cout << "&a:" << &a << ", &c:" << &c << endl; // 地址不同 c++; // 允許更改 auto d = b; // auto: int d: int cout << "d的類型:" << typeid(d).name() << endl; // i cout << "&b:" << &b << ", &d:" << &d << endl; // 地址不同 d++; // 允許更改 const auto e = b; // auto: int e: const int cout << "e的類型:" << typeid(e).name() << endl; // i cout << "&b:" << &b << ", &e:" << &e << endl; // 地址不同 // e++; // 不允許更改 auto f = &b; // 如果初始化數(shù)據(jù)為常指針(const int*),則可以自動(dòng)推導(dǎo)出const(第一種情況) // auto: const int* f: const int* cout << "f的類型:" << typeid(f).name() << endl; // PKi // *f = 888; // 不允許更改 f = NULL; return 0; } auto與引用、聯(lián)用 按照定義獨(dú)立對象并根據(jù)初始化數(shù)據(jù)的類型進(jìn)行推導(dǎo),所以不可能推導(dǎo)出引用 除非auto的上下文指明按照引用推導(dǎo) 若指明按引用推導(dǎo)并且目標(biāo)帶有常屬性,則可以自動(dòng)推導(dǎo)const 示例 // 類型推導(dǎo) 和 引用 的聯(lián)合使用 #include #include using namespace std; int main( void ) { int a = 10; const int b = 20; auto c = a; // auto: int c: int cout << "c的類型:" << typeid(c).name() << endl; // i cout << "&a:" << &a << ", &c:" << &c << endl; // 地址不同 c++; // 允許更改 auto& d = a; // auto: int d: int& cout << "d的類型:" << typeid(d).name() << endl; // i cout << "&a:" << &a << ", &d:" << &d << endl; // 地址相同 d++; // 允許更改 auto& e = b; // 指明按引用推導(dǎo),目標(biāo)帶有常屬性 則可以自動(dòng)推導(dǎo)出const(第二種推導(dǎo)const的情況) // auto: const int e: const int& cout << "e的類型:" << typeid(e).name() << endl; // i cout << "&e:" << &e << ", &b:" << &b << endl; // 地址相同 // e++; // 不允許更改 return 0; } auto關(guān)鍵字的使用限制 函數(shù)形參類型無法推導(dǎo)(C++14標(biāo)準(zhǔn)支持) 類的成員變量無法推導(dǎo) 示例 // 類型推導(dǎo) 的 局限 #include #include using namespace std; /* void foo( auto v ) { // 11標(biāo)準(zhǔn)不支持形參類型推導(dǎo),但14標(biāo)準(zhǔn)支持 // ... } */ class A { public: auto i; // 聲明 auto j; // 聲明 }; // 以上代碼模擬類的設(shè)計(jì)者(標(biāo)準(zhǔn)庫提供的類,第三方提供的類,自己設(shè)計(jì)的類) // --------------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { // foo( 10 ); // foo( 3.14 ); return 0; } 類型計(jì)算 類型計(jì)算分類 C語言 : sizeof - 計(jì)算類型的大小C++語言:typeid - 可以獲取類型的信息字符串C++11 : decltype - 獲取參數(shù)表達(dá)式的類型注意:類型計(jì)算由編譯器確定,并不是運(yùn)行期確定 與類型推導(dǎo)相比 對類型的確定更加精準(zhǔn) const int a = 10; auto b = a;//b類型推導(dǎo)為int decltype(a) c = a; //c類型計(jì)算為 const int 可以做到類型相同但值不同 const int a = 10; auto b = a; decltype(a) c = 100; 示例 // 類型計(jì)算 和 類型推導(dǎo) 的對比 // (1) 類型計(jì)算 比 類型推導(dǎo) 在類型的確定更加精準(zhǔn) // (2) 類型計(jì)算 比 類型推導(dǎo) 在初始值的確定上 更加靈活 #include #include using namespace std; int main( void ) { const int a = 10; auto b = a; // b: int cout << "b的類型:" << typeid(b).name() << endl; // i cout << "&b:" << &b << ", &a:" << &a << endl; // 地址不同 b++; // 允許更改 decltype(a) c = 888; // = a // c: const int cout << "c的類型:" << typeid(c).name() << endl; // i cout << "&c:" << &c << ", &a:" << &a << endl; // 地址不同 // c++; // 不允許更改 return 0; } 三種類型計(jì)算的規(guī)則 標(biāo)識符表達(dá)式,直接取表達(dá)式的類型 int a; decltype(a) ->int 函數(shù)表達(dá)式,取函數(shù)返回值的類型 int foo(int,int); decltype(foo(10,20))->int 其他表達(dá)式,如果表達(dá)式的值為左值,取該左值引用的類型,如果表達(dá)式的值為右值,取該右值本身的類型 示例 // 類型計(jì)算 是 編譯器計(jì)算類型 不是運(yùn)行時(shí)計(jì)算類型 #include #include using namespace std; double foo( int x, float y ); int main( void ) { int a = 10; // 如果給decltype傳遞的為標(biāo)識符表達(dá)式,decltype取該標(biāo)識符的類型作為最終計(jì)算出的類型 decltype(a) c = a; // c: int cout << "c的類型:" << typeid(c).name() << endl; // i cout << "&c:" << &c << ", &a:" << &a << endl; // 地址不同 c++; // 允許更改 // 如果給decltype傳遞的為函數(shù)表達(dá)式,decltype取該函數(shù)的返回值類型作為最終計(jì)算出的類型 decltype(foo(3,6.1)) d = a; // d: double cout << "d的類型:" << typeid(d).name() << endl; // d cout << "&d:" << &d << ", &a:" << &a << endl; // 地址不同 d++; // 允許更改 // 如果decltype傳遞的為其他表達(dá)式,并且表達(dá)式的結(jié)果為左值,decltype取該左值引用類型作為最終計(jì)算出的類型 decltype(++a) e = a; // e: int& cout << "e的類型:" << typeid(e).name() << endl; // i cout << "&e:" << &e << ", &a:" << &a << endl; // 地址相同 e++; // 允許更改 // 如果decltype傳遞的為其他表達(dá)式,并且表達(dá)式的結(jié)果為右值,decltype取該右值本身類型作為最終計(jì)算出的類型 decltype(a++) f = a; // f: int cout << "f的類型:" << typeid(f).name() << endl; // i cout << "&f:" << &f << ", &a:" << &a << endl; // 地址不同 f++; // 允許更改 return 0; } 返回值后置 auto 函數(shù)名(形參表)->decltype(表達(dá)式) 示例 // 返回值類型 后置 #include #include using namespace std; auto foo( int x, double y )->decltype(x+y) { return x + y; } int main( void ) { auto f = foo( 2, 3.14 ); cout << "f的類型:" << typeid(f).name() << endl; // d return 0; } 列表初始化 初始化方法紛繁復(fù)雜 等號初始化 int a = 123; 小括號初始化 new double(1.23); 構(gòu)造初始化 string c(“123”); 花括號初始化 struct Student d = {“張飛”,20,{1997,10,10}}; float e[] = {1.1,2.2,3.3}; 初始化方法大一統(tǒng) 之 列表初始化 基本類型,類類型,結(jié)構(gòu)/聯(lián)合/枚舉類型等等的 單個(gè)對象 或?qū)ο髷?shù)組,都可以采用形式完全統(tǒng)一的列表初始化語法進(jìn)行對象的初始化 書寫形式:類型 對象{初值表}; int a{123}; new double {1.23}; string c{“123”}; struct Student d{“張飛”,20,{1997,10,10}}; float e[]{1.1,2.2,3.3}; 示例 // 初始化大一統(tǒng) --- 列表初始化 #include using namespace std; struct BD { int m_year; int m_month; int m_day; }; struct Student { string m_name; int m_age; BD m_bday; }; class Human { public: Human( int age=0, const char* name="無名" ) : m_age{age},m_name{name} {}//形參表中不可以用{}代替=,默認(rèn)值不是初始值,{}僅用于初始化 string m_name; int m_age; }; int main( void ) { int a{123}; // = 123; cout << "a=" << a << endl; double* pd{ new double{1.23} }; // = new double{1.23}; // (1.23); cout << "*pd=" << *pd << endl; int b[]{7,8,9}; // = {7, 8, 9}; for( int i=0; i<3; i++ ) { cout << b[i] << ' '; } cout << endl; Student s{"張飛",22,{1997,5,2}}; // = {"張飛",22,{1997,5,2}}; cout << s.m_name << ":" << s.m_age << ":" << s.m_bday.m_year << "-" << s.m_bday.m_month << "-" << s.m_bday.m_day << endl; Human h{25,"關(guān)羽"}; // 定義h,利用h.Human(25,"關(guān)羽") //(25, "關(guān)羽"); cout << "姓名:" << h.m_name << ", 年齡:" << h.m_age << endl; Human* ph{ new Human{20,"趙云"} }; // = new Human{20,"趙云"}; //(20,"趙云"); cout << "姓名:" << ph->m_name << ", 年齡:" << ph->m_age << endl; return 0; } Lambda表達(dá)式 引語 C++語法中函數(shù)作用域中不能再定義函數(shù),因此沒有所謂局部函數(shù)的概念,以下寫法是不允許的 void foo(void){ ... int bar(int x, int y){ reutrn x + y; } ... cout << bar(100,200) << endl; ... } 但 函數(shù)作用域 中 可以有 類型, 當(dāng)然也可以有表達(dá)式 示例 // 函數(shù)內(nèi)部 不可以 有函數(shù) #include #include using namespace std; /* void foo( int x, double y ) { int a; // ok cout << 2*a << endl; // ok void bar() { // error cout << "bar()" << endl; } } */ // 編譯器針對這種情況(函數(shù)內(nèi)部有類型)如何工作:(1)先編函數(shù)內(nèi)部的類型 (2)再編函數(shù)本身的代碼 /* int a; void foo( int b ) { int c; class A { public: void bar( int d ) { a = 10; // ok b = 20; // err c = 30; // err d = 40; // ok } }; } */ // 編譯器針對這種情況(函數(shù)內(nèi)部有類型)如何工作:(1)先編函數(shù)內(nèi)部的類型 (2)再編函數(shù)本身的代碼 int a; class A { public: void foo( int c ) { class B { public: void bar( int d ) { a = 10; // ok b = 20; // ok c = 30; // err d = 40; // ok } }; } private: static int b; }; int A::b; int main( void ) { return 0; } 函數(shù)操作符函數(shù) 該函數(shù)無一般用法,可根據(jù)自己的需求定義該函數(shù) 示例 // 小括號操作符 函數(shù) #include using namespace std; class A { public: void operator()() { cout << "無聊" << endl; } int operator()(int& x, int& y) { return x > y ? x : y; } int operator()(int& x, int& y, int) { return x + y; } }; int main( void ) { A a; a(); // a.operator()() int m=10, n=20; cout << a(m,n) << endl; // a.operator()(m,n) cout << a(m,n,1) << endl; // a.operator()(m,n,1) return 0; } 使用 語法規(guī)則 [捕獲表](參數(shù)表) 選項(xiàng) -> 返回類型 { 函數(shù)體; }; 表像:lambda表達(dá)式的名稱是一個(gè)表達(dá)式(外觀類似函數(shù)),但本質(zhì)絕非如此 本質(zhì):lambda表達(dá)式本質(zhì)其實(shí)是一個(gè)類, 并且最終返回值為這個(gè)類的對象,因此對lambda表達(dá)式的調(diào)用就是該對象的函數(shù)操作符的調(diào)用 可以沒有返回值類型,將根據(jù)return推斷 如果連return也沒有,則返回值為void 參數(shù)為void可以省略不寫的 示例 // lambda表達(dá)式 #include #include using namespace std; int Max( int x, int y ) { return x > y ? x : y; } int main( void ) { int a{10}, b{20}; cout << Max( a, b ) << endl; auto f = [](int x, int y)->int { return x > y ? x : y; }; // 定義 // 編譯器根據(jù)lambda表達(dá)式 (1)生成一個(gè)類 (2)類內(nèi)定義小括號操作符函數(shù) (3)返回這個(gè)類匿名對象 /* class Z4mainEUliiE_ { public: int operator()(int x, int y) { return x > y ? x : y; } }; auto f = Z4mainEUliiE_{}; */ cout << f(a,b) << endl; // f.operator()(a,b) // 使用 cout << "f的類型:" << typeid(f).name() << endl; // 可以沒有返回值類型,將根據(jù)return推斷 cout << [](int x, int y){ return x+y; }(a,b) << endl; /* class X { public: auto operator()(int x, int y)->decltype(x+y) { return x+y; } }; cout << X{}(a,b) << endl; // X{}.operator()(a,b) */ // 如果沒有返回值類型,也沒有return,返回值類型就為void [](int x, int y){ cout << x << ' ' << y << endl; }(a,b); /* class XX { public: void operator()(int x, int y) { cout << x << ' ' << y << endl; } }; XX{}(a,b); // XX{}.operator()(a,b) */ // 如果沒有形參,可以省略不寫 []{ cout << "hello world" << endl; }(); /* class XXXX { public: void operator()(void) { cout << "hello world" << endl; } }; XXXX{}(); // XXXX{}.operator()() */ return 0; } 捕獲表 [] - 不捕獲任何外部變量 [variable] - 捕獲外部變量的值 (具備只讀屬性) [&variable] - 按引用捕獲,指定的外部變量 [this] - 捕獲this指針,訪問外部對象的成員 [=] - 按值捕獲所有的外部變量,也包括this [&] - 按引用捕獲所有的外部變量,也包括this [=,&variable] - 按值捕獲所有的外部變量包括this,但 是指定的外部變量按引用捕獲 [&,=variable] - 按引用捕獲所有的外部變量,也包括 this,但是指定的外部變量按值捕獲 示例 // lambda表達(dá)式 -- 捕獲表 #include #include using namespace std; int a = 10; class Y { public: Y() : e(50) {} void foo( /* Y* this */ int c=30 ) { cout << "-------------[]---------------" << endl; [](int d=40) { cout << "a=" << a << endl; cout << "b=" << b << endl; // cout << "c=" << c << endl; // err cout << "d=" << d << endl; // cout << "e=" << e << endl; // err }(); /* class X { public: void operator()(int d=40) { cout << "a=" << a << endl; cout << "b=" << b << endl; // cout << "c=" << c << endl; // err cout << "d=" << d << endl; cout << "e=" << e << endl; // err } }; X{}(); // X{}.operator()() */ cout << "------------[c]---------------" << endl; // 捕獲外部變量的值 [c](int d=40) { cout << "c=" << /*++*/c << endl; }(); /* class XX { public: XX( int m ) : c(m) {} // 這里的c并不是foo函數(shù)形參c,而是XX類的成員變量c void operator()(int d=40) { cout << "c=" << c << endl; // 這里的c并不是foo函數(shù)形參c,而是XX類成員變量c } private: const int c; //這里的c并不是foo函數(shù)形參c,而是XX類成員變量c }; XX{c}(); // 這里的c就是foo函數(shù)形參c */ cout << "------------[&c]--------------" << endl; [&c](int d=40) { cout << "c=" << ++c << endl; }(); /* class XXXX { public: XXXX(int& m) : c(m) {} // 這里的c并不是foo函數(shù)形參c,而是XXXX類成員變量c void operator()(int d=40) { cout << "c=" << c << endl; // 這里的c并不是foo函數(shù)形參c,而是XXXX類成員變量c } private: int& c; // 這里的c并不是foo函數(shù)形參c,而是XXXX類成員變量c }; XXXX{c}(); // 這里的c就是foo函數(shù)的形參c */ cout << "-----------[this]-------------" << endl; [this](int d=40) { cout << "e=" << e << endl; }(); /* class XXXXX { public: XXXXX( Y* t ) : thisT(t) {} void operator()(int d=40) { cout << "e=" << thisT->e << endl; } private: Y* thisT; }; XXXXX{this}; */ } private: static int b; int e; }; int Y::b = 20; int main( void ) { Y y; y.foo(); return 0; } 右值引用 左值 和 右值 可以“取”地址的值就是左值,左值通常具名不可“取”地址的值就是右值,右值通常匿名左值 lvalue(非常左值,常左值) 右值 rvalue:純右值xvalue:將亡值 左值引用 和 右值引用 左值引用只能引用左值,不能引用右值 int a; int& b = a; // OK int c; int& d = a + c; // ERROR 右值引用只能引用右值,不能引用左值 int&& e = a + c;// OK int&& f = a; // ERROR 常左值引用,既能引用左值,也能引用右值 const int& g = a + c; // OK const int& h = a; // OK 有沒有常右值引用呢? 其實(shí)有的,只是沒有必要,因?yàn)槌S抑狄?,完全可以被常左值引用替? 示例 // 左值引用 和 右值引用 #include using namespace std; int main( void ) { int a=10, c=20; // 左值引用只能引用左值, int& b = a; // ok // 左值引用不可以引用右值。 // int& d = /*|30|*/ a + c; // error // 右值引用只能引用右值, int&& e = /*|30|*/ a + c; // ok e = 888; // ok, 利用右值引用 引用 右值,可以通過右值引用修改目標(biāo) // 右值引用不可以引用左值。 // int&& f = a; // error // 常左值引用可以引用左值 const int& g = a; // ok // 常左值引用可以引用右值 const int& h = /*|30|*/ a + c; // ok // h = 888; // error, 利用常左值引用 引用 右值,不能通過常左值引用修改目標(biāo) return 0; } 移動(dòng)語義 方法 資源的轉(zhuǎn)移 代替 資源的重建 作用 保證功能正確的情況下,做到性能提升 示例 編譯時(shí)不要編譯器優(yōu)化:g++ filename.cpp -std=c++11 -fno-elide-constructors // 默認(rèn)拷貝構(gòu)造 以及 默認(rèn)拷貝賦值,在某些特定場景(類中有指針型成員)有(淺拷貝)缺陷 #include #include using namespace std; // 模擬C++標(biāo)準(zhǔn)庫的string類,設(shè)計(jì)一個(gè)自己的String類 class String { public: String( const char* psz="" ) : m_psz(new char[strlen(psz)+1]) { //【char* m_psz=new char[...];】定義m_psz,初值為指向一塊堆內(nèi)存(動(dòng)態(tài)資源) strcpy( m_psz, psz ); } ~String( /* String* this */ ) { delete[] this->m_psz; this->m_psz = NULL; // 釋放 m_psz 本身所占內(nèi)存空間 } char* c_str() const { return m_psz; } // 深拷貝構(gòu)造函數(shù)(體現(xiàn)的是 資源的重建) String( const String& that ) : m_psz(new char[strlen(that.m_psz)+1]) { //【char* m_psz=new char[...];】 strcpy( m_psz, that.m_psz ); // 復(fù)制了數(shù)據(jù),不復(fù)制地址-->深拷貝 cout << "String類深拷貝構(gòu)造函數(shù)被調(diào)用" << endl; } // 轉(zhuǎn)移構(gòu)造函數(shù)(體現(xiàn)的是 資源的轉(zhuǎn)移) String( String&& that ) : m_psz(that.m_psz) { //【char* m_psz = that.m_psz;】 that.m_psz = NULL; cout << "String類的轉(zhuǎn)移構(gòu)造函數(shù)被調(diào)用" << endl; } // 深拷貝賦值函數(shù)(體現(xiàn)的是 資源的重建) String& operator=( /* String* this */ const String& that ) { cout << "String類的深拷貝賦值函數(shù)被調(diào)用" << endl; if( this!=&that ) { // 防止自賦值 delete[] this->m_psz; // 釋放舊資源 this->m_psz = new char[strlen(that.m_psz)+1]; // 分配新資源 strcpy( this->m_psz, that.m_psz ); // 拷貝新內(nèi)容 } return *this; // 返回自引用 } // 轉(zhuǎn)移賦值函數(shù)(體現(xiàn)的是 資源的轉(zhuǎn)移) String& operator=( /* String* this */ String&& that ) { cout << "String類的轉(zhuǎn)移賦值函數(shù)被調(diào)用" << endl; delete[] this->m_psz; this->m_psz = that.m_psz; that.m_psz = NULL; return *this; } private: char* m_psz; }; ostream& operator<<( ostream& os, const String& that ) { os << that.c_str(); //that.m_psz; return os; } // 以上代碼模擬類的設(shè)計(jì)者 // ---------------------------- // 以下代碼模擬類的使用者(用戶) int main( void ) { String s1("hello"); cout << "------------1---------------" << endl; String s2 = s1; // 定義s2,利用s2.String(s1)-->觸發(fā)深拷貝構(gòu)造函數(shù) cout << "------------2---------------" << endl; String s3 = String("world"); // 定義s3,利用s3.String( String("world") )-->觸發(fā)轉(zhuǎn)移構(gòu)造函數(shù) cout << "------------3---------------" << endl; String s4; s4 = s3; // s4.operator=(s3)-->觸發(fā)深拷貝賦值函數(shù) cout << "------------4---------------" << endl; s4 = String("hello world"); // s4.operator=( String("hello world") )-->觸發(fā)轉(zhuǎn)移賦值函數(shù) return 0; } 推薦書目 入門 C++ 程序設(shè)計(jì)原理與實(shí)踐 - Bjarne StroustrupC++ Primer - Stanley B. Lippman 進(jìn)階 Effective C++: 改善程序與設(shè)計(jì)的55個(gè)具體做法 - Scott MeyersMore Effective C++: 35個(gè)改善編程與設(shè)計(jì)的有效方法 - Scott Meyers 深研 深度探索C++對象模型 - Stanley B. Lippman設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ) - Erich Gamma, Richard Helm, … 拓展 深入理解C++11: C++11新特性解析與應(yīng)用 - IBM XL編譯器中國開發(fā)團(tuán)隊(duì)Boost程序庫完全開發(fā)指南:深入C++“準(zhǔn)”標(biāo)準(zhǔn)庫 - 羅劍鋒 休閑 C++語言99個(gè)常見編程錯(cuò)誤 - Stephen C. DewhurstC++語言的設(shè)計(jì)與演化 - Bjarne Stroustrup大話設(shè)計(jì)模式 - 程杰 高級語法特性 模板 前言 C++為強(qiáng)類型語言 優(yōu)點(diǎn):有很多的數(shù)據(jù)類型(基本類型、類類型)在類型安全性方面是無可比擬的缺點(diǎn):因?yàn)閿?shù)據(jù)類型的多樣性,在很大程度上會(huì)給程序員編寫通用代碼帶來麻煩它使程序員需要為每一種數(shù)據(jù)類型編寫完全相同或者近乎完全相同的代碼,即便它們在抽象層面是一致的 函數(shù)模板 函數(shù)模板定義 語法 template } 注意 可以使用任何標(biāo)識符作為類型形參的名稱,但使用“T”已經(jīng)稱為一種慣例,“T”表示的是,調(diào)用者在使用函數(shù)模板時(shí)指定的任意類型 函數(shù)模板使用 使用函數(shù)模板必須對函數(shù)模板進(jìn)行實(shí)例化 語法 函數(shù)模板名<類型實(shí)參1,類型實(shí)參2,…>(調(diào)用實(shí)參1,…); Max(123,456); Max(12.3,45.6); Max(“hello”,“world”); 函數(shù)模板分析 編譯器并沒有把函數(shù)模板翻譯成一個(gè)可以處理任何數(shù)據(jù)類型的單一實(shí)體 編譯器在實(shí)例化函數(shù)模板時(shí)根據(jù)類型實(shí)參從函數(shù)模板中產(chǎn)生一個(gè)真正的函數(shù)實(shí)體 函數(shù)模板并不是一個(gè)函數(shù)實(shí)體,通過實(shí)例化才能產(chǎn)生真正的函數(shù)實(shí)體 函數(shù)模板可以看成是編譯器生成函數(shù)實(shí)體的一個(gè)依據(jù)而已 這種用具體數(shù)據(jù)類型替換函數(shù)模板類型形參的過程叫做實(shí)例化,這個(gè)過程將產(chǎn)生一個(gè)函數(shù)模板的實(shí)例(函數(shù)實(shí)體) 示例 // 函數(shù)模板 并不是 真正的函數(shù) #include using namespace std; class Integer { // Integer類并不支持>操作符 public: Integer( int i ) : m_i(i) {} bool operator>( const Integer& that ) const { return this->m_i > that.m_i; } private: int m_i; friend ostream& operator<<( ostream& os, const Integer& that ); }; ostream& operator<<( ostream& os, const Integer& that ) { os << that.m_i; return os; } template return x > y ? x : y; } // int Max( int x, int y ) { ... } // double Max( double x, double y ) {...} // string Max( string x, string y ) {...} // Integer Max( Integer x, Integer y ) { return x > y ? x : y; } int main( void ) { Integer ix(666), iy(999); cout << Max int nx=10, ny=20; cout << Max // Max(nx,ny) double dx=1.23, dy=4.56; cout << Max // Max(dx,dy) string sx="world", sy="hello"; cout << Max // Max(sx,sy) return 0; } 實(shí)例化函數(shù)模板的條件 原則上來說可以使用任何類型來實(shí)例化函數(shù)模板,不管其為基本類型還是類類型但前提是這個(gè)類型必須支持函數(shù)模板所要執(zhí)行的操作 隱式推斷類型實(shí)參 概念 若函數(shù)模板的調(diào)用形參和類型形參相關(guān),那么在實(shí)例化函數(shù)模板時(shí)即使不顯式指明類型實(shí)參,編譯器也有能力根據(jù)調(diào)用實(shí)參的類型隱式推斷出類型實(shí)參的類型。例如: 聲明:templateT Max(T x,T y){…} 實(shí)例化:Max(123,456); -> Max(123,456); 目的 獲得和調(diào)用普通函數(shù)一致的書寫形式 三種情況不能做隱式推斷 調(diào)用參數(shù)和類型參數(shù)不完全相關(guān) template #include using namespace std; template return x > y ? x : y; } template } template R r; return r; } int main( void ) { Bar Max( 123, (int)45.6 ); // 隱式推斷類型實(shí)參 的同時(shí) 不能做 隱式轉(zhuǎn)換 Foo int nx=10, ny=20; cout << Max(nx,ny) << endl; // Max<>(nx,ny) --> Max double dx=1.23, dy=4.56; cout << Max(dx,dy) << endl; // Max<>(dx,dy) --> Max string sx="world", sy="hello"; cout << Max(sx,sy) << endl; // Max<>(sx,sy) --> Max return 0; } 函數(shù)模板重載 普通函數(shù)和能夠?qū)嵗鲈摵瘮?shù)的函數(shù)模板構(gòu)成重載關(guān)系 在調(diào)用參數(shù)類型匹配度相同情況下編譯器優(yōu)先選擇普通函數(shù)除非函數(shù)模板可以產(chǎn)生調(diào)用參數(shù)類型匹配度更好的函數(shù) 隱式推斷類型實(shí)參不能同時(shí)隱式類型轉(zhuǎn)換 但 普通函數(shù)可以 在傳遞參數(shù)時(shí)如果需要編譯器做隱式類型轉(zhuǎn)換,則編譯器選擇普通函數(shù) 可以在實(shí)例化時(shí)用<>強(qiáng)行通知編譯器選擇函數(shù)模板 示例 // 函數(shù)模板的重載 #include using namespace std; void Max( int x, int y ) { // 普通函數(shù) cout << "1. Max(int,int)" << endl; } template cout << "2. Max(T,T)" << endl; } int main( void ) { int nx=10, ny=20; Max(nx,ny); double dx=1.23, dy=4.56; Max(dx,dy); Max(nx,dy); Max<>(nx,ny); return 0; } 函數(shù)模板類型形參缺省值 在實(shí)例化函數(shù)模板時(shí),如果提供了類型實(shí)參則用所提供的類型實(shí)參來實(shí)例化函數(shù)模板 在實(shí)例化函數(shù)模板時(shí),如果沒提供類型實(shí)參則用類型形參的缺省類型來實(shí)例化函數(shù)模板 如果某一個(gè)類型形參帶有缺省類型,則其后的類型形參都必須帶有缺省類型 11標(biāo)準(zhǔn)支持 示例 // 函數(shù)模板 的 默認(rèn)類型(C++11標(biāo)準(zhǔn)開始支持) #include #include using namespace std; template T t; D d; cout << "t的類型:" << typeid(t).name() << ",d的類型:" << typeid(d).name() << endl; } int main( void ) { foo(); foo return 0; } 類模板 類模板定義 形式 template 使用類模板必須對類模板進(jìn)行實(shí)例化(產(chǎn)生真正的類) 類模板本身并不代表一個(gè)確定的類型(即不能用于定義對象)只有通過類型實(shí)參實(shí)例化成真正的類后才具備類的特性(即可以定義對象)如:CMath #include #include using namespace std; template class CMath { public: CMath( T t1, T t2 ) : m_t1(t1), m_t2(t2) {} T add(); // 聲明 private: T m_t1; T m_t2; }; // (1) 帽子不能丟 (2)不要用類模板引成員,要用類引成員 template T CMath return m_t1 + m_t2; } // class CMath // class CMath // class CMath int main( void ) { int nx=10, ny=20; CMath cout << m1.add() << endl; double dx=1.23, dy=4.56; CMath cout << m2.add() << endl; string sx="hello", sy=" world"; CMath cout << m3.add() << endl; return 0; } 成員函數(shù)的延遲實(shí)例化 類模板被實(shí)例化產(chǎn)生真正類的一刻,類模板中的成員函數(shù)并沒有實(shí)例化, 成員函數(shù)只有在被調(diào)用時(shí)才會(huì)被實(shí)例化(即產(chǎn)生真正成員函數(shù)),稱之為成員函數(shù)的延遲實(shí)例化,但成員虛函數(shù)除外 某些類型雖然并沒有提供類模板所需要的全部功能但照樣可以用它來實(shí)例化類模板,只要不調(diào)用那些未提供功能的成員函數(shù)即可 示例 // 類模板 成員函數(shù)的 延遲實(shí)例化 #include #include using namespace std; class Integer { public: Integer( int i ) : m_i(i) {} Integer operator+( const Integer& that ) { return Integer( this->m_i+that.m_i ); } private: int m_i; }; template class CMath { public: CMath( T t1, T t2 ) : m_t1(t1), m_t2(t2) {} T add() { return m_t1 + m_t2; } private: T m_t1; T m_t2; }; /* class CMath public: CMath int add() { return m_t1 + m_t2; } private: int m_t1; int m_t2; }; */ /* class CMath public: CMath Integer add() { return m_t1 + m_t2; } private: Integer m_t1; Integer m_t2; }; */ int main( void ) { Integer ix(100), iy(200); CMath m2.add(); int nx=10, ny=20; CMath m.add(); return 0; } 類模板的靜態(tài)成員 類模板中的靜態(tài)成員既不是每個(gè)對象擁有一份也不是類模板擁有一份,應(yīng)該是由類模板實(shí)例化出的每一個(gè)真正的類各自擁有一份,且為該實(shí)例化類定義的所有對象共享 示例 // 類模板 的 靜態(tài)成員:不是類模板擁有,也不是類對象擁有,而是實(shí)例化出來的每個(gè)類各自擁有,并被該類的所有對象共享 #include #include using namespace std; template class A { public: static T m_t; // 聲明 static void foo() { cout << "A } }; template int main( void ) { A cout << "&A cout << "&x.m_t: " << &x.m_t << endl; // &A cout << "&y.m_t: " << &y.m_t << endl; // &A A A cout << "&A cout << "&m.m_t: " << &m.m_t << endl; // &A cout << "&n.m_t: " << &n.m_t << endl;; // &A A return 0; } 類模板的遞歸實(shí)例化 概念 利用類模板實(shí)例化產(chǎn)生的類來實(shí)例化類模板自身,這種做法稱之為類模板的遞歸實(shí)例化 應(yīng)用 通過這種方法可以構(gòu)建空間上具有遞歸特性的數(shù)據(jù)結(jié)構(gòu)(例如:多維數(shù)組),如Array< Array > 示例 // 類模板 的 遞歸實(shí)例化 #include #include using namespace std; template class Array { public: T& operator[](size_t i) { return arr[i]; } private: T arr[10]; }; // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { Array< Array for( int i=0; i<10; i++ ) { for( int j=0; j<10; j++ ) { s[i][j] = i*j; } } for( int i=1; i<10; i++ ) { for( int j=1; j<=i; j++ ) { cout << j << 'X' << i << "="; cout << setw(2) << setfill(' ') << s[i][j] << ' '; } cout << endl; } /* Array for( int i=0; i<10; i++ ) { a[i] = 1000+i; } for( int i=0; i<10; i++ ) { cout << a[i] << ' '; } cout << endl; */ return 0; } 類模板類型形參缺省值 在實(shí)例化類模板時(shí),如果提供了類型實(shí)參則用所提供的類型實(shí)參來實(shí)例化類模板 在實(shí)例化類模板時(shí),如果沒提供類型實(shí)參則用類型形參的缺省類型來實(shí)例化類模板 如果某一個(gè)類型形參帶有缺省類型,則其后的類型形參都必須帶有缺省類型 示例 // 類模板 類型形參 的 默認(rèn)類型(98/03就支持) #include #include using namespace std; template class CMath { public: void print() { cout << "m_t的類型:" << typeid(m_t).name() << endl; cout << "m_d的類型:" << typeid(m_d).name() << endl; } private: T m_t; D m_d; }; // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { CMath m.print(); CMath<> m2; m2.print(); CMath m3.print(); return 0; } 類模板擴(kuò)展 模板型成員變量 成員變量,但其類型是由類模板實(shí)例化的未知類,稱之為模板型成員變量 templateclass Arrary{…}; templateclass Sum{ public: ? Arrary m_s; // 模板型成員變量 } 示例 // 模板型 成員變量 : 1. 成員變量 2.它的類型必須是由類模板實(shí)例化出的未知類 #include using namespace std; template class Array { public: T& operator[](size_t i) { return arr[i]; } private: T arr[10]; }; template public: Sum( const Array D add() { // 求和器 D d = D(); for( int i=0; i<10; i++ ) { d += m_a[i]; } return d; } private: Array }; // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { Array for( int i=0; i<10; i++ ) { a[i] = i+1; } Sum cout << s.add() << endl; Array for( int i=0; i<10; i++ ) { sa[i] = "hello"; } Sum cout << ss.add() << endl; return 0; } 模板型成員函數(shù) 模板型成員函數(shù) 又名 成員函數(shù)模板 template class A { public: template< typename D>void foo() {…} // 成員函數(shù)模板 }; 在類外實(shí)現(xiàn) template< typename T>template< typename D> void A::foo() { … } 示例 // 模板型成員函數(shù) 又名 成員 函數(shù)模板 #include using namespace std; template public: template }; // (1) 帽子不能丟 (2) 類名::XX成員 template template void CMath cout << "CMath } // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { CMath m.foo return 0; } 模板型成員類型 模板型成員類型 又名 成員類模板 templateclass A{ public: ? templateclass B{…}; // 模板型成員類型 }; 示例 // 模板型成員類型 又名 成員類模板 #include using namespace std; template public: template public: /* template cout << "foo" << endl; } */ template }; }; template template template void A cout << "foo" << endl; } // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { A b.foo return 0; } 類模板中成員虛函數(shù) 類模板中可以有虛函數(shù) 類模板中可以定義成員虛函數(shù),和普通類的成員虛函數(shù)一樣,類模板的成員虛函數(shù)也可以表現(xiàn)出多態(tài)性 類模板中不可以有成員虛函數(shù)模板 根據(jù)成員虛函數(shù)的多態(tài)機(jī)制,需要一個(gè)虛函數(shù)表(表中保存成員虛函數(shù)的入口地址),而這個(gè)表是編譯器在實(shí)例化類模板時(shí)就創(chuàng)建,成員函數(shù)模板的實(shí)例化(即產(chǎn)生真正的函數(shù)實(shí)體)需要編譯器處理完調(diào)用后才會(huì)完成,這時(shí)才出現(xiàn)成員虛函數(shù)的地址 總結(jié) 成員函數(shù)模板的延遲實(shí)例化,阻礙了虛函數(shù)表的構(gòu)建 示例 // 類模板中 可以出現(xiàn) 虛函數(shù) (也能表現(xiàn)出多態(tài)) #include using namespace std; template public: virtual void foo() { cout << "Base template }; template public: void foo() { cout << "Derived }; // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { Derived Base pBase->foo(); // 可以表現(xiàn)出 多態(tài) pBase->bar return 0; } 模板特殊用法 數(shù)值型的類型形參 模板的類型形參也可以是是數(shù)值類型(只能是整數(shù)),可以有缺省值 template #include using namespace std; template class Array { public: T& operator[](size_t i) { return arr[i]; } int size() { return S; } private: T arr[S]; }; // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { Array for( int i=0; i a[i] = 1000+i; } for( int i=0; i cout << a[i] << ' '; } cout << endl; return 0; } 模板型的類型形參 模板的類型形參也可以是類模板,可以有缺省值 template class Arrary{….}; template< template typename C=Arrary > class Sum{ …… }; template< template typename C=Arrary > void foo() { … } 示例 // 模板 的類型形參 也可以是 類模板 #include using namespace std; template class Array { public: T& operator[](size_t i) { return arr[i]; } private: T arr[10]; }; template public: Sum( const C D add() { // 求和器 D d = D(); for( int i=0; i<10; i++ ) { d += m_a[i]; } return d; } private: C }; // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { Array for( int i=0; i<10; i++ ) { a[i] = i+1; } Sum cout << s.add() << endl; Array for( int i=0; i<10; i++ ) { sa[i] = "hello"; } Sum cout << ss.add() << endl; return 0; } 模板二次編譯 編譯器對模板會(huì)進(jìn)行兩次編譯 第一次編譯發(fā)生在實(shí)例化模板之前(產(chǎn)生真正函數(shù)或真正類之前)只檢查模板本身內(nèi)部代碼(只檢查基本詞法是否正確) 模板內(nèi)部出現(xiàn)的所有標(biāo)識符是否均有聲明對于已知類型的調(diào)用要檢查調(diào)用是否有效對于未知類型調(diào)用認(rèn)為都合理 第二次編譯發(fā)生在實(shí)例化模板之后(產(chǎn)生真正函數(shù)或真正類之后)結(jié)合所使用的類型實(shí)參,再次檢查模板代碼,查看所有調(diào)用是否真的都有效 示例 // 二次編譯( 第一次編譯過不去,談不上第二次編譯 ) #include using namespace std; class A { public: void foo() { cout << "A::foo" << endl; } }; template // abc = 10; // 第一次編譯 error A a; a.foo(); // 第一次編譯 ok // a.sdf(); // 第一次編譯 error // 第一次編譯時(shí),編譯器針對未知類型調(diào)用采取隱忍態(tài)度,盡量認(rèn)為都合理 // 第二次編譯時(shí),編譯器結(jié)合類型實(shí)參,再次檢查 調(diào)用 是否這的合理 T t; t.foo(); // 第一次編譯 ok, 第二次編譯 ok // t.fdsjfdjflds(); // 第一次編譯 ok, 第二次編譯 error // t.ffdjk<>fd(); // 第一次編譯 error } // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { func(); return 0; } 模板典型錯(cuò)誤 嵌套依賴 問題 由于模板要經(jīng)過兩次編譯,在第一次編譯模板的代碼時(shí),類型形參的具體類型尚不明確,編譯器將把類型形參的嵌套類型理解為某個(gè)未知類型的靜態(tài)成員變量,因此編譯器看到使用這樣的標(biāo)識符聲明變量時(shí)會(huì)報(bào)告錯(cuò)誤,這就叫嵌套依賴 解決方法 在類型形參的前面增加一個(gè) typename 標(biāo)識符,意在告訴編譯器其后是嵌套類的使用 示例 // 嵌套依賴 #include using namespace std; class A { public: class B { public: void foo() { cout << "A::B::foo" << endl; } }; }; template typename T::B b; // typename出現(xiàn)在這里,就是 告訴編譯器 B是T這個(gè)未知類的 嵌套類型 b.foo(); } // 以上代碼模擬類模板的設(shè)計(jì)者 // ------------------- // 以下代碼模擬用戶 int main( void ) { func(); return 0; } 利用類型形參訪問成員函數(shù)模板 問題 利用未知類定義的對象來訪問成員函數(shù)模板時(shí),編譯器在第一次編譯時(shí)無法解析成員函數(shù)模板的類型參數(shù)列表的<>而報(bào)告編譯錯(cuò)誤 解決方法 在成員函數(shù)模板之前增加template關(guān)鍵字,意在告訴編譯器其后是一個(gè)函數(shù)模板實(shí)例,編譯器就可以正確理解<>了 示例 // 利用 類型形參 訪問 成員函數(shù)模板 #include using namespace std; class A { public: template }; template T t; t.template foo } int main( void ) { func(); return 0; } 子類模板訪問基類模板 問題 在子類模板中訪問基類模板的成員,編譯器第一次編譯時(shí)只在子類模板和全局域中搜索使用的標(biāo)識符號,不會(huì)到基類模板中搜索 解決方法 在子類模板中可以通過使用作用域限定符或顯式使用this指針 示例 // 子類模板中 訪問 基類模板的成員 #include using namespace std; template public: int m_i; void foo() { cout << "Base }; //int m_i; //void foo() {} template public: // int m_i; // void foo() {} void bar( /* Derived Base Base this->m_i = 200; this->foo(); // 以上 四行代碼都為 未知類型調(diào)用,編譯器第一次編譯時(shí) 隱忍 } }; int main( void ) { Derived return 0; } STL STL - Standard Template Library (標(biāo)準(zhǔn)模板庫) 自定義雙鏈表容器演示 #include using namespace std; template public: // // 缺省構(gòu)造 // list() : m_head(NULL), m_tail(NULL) {} // // 拷貝構(gòu)造 // list( const list& that ) : m_head(NULL),m_tail(NULL) { for( node* pnode=that.m_head; pnode!=NULL; pnode=pnode->m_next ) { push_back(pnode->m_data); } } // // 析構(gòu)函數(shù) // ~list() { clear(); } // // 鏈表判空 // bool empty() const { return m_head==NULL && m_tail==NULL; } // // 添加頭節(jié)點(diǎn) // void push_front( const T& data ) { m_head = new node( NULL, data, m_head ); if( m_head->m_next ) m_head->m_next->m_prev = m_head; else m_tail = m_head; } // // 添加尾節(jié)點(diǎn) // void push_back( const T& data ) { m_tail = new node( m_tail, data, NULL ); if( m_tail->m_prev ) m_tail->m_prev->m_next = m_tail; else m_head = m_tail; } // // 刪除頭節(jié)點(diǎn) // void pop_front() throw(underflow_error) { if( empty() ) throw underflow_error("null node"); node* pnext = m_head->m_next; delete m_head; if( pnext ) pnext->m_prev = NULL; else m_tail = NULL; m_head = pnext; } // // 刪除尾節(jié)點(diǎn) // void pop_back() { if( empty() ) throw underflow_error("null node"); node* prev = m_tail->m_prev; delete m_tail; if( prev ) prev->m_next = NULL; else m_head = NULL; m_tail = prev; } // // 獲取頭節(jié)點(diǎn)數(shù)據(jù) // T front() const throw(out_of_range) { if( empty() ) throw out_of_range("null node"); return m_head->m_data; } // // 獲取尾節(jié)點(diǎn)數(shù)據(jù) // T back() const throw(out_of_range) { if( empty() ) throw out_of_range("null node"); return m_tail->m_data; } // // 鏈表清空 // void clear() { while( !empty() ) { pop_front(); } } // // 獲取鏈表大小 // size_t size() const { size_t i = 0; for( node* pnode=m_head; pnode!=NULL; pnode=pnode->m_next ) { ++i; } return i; } private: // // 節(jié)點(diǎn)類 // class node { public: node( node* prev, const T& data, node* next ) : m_prev(prev),m_data(data),m_next(next) {} node* m_prev; // 前指針 T m_data; node* m_next; // 后指針 }; public: // // 非常迭代類(用于非常容器) // class iterator { public: iterator( node* start, node* cur, node* end ) : m_start(start),m_cur(cur),m_end(end) {} iterator& operator++() { if( m_cur == NULL ) m_cur = m_start; else m_cur = m_cur->m_next; return *this; } iterator& operator--() { if( m_cur==NULL ) m_cur = m_end; else m_cur = m_cur->m_prev; return *this; } T& operator*() throw(out_of_range) { if( m_cur==NULL ) throw out_of_range("null node"); return m_cur->m_data; } bool operator==( const iterator& that ) const { return m_start==that.m_start && m_cur==that.m_cur && m_end==that.m_end; } bool operator!=( const iterator& that ) const { return !(*this==that); } private: node* m_start; // 開始指向 node* m_cur; // 當(dāng)前指向 node* m_end; // 終止指向 friend class list; }; // // 常迭代類(用于常容器) // class const_iterator { public: const_iterator( node* start, node* cur, node* end ) : m_start(start),m_cur(cur),m_end(end) {} const_iterator& operator++() { if( m_cur == NULL ) m_cur = m_start; else m_cur = m_cur->m_next; return *this; } const_iterator& operator--() { if( m_cur == NULL ) m_cur = m_end; else m_cur = m_cur->m_prev; return *this; } const T& operator*() throw(out_of_range) { // 重點(diǎn)體會(huì)為什么在這里加const******************** if( m_cur==NULL ) throw out_of_range("null node"); return m_cur->m_data; } bool operator==( const const_iterator& that ) const { return m_start==that.m_start && m_cur==that.m_cur && m_end==that.m_end; } bool operator!=( const const_iterator& that ) const { return !(*this==that); } private: node* m_start; node* m_cur; node* m_end; }; // // 創(chuàng)建非常起始迭代器(用于遍歷) // iterator begin() { return iterator(m_head,m_head,m_tail); } // // 創(chuàng)建非常終止迭代器(結(jié)束標(biāo)識) // iterator end() { return iterator(m_head,NULL,m_tail); } // // 創(chuàng)建常起始迭代器(用于遍歷) // const_iterator begin() const { return const_iterator(m_head,m_head,m_tail); } // // 創(chuàng)建常終止迭代器(結(jié)束標(biāo)識) // const_iterator end() const { return const_iterator(m_head,NULL,m_tail); } // // 在迭代器指向的位置添加節(jié)點(diǎn) // void insert( const iterator& loc, const T& data ) { if( loc == end() ) { push_back( data ); } else { node* pnew = new node(loc.m_cur->m_prev, data, loc.m_cur ); if( pnew->m_prev ) pnew->m_prev->m_next = pnew; else m_head = pnew; pnew->m_next->m_prev = pnew; } } // // 刪除迭代器指向的節(jié)點(diǎn) // void erase( const iterator& loc ) { if( loc==end() ) throw out_of_range("null node"); node* pdel = loc.m_cur; if( pdel->m_prev ) pdel->m_prev->m_next = pdel->m_next; else { pdel->m_next->m_prev = NULL; m_head = pdel->m_next; } if( pdel->m_next ) pdel->m_next->m_prev = pdel->m_prev; else { pdel->m_prev->m_next = NULL; m_tail = pdel->m_prev; } delete pdel; } private: node* m_head; // 鏈表頭指針 node* m_tail; // 鏈表尾指針 }; // // 利用"=="比較查找 // template for( IT it=beg; it!=end; ++it ) { if( *it==data ) { return it; } } return end; } // // 利用"<"實(shí)現(xiàn)的排序 // template IT last = end; --last; IT p = beg; for( IT i=beg, j=last; i!=j; ) { while( i!=p && *i<*p ) { ++i; } if( i!=p ) { swap( *i, *p ); } while( j!=p && *p<*j ) { --j; } if( j!=p ) { swap( *p, *j ); } } IT it = beg; ++it; if( p!=beg && p!=it ) { sort( beg, p ); } it = p; ++it; if( it!=end && it!=last ) { sort( it, end ); } } // // 容器設(shè)計(jì)者提供比較類 // template public: bool operator()( T x, T y) { return x > y; } }; // // 利用"比較器"實(shí)現(xiàn)的排序 // template IT last = end; --last; IT p = beg; for( IT i=beg, j=last; i!=j; ) { while( i!=p && cmp(*i,*p) ) { // cmp.operator()(*i,*p) ++i; } if( i!=p ) { swap( *i, *p ); } while( j!=p && cmp(*p,*j) ) { // cmp.operator()(*p,*j) --j; } if( j!=p ) { swap( *p, *j ); } } IT it = beg; ++it; if( p!=beg && p!=it ) { sort( beg, p, cmp ); } it = p; ++it; if( it!=end && it!=last ) { sort( it, end, cmp ); } } // 以上代碼容器的設(shè)計(jì)者 // ----------------------- // 以下代碼容器的使用者 void Print( const string& str, const list cout << str << endl; typedef list for( CIT it=l.begin(); it!=l.end(); ++it ) { cout << *it << ' '; } cout << endl << "----------------------------" << endl; } // 用戶設(shè)計(jì)比較類 class ZJW { public: bool operator()( int x, int y ) { return x < y; } }; int main( void ) { list for( int i=0; i<5; i++ ) { ls.push_front(10+i); } for( int i=0; i<5; i++ ) { ls.push_back(100+i); } Print( "添加頭尾節(jié)點(diǎn)后:", ls ); ls.pop_front(); ls.pop_back(); Print( "刪除頭尾節(jié)點(diǎn)后:", ls ); ls.insert( ++ls.begin(), 888 ); // 增 Print( "迭代器指向的位置添加節(jié)點(diǎn)后:", ls ); ls.erase( ----ls.end() ); // 刪 Print( "刪除迭代器指向的節(jié)點(diǎn)后:", ls ); typedef list IT it = ls.begin(); *it = 999; // 改 Print( "修改迭代器指向的節(jié)點(diǎn)后:", ls ); IT fit = find( ls.begin(), ls.end(), 100 ); // 查 if( fit != ls.end() ) { ls.erase( fit ); } Print( "找到100并刪除后:", ls ); // sort( ls.begin(), ls.end() ); // 利用<排序 // sort( ls.begin(), ls.end(), ZJW() ); // 利用 用戶自己設(shè)計(jì)的比較器 sort( ls.begin(), ls.end(), Greater Print( "排序后:", ls ); const list Print( "常容器:", cls ); return 0; } 線性容器 基本容器 向量 vector 常用函數(shù) 函數(shù)功能front()獲取首元素back()獲取尾元素insert()插入元素erase()刪除元素push_back()添加尾元素pop_back()刪除尾元素empty()判空clear()清空size()向量維護(hù)元素個(gè)數(shù)resize()設(shè)置向量元素個(gè)數(shù)capacity()獲取向量容量reserve()設(shè)置向量的容量 向量維護(hù)的內(nèi)存空間會(huì)隨著新元素的增加而自動(dòng)增長 如果內(nèi)存空間無法滿足新元素的增加,向量會(huì)開辟新的足夠的連續(xù)的內(nèi)存空間,并把原內(nèi)存空間的數(shù)據(jù)復(fù)制到新的內(nèi)存空間,釋放原內(nèi)存空間 向量的增加會(huì)伴隨著內(nèi)存空間的分配和釋放,元素復(fù)制和銷毀等額外開銷,如果能夠在創(chuàng)建向量時(shí),合理預(yù)分配一些空間將很大程度上緩解這些額外開銷 示例 #include #include #include using namespace std; class Student { public: Student( const char* name="無名" ) : m_name(name) { cout << "缺省構(gòu)造了:" << m_name << "(" << this << ")" << endl; } Student( const Student& that ) : m_name(that.m_name) { cout << "用:" << that.m_name << "(" << &that << ")" << "克隆了:" << m_name << "(" << this << ")" << endl; } ~Student() { cout << "銷毀了:" << m_name << "(" << this << ")" << endl; } private: string m_name; }; int main( void ) { vector vs.reserve(10); // 設(shè)置容量 vs.push_back( Student("超哥") ); vs.push_back( Student("恒哥") ); vs.push_back( Student("文哥") ); vs.resize(8); // 設(shè)置大小 --> 內(nèi)部 調(diào)用 Student() 造3個(gè)學(xué)生 cout << "向量中學(xué)生的個(gè)數(shù):" << vs.size() << endl; getchar(); return 0; } 迭代器使用 增操作: insert 刪操作 : erase 改操作 :迭代器解引用 查操作 : find 排序操作 : sort 示例 #include #include #include using namespace std; class Student { public: Student( const char* name="無名", int age=0 ) : m_name(name), m_age(age) {} bool operator==( const Student& that ) const { return m_name==that.m_name && m_age==that.m_age; } bool operator<( const Student& that ) const { return m_age < that.m_age; } bool operator>( const Student& that ) const { return m_age > that.m_age; } private: string m_name; int m_age; friend ostream& operator<<( ostream& os, const Student& that ); }; ostream& operator<<( ostream& os, const Student& that ) { os << that.m_name << ":" << that.m_age; return os; } void Print( const string& str, const vector cout << str << endl; typedef vector for( CIT cit=v.begin(); cit!=v.end(); ++cit ) { cout << *cit << ' '; // 迭代器指向節(jié)點(diǎn)數(shù)據(jù)的 常引用 } cout << endl << "--------------------------" << endl; } class ZJW { public: bool operator()( const Student& a, const Student& b ) { return a > b; } }; int main( void ) { vector vs.reserve(30); vs.push_back(Student("張飛",22)); vs.push_back(Student("趙云",20)); vs.push_back(Student("關(guān)羽",25)); vs.push_back(Student("馬超",32)); vs.push_back(Student("黃忠",45)); Print("添加節(jié)點(diǎn)后:", vs); vs.insert( vs.begin(), Student("林沖", 19) ); // 增 Print("迭代器指向的位置添加節(jié)點(diǎn)后:", vs); vs.erase( --vs.end() ); // 刪 Print("刪除迭代器指向的節(jié)點(diǎn)后:", vs); typedef vector IT it = vs.begin(); *it = Student("西門慶",20); // 改 Print("修改迭代器指向的節(jié)點(diǎn)后", vs ); IT fit = find( vs.begin(), vs.end(), Student("趙云",20) ); // 查 if( fit != vs.end() ) { vs.erase( fit ); } Print( "找到趙云并刪除后:", vs ); // sort( vs.begin(), vs.end() ); // sort( vs.begin(), vs.end(), greater sort( vs.begin(), vs.end(), ZJW() ); // 用的是 自己設(shè)計(jì)的比較器 Print( "排序后:", vs ); return 0; } 雙端隊(duì)列 deque 常用函數(shù) 函數(shù)功能front()獲取首元素back()獲取尾元素insert()插入元素erase()刪除元素push_front()添加首元素pop_front()刪除首元素push_back()添加尾元素pop_back()刪除尾元素empty()判空clear()清空size()隊(duì)列維護(hù)元素個(gè)數(shù)resize()隊(duì)列向量的容量 雙端隊(duì)列和向量的差別 和向量差別就是首尾兩端同樣都是開放的,因此他同時(shí)提供了首尾兩端增刪元素的接口沒有提供設(shè)置/獲取容量的函數(shù),設(shè)置和獲取容器大小的函數(shù)存在 示例 #include #include #include using namespace std; class Human { public: Human( int age=0, const char* name="無名" ) : m_age(age),m_name(name) {} bool operator==( const Human& that ) const { return m_name==that.m_name && m_age==that.m_age; } bool operator<( const Human& that ) const { return m_age < that.m_age; } bool operator>( const Human& that ) const { return m_age > that.m_age; } private: int m_age; string m_name; friend ostream& operator<<( ostream& os, const Human& that ); }; ostream& operator<<( ostream& os, const Human& that ) { os << that.m_name << ":" << that.m_age; return os; } /* void Print( const string& str, deque cout << str << endl; typedef deque for( IT it=d.begin(); it!=d.end(); ++it ) { // begin()/end() -- 返回iterator類對象 cout << *it << ' '; } cout << endl << "------------------------" << endl; } */ void Print( const string& str, const deque cout << str << endl; typedef deque for( IT it=d.begin(); it!=d.end(); ++it ) { // begin()/end() -- 返回const_iterator類對象 cout << *it << ' '; } cout << endl << "------------------------" << endl; } template public: bool operator()( const T& a, const T& b ) { return a > b; } }; int main( void ) { deque dq.push_front( Human(22,"張飛") ); dq.push_front( Human(20,"趙云") ); dq.push_front( Human(25,"關(guān)羽") ); dq.push_front( Human(32,"馬超") ); dq.push_front( Human(45,"黃忠") ); dq.push_back( Human(18,"武松") ); dq.push_back( Human(10,"林沖") ); dq.push_back( Human(15,"魯達(dá)") ); dq.push_back( Human(12,"李逵") ); dq.push_back( Human(23,"關(guān)勝") ); Print( "添加頭尾節(jié)點(diǎn)后:", dq ); dq.pop_front(); dq.pop_back(); Print( "刪除頭尾節(jié)點(diǎn)后:", dq ); dq.insert( ++dq.begin(), Human(35,"宋江"));// 利用 迭代器 添加節(jié)點(diǎn) Human(35,"宋江") Print( "迭代器指向的位置添加節(jié)點(diǎn)后:", dq ); dq.erase( ++dq.begin() ); // 刪 Print( "刪除迭代器指向的節(jié)點(diǎn)后:", dq ); typedef deque IT it = dq.begin(); *it = Human(19,"孔明"); // 改 Print( "修改迭代器指向的節(jié)點(diǎn)后:", dq ); IT fit = find( dq.begin(), dq.end(), Human(22,"張飛") ); // 查 if( fit != dq.end() ) { dq.erase( fit ); } Print( "找到張飛并刪除后:", dq ); // sort( dq.begin(), dq.end() ); // "<" // sort( dq.begin(), dq.end(), greater sort( dq.begin(), dq.end(), ZJW Print( "排序后:", dq ); return 0; } 列表(鏈表) list 常用函數(shù) 函數(shù)功能front()獲取首元素back()獲取尾元素insert()插入元素erase()刪除元素push_front()添加首元素pop_front()刪除首元素push_back()添加尾元素pop_back()刪除尾元素empty()判空clear()清空size()隊(duì)列維護(hù)元素個(gè)數(shù)resize()隊(duì)列向量的容量 唯一化 void unique(void); 將連續(xù)重復(fù)出現(xiàn)的元素唯一化 排序(都是全局排序)注意sort是成員函數(shù) void sort(void) 通過 < 比大小 templatevoid sort(LESS less) 通過比較器比大小 拆分:將參數(shù)列表中的部分或全部元素剪切到調(diào)用列表中 templatevoid splice( IT pos, list& lst ) 將全部節(jié)點(diǎn)剪切到pos指向位置的前面 templatevoid splice( IT pos, list& lst, IT del ) 將del指向位置的節(jié)點(diǎn)剪切到pos指向位置的前面 templatevoid splice( IT pos, list& lst, IT begin, IT end ) 將begin和end中間的節(jié)點(diǎn)(左閉右開)剪切到pos指向位置的前面 示例 #include #include #include using namespace std; // 使用 STL中 列表容器(list) // 以上代碼容器的設(shè)計(jì)者 // ----------------------- // 以下代碼容器的使用者 void Print( const string& str, const list cout << str << endl; typedef list for( CIT it=l.begin(); it!=l.end(); ++it ) { cout << *it << ' '; } cout << endl << "----------------------------" << endl; } // 用戶設(shè)計(jì)比較類 class ZJW { public: bool operator()( int x, int y ) { return x < y; } }; int main( void ) { list for( int i=0; i<5; i++ ) { ls.push_front(10+i); } for( int i=0; i<5; i++ ) { ls.push_back(10-i); } Print( "添加頭尾節(jié)點(diǎn)后:", ls ); ls.pop_front(); ls.pop_back(); Print( "刪除頭尾節(jié)點(diǎn)后:", ls ); ls.insert( ++ls.begin(), 10 ); // 增 Print( "迭代器指向的位置添加節(jié)點(diǎn)后:", ls ); ls.erase( ----ls.end() ); // 刪 Print( "刪除迭代器指向的節(jié)點(diǎn)后:", ls ); typedef list IT it = ls.begin(); *it = 999; // 改 Print( "修改迭代器指向的節(jié)點(diǎn)后:", ls ); IT fit = find( ls.begin(), ls.end(), 100 ); // 查 if( fit != ls.end() ) { ls.erase( fit ); } Print( "找到100并刪除后:", ls ); ls.unique(); Print("唯一化后:", ls); // ls.sort(); ls.sort(greater Print( "排序后:", ls ); list lst.push_back(1000); lst.push_back(2000); lst.push_back(3000); lst.push_back(4000); // ls.splice( ++ls.begin(), lst); // ls.splice( ++ls.begin(), lst, lst.begin() ); ls.splice( ++ls.begin(), lst, ++lst.begin(),--lst.end() ); Print("調(diào)用列表ls:", ls); Print("參數(shù)列表lst:", lst); const list Print( "常容器:", cls ); return 0; } 適配器容器 又叫裁剪型容器,由線性容器裁剪部分功能后得到 棧 stack 定義形式 stack<元素類型 [ , 底層容器類型]> 堆棧對象 底層容器 deque(默認(rèn)) / vector / list / 自己實(shí)現(xiàn)的容器 成員函數(shù) push ->push_backpop ->pop_backtop -> backsize -> sizeempty -> empty 棧的底層實(shí)現(xiàn) template #include #include #include using namespace std; // 使用 STL中 列表容器(list) // 以上代碼容器的設(shè)計(jì)者 // ----------------------- // 以下代碼容器的使用者 int main( void ) { // stack // stack // stack stack s.push(1); // c.push_back(1) s.push(2); // .. s.push(3); s.push(4); s.push(5); s.push(6); while( !s.empty() ) { cout << s.top() << endl; // c.back() s.pop(); // c.pop_back() } return 0; } 隊(duì)列 queue 定義形式 queue<元素類型 [,底層容器類型]> 隊(duì)列對象 底層容器 deque(默認(rèn)) / list不能使用vector 成員函數(shù) push -> push_backpop -> pop_frontback - > backfront -> frontsize -> sizeempty -> empty 隊(duì)列的底層實(shí)現(xiàn) template #include #include #include using namespace std; // 使用 STL中 (queue)隊(duì)列容器 // 以上代碼容器的設(shè)計(jì)者 // ----------------------- // 以下代碼容器的使用者 int main( void ) { // queue // queue // queue queue d.push(1); // c.push_back(1) d.push(2); // ... d.push(3); d.push(4); d.push(5); d.push(6); while( !d.empty() ) { cout << d.front() << endl; // 讀 c.front() d.pop(); // 取 c.pop_front() } return 0; } 優(yōu)先隊(duì)列 priority_queue 定義形式 priority_queue<元素類型 [,底層容器類型][,比較器類型]> 優(yōu)先隊(duì)列對象 底層容器 deque / vector(默認(rèn))不能使用list (因其不支持隨機(jī)迭代) 注意事項(xiàng) 優(yōu)者先出,默認(rèn)以大者為優(yōu)(默認(rèn)內(nèi)部使用<運(yùn)算符進(jìn)行比較)可以通過比較器定制比較規(guī)則并不是出隊(duì)列時(shí)挑,而是進(jìn)隊(duì)列時(shí)就保證有序 成員函數(shù) push -> push_backpop -> pop_backtop -> frontsize -> sizeempty -> empty 示例 #include #include #include using namespace std; // 使用 STL中 (priority_queue)優(yōu)先隊(duì)列容器 // 以上代碼容器的設(shè)計(jì)者 // ----------------------- // 以下代碼容器的使用者 class ZJW { public: bool operator()( int x, int y ) { return x > y; } }; int main( void ) { // priority_queue // priority_queue // priority_queue // priority_queue priority_queue pq.push(5); // c.push_back(5); pq.push(8); // c.push_back(8); 立即排序 pq.push(9); // ... pq.push(3); // c.push_back(3); 立即排序 pq.push(4); pq.push(7); pq.push(6); while( !pq.empty() ) { cout << pq.top() << endl; pq.pop(); // c.pop_back() } return 0; } 關(guān)聯(lián)容器 以下介紹的容器底層維護(hù)的數(shù)據(jù)結(jié)構(gòu)為平衡二叉樹(紅黑樹) 映射 map 映射容器的每個(gè)節(jié)點(diǎn)保存的為 pair對象(鍵/值 對) pair類模板 template 創(chuàng)建空的map容器 map 插入一個(gè)pair 使用迭代器訪問元素 begin()/end() begin() const / end() const rbegin()/rend() rbegin() const / rend() const 使用鍵獲取值 [key]:key存在時(shí)返回值的引用,不存在時(shí)添加新元素并返回值的引用at(key): key存在時(shí)返回值的引用,不存在時(shí)拋出異常out_of_range map元素的刪除 erase(key) 移除和鍵參數(shù)匹配的元素,返回所移除元素的個(gè)數(shù) erase(iterator) 移除參數(shù)迭代器指向的元素,返回迭代器指向被刪除元素的下一個(gè)位置。參數(shù)必須是容器中的有效迭代器,不能是終止迭代器。如果迭代器參數(shù)指向的是容器的最后一個(gè)元素,則返回終止迭代器 map元素的查找 find(key) 返回一個(gè)元素的迭代器,這個(gè)元素的鍵和參數(shù)匹配。如果沒有和參數(shù)匹配的元素,find()函數(shù)會(huì)返回容器的終止迭代器。因此在使用這個(gè)迭代器之前,必須先對它進(jìn)行檢查 示例 #include #include using namespace std; class Candidate { public: Candidate( const char* name="" ) : m_name(name), m_vote(0) {} string getName() { return m_name; } void setVote() { ++m_vote; } int getVote() { return m_vote; } private: string m_name; int m_vote; }; void Print( map typedef map for( IT it=m.begin(); it!=m.end(); ++it ) { cout << "(" << (*it).first << ")" << (*it).second.getName() << ' '; } cout << endl; } int main( void ) { map m.insert( pair m.insert( make_pair('C', Candidate("關(guān)羽")) ); m.insert( { {'D', Candidate("馬超")} } ); m['E'] = Candidate("黃忠"); typedef map for( int i=0; i<10; i++ ) { Print( m ); char ch; cin >> ch; IT fit = m.find(ch); if( fit != m.end() ) { (*fit).second.setVote(); } else { cout << "廢票" << endl; continue; } /* try { m.at(ch).setVote(); } catch( out_of_range& e ) { cout << "廢票" << endl; continue; } */ } for( IT it=m.begin(); it!=m.end(); ++it ) { cout << (*it).second.getName() << ":" << (*it).second.getVote() << endl; } return 0; } 多重映射 multimap 允許鍵重復(fù)的映射,表示一對多的邏輯關(guān)系,不支持下標(biāo)運(yùn)算符 定義形式: multimap<鍵類型,值類型> 映射對象; 示例 #include #include using namespace std; void Print( multimap typedef multimap for( IT it=m.begin(); it!=m.end(); ++it ) { cout << (*it).first << ":" << (*it).second << endl; } } int main( void ) { multimap m.insert( pair m.insert( make_pair("張飛", 65) ); m.insert( { {"趙云",55}, {"關(guān)羽",45} } ); Print( m ); return 0; } 集合 set 沒有值只有鍵的映射 與向量等基本容器相比最大優(yōu)勢就是 排重 定義形式:set 集合對象; 示例 #include #include using namespace std; void Print( set typedef set for( IT it=s.begin(); it!=s.end(); ++it ) { cout << *it << ' '; } cout << endl; } int main( void ) { set s.insert( 5 ); s.insert( 6 ); s.insert( 3 ); s.insert( 4 ); s.insert( 7 ); s.insert( 5 ); Print( s ); return 0; } 多重集合 multiset 鍵可以重復(fù)的集合 定義形式:multiset 多重集合對象; 示例 #include #include using namespace std; void Print( multiset typedef set for( IT it=s.begin(); it!=s.end(); ++it ) { cout << *it << ' '; } cout << endl; } int main( void ) { multiset s.insert( 5 ); s.insert( 6 ); s.insert( 3 ); s.insert( 4 ); s.insert( 7 ); s.insert( 5 ); Print( s ); return 0; } 無序容器 哈希 哈希算法是用給定范圍的基本類型的數(shù)據(jù)項(xiàng)也包括string,生成整數(shù)值的過程 哈希算法產(chǎn)生的值叫做哈希值或者哈希碼 理想情況下,每個(gè)對象產(chǎn)生的哈希值是唯一的,但實(shí)際是可能產(chǎn)生重復(fù)的。重復(fù)的哈希值稱為哈希碰撞(概率極低) 哈希類模板 template class hash{ … size_t operator()(Key k){} … }; 無序映射 unordered_map,內(nèi)部維護(hù)的數(shù)據(jù)結(jié)構(gòu)為哈希散列表 定義形式 unordered_map<鍵的類型,值的類型[,哈希器類型][,判等器類型]> 對象; 底層實(shí)現(xiàn) template #include using namespace std; // map提供的操作 unordered_map 都支持 int main( void ) { unordered_map // 構(gòu)造函數(shù)中 定義hash // // 構(gòu)造函數(shù)中 將計(jì)算出的哈希值進(jìn)行處理(例如:對10取余.....) // 將哈希值 和 數(shù)據(jù)(pair對象)保存到 哈希散列表相應(yīng)的 支脈 um.insert( pair // insert函數(shù)中 定義hash // // insert函數(shù)中 將計(jì)算出的哈希值進(jìn)行處理(例如:對10取余.....) // 將哈希值 和 數(shù)據(jù)(pair對象)保存到 哈希散列表相應(yīng)的 支脈 um.insert( make_pair("關(guān)羽",25) ); um["馬超"] = 32; typedef unordered_map for( IT it=um.begin(); it!=um.end(); ++it ) { cout << (*it).first << ":" << (*it).second << endl; } return 0; } 柚子快報(bào)邀請碼778899分享:標(biāo)準(zhǔn)C++ 推薦閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。