C++复习笔记

 

1.函数

1.1函数地址
【以double simple(int,int);为例】
下面三种均可表示函数地址
simple
&simple
*&simple


1.2函数调用    //注意,调用时是实参
调用方式:函数地址(实际参数表)

【以double simple(int,int);为例】
simple(x,y);
(&simple)(x,y);    //因为小括号的运算符优先级最高,所以必须是(&simple)
(*&simple)(x,y); //因为小括号的运算符优先级最高,所以必须是(*&simple)

//需要返回值时
double t;
t=simple(x,y);
t=(&simple)(x,y);    //因为小括号的运算符优先级最高,所以必须是(&simple)
t=(*&simple)(x,y); //因为小括号的运算符优先级最高,所以必须是(*&simple)


1.3函数类型
定义方式: typedef 类型 函数类型名称(形式参数表);  //注意,typedef最后有分号

【以double simple(int,int);为例】
—— typedef double S(int,int);  
上面的定义是一种自定义类型,它将double (int,int)这种函数类型表达为S类型

既然是类型就可以定义相应的"变量",typedef定义函数类型后,就可以按定义变量的形式写同类型函数的原型声明
—— S a,b;  //就是函数a,函数b的声明,类比于int a,b;的变量声明
上面的S a,b;相当于 double a(int,int);和double b(int,int);这两个函数声明


1.4函数指针
①(定义一个指针变量)

    定义方式1:类型 (*指针变量名)(形式参数表); 
    【以double simple(int,int);为例】
    —— double (*pa)(int,int); //pa就是函数指针
    —— double (*pb)(int,int); //pb就是函数指针
    并且可以与普通的double变量同时定义
    —— double x, y, (*pa)(int,int), (*pb)(int,int);


    定义方式2:函数类型 *指针变量名;
    【以double simple(int,int);为例】
    在上面typedef定义该函数类型为S类型后,可以直接按定义指针的形式定义函数指针
    ——S *pa,*pb;  //pa,pb就是函数指针,类比于int *pa,*pb;



②(定义一种指针类型)
还可以像定义函数类型一样,定义该函数类型的指针类型

    定义方式1: typedef 类型 (指针类型名)(形式参数表); //注意,typedef最后有分号
    【以double simple(int,int);为例】
    —— typedef double (*Pointer)(int,int);  //定义该类函数的函数指针类型名为Pointer
    —— Pointer pa,pb; //pa,pb就是函数指针
    

    定义方式2: typedef 函数类型 *指针类型名
    【以double simple(int,int);为例】
    在上面typedef定义该函数类型为S类型后,可以定义该函数的指针类型
    —— typedef S *Pointer;  //定义S类型函数的函数指针类型名为Pointer
    —— Pointer pa,pb; //pa,pb就是函数指针
    


1.5用函数指针调用函数    //注意,调用时是实参
调用方式:(*指针变量名)(实际参数表)
【以double simple(int,int);为例】
pa=&a; //函数指针pa获得函数地址a
pb=&b; //函数指针pb获得函数地址b
当然pa=&b; pa=&b;也是可以的,因为它们都是是相同函数类型下的函数与函数指针

    
几种等价的调用方式:
double (*pa)(int,int)=a; //函数指针pa获取函数地址
double t; //变量t接收返回值

t=a(1,4); //函数名调用
t=(&a)(1,4); //函数地址调用
t=(*&a)(1,4); //函数地址调用

t=pa(1,4);    //函数指针调用    
//t=(&pa)(1,4); 这个是错误的,pa中保存的是函数a的地址,而&pa获得的是指针pa自身的地址,并不能调用函数a
t=(*pa)(1,4); //函数指针调用
t=(*&pa)(1,4); //函数指针调用




1.6函数指针数组
定义方式1:类型 (*指针数组名[ 整型表达式])(形式参数表); 

【以double simple(int,int);为例】
——double (*pfun[5])(int,int);  //定义double(int,int)函数类型下的 函数指针数组,有5个元素,每个元素都是一个函数指针
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


1.7用函数指针数组调用函数
调用方式:(*指针数组元素)(实际参数表)
S a,b,c,d,e; //5个函数的原型声明,假定各函数的函数体已经定义
S *pfun[5];  //函数指针数组pfun

