c++继承(一)

一、总结封装

1、从类的角度:把数据方法放到一起,想让你访问的定义为公有,不想访问的定义为私有。

2、把一个类放到另一个类中,通过typedef 成员函数等方式封装出一个新的类。

二、继承相关概念

1、继承理解

继承与模板相似,都是复用。模板解决类型的复用,继承是类成员设计的复用。

2、语法

cpp 复制代码
class Student : public Person
{...}

Student:要继承的类,叫子类或派生类。

public:继承方式。

Person:被继承的类,叫父类或基类。

3、类成员与继承方式

|--------------|--------------|--------------|------------|
| 类成员 / 继承方式 | public继承 | protected继承 | private继承 |
| 父类的public | 派生类public | 派生类protected | 派生类private |
| 父类的protected | 派生类protected | 派生类protected | 派生类private |
| 父类的private | 派生类不可见 | 派生类不可见 | 派生类不可见 |

不可见:不是没有继承,而是派生类用不了父类的私有成员。

细节

(1)父类 private 在派生类中不可见,但私有是相对的,可以在父类的 get set 函数中获得。

(2)父类其他成员在派生类中的访问方式 == min(访问限定符,继承方式)

(3)protected 成员在派生类中可以使用,类外不能使用,可以看出 protected 因继承出现。

(4)一般用 public 继承

(5)struct 默认共有继承,class 默认私有继承。

4、切片

假设我现在有一个父类 Person(成员变量是name) 和一个子类 Student(成员变量是id)

cpp 复制代码
int main()
{
    Student s;
    Person p1 = s;
    Proson& p2 = s;
    Person* p3 = &s;
    return 0;
}

p1就是典型的切片,在public继承中,如果要把子类赋值给父类,那么就是把子类从父类继承的成员变量赋值给父类。这种是赋值兼容,不产生临时对象!

p2, p3就是直接指向子类中父类的成员变量。

不能把父类赋值给子类,因为在子类中有父类没有的成员变量。

5、继承的作用域

|-------|----------------------|
| 域名 | 备注 |
| 全局域 | 编译器默认在里面找数据,影响变量生命周期 |
| 局部域 | 编译器默认在里面找数据,影响变量生命周期 |
| 类域 | 分为父类域和子类域,是两个不同的域 |
| 命名空间域 | 用命名空间包的域,独立 |

结论

(1)父类与子类的作用域是两个不同的域。

(2)父类与子类可以定义同名成员,若直接访问,默认访问子类成员,这就是隐藏或重定义,可以用父类::父类成员的方式访问。

(3)成员函数名相同就构成隐藏。

注意区分隐藏和函数重载:函数重载是在同一个域中两个函数名相同,参数不同的函数,但是隐藏是父类和子类这两个不同域中的概念。

三、派生类中的默认成员函数

1、举例

cpp 复制代码
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& 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(int num, const char* str, const char* name)
		:Person(name)
		,_num(num)
		,_str(str)
	{
		cout << "Student()" << endl;
	}

	//拷贝构造,切片
	Student(const Student& s)
		:Person(s)
		,_num(s._num)
		,_str(s._str)
	{}


    //赋值构造
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
			_str = s._str;
		}

		return *this;
	}


	// 子类的析构也会隐藏父类
	// 因为后续多态的需要,析构函数名字会被统一处理成destructor
	~Student()
	{
		//如果显示调用父类析构会造成析构两次
		//Person::~Person();

		cout << _name << endl;
		cout << "~Student()" << endl;

		// 注意,为了析构顺序是先子后父,子类析构函数结束后会自动调用父类析构
	}

protected:
	int _num;	
	string _str;
};

2、解析

(1)子类构造函数

首先子类被编译器看成父类成员 + 子类成员(内置类型 + 自定义类型)

构造子类成员和普通类一样:内置类型不做处理,自定义类型调用它的默认构造。

构造父类成员:如果父类有默认构造就可以不显示调用,如果父类没有默认构造就要像上面代码一样显示调用父类构造。

(2)子类拷贝构造函数

像上面代码一样调用父类的拷贝构造函数,传参进行切片。

(3)子类赋值构造函数

调用父类赋值构造函数必须加上类域,不然会默认调用子类赋值构造无限递归栈溢出。

(4)子类析构函数

不要显示调用父类析构函数,为了先析构子类,在析构父类,编译器会自动调用父类析构,如果再手动调用会导致一块空间被释放两次报错。

构造函数:先父后子

析构函数:先子后父

相关推荐
染指11102 小时前
50.第二阶段x86游戏实战2-lua获取本地寻路,跨地图寻路和获取当前地图id
c++·windows·lua·游戏安全·反游戏外挂·游戏逆向·luastudio
Code out the future2 小时前
【C++——临时对象,const T&】
开发语言·c++
sam-zy2 小时前
MFC用List Control 和Picture控件实现界面切换效果
c++·mfc
aaasssdddd963 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
发呆小天才O.oᯅ3 小时前
YOLOv8目标检测——详细记录使用OpenCV的DNN模块进行推理部署C++实现
c++·图像处理·人工智能·opencv·yolo·目标检测·dnn
qincjun4 小时前
文件I/O操作:C++
开发语言·c++
星语心愿.4 小时前
D4——贪心练习
c++·算法·贪心算法
汉克老师4 小时前
2023年厦门市第30届小学生C++信息学竞赛复赛上机操作题(三、2023C. 太空旅行(travel))
开发语言·c++
single5944 小时前
【c++笔试强训】(第四十一篇)
java·c++·算法·深度优先·图论·牛客
yuanbenshidiaos4 小时前
C++-----函数与库
开发语言·c++·算法