C++基础语法——继承

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

目录

1.继承是什么?

2.如何定义与书写继承?

3.继承关系与访问限定符及访问关系的变化

4.基类与派生类的赋值转换

5.基类与派生类成员的作用域

6.继承中的默认成员函数

7.继承中的友元

8.继承中的静态成员

9.菱形继承与虚拟菱形继承

①菱形继承是什么?

②菱形继承的缺点

③如何解决菱形继承的问题?

④虚拟菱形继承的底层

⑤一些问题的思考

10.继承与组合


1.继承是什么?

继承是一种面向对象编程的概念,它允许一个类(称为子类或派生类)从另一个类(称为基类或父类)继承属性和方法。继承使得子类能够使用基类已有的代码,并且可以在此基础上进行扩展或修改。继承后,子类可以获得基类的数据成员和成员函数,并且可以在自己的代码中添加新的数据成员和成员函数。子类可以重写(覆盖)继承自基类的方法来实现自己的行为,也可以通过继承实现代码的复用,从而减少重复编写相似代码的工作量。

2.如何定义与书写继承?

在这里我们先举一个例子

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18; // 年龄
};

class Student : public Person
{
protected:
	int _stuid; // 学号
};

class Teacher : public Person
{
protected:
	int _jobid; // 工号
};

同一个人它有可能同时具有多重身份,在上述例子中他可能就同时具有老师和学生两个身份,这两个身份有一些相同点(如它们都是人,所具有的年龄和姓名不会变化),也有一些不同点(如作为老师时有工号,学生时有学号) 。了解这个例子之后我们大概就了解到了继承是什么,那么我们如何来书写它呢?

C++基础语法——继承,c++,c++,开发语言

一般的继承如上图所示书写,冒号后的继承方式可以省略(class类默认的继承方式为private,struct类的默认继承方式为public) 

3.继承关系与访问限定符及访问关系的变化

C++基础语法——继承,c++,c++,开发语言

继承方式与访问限定符如上图所示,而继承方式与访问限定符互相结合便有以下9种情况

C++基础语法——继承,c++,c++,开发语言

通俗一点来说,就是只要是有private修饰的成员(或继承方式)在派生类中均不可见(即在派生类的类里类外均无法访问),而其他情况子类的访问方式由最小的权限确定,权限比较:public > protected > private。

4.基类与派生类的赋值转换

对于之前的例子我们可以发现

C++基础语法——继承,c++,c++,开发语言

在这里我们使用以下代码测试

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18; // 年龄
};

class Teacher : public Person
{
public:
	int _jobid; // 工号
};

int main()
{
	Teacher tobj;
	// 1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = tobj;
	Person* pp = &tobj;
	Person& rp = tobj;

	//2.基类对象不能赋值给派生类对象
	tobj = pobj;

	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
	pp = &tobj;
	Teacher * ps1 = (Teacher*)pp; // 这种情况转换时可以的。
	ps1->_jobid = 10;

	pp = &pobj;
	Teacher* ps2 = (Teacher*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
	ps2->_jobid = 10;

	return 0;
}

 可以发现

1.派生类对象可以赋值给基类的对象 / 指针 / 引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。即上图中的红线部分。
2.基类对象不能赋值给派生类对象。因为派生类对象是以基类对象为基础拓展的类,派生类中含有基类没有的未知项,因此基类无法赋值给派生类。

3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

5.基类与派生类成员的作用域

1. 在继承体系中基类派生类都有独立的作用域。
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员。

我们以下面这段代码为例

class Person
{
protected:
	string _name = "小李子"; // 姓名
	int _num = 111; // 身份证号
};

class Student : public Person
{
public:
	void Print()
	{
		int _num = 0; // 初始号
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << Person::_num << endl;
		cout << " 学号:" << Student::_num << endl;
		cout << " 初始号:" << _num << endl;
	}
protected:
	int _num = 999; // 学号
};

int main()
{
	Student s1;
	s1.Print();

	return 0;
};

 运行有

C++基础语法——继承,c++,c++,开发语言

其实这里实际上与命名空间的作用域类似,查找一个临时变量(成员)时,优先在本函数域内查找,如果没找到就在其他层的域中寻找,此外也可以直接使用预作用限定符到指定域中查找。

值得注意的是,成员函数间构成隐藏时只需要同名即可,因此下面的两个fun函数并不构成重载(不在同一作用域)。 

class A
{
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};

class B : public A
{
public:
	void fun(int i)
	{
		A::fun();
		cout << "func(int i)->" << i << endl;
	}
};

6.继承中的默认成员函数

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构。
7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

我们使用如下的代码来验证

class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person & p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	Student(const char* name, int num)
		: 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)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator =(s);
			_num = s._num;
		}
		return *this;
	}

	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};

int main()
{
	Student s1("jack", 18);
	Student s2(s1);
	Student s3("rose", 17);
	s1 = s3;

	return 0;
}

 我们先运行s1,有

C++基础语法——继承,c++,c++,开发语言

再然后运行s2有 

