类的继承,是新的类从已有类那里得到已有的特性。从另一个角度来看这个问题,从已有类产生新类的过程就是类的派生。
派生类的定义
class 派生类名:继承方式 基类名1,继承方式 基类名2
{
派生类成员声明
}
访问控制
基类的成员可以有public(公有)、protected(保护)和private(私有)三种访问属性。
基类的自身成员可以对基类中任何一个其他成员进行访问,但是通过基类的对象,就只能访问该类的公有成员。
类的继承方式有public(公有继承)、protected(保护继承)和private(私有继承)三种。
不同的继承方式,导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。
- 派生类中的新增成员访问从基类继承的成员
- 在派生类外部(非类族内的成员),通过派生类的对象访问从基类继承的成员
公有继承
当类的继承方式为公有继承时,基类的公有成员和保护成员的访问属性在派生类中不变,而基类的私有成员不可直接访问。也就是说
- 基类的公有成员和保护成员被继承到派生类中访问属性不变,仍作为派生类的公有成员和保护成员,派生类的其他成员可以直接访问它们。
- 在类族之外只能通过派生类的对象访问从基类继承的公有成员,
- 无论是派生类的成员还是派生类的对象都无法直接访问基类的私有成员。
//Rectangle.h
class Point //基类Point类的声明
{
public: //公有函数成员
void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private: //私有数据成员
float X,Y;
};
class Rectangle: public Point //派生类声明部分
{
public: //新增公有函数成员
void InitR(float x, float y, float w, float h)
{
InitP(x,y);
W=w;H=h;
} //调用基类公有成员函数
float GetH() {return H;}
float GetW() {return W;}
private: //新增私有数据成员
float W,H;
};
//7_1.cpp
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
void main()
{
Rectangle rect; //声明Rectangle类的对象
rect.InitR(2,3,20,10); //设置矩形的数据
rect.Move(3,2); //移动矩形位置
cout<<"The data of rect(X,Y,W,H):"<<endl;
cout<<rect.GetX()<<"," //输出矩形的特征参数
<<rect.GetY()<<","
<<rect.GetW()<<","
<<rect.GetH()<<endl;
}
主函数中首先声明了一个派生类的对象rect,对象生成时调用了系统所产生的默认构造函数,这个函数的功能是什么都不做。然后通过派生类的对象,访问了派生类的公有函数initR,move等,也访问了派生类从基类继承来的公有函数getX(),getY()。这样我们看到了,从一个基类以公有方式产生了派生类之后,在派生类的成员函数中,以及通过派生类的对象如何访问从基类继承的公有成员。
私有继承
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可直接访问。也就是说
- 基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类族外部通过派生类的对象无法直接访问它们。
- 无论是派生类的成员还是通过派生类的对象,都无法直接访问从基类继承的私有成员。
//rectangle.h
class Point //基类声明
{
public:
void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private:
float X,Y;
};
class Rectangle: private Point //派生类声明
{
public: //新增外部接口
void InitR(float x, float y, float w, float h)
{InitP(x,y);W=w;H=h;} //派生类访问基类公有成员
void Move(float xOff, float yOff) {Point::Move(xOff,yOff);}
float GetX() {return Point::GetX();}
float GetY() {return Point::GetY();}
float GetH() {return H;}
float GetW() {return W;}
private: //新增私有数据
float W,H;
};
同样,派生类Rectangle继承了Point类的成员,因此在派生类中,实际所拥有的成员就是从基类继承来的成员与派生类新成员的总和。继承方式为私有继承,这时,基类中的公有和保护成员在派生类中都以私有成员的身份出现。派生类的成员函数及对象无法访问基类的私有成员(例如基类的x,y)。派生类的成员仍然可以访问到从基类继承过来的公有和保护成员(例如在派生类函数成员initRectangle中直接调用基类的函数initPoint),但是在类外部通过派生类的对象根本无法直接访问到基类的任何成员,基类原有的外部接口(例如基类的getX()和getY()函数)被派生类封装和隐蔽起来。当然,派生类新增的成员之间仍然可以自由地互相访问。
在私有继承情况下,为了保证基类的一部分外部接口特征能够在派生类中也存在,就必须在派生类中重新声明同名的成员。这里在派生类Rectangle中,重新声明了move,getX,getY等函数,利用派生类对基类成员的访问能力,把基类的原有成员函数的功能照搬过来。这种在派生类中重新声明的成员函数具有比基类同名成员函数更小的作用域,因此在调用时,根据同名隐藏的原则,自然会使用派生类的函数。
//7_2.cpp
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
void main()
{
Rectangle rect; //声明Rectangle类的对象
rect.InitR(2,3,20,10); //设置矩形的数据
rect.Move(3,2); //移动矩形位置
cout<<"The data of rect(X,Y,W,H):"<<endl;
cout<<rect.GetX()<<"," //输出矩形的特征参数
<<rect.GetY()<<","
<<rect.GetW()<<","
<<rect.GetH()<<endl;
}
本例的Rectangle类对象rect调用的函数都是派生类自身的公有成员,因为是私有继承,它不可能访问到任何一个基类的成员。
保护继承
保护继承中,基类的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可直接访问。这样,
派生类的其他成员就可以直接访问从基类继承来的公有和保护成员,但在类外部通过派生类的对象无法直接访问它们
无论是派生类的成员还是派生类的对象都无法直接访问基类的私有成员。
类型兼容规则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下的情况。
- 派生类的对象可以隐含转换为基类对象。
- 派生类的对象可以初始化基类的引用。
- 派生类的指针可以隐含转换为基类的指针。
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
class B{…}
class D:public B{…}
B b1,*pb1;
D d1;
派生类对象可以隐含转换为基类对象,即用派生类对象中从基类继承来的成员,逐个赋值给基类对象的成员:
b1=d1;
派生类的对象也可以初始化基类对象的引用:
B &rb = d1;
派生类对象的地址也可以隐含转换为指向基类的指针
pb1=&d1;
类型兼容规则是多态性的重要基础之一
//7_4.cpp
#include <iostream>
using namespace std;
class B0 //基类B0声明
{
public:
void display()
{
cout<<"B0::display()"<<endl;
} //公有成员函数
};
class B1: public B0 //公有派生类B1声明
{
public:
void display()
{
cout<<"B1::display()"<<endl;
} //公有成员函数
};
class D1: public B1 //公有派生类D1声明
{
public:
void display()
{
cout<<"D1::display()"<<endl;
} //公有成员函数
};
void fun(B0 *ptr) //普通函数
{ //参数为指向基类对象的指针
ptr->display(); //"对象指针->成员名"
// (*ptr).display();
}
void main() //主函数
{
B0 b0; //声明B0类对象
B1 b1; //声明B1类对象
D1 d1; //声明D1类对象
B0 *p; //声明B0类指针
p=&b0; //B0类指针指向B0类对象
fun(p);
p=&b1; //B0类指针指向B1类对象
fun(p);
b1.display();
p=&d1; //B0类指针指向D1类对象
fun(p);
}
程序运行的结果
派生类的构造和析构函数
由于基类的构造函数和析构函数不能被继承,在派生类中,如果对派生类新增的成员进行初始化,就必须为派生类添加新的构造函数。但是派生类的构造函数只负责对派生类新增的成员进行初始化,对所有从基类继承下来的成员,其初始化工作还是由基类的构造函数完成。同样,对派生类对象的扫尾、清理工作也需要加入新的析构函数。
构造函数
派生类构造函数的一般语法形式为:
派生类名::派生类名(参数表):基类名1(基类1初始化参数表),…,基类名n(基类n初始化参数表),
成员对象名1《战员对象1初始化参数表)…,成员对象名m(成员对象m初始化参数表)
{
派生类构造函数的其他初始化操作;
}
例子:
//7_5.cpp
#include <iostream>
using namespace std;
class B1 //基类B1,构造函数有参数
{
public:
B1(int i)
{
cout<<"constructing B1 "<<i<<endl;
}
private:
int i;
};
class B2 //基类B2,构造函数有参数
{
public:
B2(int j)
{
cout<<"constructing B2 "<<j<<endl;
}
private:
int j;
};
class B3 //基类B3,构造函数无参数
{
public:
B3()
{
cout<<"constructing B3 *"<<endl;
}
};
class C: public B2, public B1, public B3 //派生新类C
//注意基类名的顺序
{
public: //派生类的公有成员
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
//注意基类名的个数与顺序
//注意成员对象名的个数与顺序
private: //派生类的私有对象成员
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
void main()
{
C obj(1,2,3,4);
}
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
构造函数的参数表中给出了基类及内嵌成员对象所需的全部参数,在冒号之后,分别列出了各个基类及内嵌对象名和各自的参数。这里,有两个问题需要注意:首先,这里并没有列出全部基类和成员对象,由于Base3类只有默认构造函数,不需要给它传递参数,因此,基类Base3以及Base3类成员对象member3就不必列出。其次,基类名和成员对象名的顺序是随意的。这个派生类构造函数的函数体为空,可见实际上只是起到了传递参数和调用基类及内嵌对象构造函数的作用。
复制构造函数
当存在类的继承关系时,复制构造函数该如何编写呢?对一个类,如果程序员没有编写复制构造函数,编译系统会在必要时自动生成一个隐含的复制构造函数,这个隐含的复制构造函数会自动调用基类的复制构造函数,然后对派生类新增的成员对象一一执行复制。
如果要为派生类编写复制构造函数,一般需要为基类相应的复制构造函数传递参数。
例如,假设Derived类是Base类的派生类,Derived类的复制构造函数形式如下:
Derived::Derived(const Derived &v):Base(v){............}
为什么Base类的复制构造函数参数类型不是Base类对象的引用?怎么这里用Derived类对象的引用v作为参数呢?
这是因为类型兼容规则在这里起了作用:可以用派生类的对象去初始化基类的引用。因此当函数的形参是基类的引用时,实参可以是派生类的对象。
析构函数
派生类的析构函数的功能是在该类对象消亡之前进行一些必要的清理工作。析构函数没有类型,也没有参数,和构造函数相比情况略微简单些。
//7_6.cpp
#include <iostream>
using namespace std;
class B1 //基类B1声明
{
public:
B1(int i) {cout<<"constructing B1 "<<i<<endl;} //B1的构造函数
~B1() {cout<<"destructing B1 "<<endl;} //B1的析构函数
};
class B2 //基类B2声明
{
public:
B2(int j) {cout<<"constructing B2 "<<j<<endl;} //B2的构造函数
~B2() {cout<<"destructing B2 "<<endl;} //B2的析构函数
};
class B3 //基类B3声明
{
public:
B3(){cout<<"constructing B3 *"<<endl;} //B3的构造函数
~B3() {cout<<"destructing B3 "<<endl;} //B3的析构函数
};
class C: public B2, public B1, public B3 //派生类C声明
{
public:
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
//派生类构造函数定义
private:
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
void main()
{ C obj(1,2,3,4);
}
程序的运行结果如下:
程序中,给3个基类分别加入了析构函数,派生类没有做任何改动,仍然使用的是由系统提供的默认析构函数。主函数也保持原样。程序在执行时,首先执行派生类的构造函数,然后执行派生类的析构函数。构造函数部分已经讨论过了,派生类默认的析构函数又分别调用了成员对象及基类的析构函数,这时的次序刚好和构造函数执行时完全相反。
多态
面向对象的多态性可以分为4类:重载多态、强制多态、包含多态和参数多态。
重载多态:类的成员函数的重载就属于重载多态。除此以外还有运算符重载,加法运算可以分别使用于浮点数、整型数之间就是重载的实例。
强制多态:是指将一个变元的类型加以变化,以符合一个函数或者操作的要求,加法运算符在进行浮点数与整型数相加时,首先进行类型强制转换,把整型数变为浮点数再相加的情况,就是强制多态的实例。
包含多态:是类族中定义于不同类中的同名成员函数的多态行为,主要是通过虚函数来实现。
参数多态:与类模板相关联,在使用时必须赋予实际的类型才可以实例化。这样,由类模板实例化的各个类都具有相同的操作,而操作对象的类型却各不相同。
运算符重载
C++中预定义的运算符的操作对象只能是基本数据类型。实际上,对于很多用户自定义类型(比如类),也需要有类似的运算操作。
运算符重载的规则如下:
- C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已经有的运算符。
- 重载之后运算符的优先级和结合性都不会改变
- 运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造,一般来讲,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。
注意:不能重载的运算符只有5个,它们是类属关系运算符“.”、成员指针运算符“.*”、作用域分辨符“::”、sizeof运算符和三木运算符“?:”。前面两个运算符保证了C++中访问成员功能的含义不被改变。作用域分辨符和sizeof运算符的操作数是类型。
- 运算符重载的一般语法形式为:
函数类型 operator 运算符(形参表)
{
函数体
}
返回类型指定了重载运算符的返回值类型,也就是运算结果类型;operator是定义运算符重载函数的关键字;运算符即是要重载的运算符名称,必须是C++中可重载的运算符,比如要重载加法运算符,这里就写“+”;形参表中给出重载运算符所需要的参数和类型。
运算符重载为成员函数
//8_1.cpp
#include<iostream>
using namespace std;
class complex //复数类声明
{
public: //外部接口
complex(double r=0.0,double i=0.0){real=r;imag=i;} //构造函数
complex operator + (complex c2); //运算符+重载成员函数
complex operator - (complex c2); //运算符-重载成员函数
void display(); //输出复数
private: //私有数据成员
double real; //复数实部
double imag; //复数虚部
};
complex complex::operator +(complex c2) //重载运算符函数实现
{
return complex(real+c2.real, imag+c2.imag); //创建一个临时无名对象作为返回值
}
complex complex::operator -(complex c2) //重载运算符函数实现
{
return complex(real-c2.real, imag-c2.imag); //创建一个临时无名对象作为返回值
}
void complex::display()
{
cout<<"("<<real<<","<<imag<<")"<<endl;
}
void main() //主函数
{
complex c1(5,4),c2(2,10),c3; //声明复数类的对象
cout<<"c1=";c1.display();
cout<<"c2=";c2.display();
c3=c1-c2; //使用重载运算符完成复数减法
cout<<"c3=c1-c2=";
c3.display();
c3=c1+c2; //使用重载运算符完成复数加法
cout<<"c3=c1+c2=";
c3.display();
}
重载单目运算符“++”
//8_2.cpp
#include<iostream>
using namespace std;
class Clock //时钟类声明
{
public: //外部接口
Clock(int NewH=0, int NewM=0, int NewS=0);
void ShowTime();
void operator ++(); //前置单目运算符重载
void operator ++(int); //后置单目运算符重载
private: //私有数据成员
int Hour,Minute,Second;
};
Clock::Clock(int NewH, int NewM, int NewS) //构造函数
{
if(0 <= NewH && NewH < 24 && 0 <= NewM && NewM < 60 && 0 <= NewS && NewS < 60)
{ Hour=NewH;
Minute=NewM;
Second=NewS;
}
else
cout<<"Time error!"<<endl;
}
void Clock::ShowTime() //显示时间函数
{
cout<<Hour<<":"<<Minute<<":"<<Second<<endl;
}
void Clock::operator ++() //前置单目运算符重载函数
{
Second++;
if(Second>=60)
{
Second=Second-60;
Minute++;
if(Minute>=60)
{
Minute=Minute-60;
Hour++;
Hour=Hour%24;
}
}
cout<<"++Clock: ";
}
void Clock::operator ++(int) //后置单目运算符重载
{ //注意形参表中的整型参数
Second++;
if(Second>=60)
{
Second=Second-60;
Minute++;
if(Minute>=60)
{
Minute=Minute-60;
Hour++;
Hour=Hour%24;
}
}
cout<<"Clock++: ";
}
void main()
{
Clock myClock(23,59,59);
cout<<"First time output:";
myClock.ShowTime();
myClock++;
myClock.ShowTime();
++myClock;
myClock.ShowTime();
}
运算符重载为非成员函数
//8_3.cpp
#include<iostream>
using namespace std;
class complex //复数类声明
{
public: //外部接口
complex(double r=0.0,double i=0.0){real=r;imag=i;} //构造函数
friend complex operator + (complex c1,complex c2); //运算符+重载友元函数
friend complex operator - (complex c1,complex c2); //运算符-重载友元函数
void display(); //显示复数的值
private: //私有数据成员
double real;
double imag;
}; //显示函数实现
void complex::display()
{ cout<<"("<<real<<","<<imag<<")"<<endl;}
complex operator +(complex c1,complex c2) //运算符重载友元函数实现
{ return complex(c2.real+c1.real,c2.imag+c1.imag);}
complex operator -(complex c1,complex c2) //运算符重载友元函数实现
{ return complex(c1.real-c2.real,c1.imag-c2.imag);}
void main() //主函数
{
complex c1(5,4),c2(2,10),c3;
cout<<"c1=";c1.display();
cout<<"c2=";c2.display();
c3=c1-c2; //使用重载运算符
cout<<"c3=c1-c2=";
c3.display();
c3=c1+c2; //使用重载运算符
cout<<"c3=c1+c2=";
c3.display();
}