pfun[0]=a; //函数指针数组元素pfun[0]获取函数地址a
pfun[1]=b; //函数指针数组元素pfun[1]获取函数地址b
pfun[2]=c; //函数指针数组元素pfun[2]获取函数地址c
pfun[3]=d; //函数指针数组元素pfun[3]获取函数地址d
pfun[4]=e; //函数指针数组元素pfun[4]获取函数地址e

int x=1,y=4;
for(int i=0;i<5;i++) //依次调用函数a(x,y), b(x,y), c(x,y), d(x,y), e(x,y)
{
    //调用函数的三种方式
    cout<<(pfun[i])(x,y)<<endl;  
    cout<<(*pfun[i])(x,y)<<endl; 
    cout<<(*&pfun[i])(x,y)<<endl; 
}


1.8指针数组指向多个等长数组(类似于二维数组)
①char类型
//相当于4行6列的二维数组
char *p[4]; //行向量
char a[6],b[6],c[6],d[6]; //列向量
p[0]=a;
p[1]=b;
p[2]=c;
p[3]=d;

//效果图(不一定连续存储)
p[0] ==》 a[0] a[1] a[2] a[3] a[4] a[5] 
p[1] ==》 b[0] b[1] b[2] b[3] b[4] b[5]
p[2] ==》 c[0] c[1] c[2] c[3] c[4] c[5]
p[3] ==》 d[0] d[1] d[2] d[3] d[4] d[5]

访问方式:     二维:   p[i][j]、*(p[i]+j)、*(*(p+i)+j)  // p[0][0] -> p[3][5]

            行向量: p[i]、*(p+i) //char类型数组才可以按行访问 p[0] -> p[3] 


②int类型
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[0] ==》 a[0] a[1] a[2] a[3] a[4] a[5] 
p[1] ==》 b[0] b[1] b[2] b[3] b[4] b[5]
p[2] ==》 c[0] c[1] c[2] c[3] c[4] c[5]
p[3] ==》 d[0] d[1] d[2] d[3] d[4] d[5]

访问方式:     二维:   p[i][j]、*(p[i]+j)、*(*(p+i)+j)  // p[0][0] -> p[3][5]




1.8.X 二维数组
①char类型
char a[4][6];

//二维数组连续存储
a[0][0] a[0][1] a[0][2] a[0][3] a[0][4] a[0][5] 
a[1][0] a[1][1] a[1][2] a[1][3] a[1][4] a[1][5] 
a[2][0] a[2][1] a[2][2] a[2][3] a[2][4] a[2][5] 
a[3][0] a[3][1] a[3][2] a[3][3] a[3][4] a[3][5] 

访问方式:    二维:   a[i][j]、*(a[i]+j)、*(*(a+i)+j)  // a[0][0] -> a[3][5]
            
            行向量: a[i]、*(a+i) //只有char类型数组才可以按行访问 a[0] -> a[3] 

            一维:  char *t=(char*)a; //强制类型转换为一维数组指针
                   t[i]、*(t+i) //t[0] -> t[23] 


②int类型
int a[4][6];

//二维数组连续存储
a[0][0] a[0][1] a[0][2] a[0][3] a[0][4] a[0][5] 
a[1][0] a[1][1] a[1][2] a[1][3] a[1][4] a[1][5] 
a[2][0] a[2][1] a[2][2] a[2][3] a[2][4] a[2][5] 
a[3][0] a[3][1] a[3][2] a[3][3] a[3][4] a[3][5] 

访问方式:    二维:  a[i][j]、*(a[i]+j)、*(*(a+i)+j)  // a[0][0] -> a[3][5]
            
            一维:  int *t=(int*)a; //强制类型转换为一维数组指针
                   t[i]、*(t+i) //t[0] -> t[23] 


1.9指针数组指向多个不等长的数组
int *p[4];
int a[12];
int b[5];
int c[3];
int d[7];
p[0]=a;
p[1]=b;
p[2]=c;
p[3]=d;

//效果图
p[0] ==》 a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] a[10] a[11]
p[1] ==》 b[0] b[1] b[2] b[3] b[4]
p[2] ==》 c[0] c[1] c[2]
p[3] ==》 d[0] d[1] d[2] d[3] d[4] d[5] d[6]

