【C++】继承初识

这篇具有很好参考价值的文章主要介绍了【C++】继承初识。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

继承的概念介绍

继承是面向对象程序设计中代码复用最重要的手段。它允许在保持原有基类(父类)的基础上进行类的扩展,产生派生类(子类)。这体现了面向对象程序设计的层次结构。
在继承之前所接触的复用大多都是函数复用,继承却是类设计层次的复用。

继承的使用方式

class Person
{};

class Student : public Person
{};

【C++】继承初识
继承方式与访问限定符一样,也有3种。
【C++】继承初识
基类中不同的访问权限,被派生类通过不同方式继承后,也会有不同的结果。

基类成员 \ 继承方式 public继承 protected继承 private继承
public成员 成为派生类的 public 成员 成为派生类的 protected 成员 成为派生类的 private 成员
protected成员 成为派生类的 protected 成员 成为派生类的 protected 成员 成为派生类的 private 成员
private成员 在派生类中“不可见” 在派生类中“不可见” 在派生类中“不可见”

这里的“不可见”是指:基类的私有成员虽然被继承到了派生类中,但派生类对象是没有办法访问到的。
protected访问限定符其实是由于继承才出现的,它的目的就是被派生类继承后只能在类里面访问到,类外访问不到。
上面表格中继承后的各种结果除了基类私有成员“不可见”外,其余的结果可以通过如下公式得到。
基类成员被子类继承的结果 = Min(成员在基类中被访问的权限,继承方式),默认public > protected > private
实际运用中通常都是使用public继承,同时也不提倡使用protected/private继承。因为protected/private继承下来的成员在类外是无法使用的。

继承中的作用域

基类和派生类的作用域是独立的。
当派生类中的成员和基类成员有同名的时候,派生类成员将屏蔽对基类同名成员的直接访问,这种情况叫做隐藏重定义。但仍然可以指定使用基类::基类成员进行访问。
如果是成员函数的隐藏,只需函数名相同就构成隐藏。
所以在实际的继承体系中最好不要定义同名的成员。

// 因为是在类外访问,所以权限都改成了 public
class Person
{
public:
	int _id;
};

class Student : public Person
{
public:
	int _id;
};

void Test1()
{
	Student s;
	s._id = 1;
	s.Person::_id = 2;
}

【C++】继承初识

继承中的切片

派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这就是继承的切片,是指把派生类中基类的那部分切下来赋值过去。
派生类对象可以切片赋值给基类对象,反过来却不行。
【C++】继承初识

class Person
{
protected:
	string _name;
};

class Student : public Person
{
protected:
	int _num;
};

void Test2()
{
	Student s;
	Person p = s;
	Person* pp = &s;
	Person& rp = s;
}

派生类的默认成员函数

构造函数:派生类的构造函数必须调用基类的构造函数来完成基类那一部分成员的初始化工作。如果基类没有默认构造函数,那就必须在派生类的构造函数的初始化列表阶段进行显式调用。
派生类对象先调用基类构造再调用派生类构造来初始化。派生类对象先调用派生类析构再调用基类析构来进行对象的析构清理。

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;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}

protected:
	string _name;
};

class Student : public Person
{
public:
	Student(const char* name, int num = 1)
		: Person(name) // 显式调用基类的构造函数
		, _num(num)
	{
		cout << "Student()" << endl;
	}

	Student(const Student& s)
		: Person(s) // 切片
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s); // 构成隐藏 -> 显式调用
			_num = s._num;
		}

		cout << "Student& operator=(const Student& s)" << endl;
		return *this;
	}

	// 子类的析构函数跟父类的析构函数构成隐藏
	// 由于多态的需要,析构函数的名字会被统一处理成destructor()
	~Student()
	{
		// 子类析构后面,会自动调用父类的析构,这样保证先析构子类,再析构父类
		cout << "~Student()" << endl;
	}

private:
	int _num;
};

