C++的忠实粉丝-继承的热情(1)

目录

前言

1.继承的概念和定义

1.1继承的概念

1.2继承的定义

[1.2.1 定义格式](#1.2.1 定义格式)

[1.2.2 继承关系和访问限定符](#1.2.2 继承关系和访问限定符)

[1.2.3 继承基类成员访问方式的变化](#1.2.3 继承基类成员访问方式的变化)

2.基类和派生类对象之间的转换

3.继承中的作用域

[3.1 隐藏规则](#3.1 隐藏规则)

[3.2 选择题练习](#3.2 选择题练习)

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

[4.1 4个常见默认成员函数](#4.1 4个常见默认成员函数)

[4.2 实现一个不能继承的类](#4.2 实现一个不能继承的类)

结束语


前言

我们知道C++有三大特性,封装,继承,多态,它们是构建面向对象程序的基础,本节内容我们将来领略继承的魅力。

1.继承的概念和定义

1.1继承的概念

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

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class  person {
public:
	person() {
		}
	void print() {
		cout << _name << " " << _age << endl;
	}
protected:
	string _name="xiaoyan";
	int _age=21;
};
class student :public person {
public:
	student() {
		cout << "student id:"<<id << endl;
	}
	
protected :
	int id=21;
};
class teacher : public person {
public:
	teacher() {
		cout << "teacher job_id:" << job_id << endl;
	}

protected:
	int job_id=23;
};
int main() {
	student s;
	teacher t;
	s.print();
	t.print();
	return 0;
}

继承后父类的p erson 的成员(成员函数 + 成员变量)都会变成子类的一部分。这里体现出了
Student 和 Teacher 复用了 Person 的成员。下面我们使用监视窗口查看s tudent 和t eacher 对象,可
以看到变量的复用。调用 Print 可以看到成员函数的复用。

接下来我们将细节层面讲解继承的相关知识。

1.2继承的定义

1.2.1 定义格式

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

1.2.2****继承关系和访问限定符

1.2.3****继承基类成员访问方式的变化

总结:

  1. 基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的 不可见是指基类的私
    有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面
    都不能去访问它
  2. 基类 private 成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在
    派生类中能访问,就定义为 protected 。 可以看出保护成员限定符是因继承才出现的
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他
    成员在子类的访问方式 == Min( 成员在基类的访问限定符,继承方式 ) , public > protected > private。
  4. 使用关键字 class 时默认的继承方式是 private ,使用 struct 时默认的继承方式是 public , 不过
    最好显示的写出继承方式
  5. 在实际运用中一般使用都是 public 继承,几乎很少使用 protetced/private 继承 ,也不提倡
    使用 protetced/private 继承,因为 protetced/private 继承下来的成员都只能在派生类的类里
    面使用,实际中扩展维护性不强。
cpp 复制代码
#include <iostream>
using namespace std;
class Person
{
public:
	void Print()
	{
		cout << _name << endl;
	}
protected:
	string _name="萧炎"; // 姓名
private:
	int _age; // 年龄
};
//class Student : protected Person
class Student : private Person
//class Student : public Person
{
protected:
	int _stunum; // 学号
};
int main() {
	Student s;
	s.Print();
	return 0;
}

我们发现只有在public的情况下代码才能正常运行。或者用struct Student :Person

2.基类和派生类对象之间的转换

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用 。这里有个形象的说法叫切片
或者切割。寓意把派生类中父类那部分切来赋值过去。
基类对象不能赋值给派生类对象。
基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用 RTTI(Run
Time Type Information) 的 dynamic_cast 来进行识别后进行安全转换。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class person {
public:
	person() {}

	person(const string& name, int age, const string& sex)
		: _name(name), _age(age), _sex(sex) {}

protected:
	string _name;
	int _age;
	string _sex;
};

class student : public person {
public:
	// 使用初始化列表调用基类构造函数
	student(const string& name, int age, const string& sex)
		: person(name, age, sex) {}

	int stu_id;
};

int main() {

	student s("萧炎", 21, "男");
	s.stu_id = 12345;  
	person  p = s;
	return 0;
}
cpp 复制代码
#include <iostream>
using namespace std;

class person {
public:
	person() {}

	person(const string& name, int age, const string& sex)
		: _name(name), _age(age), _sex(sex) {}

protected:
	string _name;
	int _age;
	string _sex;
};

class student : public person {
public:
	// 使用初始化列表调用基类构造函数
	student(const string& name, int age, const string& sex)
		: person(name, age, sex) {}

	int stu_id;
};

int main() {

	student s("萧炎", 21, "男");
	s.stu_id = 12345;  
	// 1.子类对象可以赋值给父类对象/指针/引用
	person  p = s;
	person* p1 = &s;
	person& p2 = s;
	//2.基类对象不能赋值给派生类对象
	//s = p;
	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
	p1= &s;
	student * ps1 = (student*)p1; // 这种情况转换时可以的。
	ps1->stu_id= 10;

	return 0;
}

3.继承中的作用域

3.1 隐藏规则

  1. 在继承体系中基类和派生类都有独立的作⽤域。
  2. 派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。
    (在派生类成员函数中,可以使用基类::基类成员显示访问)
  3. 需要注意的是如果是成员函数的隐藏, 只需要函数名相同就构成隐藏 。
  4. 注意在实际中在继承体里面最好不要定义同名的成员。
cpp 复制代码
class Person
{
protected:
	string _name = "萧炎"; // 姓名
	int _num = 111; // ⾝份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << Person::_num << endl;
		cout << " 学号:" << _num << endl;
	}
protected:
	int _num = 520; // 学号
};
int main()
{
	Student s1;
	s1.Print();
	return 0;
};

3.2 选择题练习

cpp 复制代码
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
cout << "func(int i)" <<i<<endl;
}
};
int main()
{
B b;
b.fun(10);
b.fun();
return 0;
};

A和B类中的两个func构成什么关系(B)
A. 重载 B. 隐藏 C.没关系
程序的编译运行结果是什么(A)
A. 编译报错 B. 运行报错 C. 正常运行

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

4.1 4个常见默认成员函数

6个默认成员函数,默认的意思就是指我们不写,编译器会变我们自动生成⼀个,那么在派生类中,这几个成员函数是如何生成的?

  1. 派生类的构造函数必须调调基类的构造函数初始化基类的那一部分成员 。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的 拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。需要注意的是派生类的
    operator=隐藏了基类的operator=,所以 显示调用基类的operator=,需要指定基类作用域
  4. 派生类的析构函数会在被调用完成后自动调基类的析构函数清理基类成员。 因为这样才能保证派
    生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构
  7. 因为多态中一些场景析构函数需要构成重写,重写的条件之一是函数名相同。
    那么编译器会对析构函数名进行特殊处理,处理成destructor(),所以基类析构函数不加
    virtual的情况下,派生类析构函数和基类析构函数构成隐藏关系。
cpp 复制代码
#include <iostream>
using namespace std;
class person {
public:
	person() {};
	person(const string& name, int age, const string& sex)
		:_name(name),
		_age(age),
		_sex(sex)
	{
		cout << "person()" << endl;
	}
	~person() {
		cout << "~person()" << endl;
	}
	person(const person& p) {
		_name = p._name;
		_age = p._age;
		_sex = p._sex;
		cout << "person(const person& p)" << endl;
	}
	person& operator=(const person& p) {
		if (&p != this) {
			_name = p._name;
			_age = p._age;
			_sex = p._sex;
			cout << "person operator=(const person& p)" << endl;
			return *this;
		}
	}
protected:
	string _name;
	int _age;
	string _sex;

};
class student :public person {
public:
	student(const string& name, int age, const string& sex, int id) {
		person(name, age, sex);
		_id = id;
		cout << "student()" << endl;
	}
	/*
	student(const student& s){
    person(s._name, s._age, s._sex); // 使用 s 的成员变量来调用基类的构造函数
    _id = s._id;
    
    cout << "student(const student& s)" << endl;
}
	*/
	student(const student&s)
		:person(s),
		_id(s._id)
	{}
	student& operator=(const student& s) {
		if (&s != this) {
			person::operator=(s);
			_id = s._id;
			cout << "student operator=(const student& s)" << endl;
			return *this;
		}
	}
	~student() {
		cout << "~student()" << endl;
	}
protected:
	int _id;
};
int main() {
	student s1("zhangsan", 18, "nan", 1001);
	student s2(s1);
	student s3("lisi", 19, "nv", 1002);
	s3 = s1;


	return 0;
}

s3赋值后:

可通过修改注释测试的代码来观察对应的情况。

4.2 实现一个不能继承的类

方法1:基类的构造函数私有,派生类的构成必须调用基类的构造函数,但是基类的构成函数私有化以后,派生类看不见就不能调用了,那么派生类就无法实例化出对象。

cpp 复制代码
class Base 
{
private:
	// C++98的⽅法
	Base(){}
};
class Derive :public Base
{
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}


方法2:C++11新增了一个final关键字,final修改基类,派生类就不能继承了。

cpp 复制代码
#include <iostream>
using namespace std;
class Base
{
public:
	Base() {}
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
};
class Derive :public Base
{
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}

上述代码能编译成功,但是加了final后就会失败。

cpp 复制代码
#include <iostream>
using namespace std;
class Base final
{
public:
	Base() {}
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
};
class Derive :public Base
{
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}

结束语

本节内容就到此结束了 ,继承的内容还有一些部分没有讲完,我们下节在继续补充!!!

最后,小编衷心感谢大家一路来的支持和帮助!!!

相关推荐
数据小爬虫@8 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.10 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy15 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader22 分钟前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默33 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
yuyanjingtao36 分钟前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
Code哈哈笑42 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_433618441 小时前
shell 编程(二)
开发语言·bash·shell
闻缺陷则喜何志丹1 小时前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径