1.10指针数组
类型* 数组名[整型表达式];
int *p[10]; //定义指针数组a[0]到a[9],每个数组元素都是int * 指针,即10个指针

1.11数组指针
类型 (*数组名)[整型表达式];
int a[10];
int (*p)[10]; //定义数组指针*p,指针p指向 类型为int [10]的数组,即1个指针
p=a; 
cout<<p[4]<<endl;

1.12数组指针数组指向多个等长二维数组(三维数组)
类型 (*数组名[整型表达式])[整型表达式];
int (*p[5])[4];  //定义数组指针数组p[0]到p[4],每个数组元素都是指向类型为int(*)[4]数组的指针,共5个数组指针

int a[3][4]= {1};
int b[3][4]= {2};
int c[3][4]= {3};
int d[3][4]= {4};
int e[3][4]= {5};

p[0]=a;
p[1]=b;
p[2]=c;
p[3]=d;
p[4]=e;

//相当于三维数组p[5][3][4]
int x,y,z;
for(x=0; x<5; x++)
{
    for(y=0; y<3; y++)
    {
        for(z=0; z<4; z++)
            cout<<p[x][y][z]<<" ";
        cout<<endl;
    }
    cout<<endl;
}

//效果图
p[0] ==》a[0][0]...............a[0][3]
            ...                   ...
         a[2][0]...............a[2][3]    
         
p[1] ==》b[0][0]...............b[0][3]
            ...                   ...
         b[2][0]...............b[2][3]

p[2] ==》c[0][0]...............c[0][3]
            ...                   ...
         c[2][0]...............c[2][3]

p[3] ==》d[0][0]...............d[0][3]
            ...                   ...
         d[2][0]...............d[2][3]

p[4] ==》e[0][0]...............e[0][3]
            ...                   ...
         e[2][0]...............e[2][3]


1.13数组指针数组指向多个不等长二维数组
int (*p[5])[4];  //定义数组指针数组p[0]到p[4],每个数组元素都是指向类型为int(*)[4]数组的指针,共5个数组指针
    
int a[7][4]={1}; 
int b[3][4]={2}; 
int c[6][4]={3}; 
int d[4][4]={4}; 
int e[2][4]={5}; 

p[0]=a;
p[1]=b;
p[2]=c;
p[3]=d;
p[4]=e;

cout<<p[3][0][0]<<endl; //相当于d[0][0]

//效果图
p[0] ==》a[0][0]...............a[0][3]
            ...                   ...
            ...                   ...
            ...                   ...
            ...                   ...
            ...                   ...
         a[6][0]...............a[6][3]    
         
p[1] ==》b[0][0]...............b[0][3]
            ...                   ...
         b[2][0]...............b[2][3]

p[2] ==》c[0][0]...............c[0][3]
            ...                   ...
            ...                   ...
            ...                   ...
            ...                   ...
         c[5][0]...............c[5][3]

p[3] ==》d[0][0]...............d[0][3]
            ...                   ...
            ...                   ...
         d[3][0]...............d[3][3]

p[4] ==》e[0][0]...............e[0][3]
         e[1][0]...............e[1][3]





2.字符串处理函数
    
2.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.2.1 字符串复制 strcpy (=string copy)
函数原型:char * strcpy(char *s1, const char *s2); 
strcpy(s1,s2); //把s2复制到s1中,类似于变量s1=s2;

2.2.2 字符串复制 strcpy_s (=string copy safe) //仅用于Visual Studio
函数原型:char * strcpy_s(char *s1, int s1_n, const char *s2); //s1_n是字符数组s1的长度,保证复制时不会超出n个,增强安全性
strcpy_s(s1,sizeof(s1),s2); //复制后s1的长度为sizeof(s1),防止溢出

2.2.3 字符串复制前n个 strncpy (=string n copy)
函数原型:char * strncpy(char *s1, const char *s2, int s2_n); 
strncpy(s1,s2,n); //将s2的前n个字符复制到s1中
    