void Test3()
{
	// 子类默认生成的构造函数
	// 1.对于自己的成员,跟普通类调用构造函数一样
	// 2.对于继承的父类成员,必须调用父类的构造函数
	Student s1("zs", 10);
	Student s2("sz", 20);

	// 拷贝构造
	// 1.对于自己的成员,和普通类调用拷贝构造一样
	// 2.对于继承的父类成员,必须调用父类的拷贝构造
	Student s3 = s1;
	
	// operator=的使用同上
	s2 = s1;

继承中的友元与静态成员函数

继承体系中,友元关系不能被继承,即基类的友元函数不能访问派生类的privateprotected成员。
基类中的友元函数如果想要访问派生类中的所有成员,就需要再成为派生类的友元。
基类中的静态成员,在整个继承体系中只存在一份(存在静态区)。所有的派生类对象都共享这一份静态成员实例。

class Person
{
public:
	Person()
	{
		++_count;
	}
public:
	static int _count;
};
int Person::_count = 0;

class Student : public Person
{};

void Test4()
{
	Person p;
	Student s;
	
	cout << "人数: " << Person::_count << endl;
	
	cout << "&Person::_count: " << &Person::_count << endl;
	cout << "&Student::_count: " << &Student::_count << endl;
}

【C++】继承初识

菱形继承与菱形虚拟继承

单继承:一个子类只有一个直接父类时。
多继承:一个子类有两个或以上直接父类。
如果一个类不想被继承可以使用关键字final

class A final
{};

菱形继承:菱形继承是多继承的一种特殊情况。

class A
{
public:
	int _a;
};

class B : public A
{
public:
	int _b;
};

class C : public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

void Test5()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
}

【C++】继承初识
菱形继承的问题就在于其数据冗余和二义性问题。
【C++】继承初识
【C++】继承初识
可以使用菱形虚拟继承来解决菱形继承的问题。

class A
{
public:
	int _a;
};

class B : virtual public A
{
public:
	int _b;
};

class C : virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

void Test5()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
}

【C++】继承初识
此例中,在菱形虚拟继承下,B对象和C对象中的_a共用同一份的。这一份_a存储在了为D对象开辟的高地址处。(D对象存储在栈上,栈的空间使用是从高地址向低地址开辟)
B对象和C对象是通过存储的指针(虚基表指针)找到虚基表中的偏移量,通过指针偏移来找到公共的_a
【C++】继承初识
【C++】继承初识
虚拟继承与菱形继承内存对象存储的对比。
【C++】继承初识
如果单继承使用虚拟继承会是什么样子呢?

class A
{
public:
	int _a;
};

class B : virtual public A
{
public:
	int _b;
};

void Test6()
{
	B b;
	b._a = 1;
	b._b = 2;

	cout << sizeof B << endl;
}

【C++】继承初识
【C++】继承初识

继承的一些反思

菱形继承的问题本质上还是归咎于多继承这种设计。所以自己使用时尽量不要使用多继承,一定不要搞出菱形继承。像Java就只有单继承。
继承本质是一种白箱复用。
“白箱”是相对可视性而言的:在继承体系中,基类的内部细节对派生类是可见的,所以继承常被认为“破坏了封装性”。基类内部实现的改变也会影响到派生类。导致派生类和基类键有很强的的依赖关系,耦合度高。
所以这里要引出另一种复用手段:对象组合。
通常来说,继承是一种is-a的关系,而组合是一种has-a的关系。
对象组合的复用正是一种黑箱复用。

class A
{};

// 组合
class B
{
protected:
	A _a;
};

对象组合要求被组合对象具有良好定义的接口。对象的内部细节是不可见的,所以组合类之间没有很强的依赖关系,耦合度低,具有更好的封装性。
所以实际中,一个关系如果继承和组合都可以实现,那就用组合。
但是继承肯定有它存在的价值,不免有一些关系必须要使用继承来实现,另外要实现多态,也必须要继承。文章来源地址https://www.toymoban.com/news/detail-430495.html

