C++程序设计(六)—— 继承和派生

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Alexshi5/article/details/82193679

一、继承和派生的基本概念

        从一个或多个以前定义的类(基类)产生新类的过程称为派生,这个新类称为派生类。基类和派生类又可以分别叫做父类和子类。派生的新类可以增加或重新定义数据和操作,这就产生了类的层次性。

        类的继承是指派生类继承基类的数据成员和成员函数。继承通常用来表示类属关系,不能将继承理解为构成关系。当从现有类中派生出新类时,派生类可以有以下几种变化:

① 增加新的成员;

② 重新定义已有的成员函数;

③ 改变基类成员的访问权限。

        C++中有两种继承:单一继承和多重继承。

二、单一继承

1、派生类的构造函数和析构函数

        先看一个简单示例:

#include <iostream>
using std::cout;

class Point {
private:
	int x, y;
public:
	Point(int a, int b) {
		x = a;
		y = b;
		cout << "正在初始化Point类的对象" << "\n";
	}

	~Point() {
		cout << "销毁Point类的对象" << "\n";
	}

	void showXY() {
		cout << "x=" << x << ",y=" << y << "\n";
	}
};

class Rectangle: public Point {//派生类继承基类并使用访问控制
private:
	int width, height;
public:
	Rectangle(int a, int b, int c, int d) :Point(a, b) {//定义派生类的构造函数
		width = c;
		height = d;
		cout << "正在初始化Rectangle类" << "\n";
	}

	~Rectangle() {
		cout << "销毁Rectangle类的对象" << "\n";
	}

	void showWH() {
		cout << "W=" << width << ",H=" << height << "\n";
	}
};
#include "Example1.h"
void example1();

int main() {
	example1();
	return 0;
}

void example1() {
	Rectangle r(1, 2, 3, 4);
	r.showWH(); //调用派生类的成员函数
	r.showXY(); //调用基类的成员函数
//	正在初始化Point类的对象
//	正在初始化Rectangle类
//	W=3,H=4
//	x=1,y=2
//	销毁Rectangle类的对象
//	销毁Point类的对象
}

        派生类的声明要在类名后添加一个冒号,冒号后面是访问控制关键字(public、protected、private),之后是基类名。 由于构造函数是不被继承的,所以一个派生类只能调用它的直接基类的构造函数,对基类成员进行初始化,然后执行派生类的构造函数。如果一个基类依是一个派生类,则这具过程递归进行,当该对象消失时,析构函数的执行顺序和执行构造函数时的顺序正好相反。

2、类的保护成员

        关键字protected之后声明的是类的保护成员,保护成员具有私有成员和公有成员的双重角色,对派生类的成员函数而言,它是公有成员可以被访问;对其他函数而言则仍是私有成员,不能被访问。示例如下:

#include<iostream>
using std::cout;

class Point2{
protected:
	int x,y;
public:
	Point2(int a,int b){
		x = a;
		y = b;
	}
	void show(){
		cout << "x=" << x << ",y=" << y << "\n";
	}
};

class Rectangle2: public Point2{
protected:
	int width,height;
public:
	Rectangle2(int a,int b,int c,int d):Point2(a,b){
		width = c;
		height = d;
	}
	void show(){
		cout << "x=" << x << ",y=" << y << ",width=" << width << ",height=" << height << "\n";
	}
};
#include "Example2.h"
void example2();
int main() {
	example2();
	return 0;
}

void example2(){
	Point2 a(1,2);
	Rectangle2 b(10,20,30,40);
	a.show();
	b.show();
//	x=1,y=2
//	x=10,y=20,width=30,height=40
}

 上例中,派生类虽然继承了基类的成员函数show(),但它改造了这个函数,使函数能显示所有数据,这并不会影响基类函数原来的功能。

3、访问权限和赋值兼容规则

⑴ 公有派生和赋值兼容规则

        在公共派生情况下,基类成员的访问权限在派生类中保持不变。所谓兼容规则是指在公有派生情况下,一个派生类的对象可以作为基类的对象来使用的情况。示例如下:

#include <iostream>
#include <string>
using std::cout;
using std::string;

class Dog{
protected:
	int age;
public:
	Dog(int a){
		age = a;
	}
	void show(){
		cout << "狗狗" << age << "岁了" << "\n";
	}
};

class WhiteDog : public Dog{
private:
	string color;
public:
	WhiteDog(int a,string b):Dog(a){
		color = b;
	}
	void show(){
		cout << color << "狗狗" << age << "岁了" << "\n";
	}
};
#include "Example3.h"
void example3();
int main() {
	example3();
	return 0;
}

void example3() {
	WhiteDog wd(1, "白色");
	Dog d(2);
	wd.show();//白色狗狗1岁了
	d.show();//狗狗2岁了

	//派生类的对象初始化基类的引用
	Dog &d1 = wd;
	d1.show();//狗狗1岁了,实际调用的是基类的show函数

	//派生类对象的地址赋给指向基类的指针
	Dog *d2 = &wd;
	d2->show();//狗狗1岁了,实际调用的是基类的show函数
	WhiteDog *wd1 = &wd;
	wd1->show();//白色狗狗1岁了,调用的是派生类的show函数

	//派生类的对象赋给基类的对象
	d = wd;
	d.show();//狗狗1岁了,派生类对象的属性值更新基类对象的属性值

}

 注意:静态成员可以被继承,这时基类对象和派生类对象共享该静态成员。

⑵ is-a和has-a的区别

        前者属于继承和派生问题,后者是一个类使用另一个类的问题,即把一个类的对象作为自己的数据成员或者成员函数的参数。