2.2.4 字符串复制前n个 strncpy_s (=string n copy safe) //仅用于Visual Studio
函数原型:char * strncpy_s(char *s1, int s1_n, const char *s2, int s2_n); 
strncpy(s1,sizeof(s1),s2,n); //将s2的前n个字符复制到s1中,并且复制后s1的长度为sizeof(s1),防止溢出



2.3.1 字符串连接 strcat (=string catenate)
函数原型:char * strcat(char *s1,const char *s2);
strcat(s1,s2) //在s1后面连接上s2,并且去掉s1最后的\0

2.3.2 字符串连接 strcat_s (=string catenate safe) //仅用于Visual Studio
函数原型:char * strcat_s(char *s1,int s1_n,const char *s2);
strcat(s1,sizeof(s1),s2); //规定s1和s2连接后的长度为sizeof(s1),防止溢出

2.3.3 字符串连接前n个 strncat (=string n catenate)
函数原型:char * strncat(char *s1,const char *s2,int s2_n);
strncat(s1,s2,n); //将s2的前n个字符连接到s1后面

2.3.4 字符串连接前n个 strncat_s (=string n catenate safe) //仅用于Visual Studio
函数原型:char * strncat_s(char *s1,int s1_n,const char *s2,int s2_n);
strncat_s(s1,sizeof(s1),s2,n); //将s2的前n个字符连接到s1后面,并且连接后s1的长度为sizeof(s1),防止溢出



2.4.1 字符串比较 strcmp (=string compare)
函数原型:int strcmp(char *s1,const char *s2);
strcmp(s1,s2); //逐个比较字符串中字符的ASCII码,如果字符串相等则返回0,如果s1的字符不等于s2的字符则返回ASCII码的差值(s1>s2为正,s1<s2位负)

2.4.2 字符串比较 strncmp (=string n compare) 
函数原型:int strncmp(char *s1,const char *s2,int n); 
strncmp(s1,s2,n); //仅比较s1和s2的前n个字符

    

    
    
    
3.类
类的构造函数和析构函数都没有返回值

3.1构造函数
1. 第一种:类内声明,类外定义
class 类名
{
public:   //构造函数必须是公有的,因为对象在类外定义,构造对象时调用构造函数,如果是私有则无法在类外调用
    类名(参数表); //构造函数的原型声明
};

类名::类名(参数表) //构造函数的类外定义
{
        //构造函数具体定义
}

2. 第二种:类内直接定义
class 类名
{
public:   //构造函数必须是公有的,因为对象在类外定义,在对象构造时调用构造函数,如果是私有则无法在类外调用
    类名(参数表) //构造函数的类内直接定义
    {
            //构造函数具体定义
    }
};


3.2析构函数
1. 第一种:类内声明,类外定义
class 类名
{
public:  //析构函数必须是公有的,因为对象在类外定义,在对象销毁时调用析构函数,如果是私有则无法在类外调用
    ~类名(); //析构函数一定没有参数,析构函数的原型声明
};

类名::~类名() //析构函数一定没有参数,析构函数的类外定义
{
        //析构函数具体定义
}

2. 第二种:类内直接定义
class 类名
{
public:
    ~类名() //析构函数一定没有参数,析构函数的类内直接定义
    {
            //析构函数具体定义
    }
};


构造函数有参数可以重载,析构函数没有参数不能重载

3.3重载构造函数
class 类名
{
public:
    类名(); //无参构造函数的原型声明
    类名(类型1 参数1); //带1个参数的构造函数的原型声明
    类名(类型1 参数1,类型2 参数2); //带2个参数的构造函数的原型声明
    
    ////////以此类推
    
};

类名::类名() //无参构造函数的类外定义
{
        //构造函数具体定义
}

类名::类名(类型1 参数1) //带1个参数的构造函数的类外定义
{
        //构造函数具体定义
}
类名::类名(类型1 参数1,类型2 参数2) //带2个参数的构造函数的类外定义
{
        //构造函数具体定义
}

3.3.1有默认参数的构造函数
class 类名
{
public:
    类名(类型1 参数1=0,类型2 参数2=0); //带2个默认参数的构造函数的原型声明
}

类名::类名(类型1 参数1,类型2 参数2) //带2个参数的构造函数的类外定义(注意,默认参数只在函数第一次出现时写出,一般在原型声明中写出)


