C++复习笔记
c++基本数据类型
————————————————————————————————————————————————————
char 1字节(8位) 取值范围 (-2^7)~(2^7-1)即-128~127
signed char 1字节(8位) 取值范围 (-2^7)~(2^7-1)即-128~127
————————————————————————————————————————————————————
unsigned char 1字节(8位) 取值范围 0~(2^8-1)即0~255
————————————————————————————————————————————————————
short int(或short) 2字节(16位) 取值范围(-2^15)~(2^15-1) 即-32768~32767
signed short int(或signed short) 2字节(16位) 取值范围(-2^15)~(2^15-1) 即-32768~32767
————————————————————————————————————————————————————
unsigned short int(或unsigned short) 2字节(16位) 取值范围 0~(2^16-1) 即0~65535
————————————————————————————————————————————————————
int 4字节(32位) 取值范围 (-2^31)~(2^31-1) 即-2147483648~2147483647
signed int(或signed) 4字节(32位) 取值范围 (-2^31)~(2^31-1) 即-2147483648~2147483647
————————————————————————————————————————————————————
unsigned int(或unsigned) 4字节(32位) 取值范围 0~(2^32-1) 即0~4294967295
————————————————————————————————————————————————————
long int(或long) 4字节(32位) 取值范围 (-2^31)~(2^31-1) 即-2147483648~2147483647
signed long int(或signed long) 4字节(32位) 取值范围 (-2^31)~(2^31-1) 即-2147483648~2147483647
————————————————————————————————————————————————————
unsigned long int(或unsigned long) 4字节(32位) 取值范围 0~(2^32-1) 即0~4294967295
————————————————————————————————————————————————————
float 4字节(32位) 取值范围 (-3.4x10^38)~(3.4x10^38) ,约6位有效数字
————————————————————————————————————————————————————
double 8字节(64位) 取值范围 (-1.7x10^308)~(1.7x10^308) ,约15位有效数字
long double 8字节(64位) 取值范围 (-1.7x10^308)~(1.7x10^308) ,约15位有效数字
————————————————————————————————————————————————————
char,signed char 是等价的
short,short int,signed short,signed short int 都是等价的
int,signed,signed int,long,long int,signed long,signed long int 都是等价的
double,long double 是等价的
c++基本数据类型 ———————————————————————————————————————————————————— char 1字节(8位) 取值范围 (-2^7)~(2^7-1)即-128~127 signed char 1字节(8位) 取值范围 (-2^7)~(2^7-1)即-128~127 ———————————————————————————————————————————————————— unsigned char 1字节(8位) 取值范围 0~(2^8-1)即0~255 ———————————————————————————————————————————————————— short int(或short) 2字节(16位) 取值范围(-2^15)~(2^15-1) 即-32768~32767 signed short int(或signed short) 2字节(16位) 取值范围(-2^15)~(2^15-1) 即-32768~32767 ———————————————————————————————————————————————————— unsigned short int(或unsigned short) 2字节(16位) 取值范围 0~(2^16-1) 即0~65535 ———————————————————————————————————————————————————— int 4字节(32位) 取值范围 (-2^31)~(2^31-1) 即-2147483648~2147483647 signed int(或signed) 4字节(32位) 取值范围 (-2^31)~(2^31-1) 即-2147483648~2147483647 ———————————————————————————————————————————————————— unsigned int(或unsigned) 4字节(32位) 取值范围 0~(2^32-1) 即0~4294967295 ———————————————————————————————————————————————————— long int(或long) 4字节(32位) 取值范围 (-2^31)~(2^31-1) 即-2147483648~2147483647 signed long int(或signed long) 4字节(32位) 取值范围 (-2^31)~(2^31-1) 即-2147483648~2147483647 ———————————————————————————————————————————————————— unsigned long int(或unsigned long) 4字节(32位) 取值范围 0~(2^32-1) 即0~4294967295 ———————————————————————————————————————————————————— float 4字节(32位) 取值范围 (-3.4x10^38)~(3.4x10^38) ,约6位有效数字 ———————————————————————————————————————————————————— double 8字节(64位) 取值范围 (-1.7x10^308)~(1.7x10^308) ,约15位有效数字 long double 8字节(64位) 取值范围 (-1.7x10^308)~(1.7x10^308) ,约15位有效数字 ————————————————————————————————————————————————————
char,signed char 是等价的
short,short int,signed short,signed short int 都是等价的
int,signed,signed int,long,long int,signed long,signed long int 都是等价的
double,long double 是等价的
3.1函数地址 //以double simple(int,int);为例 下面三种均可表示函数地址 simple &simple *&simple 3.2函数调用 //注意,调用时是实参 调用方式:函数地址(实际参数表) //以double simple(int,int);为例 simple(a,b) (&simple)(a,b) //因为小括号的运算符优先级最高,所以必须是(&simple) (*&simple)(a,b) //因为小括号的运算符优先级最高,所以必须是(*&simple) 3.3函数类型 定义形式: typedef 类型 函数类型名称(形式参数表); //注意,typedef最后有分号 //以double simple(int,int);为例 —— typedef double s(int,int); 上面的定义是一种自定义类型,它将double (int,int)这种函数类型表达为s类型 既然是类型就可以定义相应的"变量",typedef定义函数类型后,就可以按定义变量的形式写同类型函数的原型声明 —— s a,b; //类比于int a,b; 上面的函数类型定义相当于 double a(int,int);和double b(int,int);这两个函数声明 3.4函数指针 ①(定义一个指针变量) 定义方式1:类型 (*指针变量名)(形式参数表); //以double simple(int,int);为例 —— double (*pa)(int,int); //pa就是函数指针 —— double (*pb)(int,int); //pb就是函数指针 并且可以与普通的double变量同时定义 —— double a, b, (*pa)(int,int), (*pb)(int,int); 定义方式2:函数类型 *指针变量名; //以double simple(int,int);为例 在上面typedef定义该函数类型为s类型后,可以直接按定义指针的形式定义函数指针 ——s *pa,*pb; //类比于int *pa,*pb; //pa,pb就是函数指针 ②(定义一种指针类型) 还可以像定义函数类型一样,定义该函数类型的指针类型 定义形式1: typedef 类型 (指针类型名)(形式参数表); //注意,typedef最后有分号 //以double simple(int,int);为例 —— typedef double (*type)(int,int); //type就是指针类型 —— type pa,pb; //类比于int *pa,*pb //pa,pb就是函数指针 定义形式2: typedef 函数类型 *指针类型名 //以double simple(int,int);为例 在上面typedef定义该函数类型为s类型后,可以定义该函数的指针类型 —— typedef s *type; //type就是指针类型 —— type pa,pb; //类比于int *pa,*pb; //pa,pb就是函数指针 3.5用函数指针调用函数 //注意,调用时是实参 调用方式:(*指针变量名)(实际参数表) //以double simple(int,int);为例 pa=a; //函数指针pa获得函数地址a pb=b; //函数指针pb获得函数地址b 当然pa=b; pa=b;都是可以的,因为它们都是是相同函数类型下的函数与函数指针 类比于 int a,b; int *pa,*pb; pa=a; pa=b; 它们都是int类型下的变量与指针 double t,k; t=a(1,4); t=pa(1,4); t=(*pa)(1,4);是等价的 //但是t=(&pa)是错误的,&pa获得的是指针自身的地址,而不是函数a的地址 k=b(2,9); t=pb(2,9); t=(*pb)(2,9)是等价的 //但是t=(&pb)是错误的,&pb获得的是指针自身的地址,而不是函数b的地址 3.6函数指针数组 定义方式1:类型 (*指针数组名[ 整型表达式])(形式参数表); //以double simple(int,int);为例 ——double (*pfun[5])(int,int); //定义double(int,int)函数类型下的 函数指针数组,每个数组元素都是一个函数指针 pfun[0]=a; //函数指针数组元素pfun[0]获取函数地址a pfun[1]=b; //函数指针数组元素pfun[1]获取函数地址b 定义方式2:函数类型 *指针数组名; //以double simple(int,int);为例 在上面typedef定义该函数类型为s类型后,可以直接按定义指针的形式定义函数指针 ——s *pfun[5]; pfun[0]=a; //函数指针数组元素pfun[0]获取函数地址a pfun[1]=b; //函数指针数组元素pfun[1]获取函数地址b 3.7用函数指针数组调用函数 调用方式:(*指针数组元素)(实际参数表) //以double simple(int,int);为例 pfun[0]=a; //函数指针数组元素pfun[0]获取函数地址a pfun[1]=b; //函数指针数组元素pfun[1]获取函数地址b int x,y; for(int i=0;i<=1;i++) { cout<<(*pfun[i])(x,y)<<endl; //相当于调用a(x,y)和b(x,y) } 3.8指针数组指向多个等长数组(二维数组) //相当于4行6列的二维数组 int *p[4]; //行向量 int a[6],b[6],c[6],d[6]; //列向量 p[0]=a; p[1]=b; p[2]=c; p[3]=d; 访问方式:二维:p[i][j]、*(p[i]+j)、*(*(p+i)+j) // i :0~3,j :0~5 行向量:p[i]、*(p+i) // i :0~3 一维:p[t] // t :0~25 3.9指针数组指向多个不等长的数组 int *p[4]; int a[12]; int b[5]; int c[24]; int d[7]; p[0]=a; p[1]=b; p[2]=c; p[3]=d; 3.10指针数组 类型* 数组名[整型表达式]; int *p[10]; //定义指针数组a[0]到a[9],每个数组元素都是int * 指针,即10个指针 3.10数组指针 类型 (*数组名)[整型表达式]; int a[10]; int (*p)[10]; //定义数组指针*p,指针p指向 类型为int [10]的数组,即1个指针 p=a; cout<<p[4]<<endl; 3.10.1数组指针数组 int a[7][8]; //7x8的二维数组 int (*p[5])[8]; //定义数组指针数组p[0]到p[4],每个数组元素都是指向类型为int [8]数组的指针,共5个数组指针 for(i=0;i<5;i++) //仅访问前五行 p=a[i]; //数组指针获取行向量的地址 cout<<p[3][6]<<endl; 4.1字符串处理函数 1. 字符串长度 strlen (=string length) 函数原型:int strlen( const char *string ); strlen(s); //统计有效字符长度,以\0结束,不包括\0 ——字符数组的strlen与sizeof比较 char s[8]="abcde"; cout<<strlen(s); //输出5,字符数组有效字符长度为5 cout<<sizeof(s); //输出8,字符数组字节数为8 2.1 字符串复制 strcpy (=string copy) 函数原型:char * strcpy(char *s1, const char *s2); strcpy(s1,s2); //把s2复制到s1中,类似于变量s1=s2; 2.2 字符串复制 strcpy_s (=string copy safe) 函数原型:char * strcpy_s(char *s1, int s1_n, const char *s2); //s1_n是字符数组s1的长度,保证复制时不会超出n个,增强安全性 char s2="zyxw"; int len=strlen(s2); char *s1=new char[len+1]; //len+1是为了存储字符串结束符\0 strcpy_s(s1,len+1,s); cout<<s1; 2.3 字符串前n个复制 strncpy (=string n copy) 函数原型:char * strncpy(char *s1, const char *s2, int s2_n); strncpy(s1,s2,n); //将s2的前n个字符复制到s1中 3.1 字符串连接 strcat (=string catenate) 函数原型:char * strcat(char *s1,const char *s2); strcat(s1,s2) //在s1后面连接上s2,并且去掉s1最后的\0 3.2 字符串连接 strcat_s (=string catenate safe) 函数原型:char * strcat(char *s1,int s1_n,const char *s2); strcat(s1,sizeof(s1),s2); //规定s1和s2连接后的长度为sizeof(s1),防止溢出 3.3 字符串连接 strncat (=string n catenate) 函数原型:char * strncat(char *s1,const char *s2,int s2_n); strncat(s1,s2,n); //将s2的前n个字符连接到s1后面 3.4 字符串连接 strncat_s (=string n catenate safe) 函数原型:char * strncat(char *s1,int s1_n,const char *s2,int s2_n); strncat_s(s1,sizeof(s1),s2,n); //将s2的前n个字符连接到s1后面,并且连接后s1的长度为sizeof(s1),防止溢出 4.1 字符串比较 strcmp (=string compare) 函数原型:int strcmp(char *s1,const char *s2); strcmp(s1,s2); //逐个比较字符串中字符的ascii码,如果字符串相等则返回0,如果s1的字符不等于s2的字符则返回ascii码的差值(s1>s2为正,s1<s2位负) 4.2 字符串比较 strncmp (=string n compare) 函数原型:int strncmp(char *s1,const char *s2,int n); strncmp(s1,s2,n); //仅比较s1和s2的前n个字符 构造函数和析构函数都没有返回值 6.1构造函数 1. 第一种:类内声明,类外定义 class 类名 { public: //构造函数必须是公有的,因为对象在类外定义,构造对象时调用构造函数,如果是私有则无法在类外调用 类名(参数表); //构造函数的原型声明 }; 类名::类名(参数表) //构造函数的类外定义 { //构造函数具体定义 } 2. 第二种:类内直接定义 class 类名 { public: //构造函数必须是公有的,因为对象在类外定义,在对象构造时调用构造函数,如果是私有则无法在类外调用 类名(参数表) //构造函数的类内直接定义 { //构造函数具体定义 } }; 6.2析构函数 1. 第一种:类内声明,类外定义 class 类名 { public: //析构函数必须是公有的,因为对象在类外定义,在对象销毁时调用析构函数,如果是私有则无法在类外调用 ~类名(); //析构函数一定没有参数,析构函数的原型声明 }; 类名::~类名() //析构函数一定没有参数,析构函数的类外定义 { //析构函数具体定义 } 2. 第二种:类内直接定义 class 类名 { public: ~类名() //析构函数一定没有参数,析构函数的类内直接定义 { //析构函数具体定义 } }; 构造函数有参数可以重载,析构函数没有参数不能重载 6.3重载构造函数 class 类名 { public: 类名(); //无参构造函数的原型声明 类名(类型1 参数1); //带1个参数的构造函数的原型声明 类名(类型1 参数1,类型2 参数2); //带2个参数的构造函数的原型声明 ////////以此类推 }; 类名::类名() //无参构造函数的类外定义 { //构造函数具体定义 } 类名::类名(类型1 参数1) //带1个参数的构造函数的类外定义 { //构造函数具体定义 } 类名::类名(类型1 参数1,类型2 参数2) //带2个参数的构造函数的类外定义 { //构造函数具体定义 } 6.3.1有默认参数的构造函数 class 类名 { public: 类名(类型1 参数1=0,类型2 参数2=0); //带2个默认参数的构造函数的原型声明 } 类名::类名(类型1 参数1,类型2 参数2) //带2个参数的构造函数的类外定义(注意,默认参数只在函数第一次出现时写出,一般在原型声明中写出) 6.4复制构造函数 (是一种特殊的重载构造函数,其参数包含一个自身类类型的常引用参数) class 类名 { public: 类名(){} //类内直接定义的无参且无函数体的构造函数 类名(const 类名& 引用名); //复制构造函数的原型声明 }; 类名::类名(const 类名& 引用名) //复制构造函数的类外定义 { //复制构造函数具体定义 } 6.5常成员变量 class 类名 { public: const 类型1 常量1; //变量1是类的常成员变量 类型2 变量2; //变量2是类的普通成员变量 类名(类型1 变量3,类型2 变量4):常量1(变量3),变量2(变量4) //构造函数初始化式,同类型变量给相应变量初始化,常量只能靠初始化式赋值 { //构造函数具体定义 } }; 6.6常成员函数 (一类特殊的成员函数,其this指针被约束为指向常量的常指针) class 类名 { public: 返回值类型 函数名(参数表)const; //常成员函数的原型声明 }; 类名::返回值类型 函数名(参数表)const { //常成员函数具体定义 } 调用方式: int main() { 类名 对象1; 对象1.常成员函数名(参数表); //调用时不加const } 6.7常对象 class 类名 { //类的具体定义 }; int main() { const 类名 t; //常对象t中所有的成员变量都被约束为const只读 } 6.8静态成员变量 class 类名 { static 类型1 变量1; //静态成员变量,类内声明 static 类型1 变量2; }; int 类名::变量1=0; //静态成员变量,类外定义和初始化 int 类名::变量2=1; //同类型静态成员变量可以同时定义 int 类名::变量1=0,类名::变量2=1; 6.9静态成员函数 //没有this指针 class 类名 { public: static 返回值类型 函数名(参数表); //静态成员函数的原型声明 }; 类名::返回值类型 函数名(参数表) //静态成员函数的类外定义,不加static { //静态成员函数的具体定义 } 调用方式: 1.定义对象调用 int main() { 类名 对象1; 对象1.静态成员函数名(参数表); } 2.类作用域调用 (因为静态成员是不依赖于对象的,仅对应类) int main() { 类名::静态成员函数名(参数表); } *静态成员函数只能访问静态成员变量。 *但是静态成员变量可以被普通成员函数和静态成员函数访问。 6.10友元函数 //没有this指针 友元函数不属于类,它是类外的函数 class 类名 { friend 返回值类型1 函数名(类名&,......); //原型声明,友元函数通过类类型的 引用参数 来访问类中成员 friend 返回值类型2 函数名(类名*,........); //原型声明,友元函数通过类类型的 指针参数 来访问类中成员 }; 返回值类型 函数名(类名&,.....) //友元函数类外定义时不加friend关键字 { } 返回值类型2 函数名(类名*,........); //友元函数类外定义时不加friend关键字 { } 6.11友元类 class 类名1 { friend class 类名2; //将类2声明为类1的友元 }; class 类名2 { 类名1 对象1; //以类包含方式访问类1对象 返回值类型 函数名(类1&,.....); //以引用参数方式访问类1对象 }; 不能重载的运算符: . .* :: ?: sizeof 不能用友元函数重载的运算符: = () [] -> 成员函数,友元函数,普通函数均可重载运算符,但是普通函数需要操纵公有成员实现重载,增加程序开销,所以通常用成员函数和友元函数重载运算符 7.1通过成员函数重载运算符 【左操作数是对象时,用成员函数重载】 class 类名 { 返回值类型 operator op(); //函数声明,一元运算符重载 返回值类型 operator op(类名 对象名); //函数声明,二元运算符重载 }; 返回值类型 类名::operator op() //函数定义,一元运算符重载 { } 返回值类型 类名::operator op(类名 对象名) //函数定义,二元运算符重载 { } 调用方式 1.一元运算符 类名 对象1; 对象1 op;//例如a++; op 对象1; //例如++a; 对象1.operator op(); 2.二元运算符 类名 对象1,对象2; 对象1 op 对象2; //例如a+b; 对象1.operator op(对象2); 7.2通过友元函数重载运算符 【左右操作数类型不同时,用友元函数重载】 class 类名 { friend 返回值类型 operator op(类名 对象名); //函数声明,一元运算符重载 friend 返回值类型 operator op(类名 对象名1,类名 对象名2); //函数声明,二元运算符重载 }; 返回值类型 operator op(类名 对象名) //函数定义,一元运算符重载 { } 返回值类型 operator op(类名 对象名1,类名 对象名2) //函数定义,二元运算符重载 { } 调用方式 1.一元运算符 类名 对象1; operator op(对象1); 2.二元运算符 类名 对象1,对象2; operator op(对象1,对象2); 7.3重载 前置++ //一元运算符重载 class 类名 { 类名& operator++(); friend 类名& operator++(类名&); }; 类名& 类名::operator++() { } 类名& operator++(类名& 对象名) { } 调用方式: 类名 对象1; ++对象1; 7.4重载 后置++ //二元运算符重载 class 类名 { 类名& operator++(int); //右操作数默认为0 friend 类名& operator++(类名&,int); //右操作数默认为0 }; 类名& 类名::operator++(int x) { } 类名& operator++(类名& 对象名,int x) { } 调用方式: 类名 对象1; 对象1++; 7.5重载 赋值运算符(=) //只能成员函数重载,且不能被继承 class 类名 { 类名& operator=(类名); }; 类名& 类名::operator=(类名) { } 调用方式: 类名 对象1,对象2,对象3; 对象3=对象2=对象1; 7.6重载 []运算符 //只能成员函数重载 class 类名 { 类名& operator[](int); 类名 operator()(int); }; 类名& 类名::operator[](int x) { } 类名 类名::operator()(int x) { } 调用方式: 类名 对象1; int x 对象1[x]; 对象1(x); 7.7类类型转换 (非默认参数的构造函数) 【类/基本——>类】 class 类名 { 类名(类型1 变量1,......); //没有默认参数的构造函数 }; 类名::类名(类型1 变量1,......) //将 类型1—转换为—>本类类型 { } 例: class complex { public: complex(int i); int x; } complex::complex(int i) //将 int—转换为—>complex类型 { x=i; } 显式调用: complex d; d=complex(6); //显式调用构造函数,将常量6强制类型转换为complex类型 隐式调用: 1.直接赋值 complex b; b=5; //隐式调用构造函数,将常量5强制类型转换为complex类型 2.函数传参 如果有函数声明 void fun(complex); int main() { fun(38); //隐式调用构造函数,将常量38强制类型转换为complex类型 } 3.算术运算 complex h; h=h+8; //隐式调用构造函数,将常量8强制类型转换为complex类型,而且必须用友元函数重载运算符(+) 7.8类型转换函数 【类——>基本/类】 class 类名 { operator 类型(); //成员函数,没有参数,没有返回值 }; 类名::operator 类型() { return 类型值; } 例: class complex { public: operator int(); int x; }; complex::operator int() { return x; //x是类的成员变量,类型是int } 8.1继承语句 class 派生类名:基类名表 { }; 基类名表格式: 访问控制 基类1,访问控制 基类2 例: class a { }; class b { }; class c: public a, public b //基类名表用逗号隔开 { }; 8.2公有继承 class b:public a public ——>public protected ——>protected private ——>private //派生类中不可见,但有内存空间 8.3保护继承 class b:protected a public ——>protected protected ——>protected private ——>private //派生类中不可见,但有内存空间 8.4私有继承 class b:private a public ——>private protected ——>private private ——>private //派生类中不可见,但有内存空间 在派生类的成员函数中可以用 基类::基类成员函数(参数表); 方式来调用基类成员函数 class b:public a { public: void print() { a::print(); //调用基类a的成员函数print() } } 在保护继承,私有继承中,可以使用访问声明让某些成员变量和成员函数恢复原本的访问控制(但是不可以提升或降低可访问性) class b:private a { public: a::print; //让基类基类a的成员函数print()恢复public访问。 //注意,此访问声明仅写函数名或变量名,没有返回值和参数,和上面的基类成员函数调用是不同的 }; 8.5.1重名成员变量 class a { public: int a; }; class b:public a { public: int a; }; int main() { a m1; //成员变量有a::a b m2; //成员变量有a::a和b::a } 8.5.2重名成员函数(派生类重载函数) class a { void print() { cout<<"a"<<endl; } }; class b:public a { void print() { cout<<"b"<<endl; } }; int main() { a a; a.print(); //输出a b b; b.priint(); //输出b } 8.6派生类访问静态成员 class a { public: static int i; //静态成员变量,类内声明 void add() { i++; } }; int a::i=1; //静态成员变量,类外定义和初始化 class b:private a { public: void f() { add(); //i 自增 add(); //i 自增 } void show() { cout<<i<<endl; } }; int main() { b b; b.f(); //i 自增两次 b.f(); //i 自增两次 b.show(); //i 总共自增四次,i=5 } 8.7基类初始化 构造函数名(参数表) : 基类1(变元表1),基类2(变元表2) 构造参数初始式不仅能初始化基类,还能初始化派生类的成员变量 构造函数先声明后定义的情况,只能在定义时使用参数初始式 class a { public: a(int x); int a; }; class b:public a { public: //b(int x,int y):a(x),b(y){} //用参数初始式简化定义的派生类构造函数 b(int x,int y); //派生类构造函数声明 int b }; b::b(int x,int y):a(x) { b=y; } 构造时:先基类,后派生类 析构时:先派生,类后基类 (早出晚归和迟到早退) 8.8类继承和类包含的比较 类继承 class a { a(int x,int y) { a=x; b=y; } int a,b; }; class b:public a { b(int x,int y,int z):a(x,y) //参数初始式是基类 { c=z; } int c; }; 类包含 class a { public: a(int x,int y):a(x),b(y){} int a,b; }; class b { public: b(int x,int y,int z):pp(x,y),c(z){} //参数初始式是类类型的成员变量 a pp; int c }; 8.9多继承 class 派生类 : 访问控制 基类1 , 访问控制 基类2 …… class a { }; class b { }; class c: public a, public b //基类名表用逗号隔开 { }; 构造顺序是按照基类名表的顺序来,比如上面的例子,顺序是a—>b—>c。(注意,只与基类名表的继承顺序有关,与参数初始式无关) 析构顺序与构造顺序相反 8.10非虚继承 菱形继承的问题 class a { }; class b1:public a { }; class b2:public a { }; class c:public b1,public b2 { }; 此时c会同时生成间接基类a的两个副本,造成访问的二义性 8.11虚继承 虚继承为了解决菱形继承的问题 class a { }; class b1:virtual public a //虚继承 { }; class b2:virtual public a //虚继承 { }; class c:public b1,public b2 { }; 此时c只会生成间接基类a的一个副本,没有二义性 创建派生类对象时,构造函数的执行顺序是 基类构造函数——>对象成员构造函数(类包含时)——>派生类本身的构造函数 派生类初始化时,初始化顺序与构造函数参数初始式无关 多继承初始化时仅调用间接基类自身的构造函数 派生类成员变量初始化式按类中的定义顺序初始化 9.1基类指针访问派生类对象 //正确 动态联编依赖 虚函数 和 基类指针 实现 基类指针仅能访问派生类中继承的基类成员 基类指针强制转换为派生类指针才能访问派生类本身的成员 class a { public: void f1(); int a1; }; class b:public a { public: void f2(); int b2; }; int main() { a *p; //基类指针 b b; //派生类对象 p=&b; p->f1(); //a::f1() cout<<( p->a1 )<<endl; //a::a1 ((b*)p)->f2(); //b::f2() cout<<( ((b*)p)->b2 )<<endl; //b::b2 } 将基类a的指针强制类型转换为派生类b的指针 ( (b*)p )->f2(); //p的外层括号不能丢,因为成员运算符优先级高于强制类型转换运算符 9.2派生类指针访问基类对象 //不安全 class a { public: void f1(); int a1; }; class b:public a { public: void f2(); int b2; }; int main() { a a; b b; b *p; ((a)b).f1(); //a::f1(),派生类对象强制转换为基类对象访问 cout<<((a)b).a1<<endl; //a::a1,派生类对象强制转换为基类对象访问 b.f2(); cout<<b.b2<<endl; p=&a; ((a*)p)->f1(); //a::f1(),派生类指针强制转换为基类指针访问 cout<<( ((a*)p)->a1 )<<endl; //a::a1,派生类指针强制转换为基类指针访问 p->f2(); cout<<( p->b2 )<<endl; } 9.3派生类成员函数调用基类同名成员函数 class a { public: void print(){} }; class b:public a { public : void print() { a::print(); //作用域运算符调用基类print()函数 ( (a*)this )->print(); //将this指针强制类型转换为基类指针,调用基类print()函数 ( (a)(*this) ).print(); //将this对象强制类型转换为基类对象,调用基类print()函数 } }; 9.4虚函数与派生类普通重载函数比较 派生类普通重载函数 class a { public: void print() { cout<<"a::print()"<<endl; } }; class b { public: void print() { cout<<"b::print()"<<endl; } }; int main() { a *p; //基类指针 a a; p=&a; p->print(); //a::print() b b; p=&b; p->print(); //a::print() 基类指针只能访问继承的基类成员print() } 虚函数 一般把virtual关键字写在基类的虚函数中,派生类中可省略 class a { public: virtual void print() { cout<<"a::print()"<<endl; } }; class b:public a { public: void print() //也可写为virtual void print(),派生类中virtual可省略 { cout<<"b::print()"<<endl; } } int main() { a *p; //基类指针 a a; p=&a; p->print(); //a::print() b b; p=&b; p->print(); //b::print() 虚函数多态性 } 注意,虚函数重载时返回值类型,函数名,参数个数,参数类型必须完全相同 如果函数原型不同,仅函数名相同,就是派生类普通函数重载了 如果仅返回值类型不同,则是错误重载,编译不通过 9.4虚析构函数 普通析构函数的问题 class a { public: a() { cout<<"构造a::a()"<<endl; } ~a() { cout<<"析构a::~a()"<<endl; } }; class b:public a { public: b() { cout<<"构造b::b()"<<endl; } ~b() { cout<<"析构b::~b()"<<endl; } }; int main() { a *p; p=new b; //调用a::a(),b::b(),基类指针动态建立派生类对象 delete b; //调用a::~a(),仅调用了基类析构函数,不能释放派生类对象的内存空间,造成内存泄露 p=null; //基类指针置空 } 虚析构 class a { public: a() { cout<<"构造a::a()"<<endl; } virtual ~a() { cout<<"析构a::~a()"<<endl; } }; class b:public a { public: b() { cout<<"构造b::b()"<<endl; } ~b() //或写为virtual ~b(),派生类中virtual可省略 { cout<<"析构b::~b()"<<endl; } }; int main() { a *p; p=new b; //调用a::a(),b::b(),基类指针动态建立派生类对象 delete b; //调用b::~b(),a::~a(),释放基类成员和派生类成员的内存空间 p=null; //基类指针置空 } 9.5纯虚函数 提取一般概念或公共属性,具体实现由派生类完成。 有纯虚函数的基类叫抽象类,具体实现该函数的派生类叫具体类 virtual 类型 函数名(参数表)=0; class a //a是抽象类 { public: virtual void show()=0; }; class b:public a //b是具体类 { public: void show() //派生类中实现show()函数 { cout<<"b::show()"<<endl; } }; 9.6异质链表 在基类中定义基类指针,建立一条由不同派生类组成的单链表 class a { }; class b:public a { }; class c:public a { }; class d:public a { }; void add(a *head,a *p) { p->next=head; head=p; } int main() { a *head=null,*p; p=new b; add(head,p); p=new c; add(head,p); p=new d; add(head,p); } 10.1模板声明 template<typename t1,typename t2,typename t3.....> t1,t2,t3是自定义的形式类型参数,可以实例化为任何类型 10.2函数模板 (函数的模板) 注:模(mú)板 【 先写模板声明,再定义函数模板 】 template<typename 形式类型名> 返回类型 函数名(参数表) { } 例: template<typename t> t max(t a,t b) //类属参数可以作参数,也可以作返回值 { return a>b?a:b; } 10.3模板函数 (通过模板生成的函数) 注:模(mú)板 在运行时根据参数类型自动实例化为对应类型的模板函数 t=max(5,6); //生成模板函数 int max(int a,int b); 10.4重载函数模板 10.4.1通过模板函数重载 例: template<typename t> t max(t a,t b); template<typename t> t max(const t *a,int n); //模板函数重载max 10.4.1通过普通函数重载 例: template<typename t> t max(t a,t b); int max(const char a,const int b); //普通函数重载max 10.5类模板 类模板中的成员函数都是模板函数 成员函数在类外定义时,每个都要有模板声明 template<typename 类属参数名> class 类名 { 类属参数名 变量名; //类属参数在类说明中至少出现一次 返回类型 函数名(参数表); }; 返回类型 类名<t>::函数名(参数表) { } 例: template<typename t> class stu { void f1(t a); t x; }; template<typename t> void stu<t>::f1(t a) { } 类模板实例化为模板类时,成员函数(函数模板)实例化为模板函数 10.6类模板作为函数的参数 template<typename 类属参数名> 返回类型 函数名(类属参数 变量1,类型2 变量2....); 例: template<typename t> class array { }; template<typename t> void fun(array<t> x,int n) { } int main() { array<double> ban; fun(ban,5); } 10.7类模板派生类模板 template<typename t> class a { public: t x; a(int a); }; template<typename t> a<t>::a(int a) //a类构造函数的类外定义 { } template<typename t> class b:public a<t> //继承时,类模板a<t> 派生出 类模板b<t> { public: b(int a,int b); }; template<typename t> b<t>::b(int a,int b) : a<t>(a) //构造函数参数初始式,类模板a<t> 派生出 类模板b<t> { }; 调用: int main() { a<int> a(2); //类模板a<t>实例化为模板类a<int>,生成对象a b<int> b(3,7); //类模板b<t>实例化为模板类b<int>,生成对象b } 10.8类模板派生模板类(普通类) 在继承时实例化类模板 template<typename t> class a { public: t x; a(int a); }; template<typename t> a<t>::a(int a) //a类构造函数的类外定义 { } template<typename t> class b:public a<int> // //继承时,类模板a<t>实例化为模板类a<int>,然后模板类a<int>派生出 模板类b<int> { public: b(int a,int b); }; template<typename t> b<t>::b(int a,int b) : a<int>(a) //构造函数参数初始式,类模板a<t>实例化为模板类a<int>,然后模板类a<int>派生出 模板类b<int> { }; 调用: int main() { a<int> a(2); //类模板a<t>实例化为类模板a<int>,生成对象a b b(3,7); //模板类b<int>生成对象b } 10.9类模板的友元函数 ①一般函数 template<typename t> class x { friend void f1(); //类内声明 }; void f1() //类外定义 { } ②函数模板 template<typename t> class x { template<typename t> friend void f2( x<t> &a ); //类内声明 }; template<typename t> void f2(x<t> &a) //类外定义 { } ③普通类的成员函数 template<typename t> class x { friend void a::f3(); //类内声明 f3()函数是a类的成员函数 }; void a::f3() //类外定义 { } ④类模板的成员函数 template<typename t> class x { template<typename t> friend void b<t>::f4( x<t> &a ); //类内声明 }; template<typename t> friend void b<t>::f4(x<t> &a) //类外定义 { } 10.10类模板的友元类 ①普通类 template<typename t> class x { friend class a; }; ②类模板 template<typename t> class x { template<typename t1> friend class b; //类模板b<t>不加类属参数 }; 10.11.1类模板的静态成员变量 template<typename t> class a { static int num; //类内声明 }; template<typename t> int a<t>::num=0; //类外定义和初始化 10.11.2类模板的静态成员函数 template<typename t> class a { static int show(); //类内声明 }; template<typename t> int a<t>::show() //类外定义 { } 10.12标准模版(stl) 标准模板库由 容器、迭代器、算法组成