C++
98/03标准
名字空间
划分逻辑单元 ,避免名字冲突
-
什么是名字空间
-
名字空间定义 namespace 名字空间名 {…}
-
名字空间合并
-
声明定义分开
-
示例
// 名字空间:划分更多的逻辑空间,有效避免名字冲突的问题 #include <iostream> 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 { // 编译器将合并为一个名字空间 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 <iostream> using namespace std; namespace ns { int g_value = 0; } //int g_value = 0; //using namespace ns;//从这行代码开始,ns中的内容在当前作用域 可见 int main( void ) { // int g_value = 0; using namespace ns;//从这行代码开始,ns中的内容在当前作用域 可见 g_value = 100; /*std::*/cout << "ns::g_value=" << ns::g_value << /*std::*/endl; return 0; }
-
-
名字空间声明
-
示例
// 名字空间声明 #include <iostream> using namespace std; namespace ns { int g_value = 0; } //int g_value = 0; //using ns::g_value;//从这行代码开始,ns中的g_value引入当前作用域(相当于定义) int main( void ) { // int g_value = 0; // using ns::g_value;//从这行代码开始,ns中的g_value引入当前作用域(相当于定义) g_value = 100; cout << "ns::g_value=" << ns::g_value << endl; return 0; }
-
-
名字空间指令和名字空间声明差别示例
// 名字空间声明 和 名字空间指令 的差别 #include <iostream> 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中的所有内容在当前作用域可见(可见表) using ns2::g_value; //名字空间声明,ns2中的g_value引入当前作用域(定义表),仅仅只有g_value出现在定义表中 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; }
-
-
名字空间嵌套
- 内层标识符与外层同名标识符为隐藏关系
- 嵌套的名字空间需要逐层分解
-
名字空间别名
- –可通过名字空间别名简化书写
namespace ns_four = ns1::ns2::ns3::ns4;
- –可通过名字空间别名简化书写
-
示例
// 名字空间的嵌套 #include <iostream> 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++复合类型
结构、联合和枚举
-
C++的结构
-
定义结构型的变量时,可以省略struct关键字
-
可以定义成员函数,在结构体中的成员函数内部可以直接访问本结构体的成员,无需通过".“或”->"
-
-
C++的联合
- 定义联合型的变量时,可以省略union关键字
- 支持匿名联合
-
C++的枚举
- 定义枚举型的变量时,可以省略enum关键字
- 独立的类型,和整型之间不能隐式转换
-
布尔类型
- 表示布尔量的数据类型:bool
- 布尔类型的字面值常量:true表示真,false表示假
- 布尔类型的本质:单字节整数,用1和0表示真和假
- 任何基本类型都可以被隐式转换为布尔类型:非0即真,0即假
-
示例
// C++的复合类型 #include <iostream> #include <cstring> using namespace std; void TestStruct( ) { struct Student { int m_age; // 成员变量 char m_name[256]; // 成员变量 void getinfo() { // 成员函数(可以直接访问本结构体的成员) 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 { // 匿名联合体:主要体现的为内存的排布方式(共用同一块内存空间) int i; char c[4]; }; i = 0x12345678; // 小端字节序 : 低数位 占 低地址 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; }
函数关系–重载
-
重载关系
-
同一作用域内,函数名相同,参数表不同
-
根据实参类型和形参的类型进行匹配,调用最匹配的函数
-
示例
// 函数之间的关系--重载(1.同一作用域内 2.函数名必须相同 3.形参表必须不同) // 函数的形参表是否相同 和 形参名没有关系,和 形参的个数 以及 每个"对应"形参的类型有关 #include <iostream> 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; }
-
-
只有同一作用域内的同名函数才涉及重载的关系,不同作用域的同名函数涉及的是隐藏关系
-
示例
// 详谈 同一作用域内 (具体要看站在哪个点来说,而且具体情况具体分析) #include <iostream> 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出现在可见表中 using ns2::foo; // 名字空间声明,ns2中的foo出现在定义表中 char* c; short s; foo( c, s ); // 3 return 0; }
-
-
-
重载解析
-
完全匹配>常量转换>升级转换>标准转换>自定义转换>省略号匹配
-
函数指针的类型决定其调用的重载函数的版本
-
示例
// 重载匹配(解析)优先级 #include <iostream> 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 常量转换" << endl; } void foo( char* c, int s ) { // _Z3fooPci cout << "3. foo 升级转换" << endl; } void foo( char* c, char s ) { // _Z3fooPcc cout << "4. foo 标准转换" << endl; } void foo( ... ) { // _Z3fooz cout << "5. foo 可变长参数" << endl; } int main( void ) { char* c; short s; foo( c, s ); // _Z3fooPcs(c,s); // 普通方式调用,根据 实参和形参的类型 来确定调用哪个foo void(*pfunc)(const char*, short) = foo; // _Z3fooPKcs pfunc( c, s );// 函数指针方式调用,根据函数指针本身的类型 来确定调用哪个foo return 0; }
-
-
重载的本质
-
重载是通过C++换名机制来实现的
-
通过extern "C"可以要求C++编译器按照C方式编译函数,即不做换名,当然也就无法重载
-
示例
extern "C" { int sum( int a, int b ) { return a + b; } int sub( int a, int b ) { return a - b; } }
-
哑元函数
-
只指定形参类型而不指定形参名称的函数,谓之哑元
- 保证函数的向下兼容
- 形成函数的重载版本
-
示例
// 哑元函数 #include <iostream> using namespace std; void foo( int ) { // 高精尖人工智能算法,不需要利用用户提供基准数据也能得到正确结果 // 函数内部 不能获取 用户传递的实参数据 cout << "foo(int)" << endl; } void foo() { cout << "foo()" << endl; } int main( void ) { foo( 10 ); foo(); return 0; }
缺省(默认)参数
-
可以为函数的形参指定缺省(默认)值,当调用该函数时若未指定实参,则使用形参的缺省(默认)值
-
如果函数的某一个形参具有缺省(默认)值,那么该形参后面的所有形参必须都具有缺省(默认)值
-
尽量避免因为使用缺省参数而导致重载匹配歧义
-
函数形参的缺省(默认)值只能在函数声明中指定
-
示例
// 缺省参数: 带默认值的形参( 默认值不是初始值 ) #include <iostream> 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; }
内联函数
-
调用普通函数的问题:每个普通函数调用语句都需要发生跳转操作,这种跳转操作会带来时间开销
-
内联就是用函数已被编译好的二进制代码,替换对该函数的调用指令
-
内联在保证函数特性的同时,避免了函数调用的时间开销
-
内联会使可执行文件的体积和进程代码的内存变大,因此只有频繁调用的简单函数才适合内联,稀少被调用的复杂函数和递归函数都不适合内联
-
inline关键字仅表示期望该函数被优化为内联,但是否适合内联则完全由编译器决定
-
示例
// 内联函数 - 编译器的优化策略 #include <iostream> using namespace std; void foo( int x ) { // 普通函数 cout << "foo(int):" << x << endl; } inline void bar( int x ) { // 内联函数 cout << "bar(int):" << x << endl; } int main( void ) { foo(10); // 生成跳转指令 // ... foo(20); // ... // ... foo(30); // ... bar(10); // 将此处替换为bar函数编译后产生的二进制指令集 // ... bar(20); // ... // ... bar(30); // ... return 0; }
动态内存分配
-
可以继续使用标准C库函数malloc/free
-
更建议使用new/delete操作符在堆中分配/释放内存
int* pi = new int;
delete pi; -
在分配内存的同时初始化
int* pi = new int (100);
-
以数组方式new的也要以数组方式delete
int* pi = new int [4] {10, 20, 30, 40};
delete[] pi; -
通过new操作符分配N维数组,返回N-1维数组指针
int (*prow) [4] = new int [3][4];
int (*ppage) [4][5] = new int [3][4][5];
-
不能通过delete操作符释放已释放过的内存
-
delete野指针后果未定义,delete空指针安全
-
new操作符申请内存失败,将抛出异常
-
示例
// 动态(堆)内存分配 #include <iostream> #include <cstdlib> using namespace std; int main( void ) { int* pm = (int*)malloc(4); cout << "*pm=" << *pm << endl; free( pm );//当这行代码执行结束,pm指向的堆内存被释放,进而pm变为野(悬空)指针 pm = NULL; free( pm );//若给free传递的为野指针,释放野指针后果很严重,释放空指针是安全的 int* pn = new int(100); cout << "*pn=" << *pn << endl; delete pn;//当这行代码执行结束,pn指向的堆内存被释放,进而pn变为野(悬空)指针 pn = NULL; delete pn;//若给delete传递的为野指针,释放野指针后果很严重,释放空指针是安全的 int* parr = new int[4]{10,20,30,40}; // 以数组方式new一块内存,“永远”返回第一个(首)元素的地址 for( int i=0; i<4; i++ ) { cout << parr[i] << ' '; } cout << endl; delete[] parr; // 不管几维数组,都应该当做一维数组看待 int(*p)[4] = new int[3][4]; delete[] p; try { new int[0xFFFFFFFF]; } catch( ... ) { } return 0; }
引用
-
引用即内存的别名
int a = 10;
int& b = a; -
引用本身不占内存,并非实体, 对引用的所有操作都是在对目标内存进行操作
-
引用必须初始化,且不能更换目标
int c = 20;
b = c;//仅仅是在对引用的目标内存进行赋值 -
不存在引用的引用
-
引用的常属性须和目标的常属性“一致”,
const int e = 10;
int& f = e;//ERROR
const int& g = e;//OK
-
但可以限定更加严格
int a = 10;
const int& h = a;//OK
-
示例
// 引用:就是一块内存的别名 #include <iostream> #include <cstdlib> using namespace std; int main( void ) { int a = 10; int& b = a;//这里不要理解为利用a给b赋值,而应该理解为 引用b是目标内存(a)的别名 b = 20; // 表面看是在给引用b赋值,其实是在给引用b的目标内存(a)赋值 cout << "a=" << a << ", b=" << b << endl; // 表面看是在读取b的值,其实读取的为引用b的目标内存(a)的值 cout << "&b:" << &b << ", &a:" << &a << endl; // 取引用b的地址,其实取的是引用b的目标内存(a)的地址 int c = 30; b = c; // 仅仅是利用c给引用b的目标内存(a)赋值 cout << "a=" << a << endl; cout << "&a:" << &a << ", &b:" << &b << ", &c:" << &c << endl; int& d = b; // 不要理解为d是b别名,而应该理解为b和d都是目标内存(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, 别名可以比真名限定的更加严格 return 0; }
左值和右值
-
左值:能够取地址(&);右值:不能够取地址
-
示例
// 左值 和 右值 #include <iostream> #include <cstdlib> using namespace std; int foo() { int m = 30; return m; } int main( void ) { // 当前作用域的生命期 // 具名内存--->能够取址--->左值|非常左值(无const修饰) // |常左值 (有const修饰) int a = 10; &a; // ok a = 15; // ok const int b = 10; &b; // ok // b = 15; // err // 语句级生命期 // 匿名内存--->不能取址--->右值|C++98/03标准认为直接更改右值毫无意义 // 10; // &10; // err // 10 = 15; // err /*|30|*/ foo(); // (1)分配一块无名内存空间 (2)生成跳转指令 // &foo(); // err // foo() = 15; // err return 0; }
-
-
引用可以延长右值的生命周期
-
常引用 即 万能引用
-
常引用 即 万能引用( 可以引用 非常左值、常左值、右值)
int a = 10;
const int& cra = a; // 但如果常引用 引用的为 非常左值,那么通过常引用将丧失修改目标内存的权限 -
常指针 即 万能指针( 可以指向 非常左值、常左值、右值)
const int* pra = &a; // 但如果常指针 指向的为 非常左值,那么通过常指针将丧失修改目标内存的权限
-
-
引用的生命周期不能长于目标
-
示例
// 左值/右值 和 引用 #include <iostream> #include <cstdlib> using namespace std; int foo() { int m = 30; return m; } int main( void ) { // 当前作用域的生命期 // 具名内存--->能够取址--->左值|非常左值(无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 // 语句级生命期 (引用可以延长右值的生命期) // 匿名内存--->不能取址--->右值|C++98/03标准认为直接更改右值毫无意义 // |C++11标准不是这样认为的???? const int& ri = 10; // ok const int& rf = /*|30|*/ foo(); // (1)分配一块无名内存空间 (2)生成跳转指令 return 0; }
引用的应用
-
引用型参数,函数的形参是实参的别名,避免对象复制的开销
-
非常引用型参数
- 在函数中修改实参值
-
常引用型参数
- 防止对实参的意外修改
- 接受常量型实参
-
示例
// 引用型形参 : 书写简洁,提高传参效率 // 当我们在设计一个函数时,只要能够保证函数内部绝对不修改实参,那么就大胆加上const #include <iostream> using namespace std; void Swap( int& x, int& y ) { // 非常引用型 形参:可以在函数内部直接修改实参数据 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 ) { // 常引用型 形参:防止函数内部意外修改实参 // x = 8888; // 不小心写的,编译器将报错提示 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; }
-
-
引用型的返回值,从函数中返回引用,一定要保证在函数返回以后,该引用的目标依然有效
-
可以返回全局、静态变量的引用
-
可以返回成员变量的引用
-
可以返回在堆中动态创建的对象的引用
-
可以返回调用对象自身的引用
-
可以返回引用型参数本身
-
不能返回局部变量的引用
-
非常引用型返回值
- 通过引用可以修改目标
-
常引用型返回值
- 通过引用不能修改目标
-
示例
// 引用型返回值 #include <iostream> using namespace std; int g_value = 0; int& foo( ) { // 非常引用型 返回值:可以利用引用修改目标 return g_value; } const int& fooo( ) { // 常引用型 返回值:利用引用不能修改目标 return g_value; } int& bar( ) { static int s_value = 0;//这行代码是程序启动就执行,而且只执行一次,不是每次调用bar函数执行 cout << "s_value=" << s_value << endl; return s_value; } int& hum( ) { int* p = new int; return *p; } int& fun( int& x ) { return x; // 返回 引用型参数 本身 } /* int& boo( ) { int m = 0; return m; // 返回局部变量的引用 } */ int main( void ) { foo() = 100; cout << "g_value=" << g_value << endl; // fooo() = 8888; // err,通过常引用不能修改目标 bar() = 200; bar(); hum() = 300; int a_value = 0; fun(a_value) = 400; cout << "a_value=" << a_value << endl; // boo(); return 0; }
-
-
在实现层面,引用就是指针,但在C++语言层面,引用不是实体类型,因此C++语言层面引用与指针存在明显的差别
-
指针可以不初始化,而引用必须初始化
-
指针的目标可在初始化后随意变更(除非是指针常量),而引用一旦初始化就无法变更其目标
-
存在空指针,不存在空引用
-
存在指向指针的指针,不存在引用的引用
-
存在指针的引用,不存在引用的指针
-
存在指针数组,不存在引用数组,但存在数组引用
-
示例
// 引用 和 指针 的差别 #include <iostream> using namespace std; int main( void ) { int a=10, b=20; // 指针可以不做初始化,也可以做初始化 int* pa = &a; // 指针的目标内存可以变更 pa = &b; // 引用必须做初始化 int& ra = a; // 引用的目标内存不可以变更 ra = b; // 这句代码并不会更改引用ra的目标内存 // 存在空指针 pa = NULL; // 不存在空引用 // ra = NULL; // error // 存在二级指针 int** ppa = &pa; // 不存在二级引用 // int&& rra = ra; // error // 存在指针的引用 int * & rpa = pa; // 不存在引用的指针 // int & * pra = &ra; // error // 存在指针的数组 int* parr[2] = {&a, &b}; // 不存在引用的数组 // int& rarr[2] = {a,b}; // error // 存在数组的引用 int arr[3]; int(&rr)[3] = arr; return 0; }
-
类型强转
显式类型转换
-
C风格的显式类型转换
- (目标类型)源类型变量
-
C++风格的显式类型转换
- 目标类型(源类型变量)
-
静态类型转换
- static_cast<目标类型> (源类型变量)
- 隐式类型转换的逆转换
- 自定义类型转换
-
动态类型转换
- dynamic_cast<目标类型> (源类型变量)
- 多态父子类指针或引用之间的转换
-
常类型转换
- const_cast<目标类型> (源类型变量)
- 去除指针或引用上的const属性
-
重解释类型转换
- reinterpret_cast<目标类型> (源类型变量)
- 任意类型的指针之间的转换或引用之间的转换
- 任意类型的指针和整型之间的转换
-
示例
// 显式(强制)类型转换 #include <iostream> using namespace std; int main( void ) { int a; double b; float c; short d; char e; // 所有基本类型的变量之间都可以 隐式转换 a = b = c = d = e; e = d = c = b = a; // 任何其他类型的指针 到 void* 都可以 隐式转换 void* pv = &a; // int*-->void* pv = &b; pv = &c; pv = &d; pv = &e; // void* 到 任何其他类型的指针 必须强转*************************** int* pa = static_cast<int*>(pv); // void*-->int* 的反向 int*-->void* 可以隐式转换 double* pb = static_cast<double*>(pv); float* pc = static_cast<float*>(pv); short* pd = static_cast<short*>(pv); char* pe = static_cast<char*>(pv); // 任何类型 非常指针 到 同类型 常指针 都可以 隐式转换 const int* cpa = pa; // int*-->const int* const double* cpb = pb; const float* cpc = pc; const short* cpd = pd; const char* cpe = pe; // 任何类型 常指针 到 同类型 非常指针 必须强转********************** pa = const_cast<int*>(cpa); pb = const_cast<double*>(cpb); pc = const_cast<float*>(cpc); pd = const_cast<short*>(cpd); pe = const_cast<char*>(cpe); // 除了void*外,任何其他类型的指针之间都 必须强转******************* pb = reinterpret_cast<double*>(pa); // int*-->double* pb = reinterpret_cast<double*>(1000); // int-->double* return 0; }
面向对象
-
什么是对象
- 万物皆对象,这是人类面对世界最朴素,最自然的认知、感觉
- 对象拥有足够的智能,能够理解来自其它对象的信息,并以适当的行为作出反应
- 对象能够从高层对象继承属性和行为,并允许低层对象从自己继承属性和行为等
- –面向对象的三大要件:封装、继承、多态
-
为什么要面向对象
-
相比于分而治之的结构化程序设计,强调大处着眼的面向对象程序设计思想,更适合于开发大型软件
-
得益于代码复用等面向对象的固有特征,软件开发的效率获得极大地提升,成本却大幅降低
-
-
怎样面向对象
- 至少掌握一种面向对象的程序设计语言
- 深入理解封装、继承和多态等面向对象的重要概念
- 学习设计模式
类和对象
-
类是抽象事物的一套规则
- 类是一种用户自定义的复合数据类型,即包括表达属性的成员变量,也包括表达行为的成员函数
- 类可用于表达那些不能直接与内置基本类型建立自然映射关系的逻辑抽象
- 类是现实世界的抽象,对象是类在虚拟世界的实例
-
类的一般形式
-
类的定义—访问控制限定符
- public:公有成员,谁都可以访问
- protected:保护成员,只有自己和子类可以访问
- private:私有成员,只有自己可以访问
- 在C++中,类(class)和结构(struct)已没有本质性的差别,唯一的不同在于
- 类的缺省访问控制属性为私有(private)
- 结构的缺省访问控制属性为公有(public)
- 访问控制限定符仅作用于类,而非作用于对象。
- 对不同成员的访问控制属性加以区分,体现了C++作为面向对象程序设计语言的封装特性
-
示例
// 类:抽取事物特征的规则 #include <iostream> #include <cstring> using namespace std; // struct class Human { public: void setinfo( int age=0, const char* name="无名" ) { // 桥梁函数 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]; // 声明 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human h; // 定义h(给h分配内存空间) // 在h所占内存空间中 定义m_age(给m_age分配内存空间)初值为随机数 // 在h所占内存空间中 定义m_name(给m_name分配内存空间)初值为随机数 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; }
成员函数参数–this
-
C++对象模型
-
同一个类的不同对象各自拥有一份独立的成员变量
-
同一个类的不同对象彼此共享同一份成员函数
-
-
C++成员函数模型
-
类的每个成员函数(除静态成员函数外),都有一个隐藏的指针型参数,形参名为 this,指向调用该成员函数的对象,这就是this指针
-
在类的成员函数中(除静态成员函数外),对所有成员的访问,都是通过this指针进行的
-
示例
// 成员函数的形参 - this #include <iostream> #include <cstring> using namespace std; // 当前程序中有两个对象(h/h2),每个对象内部各拥有一份成员变量(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]; // 声明 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human h; // 定义h(给h分配内存空间) // 在h所占内存空间中 定义m_age(给m_age分配内存空间)初值为随机数 // 在h所占内存空间中 定义m_name(给m_name分配内存空间)初值为随机数 cout << "h的大小:" << sizeof(h) << endl; // 260 h.setinfo( 22, "zhangfei" ); // _ZN5Human7setinfoEiPKc( &h, ...); h.getinfo(); // _ZN5Human7getinfoEv( &h ) Human h2; // 定义h2(给h2分配内存空间) // 在h2所占内存空间中 定义m_age(给m_age分配内存空间)初值为随机数 // 在h2所占内存空间中 定义m_name(给m_name分配内存空间)初值为随机数 cout << "h2的大小:" << sizeof(h2) << endl; h2.setinfo( 20, "zhaoyun" ); // _ZN5Human7setinfoEiPKc( &h2, ...); h2.getinfo(); // _ZN5Human7getinfoEv( &h2 ) return 0; }
-
-
this指针的应用
-
多数情况下,程序并不需要显式地使用this指针
-
有时为了方便,将类的成员变量与该类成员函数的参数取相同标识符,这时在成员函数内部,可通过this指针将二者加以区分
-
返回基于this指针的自引用,以支持串连调用
-
将this指针作为函数的参数,以实现对象交互
-
示例
// 必须自己写this的情况 #include <iostream> using namespace std; class Integer { public: void setinfo( int i ) { this->i = i; // (1)必须自己写this的情况 } void getinfo( /* Integerr* this */ ) { cout << /*this->*/i << endl; // 编译器会补this foo( this ); // (3)必须自己写this的情况 } Integer& increment( /* Integer* this */ ) { ++/*this->*/i; // 编译器会补this return *this; // 返回基于this指针的自引用 (2)必须自己写this的情况 } private: int i; }; void foo( Integer* v ) { // ... } // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Integer ix; ix.setinfo( 1000 ); ix.getinfo(); ix.increment().increment().increment(); // 串联调用 ix.getinfo(); return 0; }
-
-
常函数与常对象
-
常对象:被const关键字修饰的对象、对象指针或对象引用,统称为常对象
-
常函数:在类成员函数的形参表之后,函数体之前加上const关键字,该成员函数的this指针即具有常属性,这样的成员函数被称为常函数
-
原型相同的成员函数,常版本和非常版本构成重载
- 非常对象优先选择非常版本,如果没有非常版本,也能选择常版本
- 常对象只能选择常版本
-
在常函数内部无法修改成员变量的值,除非该成员变量被mutable关键字修饰
-
示例
// 常对象(被const修饰的 对象/指针/引用) 和 非常对象(没有被const修饰的 对象/指针/引用) // 常函数(编译器补充的this指针有const限定) 和 非常函数(...) #include <iostream> using namespace std; class Integer { public: void setinfo( /* Integer* this */ int i ) { m_i = i; } void getinfo( /* Integer* this */ ) { // 非常函数 cout << "非常函数getinfo:" << m_i << endl; } void getinfo( /* const Integer* this */ ) const { // 常函数 const_cast<Integer*>(this)->m_i = 8888;//加mutable相当于去掉this指针的常属性 cout << "常函数getinfo:" << m_i << endl; } private: /*mutable*/ int m_i; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Integer ix; // ix是 非常对象 ix.setinfo( 1000 ); ix.getinfo(); // getinfo( &ix )-->实参的类型为Integer* const Integer cix = ix; // cix是 常对象 cix.getinfo(); // getinfo( &cix )-->实参的类型为const Integer* return 0; }
-
类的定义与实例化
-
构造函数
- 函数名必须与类名相同,且没有返回值类型
-
构造函数调用时间
- 在定义对象同时自动被调用,且仅被调用一次
- 对象定义语句
- new操作符
- 在定义对象同时自动被调用,且仅被调用一次
-
构造函数的作用
-
定义对象的各个成员变量并赋初值。设置对象的初始状态
-
在对象定义之初想实现的任何操作
-
示例
// 构造函数:1.函数名必须和类名相同 2.没有返回值类型 #include <iostream> #include <cstring> using namespace std; class Human { public: Human( /* Human* this */ int age=0, const char* name="无名" ) { // 在this所指向的内存空间中 定义m_age(给m_age分配内存空间)初值为随机数 // 在this所指向的内存空间中 定义m_name(给m_name分配内存空间)初值为随机数 cout << "Human类的构造函数被调用" << 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]; // 声明 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human h(22,"张飞"); // 定义h(给h分配内存空间),利用 h.Human(22,"张飞") cout << "h对象创建完毕" << endl; // h.setinfo( 22, "zhangfei" ); h.getinfo(); return 0; }
-
-
对象的定义过程
- 为整个对象分配内存空间
- 以构造实参调用构造函数
- 定义成员变量
- 执行构造代码
-
类的声明与实现可以分开
-
示例
// 类的声明 和 实现(定义) 分开 #include <iostream> #include <cstring> 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所指向的内存空间中 定义m_age(给m_age分配内存空间)初值为随机数 // 在this所指向的内存空间中 定义m_name(给m_name分配内存空间)初值为随机数 cout << "Human类的构造函数被调用" << endl; m_age = age; strcpy( m_name, name ); } void Human::getinfo( /* Human* this */ ) { // 定义 cout << "姓名:" << m_name << ", 年龄:" << m_age << endl; } // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human h(22,"张飞"); // 定义h(给h分配内存空间),利用 h.Human(22,"张飞") h.getinfo(); return 0; }
-
-
将类的声明、实现与使用分别放在不同的文件里
-
对象的定义与销毁
-
在栈中定义单个对象
- 类名 对象; // 注意不要加空括号
- 类名 对象 (实参表);
-
在栈中定义对象数组
- 类名 对象数组[元素个数];
- 类名 对象数组[元素个数] = {类名 (实参表), …};
- 类名 对象数组[] = {类名 (实参表), …};
-
在堆中定义/销毁单个对象
- –类名* 对象指针 = new 类名;
- 类名* 对象指针 = new 类名 ();
- 类名* 对象指针 = new 类名 (实参表);
- delete 对象指针;
-
在堆中定义/销毁对象数组
- 类名* 对象数组指针 = new 类名[元素个数];
- 类名* 对象数组指针 = new 类名[元素个数] {类名 (实参表), …};// 需要编译器支持C++11标准
- delete[] 对象数组指针;
-
示例
// 定义类对象的11种方法 #include <iostream> #include <cstring> 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所指向的内存空间中 定义m_age(给m_age分配内存空间)初值为随机数 // 在this所指向的内存空间中 定义m_name(给m_name分配内存空间)初值为随机数 cout << "Human类的构造函数被调用" << endl; m_age = age; strcpy( m_name, name ); } void Human::getinfo( /* Human* this */ ) { // 定义 cout << "姓名:" << m_name << ", 年龄:" << m_age << endl; } // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human(32,"马超").getinfo(); // 定义 匿名Human类对象,利用 匿名Human类对象.Human(32,"马超") Human h(22,"张飞"); // 定义h(给h分配内存空间),利用 h.Human(22,"张飞") h.getinfo(); Human h2; // 定义h2,利用h2.Human() h2.getinfo(); Human h3[3]; // 定义3个Human类对象,并分别利用这3个Human类对象.Human() for( int i=0; i<3; i++ ) { h3[i].getinfo(); } Human h4[3] = { Human(22,"张飞"), Human(20,"赵云"), Human(25,"关羽") }; for( int i=0; i<3; i++ ) { h4[i].getinfo(); } Human h5[] = { Human(22,"张飞"), Human(20,"赵云"), Human(25,"关羽"), Human(45,"黄忠") }; for( int i=0; i<sizeof(h5)/sizeof(h5[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个Human类堆对象,分别利用这3个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,"鲁达") }; for( int i=0; i<3; i++ ) { ph5[i].getinfo(); } delete[] ph5; ph5 = NULL; return 0; }
-
C++标准库–string类
-
示例
// string类的使用 #include <iostream> using namespace std; // C++标准库中设计的string类-->char* m_psz int main( void ) { string s1("hello"); // 定义s1,利用s1.string("hello")-->s1维护的字符串为"hello" cout << "s1: " << s1 << endl; // 如果在做初始化,并且"="两边的类型完全一致,那么=xxx和(xxx)无差别 string s2(s1); //= s1; 定义s2,利用s2.string(s1)-->s2维护的字符串和s1维护的字符串 内容相同 cout << "s2: " << s2 << endl; string s3; // 定义s3,利用s3.string()-->s3维护的字符串为"\0" cout << "s3被赋值前: " << s3 << endl; // 如果在做赋值,并且"="两边的类型完全一致,那么将触发operator=函数的调用 s3 = s2; // s3.operator=(s2)-->s3维护的字符串 和 s2维护的字符串 内容相同 cout << "s3被赋值后: " << s3 << endl; // 无论是初始化还是赋值,只要"="两边的类型不一致,都会触发类型转换操作 string s4 = "hello"; // 定义 匿名string类对象,利用 匿名string类对象.string("hello")-->匿名对象维护"hello" // string s4 = 匿名string类对象-->s4维护的字符串 和 匿名string类对象维护的字符串内容相同 // -->s4维护的字符串为"hello" cout << "s4: " << s4 << endl; string s5; cout << "s5被赋值前:" << s5 << endl; s5 = "hello"; // 定义 匿名string类对象,利用 匿名string类对象.string("hello")-->匿名对象维护"hello" // s5 = 匿名string类对象-->s5维护的字符串 和 匿名string类对象维护的字符串 内容相同 // -->s5维护的字符串为"hello" cout << "s5被赋值后: " << s5 << endl; return 0; }
构造函数
-
构造函数可以重载
-
构造函数也可以通过参数表的差别化形成重载
-
重载的构造函数,通过构造函数的实参类型进行匹配
-
不同的构造函数,表示对象的不同创建方式
-
使用缺省参数可以减少构造函数重载的数量
-
示例
// 构造函数的重载 #include <iostream> 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; // 声明 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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; }
-
-
构造函数分类
- 多参构造函数:按多参方式构造
- 无参(缺省)构造函数:按无参方式构造
- 类型转换构造函数:利用不同类型的对象构造
- 拷贝构造函数:利用相同类型的对象构造
-
无参构造函数
-
无参构造函数亦称缺省构造函数,但其未必真的没有任何参数,为一个有参构造函数的每个参数都提供一个缺省值,同样可以达到无参构造函数的效果
-
如果一个类没有定义任何构造函数,那么编译器会为其提供一个无参构造函数
-
对基本类型的成员变量进行定义,并初始化为随机数
-
对类类型的成员变量进行定义,调用相应类型的无参构造函数
-
如果一个类定义了构造函数,无论这个构造函数是否带有参数,编译器都不会再为这个类再提供无参构造函数
-
示例
// 无参(缺省)构造函数 #include <iostream> using namespace std; class Human { public: // 如果类没有提供任何构造函数,编译器将提供一个无参构造函数 /* Human() { 【int m_age;】定义m_age,初值为随机数 【string m_name;】定义m_name,利用m_name.string() }*/ Human(int age=0, const char* name="无名") { //【int m_age;】定义m_age,初值为随机数 //【string m_name;】定义m_name,利用m_name.string() cout << "Human类的缺省构造函数被调用" << endl; m_age = age; m_name = name; } void getinfo( ) { cout << "姓名:" << m_name << ", 年龄:" << m_age << endl; } private: int m_age; // 基本类型成员变量 string m_name; // 类类型成员变量 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human h; // 定义h,利用h.Human() h.getinfo(); Human h2(22,"张飞"); // 定义h2,利用 h2.Human(22,"张飞") h2.getinfo(); return 0; }
-
-
有时必须为一个类提供无参的构造函数,仅仅因为它可能作为另外一个类的类类型成员变量
-
示例
// 建议大家设计一个类时候 提供无参(缺省)构造函数 #include <iostream> using namespace std; class A { // 当前A类 有 无参构造函数 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,初值为随机数 //【string m_name;】定义m_name,利用m_name.string() //【A m_a;】定义m_a,利用m_a.A() cout << "Human类的缺省构造函数被调用" << 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; // 类类型成员变量 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human h; // 定义h,利用h.Human() h.getinfo(); Human h2(22,"张飞"); // 定义h2,利用 h2.Human(22,"张飞") h2.getinfo(); return 0; }
-
-
-
拷贝构造函数
-
形如
class 类名 {
类名 (const 类名& that) { … }
};
的构造函数被称为拷贝构造函数。 -
用于,利用一个已定义的对象,来定义其同类型的副本对象,即对象克隆
-
如果一个类没有定义拷贝构造函数,那么编译器会为其提供一个默认拷贝构造函数
- 对基本类型成员变量进行定义,并赋初值(按字节复制)
- 对类类型成员变量进行定义,并调用相应类型的拷贝构造函数
-
如果自己定义了拷贝构造函数,编译器将不再提供默认拷贝构造函数,这时所有与成员复制有关的操作,都必须在自定义拷贝构造函数中自己编写代码完成
-
若默认拷贝构造函数不能满足要求,则需自己定义
-
示例
// 拷贝构造函数 #include <iostream> using namespace std; class Human { public: // 如果类没有提供任何构造函数,编译器将提供一个无参构造函数 /* Human() { 【int m_age;】定义m_age,初值为随机数 【string m_name;】定义m_name,利用m_name.string() }*/ Human(int age=0, const char* name="无名") { //【int m_age;】定义m_age,初值为随机数 //【string m_name;】定义m_name,利用m_name.string() cout << "Human类的缺省构造函数被调用" << endl; m_age = age; m_name = name; } // 如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数 /* 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类的拷贝构造 }*/ Human( const Human& that ) { // 后面将提升效率??? //【int m_age;】定义m_age,初值为随机数 //【string m_name;】定义m_name,利用m_name.string() cout << "Human类的拷贝构造函数被调用" << 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; // 类类型成员变量 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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)-->拷贝构造函数 h3.getinfo(); return 0; }
-
-
拷贝构造函数的调用时机
-
用已定义对象作为同类型对象的构造实参
-
以对象的形式向函数传递参数
-
从函数中返回对象
-
注意:某些拷贝构造过程会因编译优化而被省略
-
示例
// 拷贝构造函数被调用的时间 #include <iostream> using namespace std; class Human { public: Human(int age=0, const char* name="无名") { //【int m_age;】定义m_age,初值为随机数 //【string m_name;】定义m_name,利用m_name.string() m_age = age; m_name = name; } Human( const Human& that ) { //【int m_age;】定义m_age,初值为随机数 //【string m_name;】定义m_name,利用m_name.string() cout << "Human类的拷贝构造函数被调用" << 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; // 类类型成员变量 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) void foo( Human v ) {} Human bar() { Human m; return m; } int main( void ) { Human h2(22,"张飞"); Human h3(h2); //= h2; 定义h3,利用 h3.Human(h2)-->触发拷贝构造函数 (1) foo( h3 ); // -->触发拷贝构造函数 (2) Human h4 = /*|...|*/ bar(); // 触发2次拷贝构造函数(被编译器优化) // -fno-elide-constructors return 0; }
-
-
-
自定义构造函数和编译器定义构造函数
自定义构造函数 编译器定义构造函数 无 缺省构造函数;缺省拷贝构造函数 除拷贝构造函数以外的任何构造函数 缺省拷贝构造函数 拷贝构造函数 无 所有编译器定义的构造函数,其访问控制属性均为公有(public)
-
类型转换构造函数
-
形如:
class 目标类型 {
目标类型 (const 源类型& src) { … }
};的构造函数被称为类型转换构造函数
-
用于
-
利用一个已定义的对象, 来定义另一个不同类型的对象
-
实现从源类型到目标类型的隐式类型转换的目的
-
通过explicit关键字,可以强制这种通过类型转换构造函数实现的类型转换必须通过静态转换显式地进行
class 目标类型 {
explicit 目标类型 (const 源类型& src) { … }
}; -
示例
// 类型转换构造函数 #include <iostream> using namespace std; class Cat { public: explicit Cat( const char* name ) : m_name(name) { // 类型转换构造函数 //【string m_name(name);】 cout << "Cat类的类型转换构造函数被调用" << endl; } void talk() { cout << m_name << ": 喵喵~~~" << endl; } private: string m_name; friend class Dog; // 友元声明 }; class Dog { public: Dog( const char* name ) : m_name(name) { // 类型转换构造函数 //【string m_name(name);】 } explicit Dog( const Cat& c ) { // 类型转换构造函数( 定制了 Cat->Dog的转换规则 ) m_name = c.m_name; cout << "Dog类的类型转换构造函数被调用" << endl; } void talk() { cout << m_name << ": 汪汪~~~" << endl; } private: string m_name; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { // Cat smallwhite("小白"); // 定义smallwhite,利用smallwhite.Cat("小白")-->类型转换构造函数 // Cat smallwhite = "小白"; // 定义 匿名Cat类对象,利用 匿名Cat类对象.Cat("小白")-->类型转换构造函数 Cat smallwhite = static_cast<Cat>("小白"); // 定义 匿名Cat类对象,利用 匿名Cat类对象.Cat("小白")-->类型转换构造函数 smallwhite.talk(); // Dog bigyellow( smallwhite ); // 定义bigyellow,利用bigyellow.Dog( smallwhite )-->类型转换构造 // Dog bigyellow = smallwhite; // 定义 匿名Dog类对象,利用 匿名Dog类对象.Dog(smallwhite)-->类型转换构造 // Dog bigyellow = 匿名Dog类对象 Dog bigyellow = static_cast<Dog>(smallwhite); // 定义 匿名Dog类对象,利用 匿名Dog类对象.Dog(smallwhite)-->类型转换构造 // Dog bigyellow = 匿名Dog类对象 bigyellow.talk(); return 0; }
-
拷贝赋值函数
-
形如
class 类名 {
类名& operator= (const 类名& that) { … }
};
的函数被称为拷贝赋值函数,用于一个已定义的对象给同类型的对象赋值,即对象赋值 -
如果一个类没有定义拷贝赋值函数,那么编译器会为其提供一个默认拷贝赋值函数
- 对基本类型成员变量,值传递(按字节复制)
- 对类类型成员变量,调用相应类型的拷贝赋值函数
-
如果自己定义了拷贝赋值函数,编译器将不再提供默认拷贝赋值函数,这时所有与成员复制有关的操作,都必须在自定义拷贝赋值函数中自己编写代码完成
-
若默认拷贝赋值函数不能满足要求时,则需自己定义
-
示例
// 拷贝赋值函数:用于对象之间的赋值 #include <iostream> using namespace std; class Human { public: // 如果类没有提供任何构造函数,编译器将提供一个无参构造函数 /* Human() { 【int m_age;】定义m_age,初值为随机数 【string m_name;】定义m_name,利用m_name.string() }*/ Human(int age=0, const char* name="无名") { //【int m_age;】定义m_age,初值为随机数 //【string m_name;】定义m_name,利用m_name.string() cout << "Human类的缺省构造函数被调用" << endl; m_age = age; m_name = name; } // 如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数 /* 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类的拷贝构造 }*/ Human( const Human& that ) { // 后面将提升效率??? //【int m_age;】定义m_age,初值为随机数 //【string m_name;】定义m_name,利用m_name.string() cout << "Human类的拷贝构造函数被调用" << endl; m_age = that.m_age; m_name = that.m_name; } // 如果类没有提供拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数 /* 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类拷贝赋值函数 return *this; }*/ Human& operator=( const Human& that ) { // 编译器不会在拷贝赋值函数中塞操作 cout << "Human类的拷贝赋值函数被调用" << endl; this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string类拷贝赋值函数 return *this; } void getinfo( ) { cout << "姓名:" << m_name << ", 年龄:" << m_age << endl; } private: int m_age; // 基本类型成员变量 string m_name; // 类类型成员变量 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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)-->拷贝构造函数 h3.getinfo(); Human h4; // 定义h,利用h.Human() cout << "h4被赋值前--"; h4.getinfo(); h4 = h3; // h4.operator=(h3)-->拷贝赋值函数 cout << "h4被赋值后--"; h4.getinfo(); return 0; }
初始化表
-
通过在类的构造函数中使用初始化表,可以通知编译器该类的成员变量如何被初始化
-
示例
// 初始化表 #include <iostream> using namespace std; class Human { public: // 如果类没有提供任何构造函数,编译器将提供一个无参构造函数 /* Human() { 【int m_age;】定义m_age,初值为随机数 【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类的缺省构造函数被调用" << endl; } // 如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数 /* 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类的拷贝构造 }*/ 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类的拷贝构造函数被调用" << endl; } // 如果类没有提供拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数 /* 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类拷贝赋值函数 return *this; }*/ Human& operator=( const Human& that ) { // 编译器不会在拷贝赋值函数中塞操作 cout << "Human类的拷贝赋值函数被调用" << endl; this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string类拷贝赋值函数 return *this; } void getinfo( ) { cout << "姓名:" << m_name << ", 年龄:" << m_age << endl; } private: int m_age; // 基本类型成员变量 string m_name; // 类类型成员变量 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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)-->拷贝构造函数 h3.getinfo(); Human h4; // 定义h,利用h.Human() cout << "h4被赋值前--"; h4.getinfo(); h4 = h3; // h4.operator=(h3)-->拷贝赋值函数 cout << "h4被赋值后--"; h4.getinfo(); return 0; }
-
-
类中的基本类型成员变量,最好在初始化表中显式指明如何初始化,否则初值不确定
-
类中的类类型成员变量,也最好在初始化表中显式指明如何初始化,否则将调动相应类型的无参构造函数
-
类的常量型和引用型成员变量,必须在初始化表中显式初始化
-
*类的成员变量*按其在类中的声明顺序依次被初始化,而与其在初始化表中的顺序无关。所以在初始化成员变量时最好不要涉及其他成员变量
-
示例
// 必须使用初始化表的情况 #include <iostream> #include <cstring> 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类的缺省构造函数被调用" << 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类的拷贝构造函数被调用" << 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; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human h2(22,"张飞",88.5); // 定义h2,利用 h2.Human(22,"张飞",88.5) h2.getinfo(); Human h3(h2); //= h2; 定义h3,利用 h3.Human(h2)-->拷贝构造函数 h3.getinfo(); return 0; }
-
析构函数
-
析构函数的函数名就是在类名前面加“~”,没有返回类型也没有参数,不能重载
-
示例
// 析构函数 #include <iostream> using namespace std; class Human { public: // 如果类没有提供任何构造函数,编译器将提供一个无参构造函数 /* Human() { 【int m_age;】定义m_age,初值为随机数 【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类的缺省构造函数被调用" << endl; } // 如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数 /* 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类的拷贝构造 }*/ 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类的拷贝构造函数被调用" << endl; } // 如果类没有提供拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数 /* 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类拷贝赋值函数 return *this; }*/ Human& operator=( const Human& that ) { // 编译器不会在拷贝赋值函数中塞操作 cout << "Human类的拷贝赋值函数被调用" << endl; this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string类拷贝赋值函数 return *this; } // 如果类没有提供析构函数,编译器将提供一个默认的析构函数 /* ~Human() { 对于基本类型的成员变量m_age,什么都不做 对于类类型的成员变量m_name,利用m_name.~string() 释放 m_age/m_name 本身所占内存空间 }*/ ~Human() { cout << "Human类的析构函数被调用" << endl; // 对于类类型的成员变量m_name,利用m_name.~string() // 释放 m_age/m_name 本身所占内存空间 } void getinfo( ) { cout << "姓名:" << m_name << ", 年龄:" << m_age << endl; } private: int m_age; // 基本类型成员变量 string m_name; // 类类型成员变量 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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)-->拷贝构造函数 h3.getinfo(); Human h4; // 定义h,利用h.Human() cout << "h4被赋值前--"; h4.getinfo(); h4 = h3; // h4.operator=(h3)-->拷贝赋值函数 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本身所占内存空间
-
-
在销毁对象之前一刻自动被调用,且仅被调用一次
- 对象离开作用域
- delete操作符
-
作用:销毁对象的各个成员变量
-
如果一个类没有定义析构函数,那么编译器会为其提供一个默认析构函数
- 对基本类型的成员变量,什么也不做
- 对类类型的成员变量,调用相应类型的析构函数
- 销毁对象的各个成员变量
-
对象的销毁过程
- 调用析构函数
- 执行析构函数的代码
- 调用成员变量的析构函数
- 释放对象各成员变量所占内存空间
- 释放整个对象所占用的内存空间
- 调用析构函数
-
通常情况下,若对象在其生命周期的最终时刻,并不持有任何动态分配的资源,可以不定义析构函数
-
但若对象在其生命周期的最终时刻,持有动态资源则必须自己定义析构函数,释放对象所持有的动态资源
-
示例
// 析构函数 #include <iostream> 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,初值为指向一块堆内存(动态资源) //【int m_f=open(...);】定义m_f,初值为文件描述符-->文件表等内核结构(动态资源) } ~A() { delete m_p; close( m_f ); // 释放 m_i/m_p/m_f 本身所占内存空间 } /* 默认析构 ~A() { 释放 m_i/m_p/m_f 本身所占内存空间 } */ private: int m_i; int* m_p; int m_f; }; // 以上代码模拟类的设计者 // ---------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { A a; // 定义a,利用a.A() return 0; } // a.~A() 释放a本身所占内存空间
-
-
析构函数的功能并不局限在释放资源上,它可以执行我们希望在对象被释放之前执行的任何操作
深浅 拷贝构造 与 拷贝赋值
-
如果类不提供拷贝构造和拷贝赋值编译器将提供默认的拷贝构造和拷贝赋值,而默认的拷贝构造和拷贝赋值函数,对于指针型成员变量都是只复制地址,而并不是复制地址指向的数据,这将导致浅拷贝问题
-
为了获得完整意义上的对象副本,必须自己定义拷贝构造和拷贝赋值,针对指针型成员变量做深拷贝
-
相对于拷贝构造,拷贝赋值需要做更多的工作
- 避免自赋值
- 分配新资源
- 拷贝新内容
- 释放旧资源
- 返回自引用
-
无论是拷贝构造还是拷贝赋值,其默认实现对任何类型的指针成员都是简单地复制地址,因此应尽量避免使用指针型成员变量
-
出于具体原因的考虑,如果确实无法实现完整意义上的拷贝构造和拷贝赋值,可将它们私有化,以防误用
-
如果为一个类提供了自定义的拷贝构造函数,就没有理由不提供相同逻辑的拷贝赋值运算符函数
-
示例
// 默认拷贝构造 以及 默认拷贝赋值,在某些特定场景(类中有指针型成员)有(浅拷贝)缺陷 #include <iostream> #include <cstring> using namespace std; // 模拟C++标准库的string类,设计一个自己的String类 class String { public: String( const char* psz="" ) : m_psz(new char[strlen(psz)+1]) { //【char* m_psz=new char[...];】定义m_psz,初值为指向一块堆内存(动态资源) strcpy( m_psz, psz ); } ~String( /* String* this */ ) { delete[] this->m_psz; this->m_psz = NULL; // 释放 m_psz 本身所占内存空间 } char* c_str() { return m_psz; } /* 默认拷贝构造函数 String( const String& that ) { 【char* m_psz=that.m_psz;】只复制了地址,没有复制地址指向的数据-->浅拷贝 } */ // 深拷贝构造函数 String( const String& that ) : m_psz(new char[strlen(that.m_psz)+1]) { //【char* m_psz=new char[...];】 strcpy( m_psz, that.m_psz ); // 复制了数据,不复制地址-->深拷贝 cout << "String类深拷贝构造函数被调用" << endl; } /* 默认的拷贝赋值函数 String& operator=( const String& that ) { this->m_psz = that.m_psz; // 只复制了地址,没有复制地址指向的数据-->浅拷贝 return *this; }*/ // 深拷贝赋值函数 String& operator=( /* String* this */ const String& that ) { cout << "String类的深拷贝赋值函数被调用" << 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 ); // 拷贝新内容 } return *this; // 返回自引用 } private: char* m_psz; }; // 以上代码模拟类的设计者 // ---------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { String s1("hello"); // 定义s1,利用s1.String() cout << "s1维护的字符串:" << s1.c_str() << ", s1维护堆内存首地址:" << (void*)s1.c_str() << endl; String s2 = s1; // 定义s2,利用s2.String(s1)-->触发拷贝构造函数 cout << "s2维护的字符串:" << s2.c_str() << ", s2维护堆内存首地址:" << (void*)s2.c_str() << endl; String s3; // 定义s3,利用s3.String()-->s3维护空串(占一个字节的堆内存) s3 = s2; // s3.operator=(s2)-->触发拷贝赋值函数 cout << "s3维护的字符串:" << s3.c_str() << ", s3维护堆内存首地址:" << (void*)s3.c_str() << endl; return 0; } // s1.~String() s2.~String() s3.~String()
类的静态成员
-
静态成员变量
-
静态成员变量属于类,而不属于对象
-
静态成员变量不包含在对象中,进程级生命期
-
静态成员变量的定义和初始化,只能在类的外部而不能在构造函数中进行
-
静态成员变量依然受类作用域和访问控制限定符的约束
-
访问静态成员变量,既可以通过类也可以通过对象
-
静态成员变量为该类的所有对象实例所共享
-
示例
// 静态成员变量 #include <iostream> using namespace std; // 普通成员变量:属于对象,对象的生命期 静态成员变量:不属于对象,进程级生命期 class A { public: A() { //【int m_i;】定义 } int m_i; // 声明 static int m_si; // 声明 }; int A::m_si = 0; // 全局域中定义 -- 进程级生命期 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { A a, b; // 静态成员不在对象中保存 -- 不属于对象 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 // 静态成员变量 被该类所有对象 共享 return 0; }
-
-
静态成员函数
-
静态成员函数属于类,而不属于对象
-
静态成员函数没有this指针,也没有常属性
-
静态成员函数依然受类作用域和访问控制限定符的约束
-
访问静态成员函数,既可以通过类也可以通过对象
-
静态成员函数只能访问静态成员,而非静态成员函数既可以访问静态成员,也可以访问非静态成员
-
示例
// 静态成员变量 和 静态成员函数 #include <iostream> using namespace std; // 普通成员函数属于对象:普通成员函数必须利用对象来调用 // 静态成员函数不属于对象:静态成员函数不用对象调用 class A { public: int m_i; // 非静态成员变量 void foo( /* const A* this */ ) const { // 非静态成员函数 cout << "foo()" << endl; cout << m_i << endl; // ok cout << m_si << endl;// ok bar(); // ok // 以上三行代码证明:非静态普通成员函数既可访问非静态普成员也可访问静态成员 } static int m_si; // 静态成员变量 static void bar( /*无this参数*/ ) /*const*/ { // 静态成员函数 cout << "bar()" << endl; cout << m_si << endl; // ok // cout << m_i << endl; // err // foo(); // err // 以上三行代码证明:静态成员函数只能访问静态成员,不能访问非静态普通成员 } }; int A::m_si = 0; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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; }
-
事实上,类的静态成员变量和静态成员函数,更象是普通的全局变量和全局函数,只是多了一层类作用域和访问控制限定符的约束,相当于具有成员访问属性的全局变量和全局函数
-
单例模式
-
一个类仅有一个实例(对象)
-
空类对象保存 1个字节的垃圾数据
-
示例
#include <iostream> using namespace std; /* class A { // 空类 }; int main( void ) { A a; // 空类对象保存 1个字节的垃圾数据 cout << "a的大小:" << sizeof(a) << endl; return 0; } */ class A { public: int m_i; // A m_a; // 类中 不能包含一个本类的普通对象作为成员变量 static A m_sa; // 类中 能包含一个本类的静态对象作为成员变量 }; int main( void ) { A a; // 定义a(给a分配内存空间) return 0; }
-
-
将包括,类的拷贝构造函数在内的所有构造函数私有化 , 防止使用者在类的外部创建对象
-
公有静态成员函数getInstance()是获取对象实例的唯一渠道
-
饿汉式:无论用不用,程序启动即创建
-
示例
// 单例模式 - 设计一个类,当用户使用这个类时只能出现一个对象 #include <iostream> using namespace std; // 饿汉式单例 class Singleton { public://4 - 为什么函数需要static :不需要用户利用对象调用 //5 - 为什么引用型返回值:不允许出现匿名对象 static Singleton& getInstance() { // 让用户可以得到唯一的对象 return s_instance; } private: Singleton() {} // 1 - 让用户无法定义对象 Singleton( const Singleton& that ) {} // 6 - 不允许用户克隆对象 static Singleton s_instance; // 2 - 类的设计者唯一的对象 }; Singleton Singleton::s_instance; // 3 - 静态成员变量定义必须在全局域 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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; }
-
-
懒汉式:用的时候创建,不用了即销毁
-
引用计数
-
示例
// 单例模式 - 设计一个类,当用户使用这个类时只能出现一个对象 #include <iostream> using namespace std; // 懒汉式单例 class Singleton { public: static Singleton& getInstance() { if( s_instance==NULL ) { // 判断是否是第一次被调用 s_instance = new Singleton; // 唯一的对象 cout << "创建了对象" << 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; // 计数 }; Singleton* Singleton::s_instance = NULL; int Singleton::s_counter = 0; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Singleton& s1 = Singleton::getInstance();// 第一次调用getInstance函数时,创建对象 Singleton& s2 = Singleton::getInstance();// 以后再调用就不要创建对象,返回原来创建对象 Singleton& s3 = Singleton::getInstance();// ... cout << "&s1:" << &s1 << ", &s2:" << &s2 << ", &s3:" << &s3 << endl; s1.releaseInstance(); // 将计数减1 s2.releaseInstance(); // 将计数减1 s3.releaseInstance(); // 最后一次调用releaseInstance函数时,将对象销毁 return 0; }
-
操作符重载
-
操作符标记
- 单目操作符:-、++、–、*、->等
- 双目操作符:+、-、>、<、+=、-=、>>、<<等
- 三目操作符:?:
-
操作符函数
-
在特定条件下,编译器有能力把一个由操作数和操作符组成的表达式,
- 解释为一个全局函数的调用
- 解释为一个成员函数的调用
,该全局函数或成员函数被称为操作符函数
-
通过定义操作符函数,可以实现针对自定义类型的运算法则,使之与内置类型一样参与各种操作符表达式
-
示例
// #include <iostream> 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; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human a(22,"张飞"), b(20,"赵云"), c(25,"关羽"), 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; }
-
-
双目操作符表达式:L#R
- 成员形式的操作符函数调用:L.operator# ®
左操作数是调用对象,右操作数是参数对象 - 全局形式的操作符函数调用:operator# (L, R)
左操作数是第一参数,右操作数是第二参数
- 成员形式的操作符函数调用:L.operator# ®
-
单目操作符表达式:#O/O#
- 成员形式的操作符函数调用:O.operator# ()
- 全局形式的操作符函数调用:operator# (O)
-
三目操作符表达式:F#S#T
- 无法重载
-
运算类双目操作符:+、-、*、/等
-
左操作数应可以为非常左值、常左值或右值
-
右操作数应可以为非常左值、常左值或右值
-
表达式的结果必须为右值
-
示例
// 运算类双目操作符 #include <iostream> 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 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 ); // 友元声明 }; // 全局形式的操作符函数 Human operator+( const Human& l, const Human& r ) { return Human(l.m_age+r.m_age, (l.m_name+"+"+r.m_name).c_str() ); } // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human a(22,"张飞"), b(20,"赵云"); // 非常左值 const Human c(25,"关羽"), 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关键字,把一个全局函数、另一个类的成员函数或者另一个类整体,声明为授权类的友元
- 友元拥有访问授权类任何非公有成员的特权
- 友元声明可以出现在授权类的公有、私有或者保护等任何区域,且不受访问控制限定符的约束
- 友元不是成员,其作用域并不隶属于授权类,也不拥有授权类类型的this指针
典型双目操作符的重载
-
赋值类双目操作符:=、+=、-=、*=、/=等
-
右操作数应可以为非常左值、常左值或右值
-
左操作数必须为非常左值
-
表达式结果必须为左操作数本身(而非副本)
-
示例
// 赋值类双目操作符 #include <iostream> 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& 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; }; // 全局形式的操作符函数 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human a(22,"张飞"), b(20,"赵云"); // 非常左值 const Human c(25,"关羽"), 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; }
-
-
比较类双目操作符:>、<、==、<=、>=等
-
左操作数可以为非常左值、常左值或右值
-
右操作数可以为非常左值、常左值或右值
-
表达式结果必须为 bool
-
示例
// 比较类双目操作符 #include <iostream> 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; } // 成员形式的操作符函数 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; }; // 全局形式的操作符函数 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human a(22,"张飞"), b(20,"赵云"); // 非常左值 const Human c(25,"关羽"), 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; }
-
典型单目操作符的重载
-
运算类单目操作符:-、~、!等
-
唯一操作数为非常左值、常左值或右值
-
表达式的结果必须为右值
-
示例
// 运算类单目操作符 #include <iostream> 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 operator-( /* const Human* this */ ) const { return Human(-this->m_age, this->m_name.c_str() ); } private: int m_age; string m_name; }; // 全局形式的操作符函数 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human a(22,"张飞"), b(20,"赵云"); // 非常左值 const Human c(25,"关羽"), 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; }
-
-
前自增减类单目操作符:前++、前–
- 操作数为非常左值
- 表达式的结果为操作数本身(而非副本)
-
后自增减类单目操作符:后++、后–
-
操作数为非常左值
-
表达式的结果为右值,且为自增减以前的值
-
示例
// 前++ 和 后++ #include <iostream> 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& 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; }; // 全局形式的操作符函数 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human a(22,"张飞"), b(20,"赵云"); // 非常左值 const Human c(25,"关羽"), 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; }
-
输入输出操作符的重载
-
输出操作符:<<
- 左操作数为非常左值形式的输出流(ostream)对象,右操作数为左值或右值
- 表达式的结果为左操作数本身(而非副本)
- 左操作数的类型为ostream,若以成员函数形式重载该操作符,就应将其定义为ostream类的成员,该类为标准库提供,无法添加新的成员,因此只能以全局函数形式重载该操作符
ostream& operator<< (ostream& os,
const RIGHT& right) { … }
-
输入操作符:>>
- 左操作数为非常左值形式的输入流(istream)对象,右操作数为非常左值
- 表达式的结果为左操作数本身(而非副本)
- 左操作数的类型为istream,若以成员函数形式重载该操作符,就应将其定义为istream类的成员,该类为标准库提供,无法添加新的成员,因此只能以全局函数形式重载该操作符
istream& operator>> (istream& is,
RIGHT& right) { … }
-
示例
// 输出操作符(<<) 和 输入操作符(>>) #include <iostream> 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; } // 成员形式的操作符函数 private: int m_age; string m_name; friend ostream& operator<<( ostream& os, const Human& r ) ; friend istream& operator>>( istream& is, Human& r ) ; }; // 全局形式的操作符函数 ostream& operator<<( ostream& os, const Human& r ) { os << "姓名:" << r.m_name << ", 年龄:" << r.m_age; return os; // 返回引用型参数本身 } istream& operator>>( istream& is, Human& r ) { is >> r.m_name >> r.m_age; return is; } // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Human a(22,"张飞"), b(20,"赵云"); // 非常左值 const Human c(25,"关羽"), 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; }
下标操作符
-
下标操作符:[]
-
常用于在容器类型中以下标方式获取数据元素
-
非常容器的元素为非常左值,常容器的元素为常左值
-
示例
// 简易的栈容器 #include <iostream> 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 { // 常函数 return this->arr[i]; } int& operator[]( /* Stack* this */ size_t i) { // 非常函数 return this->arr[i]; } private: int arr[20]; // 保存数据 int len; // 保存数据个数 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Stack s; // s是非常容器, for( int i=0; i<20; i++ ) { s.push(1000+i); } cout << "压栈后s容器中数据的个数:" << s.size() << endl; s[5] = 888; // 元素是非常左值. s.operator[](5) for( int i=0; i<20; i++ ) { cout << s[i] << ' '; } cout << endl; cout << "读数据后s容器中数据的个数:" << s.size() << endl; const Stack cs = s; // cs是常容器, cs[5] = 666; // cs.operator[](5) 应该让编译器报告 readonly 错误 /* int s[20] = {....}; // s是非常容器, s[5] = 888; // 元素是非常左值. const int cs[20] = {...}; // cs是常容器, cs[5] = 666; // 元素是常左值 (编译器将报告 readonly 错误) */ return 0; }
-
解引用和间接成员访问操作符
-
解引用和间接成员访问操作符:*、->
- 如果一个类重载了 “解引用”和 “间接成员访问操作符”,那么该类的对象就可以被当做指针来使用
-
应用的体现(智能指针)
- 智能指针的本质就是一个类对象,并且其维护一个指针型成员变量
智能指针
-
常规指针的缺点
- 当一个常规指针离开它的作用域时,只有该指针变量本身所占据的内存空间(通常是4个字节)会被释放,而它所指向的动态内存并未得到释放,必须自己手动释放
-
智能指针的优点
- 智能指针是一个类对象(封装了常规指针),当它离开作用域时,其析构函数负责释放该常规指针所指向的动态内存
-
智能指针与常规指针的一致性
- 为了使智能指针也能象常规指针一样,通过“*”操作符解引用,通过“->”操作符访问其目标的成员,就需要对这两个操作符进行重载
-
智能指针与常规指针的不一致性
- 智能指针的默认拷贝构造和默认拷贝赋值只是简单复制堆对象的地址
- 当多个智能指针持有同一个堆对象的地址,该堆对象将在多个智能指针的析构函数中被释放多次(double free)
-
愚蠢的解决方法(11标准已解决)
- 只允许 一个智能指针 持有 堆对象 的地址
- 自定义拷贝构造和拷贝赋值,对智能指针所持有的堆对象地址,以地址间的转移代替复制
- 智能指针的转移语义与常规指针的复制语义不一致
-
示例
// 解引用 和 间接成员访问 操作符 #include <iostream> #include <fcntl.h> #include <unistd.h> #include <memory> 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 - 关闭了file文件" << endl; // 释放 m_f 本身所占内存空间 } 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常规指针指向的内存(用户new的内存) 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; // 地址转移 } private: A* m_a; // 常规指针(保存的是用户new的内存的地址) }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { // C++标准库设计的智能指针 auto_ptr<A> pau(new A); (*pau).foo(); pau->foo(); auto_ptr<A> pbu = pau; (*pbu).foo(); pbu->foo(); // (*pau).foo(); // pau已经失效 /* 自己设计的智能指针 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已经失效 /* 常规指针 A* pa = new A; // 定义pa,初值为指向一块堆内存(A类堆对象) (*pa).foo(); pa->foo(); A* pb = pa; delete pa; // 利用A类堆对象.~A() 释放A类堆对象本身所占内存空间 */ return 0; } // pau.~Auto_ptr() pbu.~Auto_ptr() 释放 pau/pbu 本身所占内存空间
类型转换操作符
-
若源类型是基本类型,目标类型是类类型,则只能通过类型转换构造函数实现自定义类型转换
class 目标类型 {
目标类型 (const 源类型& src) { … }
};
-
若目标类型是基本类型,源类型是类类型,则只能通过类型转换操作符函数 实现自定义类型转换
class 源类型 {
operator 目标类型 (void) const { … }
};
-
示例
// 类型转换操作符函数 和 类型转换构造函数 #include <iostream> using namespace std; class Integer { public: Integer( int i ) : m_i(i) { // 类型转换构造函数 //【int m_i=i;】 cout << "Integer类的类型转换构造函数被调用" << endl; } operator int( /* const Integer* this */ ) const { cout << "Integer类的类型转换操作符函数被调用" << endl; return this->m_i; } private: int m_i; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { int n = 10; // 类类型 基本类型 // Integer <--- int Integer ix = n; // 定义 匿名Integer类对象, 利用 匿名Integer类对象.Integer(n)-->类型转换构造 // Integer ix = n.operator Integer()->int类中没有一个成员函数operator Integer(走不通) // 基本类型 类类型 // int<---Integer int m = ix; // 定义 匿名int类对象,利用 匿名int类对象.int(ix)->int类中没有一个形参为Integer构造函数(走不通) // int m = ix.operator int() ---> 类型转换操作符函数 return 0; }
-
类对象转换为布尔示例
// 类对象 转化为 布尔 #include <iostream> using namespace std; class A { public: A( int a ) : m_a(a) { //【int m_a=a;】 } operator bool( /* const A* this */ ) const { cout << "A类中的类型转换操作符函数被调用" << endl; return this->m_a; } private: int m_a; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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循环的第二个判定条件为true" << endl; break; } while( a ) { // a.operator bool() cout << "while循环判定条件为true" << endl; break; } cout << !a << endl; // ! a.operator bool() return 0; }
-
自定义类型转换
-
若源类型和目标类型都是类类型(而非基本类型),则既可以通过类型转换构造函数也可以通过类型转换操作符函数实现自定义类型转换,但不要两者同时使用,没有必要
-
若源类型和目标类型都是基本类型,则无法实现自定义类型转换,基本类型间的类型转换规则完全由编译器内置
-
示例
// 类型转换构造函数 和 类型转换操作符函数 #include <iostream> using namespace std; class Dog; // 短式声明 class Cat { public: Cat( const char* name ) : m_name(name) { // 类型转换构造函数 //【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) { // 类型转换构造函数 //【string m_name(name);】 } Dog( const Cat& c ) { // 类型转换构造函数( 定制了 Cat->Dog的转换规则 ) m_name = c.m_name; cout << "Dog类的类型转换构造函数被调用" << endl; } void talk() { cout << m_name << ": 汪汪~~~" << endl; } private: string m_name; }; Cat::operator Dog( /* const Cat* this */ ) const { cout << "Cat类的类型转换操作符函数被调动" << endl; return Dog( this->m_name.c_str() ); } // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Cat smallwhite("小白"); Dog bigyellow = smallwhite; // 定义 匿名Dog类对象,利用 匿名Dog类对象.Dog(smallwhite)-->类型转换构造 // Dog bigyellow = smallwhite.operator Dog() -->类型转换操作符函数 return 0; }
-
-
操作符重载的限制
- 不是所有的操作符都能重载,以下操作符不能重载
- 作用域限定操作符( :: )
- 直接成员访问操作符( . )
- 条件操作符( ?: )
- 字节长度操作符( sizeof )
- 类型信息操作符( typeid )
- 无法重载所有操作数均为基本类型的操作符
- 1 + 1 = 8 ?
- 不是所有的操作符都能重载,以下操作符不能重载
继承
-
继承的语法
class 子类 : 继承方式1 基类1, 继承方式2 基类2, … {
…
}; -
继承方式
- 公有继承:public
- 保护继承:protected
- 有继承:private
三种继承方式相同的基本特点
-
继承所要达到的目的:
- 子类对象包含基类子对象(成员包含基类类型的对象)
- 子类内部可以直接访问基类的所有非私有成员
-
继承的本质:
-
基类的非私有成员在子类中仅仅为可见,而非拥有
-
示例
// 继承最基本特点: // (1) 子类对象 内部包含 基类子对象 // (2) 子类内部 可以直接访问 基类的 非私有(公有/保护)成员(变量/函数) #include <iostream> 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; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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; }
-
-
对于继承切忌不要理解为基类的成员变为子类的成员,继承不会改变类成员的作用域,基类的成员永远都是基类的成员,并不会因为继承而变成子类的成员
-
尽管基类的公有和保护成员在子类中直接可见,但仍然可以在子类中重新定义这些名字,子类中的名字会隐藏所有基类中的同名定义,如果需要在子类内部访问一个在基类中定义却被子类标识符所隐藏的名字,可以借助作用域限定操作符“::”实现
-
示例
#include <iostream> 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; } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Derived d; d.fun(); return 0; }
-
-
因为作用域的不同,分别在子类和基类中定义的同名成员函数(包括静态成员函数),并不构成重载关系,相反是一种隐藏关系
-
任何时候,在子类的内部,总可以通过作用域限定操作符“::”,显式地调用那些在基类中定义却被子类所隐藏的成员
三种继承方式的差别
-
基类中的公有、保护和私有成员,在子类中将对这些基类成员的访问控制限定进行重新标记,继承方式决定了限定上限
基类中的 在公有子类中标记为 在保护子类中标记为 在私有子类中标记为 公有成员 公有成员 保护成员 私有成员 保护成员 保护成员 保护成员 私有成员 私有成员 私有成员 私有成员 私有成员 -
当“通过”子类访问其所继承的基类的成员时,需要考虑因继承方式对访问控制限定的影响
-
公有继承
// 公有继承 #include <iostream> 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 { // 子类将对基类的成员重新标记访控限定 m_a/foo是public m_b/bar是protected m_c/hum是private 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; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Derived d; // 利用子类对象在类外 访问 基类的成员时,编译器要查看这些成员在 "子类中的重新标记" 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; }
-
保护继承
// 保护继承 #include <iostream> 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 : protected Base { // 子类将对基类的成员重新标记访控限定 m_a/foo是protected m_b/bar是protected m_c/hum是private 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; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Derived d; // 利用子类对象在类外 访问 基类的成员时,编译器要查看这些成员在 "子类中的重新标记" // 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 <iostream> 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 : private Base { // 子类将对基类的成员重新标记访控限定 m_a/foo是private m_b/bar是private m_c/hum是private 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; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Derived d; // 利用子类对象在类外 访问 基类的成员时,编译器要查看这些成员在 "子类中的重新标记" // 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 <iostream> 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 { // 子类将对基类的成员重新标记访控限定 m_a/foo是public m_b/bar是protected m_c/hum是private public: void foo() { cout << "Derived::foo" << endl; } private: int m_d; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Derived d; d.m_a = 100; // d.Base::foo(); // 以上两行代码证明:只有在公有继承下,子类对象在类外可以访问基类的公有成员(其他继承不可以) return 0; }
-
-
子类类型的指针或引用 和 基类类型的指针或引用可以进行转换(其他继承方式不可以)
-
子类类型的指针或者引用能隐式转换为基类类型,编译器认为访问范围缩小是安全的
-
基类类型的指针或者引用不能隐式转换为子类类型,编译器认为访问范围扩大是危险的
-
编译器对类型安全的检测仅仅基于指针或引用本身,基类指针或引用的实际目标,究竟是不是子类对象,完全由程序员自己判断
-
示例
// 子类类型指针 和 基类类型指针 的转换 #include <iostream> 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; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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* (子类类型指针 转换为 基类类型指针) Human& rh = s; // 编译器认为 以上两个转换访问范围缩小 是安全的 // Student* ps = static_cast<Student*>(&h); // Human*-->Student* (基类类型指针 转换为 子类类型指针) // Student& rs = static_cast<Student&>(h); // 编译器认为 以上两个转换访问范围扩大 是有风险的 // 虽然通过强转可以成功,但是风险依然存在,极其不建议大家这样做 Student* ps = static_cast<Student*>(ph); // Human*-->Student*(基类类型指针 转换为 子类类型指针) Student& rs = static_cast<Student&>(rh); // Human&-->Student&(基类类型引用 转换为 子类类型引用) // 以上两种转换毫无风险,极其建议大家这么做 // 编译器就是简单而且粗暴 根据类型 来判断是否存在风险 return 0; }
-
子类的构造、析构和拷贝
-
子类的构造
-
子类没有定义构造函数
- 编译器为子类提供的默认无参构造函数:定义基类子对象,并调用其基类的无参构造函数,构造该子类对象中的基类子对象
-
子类定义构造函数但没有在初始化表中指明基类部分构造方式
- 定义基类子对象,并调用其基类的无参构造函数,构造该子类对象中的基类子对象
-
子类定义构造函数并在初始化表中指明基类部分构造方式
- 定义基类子对象并 调用指明的其基类的构造函数
-
子类对象的构造过程
-
构造基类子对象->构造成员变量->执行构造代码
-
阻断继承
- 子类的构造函数无论如何都会调用基类的构造函数,构造子类对象中的基类子对象
- 如果把基类的构造函数定义为私有,那么该类的子类就永远无法被实例化为对象
- C++中可以用这种方法阻断一个类被扩展
-
-
-
子类的析构
-
子类没有定义析构函数
- 编译器将提供一个默认析构函数:析构完所有的成员变量以后,会自动调用其基类的析构函数
-
子类定义析构函数
- 子类的析构函数在执行完自身析构代码,并析构完所有的成员变量以后,会自动调用其基类的析构函数
-
子类对象的析构过程
- 执行析构代码->析构成员变量->析构基类子对象
-
-
子类的拷贝构造
- 子类没有定义拷贝构造函数
- 编译器为子类提供的默认拷贝构造函数:定义基类子对象,并调用其基类的拷贝构造函数,构造该子类对象中的基类子对象
- 子类定义了拷贝构造函数,但没有在初始化表指明其基类部分的构造方式
- 定义基类子对象,并调用其基类的无参构造函数,构造该子类对象中的基类子对象
- 子类定义了拷贝构造函数,同时初始化表中指明了其基类部分以拷贝方式构造
- 定义基类子类对象,并调用其基类的拷贝构造函数,构造该子类对象中的基类子对象
- 子类没有定义拷贝构造函数
-
子类的拷贝赋值
-
子类没有定义拷贝赋值函数
- 编译器为子类提供的缺省拷贝赋值函数,会自动调用其基类的拷贝赋值函数,复制该子类对象中的基类子对象
-
子类定义了拷贝赋值函数,但没有显式调用其基类的拷贝赋值函数
- 子类对象中的基类子对象将得不到复制
-
子类定义了拷贝赋值函数,同时显式调用了其基类的拷贝赋值函数
- 子类对象中的基类子对象将得到复制
-
-
示例
// 子类的构造函数 和 析构函数 #include <iostream> 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类的缺省构造函数被调用" << 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类的拷贝构造函数被调用" << endl; } Human& operator=( const Human& that ) { // 编译器不会在拷贝赋值函数中塞操作 cout << "Human类的拷贝赋值函数被调用" << endl; this->m_age = that.m_age; this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string类拷贝赋值函数 return *this; } ~Human() { cout << "Human类的析构函数被调用" << endl; // 对于类类型的成员变量m_name,利用m_name.~string() // 释放 m_age/m_name 本身所占内存空间 } 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; } // 如果子类没有提供任何构造函数,编译器将提供一个无参的构造函数 /* 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类的缺省构造函数被调用" << endl; } // 如果子类没有提供析构函数,编译器将提供一个默认的析构函数 /* ~Student() { 对于m_remark,利用m_remark.~string() 对于基类子对象,利用基类子对象.~Human() 释放 m_score/m_remark/基类子对象 本身所占内存空间 }*/ ~Student() { cout << "Student类的析构函数被调用" << endl; // 对于m_remark,利用m_remark.~string() // 对于基类子对象,利用基类子对象.~Human() // 释放 m_score/m_remark/基类子对象 本身所占内存空间 } // 如果子类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数 /* Student( const Student& that ) { 【Human(that);】定义 基类子对象,利用 基类子对象.Human(that)-->调用Human类的拷贝构造函数 【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)-->调用基类的拷贝构造函数 //【float m_score=that.m_score;】 //【string m_remark=that.m_remark;】 cout << "Student类的拷贝构造函数被调用" << endl; } // 如果子类没有提供拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数 /* Student& operator=( const Student& that ) { Human& rh = *this; rh = that; // rh.operator=(that)-->调用Human类的operator=函数 this->m_score = that.m_score; this->m_remark = that.m_remark; return *this; }*/ Student& operator=( const Student& that ) { // 编译器不会在拷贝赋值函数中塞操作 cout << "Student类的拷贝赋值函数被调用" << endl; Human& rh = *this; rh = that; // rh.operator=(that)-->调用Human类的operator=函数 this->m_score = that.m_score; this->m_remark = that.m_remark; return *this; } private: float m_score; string m_remark; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { cout << "--------------s1创建信息--------------" << endl; Student s1(22,"张飞",88.5,"良好"); // 定义s1,利用s1.Student(22,"张飞",88.5,"良好) s1.getinfo(); cout << "--------------s2创建信息--------------" << endl; Student s2 = s1;//(s1) 定义s2,利用s2.Student(s1) s2.getinfo(); cout << "--------------s3创建信息--------------" << 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本身所占内存空间
多重继承
一个类可以同时从多个基类继承实现代码
-
多重继承的内存布局
-
子类对象中的多个基类子对象,按照继承表的顺序依次被构造,析构的顺序则与构造严格相反,各个基类子对象按照从低地址到高地址排列
-
示例
// 多重继承 #include <iostream> 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; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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 << "整个子类对象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; }
-
-
多重继承的类型转换
-
将多重继承的子类对象的指针,隐式转换为它的基类类型,编译器会根据各个基类子对象在子类对象中的内存位置,进行适当的偏移计算,以保证指针的类型与其所指向目标对象的类型一致
-
反之,将任何一个基类类型的指针静态转换为子类类型,编译器同样会进行适当的偏移计算
-
无论在哪个方向上,重解释类型转换(reinterpret_cast)都不进行任何偏移计算
-
引用的情况与指针类似,因为引用的本质就是指针
-
示例
// 多重继承 的 类型转换 #include <iostream> 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; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { D d; D* pd = &d; cout << "整个子类对象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 << "-------------隐式转换-----------------" << 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 << "-------------静态转换-----------------" << endl; D* p1 = static_cast<D*>(pa); cout << "A* pa--->D* p1: " << p1 << endl; D* p2 = static_cast<D*>(pb); cout << "B* pb--->D* p2: " << p2 << endl; D* p3 = static_cast<D*>(pc); cout << "C* pc--->D* p3: " << p3 << endl; cout << "-------------重解释转换-----------------" << endl; pa = reinterpret_cast<A*>(pd); cout << "D* pd--->A* pa: " << pa << endl; pb = reinterpret_cast<B*>(pd); cout << "D* pd--->B* pb: " << pb << endl; pc = reinterpret_cast<C*>(pd); cout << "D* pd--->C* pc: " << pc << endl; return 0; }
-
-
围绕多重继承,历来争议颇多
- 现实世界中的实体本来就具有同时从多个来源共同继承的特性,因此多重继承有助于面向现实世界的问题域直接建立逻辑模型
- 多重继承可能会在大型程序中引入令人难以察觉的BUG,并极大地增加对类层次体系进行扩展的难度
-
名字冲突问题
-
如果在子类的多个基类中,存在同名的标识符,那么任何试图通过子类对象,或在子类内部访问该名字的操作,都将引发歧义
-
解决办法
- 子类隐藏该标识符
- 通过作用域限定操作符“::”显式指明所属基类
-
示例
// 多重继承 的 名字冲突问题 #include <iostream> using namespace std; class A { // 学生类 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; // 在业务上毫无意义, 仅仅将基类的m_c隐藏 void foo() { B::m_c = 8000; // } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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; }
-
钻石继承
一个子类继承自多个基类,而这些基类又源自共同的祖先,这样的继承结构称为钻石继承(菱形继承)
-
钻石继承问题
-
公共基类子对象,在汇聚子类对象中,存在多个实例
-
在汇聚子类内部,或通过汇聚子类对象,访问公共基类的成员,会因继承路径的不同而导致匹配歧义
-
示例
// 钻石继承 #include <iostream> using namespace std; class A { // 公共基类(人类) public: int m_a; // 年龄 }; class X : public A { // 中间子类(学生类) 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; // 歧义 } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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关键字
-
虚继承可以保证
-
公共虚基类子对象在汇聚子类对象中仅存一份实例
-
公共虚基类子对象被多个中间子类子对象所共享
-
-
-
虚继承实现原理
-
汇聚子类对象中的每个中间子类子对象都持有一个指针,通过该指针可以获取 中间子类子对象的首地址 到 公共虚基类子对象的首地址的 偏移量
-
-
示例
// 钻石继承缺陷的解决方法 --- 虚继承 // 虚继承能够保证两件事: // (1) 保证 公共虚基类(A类)子对象 在 汇聚子类(Z类)对象中只有一份 // (2) 公共虚基类子对象被多个中间子类子对象所共享 #include <iostream> using namespace std; #pragma pack(1) class A { // 公共虚基类(人类) public: int m_a; // 年龄 }; class X : virtual public A { // 中间子类(学生类) 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; // } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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 ) --> 实参类型为Z* cout << z.getAge() << endl; // getAge( &z ) --> 实参类型为Z* return 0; }
虚函数
-
对象的自恰性
-
对同样的函数调用,各个类的对象都会做出恰当的响应(是哪个类的对象,就优先调哪个类的成员函数)
-
通过基类类型指针调用普通成员函数只能调用基类的成员函数
- 即便这个基类类型的指针指向子类对象,调用的也为基类的成员函数
- 一旦调用子类所特有的成员函数,将引发编译错误
- 编译器仅根据指针的类型确定调用哪个类的普通成员函数
-
示例
// 非虚的世界 -- 没有虚函数的程序 #include <iostream> 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() {} }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { cout << "--------利用对象调用 非虚的成员函数------------" << endl; // 对象的自恰性:哪个类对象 就调用了 哪个类的 非虚的成员函数 Shape s; s.Draw(); // Shape::Draw Rect r; r.Draw(); // Rect::Draw Circle c; c.Draw(); // Circle::Draw cout << "-------利用指针调用 非虚的成员函数-------------" << endl; // 基类类型指针 只能调用 基类的 非虚的成员函数 Shape* ps = &s; ps->Draw(); // Shape::Draw // 即便 基类类型指针 指向的是 子类对象,调用的仍然为 基类的 非虚的成员函数 ps = &r; ps->Draw(); // Shape::Draw ps = &c; ps->Draw(); // Shape::Draw // ps->foo(); // error // 编译器简单而且粗暴的 根据 指针本身的类型 来确定调用哪个类的非虚的成员函数 return 0; }
-
-
虚函数
形如
class 类名 {
virtual 返回类型 函数名 (形参表) { … }
};
的成员函数,称为虚函数或方法 -
覆盖
- 如果子类的成员函数和基类的虚函数具有相同的函数签名,那么该成员函数就也是虚函数,无论其是否带有virtual关键字,且与基类的虚函数构成覆盖关系
-
通过基类类型指针调用虚函数
-
如果基类型指针指向基类对象,调用基类的原始版本虚函数
-
如果基类型指针指向子类对象,调用子类的覆盖版本虚函数
-
虚函数应用-多态
-
多态
-
如果子类提供了对基类虚函数的有效覆盖,那么通过一个基类型指针( 指向子类对象 ),或者基类型引用( 引用子类对象 ),调用该虚函数,实际被调用的将是子类中的覆盖版本,而非基类中的原始版本,这种现象称为多态
-
多态的重要意义在于,一般情况下,调用哪个类的成员函数是由指针或引用本身的类型决定的,而当多态发生时,调用哪个类的成员函数是由指针或引用的实际目标对象的类型决定的
-
示例
// 虚的世界 -- 有虚函数的程序 #include <iostream> using namespace std; class Shape { public: virtual void Draw() { cout << "Shape::Draw" << endl; } // 原始版本虚函数 int m_x; int m_y; }; class Rect : public Shape { public: void Draw() { cout << "Rect::Draw" << endl; } // 虚函数,编译器补virtual,与基类虚函数构成覆盖关系 int m_rx; int m_ry; }; class Circle : public Shape { public: virtual void Draw() { cout << "Circle::Draw" << endl; }// 虚函数,编译器不补virtual,与基类虚函数构成覆盖关系 int m_radius; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { cout << "--------利用对象调用 虚的成员函数------------" << endl; // 对象的自恰性:哪个类对象 就调用了 哪个类的 虚成员函数 Shape s; s.Draw(); // Shape::Draw Rect r; r.Draw(); // Rect::Draw Circle c; c.Draw(); // Circle::Draw cout << "-------利用指针调用 虚的成员函数-------------" << endl; Shape* ps = &s; ps->Draw(); // Shape::Draw ps = &r; ps->Draw(); // Rect::Draw (多态) ps = &c; ps->Draw(); // Circle::Draw (多态) // 根据 指针指向的 对象的类型 来确定调用哪个类的 虚的成员函数 return 0; }
-
-
具备以下两个要件,多态才能表现出来
- 需要在基类中定义虚函数,子类提供覆盖版本
- 必须借助基类型指针(指向子类对象)或者基类型引用(引用子类对象)调用该虚函数
-
调用虚函数的指针也可以是基类中的this指针,同样能满足多态的条件,但在构造和析构函数中除外
-
示例
// this指针 和 多态 #include <iostream> using namespace std; class Base { public: virtual void vfun() { cout << "Base::vfun()" << endl; } void foo( /* Base* this */ ) { cout << "foo函数中调用的为--"; this->vfun(); } Base( /* Base* this */ ) { cout << "构造函数中调用的为--"; this->vfun(); } ~Base( /* Base* this */ ) { cout << "析构函数中调用的为--"; this->vfun(); } }; class Derived : public Base { public: Derived() { //【Base();】定义 基类子对象,利用 基类子对象.Base() } ~Derived() { // 对于 基类子对象,利用 基类子对象.~Base() } void vfun() { cout << "Derived::vfun()" << endl; } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { Derived d; // 定义d,利用d.Derived() d.foo(); // foo( &d ) return 0; } // d.~Derived()
-
-
虚函数表
-
示例
// 多态揭秘 -- 虚函数表 #include <iostream> using namespace std; class A { // 编译器根据A类信息,将制作一张虚函数表 A::foo的地址 A::bar的地址 public: virtual void foo() { cout << "A::foo" << endl; } virtual void bar() { cout << "A::bar" << endl; } }; class B : public A { // 编译器根据B类信息,将制作一张虚函数表 B::foo的地址 A::bar的地址 public: void foo() { cout << "B::foo" << endl; } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { A a; // |虚表指针|-->编译器根据A类信息制作的虚函数表 cout << "a对象的大小:" << sizeof(a) << endl; // 8 B b; // |虚表指针|-->编译器根据B类信息制作的虚函数表 cout << "b对象的大小:" << sizeof(b) << endl; // 8 void(**pfunc)() = *((void(***)())&a); // 将a中的数据(虚表指针)取出来,虚表指针是一个二级指针 pfunc[0](); // A::foo pfunc[1](); // A::bar void(**pfunc2)() = *((void(***)())&b); // 将b中的数据(虚表指针)取出来 pfunc2[0](); // B::foo pfunc2[1](); // A::bar A* pa = &b; pa->foo(); // 在编译期间,编译器并不知道调用哪个foo // 1. 利用pa找到b对象所占内存空间 // 2. 从b对象所占内存空间中获取 虚表指针 // 3. 利用虚表指针 找到 编译器根据B类信息制作的虚函数表 // 4. 从虚函数表中 获取 虚函数的入口地址 // 5. 利用 虚函数的入口地址 调用 虚函数 return 0; }
-
-
多态应用示例
// 多态的应用场景 #include <iostream> #include <pthread.h> #include <cstdio> #include <unistd.h> using namespace std; // 设计一个 "通用" 的线程类,能满足全天下所有用户开线程需求 class Thread { public: void start( /* Thread* this */ ) { pthread_create(&m_tid,NULL,threadrun,this); } static void* threadrun( void* arg ) {//编译器不会给静态函数补this指针 // 线程开启后执行的操作不应该由类的设计者提供,应该由用户提供 // 类的设计者调用用户提供的操作 Thread* p = (Thread*)arg; p->run(); } virtual void run() = 0; // 原始版本虚函数 private: pthread_t m_tid; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) class MyThread : public Thread { public: MyThread( char ch, int sec ) : m_ch(ch),m_sec(sec) {} void run() { // 覆盖版本虚函数 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; }
-
动态绑定
- 当编译器看到通过指针或引用调用虚函数的语句时,并不急于生成有关函数调用的指令,相反它会用一段代码替代该语句,这段代码在运行时才能被执行,完成如下操作:
- 确定指针或引用的目标对象所占内存空间
- 从目标对象所占内存空间中找到虚表指针
- 利用虚表指针找到虚函数表
- 从虚函数表中获取所调用虚函数的入口地址
- 根据入口地址,调用该函数
- 当编译器看到通过指针或引用调用虚函数的语句时,并不急于生成有关函数调用的指令,相反它会用一段代码替代该语句,这段代码在运行时才能被执行,完成如下操作:
-
动态绑定对性能的影响
- 虚函数表本身会增加进程内存空间的开销
- 与普通函数调用相比,虚函数调用要多出几个步骤,会增加运行时间的开销
- 动态绑定会妨碍编译器通过内联来优化代码
- 只有在确实需要多态特性的场合才使用虚函数,否则尽量使用普通函数
纯虚函数
-
纯虚函数
形如
class 类名 {
virtual 返回类型 函数名 (形参表) = 0;
};
的虚函数,称为纯虚函数或抽象方法 -
抽象类
- 拥有纯虚函数的类称为抽象类
- 抽象类不能实例化为对象
- 抽象类的子类如果不对基类中的全部纯虚函数提供有效的覆盖,那么该子类就也是抽象类
-
纯抽象类
- 全部由纯虚函数构成的抽象类称为纯抽象类或接口
-
示例
// 纯虚函数 和 抽象类 #include <iostream> using namespace std; class A { // 抽象类 public: void bar() { } virtual void foo() = 0; // 纯虚函数 }; class B : public A { public: void foo() { // ... } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { // A a; // new A; B b; new B; return 0; }
虚函数的其他应用
-
动态类型转换(dynamic_cast)
-
用于将基类类型的指针或引用转换为其子类类型的指针或引用
-
前提是子类必须从基类多态继承(即基类包含至少一个虚函数)
-
动态类型转换会对所需转换的基类指针或引用做检查,如果其指向的对象的类型与所要转换的目标类型一致,则转换成功,否则转换失败
-
针对指针的动态类型转换,以返回空指针(NULL)表示失败,针对引用的动态类型转换,以抛出bad_cast异常表示失败
-
示例
// 动态类型转换 -- 将基类类型指针 转换为 子类类型指针 // 将基类类型引用 转换为 子类类型引用 #include <iostream> using namespace std; class A { // 编译器根据A类信息,将制作一张虚函数表 "A"...|A::foo的地址 virtual void foo() {} }; class B : public A { // 编译器根据B类信息,将制作一张虚函数表 "B"...|A::foo的地址 }; class C : public B { // 编译器根据C类信息,将制作一张虚函数表 "C"...|A::foo的地址 }; class D { // 编译器根据D类信息 不会制作虚函数表 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { B b; // |虚表指针|-->编译器根据B类信息制作的虚函数表 A* pa = &b; // B*-->A* (子类类型指针-->基类类型指针) cout << "---------dynamic_cast 不是编译器检查,是在运行期间检查并转换---------------" << endl; B* pb = dynamic_cast<B*>(pa); // pa->b对象所占内存空间->虚表指针->编译器根据B类信息制作的虚函数表->"B" cout << "A* pa--->B* pb:" << pb << endl; C* pc = dynamic_cast<C*>(pa); // pa->b对象所占内存空间->虚表指针->编译器根据B类信息制作的虚函数表->"B" cout << "A* pa--->C* pc:" << pc << endl; D* pd = dynamic_cast<D*>(pa); // pa->b对象所占内存空间->虚表指针->编译器根据B类信息制作的虚函数表->"B" cout << "A* pa--->D* pd:" << pd << endl; cout << "---------static_cast 编译器给我们检查并转换---------------" << endl; pb = static_cast<B*>(pa); // A*-->B*的反向B*-->A*可以隐式 cout << "A* pa--->B* pb:" << pb << endl; pc = static_cast<C*>(pa); // A*-->C*的反向C*--->A*可以隐式 cout << "A* pa--->C* pc:" << pc << endl; // pd = static_cast<D*>(pa); // A*--->D*的反向D*--->A*不可以隐式 // cout << "A* pa--->D* pd:" << pd << endl; return 0; }
-
-
typeid操作符
-
#include
-
返回type_info类型对象的常引用
- type_info类的成员函数name(),返回类型名字符串
- type_info类支持“==”和“!=”操作符,可直接用于类型相同与否的判断
-
当其作用于基类类型的指针或引用的目标对象时
- 若基类不包含虚函数 typeid所返回类型信息由该指针或引用本身的类型决定
- 若基类包含至少一个虚函数,即存在多态继承,typeid所返回类型信息由该指针或引用的实际目标对象的类型决定
-
示例
// typeid操作符 : 无法获取对象本身的常属性信息 #include <iostream> #include <typeinfo> using namespace std; class A { // 编译器根据A类信息,将制作一张虚函数表 "A"...|A::foo的地址 virtual void foo() {} }; class B : public A { // 编译器根据B类信息,将制作一张虚函数表 "B"...|A::foo的地址 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { B b; // |虚表指针|-->编译器根据B类信息制作的虚函数表 A* pa = &b; A& ra = b; cout << typeid(*pa).name() << endl;//pa->b对象所占内存->虚表指针->编译器根据B类信息制作的虚函数表->"B" cout << typeid(ra).name() << endl;//ra->b对象所占内存->虚表指针->编译器根据B类信息制作的虚函数表->"B" int m; const type_info& r = typeid(m); // 1. 获取m的类型信息(类名,类大小,类版本...) // 2. 定义一个type_info类的对象 // 3. 将第一步获取的类型信息 保存到 第二步创建type_info类对象的 各个"私有"成员变量中 // 4. 返回 上面创建的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; }
-
虚析构函数
-
delete一个基类指针(指向子类对象)
-
实际被调用的仅仅是基类的析构函数
-
基类的析构函数只负责析构子类对象中的基类子对象
-
基类的析构函数不会调用子类的析构函数
-
在子类中分配的资源将无法得到释放
-
如果将基类的析构函数声明为虚函数,那么实际被调用的将是子类的析构函数
-
子类的析构函数将首先释放子类对象自己的成员,然后再调用基类的析构函数释放该子类对象的基类部分,最终实现完美的资源释放
-
示例
// 虚 析构函数 能够解决 delete一个基类指针(指向子类对象)能够正确的调用子类析构函数 #include <iostream> #include <fcntl.h> #include <unistd.h> 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 - 关闭了cfg1文件" << endl; // 释放m_a本身所占内存空间 } 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 - 关闭了cfg2文件" << endl; // 对于 基类子对象.~A() // 释放 基类子对象/m_b 本身所占内存空间 } private: int m_b; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { A* p = new B; // 定义 B类堆对象,利用 B类堆对象.B() // ... delete p; // p->虚析构函数( ~B() ) 释放 B类堆对象所占内存空间 return 0; } // 释放 p 指针本身所占内存
-
-
空虚析构函数
- 没有分配任何动态资源的类,无需定义析构函数
- 没有定义析构函数的类,编译器会为其提供一个缺省析构函数,但缺省析构函数并不是虚函数
- 为了保证delete一个指向子类对象的基类指针时,能够正确调用子类的析构函数,就必须把基类的析构函数定义为虚函数,即使它是一个空函数
- 任何时候,为基类定义一个虚析构函数总是无害的
-
一个类中,除了构造函数和静态成员函数外,任何函数都可以被声明为虚函数
异常
-
三种典型的错误报告机制
-
通过返回值返回错误信息
-
所有局部对象都能正确地被析构
-
逐层判断,流程繁琐
-
示例
// 利用 return 报告异常 #include <iostream> #include <cstdio> using namespace std; class A { public: A() { cout << "A() is invoked" << endl; } ~A() { cout << "~A() is invoked" << endl; } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int foo( ) { cout << "foo出错前的几百行代码" << endl; A a; FILE* pfile = fopen( "./cfg", "r" ); if( !pfile ) return -1; // (1)将数据返回给调用者 (2)跳转至右花括号 cout << "foo出错后的几百行代码" << endl; return 0; } // a.~A() 释放a本身所占内存空间 int bar( ) { cout << "bar出错前的几百行代码" << endl; A b; if( foo()==-1 ) return -1; cout << "bar出错后的几百行代码" << endl; return 0; } // b.~A() 释放b本身所占内存空间 int hum( ) { cout << "hum出错前的几百行代码" << endl; A c; if( bar()==-1 ) return -1; cout << "hum出错后的几百行代码" << endl; return 0; } // c.~A() 释放c本身所占内存空间 int main( void ) { cout << "main出错前的几百行代码" << endl; A d; if( hum()==-1 ) return -1; cout << "main出错后的几百行代码" << endl; return 0; } // d.~A() 释放d本身所占内存空间
-
-
借助setjmp/longjmp远程跳转
- 一步到位,流程简单
- 某些局部对象可能因此丧失被析构的机会
-
抛出——捕获异常对象
- 形式上一步到位,流程简单
- 实际上逐层析构局部对象,避免内存泄漏
-
C++异常处理
-
抛出异常
-
throw 异常对象;
-
可以抛出基本类型的对象,如:
throw -1;
throw “内存分配失败!”; -
也可以抛出类类型的对象,如:
MemoryException ex;
throw ex;
throw MemoryException (); -
但不要抛出局部对象的指针,如:
MemoryException ex;
throw &ex; // 错误! -
示例
// 利用 throw 报告异常 #include <iostream> #include <cstdio> using namespace std; class A { public: A() { cout << "A() is invoked" << endl; } ~A() { cout << "~A() is invoked" << endl; } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) void foo( ) { cout << "foo出错前的几百行代码" << endl; A a; FILE* pfile = fopen( "./cfg", "r" ); if( !pfile ) throw -1; cout << "foo出错后的几百行代码" << endl; } // a.~A() 释放a本身所占内存空间 void bar( ) { cout << "bar出错前的几百行代码" << endl; A b; // try { foo(); // } // catch( int e ) { // cout << "bar中捕获异常信息:" << e << endl; // } cout << "bar出错后的几百行代码" << endl; } // b.~A() 释放b本身所占内存空间 void hum( ) { cout << "hum出错前的几百行代码" << endl; A c; // try { bar(); // } // catch( int e ) { // cout << "hum中捕获异常信息:" << e << endl; // } cout << "hum出错后的几百行代码" << endl; } // c.~A() 释放c本身所占内存空间 int main( void ) { cout << "main出错前的几百行代码" << endl; A d; try { hum(); } catch( int e ) { cout << "main中捕获异常信息:" << e << endl; } cout << "main出错后的几百行代码" << endl; return 0; } // d.~A() 释放d本身所占内存空间
-
-
捕获异常
try {
可能引发异常的语句;}
catch (异常类型1& ex) {
针对异常类型1的异常处理;}
catch (异常类型2& ex) {
针对异常类型2的异常处理;}
…
catch (异常类型n& ex) {
针对异常类型n的异常处理;}
-
未抛出异常时的流程(直线代表正常执行,弧线代表跳过)
-
发生异常时的流程
-
捕获异常
-
建议在catch子句中使用引用接收异常对象,避免因为拷贝构造带来性能损失
-
推荐以匿名临时对象的形式抛出异常
-
异常对象必须允许被拷贝构造和析构
-
示例
// 关于捕获异常的几点建议 #include <iostream> using namespace std; class A { public: A() {} A( const A& that ) { cout << "A类拷贝构造函数被调用" << endl; } ~A() {} }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) void foo( ) { A a; throw A(); // 建议抛出匿名临时对象(被编译器优化) } int main( void ) { try { foo(); } catch( A& e ) { // 建议 以引用方式 接收 异常对象 // ... } return 0; }
-
-
匹配顺序
-
根据异常对象的类型自上至下顺序匹配,而非最优匹配,因此对子类类型异常的捕获不要放在对基类类型异常的捕获后面
-
示例
// 捕获异常的匹配顺序 : 顺序匹配 而非 最优匹配 #include <iostream> using namespace std; class A {}; class B : public A {}; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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; }
-
-
标准库异常
异常说明
-
异常说明是函数原型的一部分,旨在说明函数可能抛出的异常类型
返回类型 函数名 (形参表) throw (异常类型1, 异常类型2, …) {
函数体;
} -
异常说明是一种承诺,承诺函数不会抛出异常说明以外的异常类型
-
如果函数抛出了异常说明以外的异常类型,那么该异常将无法被捕获,并导致进程中止
-
终止流程:std::unexpected() -> std::terminate() -> abort()
-
示例
// 异常说明 : 承诺调用者 函数内部只可能抛出 哪些类型的异常 #include <iostream> using namespace std; void foo() throw(int,double,const char*) { // 显式抛出异常 throw "hello world"; // 3.14; // -1; } void bar() throw(int,double,const char*) { // 隐式抛出异常 foo(); // 调用了几十个不同的函数,而且每个函数都抛出异常,并且抛出的异常类型还都不一样 } // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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; }
-
-
隐式抛出异常的函数也可以列出它的异常说明
-
异常说明可以没有也可以为空
-
没有异常说明,表示可能抛出任何类型的异常
void foo (void) { … } -
异常说明为空,表示不会抛出任何类型的异常
void foo (void) throw () { … } -
示例
// 没有异常说明 和 异常说明为空 #include <iostream> using namespace std; void foo() { // 没有异常说明:承诺调用者,函数内部可能抛出任何类型的异常 throw 3.14; // -1; // "hello world"; } void bar() throw() { // 异常说明为空:承诺调用者,函数内部不会抛出任何类型的异常 throw -1; } void hum() throw(int,double); // 声明 void hum() throw(int,double) { // 定义 } // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) 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; }
-
-
异常说明在函数的声明和定义中必须保持严格一致,否则将导致编译错误
-
异常处理模式
- 抛出基本类型的异常,根据异常对象的值分别处理
- 抛出类类型的异常,根据异常对象的类型分别处理
- 利用类类型的异常,携带更多诊断信息,以便查错
- 可以在catch块中继续抛出所捕获的异常,或其它异常
- 任何未被捕获的异常,都会由std::unexpected()函数处理,缺省的处理方式就是中止进程
- 忽略异常,不做处理
构造函数中的异常
-
构造函数可以抛出异常,某些时候还必须抛出异常
- 构造过程中可能遇到各种错误,比如内存分配失败
- 构造函数没有返回值,无法通过返回值通知调用者
-
构造函数抛出异常,对象将被不完整构造,而一个被不完整构造的对象,其析构函数永远不会被执行
- 所有对象形式的成员变量,在抛出异常的瞬间,都能得到正确地析构(构造函数的回滚机制)
- 有动态分配的资源,必须在抛出异常之前,自己手动释放,否则将形成资源的泄漏
-
示例
// 构造函数中的异常 #include <iostream> #include <cstdio> 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; // 自己手动释放动态资源 // 利用m_a.~A() // 释放 m_a/m_p 本身所占内存空间 throw -1; } // ... 后续代码 ... } ~C() { cout << "~C() is invoked" << endl; delete m_p; // 利用m_a.~A() // 释放 m_a/m_p 本身所占内存空间 } private: A m_a; int* m_p; }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { try { C c; // 定义c,利用c.C() } // 如果c是完整构造对象,将利用c.~C() , 但如果c是残缺对象,将不调用~C() catch( ... ) { // ... } return 0; }
析构函数中的异常
-
不要从析构函数中主动抛出异常
- 在两种情况下,析构函数会被调用
- 正常销毁对象,离开作用域或显式delete
- 在异常传递的堆栈辗转开解(stack-unwinding)过程中
- 对于第二种情况,异常正处于激活状态,而析构函数又抛出了异常,这时C++将通过std::terminate()函数,令进程中止
- 在两种情况下,析构函数会被调用
-
对于可能引发异常的操作,尽量在析构函数内部消化
try { … } catch (…) { … }
-
示例
// 不要在析构函数中 "主动抛出" 异常 #include <iostream> #include <cstdio> using namespace std; void foo() { throw "foo函数中抛出的异常"; } void bar() { throw "bar函数中抛出的异常"; } class A { public: ~A() { try { bar(); } catch( const char* e ) { cout << "析构函数中捕获异常信息:" << e << endl; } } }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { try { A a; foo(); // ... 其他代码 ... } // a.~A() catch( const char* e ) { cout << "main中捕获异常信息:" << e << endl; } return 0; }
I/O流
I/O流的基本概念
-
流/流数据(Stream)
- 字节序列形式的数据(如:二进制数据、文本字符、图形图像、音频视频,等等),犹如流水一般,从一个对象流向另一个对象
-
输入流(Input Stream)
- 数据从表示输入设备(如:键盘、磁盘文件等)的对象流向内存对象。
例如:cin >> student;
- 数据从表示输入设备(如:键盘、磁盘文件等)的对象流向内存对象。
-
输出流(Output Stream)
- 数据从内存对象流向表示输出设备(如:显示器、打印机、磁盘文件等)的对象。
例如:cout << student;
- 数据从内存对象流向表示输出设备(如:显示器、打印机、磁盘文件等)的对象。
-
流缓冲(Stream Buffer)
- 介于各种I/O设备和内存对象之间的内存缓冲区
- 当从键盘输入时,数据首先进入键盘缓冲区,直到按下回车键,才将键盘缓冲区中的数据灌注到输入流缓冲区,之后再通过流操作符“>>”,进入内存对象
- 当向显示器输出时,数据首先通过流操作符“<<”, 从内存对象进入输出流缓冲区,直到缓冲区满或遇到换行符,才将其中的数据灌注到显示器上显示出来
-
流对象(Stream Object)
- 表示各种输入输出设备的对象,如键盘、显示器、打印机、磁盘文件等,因其皆以流的方式接收或提供数据,故称为流对象
- 向下访问各种物理设备接口,向上与应用程序交互,中间维护流缓冲区
- 三个预定义的标准流对象
- cin:标准输入设备——键盘
- cout:标准输出设备——显示器
- cerr:标准错误输出设备——显示器,不带缓冲
-
流类(Stream Class)
- 用于实例化流对象的类
- cin和cout分别是istream_withassign和ostream_withassign类的对象
-
流类库(Stream Class Library)
- C++以继承的方式定义了一组流类,并将其作为标准C++库的一部分提供给用户
- 基于流类库可以构建三种形式的流对象
- 面向控制台的I/O流
- 面向文件的I/O流
- 向内存的I/O流
I/O流类库
目标 | 抽象 | 输入 | 输出 | 输入输出 |
---|---|---|---|---|
抽象 | ios | istream | ostream | iostream |
控制台 | —— | istream_ withassign | ostream_ withassign | iostream_withassign |
文件 | fstreambase | ifstream | ofstream | fstream |
内存 | strstreambase | istrstream | ostrstream | strstream |
-
以上只有非抽象的9个类,针对具体目标执行具体操作
-
其中控制台的三个类已经预定义了cin/cout/cerr流对象
-
实际编程中主要使用文件和内存的6个类实现针对文件和内存的I/O
-
出于某些原因,所有I/O流类都不支持拷贝构造和拷贝赋值
-
#include <iostream>
- ios、istream、ostream、iostream
- istream_withassign、ostream_withassign、iostream_withassign
-
#include <fstream>
- ifstream、ofstream、fstream
-
#include <strstream>
- istrstream、ostrstream、strstream
-
#include <sstream>
- istringstream、ostringstream、stringstream
I/O流的打开与关闭
-
通过构造函数打开I/O流
- 打开输入流
ifstream(const char* filename,openmode mode); - 打开输出流
ofstream(const char* filename,openmode mode); - 打开输入输出流
fstream(const char* filename,openmode mode);
- 打开输入流
-
其中filename表示文件路径,mode表示打开模式
-
打开模式
-
ios::out
-
打开文件用于写入,不存在则创建,存在则清空
-
适用于ofstream(缺省)/fstream
-
示例
#include <iostream> #include <fstream> using namespace std; // C++标准设计的类 -- ofstream(输出文件流)类 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { ofstream ofs1( "./ofs", ios::out ); if( !ofs1 ) { // ! ofs1.operator bool() cerr << "ofs1流对象状态错误--打开文件失败" << endl; } ofs1 << 1234 << ' ' << 56.78 << ' ' << "hello" << '\n'; if( !ofs1 ) { // ! ofs1.operator bool() cerr << "ofs1流对象状态错误--写文件失败" << endl; } ofs1.close(); ofstream ofs2( "./ofs", ios::app ); if( !ofs2 ) { // ! ofs2.operator bool() cerr << "ofs2流对象状态错误--打开文件失败" << endl; } ofs2 << "world" << endl; if( !ofs2 ) { // ! ofs2.operator bool() cerr << "ofs2流对象状态错误--写文件失败" << endl; } ofs2.close(); return 0; }
-
-
ios::app
- 打开文件用于追加,不存在则创建,存在不清空
- 适用于ofstream/fstream
-
ios::trunc
- 打开时清空原内容
- 适用于ofstream/fstream
-
ios::in
-
打开文件用于读取,不存在则失败,存在不清空
-
适用于ifstream(缺省)/fstream
-
示例
#include <iostream> #include <fstream> using namespace std; // C++标准设计的类 -- ifstream(输入文件流)类 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { cout << "------------第一次读文件-----------------------" << endl; ifstream ifs1( "./ofs", ios::in ); if( !ifs1 ) { // ! ifs1.operator bool() cerr << "ifs1流对象状态错误--打开文件失败" << endl; } int i; double d; string s1, s2; ifs1 >> i >> d >> s1 >> s2; if( !ifs1 ) { // ! ifs1.operator bool() cerr << "ifs1流对象状态错误--读文件失败" << endl; } cout << i << ' ' << d << ' ' << s1 << ' ' << s2 << endl; ifs1.close(); cout << "------------第二次读文件-----------------------" << endl; ifstream ifs2( "./ofs", ios::ate ); if( !ifs2 ) { // ! ifs2.operator bool() cerr << "ifs2流对象状态错误--打开文件失败" << 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流对象状态错误--读文件失败" << endl; } cout << ii << ' ' << dd << ' ' << ss1 << ' ' << ss2 << endl; ifs2.close(); return 0; }
-
-
ios::ate
- 打开时定位文件尾
- 适用于ifstream/fstream
-
ios::binary
- 以二进制模式读写
- 适用于ifstream/ofstream/fstream
-
I/O流对象的状态
-
I/O流类对象内部保存当前状态,其值为以下常量的位或
- ios::goodbit:0,一切正常
- ios::badbit:1,发生致命错误
- ios::eofbit:2,遇到文件尾
- ios::failbit:4,打开文件失败或实际读写字节数未达预期
-
I/O流的状态成员函数
状态成员函数 说明 bool ios::good (void); 流可用,状态位全零,返回true bool ios::bad (void); badbit位是否被设置 bool ios::eof (void); eofbit位是否被设置 bool ios::fail (void); failbit位是否被设置 iostate ios::rdstate (void); 获取当状态 void ios::clear ( iostate s = ios::goodbit); 设置(复位)流状态 -
I/O流类对象支持到bool类型的隐式转换
- 发生1,2,4等情况,返回false,否则返回true
- 将I/O流对象直接应用到布尔上下文中,即可实现转换
-
处于1或4状态的流,在复位前无法工作
-
示例
#include <iostream> #include <fstream> using namespace std; // C++标准设计的类 -- ifstream(输入文件流)类 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { ifstream ifs2( "./ofs", ios::ate ); if( !ifs2 ) { // ! ifs2.operator bool() cerr << "ifs2流对象状态错误--打开文件失败" << endl; } cout << "------------第一次读文件-----------------------" << endl; int ii; double dd; string ss1,ss2; ifs2 >> ii >> dd >> ss1 >> ss2; if( !ifs2 ) { // ! ifs2.operator bool() cerr << "ifs2流对象状态错误--读文件失败" << endl; cerr << boolalpha << "ifs2是0状态吗?" << ifs2.good() << ", ifs2是1状态吗?" << ifs2.bad() << ", ifs2是2状态吗?" << ifs2.eof() << ", ifs2是4状态吗?" << ifs2.fail() << endl; cerr << "ifs2当前具体状态值:" << 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流对象状态错误--读文件失败" << endl; cerr << boolalpha << "ifs2是0状态吗?" << ifs2.good() << ", ifs2是1状态吗?" << ifs2.bad() << ", ifs2是2状态吗?" << ifs2.eof() << ", ifs2是4状态吗?" << ifs2.fail() << endl; cerr << "ifs2当前具体状态值:" << ifs2.rdstate() << endl; } cout << ii << ' ' << dd << ' ' << ss1 << ' ' << ss2 << endl; ifs2.close(); return 0; }
非格式化I/O
-
写入字符
- ostream& ostream::put (char ch);
- 一次向输出流写入一个字符,返回流本身
- ostream& ostream::put (char ch);
-
刷输出流
- ostream& ostream::flush (void);
- 将输出流缓冲区中的数据刷到设备上,返回流本身
-
读取字符
-
int istream::get (void);
- 成功返回读到的字符,失败或遇到文件尾返回EOF
-
istream& istream::get (char& ch);
- 返回输入流本身,其在布尔上下文中的值,成功为true,失败或遇到文件尾为false
-
示例
#include <iostream> #include <fstream> using namespace std; // C++标准设计的类 -- ofstream(输出文件流)类 / ifstream(输入文件流)类 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { ofstream ofs( "./noformat", ios::out ); if( !ofs ) { cerr << "ofs流对象状态错误--打开文件失败" << endl; } for( char c=' '; c<='~'; c++ ) { ofs.put(c).flush(); } ofs.close(); ifstream ifs( "./noformat", ios::in ); if( !ifs ) { cerr << "ifs流对象状态错误--打开文件失败" << endl; } char c; // 单参get while( 1 ) { ifs.get(c); if( ifs ) { cout << c; } else { break; // 利用ifs流对象状态错误来退循环 } } 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个字符还未读取定界符,第num个字符设置为’\0’,返回(输入流对象状态为4)
- 如果因为遇到定界符(缺省为‘\n’)返回(输入流对象状态为0)定界符被读取并丢弃,追加结尾空字符‘\0’,读指针停在该定界符的下一个位置
- 遇到文件尾,返回 (输入流对象状态为6)
-
示例
#include <iostream> #include <fstream> using namespace std; // C++标准设计的类 -- ifstream(输入文件流)类 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { ifstream ifs( "./getline", ios::in ); if( !ifs ) { cerr << "ifs流对象状态错误--打开文件失败" << 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流对象状态值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); // bbbb\n cout << buf << endl; cout << "ifs流对象状态值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); // cccccc\n cout << buf << endl; cout << "ifs流对象状态值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); // dddddddd\n cout << buf << endl; cout << "ifs流对象状态值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); // 0123456789\n cout << buf << endl; cout << "ifs流对象状态值:" << ifs.rdstate() << endl; ifs.getline( buf, 256, '\n' ); cout << buf << endl; cout << "ifs流对象状态值:" << ifs.rdstate() << endl; */ ifs.close(); return 0; }
-
二进制I/O
-
读取二进制数据
- istream& istream::read (char* buffer,streamsize num);
- 从输入流中读取num个字节到缓冲区buffer中
- 返回流对象本身,其在布尔上下文中的值,成功(读满)为true,失败(没读满)为false
- 如果没读满num个字节,函数就返回了,比如遇到文件尾,最后一次读到缓冲区buffer中的字节数,可以通过istream::gcount()函数获得
- istream& istream::read (char* buffer,streamsize num);
-
获取读长度
- streamsize istream::gcount (void);
- 返回最后一次从输入流中读取的字节数
- streamsize istream::gcount (void);
-
写入二进制数据
- ostream& ostream::write (const char* buffer,streamsize num);
- 将缓冲区buffer中的num个字节写入到输出流中
- 返回流本身,其在布尔上下文中的值,成功(写满)为true,失败(没写满)为false
- ostream& ostream::write (const char* buffer,streamsize num);
-
示例
#include <iostream> #include <fstream> using namespace std; // C++标准设计的类 -- ifstream(输入文件流)类 / ofstream(输出文件流)类 // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { ifstream ifs( "./getline", ios::ate ); if( !ifs ) { cerr << "ifs流对象状态错误--打开文件失败" << endl; } ofstream ofs( "./zjw", ios::out ); if( !ofs ) { cerr << "ofs流对象状态错误--打开文件失败" << 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; }
读写指针与随机访问
-
设置读/写指针位置
-
istream& istream::seekg (off_type offset,ios::seekdir origin);
ostream& ostream::seekp (off_type offset,ios::seekdir origin);
- origin表示偏移量offset的起点
- ios::beg:从文件的第一个字节
- ios::cur:从文件的当前位置
- ios::end:从文件最后一个字节的下一个位置
- offset为负/正表示向文件头/尾的方向偏移
- 读/写指针被移到文件头之前或文件尾之后,则失败
-
-
获取读/写指针位置
- pos_type istream::tellg (void);
pos_type ostream::tellp (void);- 返回读/写指针当前位置相对于文件头的字节偏移量
- pos_type istream::tellg (void);
字符串流
-
输出字符串流
#include <sstream> ostringstream oss; oss << 1234 << ' ' << 56.78 << ' ' << "ABCD"; string os = oss.str ();
-
输入字符串流
#include <sstream> string is ("1234 56.78 ABCD"); istringstream iss (is); int i; double d; string s; iss >> i >> d >> s;
格式化I/O
- 流函数(一组成员函数)
- I/O流类(ios)定义了一组用于控制输入输出格式的公有成员函数,调用这些函数可以改变I/O流对象内部的格式状态,进而影响后续输入输出的格式化方式
- 流控制符 ( 一组全局函数 )
- 标准库提供了一组特殊的全局函数,它们有的带有参数(在iomanip头文件中声明),有的不带参数(在iostream头文件中声明)
- 因其可被直接嵌入到输入输出表达式中,影响后续输入输出格式,故形象称之为流控制符
I/O流格式化函数
格式化函数 | 说明 |
---|---|
int ios::precision (int); | 设置浮点精度,返回原精度 |
int ios::precision (void) const; | 获取浮点精度 |
int ios::width (int); | 设置显示域宽,返回原域宽 |
int ios::width (void) const; | 获取显示域宽 |
char ios::fill (char); | 设置填充字符,返回原字符 |
char ios::fill (void) const; | 获取填充字符 |
long ios::flags (long); | 设置格式标志,返回原标志 |
long ios::flags (void) const; | 获取格式标志 |
long ios::setf (long); | 添加格式标志位,返回原标志 |
long ios::setf (long, long); | 添加格式标志位,返回原标志 先用第二个参数将互斥域清零 |
long ios::unsetf (long); | 清除格式标志位,返回原标志 |
- 一般而言,对I/O流格式的改变都是持久的,即只要不再设置新格式,当前格式将始终保持下去
- 显示域宽是个例外,通过ios::width(int)所设置的显示域宽,只影响紧随其后的第一次输出,再往后的输出又恢复到默认状态
I/O流格式标志
格式标志位 | 互斥域 | 说明 |
---|---|---|
ios::left | ios::adjustfield | 左对齐 |
ios::right | ios::adjustfield | 右对齐 |
ios::internal | ios::adjustfield | 数值右对齐,符号左对齐 |
ios::dec | ios::basefield | 十进制 |
ios::oct | ios::basefield | 八进制 |
ios::hex | ios::basefield | 十六进制 |
ios::fixed | ios::floatfield | 用定点小数表示浮点数 |
ios::scientific | ios::basefield | 用科学计数法表示浮点数 |
格式标志位 | 说明 |
---|---|
ios::showpos | 正整数前面显示+号 |
ios::showbase | 显示进制前缀0或0x |
ios::showpoint | 显示小数点和尾数0 |
ios::uppercase | 数中字母显示为大写 |
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 | 数值右对齐,符号左对齐 | √ | |
dec | 十进制 | √ | √ |
oct | 八进制 | √ | √ |
hex | 十六进制 | √ | √ |
fixed | 用定点小数表示浮点数 | √ | |
scientific | 用科学计数法表示浮点数 | √ | |
(no)showpos | 正整数前面(不)显示+号 | √ | |
(no)showbase | (不)显示进制前缀0或0x | √ | |
(no)showpoint | (不)显示小数点和尾数0 | √ | |
(no)uppercase | 数中字母(不)显示为大写 | √ | |
(no)boolalpha | (不)用字符串表示布尔值 | √ | √ |
(no)unitbuf | (不)每次插入都刷流缓冲 | √ | |
(no)skipws | (不)以空白字符作分隔符 | √ | |
ws | 跳过前导空白字符 | √ | |
ends | 空字符 | √ | |
endl | 换行符,刷流缓冲 | √ | |
flush | 刷流缓冲 | √ | |
setprecision (int) | 设置浮点精度 | √ | |
setw (int) | 设置显示域宽 | √ | |
setfill (int) | 设置填充字符 | √ | |
setiosflags (long) | 设置格式标志 | √ | √ |
resetiosflags (long) | 清除格式标志 | √ | √ |
-
示例
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++语言各版本目标
- C++98的目标:比C强、要抽象、OO、泛型
- C++03的目标:修补98的bug
- C++11的目标:库、更简单、效率更高
-
编译时使用11标准
g++ filename.cpp -std=c++11
类型推导
-
auto关键字
-
C++98中,auto表示栈变量,通常省略不写
void foo(void) {
int i;
auto int j;//表示在栈里分配的
}
-
C++11中,给auto赋予新的语义,表示自动类型推导
-
既根据对变量进行初始化时所使用的数据的类型,由编译器自动推导出所定义变量的实际类型
-
例如:
auto i=10;–> int i = 10;
auto j=i; --> int j = i;
-
-
-
auto类型推断本质: auto1.cpp
-
按照定义独立对象并根据初始化数据的类型进行推导
-
无法自动推断 const,只能在auto的上下文显示指明
-
如果给出的 初始化数据类型为常量指针,则可以自动推导const
-
示例
// 类型推导 绝对不是 类型照抄 #include <iostream> #include <typeinfo> 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; // 如果初始化数据为常指针(const int*),则可以自动推导出const(第一种情况) // auto: const int* f: const int* cout << "f的类型:" << typeid(f).name() << endl; // PKi // *f = 888; // 不允许更改 f = NULL; return 0; }
-
-
auto与引用、联用
-
按照定义独立对象并根据初始化数据的类型进行推导,所以不可能推导出引用
-
除非auto的上下文指明按照引用推导
-
若指明按引用推导并且目标带有常属性,则可以自动推导const
-
示例
// 类型推导 和 引用 的联合使用 #include <iostream> #include <typeinfo> 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; // 指明按引用推导,目标带有常属性 则可以自动推导出const(第二种推导const的情况) // auto: const int e: const int& cout << "e的类型:" << typeid(e).name() << endl; // i cout << "&e:" << &e << ", &b:" << &b << endl; // 地址相同 // e++; // 不允许更改 return 0; }
-
-
auto关键字的使用限制
-
函数形参类型无法推导(C++14标准支持)
-
类的成员变量无法推导
-
示例
// 类型推导 的 局限 #include <iostream> #include <typeinfo> using namespace std; /* void foo( auto v ) { // 11标准不支持形参类型推导,但14标准支持 // ... } */ class A { public: auto i; // 声明 auto j; // 声明 }; // 以上代码模拟类的设计者(标准库提供的类,第三方提供的类,自己设计的类) // --------------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { // foo( 10 ); // foo( 3.14 ); return 0; }
-
类型计算
-
类型计算分类
- C语言 : sizeof - 计算类型的大小
- C++语言:typeid - 可以获取类型的信息字符串
- C++11 : decltype - 获取参数表达式的类型
- 注意:类型计算由编译器确定,并不是运行期确定
-
与类型推导相比
-
对类型的确定更加精准
const int a = 10;
auto b = a;//b类型推导为int
decltype(a) c = a; //c类型计算为 const int
-
可以做到类型相同但值不同
const int a = 10;
auto b = a;
decltype(a) c = 100;
-
示例
// 类型计算 和 类型推导 的对比 // (1) 类型计算 比 类型推导 在类型的确定更加精准 // (2) 类型计算 比 类型推导 在初始值的确定上 更加灵活 #include <iostream> #include <typeinfo> 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; }
-
-
三种类型计算的规则
-
标识符表达式,直接取表达式的类型
int a;
decltype(a) ->int
-
函数表达式,取函数返回值的类型
int foo(int,int);
decltype(foo(10,20))->int
-
其他表达式,如果表达式的值为左值,取该左值引用的类型,如果表达式的值为右值,取该右值本身的类型
-
示例
// 类型计算 是 编译器计算类型 不是运行时计算类型 #include <iostream> #include <typeinfo> using namespace std; double foo( int x, float y ); int main( void ) { int a = 10; // 如果给decltype传递的为标识符表达式,decltype取该标识符的类型作为最终计算出的类型 decltype(a) c = a; // c: int cout << "c的类型:" << typeid(c).name() << endl; // i cout << "&c:" << &c << ", &a:" << &a << endl; // 地址不同 c++; // 允许更改 // 如果给decltype传递的为函数表达式,decltype取该函数的返回值类型作为最终计算出的类型 decltype(foo(3,6.1)) d = a; // d: double cout << "d的类型:" << typeid(d).name() << endl; // d cout << "&d:" << &d << ", &a:" << &a << endl; // 地址不同 d++; // 允许更改 // 如果decltype传递的为其他表达式,并且表达式的结果为左值,decltype取该左值引用类型作为最终计算出的类型 decltype(++a) e = a; // e: int& cout << "e的类型:" << typeid(e).name() << endl; // i cout << "&e:" << &e << ", &a:" << &a << endl; // 地址相同 e++; // 允许更改 // 如果decltype传递的为其他表达式,并且表达式的结果为右值,decltype取该右值本身类型作为最终计算出的类型 decltype(a++) f = a; // f: int cout << "f的类型:" << typeid(f).name() << endl; // i cout << "&f:" << &f << ", &a:" << &a << endl; // 地址不同 f++; // 允许更改 return 0; }
-
-
返回值后置
-
auto 函数名(形参表)->decltype(表达式)
-
示例
// 返回值类型 后置 #include <iostream> #include <typeinfo> 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; }
-
列表初始化
-
初始化方法纷繁复杂
-
等号初始化 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};
-
-
初始化方法大一统 之 列表初始化
-
基本类型,类类型,结构/联合/枚举类型等等的 单个对象 或对象数组,都可以采用形式完全统一的列表初始化语法进行对象的初始化
-
书写形式:类型 对象{初值表};
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};
-
-
示例
// 初始化大一统 --- 列表初始化 #include <iostream> 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} {}//形参表中不可以用{}代替=,默认值不是初始值,{}仅用于初始化 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,"关羽"}; // 定义h,利用h.Human(25,"关羽") //(25, "关羽"); 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表达式
引语
-
C++语法中函数作用域中不能再定义函数,因此没有所谓局部函数的概念,以下写法是不允许的
void foo(void){ ... int bar(int x, int y){ reutrn x + y; } ... cout << bar(100,200) << endl; ... }
但 函数作用域 中 可以有 类型, 当然也可以有表达式
-
示例
// 函数内部 不可以 有函数 #include <iostream> #include <typeinfo> using namespace std; /* void foo( int x, double y ) { int a; // ok cout << 2*a << endl; // ok void bar() { // error cout << "bar()" << endl; } } */ // 编译器针对这种情况(函数内部有类型)如何工作:(1)先编函数内部的类型 (2)再编函数本身的代码 /* 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 } }; } */ // 编译器针对这种情况(函数内部有类型)如何工作:(1)先编函数内部的类型 (2)再编函数本身的代码 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; }
-
函数操作符函数
该函数无一般用法,可根据自己的需求定义该函数
-
示例
// 小括号操作符 函数 #include <iostream> 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; }
使用
-
语法规则
[捕获表](参数表) 选项 -> 返回类型
{
函数体;
};
-
表像:lambda表达式的名称是一个表达式(外观类似函数),但本质绝非如此
-
本质:lambda表达式本质其实是一个类, 并且最终返回值为这个类的对象,因此对lambda表达式的调用就是该对象的函数操作符的调用
-
可以没有返回值类型,将根据return推断
-
如果连return也没有,则返回值为void
-
参数为void可以省略不写的
-
示例
// lambda表达式 #include <iostream> #include <typeinfo> 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; }; // 定义 // 编译器根据lambda表达式 (1)生成一个类 (2)类内定义小括号操作符函数 (3)返回这个类匿名对象 /* 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; // 可以没有返回值类型,将根据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表达式 -- 捕获表 #include <iostream> #include <typeinfo> 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函数形参c,而是XX类的成员变量c void operator()(int d=40) { cout << "c=" << c << endl; // 这里的c并不是foo函数形参c,而是XX类成员变量c } private: const int c; //这里的c并不是foo函数形参c,而是XX类成员变量c }; XX{c}(); // 这里的c就是foo函数形参c */ cout << "------------[&c]--------------" << endl; [&c](int d=40) { cout << "c=" << ++c << endl; }(); /* class XXXX { public: XXXX(int& m) : c(m) {} // 这里的c并不是foo函数形参c,而是XXXX类成员变量c void operator()(int d=40) { cout << "c=" << c << endl; // 这里的c并不是foo函数形参c,而是XXXX类成员变量c } private: int& c; // 这里的c并不是foo函数形参c,而是XXXX类成员变量c }; XXXX{c}(); // 这里的c就是foo函数的形参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
-
-
有没有常右值引用呢?
- 其实有的,只是没有必要,因为常右值引用,完全可以被常左值引用替代
-
示例
// 左值引用 和 右值引用 #include <iostream> 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, 利用右值引用 引用 右值,可以通过右值引用修改目标 // 右值引用不可以引用左值。 // int&& f = a; // error // 常左值引用可以引用左值 const int& g = a; // ok // 常左值引用可以引用右值 const int& h = /*|30|*/ a + c; // ok // h = 888; // error, 利用常左值引用 引用 右值,不能通过常左值引用修改目标 return 0; }
移动语义
-
方法
- 资源的转移 代替 资源的重建
-
作用
- 保证功能正确的情况下,做到性能提升
-
示例
编译时不要编译器优化:g++ filename.cpp -std=c++11 -fno-elide-constructors
// 默认拷贝构造 以及 默认拷贝赋值,在某些特定场景(类中有指针型成员)有(浅拷贝)缺陷 #include <iostream> #include <cstring> using namespace std; // 模拟C++标准库的string类,设计一个自己的String类 class String { public: String( const char* psz="" ) : m_psz(new char[strlen(psz)+1]) { //【char* m_psz=new char[...];】定义m_psz,初值为指向一块堆内存(动态资源) strcpy( m_psz, psz ); } ~String( /* String* this */ ) { delete[] this->m_psz; this->m_psz = NULL; // 释放 m_psz 本身所占内存空间 } char* c_str() const { return m_psz; } // 深拷贝构造函数(体现的是 资源的重建) String( const String& that ) : m_psz(new char[strlen(that.m_psz)+1]) { //【char* m_psz=new char[...];】 strcpy( m_psz, that.m_psz ); // 复制了数据,不复制地址-->深拷贝 cout << "String类深拷贝构造函数被调用" << endl; } // 转移构造函数(体现的是 资源的转移) String( String&& that ) : m_psz(that.m_psz) { //【char* m_psz = that.m_psz;】 that.m_psz = NULL; cout << "String类的转移构造函数被调用" << endl; } // 深拷贝赋值函数(体现的是 资源的重建) String& operator=( /* String* this */ const String& that ) { cout << "String类的深拷贝赋值函数被调用" << 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 ); // 拷贝新内容 } return *this; // 返回自引用 } // 转移赋值函数(体现的是 资源的转移) String& operator=( /* String* this */ String&& that ) { cout << "String类的转移赋值函数被调用" << 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; } // 以上代码模拟类的设计者 // ---------------------------- // 以下代码模拟类的使用者(用户) int main( void ) { String s1("hello"); cout << "------------1---------------" << endl; String s2 = s1; // 定义s2,利用s2.String(s1)-->触发深拷贝构造函数 cout << "------------2---------------" << endl; String s3 = String("world"); // 定义s3,利用s3.String( String("world") )-->触发转移构造函数 cout << "------------3---------------" << endl; String s4; s4 = s3; // s4.operator=(s3)-->触发深拷贝赋值函数 cout << "------------4---------------" << endl; s4 = String("hello world"); // s4.operator=( String("hello world") )-->触发转移赋值函数 return 0; }
推荐书目
- 入门
- C++ 程序设计原理与实践 - Bjarne Stroustrup
- C++ Primer - Stanley B. Lippman
- 进阶
- Effective C++: 改善程序与设计的55个具体做法 - Scott Meyers
- More Effective C++: 35个改善编程与设计的有效方法 - Scott Meyers
- 深研
- 深度探索C++对象模型 - Stanley B. Lippman
- 设计模式:可复用面向对象软件的基础 - Erich Gamma, Richard Helm, …
- 拓展
- 深入理解C++11: C++11新特性解析与应用 - IBM XL编译器中国开发团队
- Boost程序库完全开发指南:深入C++“准”标准库 - 罗剑锋
- 休闲
- C++语言99个常见编程错误 - Stephen C. Dewhurst
- C++语言的设计与演化 - Bjarne Stroustrup
- 大话设计模式 - 程杰
高级语法特性
模板
前言
- C++为强类型语言
- 优点:有很多的数据类型(基本类型、类类型)在类型安全性方面是无可比拟的
- 缺点:因为数据类型的多样性,在很大程度上会给程序员编写通用代码带来麻烦
- 它使程序员需要为每一种数据类型编写完全相同或者近乎完全相同的代码,即便它们在抽象层面是一致的
函数模板
-
函数模板定义
-
语法
template<class|typename 类型形参1, class|typename 类型形参2,…> 返回值类型 函数模板名(调用形参1,调用形参2,…) {
…
}
template T Max(T x, T y) {
return x > y ? x : y;
}
-
注意
- 可以使用任何标识符作为类型形参的名称,但使用“T”已经称为一种惯例,“T”表示的是,调用者在使用函数模板时指定的任意类型
-
-
函数模板使用
-
使用函数模板必须对函数模板进行实例化
-
语法
函数模板名<类型实参1,类型实参2,…>(调用实参1,…);
Max(123,456);
Max(12.3,45.6);
Max(“hello”,“world”);
-
-
函数模板分析
-
编译器并没有把函数模板翻译成一个可以处理任何数据类型的单一实体
-
编译器在实例化函数模板时根据类型实参从函数模板中产生一个真正的函数实体
-
函数模板并不是一个函数实体,通过实例化才能产生真正的函数实体
-
函数模板可以看成是编译器生成函数实体的一个依据而已
-
这种用具体数据类型替换函数模板类型形参的过程叫做实例化,这个过程将产生一个函数模板的实例(函数实体)
-
示例
// 函数模板 并不是 真正的函数 #include <iostream> 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<typename T> T Max( T x, T y ) { 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<Integer>(ix,iy) << endl; int nx=10, ny=20; cout << Max<int>(nx,ny) << endl; // Max(nx,ny) double dx=1.23, dy=4.56; cout << Max<double>(dx,dy) << endl; // Max(dx,dy) string sx="world", sy="hello"; cout << Max<string>(sx,sy) << endl; // Max(sx,sy) return 0; }
-
-
实例化函数模板的条件
- 原则上来说可以使用任何类型来实例化函数模板,不管其为基本类型还是类类型
- 但前提是这个类型必须支持函数模板所要执行的操作
-
隐式推断类型实参
-
概念
-
若函数模板的调用形参和类型形参相关,那么在实例化函数模板时即使不显式指明类型实参,编译器也有能力根据调用实参的类型隐式推断出类型实参的类型。例如:
声明:templateT Max(T x,T y){…}
实例化:Max(123,456); -> Max(123,456);
-
-
目的
- 获得和调用普通函数一致的书写形式
-
三种情况不能做隐式推断
-
调用参数和类型参数不完全相关
template<class T,class D>void Foo(D x){}
-
隐式推断类型实参 不能同时 隐式类型转换
声明:template T Max(T x, T y){…}
实例化: Max(123,45.6);
-
返回值类型 不能 隐式推断
-
-
示例
// 隐式推断 函数模板的类型实参 #include <iostream> using namespace std; template<typename T> T Max( T x, T y ) { return x > y ? x : y; } template<typename T, typename D>void Foo( D x ) { // 类型形参 和 调用形参 不完全相关 } template<typename R, typename T> R Bar( T x ) { R r; return r; } int main( void ) { Bar<float>(100); // 返回值类型 无法隐式推断 Max( 123, (int)45.6 ); // 隐式推断类型实参 的同时 不能做 隐式转换 Foo<double>(100); int nx=10, ny=20; cout << Max(nx,ny) << endl; // Max<>(nx,ny) --> Max<int>(nx,ny) double dx=1.23, dy=4.56; cout << Max(dx,dy) << endl; // Max<>(dx,dy) --> Max<double>(dx,dy) string sx="world", sy="hello"; cout << Max(sx,sy) << endl; // Max<>(sx,sy) --> Max<string>(sx,sy) return 0; }
-
-
函数模板重载
-
普通函数和能够实例化出该函数的函数模板构成重载关系
- 在调用参数类型匹配度相同情况下编译器优先选择普通函数
- 除非函数模板可以产生调用参数类型匹配度更好的函数
-
隐式推断类型实参不能同时隐式类型转换 但 普通函数可以
- 在传递参数时如果需要编译器做隐式类型转换,则编译器选择普通函数
-
可以在实例化时用<>强行通知编译器选择函数模板
-
示例
// 函数模板的重载 #include <iostream> using namespace std; void Max( int x, int y ) { // 普通函数 cout << "1. Max(int,int)" << endl; } template<typename T>void Max( T x, T y ) { // 函数模板 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; }
-
-
函数模板类型形参缺省值
-
在实例化函数模板时,如果提供了类型实参则用所提供的类型实参来实例化函数模板
-
在实例化函数模板时,如果没提供类型实参则用类型形参的缺省类型来实例化函数模板
-
如果某一个类型形参带有缺省类型,则其后的类型形参都必须带有缺省类型
-
11标准支持
-
示例
// 函数模板 的 默认类型(C++11标准开始支持) #include <iostream> #include <typeinfo> using namespace std; template<typename T=short, typename D=float> void foo() { T t; D d; cout << "t的类型:" << typeid(t).name() << ",d的类型:" << typeid(d).name() << endl; } int main( void ) { foo(); foo<int,double>(); return 0; }
-
类模板
-
类模板定义
-
形式
template<tyepname 类型形参1,…> class 类模板名{
…
};
template<typename A, typename B> class CMath {
public:
A m_a;
B func(){…}
};
-
在类模板外实现成员函数
template<typename 类型形参1,…> 返回值类型 类模板名<类型形参1,…>::函数名(调用形参1,…) { … };
template< typename A, typename B> B CMath<A,B>::func(){
…;
};
-
-
类模板的使用
-
使用类模板必须对类模板进行实例化(产生真正的类)
- 类模板本身并不代表一个确定的类型(即不能用于定义对象)
- 只有通过类型实参实例化成真正的类后才具备类的特性(即可以定义对象)
- 如:CMath<int,double> math;
-
示例
// 类模板(并不是类) #include <iostream> #include <typeinfo> using namespace std; template<typename T> 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<typename T> T CMath<T>::add() { return m_t1 + m_t2; } // class CMath<int> {...}; // class CMath<double> {...}; // class CMath<string> {...}; int main( void ) { int nx=10, ny=20; CMath<int> m1(nx,ny); cout << m1.add() << endl; double dx=1.23, dy=4.56; CMath<double> m2(dx,dy); cout << m2.add() << endl; string sx="hello", sy=" world"; CMath<string> m3(sx,sy); cout << m3.add() << endl; return 0; }
-
-
成员函数的延迟实例化
-
类模板被实例化产生真正类的一刻,类模板中的成员函数并没有实例化, 成员函数只有在被调用时才会被实例化(即产生真正成员函数),称之为成员函数的延迟实例化,但成员虚函数除外
-
某些类型虽然并没有提供类模板所需要的全部功能但照样可以用它来实例化类模板,只要不调用那些未提供功能的成员函数即可
-
示例
// 类模板 成员函数的 延迟实例化 #include <iostream> #include <typeinfo> 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<typename T> 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<int> { public: CMath<int>( int t1, int t2 ) : m_t1(t1), m_t2(t2) {} int add() { return m_t1 + m_t2; } private: int m_t1; int m_t2; }; */ /* class CMath<Integer> { public: CMath<Integer>( Integer t1, Integer t2 ) : m_t1(t1), m_t2(t2) {} Integer add() { return m_t1 + m_t2; } private: Integer m_t1; Integer m_t2; }; */ int main( void ) { Integer ix(100), iy(200); CMath<Integer> m2(ix,iy); m2.add(); int nx=10, ny=20; CMath<int> m(nx,ny); m.add(); return 0; }
-
-
类模板的静态成员
-
类模板中的静态成员既不是每个对象拥有一份也不是类模板拥有一份,应该是由类模板实例化出的每一个真正的类各自拥有一份,且为该实例化类定义的所有对象共享
-
示例
// 类模板 的 静态成员:不是类模板拥有,也不是类对象拥有,而是实例化出来的每个类各自拥有,并被该类的所有对象共享 #include <iostream> #include <typeinfo> using namespace std; template<typename T> class A { public: static T m_t; // 声明 static void foo() { cout << "A<T>::foo()" << endl; } }; template<typename T> T A<T>::m_t = T(); // 定义 并 零值初始化 int main( void ) { A<int> x, y; cout << "&A<int>::m_t: " << &A<int>::m_t << endl; cout << "&x.m_t: " << &x.m_t << endl; // &A<int>::m_t cout << "&y.m_t: " << &y.m_t << endl; // &A<int>::m_t A<int>::foo(); A<double> m, n; cout << "&A<double>::m_t: " << &A<double>::m_t << endl; cout << "&m.m_t: " << &m.m_t << endl; // &A<double>::m_t cout << "&n.m_t: " << &n.m_t << endl;; // &A<double>::m_t A<double>::foo(); return 0; }
-
-
类模板的递归实例化
-
概念
- 利用类模板实例化产生的类来实例化类模板自身,这种做法称之为类模板的递归实例化
-
应用
- 通过这种方法可以构建空间上具有递归特性的数据结构(例如:多维数组),如Array< Array >
-
示例
// 类模板 的 递归实例化 #include <iostream> #include <iomanip> using namespace std; template<typename T> class Array { public: T& operator[](size_t i) { return arr[i]; } private: T arr[10]; }; // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { Array< Array<int> > s; // 类模板的递归实例化 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<int> a; // a对象当成一维数组看待 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; }
-
-
类模板类型形参缺省值
-
在实例化类模板时,如果提供了类型实参则用所提供的类型实参来实例化类模板
-
在实例化类模板时,如果没提供类型实参则用类型形参的缺省类型来实例化类模板
-
如果某一个类型形参带有缺省类型,则其后的类型形参都必须带有缺省类型
-
示例
// 类模板 类型形参 的 默认类型(98/03就支持) #include <iostream> #include <typeinfo> using namespace std; template<typename T=short, typename D=float> 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; }; // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { CMath<int, double> m; m.print(); CMath<> m2; m2.print(); CMath<int> m3; m3.print(); return 0; }
-
类模板扩展
-
模板型成员变量
-
成员变量,但其类型是由类模板实例化的未知类,称之为模板型成员变量
templateclass Arrary{…};
templateclass Sum{
public:
Arrary m_s; // 模板型成员变量
}
-
示例
// 模板型 成员变量 : 1. 成员变量 2.它的类型必须是由类模板实例化出的未知类 #include <iostream> using namespace std; template<typename T> class Array { public: T& operator[](size_t i) { return arr[i]; } private: T arr[10]; }; template<typename D>class Sum { public: Sum( const Array<D>& v ) : m_a(v) {} D add() { // 求和器 D d = D(); for( int i=0; i<10; i++ ) { d += m_a[i]; } return d; } private: Array<D> m_a; // 模板型成员变量 }; // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { Array<int> a; for( int i=0; i<10; i++ ) { a[i] = i+1; } Sum<int> s(a); cout << s.add() << endl; Array<string> sa; for( int i=0; i<10; i++ ) { sa[i] = "hello"; } Sum<string> ss(sa); cout << ss.add() << endl; return 0; }
-
-
模板型成员函数
-
模板型成员函数 又名 成员函数模板
template class A {
public:
template< typename D>void foo() {…} // 成员函数模板
};
在类外实现
template< typename T>template< typename D>
void A::foo() { … }
-
示例
// 模板型成员函数 又名 成员 函数模板 #include <iostream> using namespace std; template<typename T>class CMath { public: template<typename D>void foo(); // 声明 }; // (1) 帽子不能丢 (2) 类名::XX成员 template<typename T> template<typename D> void CMath<T>::foo() { cout << "CMath<T>::foo<D>()" << endl; } // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { CMath<int> m; m.foo<double>(); return 0; }
-
-
模板型成员类型
-
模板型成员类型 又名 成员类模板
templateclass A{
public:
templateclass B{…}; // 模板型成员类型
};
-
示例
// 模板型成员类型 又名 成员类模板 #include <iostream> using namespace std; template<typename X>class A { public: template<typename Y>class B { // 成员 类模板 public: /* template<typename Z> void foo() { // 成员 函数模板 cout << "foo" << endl; } */ template<typename Z> void foo(); // 声明 }; }; template<typename X> template<typename Y> template<typename Z> void A<X>::B<Y>::foo() { // 定义 cout << "foo" << endl; } // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { A<int>::B<double> b; b.foo<float>(); return 0; }
-
-
类模板中成员虚函数
-
类模板中可以有虚函数
- 类模板中可以定义成员虚函数,和普通类的成员虚函数一样,类模板的成员虚函数也可以表现出多态性
-
类模板中不可以有成员虚函数模板
- 根据成员虚函数的多态机制,需要一个虚函数表(表中保存成员虚函数的入口地址),而这个表是编译器在实例化类模板时就创建,成员函数模板的实例化(即产生真正的函数实体)需要编译器处理完调用后才会完成,这时才出现成员虚函数的地址
-
总结
- 成员函数模板的延迟实例化,阻碍了虚函数表的构建
-
示例
// 类模板中 可以出现 虚函数 (也能表现出多态) #include <iostream> using namespace std; template<typename T>class Base { public: virtual void foo() { cout << "Base<T>::foo()" << endl; } template<typename X>void bar() {} // 成员 函数模板 不可以是 虚的 }; template<typename T, typename D>class Derived : public Base<T> { public: void foo() { cout << "Derived<T,D>::foo()" << endl; } }; // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { Derived<int,double> d; // 一旦将子类模板实例化为真正的类Derived<int,double>,意味着 基类模板也实例化为真正的类Base<int> Base<int>* pBase = &d; pBase->foo(); // 可以表现出 多态 pBase->bar<int>(); // 编译器在这个时,才会帮我们产生真正的bar函数 return 0; }
-
模板特殊用法
-
数值型的类型形参
-
模板的类型形参也可以是是数值类型(只能是整数),可以有缺省值
template<typename T,int S=10> class Arrary{…};
template< typename T, int S=10>void foo() { … }
-
示例
// 模板的类型形参 也可以是 数值 #include <iostream> using namespace std; template<typename T, int S=10> class Array { public: T& operator[](size_t i) { return arr[i]; } int size() { return S; } private: T arr[S]; }; // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { Array<int> a; // a对象当成一维数组看待 for( int i=0; i<a.size(); i++ ) { a[i] = 1000+i; } for( int i=0; i<a.size(); 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 <iostream> using namespace std; template<typename T> class Array { public: T& operator[](size_t i) { return arr[i]; } private: T arr[10]; }; template<typename D, template<typename M>class C=Array >class Sum { public: Sum( const C<D>& v ) : m_a(v) {} D add() { // 求和器 D d = D(); for( int i=0; i<10; i++ ) { d += m_a[i]; } return d; } private: C<D> m_a; // Array<D> m_a; // 模板型成员变量 }; // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { Array<int> a; for( int i=0; i<10; i++ ) { a[i] = i+1; } Sum<int> s(a); cout << s.add() << endl; Array<string> sa; for( int i=0; i<10; i++ ) { sa[i] = "hello"; } Sum<string> ss(sa); cout << ss.add() << endl; return 0; }
-
模板二次编译
-
编译器对模板会进行两次编译
-
第一次编译发生在实例化模板之前(产生真正函数或真正类之前)只检查模板本身内部代码(只检查基本词法是否正确)
- 模板内部出现的所有标识符是否均有声明
- 对于已知类型的调用要检查调用是否有效
- 对于未知类型调用认为都合理
-
第二次编译发生在实例化模板之后(产生真正函数或真正类之后)结合所使用的类型实参,再次检查模板代码,查看所有调用是否真的都有效
-
示例
// 二次编译( 第一次编译过不去,谈不上第二次编译 ) #include <iostream> using namespace std; class A { public: void foo() { cout << "A::foo" << endl; } }; template<typename T>void func() { // abc = 10; // 第一次编译 error A a; a.foo(); // 第一次编译 ok // a.sdf(); // 第一次编译 error // 第一次编译时,编译器针对未知类型调用采取隐忍态度,尽量认为都合理 // 第二次编译时,编译器结合类型实参,再次检查 调用 是否这的合理 T t; t.foo(); // 第一次编译 ok, 第二次编译 ok // t.fdsjfdjflds(); // 第一次编译 ok, 第二次编译 error // t.ffdjk<>fd(); // 第一次编译 error } // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { func<A>(); return 0; }
模板典型错误
-
嵌套依赖
-
问题
- 由于模板要经过两次编译,在第一次编译模板的代码时,类型形参的具体类型尚不明确,编译器将把类型形参的嵌套类型理解为某个未知类型的静态成员变量,因此编译器看到使用这样的标识符声明变量时会报告错误,这就叫嵌套依赖
-
解决方法
- 在类型形参的前面增加一个 typename 标识符,意在告诉编译器其后是嵌套类的使用
-
示例
// 嵌套依赖 #include <iostream> using namespace std; class A { public: class B { public: void foo() { cout << "A::B::foo" << endl; } }; }; template<typename T>void func() { typename T::B b; // typename出现在这里,就是 告诉编译器 B是T这个未知类的 嵌套类型 b.foo(); } // 以上代码模拟类模板的设计者 // ------------------- // 以下代码模拟用户 int main( void ) { func<A>(); return 0; }
-
-
利用类型形参访问成员函数模板
-
问题
- 利用未知类定义的对象来访问成员函数模板时,编译器在第一次编译时无法解析成员函数模板的类型参数列表的<>而报告编译错误
-
解决方法
- 在成员函数模板之前增加template关键字,意在告诉编译器其后是一个函数模板实例,编译器就可以正确理解<>了
-
示例
// 利用 类型形参 访问 成员函数模板 #include <iostream> using namespace std; class A { public: template<typename D>void foo() { cout << "A::foo<D>()" << endl; } // 成员函数模板 }; template<typename T>void func() { T t; t.template foo<int>(); // template出现在这里 就是 告诉编译器 后面是一个函数模板实例化 } int main( void ) { func<A>(); return 0; }
-
-
子类模板访问基类模板
-
问题
- 在子类模板中访问基类模板的成员,编译器第一次编译时只在子类模板和全局域中搜索使用的标识符号,不会到基类模板中搜索
-
解决方法
- 在子类模板中可以通过使用作用域限定符或显式使用this指针
-
示例
// 子类模板中 访问 基类模板的成员 #include <iostream> using namespace std; template<typename T>class Base { public: int m_i; void foo() { cout << "Base<T>::foo" << endl; } }; //int m_i; //void foo() {} template<typename T, typename D>class Derived : public Base<T> { public: // int m_i; // void foo() {} void bar( /* Derived<T,D> *this */ ) { Base<T>::m_i = 100; Base<T>::foo(); this->m_i = 200; this->foo(); // 以上 四行代码都为 未知类型调用,编译器第一次编译时 隐忍 } }; int main( void ) { Derived<int,double> d; // Derived<int,double> Base<int> return 0; }
-
STL
STL - Standard Template Library (标准模板库)
自定义双链表容器演示
#include <iostream>
using namespace std;
template<typename T>class list {
public:
//
// 缺省构造
//
list() : m_head(NULL), m_tail(NULL) {}
//
// 拷贝构造
//
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);
}
}
//
// 析构函数
//
~list() {
clear();
}
//
// 链表判空
//
bool empty() const {
return m_head==NULL && m_tail==NULL;
}
//
// 添加头节点
//
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;
}
//
// 添加尾节点
//
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;
}
//
// 删除头节点
//
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;
}
//
// 删除尾节点
//
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;
}
//
// 获取头节点数据
//
T front() const throw(out_of_range) {
if( empty() )
throw out_of_range("null node");
return m_head->m_data;
}
//
// 获取尾节点数据
//
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:
//
// 节点类
//
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; // 当前指向
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) { // 重点体会为什么在这里加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;
};
//
// 创建非常起始迭代器(用于遍历)
//
iterator begin() {
return iterator(m_head,m_head,m_tail);
}
//
// 创建非常终止迭代器(结束标识)
//
iterator end() {
return iterator(m_head,NULL,m_tail);
}
//
// 创建常起始迭代器(用于遍历)
//
const_iterator begin() const {
return const_iterator(m_head,m_head,m_tail);
}
//
// 创建常终止迭代器(结束标识)
//
const_iterator end() const {
return const_iterator(m_head,NULL,m_tail);
}
//
// 在迭代器指向的位置添加节点
//
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;
}
}
//
// 删除迭代器指向的节点
//
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<typename IT, typename T> IT find( const IT& beg, const IT& end, const T& data ) {
for( IT it=beg; it!=end; ++it ) {
if( *it==data ) {
return it;
}
}
return end;
}
//
// 利用"<"实现的排序
//
template<typename IT>void sort( const IT& beg, const IT& end ) {
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 );
}
}
//
// 容器设计者提供比较类
//
template<typename T>class Greater {
public:
bool operator()( T x, T y) {
return x > y;
}
};
//
// 利用"比较器"实现的排序
//
template<typename IT, typename CMP>void sort( const IT& beg, const IT& end, CMP cmp ) {
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 );
}
}
// 以上代码容器的设计者
// -----------------------
// 以下代码容器的使用者
void Print( const string& str, const list<int>& l ) {
cout << str << endl;
typedef list<int>::const_iterator CIT;
for( CIT it=l.begin(); it!=l.end(); ++it ) {
cout << *it << ' ';
}
cout << endl << "----------------------------" << endl;
}
// 用户设计比较类
class ZJW {
public:
bool operator()( int x, int y ) {
return x < y;
}
};
int main( void ) {
list<int> ls; // 非常容器(非常对象)
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( "添加头尾节点后:", ls );
ls.pop_front();
ls.pop_back();
Print( "删除头尾节点后:", ls );
ls.insert( ++ls.begin(), 888 ); // 增
Print( "迭代器指向的位置添加节点后:", ls );
ls.erase( ----ls.end() ); // 删
Print( "删除迭代器指向的节点后:", ls );
typedef list<int>::iterator IT;
IT it = ls.begin();
*it = 999; // 改
Print( "修改迭代器指向的节点后:", 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() ); // 利用 用户自己设计的比较器
sort( ls.begin(), ls.end(), Greater<int>() ); // 利用 容器设计者提供的比较器
Print( "排序后:", ls );
const list<int> cls = ls; // 常容器(常对象)
Print( "常容器:", cls );
return 0;
}
线性容器
基本容器
向量
vector
-
常用函数
函数 功能 front() 获取首元素 back() 获取尾元素 insert() 插入元素 erase() 删除元素 push_back() 添加尾元素 pop_back() 删除尾元素 empty() 判空 clear() 清空 size() 向量维护元素个数 resize() 设置向量元素个数 capacity() 获取向量容量 reserve() 设置向量的容量 -
向量维护的内存空间会随着新元素的增加而自动增长
-
如果内存空间无法满足新元素的增加,向量会开辟新的足够的连续的内存空间,并把原内存空间的数据复制到新的内存空间,释放原内存空间
-
向量的增加会伴随着内存空间的分配和释放,元素复制和销毁等额外开销,如果能够在创建向量时,合理预分配一些空间将很大程度上缓解这些额外开销
-
示例
#include <iostream> #include <vector> #include <cstdio> using namespace std; class Student { public: Student( const char* name="无名" ) : m_name(name) { cout << "缺省构造了:" << 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<Student> vs{Student("武松"),Student("林冲")}; vs.reserve(10); // 设置容量 vs.push_back( Student("超哥") ); vs.push_back( Student("恒哥") ); vs.push_back( Student("文哥") ); vs.resize(8); // 设置大小 --> 内部 调用 Student() 造3个学生 cout << "向量中学生的个数:" << vs.size() << endl; getchar(); return 0; }
-
-
迭代器使用
-
增操作: insert
-
删操作 : erase
-
改操作 :迭代器解引用
-
查操作 : find
-
排序操作 : sort
-
示例
#include <iostream> #include <vector> #include <algorithm> // find / sort 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<Student>& v ) { cout << str << endl; typedef vector<Student>::const_iterator CIT; for( CIT cit=v.begin(); cit!=v.end(); ++cit ) { cout << *cit << ' '; // 迭代器指向节点数据的 常引用 } cout << endl << "--------------------------" << endl; } class ZJW { public: bool operator()( const Student& a, const Student& b ) { return a > b; } }; int main( void ) { vector<Student> vs{ Student("武松",18) }; vs.reserve(30); vs.push_back(Student("张飞",22)); vs.push_back(Student("赵云",20)); vs.push_back(Student("关羽",25)); vs.push_back(Student("马超",32)); vs.push_back(Student("黄忠",45)); Print("添加节点后:", vs); vs.insert( vs.begin(), Student("林冲", 19) ); // 增 Print("迭代器指向的位置添加节点后:", vs); vs.erase( --vs.end() ); // 删 Print("删除迭代器指向的节点后:", vs); typedef vector<Student>::iterator IT; IT it = vs.begin(); *it = Student("西门庆",20); // 改 Print("修改迭代器指向的节点后", 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<Student>() ); // 用的是 标准库的比较器 sort( vs.begin(), vs.end(), ZJW() ); // 用的是 自己设计的比较器 Print( "排序后:", vs ); return 0; }
-
双端队列
deque
-
常用函数
函数 功能 front() 获取首元素 back() 获取尾元素 insert() 插入元素 erase() 删除元素 push_front() 添加首元素 pop_front() 删除首元素 push_back() 添加尾元素 pop_back() 删除尾元素 empty() 判空 clear() 清空 size() 队列维护元素个数 resize() 队列向量的容量 -
双端队列和向量的差别
- 和向量差别就是首尾两端同样都是开放的,因此他同时提供了首尾两端增删元素的接口
- 没有提供设置/获取容量的函数,设置和获取容器大小的函数存在
-
示例
#include <iostream> #include <deque> #include <algorithm> // find / sort 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<Human>& d ) { cout << str << endl; typedef deque<Human>::iterator IT; for( IT it=d.begin(); it!=d.end(); ++it ) { // begin()/end() -- 返回iterator类对象 cout << *it << ' '; } cout << endl << "------------------------" << endl; } */ void Print( const string& str, const deque<Human>& d ) { cout << str << endl; typedef deque<Human>::const_iterator IT; for( IT it=d.begin(); it!=d.end(); ++it ) { // begin()/end() -- 返回const_iterator类对象 cout << *it << ' '; } cout << endl << "------------------------" << endl; } template<typename T>class ZJW { // greater的内部实现 和 ZJW 相同 public: bool operator()( const T& a, const T& b ) { return a > b; } }; int main( void ) { deque<Human> dq; // 非常容器 dq.push_front( Human(22,"张飞") ); dq.push_front( Human(20,"赵云") ); dq.push_front( Human(25,"关羽") ); 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,"鲁达") ); dq.push_back( Human(12,"李逵") ); dq.push_back( Human(23,"关胜") ); Print( "添加头尾节点后:", dq ); dq.pop_front(); dq.pop_back(); Print( "删除头尾节点后:", dq ); dq.insert( ++dq.begin(), Human(35,"宋江"));// 利用 迭代器 添加节点 Human(35,"宋江") Print( "迭代器指向的位置添加节点后:", dq ); dq.erase( ++dq.begin() ); // 删 Print( "删除迭代器指向的节点后:", dq ); typedef deque<Human>::iterator IT; IT it = dq.begin(); *it = Human(19,"孔明"); // 改 Print( "修改迭代器指向的节点后:", 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<Human>() ); // ">" sort( dq.begin(), dq.end(), ZJW<Human>() ); // Print( "排序后:", dq ); return 0; }
列表(链表)
list
-
常用函数
函数 功能 front() 获取首元素 back() 获取尾元素 insert() 插入元素 erase() 删除元素 push_front() 添加首元素 pop_front() 删除首元素 push_back() 添加尾元素 pop_back() 删除尾元素 empty() 判空 clear() 清空 size() 队列维护元素个数 resize() 队列向量的容量 -
唯一化
- void unique(void); 将连续重复出现的元素唯一化
-
排序(都是全局排序)注意sort是成员函数
-
void sort(void) 通过 < 比大小
-
templatevoid sort(LESS less) 通过比较器比大小
-
-
拆分:将参数列表中的部分或全部元素剪切到调用列表中
- templatevoid splice( IT pos, list& lst )
- 将全部节点剪切到pos指向位置的前面
- templatevoid splice( IT pos, list& lst, IT del )
- 将del指向位置的节点剪切到pos指向位置的前面
- templatevoid splice( IT pos, list& lst, IT begin, IT end )
- 将begin和end中间的节点(左闭右开)剪切到pos指向位置的前面
- templatevoid splice( IT pos, list& lst )
-
示例
#include <iostream> #include <list> #include <algorithm> using namespace std; // 使用 STL中 列表容器(list) // 以上代码容器的设计者 // ----------------------- // 以下代码容器的使用者 void Print( const string& str, const list<int>& l ) { cout << str << endl; typedef list<int>::const_iterator CIT; for( CIT it=l.begin(); it!=l.end(); ++it ) { cout << *it << ' '; } cout << endl << "----------------------------" << endl; } // 用户设计比较类 class ZJW { public: bool operator()( int x, int y ) { return x < y; } }; int main( void ) { list<int> ls; // 非常容器(非常对象) 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( "添加头尾节点后:", ls ); ls.pop_front(); ls.pop_back(); Print( "删除头尾节点后:", ls ); ls.insert( ++ls.begin(), 10 ); // 增 Print( "迭代器指向的位置添加节点后:", ls ); ls.erase( ----ls.end() ); // 删 Print( "删除迭代器指向的节点后:", ls ); typedef list<int>::iterator IT; IT it = ls.begin(); *it = 999; // 改 Print( "修改迭代器指向的节点后:", 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<int>()); Print( "排序后:", ls ); list<int> lst; // 作为参数列表 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("调用列表ls:", ls); Print("参数列表lst:", lst); const list<int> cls = ls; // 常容器(常对象) Print( "常容器:", cls ); return 0; }
适配器容器
又叫裁剪型容器,由线性容器裁剪部分功能后得到
栈
stack
-
定义形式
- stack<元素类型 [ , 底层容器类型]> 堆栈对象
-
底层容器
- deque(默认) / vector / list / 自己实现的容器
-
成员函数
- push ->push_back
- pop ->pop_back
- top -> back
- size -> size
- empty -> empty
-
栈的底层实现
template<tyepname T,typename Sequence=deque>class stack{
public:
void push(const T& data) { … c.push_back(data); … }
void pop() { … c.pop_back();… }
……
private:
Sequence c;
};
-
示例
#include <iostream> #include <stack> #include <vector> #include <list> using namespace std; // 使用 STL中 列表容器(list) // 以上代码容器的设计者 // ----------------------- // 以下代码容器的使用者 int main( void ) { // stack<int, deque<int> > s; // s 内部成员变量是deque<int> c; // stack<int, vector<int> > s; // s 内部成员变量是 vector<int> c; // stack<int, list<int> > s; // s 内部成员变量是 list<int> c; stack<int> s; 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; }
队列
queue
-
定义形式
- queue<元素类型 [,底层容器类型]> 队列对象
-
底层容器
- deque(默认) / list
- 不能使用vector
-
成员函数
- push -> push_back
- pop -> pop_front
- back - > back
- front -> front
- size -> size
- empty -> empty
-
队列的底层实现
template<typename T,typename Sequence=deque> class queue{
public:
void push(const T& data) { … c.push_back(data); … }
void pop() { … c.pop_front(); … }
……
private:
Sequence c;
};
-
示例
#include <iostream> #include <queue> #include <list> #include <vector> using namespace std; // 使用 STL中 (queue)队列容器 // 以上代码容器的设计者 // ----------------------- // 以下代码容器的使用者 int main( void ) { // queue<int, deque<int> > d; // 底层容器是 deque<int> c (数据保存在连续的内存空间) // queue<int, list<int> > d; // 底层容器是 list<int> c (数据保存在链式内存空间) // queue<int, vector<int> > d; //底层容器是 vector<int> c 不可以选择 error queue<int> d; // 底层容器是 deque<int> c (数据保存在连续的内存空间) 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; }
优先队列
priority_queue
-
定义形式
- priority_queue<元素类型 [,底层容器类型][,比较器类型]> 优先队列对象
-
底层容器
- deque / vector(默认)
- 不能使用list (因其不支持随机迭代)
-
注意事项
- 优者先出,默认以大者为优(默认内部使用<运算符进行比较)
- 可以通过比较器定制比较规则
- 并不是出队列时挑,而是进队列时就保证有序
-
成员函数
- push -> push_back
- pop -> pop_back
- top -> front
- size -> size
- empty -> empty
-
示例
#include <iostream> #include <queue> // queue / priority_queue #include <list> using namespace std; // 使用 STL中 (priority_queue)优先队列容器 // 以上代码容器的设计者 // ----------------------- // 以下代码容器的使用者 class ZJW { public: bool operator()( int x, int y ) { return x > y; } }; int main( void ) { // priority_queue<int, vector<int>, greater<int> > pq; // 底层容器是 vector<int> c // priority_queue<int, vector<int>, ZJW > pq; // 底层容器是 vector<int> c // priority_queue<int, deque<int>, ZJW > pq; // 底层容器是 deque<int> c // priority_queue<int, list<int>, ZJW > pq; // 底层容器是 list<int> c - 不可以作为底层容器 error priority_queue<int> pq; // 底层容器是 vector<int> c 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; }
关联容器
以下介绍的容器底层维护的数据结构为平衡二叉树(红黑树)
映射
map
-
映射容器的每个节点保存的为 pair对象(键/值 对)
-
pair类模板
template<typename FIRST, typename SECOND>
class pair {
pair(FIRST f, SECOND s):first(f),second(s) {}
FIRST first; // 保存 键
SECOND seond; // 保存 值
}
-
创建map容器
-
创建空的map容器
map<string,int> m1;
-
使用初始化列表指定map的初始值
map<string,int> m2{ {“zhangsan” ,80} ,{“lisi”, 85 },{“zhangsan”, 77 }};
-
使用现有容器或容器的一段元素创建map
map<string,int> m3{m2}
-
-
map元素的插入
-
插入一个pair<Key,T>对象
insert( pair<FIRST,SECOND>(键,值) )
-
使用make_pair<Key,T> 函数模板插入一个对
insert( make_pair(键,值))
-
初始化列表作为参数
insert({ {键, 值}, {键, 值}, {键, 值}, …})
-
-
访问map元素
-
使用迭代器访问元素
begin()/end() begin() const / end() const
rbegin()/rend() rbegin() const / rend() const
-
使用键获取值
- [key]:key存在时返回值的引用,不存在时添加新元素并返回值的引用
- at(key): key存在时返回值的引用,不存在时抛出异常out_of_range
-
-
map元素的删除
-
erase(key)
移除和键参数匹配的元素,返回所移除元素的个数
-
erase(iterator)
移除参数迭代器指向的元素,返回迭代器指向被删除元素的下一个位置。参数必须是容器中的有效迭代器,不能是终止迭代器。如果迭代器参数指向的是容器的最后一个元素,则返回终止迭代器
-
-
map元素的查找
-
find(key)
返回一个元素的迭代器,这个元素的键和参数匹配。如果没有和参数匹配的元素,find()函数会返回容器的终止迭代器。因此在使用这个迭代器之前,必须先对它进行检查
-
-
示例
#include <iostream> #include <map> 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<char,Candidate>& m ) { typedef map<char,Candidate>::iterator IT; for( IT it=m.begin(); it!=m.end(); ++it ) { cout << "(" << (*it).first << ")" << (*it).second.getName() << ' '; } cout << endl; } int main( void ) { map<char,Candidate> m{ {'A',Candidate("张飞")} }; m.insert( pair<char,Candidate>('B', Candidate("赵云")) ); m.insert( make_pair('C', Candidate("关羽")) ); m.insert( { {'D', Candidate("马超")} } ); m['E'] = Candidate("黄忠"); typedef map<char, Candidate>::iterator IT; 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
-
允许键重复的映射,表示一对多的逻辑关系,不支持下标运算符
-
定义形式: multimap<键类型,值类型> 映射对象;
-
示例
#include <iostream> #include <map> using namespace std; void Print( multimap<string,int>& m ) { typedef multimap<string,int>::iterator IT; for( IT it=m.begin(); it!=m.end(); ++it ) { cout << (*it).first << ":" << (*it).second << endl; } } int main( void ) { multimap<string ,int> m{ {"张飞",95},{"赵云",85} }; m.insert( pair<string,int>("关羽",75) ); m.insert( make_pair("张飞", 65) ); m.insert( { {"赵云",55}, {"关羽",45} } ); Print( m ); return 0; }
集合
set
-
没有值只有键的映射
-
与向量等基本容器相比最大优势就是 排重
-
定义形式:set 集合对象;
-
示例
#include <iostream> #include <set> using namespace std; void Print( set<int>& s ) { typedef set<int>::iterator IT; for( IT it=s.begin(); it!=s.end(); ++it ) { cout << *it << ' '; } cout << endl; } int main( void ) { set<int> s{ 2,8,1}; s.insert( 5 ); s.insert( 6 ); s.insert( 3 ); s.insert( 4 ); s.insert( 7 ); s.insert( 5 ); Print( s ); return 0; }
多重集合
multiset
-
键可以重复的集合
-
定义形式:multiset 多重集合对象;
-
示例
#include <iostream> #include <set> using namespace std; void Print( multiset<int>& s ) { typedef set<int>::iterator IT; for( IT it=s.begin(); it!=s.end(); ++it ) { cout << *it << ' '; } cout << endl; } int main( void ) { multiset<int> s{ 2,8,1}; s.insert( 5 ); s.insert( 6 ); s.insert( 3 ); s.insert( 4 ); s.insert( 7 ); s.insert( 5 ); Print( s ); return 0; }
无序容器
哈希
-
哈希算法是用给定范围的基本类型的数据项也包括string,生成整数值的过程
-
哈希算法产生的值叫做哈希值或者哈希码
-
理想情况下,每个对象产生的哈希值是唯一的,但实际是可能产生重复的。重复的哈希值称为哈希碰撞(概率极低)
-
哈希类模板
template class hash{
…
size_t operator()(Key k){}
…
};
无序映射
unordered_map,内部维护的数据结构为哈希散列表
-
定义形式
- unordered_map<键的类型,值的类型[,哈希器类型][,判等器类型]> 对象;
-
底层实现
template <typename K, typename V, typeame H = hash, … >
class unordered_map{
…
};
-
示例
#include <iostream> #include <unordered_map> using namespace std; // map提供的操作 unordered_map 都支持 int main( void ) { unordered_map<string,int, hash<string> > um{ {"张飞",22} }; // 构造函数中 定义hash<string>类的对象,利用 这个对象("张飞")-->触发operator()("张飞")-->根据"张飞"算哈希值 // // 构造函数中 将计算出的哈希值进行处理(例如:对10取余.....) // 将哈希值 和 数据(pair对象)保存到 哈希散列表相应的 支脉 um.insert( pair<string,int>("赵云",20) ); // insert函数中 定义hash<string>类的对象,利用 这个对象("赵云")-->触发operator()("赵云")-->根据"赵云"算哈希值 // // insert函数中 将计算出的哈希值进行处理(例如:对10取余.....) // 将哈希值 和 数据(pair对象)保存到 哈希散列表相应的 支脉 um.insert( make_pair("关羽",25) ); um["马超"] = 32; typedef unordered_map<string,int,hash<string> >::iterator IT; for( IT it=um.begin(); it!=um.end(); ++it ) { cout << (*it).first << ":" << (*it).second << endl; } return 0; }