3.4复制构造函数 (是一种特殊的重载构造函数,其参数包含一个自身类类型的常引用参数)
class 类名
{
public:
    类名(){}  //类内直接定义的无参且无函数体的构造函数
    类名(const 类名& 引用名); //复制构造函数的原型声明
};

类名::类名(const 类名& 引用名) //复制构造函数的类外定义
{
    //复制构造函数具体定义
}

3.5常成员变量
class 类名
{
public:
    const 类型1 常量1; //变量1是类的常成员变量
    类型2 变量2; //变量2是类的普通成员变量
    
    类名(类型1 变量3,类型2 变量4):常量1(变量3),变量2(变量4)  //构造函数初始化列表,同类型变量给相应变量初始化,常量只能靠构造函数初始化列表赋值
    {
            //构造函数具体定义
    }
};

3.6常成员函数 (一类特殊的成员函数,其this指针被约束为指向常量的常指针)
class 类名
{
public:
    返回值类型 函数名(参数表)const; //常成员函数的原型声明
};
类名::返回值类型 函数名(参数表)const
{
        //常成员函数具体定义
}

调用方式:
int main()
{
类名 对象1;
对象1.常成员函数名(参数表); //调用时不加const
}

3.7常对象
class 类名
{
        //类的具体定义
};

int main()
{
    const 类名 T; //常对象T中所有的成员变量都被约束为const只读
}

3.8静态成员变量
class 类名
{
    static 类型1 变量1;  //静态成员变量,类内声明
    static 类型1 变量2;
};

类型1 类名::变量1=0; //静态成员变量,类外定义和初始化
类型1 类名::变量2=1;
//同类型静态成员变量可以同时定义
类型1 类名::变量1=0,类名::变量2=1;

3.9静态成员函数 //没有this指针
class 类名 
{
public:
    static 返回值类型 函数名(参数表); //静态成员函数的原型声明
};

类名::返回值类型 函数名(参数表) //静态成员函数的类外定义,不加static
{
        //静态成员函数的具体定义
}

调用方式:
1.定义对象调用
int main()
{
    类名 对象1;
    对象1.静态成员函数名(参数表);
}

2.类作用域调用 (因为静态成员是不依赖于对象的,仅对应类)
int main()
{
    类名::静态成员函数名(参数表);
}

*静态成员函数只能访问静态成员变量。
*但是静态成员变量可以被普通成员函数和静态成员函数访问。


3.10友元函数 //没有this指针
友元函数不属于类,它是类外的函数
class 类名
{
    friend 返回值类型1 函数名(类名&,......); //原型声明,友元函数通过类类型的   引用参数  来访问类中成员
    friend 返回值类型2 函数名(类名*,........); //原型声明,友元函数通过类类型的   指针参数  来访问类中成员
};

返回值类型 函数名(类名&,.....) //友元函数类外定义时不加friend关键字
{

}
返回值类型2 函数名(类名*,........); //友元函数类外定义时不加friend关键字
{

}


3.11友元类
class 类名1
{
    friend class 类名2;  //将类2声明为类1的友元
};

class 类名2
{
    类名1 对象1; //以类包含方式访问类1对象
    
    返回值类型 函数名(类1&,.....); //以引用参数方式访问类1对象
};






4.运算符重载

不能重载的运算符:
.    .*    ::    ?:    sizeof

不能用友元函数重载的运算符:
=    ()    []    ->



成员函数,友元函数,普通函数均可重载运算符,但是普通函数需要操纵公有成员实现重载,增加程序开销,所以通常用成员函数和友元函数重载运算符


4.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);


4.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);



4.3重载 前置++  //一元运算符重载
class 类名
{
    类名& operator++(); //成员函数声明
    friend 类名& operator++(类名&); //友元函数声明
};

类名&  类名::operator++() //成员函数重载前置++
{

}

类名&  operator++(类名& 对象名) //友元函数重载前置++
{

}

调用方式:
类名 对象1;
++对象1;

4.4重载 后置++  //一元运算符重载
class 类名
{
    类名& operator++(int);  //成员函数声明,右操作数默认为0
    friend 类名& operator++(类名&,int);  //友元函数声明,右操作数默认为0
};

