c++-继承

派生类的声明

  • 单继承时

class 派生类名:继承方式 基类名

{

成员声明;

}

  • 多继承时

class Derived: public Base1, private Base2

{

public:

Derived ();

~Derived ();

};

派生类生成过程

  • 吸收基类成员

▫吸收基类成员之后,派生类实际上就包含了它的全部基类中除构造和析构函数之外的所有成员。

  • 改造基类成员

▫如果派生类声明了一个和某基类成员同名的新成员(如果是成员函数,则参数表也要相同,参数不同的情况属于重载),派生的新成员就覆盖了外层同名 成员

  • 添加新的成员

▫派生类新成员的加入是继承与派生机制的核心,是保证派生类在功能上有所发展

访问控制

  • 不同继承方式的影响主要体现在:

▫派生类成员对基类成员的访问权限

▫通过派生类对象对基类成员的访问权限

  • 三种继承方式

▫公有继承(pubilc)

▫私有继承(private)

▫保护继承(protected)

1711714801331

公有继承不改变访问权限,私有和保护继承重写基类访问权限,但注意基类的private和protected权限不可重写

公有继承(public)

•基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可直接访问。

•派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。

•通过派生类的对象只能访问基类的public成员。

eg

//Point.h
#ifndef _POINT_H
#define _POINT_H
class Point {	//基类Point类的定义
public:		//公有函数成员
	void initPoint(float x = 0, float y = 0)
  { this->x = x; this->y = y;}
	void move(float offX, float offY)
  { x += offX; y += offY; }
	float getX() const { return x; }
	float getY() const { return y; }
private:		//私有数据成员
	float x, y;
};
#endif //_POINT_H


//Rectangle.h
#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include "Point.h"
class Rectangle: public Point {	//派生类定义部分
public:	//新增公有函数成员
	void initRectangle(float x, float y, float w, float h) {
		initPoint(x, y); //调用基类公有成员函数
		this->w = w;
		this->h = h;
	}
	float getH() const { return h; }
	float getW() const { return w; }
private:	//新增私有数据成员
	float w, h;
};
#endif //_RECTANGLE_H



