第二章 c++对c的扩充
c++程序的输入与输出
1.endl和“\n”的作用相同,但当有缓冲区的流时,endl清空缓冲区。
2.
长效:对设置后的所有输入和输出都有效
短效:只对当前输入和输出有效
用const定义常变量
在c语言中,常用#define定义符号常量
#define PI 3.14
这只能在预编译时进行字符置换,这个PI不是变量,没有类型,不占用存储单元。c++提供了const定义常变量的方法,下面的PI有数据类型,占用存储单元,此变量的值是固定的,不能改变。
const float PI = 3.14
内联函数
inline 函数类型 函数名(形式参数表)555
语句序列
}
注意:1.函数体内有循环,switch和复杂嵌套的if语句时,不可定义内联函数
2.用户的内联,只是一个建议。是否“内联”取决编译器。
函数重载
int add(int x, int y);
double add(double x, double y); //形参类型不同
double add(int x, int y, int z); //形参个数不同
注意:不能根据返回类型的不同来实现函数的重载,编译器只根据参数类型和形参个数来实现。
函数模板
函数模板定义一般格式如下:
template <typename T1,typename T2>
注意:尖括号中的typename可以用class代替
例子:调用两个求和函数,分别求两个int数,两个float数之和
#include <iostream>
using namespace std;
template <typename T>
T add(T a, T b)
{
return a + b;
}
int main()
{
int m = 10, n = 20;
float u = 2.5, v = 4.6;
cout<<"m n="<<add(u,v)<<endl;
cout<<"m+n="<<add(m,n)<<endl;
return 0;
}
注意:T的类型要完全一样
程序运行结果
带默认参数的函数
函数的原型如下:
float vol(float h, float r = 12.5)
函数的调用可以采用以下形式:
vol(45.6); //相当于vol(45.6, 12.5)
vol(34.2, 10.4); //h的值为34.2,r的值为10.4
引用的基本概念
引用的语法格式: 数据类型 &引用标识符 = 目标变量;
例子:
int nValue;
int &rInt = nValue;
注意:1.声明引用时必须进行初始化
2.引用初始化后不能在被重新声明为另一个变量的别名
3.取引用的地址时,只能取到目标变量的地址
引用作为函数参数
通过引用传递,容易癌变引用的目标值,建议使用常引用,常引用的声明语法格式如下:
const 数据类型 &引用标识符 = 目标变量名;
例子:
int nValue;
const int &rInt = nValue;
nValue = 100; //正确
rInt = 200; //错误,不能通过常引用改变目标变量值
引用作为函数返回值
语法格式如下:
数据类型& 函数名(形参列表)
{函数体}
注意:函数返回引用时不能返回局部变量
第三章 类和对象
类成员的访问控制
注意:一个类声明两个对象,两个对象有各自的属性数据,共享函数代码
构造函数
默认构造函数
注意:1.C++编译器会自动产生一个默认构造函数
2.自己定义了有参数的构造函数,同时又需要无参数的构造函数,这时就需要自己在定义一个无参数的构造函数,编译器默认的构造函数只有在自己没有定义构造函数时才会自动创建
###定义构造函数
注意:1.构造函数的名字与类名相同
2.构造函数没有返回类型
3.构造函数的权限为公有
4.构造函数的调用方法是自动,隐式进行的
5.构造函数可以重载,可以为参数提供默认值
6.构造对象指针和对象引用时,不会调用构造函数
构造函数和运算符new
当使用new建立一个动态对象时,new首先分配足够的内存空间,然后自动调用构造函数初始化内存,释放所在内存用delete
构造函数的默认参数
如果定义了自己的有参构造函数,又想使用无参的构造函数,就使用带默认参数的构造函数
拷贝构造函数
拷贝构造函数是类构造函数中的一种,用途是让类的对象能够相互初始化。C++规定,每个类必须提供一个拷贝构造函数,如果没有定义,系统会自动提供一个拷贝构造函数。
拷贝构造函数的语法格式如下:
类名::类名(const 类名&)
{
函数体
}
注意:1.拷贝构造函数只有一个参数,参数的类型必须是自身类的引用
2.为防止对被复制对象的修改,参数类型常使用const限定
3.如果定义拷贝构造函数,编译器就会调用程序员自己定义的
析构函数
定义析构函数
class 类名
{
public:
~类名(); //析构函数原型声明
}
类名::~类名() //类外定义析构函数,析构函数也可以在类体内定义
{
函数体
}
注意:
1.析构函数没有返回类型,也不能带参数,但可以显示说明参数为void,不能重载
2.析构函数的权限必须是public
3.析构函数的调用方式是自动,隐式进行的
4.用new创建对象时,会自动调用构造函数;用delete释放内存时,会自动调用析构函数
5.析构函数的调用顺序:先构造后析构,后构造先析构
析构函数和运算符delete
用运算符delete删除一个动态对象时,首先为该对象调用析构函数,然后在释放该对象所占的内存。
int main()
{
Point *ptr = new Point[2];
delete []ptr;
return 0;
}
类的组合
概念:一个类中内嵌其他类,之间的关系是包含与被包含的关系。类的组合也称作类的聚合
组合类的构造函数和析构函数
例子:
#include <iostream>
using namespace std;
class Cstudent
{
public:
Cstudent();
~Cstudent();
};
Cstudent::Cstudent()
{
cout<<"Cstudent构造函数被调用"<<endl;
}
Cstudent::~Cstudent()
{
cout<<"Cstudent析构函数被调用"<<endl;
}
class CClass
{
public:
CClass();
~CClass();
private:
Cstuent std[3];
};
CClass::CClass()
{
cout<<"CClass构造函数被调用"<<endl;
}
CClass::~CClass()
{
cout<<"CClass析构函数被调用"<<endl;
}
int main()
{
CClass cls;
return 0;
}
运行结果
组合类的初始化
类名::类名(形参列表):内嵌对象1(形参列表),内嵌对象(形参列表),..
第四章 特殊函数和成员
不同对象间的数据共享
静态数据成员
静态数据成员采用static关键字来声明,语法格式如下:
static 数据类型 数据成员名;
静态数据成员不属于某个对象,而是类中所有对象共享。
访问静态数据成员:
类名::静态数据成员
初始化:
数据类型 类名::静态成员函数名 = 初始值; //不能缺少数据类型
注意:
1.编译时就被分配内存
2.静态数据成员只能做一次定义性说明
3.一般不在类的构造函数中给静态成员赋初值,而是在对静态数据成员的定义性说明时赋初值
静态成员函数
静态成员函数不需要类的对象就可以直接访问。语法格式如下:
static 返回值类型 成员函数名(参数列表);
静态成员函数只能通过类名访问。语法格式如下:
类名::成员函数名
注意:
1.类的静态成员函数只能访问该类的静态数据成员和静态成员函数。
2.静态成员为该类的所有对象共享,它们被存储在一个公共内存中
静态对象
1.整个程序中,构造函数只调用一次(例如在循环中)
2.析构函数也只调用一次
友元的基本概念
友元分两种:友元函数和友元类
特点:
1.友元关系声明语句放在任何访问权限标号后面都可以
2.友元关系是单向的,A是B的友元,B不一定是A的友元
3。友元关系不能传递,如A是B的友元,B是C的友元,A不一定是C的友元
友元函数
语法格式如下:
friend 返回值类型 函数名(参数列表)
例子:
不属于这个类的成员函数
#include <iostream>
using namespace std;
class Csms
{
private:
string name;
friend void check(Csms &sms);
}
void check(Csms &sms)
{
sms.name.length();
}
int main()
{
Csms sms;
check(sms);
return 0;
}
类中声明的友元是另一个类的成员函数
#include <iostream>
using namespace std;
class Time
{
private:
int hour;
public:
void display(Date &);
}
class Date
{
private:
int mouth;
public:
friend void Time::display(Date &);
}
void Time::display(Date &d)
{
d.hour;
}
友元类
声明格式如下:
friend 类名
例子:
#include <iostream>
using namespace std;
class Two
{
private:
int y;
public:
friend class One; //声明One为Two的友元
};
class One
{
private:
int x;
public:
void Display(Two &);
};
void One::Display(Two &r)
{
r.y;
}
关键字const
用途:数据在一定范围内共享,有保证它不被任意修改
常对象
语法格式:
类名 const 对象名[(实参列表)]; //或const 类名 对象名[(实参列表)]
//这两种声明是等价的
注意:
1.常对象的所有数据成员都不能被修改
2.常对象不能调用该对象的非const成员函数(除了由系统自动调用的隐式构造函数和析构函数),但可以访问常成员函数
常对象成员
关键词const声明的数据成员,该数据成员称为常数据成员,其值是不能改变的,包括普通常数据成员,静态常数据和常引用。
例子:
#include <iostream>
using namespace std;
class Base
{
private:
int x;
const int a; //常数据成员只能通过初始化列表来获得初值
static const int b; //静态常数据成员
const int &r; //常引用只能通过初始化列表来获得初值
public:
Base(int ,int );
}
const int Base::b = 125;
Base::Base(int i, int j):x(i), a(j), r(x)
{}
注意:
1.静态常数据成员保留了静态数据成员成员特征,需要在类外初始化
2.常数据成员和常引用只能通过构造函数的参数初始化列表来获得初值
常成员函数
声明常成员函数的语法格式如下:
类型标识符 函数名(参数列表) const;
定义常成员函数格式如下:
类型标识符 类名::函数名(参数列表)const {函数体}
类中用内联函数定义const函数,格式如下:
类型标识符 函数名(参数列表) const {函数体}
注意:
1.const成员函数的主要作用是禁止在函数体内更新实例的数据成员,所以函数体内不能改变数据成员的值,不能调用非const成员函数
2.const对象只能调用const成员函数,不能调用非const成员函数
2.非const对象既可以调用非const成员函数,又可以调用const成员函数,当两者同时存在时,优先调用非const成员函数
使用const限定指针
常量指针
语法格式如下:
const 数据指针类型 *指针变量名
例子:
long lValue1 = 100;
long lValue2 = 200;
const long *plValue = &lValue1;
*plValue = 200; //错误,编译会出错,常量指针无法改变指向变量的值
lValue2 = 100; //正确,变量本身的值可以改变
plValue = &lValue2; //正确,指针本身的值是可以改变的
分析:定义了常量指针后,其实是告诉编译系统,通过这个指针,只能作为一个常量来访问,即“*变量名”不能作为左值运算
指针常量
语法格式如下:
数据类型名 * const 指针变量名
例子:
long lValue1 = 100, lValue2 = 200;
long * const plValue = &lValue1;
plValue = &lValue2; //错误,指针以指向lValue1,不能改变
lValue1 = 200; //正确,指针所指向的变量本身可以被改变
分析:定义了指针变量,其实是告诉编译系统指针指向的地址已经确定,不能改变这个指向,即“指针变量“不能再出现在赋值运算的左边
常量指针常量
它所指向的值不能改变,其本身的值也不能被改变
语法格式如下:
const 数据类型名 * const 指针类型名;
例子:
long lValue1 = 100, lValue2 = 200;
cosnt long * const plValue = &lValue1;
*plValue = 200; //错误,通过常量指针常量间接访问,不能改变变量的值
*plValue = &lValue2; //错误,常量指针常量本身的值不能改变
lValue = 200; //正确,变量本身的值不受影响
分析:定义了常量指针常量后,其实是告诉了编译系统”*指针变量名“和”指针变量“不能在出现在赋值运算的左边,即不能被赋值
类模板
类模板的定义
语法格式如下:
template <模板参数列表>
class 类名
{
//类体
}
在类体外定义成员函数时,必须用template重写类模板的声明。语法格式如下:
template <模板参数列表>
返回类型名 类名<模板参数列表>::成员函数名(函数参数列表)
注意:
1.template是一个声明模板的关键字,表示声明的类是一个模板
2.<模板参数列表>中包含一个或者多个类型参数项,每一项由关键字typename或class后加一个用户自定义的标识符组成,该标识符为类型参数
3.类模板中可以定义数据成员,函数成员,还可以定义构造函数和析构函数
4.在使用类模板时,必须先将其实例化,即用实际的数据类型替代模板参数
5.当类模板中的成员函数在类定义体外定义时,必须定义一个函数模板的形式
类模板定义举例。代码如下:
template <typename T> //带参数T的模板声明,typename可用class代替
class TAnyTemp
{
private:
T x, y; //类型为T的私有数据成员
public:
T TAnyTemp(T x, T y); //声明构造函数
T getx();
T gety();
};
template <typename T> //定义成员函数时必须再次声明模板
TAnyTemp<T>::TAnyTemp(T a, T b)
{
x = a;
y = b;
}
template <typename> //定义成员函数时必须再次声明模板
T TAnyTemp::getx()
{
return x;
}
template <typename> //定义成员函数时必须再次声明模板
T TAnyTemp::gety()
{
return y;
}
类模板的实例化
语法格式如下:
模板类型<实际类型名> 对象名;//或 类模板名<实际类型名> 对象名(构造函数实参列表);
例子:
#include <iostream>
using namespace std;
template <typename T>
class Max4
{
private:
T a, b, c, d;
T Max(T a, T b)
return (a > b)?a:b;
public:
Max4(T, T, T, T);
T Max();
};
template <typename T>
Max4<T>::Max4(T x1, T x2, T x3, T x4): a(x1), b(x2), c(x3), d(x4)
{}
template <typename T>t
T Max4<T>::Max()
{
return Max(Max(a, b), Max(c, d));
}
int main()
{
Max4<char> C('W','w','a',''A);
}
类模板的模板参数表
类模板中的模板参数表中除了有模板参数外,还可以有普通参数
例子:
template <typename T, int i> //具有模板参数T和普通参数i的类模板
class TestClass
{
private:
T buffer[i]; //T类型数组buffer,数组大小随普通形参i的大小而改变
}
对具有一个模板参数T和一个普通参数i的类模板TestClass,在类体外定义其成员函数getData的形式如下:
template <typename T, int i>
T TestClass<T, i>::getData()
{....}
实例化
TestClass <double, 8> dobj1, dobj2;
第五章 运算符重载
比如我们常用的运算符“+”,对整数,单镜度数和双精度数进行加法运算。
运算符重载函数定义的语法格式如下:
函数类型 operator 运算符名称(形参列表)
{
运算符的重载处理
}
不能重载的运算符 有5个,分别如下:
- . (成员访问运算符)
- *(成员指针访问运算符)
- :: (域运算符)
- sizeof (长度运算符)
- ?:(条件运算符)
注意:
1.重在不能改变运算符算法对象的个数,优先级和结合性
2.重载运算符的函数不能带默认参数
3.重载的运算符必须与用户的自定义类型对象共同使用,其参数至少有一个是类对象或者类对象的引用
类成员函数重载运算符
当运算符重载为类的成员函数(除“++”和“–”运算符)时,函数的参数个数会比原来的操作数少一个,原因是当某个对象使用了重载的成员函数时,自身的数据可以直接访问,不需要将自身的数据放在参数表中
对单目运算符X,比如“+”(取正),“-”(取负),将其重载为类的成员函数,用来实现X operand ,其中operand为类的对象,X就需要重载为类的成员函数,函数不需要形参。当使用X operand的运算式时,相当于调用operand.operator X()。
对于双目运算符Y,比如“+”(加),“-”(减)等,将其重载为类的成员函数,用来实现operand1 Y operand2,其中operand1为类的对象,operand2为 其他与operand1相同或者可以转换为相同类型的操作数。当使用operand1 X operand2的运算式时,相当于调用operand1.operator Y(operand2)
例子:
(1)对两个正方形对象实现“+”运算
(2)对两个正方形对象实现“+=”运算
(3)对两个正方形对象实现“==”运算
#include <iostream>
#include <cmath>
using namespace std;
class square
{
private:
double s, area;
public:
square(double = 0);
square operator + (square);
square operator +=(square);
int operator ==(square);
void display();
}
square::square(double x)
{
s = x;
area = s*s;
}
square square::operator +(square u)
{
square t;
t.area = area + u.area;
t.s = sqrt(t.area);
return t;
}
square square::operator +=(square x)
{
area += x.area;
s = sqrt(area);
return *this;
}
int square::operator == (square x)
{
if(s == x.s)
return 1;
else
return 0;
}
void square::display()
{
cout<<"正方形边长为:"<<s<<'\t';
cout<<"正方形面积为:"<<area<<endl;
}
int main()
{
square a(2);
c = a +b;
cout<<"对象c的数据为:";
c.display();
a += c;
cout<<"对象a的数据为:";
a.display();
cout<<"a == b之值为:"<<(a == b)<<endl;
return 0;
}
程序运算结果如图:
C++规定,对于前置的“++”不用形参,而对于后置的“++”,用一个int形参,以示区别。这个int形参只是表明后置“++”而已,不起任何作用,并且在函数体内也不会出现int形参。例如:a=ck++,系统自动调用成员函数operator++()进行运算。运算符对象ck值“自增”,但返回的是ck的原值。当执行b=++ck,系统自动由对象ck调用成员函数operator++(int)进行运算,运算使对象ck值自增,返回的是ck自增后的值。
友元函数重载运算符
对于单目运算符X,比如“+”(取正),“-”(取负)等,用来实现X operand,其中operand为类的对象,则X需要重载为类 的友元函数,函数的形参为operand。当使用X operand的运算式时,相当于调用operator X(operand)
对于双目运算符Y,比如“+”(加),“-”(减)等,用来实现operand1 X operand2,其中operand1和operand2为类的对象 ,则X需要重载为类的友元函数,函数有两个参数。当使用operand1 Y operand2的运算式时,相当于调用operator Y(operand1,operand2)
例子:
#include <iostream>
using namespace std;
class square
{
private:
double s, area;
public:
square(double = 0);
friend square &operator +=(square&, square);
void display();
};
square::square(double x)
{
s = x;
area = s*s;
}
square &operator +=(square &x, square y)
{
x.area += y.area;
x.s = squrt(x.area);
return x;
}
void square::display()
{
cout<<"正方形边长为:"<<s<<'\t';
cout<<"正方形面积为:"<<area<<endl;
}
int main()
{
square a(3), b(4);
a += b;
cout<<"对象a的数据为:";
a.display();
cout<<"对象b的数据为:";
b.display();
return 0;
}
程序运行结果:
转换运算符重载
为了使自定义的数据类型也能支持数据的转换,就需要重载转换运算符。转换运算符即强制类型转换运算符,它把一种类的对象转换为其他类的对象或内部类型的对象,该运算符必须是一个非static成员函数,而不是友元函数
语法格式如下:
operator 类型名();
在生命中没有指定返回类型,但是类型名已经表明了其返回类型,所以不用指定返回类型
例子:
#include <iostream>
#include <cmath>
using namespace std;
class complex
{
private:
double real, imag;
public:
complex(double = 0.0, double = 0.0);
void show(complex);
operator double(); //声明转换运算符重载
};
complex::complex(double pr, double pi)
{
real = pr;
imag = pi;
}
void complex::show(complex c)
{
cout<<"("<<c.real<<","<<c.imag<<"i)"<<endl;
}
complex::operator double(()
{
return sqrt(real*real + imag*imag); //定义转换运算符重载
}
int main()
{
complex a(1,2),b(3,4);
double c = (double)a; //显式转换
cout<<c<<endl;
c = a+b; //显式转换
cout<<c<<endl;
return 0;
}
程序运行结果如图:
注意:对于转换运算符的重载,需要注意转换二义性的问题:同一类型提供多个转化路径,会导致编译错误。
例子:
class A
{
public:
A(B &b); //构造函数,用B对象构造A对象
}
class B
{
public:
operator A(); //转换运算函数,将B类对象转换为A对象
};
int main()
{
B b;
A a= A(b) ; //错误!编译系统无法判断是构造函数还是转化运算符
}
第六章 继承与派生
继承与派生的基本概念
单一继承派生类声明的一般格式如下:
class 派生类名:[继承方式]基类名
{
成员声明;
};
生成派生类的步骤
(1)继承基类成员
(2)改造基类成员:改造主要针对基类成员的访问控制和对基类成员的覆盖和重载。当派生类定义了与基类相同的成员(当成员函数和参数都相同的时候),通过派生类或者派生类实例只能访问派生类中的成员,这称为同名覆盖;当派生类中声明了函数名相同,参数不同的时候,称为重载
(3)为派生类增加新成员
派生类的访问属性
共有继承
私有继承
保护继承
三种派生方式的访问权限
赋值兼容
- 派生类对象可以赋值给基类的对象
- 派生类对象可以初始化积累的引用
- 派生类对象的地址可以赋值给指向基类的指针
简单派生类的构造函数
形式如下:
派生类构造函数名(总参数列表):基类构造函数名(基类参数列表)
{
派生类中新增数据成员初始化语句
}
执行派生类构造函数的顺序是:
- 调用基类构造函数,对基类数据成员初始化;
- 在执行派生类构造函数,对派生类数据成员初始化
有子对象的派生类的构造函数
形式如下:
派生类构造函数(总参数列表):基类构造函数名(基类参数列表),子对象名(参数列表)
{
派生类中新增数据成员初始化语句
}
执行派生类构造函数的顺序是:
- 调用基类构造函数,对基类数据成员初始化
- 调用子对象构造函数,对子对象数据成员初始化
- 最后执行派生类构造函数,对派生类数据成员初始化
多层派生时的构造函数
一个类派生出一个派生类,派生类继续往下派生,这种情况称为多层派生
分析:
- 每一层派生类的构造函数,只需写其上一层派生类(即他的直接基类)的构造函数即可
派生类的析构函数
- 派生类不能继承基类的析构函数,但可以通过派生类的析构函数去调用基类的析构函数。
- 析构函数调用的顺序与构造函数正好相反,先执行派生类自己的析构函数,然后调用子对象的析构函数,最后调用基类的析构函数
多重继承
多重继承派生类的声明,一般声明格式如下:
class 派生类名:继承方式 基类名1, 继承方式 基类名2, ...,继承方式 基类名n
{
成员声明;
};
例子:
class D:public A, private B, protected C
{
类D新增的成员;
};
多重继承派生类的构造函数
一般形式如下:
派生类构造函数名(总参数列表):基类1构造函数(参数列表), 基类2构造函数(参数列表),...,基类n构造函数(参数列表)
{
派生类中新增数据成员初始化语句;
}
分析:
- 各基类的排列顺序是任意的。调用基类构造函数的顺序是按照声明派生类时基类出现的顺序
- 先调用基类的构造函数,在执行派生类的构造函数的函数体
二义性
在多重继承时,如果两个基类有同名成员或者两个基类和派生类三者之间都有同名成员时,可能会造成对基类中某个成员的访问出现不唯一的情况,此时对基类成员的访问产生二义性。解决方法有三种。
- 通过作用域运算符(::),明确指出访问的是哪一个基类中的成员。作用域运算符进行限定的一般格式如下:
对象名.基类名::成员名 //数据成员
对象名.基类名::成员名(参数列表) //成员函数
- 在派生类中定义同名成员。根据同名覆盖的原则,访问派生类的同名函数。
- 使用虚基类
虚基类
派生类有多个直接基类,而这些直接基类又有一个共同基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。在引用这些同名成员时,必须 在派生类对象名后增加直接基类名,以免产生二义性。C++提供虚基类的方法,使得在继承间接共同基类时只保留一份成员。声明虚基类的一般形式如下:
class 派生类名 : virtual 继承方式 基类名
class N
{
public:
int a;
void fun();
};
class A : virtual public N{}; //虚拟继承
class B : virtual public N{}; //虚拟继承
class C : public A, public B {};
注意:为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类,否则仍然会出现对基类的多次继承。
虚基类的初始化
虚基类中定义了带参数的构造函数,且又没有定义默认构造函数,则在其所有派生类(包括直接派生类或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。
例子:
class N
{
public:
N(int i);
};
class A:virtual public N
{
public:
A(int i):N(i){}
};
class B:virtual public N
{
public:
B(int i):N(i){}
};
class C:public A, public B
{
public:
C(int i):A(i), B(i), N(i){}
}
分析:在类C的构造函数中,对所有的基类都进行了初始化。即在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类进行初始化。
第七章 多态性与虚函数
虚函数
可以通过基类指针或引用访问基类和派生类中的虚函数。这是通过虚函数实现动态关联的一种方式。
定义如下:
virtual 函数类型 成员函数名(参数列表)
{函数体}
说明:
- 虚函数一定是成员函数,不能为非成员函数。静态成员函数不能被声明为虚函数
- 虚函数与一般成员函数一样,可以定义在类体内,也可以定义在类体外。在类体外定义虚函数时,不必加上关键词virtual
在虚函数实现动态联编时,需要满足以下3个条件:
- 类与类之间的继承关系满足赋值兼容规则
- 改写了同名虚函数
- 根据赋值兼容规则使用指针(或引用)
虚析构函数
当new运算符动态生成一个派生类的对象,并让基类指针指向该派生类对象。当程序用带指针参数的delete运算符撤销对象时,会发生一种情况:系统只执行基类的析构函数,而不执行派生类的析构函数。
定义如下:
virtual ~类名()
{}
注意:
- 如果将基类的析构函数声明为虚函数,则由该基类所派生的所有派生类的析构函数也将自动成为虚函数,即使派生类的析构函数与积累的析构函数名字不同。
- 构造函数不能声明为虚函数。这是因为在执行构造函数时,类对象还未完成建立过程
纯虚函数
基类中的某一成员函数不是基类本身的需求,而是考虑到派生类的需要,在基类中预留一个函数名,具体功能给派生类去定义。
一般形式如下:
virtual 函数类型 函数名(参数列表)=0;
说明:
- 这是一个声明语句,后面因该有分号
- 纯虚函数不能被调用
- 只有在派生类中对该函数进行定义后,才具备函数的功能,才可以被调用
- 在派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数
抽象类
抽象类声明了一族派生类的共同接口,而这些接口将在抽象类中定义,但不做具体实现。抽象类不能被实例化。