到了这里,关于【C++】继承初识的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++面向对象】--- 继承 的奥秘(下篇)

    个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C++之路】💌 本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长,共同进步。🍓 接下来对C++继承体系中的 作用域 展开分析。 在C

    2024年02月12日
    浏览(41)
  • 【C++历险记】面向对象|菱形继承及菱形虚拟继承

    个人主页:兜里有颗棉花糖💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【C++之路】💌 本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长,共同进步。🍓 单继承:一个子类 只有一个直接父类 时称这个继承

    2024年02月10日
    浏览(42)
  • 第八站:C++面向对象(继承和派生)

     派生:由父类派生出子类 继承:子类继承父类(继承不会继承 析构函数和构造函数 : 父类的所有成员函数,以及数据成员,都会被子类继承! ) \\\"子类派生出的类\\\"会指向\\\"父类被继承的类\\\", 父类就是基类 实例1: 先创建一个父类,有私有成员数据(name,和age),成员函数,描述信息,有参的

    2024年01月19日
    浏览(42)
  • 面向对象的三大特性之继承(C++)

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

    2024年02月06日
    浏览(49)
  • c++面向对象之封装、继承、和多态

    把客观事物封装成类,而且可以把自己的数据和方法设置为只能让可信的类或者对象操作,对不可信的信息进行隐藏(利用public,private,protected,friend)实现 has-a :描述一个类由多个部件类构成,一个类的成员属性是另一个已经定义好的类。 use-a:一个类使用另一个类,通过类之间

    2024年02月02日
    浏览(49)
  • C++ 面向对象核心(继承、权限、多态、抽象类)

    继承(Inheritance)是面向对象编程中的一个重要概念,它允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和方法。继承是实现类之间的关系,通过继承,子类可以重用父类的代码,并且可以在此基础上添加新的功能或修改已有的功能。 在C++中,继承

    2024年02月08日
    浏览(47)
  • C++ 基础知识 五 ( 来看来看 面向对象的继承 上篇 )

    C++ 继承是指派生类(子类)从基类(父类)继承属性和行为的过程。我们可以创建一个新的类,该类可以继承另一个类的数据属性和方法。 在上述代码中,我们定义了一个父类 Person 与一个子类 Student。Student 类继承了 Person 类的属性和方法,包括 name、age、gender 和 eat() 函数

    2024年02月03日
    浏览(96)
  • 头歌Educoder实验:C++ 面向对象 - 类的继承与派生

    第1关:公有继承 —— 学生信息类 任务描述 本关任务:采用公有继承设计学生信息类。 相关知识 继承 继承是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一。简单的说,继承是指一个对象直接使用另一对象的属性和方法。 C++ 中的继承关系就好比现实生

    2024年02月04日
    浏览(123)
  • 【C++】面向对象编程(二)面向对象的编程思维:virtual虚拟调用、继承、protected成员、派生类与基类

    默认情形下,成员函数的解析都是编译时静态进行。如果要让成员函数的解析在程序运行时动态进行,需要在成员函数的声明前加上virtual: 虚函数的作用: 用基类的指针指向不同的派生类的对象时,基类指针调用其虚成员函数,会调用 真正指向对象的成员函数 ,而

    2024年02月07日
    浏览(44)
  • c、c++、java、python、js对比【面向对象、过程;解释、编译语言;封装、继承、多态】

    目录 内存管理、适用 区别 C 手动内存管理:C语言没有内置的安全检查机制,容易出现内存泄漏、缓冲区溢出等安全问题。 适用于系统级编程 C++ 手动内存管理:C++需要程序员手动管理内存,包括分配和释放内存,这可能导致内存泄漏和指针错误。 适用于游戏引擎和系统级编

    2024年02月08日
    浏览(74)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包