#include <iostream>
#include <cmath>
using namespace std;
int main() {
	Rectangle rect;	//定义Rectangle类的对象
	//设置矩形的数据
	rect.initRectangle(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;
	return 0;
}

私有继承(private)

•基类的public和protected成员都以private身份出现在派生类中,但基类的private成员不可直接访问。

•派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。

•通过派生类的对象不能直接访问基类中的任何成员。

//Point.h
#ifndef _POINT_H
#define _POINT_H
class Point {	//基类Point类的定义
public:	//公有函数成员
	void initPoint(float x = 0, float y = 0) 
  { this->x = x; this->y = y;}
	void move(float offX, float offY) 
  { x += offX; y += offY; }
	float getX() const { return x; }
	float getY() const { return y; }
private:	//私有数据成员
	float x, y;
};	
#endif //_POINT_H



//Rectangle.h
#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include "Point.h"
class Rectangle: private Point {	//派生类定义部分
public:	//新增公有函数成员
	void initRectangle(float x, float y, float w, float h) {
		initPoint(x, y); //调用基类公有成员函数
		this->w = w;
		this->h = h;
	}
	void move(float offX, float offY) { 
		Point::move(offX, offY);
	}
	float getX() const { return Point::getX(); }
	float getY() const { return Point::getY(); }
	float getH() const { return h; }
	float getW() const { return w; }
private:	//新增私有数据成员
	float w, h;
};
#endif //_RECTANGLE_H


#include <iostream>
#include <cmath>
using namespace std;

int main() {
	Rectangle rect;	//定义Rectangle类的对象
	rect.initRectangle(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;
	return 0;
}

类型兼容规则

派生类可以当基类用

•一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在:

▫派生类的对象可以隐含转换为基类对象。

▫派生类的对象可以初始化基类的引用。

▫派生类的指针可以隐含转换为基类的指针。

•通过基类对象名、指针只能使用从基类继承的成员

image-20240407192311483

#include <iostream>
using namespace std;
class Base1 { //基类Base1定义
public:
	void display() const {
		cout << "Base1::display()" << endl;
	}
};
class Base2: public Base1 { //公有派生类Base2定义
public:
	void display() const {
		cout << "Base2::display()" << endl;
	}
};
class Derived: public Base2 { //公有派生类Derived定义
public:
	void display() const {
		cout << "Derived::display()" << endl;
	}
};
void fun(Base1 *ptr) { 	//参数为指向基类对象的指针
	ptr->display();		//"对象指针->成员名"
}
int main() {	//主函数
	Base1 base1;	//声明Base1类对象
	Base2 base2;	//声明Base2类对象
	Derived derived;	//声明Derived类对象

	fun(&base1);	//用Base1对象的指针调用fun函数
	fun(&base2);	//用Base2对象的指针调用fun函数
	fun(&derived);     //用Derived对象的指针调用fun函数

	return 0;
}

image-20240407193022797

单一继承时的构造函数

派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表), 本类成员初始化列表

{

//其他初始化;

};

#include<iostream>
using namespace std;
class B {
public:
	B();
	B(int i);
	~B();
	void print() const;
private:
	int b;
};
B::B() {
	b=0;
	cout << "B's default constructor called." << endl;
}
B::B(int i) {
	b=i;
	cout << "B's constructor called." << endl;
}
B::~B() {
	cout << "B's destructor called." << endl;
}
void B::print() const {
	cout << b << endl;
}
class C: public B {
public:
	C();
	C(int i, int j);
	~C();
	void print() const;
private:
	int c;
};
C::C() {
	c = 0;
	cout << "C's default constructor called." << endl;
}
C::C(int i,int j): B(i), c(j){//往基类传参只能这样用初始化列表
    //不能在这里为作为基类的B传参,B(i)-->  在这里写代表匿名对象
	cout << "C's constructor called." << endl;
}
C::~C() {
	cout << "C's destructor called." << endl;
}
void C::print() const {
	B::print();
	cout << c << endl;
}
int main() {
	C obj(5, 6);
	obj.print();
	return 0;
}

多继承时的构造函数

派生类名::派生类名(参数表):基类名1(基类1初始化参数表), 基类名2(基类2初始化参数表), …基类名n(基类n初始化参数表), 本类成员初始化列表

{

​ //其他初始化;

};

构造函数的执行顺序

  1. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。

  2. 对本类成员初始化列表中的基本类型成员和对象成员进行初始化,初始化顺序按照它们在类中声明的顺序。对象成员初始化是自动调用对象所属类的构造函数完成的。

  3. 执行派生类的构造函数体中的内容。

    •析构函数的调用次序与构造函数相反。

eg

#include <iostream>
using namespace std;
class Base1 {	//基类Base1,构造函数有参数
public:
    Base1(int i) {
        this->i = i;
        cout << "Constructing Base1 " << this->i << endl; }
    ~Base1(){
        cout << "Destructing Base1 "<<this->i<<endl;
    }
private:
    int i;
};
class Base2 {	//基类Base2,构造函数有参数
public:
    Base2(int j):j(j){ cout << "Constructing Base2 " << this->j << endl; }
    ~Base2(){
        cout << "Destructing Base2 "<<this->j<<endl;
    }
private:
    int j;
};
class Base3 {	//基类Base3,构造函数无参数
public:
    Base3() { cout << "Constructing Base3 *" << endl; }
    ~Base3(){
        cout << "Destructing Base3 *"<< endl;
    }
};
class Derived: public Base2, public Base1, public Base3 {
//派生新类Derived,注意基类名的顺序
public:	//派生类的公有成员
    Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b)
    { }
    //注意基类名的个数与顺序,//注意成员对象名的个数与顺序
private:	//派生类的私有成员对象
    Base1 member1;
    Base2 member2;
    Base3 member3;
};

int main() {
    Derived obj(1, 2, 3, 4);
    return 0;
}

image-20240407203016317

删除delete构造函数

class Base{
public: 
      Base() = default;
      ...
      Base(Base&) = delete;       //删除复制构造函数,阻止复制
      Base(Base&&) = delete;     //删除移动构造函数
};
class Derived : public Base {
};