C++基础语法——继承,c++,c++,开发语言 将s3赋值给s1有

 C++基础语法——继承,c++,c++,开发语言

 总结如下图所示

C++基础语法——继承,c++,c++,开发语言

7.继承中的友元

友元关系不能继承,也就是说基类的友元不能访问子类私有和保护成员,举以下的代码为例

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

class Student : public Person
{
protected:
	int _stuNum; // 学号
};

void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}

int main()
{
	Person p;
	Student s;
	Display(p, s);

	return 0;
}

运行如下C++基础语法——继承,c++,c++,开发语言

可以看到,继承后不能访问Student的成员,如果想要访问需要在Student类中再声明一次。通俗来说就是父亲的朋友不一定是儿子的朋友,要是想要儿子的东西首先需要和儿子做朋友。

8.继承中的静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 。意思就是在基类中定义了静态成员后,往后的所有派生类在使用这个静态成员都共用同一空间,相当于是此静态成员的引用。对于下面这段代码

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum; // 学号
};

class Graduate : public Student 
{
protected:
	string _seminarCourse; // 研究科目
};

void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
}

int main()
{
	TestPerson();

	return 0;
}

经过调试我们可以查看不同派生类中的静态成员地址,有

C++基础语法——继承,c++,c++,开发语言

可以验证我们上面的结论。

9.菱形继承与虚拟菱形继承

①菱形继承是什么?

在了解菱形继承前我们得先了解单继承与多继承

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

C++基础语法——继承,c++,c++,开发语言

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

C++基础语法——继承,c++,c++,开发语言

菱形继承(Diamond Inheritance)是指在C++继承体系中出现的一种特殊情况,其中一个派生类从两个不同的基类继承,而这两个基类又共同继承自同一个基类。由于多重继承的特性,派生类会在继承链中多次拥有同一个基类的成员,形成一个菱形的继承图。

举个例子

C++基础语法——继承,c++,c++,开发语言

②菱形继承的缺点

以如下代码为例

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; // 主修课程
};

C++基础语法——继承,c++,c++,开发语言

从上面的对象成员模型构造,不难看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份,且访问_name成员时无法知道访问哪一个
C++基础语法——继承,c++,c++,开发语言

③如何解决菱形继承的问题?

既然如此,那要如何解决呢?首先是二义性的问题,这里可以显示指定访问父类成员,即

int main()
{
	Assistant a;
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";

	return 0;
}

但是仍无法解决数据冗余的问题,对此C++引入了虚拟菱形继承

代码如下

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; // 主修课程
};

测试有

C++基础语法——继承,c++,c++,开发语言

此时便没有任何问题

④虚拟菱形继承的底层

那么虚拟菱形继承是如何实现的呢?

在这里,我们用下面的代码测试

class A
{
public:
	int _a;
};

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

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

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

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

	return 0;
}

我们先来看菱形继承的情况

在调试中我们取出d的地址观察有

C++基础语法——继承,c++,c++,开发语言

根据对照我们可以得到它的结构,即

C++基础语法——继承,c++,c++,开发语言

我们可以明显发现在d这个对象中,_a生成了两份造成了数据的冗余。

那再来看虚拟菱形继承的情况

取出d的地址观察有

C++基础语法——继承,c++,c++,开发语言

根据对照我们可以得到它的结构,即

C++基础语法——继承,c++,c++,开发语言

可以看到B和C中第一个地址存放的各是一个地址,我们称这两个地址为虚基表,可以发现

C++基础语法——继承,c++,c++,开发语言

C++基础语法——继承,c++,c++,开发语言

在虚基表中第二列存入的数字其实是B、C对象的起始地址与_a地址相差量,即与_a的偏移量。那么它是如何被设计出来的呢?在如下场景的时候

int main()
{
	D d;
	d._a = 1;

	B b;
	b._a = 2;
	b._b = 3;

	B* ptr = &b;
	ptr->_a++;

	ptr = &d;
	ptr->_a++;

	return 0;
}

此时的ptr是无法知道自己指向的到底是基类还是派生类,所以如果他要寻找到_a的话,那么只需要在对应成员存放的地址中找出偏移量,将存放的地址加上该偏移量就可以找到_a。

下图是之前的Person关系菱形虚拟继承的原理解释:

C++基础语法——继承,c++,c++,开发语言

⑤一些问题的思考

如果虚拟继承的基类中不止一个成员变量,在最终的地址中偏移量如何存放?

测试代码如下

class A
{
public:
	int _a;
	int _e;
	int _f;
};

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

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

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

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2; 
	d.B::_e = 6;
	d.C::_e = 7;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	d.B::_f = 8;
	d.C::_f = 9;

	return 0;
}

检测有

C++基础语法——继承,c++,c++,开发语言

结论:在有多个变量时,它们按声明顺序依次存放在最后,而在B与C类中,存放的偏移量始终是最后第一个变量的地址与当前地址的差值。

10.继承与组合

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

3. 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
4. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
5. 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

下面我们作出一些解释,首先是is-a与has-a在这里的体现,举个例子,老师是一个人,学生也是一个人,所以此时继承相性较好,而一个学校是有一个人(很多人),那么此时组合相性较好。用代码描述就是

