虚函数

1
2
3
4
5
virtual<类型说明符><函数名>(<参数表>)
//被关键字virtual说明的函数称为虚函数
{
//函数体
}

注意:①通过定义虚函数来使用C++提供的多态机制时,派生类应该从它的基类公有派生。赋值兼容规则成立的前提条件是派生类从其基类公有派生
②使用对象名和点运算符的方式也可以调用虚函数,但是这种调用在编译时进行的是静态联编
③必须首先在基类中定义虚函数
④虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。但是虚函数可以在另一个类中被声明为友元函数。
⑤内联函数、构造函数不能是虚函数。⑥类外定义虚函数不加virtual关键字。

虚析构函数

如果一个类的析构函数是虚函数,那么,由它派生而来的所有派生类的析构函数也是虚析构函数,不管它是否使用了关键字virtual进行说明。

1
virtual ~类名();

虚函数重载与普通函数重载的区别

  • 普通的函数重载时,其函数的参数个数或参数类型必须有所不同,函数的返回类型也可以不同。
  • 当重载一个虚函数时,也就是说在派生类中重新定义虚函数时,要求函数名、返回类型、参数个数、参数的类型和顺序与基类中的虚函数原型完全相同。
  • 如果仅仅返回类型不同,其余均相同,系统会给出错误信息;
  • 若仅仅函数名相同,而参数的个数、类型或顺序不同,系统将它作为普通的函数重载,这时将丢失虚函数的特性。

多继承时的虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
	#include<iostream.h>
class Base1{
public
virtual void fun() //定义fun()是虚函数
{ cout<<"--Base1--\n"; }
};
class Base2{
public
void fun() //定义fun()为普通的成员函数
{ cout<<"--Base2--\n"; }
};
class Derivedpublic Base1,public Base2{
public
void fun()
{ cout<<"--Derived--\n"; }
};
void main()
{
Base1 obj1,*ptr1;
Base2 obj2,*ptr2;
Derived obj3;
ptr1=&obj1;
ptr1->fun();
ptr2=&obj2;
ptr2->fun();
ptr1=&obj3;
ptr1->fun();
ptr2=&obj3;
ptr2->fun();
}
/*运行结果:
--Base1--
--Base2--
--Derived--
--Base2—
*/

纯虚函数与抽象类

1
virtual  <函数类型>  <函数名> ( 参数表 ) = 0

一个具有纯虚函数的类称为抽象类;不允许从具体类派生出抽象类;抽象类不能用作参数类型、函数返回类型或显式转换的类型;可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性;抽象类的析构函数可以被声明为纯虚函数,这时,应该至少提供该析构函数的一个实现。

运算符重载

  • 类属关系运算符“.”、作用域分辨符“::”、成员指针运算符“*”、sizeof运算符和三目运算符“?:”不能重载。
  • 重载之后运算符的优先级和结合性都不能改变,单目运算符只能重载为单目运算符,双目运算符只能重载为双目运算符。
  • 运算符的重载形式有两种:重载为类的成员函数;重载为类的友元函数。
  • 不能改变运算符操作数的个数;不能改变运算符原有的语法结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//重载为类的成员函数格式
<类型> <类名>:: operator <要重载的运算符>(形参表)
{
函数体
}
//重载为类的友元函数格式
friend <函数类型> operator <运算符>(形参表)
{
函数体;
}
//在重载为友元函数的二元运算符被定义时,格式为
<函数返回类型> operator <重载运算符>(<形参1>, <形参2>)
{
函数体;
}
//在重载为友元函数的一元运算符被定义时,格式为
<函数返回类型> operator<一元运算符> (类名 &对象)
{
函数体
}

运算符重载为成员函数

双目运算:oprd1 B oprd2

把B重载为oprd1所属类的成员函数,只有一个形参,形参的类型是oprd2所属类。例如,经过重载之后,表达式oprd1 + oprd2就相当于函数调用