...
Derived d1;     //正确,合成了默认构造函数
Derived d2(d1);   //错误,删除了复制构造函数
Derived d3(std::move(d1));  //错误,删除了移动构造函数

多继承同名隐藏举例

当派生类与基类中有相同成员时:

•若未特别限定,则通过派生类对象使用的是派生类中的同名成员。

如要通过派生类对象访问基类中被隐藏的同名成员,应使用基类名和作用域操作符(::)来限定

eg:

#include <iostream>
using namespace std;
class Base1 {	//定义基类Base1
public:
	int var;
	void fun() { cout << "Member of Base1" << endl; }
};
class Base2 {	//定义基类Base2
public:
	int var;
	void fun() { cout << "Member of Base2" << endl; }
};
class Derived: public Base1, public Base2 { //定义派生类Derived
public:
	int var;	//同名数据成员
	void fun() { cout << "Member of Derived" << endl; }
};
int main() {
	Derived d;
	Derived *p = &d;

	d.var = 1;	//对象名.成员名标识
	d.fun();	//访问Derived类成员
	
	d.Base1::var = 2;	//作用域操作符标识
	d.Base1::fun();	//访问Base1基类成员
	
	p->Base2::var = 3;	//作用域操作符标识
	p->Base2::fun();	//访问Base2基类成员

	return 0;
}

二义性问题

•当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。

•在多继承时,基类与派生类之间,或基类之间出现同名成员时,将出现访问时的二义性(不确定性)——采用虚函数或同名隐藏来解决。

二义性问题举例

class A {
public:
	void  f();
};
class B {
public:
	void f();
	void g()
};
class C: public A, piblic B {
public:
	void g();
	void h();
};

如果定义:C  c1;
则 c1.f() 具有二义性
而 c1.g() 无二义性(同名隐藏)

解决方法一:用类名来限定c1.A::f()或c1.B::f()
解决方法二:同名隐藏在C 中声明一个同名成员函数f(),f()再根据需要调用  A::f()    或    B::f()

虚基类

优先构造

•虚基类的引入

▫用于有共同基类的场合

•声明

▫以virtual修饰说明基类
例:class B1:virtual public B

•作用

▫主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题.

▫为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝

•注意:

在第一级继承时就要将共同基类设计为虚基类。

eg

#include <iostream>
using namespace std;
class Base0 {	//定义基类Base0
public:
	int var0;
	void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: virtual public Base0 {	//定义派生类Base1
public:	//新增外部接口
	int var1;
};
class Base2: virtual public Base0 {	//定义派生类Base2
public:	//新增外部接口
	int var2;
};
class Derived: public Base1, public Base2 {
//定义派生类Derived 
public:	//新增外部接口
	int var;
	void fun() {
		cout << "Member of Derived" << endl;
	}
};

int main() {	  //程序主函数
	Derived d;	  //定义Derived类对象d
	d.var0 = 2;  //直接访问虚基类的数据成员
	d.fun0();	  //直接访问虚基类的函数成员
	return 0;
}

有虚基类时的构造函数举例

建立对象时所指定的类称为最(远)派生类。

虚基类的成员是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。

在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未列出,则表示调用该虚基类的默认构造函数。

在建立对象时,只有最派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略。

#include <iostream>
using namespace std;

class Base0 {	//定义基类Base0
public:
	Base0(int var) : var0(var) { }
	int var0;
	void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: virtual public Base0 {	//定义派生类Base1
public:	//新增外部接口
	Base1(int var) : Base0(var) { }
	int var1;
};
class Base2: virtual public Base0 {	//定义派生类Base2
public:	//新增外部接口
	Base2(int var) : Base0(var) { }
	int var2;
};
class Derived: public Base1, public Base2 {
	//定义派生类Derived
public:	//新增外部接口
	Derived(int var) : Base0(var), Base1(var), Base2(var) { }
	int var;
	void fun() { cout << "Member of Derived" << endl; }
};

int main() {	//程序主函数
	Derived d(1);	//定义Derived类对象d
	d.var0 = 2;	//直接访问虚基类的数据成员
	d.fun0();	//直接访问虚基类的函数成员
	return 0;
}

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾有所爱,其名华夏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值