// 继承
class Person
{
public:
	string _name;
};

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

// 组合
class Person
{
public:
	string _name;
};

class School
{
private:
	Person _p1;
	Person _p2;
    // ...
};

在上面这段代码中,我们可以明显发现,在继承中,父类在子类中是可见的,而子类也是以父类为基础而改良的类,因此当父类发生变化后子类也同时需要进行相应的变化,这样就叫做高耦合;而在组合中,需要做的只是调用该类的接口,而不需要关心其内部的实现,并且当调用的类发生变化后不会影响到自己,这样就叫做低耦合。因此在实际中,可以使用组合的时候应该尽量使用组合来实现低耦合。文章来源地址https://www.toymoban.com/news/detail-655073.html

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

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

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

相关文章

  • java基础语法-package构造方法-继承-多态

    java中的包 - package 包的主要功能: 包的基本语法 在一个文件中,可以没有包,或者一个包。但是不能出现两个包。 包名一般小写,是为了区分类名,类名一般大写 java中存在不同包相同类的名称,我们可以使用包名进行区分 一般情况下,在使用类的情况下,我们都使用类的

    2024年02月05日
    浏览(32)
  • 【C++】继承基础知识及简单应用,使用reportSingleClassLayout(在Visual Studio开发人员命令提示窗口)查看派生类详细信息

    author:Carlton tag:C++ topic:【C++】继承基础知识及简单应用,使用reportSingleClassLayout(在Visual Studio开发人员命令提示窗口)查看派生类详细信息 website:黑马程序员C++ date:2023年7月22日 目录 概要 继承规则 对象模型 构造析构顺序 同名与静态同名成员 多继承 菱形继承 VS开发人

    2024年02月16日
    浏览(43)
  • 初始Java篇(JavaSE基础语法)(6)(继承和多态)(上)

                                                            Java学习篇  个人主页(找往期文章包括但不限于本期文章中不懂的知识点):我要学编程(ಥ_ಥ)-CSDN博客 目录 继承篇  为什么需要继承? 继承概念 继承的语法 父类成员访问 super 子类

    2024年04月15日
    浏览(40)
  • 01 java 学习 数据类型、基础语法、封装、继承、多态、接口、泛型、异常等

    目录 环境搭建和基础知识  什么是JRE: 什么是JDK: 基础数据类型  分支选择if else switch和c一毛一样 for和while循环还有数组基本和c一样 封装 函数调用、传参、命名规范、数组新命名规范 java输入Scanner scanner = new Scanner(System.in); 类的创建和使用以及封装修饰符  构造方法:含义、

    2024年02月11日
    浏览(34)
  • 深入理解ArkTS:Harmony OS 应用开发语言 TypeScript 的基础语法和关键特性

    Harmony OS应用开发的主力语言ArkTS的前身TS语言的基本语法。通过学习变量的声明和数据类型、条件控制、函数声明、循环迭代等基本知识,并了解内核接口的声明和使用。同时还介绍了模块化开发的概念,提高代码的复用性和开发效率。该对话还涉及了if else和switch条件控制语

    2024年02月04日
    浏览(40)
  • HarmonyOS(二)—— 初识ArkTS开发语言(下)之ArkTS声明式语法和组件化基础

    通过前面ArkTS开发语言(上)之TypeScript入门以及ArkTS开发语言(中)之ArkTS的由来和演进俩文我们知道了ArkTS的由来以及演进,知道了ArkTS具备了声明式语法和组件化特性,今天,搭建一个可刷新的排行榜页面。在排行榜页面中,使用循环渲染控制语法来实现列表数据渲染,使

    2024年02月04日
    浏览(41)
  • C++基础篇:07 继承与多态

            当遇到问题,先看一下现有的类是否能够解决一部分问题,如果有则继承,并在此基础上进行扩展来解决所有问题,以此缩短解决问题的时间(代码复用)         当遇到一个大而复杂的问题时,可以把复杂问题拆分成若干个小问题,为每个小问题的解决设计一

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

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

    2024年02月03日
    浏览(83)
  • 【C++语言1】基本语法

    💓作者简介: 加油,旭杏,目前大二,正在学习 C++ , 数据结构 等👀 💓作者主页:加油,旭杏的主页👀 ⏩本文收录在:再识C进阶的专栏👀 🚚代码仓库:旭日东升 1👀 🌹欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖         这篇博客是一个新的开始——学习C++语言,众

    2024年01月24日
    浏览(39)
  • C++(1) —— 基础语法入门

    一、C++初识 1.1 第一个C++程序 1.2 注释  1.3 变量 1.4 常量 1.5 1.6 标识符命名规则 二、数据类型 2.1 整型 2.2 sizeof 2.3 实型(浮点型) 2.4 字符型 2.5 转义字符 2.6 字符串型 2.7 布尔类型 bool 2.8 数据的输入 三、运算符 3.1 算术运算符 3.1.1 加减乘除运算  3.1.2 取模运算

    2024年01月18日
    浏览(78)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包