类名& 类名::operator++(int x) //成员函数重载后置++
{

}

类名& operator++(类名& 对象名,int x) //友元函数重载后置++
{

}
调用方式:
类名 对象1;
对象1++;


4.5重载 赋值运算符(=) //只能成员函数重载,且不能被继承
class 类名 
{
    类名& operator=(类名);
};

类名& 类名::operator=(类名)
{

}
调用方式:
类名 对象1,对象2,对象3;
对象3=对象2=对象1;


4.6重载 []()运算符 //只能成员函数重载
class 类名
{
    类名& operator[](int);
    类名 operator()(int);
};

类名& 类名::operator[](int x)
{

}

类名 类名::operator()(int x)
{

}

调用方式:
类名 对象1;
int x
对象1[x]; //让对象类似于数组形式调用
对象1(x); //让对象类似于函数形式调用


4.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类型,而且必须用友元函数重载运算符(+)



4.8类型转换函数 【类——>基本/类】 //强制类型转换
class 类名
{
    operator 类型();  //成员函数,没有参数,没有返回值
};

类名::operator 类型()    
{
    return 类型值;
}
例:
class complex{
public:      
    int x; //成员变量
    complex(int i) //构造函数
    {
        x=i;
    }
    operator  int(); //类型转换函数声明(将complex类型转为int类型)
};
complex::operator  int() //类型转换函数定义
{      
    return x; //x是类的成员变量,类型是int
}
int main()
{
    complex a(5);      
    int b=2+int(a); //调用类型转换函数,将complex对象强制类型转换为int,完成加法2+5     
    cout<<b<<endl;  //输出6}
}




5.继承

5.1继承语句
class 派生类名:基类名表
{
    
};

基类名表格式: 访问控制   基类1,访问控制   基类2

例:
class A
{
};
class B
{
};

class C: public A, public B  //基类名表用逗号隔开
{
};

5.2公有继承
class B:public A

public ——>public
protected ——>protected
private ——>private //派生类中不可见,但有内存空间


5.3保护继承
class B:protected A

public ——>protected
protected ——>protected
private ——>private //派生类中不可见,但有内存空间


5.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访问。
                  //注意,此访问声明仅写函数名或变量名,没有返回值和参数,和上面的基类成员函数调用是不同的
};


5.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
}

5.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
}

5.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<<A::i<<endl;
    }
};

int main()
{
    B b;
    b.f(); //i 自增两次
    b.f(); //i 自增两次
    
    b.show(); //i 总共自增四次,i=5
}

5.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;
}

构造时:先基类,后派生类 
析构时:先派生,类后基类 
(早出晚归和迟到早退)

5.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
};


5.9多继承
class 派生类 : 访问控制 基类1 , 访问控制 基类2 ……

class A
{
};
class B
{
};

class C: public A, public B  //基类名表用逗号隔开
{
};

构造顺序是按照基类名表的顺序来,比如上面的例子,顺序是A—>B—>C。(注意,只与基类名表的继承顺序有关,与构造函数初始化列表无关)
析构顺序与构造顺序相反


5.10非虚继承
菱形继承的问题
class A
{
};

class B1:public A
{
};
class B2:public A
{
};

class C:public B1,public B2
{
};

此时C会同时生成间接基类A的两个副本,造成访问的二义性


5.11虚继承
虚继承为了解决菱形继承的问题
class A
{
};

class B1:virtual public A //虚继承
{
};
class B2:virtual public A //虚继承
{
};

class C:public B1,public B2
{
};

此时C只会生成间接基类A的一个副本,没有二义性


创建派生类对象时,构造函数的执行顺序是
基类构造函数——>对象成员构造函数(类包含时)——>派生类本身的构造函数

派生类初始化时,初始化顺序与构造函数初始化列表无关
多继承初始化时仅调用间接基类自身的构造函数
派生类成员变量初始化式按类中的定义顺序初始化




6.多态

6.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的外层括号不能丢,因为成员运算符优先级高于强制类型转换运算符

6.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;
}

6.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()函数
    }
};

6.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() 虚函数多态性
}

