类与对象
概念整理
面向对象程序设计的主要特点:抽象、封装、继承和多态。
多态(类型)
多态性: 指一段程序能够处理多种类型对象的能力。(即对同一命令有不同的理解方式)
c++中的多态包括:强制多态,重载多态,类型参数化多态,包含多态
强制多态:
定义:通过一种类型的数据转化为另一种类型的数据。
其实强制多态指的就是强制类型转化(隐式或者显式)
重载多态:
重载多态包括:函数重载和运算符重载
函数重载
我们在这里再复习一下函数重载
函数重载的定义:两个以上的函数,具有相同的类型名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数,自动确定调用哪一个函数。
例如1
2
3
4
5
6int add (double a,double b){
return a+b;
}
double add(int a, int b){
return a + b;
}
注意:
重载函数的形参必须不同:形参类型不同或者个数不同。
编译程序对实参和形参的类型及个数进行最佳匹配,来选择调用哪一个函数。
如果函数名相同参数类型也相同(无论函数的返回值类型是否相同),在编译时会被认为是语法错误(函数重复定义)。
当使用具有默认形参值得函数重载形式时,注意防止二义性。
例如:1
2
3
4void fun(int a,int b=1,int c=1);
void fun(int a);
//使用时
fun(1);/*此时无法区分使用的是哪个函数。编译器会报语法错误*/
最后,我们来总结一下函数重载的条件:
- 函数名相同
- 与函数的返回值类型无关,与形参的名字无关(即形参的标识符)
- 参数列表必须不同(参数的个数不同、参数的类型不同或者参数排列顺序不同)
- 仅仅返回值类型不同不足以成为函数重载(反而会报语法错误)
- 函数重载时要注意二义性,就像之前函数重载与有默认形参值的函数那样,系统会分辨不出你想要调用的函数
运算符重载
运算符重载的定义:对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时导致不同的行为。
运算符重载的实质就是函数重载,在实现过程中,首先把指定的运算表达式转化为对运算符函数的调用,将运算对象转化为运算符函数的实参,然后根据实参的类型来确定所需要调用的函数,这个过程是在编译过程中完成的。
#
规则:
- c++运算符除了少数几个外都可以重载,必须重载已有运算符
- 重载之后运算符的优先级和结合性都不会改变。
- 不能改变原运算符的操作对象个数,同时至少有一个操作对象时自定义类型
不能重载的运算符:
类属关系运算符 .
成员指针运算符 .*
作用域分辨符 ::
三目运算符 ?:
#
运算符重载的语法:
重载为类的非静态成员函数1
2
3返回值类型 operator 运算符(形参表){
函数体
}
1 | complex complex::operator+(const complex &c2)const{ |
重载为类的非静态成员函数时:函数的参数个数比原来的操作数个数要少一个(后置++和后置–除外)
后置++和后置–不用减一
当重载后置++或者后置–时,函数要带有一个整型形参int
重载为非成员函数1
2
3返回值类型 operator 运算符(形参表){
函数体
}
重载为非成员函数时参数个数与操作数个数相同。
提示: 当以非成员函数重载时,要访问类内的数据,可以把这个函数声明为类的友元函数。
前置:1
2
3returntype operator U(A obj){
return val;
}
后置:1
2
3returntype operator U(A obj,int){
return returntype;
}
oprd1 B oprd2:
operator B (A1 obj1,A2 obj2){
…
return val;
}
A1、A2必须都要求这个函数是友元,除非其中有不是类的。
#
类型参数化多态:
采用参数化模板,通过给出不同的类型参数,使得一个结构有多种类型。如 C++语言中的函数模板和类模板属于参数多态。参数多态又叫静态多态,它的执行速度快,异常少,调用在编译时已经确定。参数多态是应用比较广泛的一种多态,被称为最纯的多态。
包含多态(包含多态的基础是虚函数):
虚函数:
- 必须是非静态成员函数。虚函数经过派生之后,在类族中就可以实现运行过程中的多态。
- 能被派生类自动继承。
- 虚函数的声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候
- 语法:
1
2virtual 函数类型 函数名 (形参表);//函数原型
//之后实现的时候,函数返回值 类名::函数名(形参){函数体}
#
运行过程中的多态满足的条件:
- 满足赋值兼容规则
- 声明虚函数
- 由成员函数来调用或者通过指针、引用来访问虚函数。
(如果是使用对象名访问虚函数,则绑定是在编译过程中实现,即静态绑定,而无需在运行过程中进行) - 虚函数不能作为内联函数声明,因为内联函数是静态的,而虚函数的调用需要动态绑定,但是将虚函数声明为内联函数也不会引起错误。
需要强调的是:只有通过基类的指针或引用调用虚函数时,才发生动态绑定。
#
虚析构函数
构造函数不会是虚的,因为构造函数只能自己完成。
虚析构函数语法1
virtual ~类名();
如果一个类的析构函数是虚函数,那么有他派生来的所有子类的析构函数也是虚函数
纯虚函数与抽象类(P321)
语法:1
virtual 函数类型 函数名 (参数表)=0;/* 有功能但是不知道怎么实现,无函数体 */
声明为纯虚函数后,基类就可以不再给出函数的实现部分。纯虚函数的函数体由派生类给出。
基类中纯虚函数的函数体的调用,必须通过”基类名::函数名(参数表)”的形式
#
抽象类(去年期末考过)
抽象类:带有纯虚函数的类是抽象类
- 抽象类不能实例化
- 通常,抽象类总是在最高层,一个抽象类的作用在于为类的家庭提供一个接口。
- #
多态(实现)
多态从实现的角度可以分为:编译时的多态和运行时的多态
编译时的多态是在编译过程中确定了同名操作的具体操作对象,后者则是在程序运行过程中才动态地确定所针对的具体对象。
绑定工作在编译连接阶段完成的情况称为静态绑定。
绑定工作在程序运行阶段完成的情况称为动态绑定。
易错点
1:1
2
3int a=2;
a+=a-=a*a;
cout<<a;
最后输出为-4
解析:
从右向左:a*a=4
a=a-4;a=-2;
a=a+(-2);a=-4;
2:类的构造函数可以重载
3:内联函数是通过编译器实现的