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)子类析构函数

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

构造函数:先父后子

析构函数:先子后父

相关推荐
微尘813 分钟前
C语言存储类型 auto,register,static,extern
服务器·c语言·开发语言·c++·后端
金博客32 分钟前
Qt 模型视图(二):模型类QAbstractItemModel
c++·qt6.7.2
五味香1 小时前
C++学习,动态内存
java·c语言·开发语言·jvm·c++·学习·算法
无名之逆1 小时前
计算机专业的就业方向
java·开发语言·c++·人工智能·git·考研·面试
Beauty.5681 小时前
P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布
数据结构·c++·算法
jimmy.hua1 小时前
C++刷怪笼(5)内存管理
开发语言·数据结构·c++
xiaobai12 31 小时前
二叉树的遍历【C++】
开发语言·c++·算法
DieSnowK1 小时前
[项目][WebServer][Makefile & Shell]详细讲解
开发语言·c++·http·makefile·shell·项目·webserver
dc爱傲雪和技术2 小时前
在 VS Code 中调试 C++ 项目
开发语言·c++
如意.7592 小时前
【C++】——多态详解
开发语言·c++