注意,虚函数重载时返回值类型,函数名,参数个数,参数类型必须完全相同
如果函数原型不同,仅函数名相同,就是派生类普通函数重载了
如果仅返回值类型不同,则是错误重载,编译不通过

6.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; //基类指针置空
}


6.5纯虚函数
virtual 类型 函数名(参数表)=0;
提取一般概念或公共属性,具体实现由派生类完成。
有纯虚函数的基类叫抽象类,具体实现该函数的派生类叫具体类

class A //A是抽象类
{
public:
    virtual void show()=0; //纯虚函数
};

class B:public A  //B是具体类
{
public:
    void show() //派生类中实现show()函数
    {
        cout<<"B::show()"<<endl;
    }
};


6.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);
}





7.模版

7.1模板声明
template<typename T1,typename T2,typename T3.....>
T1,T2,T3是自定义的形式类型参数,可以实例化为任何类型

7.2函数模板 (函数的模板)      注:模(mú)板
【 先写模板声明,再定义函数模板 】

template<typename 形式类型名> //模板声明
返回类型 函数名(参数表)
{
}

例:
template<typename T> //模板声明
T max(T a,T b) //类属参数可以作参数,也可以作返回值
{
    return a>b?a:b;
}

7.3模板函数 (通过模板生成的函数)         注:模(mú)板
在运行时根据参数类型自动实例化为对应类型的模板函数
t=max(5,6); //生成模板函数 int max(int a,int b);

7.4重载函数模板
7.4.1通过模板函数重载
例:
template<typename T> //模板声明
T max(T a,T b);

template<typename T> //模板声明
T max(const T *a,int n); //模板函数重载max

7.4.1通过普通函数重载
例:
template<typename T> //模板声明
T max(T a,T b);

int max(const char a,const int b); //普通函数重载max


7.5类模板
类模板中的成员函数都是函数模板
成员函数在类外定义时,每个都要有模板声明

template<typename 类属参数名> //模板声明
class 类名
{
    类属参数名  变量名; //类属参数在类说明中至少出现一次
    返回类型  函数名(参数表); //类模板的成员函数的原型声明
};

template<typename 类属参数名> //模板声明
返回类型 类名<类属参数名>::函数名(参数表) //类模板的成员函数的类外定义
{
    ……
}

例:
template<typename T> //模板声明
class stu
{
    T x; //类属参数T在类说明中至少出现一次
    void f1(T a); //类模板的成员函数的原型声明
};

template<typename T> //模板声明
void stu<T>::f1(T a) //类模板的成员函数的类外定义
{
    x=a;
}

类模板实例化为模板类时,成员函数(函数模板)实例化为模板函数



7.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);
}


7.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
}

7.8类模板派生模板类(普通类)
在继承时实例化类模板
template<typename T> //模板声明
class A
{
public:
    T x;
    A(int a);
};

template<typename T> //模板声明
A<T>::A(int a) //A类构造函数的类外定义
{
}

class B:public A<int> // //继承时,类模板A<T>实例化为模板类A<int>,然后模板类A<int>派生出模板类B(普通类)
{
    public:
        B(int a,int b);
};

B::B(int a,int b) : A<int>(a) //构造函数初始化列表
{
};

调用:
int main()
{
    A<int> a(2); //类模板A<T>实例化为类模板A<int>,生成对象a
    B b(3,7); //模板类B(普通类)生成对象b
}


7.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) //类外定义
{
}


7.10类模板的友元类
①普通类
template<typename T> //模板声明
class X
{
    friend class A;
};

②类模板
template<typename T> //模板声明
class X
{
    template<typename T> //模板声明
    friend class B;    //类模板B<T>不加类属参数
};


7.11.1类模板的静态成员变量
template<typename T> //模板声明
class A
{
    static int num; //类内声明
};
template<typename T> //模板声明
int A<T>::num=0; //类外定义和初始化

7.11.2类模板的静态成员函数
template<typename T> //模板声明
class A
{
    static int show();    //类内声明
};
template<typename T> //模板声明
int A<T>::show()    //类外定义
{
}


7.12标准模版(STL)
标准模板库由 容器、迭代器、算法组成

 

posted @ 2019-07-21 20:48  AN_drew  阅读(959)  评论(1)    收藏  举报