⑶ 私有派生

        通过私有派生,基类的私有和不可访问的成员在派生类中是不可访问的,而公有和保护成员这时就成了派生类的私有成员,派生类的对象不能访问继承的基类成员,必须定义公有的成员函数作为接口。更重要的是,虽然派生类的成员函数可通过自定义的函数访问基类的成员,但将该派生类作为基类再进行派生时,这时即使使用公有派生,原基类公有成员在新的派生类中也是不可访问的。示例如下:

#include<iostream>
using std::cout;

class Point3{
protected:
	int x,y;
public:
	Point3(int a,int b){
		x = a;
		y = b;
	}
	void show(){
		cout << "x=" << x << ",y=" << y << "\n";
	}
};

class Rectangle3: private Point3{
private:
	int width,height;
public:
	Rectangle3(int a,int b,int c,int d):Point3(a,b){
		width = c;
		height = d;
	}
	void show(){
		cout << "x=" << x << ",y=" << y << ",width=" << width << ",height=" << height << "\n";
	}
};

class Test3:public Rectangle3{
public:
	Test3(int a,int b,int c,int d):Rectangle3(a,b,c,d){

	}
	void show(){
		//Point3::show();//私有派生时,不能使用此方式调用该函数
		Rectangle3::show();
	}
};
#include "Example4.h"
void example4();
int main() {
	example4();
	return 0;
}

void example4(){
	//实例化一个Test3类的对象
	Test3 t(1,2,3,4);
	t.show();//x=1,y=2,width=3,height=4
}

 ⑷ 保护派生

        派生也可以使用protected,这种派生使原来的权限都降一级使用,即private变为不可访问,protected变为private,public变为protected。

三、多重继承

        继承的规则是一样的,只不过继承的时候需要用逗号分隔。示例如下:

#include<iostream>
using std::cout;

class A{
private:
	int a;
public:
	void setA(int i){
		a = i;
	}
	void showA(){
		cout << "a=" << a << "\n";
	}
};

class B{
private:
	int b;
public:
	void setB(int t){
		b = t;
	}
	void showB(){
		cout << "b=" << b << "\n";
	}
};

class C:public A,private B{//多继承使用逗号分隔
private:
	int c;
public:
	void setC(int x,int y){
		c = x ;
		setB(y);
	}
	void showC(){
		cout << "c=" << c << "\n";
		showB();
	}
};
#include "Example5.h"
void example5();
int main() {
	example5();
	return 0;
}

void example5(){
	C c;
	c.setA(10);
	c.showA();//a=10

	c.setC(20,30);
	c.showC();//c=20 b=30
}

四、二义性及其支配规则

        对基类成员的访问必须是无二义性的,如果使用一个表达式的含义能解释为访问多个基类中的成员,则这种对基类成员的访问就是不确定的,称这种访问具有二义性。

1、作用域分辨符和成员名限定

        从类中派生其他类可能导致几个类使用同一个成员函数名或数据成员名时,程序必须确切的告诉编译器使用哪个版本的数据成员或成员函数。示例如下:

#include <iostream>
using std::cout;

class A1{
public:
	void show(){
		cout << "a1.show" << "\n";
	}
};

class B1{
public:
	void show(){
		cout << "b1.show" << "\n";
	}
	void display(){
		cout << "b1.display" << "\n";
	}
};

class C1:public A1,public B1{
public:
	void display(){
		cout << "c1.display" << "\n";
	}
	void reveal(){
		//show();//具有二义性
	}
	void reveal1(){
		A1::show();//使用基类的函数
	}
	void reveal2(){
		B1::show();//使用基类的函数
	}
};
#include "Example7.h"
void example7();
int main() {
	example7();
	return 0;
}

void example7(){
	C1 obj;
	obj.A1::show();//a1.show
	obj.B1::show();//b1.show
	obj.B1::display();//b1.display
	obj.C1::display();//c1.display
	obj.display();//c1.display
	obj.reveal1();//a1.show
	obj.reveal2();//b1.show
}

         由上例可知,如果基类中的名字在派生类中再次声明,则派生类中的名字就隐藏了基类中的相应名字,C++可以使用作用域分辨运算符“::”来存取那些被隐藏的名字,这一过程叫做作用域分辨。

2、派生类支配基类的同名函数

        基类的成员和派生类新增的成员都具有类作用域,基类在外层,派生类在内层。如果这时派生类定义了一个和基类成员函数同名的新成员函数(同名的非重载函数),派生类的新成员函数就覆盖了外层的同名成员函数。在这种情况下,直接使用成员名只能访问派生类的成员函数,只有使用作用域分辨,才能访问基类的同名成员函数。

        由于二义性原因,一个类不能从同一个类中直接继承一次以上,如果必须要这样做,可以使用一个中间类,二义性检查是在访问权限检查之前进行的,因此成员的访问权限不能解决二义性问题。如果涉及几层继承关系,对于任一基类中可以存取的成员,都可以通过作用域分辨进行存取。一般只有派生类中使用的标识符与基类中的标识符同名时,才有必要使用作用域分辨符进行存取。

五、友元与派生

        友元声明与访问控制无关,友元声明在私有区域进行或在公有区域进行是没有太大区别的,对友元函数的唯一限制是该函数必须出现在类声明内的某一部分。

        友元关系是无等级的,友元可以访问任何一个类成员函数可以访问的对象,这比一个派生类可以访问的对象还多。友元关系不能继承,一个派生类的友元只能访问该派生类的直接基类的公有和保护成员,不能访问私有成员。当友元访问直接基类的静态保护成员时,只能使用对象名而不能使用成员名限定。

猜你喜欢

转载自blog.csdn.net/Alexshi5/article/details/82193679