C++中的继承

一.继承概念

继承的概念


继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

 继承定义

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类

class Person
{
public:
	//....
};
//    子类      继承方式  父类
class Student : public Person
{
public:
	//...
};

继承方式有三种 public protected private 

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但无论是在类内还是类外都是不能访问的。

(可以调用父类函数间接访问)

2.派生类的成员类型要根据基类的成员类型 继承类型来判断,访问权限大小public  > protected> private,我们取两个类型中权限小的那个作为派生类的成员类型。

(eg.protected继承,基类public->派生类protected protected->protected private->private)

3.protected和private区别,继承方式是protected/public的话,

protected:基类在类内可以直接使用,但类外不行。

private:基类在类内外都不可以访问,只能通过父类的函数间接访问。

4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

二.基类和派生类对象赋值转换

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
或者切割。(可以子赋值给父,但父不能赋值给子)

注:这里的赋值转换和double b=3.14 int a=b的断流不同,断流是产生一个临时变量赋值,这个临时变量是常量引用 指针时需要缩小权限加const

而赋值转换不会产生临时对象,引用 指针都是指向派生类中基类的部分。

所以引用 指针可以改变派生类中的基类。

三.继承中的作用域

1. 基类和派生类是两个独立的作用域。
2. 子类和父类中有同名成员,在子类中能直接访问到子类的同名变量(父类的不能直接访问),这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基父类::父类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

可以看到这里只能找到子类的fun函数。父类和子类的fun函数构成了隐藏关系,因为分别在两个不同的作用域所以不构成重载。

四.派生类的默认成员函数

构造函数

子类的构造函数对成员变量初始化时会分为3个部分,1基本类型 2自定义类型(调用该类型的构造函数) 3整个父类成员变量(调用父类的构造函数)构造顺序是按先父类成员,再子类成员。

父类没有默认构造函数,就需要传参就显示传(类似于匿名对象Person(_name) )。

注:构造顺序是按声明顺序,与初始化列表顺序无关。

拷贝构造函数

拷贝构造函数和构造函数差不多,也是分为3部分。向父类拷贝构造函数传参时,可以直接传子类对象,利用切割/切片原理把子类父类的部分传过去实现拷贝构造。


class Person
{
public:
	Person(const char* name = "")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	// 父类构造显示调用,可以保证先父后子
	Student(const char* name = "", int x = 0, const char* address = "")
		:_x(x)
		, _address(address)
		, _name(Person::_name )
		, Person(name)
	{}
	Student(const Student& st)
		:Person(st)//切片
		, _x(st._x)
		, _address(st._address)
	{}
protected:
	int _x = 1;
	string _address;
	string _name;
};

赋值构造

子类赋值构造也需要调用父类的赋值构造函数,但调用时要注意函数重名会隐藏父类函数,

需要指名父类作用域。

Student& operator=(const Student& st)
{
	if (this != &st)
	{
		Person::operator=(st);
		_x = st._x;
		_address = st._address;
	}

	return *this;
}

析构函数

调用析构函数时不需要显示调用父类的析构函数,析构完子类成员后会自动调用父类析构函数。析构顺序与构造顺序相反,先析构子类再析构父类。

(如果先析构父类成员,在子类中再访问父类成员就会出现报错)

(构造函数初始化列表是按照声明顺序初始化的,所以不需要担心顺序)

五.继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。

class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 学号
};

六. 继承与静态成员

在基类中定义的静态成员变量,与派生类继承的静态成员变量是同一个。静态成员变量不存在于对象中,属于整个类。

#include"test.h"
class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
int main()
{
	Person p;
	Student s;
	cout << &p._count << endl;
	cout << &s._count << endl;
}

七.复杂的菱形继承及菱形虚拟继承

单继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承。

class Student : public Person

多继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

class Student : public Person, public Teacher

菱形继承

菱形继承:菱形继承是多继承的一种特殊情况。子类的任两个父类继承了相同的父类,导致子类中继承了两份相同的数据,还会引发二义性的问题。

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
void Test()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

如何解决数据冗余和二义性的问题?

虚拟继承(virtual)可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。

(在继承相同类的两个父类中加 virtaul )

eg.

class Person
{
public:
	string _name; // 姓名
};
class Student : virtual public Person
{
protected:
	int _num; //学号
};
class Teacher : virtual public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
void Test()
{
	Assistant a;
	a._name = "peter";
}

虚拟继承原理:把两个父类中共同的数据合成一个,偏移量便于找数据位置。

继承和组合

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

// Car和BMW Car和Benz构成is-a的关系
class Car {
protected:
	string _colour = "白色"; // 颜色
	string _num = "陕ABIT00";
	// 车牌号
};

class BMW : public Car {
public:
	void Drive() { cout << "好开-操控" << endl; }
};

class Benz : public Car {
public:
	void Drive() { cout << "好坐-舒适" << endl; }
};

// Tire和Car构成has-a的关系

class Tire {
protected:
	string _brand = "Michelin";  // 品牌
	size_t _size = 17;
	// 尺寸

};

class Car {
protected:
	string _colour = "白色";
	// 颜色
	string _num = "陕ABIT00";
	// 车牌号
	Tire _t;
	// 轮胎
};

public继承 派生类可以访问基类的public protected

组合 因为对象在类外只能访问类中public。实际尽量多去用组合。组合的耦合度低,代码维护性好。

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-13 13:30:05       49 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-13 13:30:05       53 阅读
  3. 在Django里面运行非项目文件

    2024-07-13 13:30:05       42 阅读
  4. Python语言-面向对象

    2024-07-13 13:30:05       53 阅读

热门阅读

  1. std::filesystem::current_path().generic_string()的bug

    2024-07-13 13:30:05       18 阅读
  2. 【Android】在渲染生效前提前测量View大小

    2024-07-13 13:30:05       18 阅读
  3. 基于节点嵌入的链接预测(暂时这样吧)

    2024-07-13 13:30:05       17 阅读
  4. C#中where的约束

    2024-07-13 13:30:05       19 阅读
  5. ABP框架中的ISoftDelete与软删除

    2024-07-13 13:30:05       23 阅读
  6. 三级_网络技术_13_局域网技术基础及应用

    2024-07-13 13:30:05       19 阅读
  7. 服务器数据出现丢失该怎样恢复?

    2024-07-13 13:30:05       13 阅读
  8. React中使用usePrevious的意义是什么,为啥要用它

    2024-07-13 13:30:05       16 阅读
  9. Spring:SpringBoot为什么可以使用Jar包启动

    2024-07-13 13:30:05       14 阅读
  10. ubuntu安装k8s+docker运行英伟达gpu cuda

    2024-07-13 13:30:05       18 阅读
  11. 使用Python绘制百分比堆积柱形图

    2024-07-13 13:30:05       16 阅读
  12. Memcached负载均衡:揭秘高效缓存分发策略

    2024-07-13 13:30:05       18 阅读