1
oprd1.operator +(oprd2)

单目运算

前置单目运算:U oprd

把U重载为oprd所属类的成员函数,没有形参。例如,“++”重载的语法形式为:

1
<函数类型>  operator  ++( );

++ oprd 就相当于函数调用oprd.operator ++( );

后置单目运算 oprd V

运算符V重载为oprd所属类的成员函数,带有一个整型(int)形参。例如,后置单目运算符“- -”重载的语法形式为:

1
<函数类型>  operator -- ( int );

oprd-- 就相当于函数调用oprd.operator --(0);

总结

对于++(或–)运算符的重载,因为编译器不能区分出++(或–)是前置的还是后置的,所以要加上(int)来区分。

1
2
3
4
operator++();   //重载前置++
operator++(int); //重载后置++
operator--(); //重载前置--
operator--(int); //重载后置--

运算符重载为友元函数

双目运算:oprd1 B oprd2

双目运算符B重载为oprd1所属类的友元函数,该函数有两个形参,表达式oprd1 B oprd2就相当于函数调用operator B (oprd1,oprd2)

单目运算

前置单目运算U oprd

前置单目运算符U重载为oprd所属类的友元函数,表达式U oprd相当于函数调用operator U(oprd)

后置单目运算oprd V

后置单目运算符V重载为oprd所属类的友元函数,表达式oprd V相当于函数调用operator V(oprd,int)

输入输出操作符的重载

输出操作符<<的重载

1
ostream & operator << (ostream &,const 自定义类 &);

输入操作符>>的重载

1
istream & operator >> (istream &, 自定义类 &);

和输出操作符不同的是输入操作符必须处理错误和文件结束的可能性。

比较运算符重载

1
2
bool operator == ( 类型名 ) ;
bool operator != (类型名 ) ;

下标运算符[]重载

1
2
3
4
类型名 &operator [ ] ( int)
//关于左值与右值的区别
a[3] = 5;// 这里用的是double & operator[](int i);
double x = a[3];// 这里用的是double operator[](int i)const;

new与delete运算符重载

1
2
void *operator new ( size_t  size)
void operator delete( void * p )

强制类型转换运算符重载

1
2
3
operator 类型名 () ;
//其后也可以加const限定,也即
operator 类型名 () const;

特点:没有返回值、功能类似强制转换

赋值运算符重载

对于赋值运算符重载一般包括以上几个步骤,首先要检查是否自赋值,如果是要立即返回,如果不返回,后面的语句会把自己所指空间删掉,从而导致错误;第二步要释放原有的内存资源;第三步要分配新的内存资源,并复制内容;第四步是返回本对象的引用。如果没有指针操作,则没有第二步操作。
赋值运算符与拷贝构造函数在功能上有些类似,都是用一个对象去填另一个对象,但拷贝构造函数是在对象建立的时候执行,赋值运算符是在对象建立之后执行。

总结与整理

  • 所有赋值运算符改变左值。所有赋值运算符返回值对于左值应该是nonconst引用
  • 重载不能改变该运算符用于内置类型时的含义,正如程序员不能改变运算符+用于两个int型相加时的含义。
  • 运算符函数的参数至少有一个必须是类的对象或者类的对象的引用,这种规定可以防止程序员运用运算符改变内置类型的函义。
  • 赋值(=)、下标([])、调用(( ))和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数在编译时标记为错误。
  • 像赋值一样,复合赋值操作符通常应定义为类的成员函数。定义成非成员函数不会出现编译错误。

类型重载

1
2
3
4
5
6
7
8
class <类名1>
{
public:
operator <类名2>( );
……
};
<类名1>::operator <类名2>()
{ 函数体; }

其中operator<类名2>为转换函数的函数名,转换函数的作用是将类型1的对象转换成类型2的对象。类中类型转换函数必须是非静态的成员函数,不能定义成友元函数,无返回值类型且不带参数。