C++知识5——类(多态)及一些易错点

类与对象

概念整理

面向对象程序设计的主要特点:抽象、封装、继承和多态。

多态(类型)

多态性: 指一段程序能够处理多种类型对象的能力。(即对同一命令有不同的理解方式)
c++中的多态包括:强制多态,重载多态,类型参数化多态,包含多态

强制多态:

定义:通过一种类型的数据转化为另一种类型的数据。
其实强制多态指的就是强制类型转化(隐式或者显式)

重载多态:

重载多态包括:函数重载和运算符重载

函数重载

我们在这里再复习一下函数重载
函数重载的定义:两个以上的函数,具有相同的类型名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数,自动确定调用哪一个函数。
例如

1
2
3
4
5
6
int add (double a,double b){
return a+b;
}
double add(int a, int b){
return a + b;
}

注意:
重载函数的形参必须不同:形参类型不同或者个数不同。
编译程序对实参和形参的类型及个数进行最佳匹配,来选择调用哪一个函数。
如果函数名相同参数类型也相同(无论函数的返回值类型是否相同),在编译时会被认为是语法错误(函数重复定义)。
当使用具有默认形参值得函数重载形式时,注意防止二义性。
例如:

1
2
3
4
void fun(int a,int b=1,int c=1);
void fun(int a);
//使用时
fun(1);/*此时无法区分使用的是哪个函数。编译器会报语法错误*/

最后,我们来总结一下函数重载的条件:

  • 函数名相同
  • 与函数的返回值类型无关,与形参的名字无关(即形参的标识符)
  • 参数列表必须不同(参数的个数不同、参数的类型不同或者参数排列顺序不同)
  • 仅仅返回值类型不同不足以成为函数重载(反而会报语法错误)
  • 函数重载时要注意二义性,就像之前函数重载与有默认形参值的函数那样,系统会分辨不出你想要调用的函数
运算符重载

运算符重载的定义:对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时导致不同的行为。
运算符重载的实质就是函数重载,在实现过程中,首先把指定的运算表达式转化为对运算符函数的调用,将运算对象转化为运算符函数的实参,然后根据实参的类型来确定所需要调用的函数,这个过程是在编译过程中完成的。
#
规则:

  • c++运算符除了少数几个外都可以重载,必须重载已有运算符
  • 重载之后运算符的优先级和结合性都不会改变。
  • 不能改变原运算符的操作对象个数,同时至少有一个操作对象时自定义类型
    不能重载的运算符:
    类属关系运算符 .
    成员指针运算符 .*
    作用域分辨符 ::
    三目运算符 ?:

#
运算符重载的语法:
重载为类的非静态成员函数

1
2
3
返回值类型  operator 运算符(形参表){
函数体
}

1
2
3
complex complex::operator+(const complex &c2)const{
return complex(real+c2.real,imag+c2.imag);
}

重载为类的非静态成员函数时:函数的参数个数比原来的操作数个数要少一个(后置++和后置–除外)
后置++和后置–不用减一
当重载后置++或者后置–时,函数要带有一个整型形参int

重载为非成员函数

1
2
3
返回值类型  operator 运算符(形参表){
函数体
}

重载为非成员函数时参数个数与操作数个数相同。
提示: 当以非成员函数重载时,要访问类内的数据,可以把这个函数声明为类的友元函数。

前置:

1
2
3
returntype operator U(A obj){
return val;
}

后置:

1
2
3
returntype operator U(A obj,int){
return returntype;
}

oprd1 B oprd2:
operator B (A1 obj1,A2 obj2){

return val;
}
A1、A2必须都要求这个函数是友元,除非其中有不是类的。
#

类型参数化多态:

采用参数化模板,通过给出不同的类型参数,使得一个结构有多种类型。如 C++语言中的函数模板和类模板属于参数多态。参数多态又叫静态多态,它的执行速度快,异常少,调用在编译时已经确定。参数多态是应用比较广泛的一种多态,被称为最纯的多态。

包含多态(包含多态的基础是虚函数):

虚函数:

  • 必须是非静态成员函数。虚函数经过派生之后,在类族中就可以实现运行过程中的多态。
  • 能被派生类自动继承。
  • 虚函数的声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候
  • 语法:
    1
    2
    virtual 函数类型 函数名 (形参表);//函数原型
    //之后实现的时候,函数返回值 类名::函数名(形参){函数体}

#
运行过程中的多态满足的条件:

  • 满足赋值兼容规则
  • 声明虚函数
  • 由成员函数来调用或者通过指针、引用来访问虚函数。
    (如果是使用对象名访问虚函数,则绑定是在编译过程中实现,即静态绑定,而无需在运行过程中进行)
  • 虚函数不能作为内联函数声明,因为内联函数是静态的,而虚函数的调用需要动态绑定,但是将虚函数声明为内联函数也不会引起错误。
    需要强调的是:只有通过基类的指针或引用调用虚函数时,才发生动态绑定。

#
虚析构函数
构造函数不会是虚的,因为构造函数只能自己完成。
虚析构函数语法

1
virtual ~类名();

如果一个类的析构函数是虚函数,那么有他派生来的所有子类的析构函数也是虚函数

纯虚函数与抽象类(P321)
语法:

1
virtual 函数类型 函数名 (参数表)=0;/* 有功能但是不知道怎么实现,无函数体 */

声明为纯虚函数后,基类就可以不再给出函数的实现部分。纯虚函数的函数体由派生类给出。
基类中纯虚函数的函数体的调用,必须通过”基类名::函数名(参数表)”的形式
#
抽象类(去年期末考过)
抽象类:带有纯虚函数的类是抽象类

  • 抽象类不能实例化
  • 通常,抽象类总是在最高层,一个抽象类的作用在于为类的家庭提供一个接口。
  • #

多态(实现)

多态从实现的角度可以分为:编译时的多态和运行时的多态
编译时的多态是在编译过程中确定了同名操作的具体操作对象,后者则是在程序运行过程中才动态地确定所针对的具体对象。
绑定工作在编译连接阶段完成的情况称为静态绑定。
绑定工作在程序运行阶段完成的情况称为动态绑定。

易错点

1:

1
2
3
int a=2;
a+=a-=a*a;
cout<<a;

最后输出为-4
解析:
从右向左:a*a=4
a=a-4;a=-2;
a=a+(-2);a=-4;
2:类的构造函数可以重载
3:内联函数是通过编译器实现的