C++继承 -- 讲解超详细(上)

1. 继承的概念和定义

1.1 继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类。一句话说明:继承=子类复用+拓展父类的功能

举个简单例子:

说白了继承就是继承"家产"和拥有独立的个性。

通过一个代码详细分析:

cpp 复制代码
class teacher
{
public:
	teacher()
	{}
private:
	string _name; 
	string _address;
	int _age;
	string _id;

	string _title;
};

class student
{
public:
	student()
	{}
private:
	string _name;
	string _address;
	int _age;
	string _id;
};

class person
{
public:
	person()
	{}
	int tel = 10086;
protected:
	//string _name = "xiaoli";
private:
	string _name = "xiaoli";
	string _address;
	int _age;
	string _id;
};

按照之前学的类,定义的话需要定义如上三个类,但是仔细观察你会发现三者有许多共同的部分:名称、地址、年龄、地址等

因此如果通过继承的方式,可以得到:

cpp 复制代码
class person
{
public:
	person()
	{}
	int tel = 10086;
protected:
	//string _name = "xiaoli";
private:
	string _name = "xiaoli";
	string _address;
	int _age;
	string _id;
};

class student : public person
{
public:
	student()
	{
		//cout << _name << endl; //私有成员无法访问
		cout << tel << endl;
	}
private:
	int _grade;
};


class teacher : public person
{
public:
	teacher()
	{
		//_name;
	}
private:
	string _title;
};

问题1:子类继承的父类的成员变量,子类能不能修改?

答案是:要看继承方式:

父类private成员:子类不能直接改,必须用父类提供的函数修改

父类public/protected成员:子类可以直接改

那么什么是继承方式?你这代码里public,protect都是啥意思?🆗接下来就介绍继承方式!

1.2 继承的定义

这里person是基类(也称作父类),student和teacher都是派生类(也称作子类)。

这些方式有什么区别?

|------------------|-------------------|-------------------|-------------------|
| 派生类继承方式 | public继承 | protetced继承 | private继承 |
| 基类的public成员 | 派生类的public成员 | 派生类的protect成员 | 派生类的private成员 |
| 基类的protect成员 | 派生类的protect成员 | 派生类的protect成员 | 派生类的private成员 |
| 基类的private成员 | 派生类的private成员 | 派生类的private成员 | 派生类的private成员 |

1)基类的private成员无论以何种方式继承都不可见。也就是派生类还是会继承基类的私有成员到类里面,但是派生类无法访问和修改它。当然不能说完全不能改,可以间接的修改(比如通过父类的公有成员访问和修改)。

2)基类的protected继承:由于基类private成员在派生类无法访问,于是官方就给出了protected,派生类里面可以访问,但是类外面不能访问。说白了自己能用 + 儿子(子类)能用 + 外人不能用!!

3)基类的私有成员在派生类都是不可见。基类的其他成员在派生类的访问方式==Min(成员在基类的访问限定符,继承方式),public >protected> private。

4)使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

cpp 复制代码
class student : person
{
public:
	student()
	{
		//cout << tel << endl;//私有继承,变成student的私有成员,类外无法访问
	}
private:
	int _grade;
};
int main()
{
	student s1;
	//cout << s1.tel << endl; //error
	return 0;
}

5)在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

1.3 继承容易混淆的点

查找:继承访问的时候先到子类中找;子类如果未找到,再去父类中继承的部分找。

本质原因:因为子类继承父类时,父类的所有成员变量(public/protected/private)都会实实在在地复制一份,存到子类对象的内存里。因此****子类对象里自带一份完整的父类成员副本
补充1:父类的成员函数并不会复制到子类,因为函数存在代码区,所有对象共用一份。
只有成员变量会复制。

补充2:静态成员变量不会复制,静态成员属于类,不属于对象,所有对象共享一份。(详见第6点)

cpp 复制代码
class Father 
{
public:
	void test()
	{
		int s = 10;
	}
private:
	int a;  // 私有,照样占空间
};

class Son : public Father 
{
private:
	int b;
};

int main() 
{
	cout << sizeof(Father) << endl;  // 4
	cout << sizeof(Son) << endl;     // 8!证明多了一份 int
}

由此可以观察正式子类存储一份父类的副本!但是增加了test函数后,结果依旧不改变,证实了父类的成员函数不存储!!!

对于查找,举个简单例子:

cpp 复制代码
// 基类模板继承按需实例化
template<class T>
class stack : public vector<T>
{
public:
    void push(const T& x)
    {
        // 基类模板继承需要实例化,否则找不到push_back
        // 具体解释见模板那一节
        vector<T>::push_back(x);
        // 只有实例化才知道去哪里找
        //push_back(x);
    }
    void pop()
    {
        vector<T>::pop_back();
    }
    const T& top()
    {
        return vector<T>::back();
    }
};

大致就是:公有继承vector,当你调用push的时候,先在子类找,找到了就调用vector的push_back函数,如果没有找到,就需要去vector内部查找push。

2. 基类和派生类的转换

(1)public继承的 派生类对象 可以赋值给 基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分

cpp 复制代码
class person
{
public:
	person()
	{
	}
	int tel = 10086;
protected:
	string _name = "xiaoli";
	string _address;
	int _age;
};

class student : public person
{
public:
	int tel = 10000;
    int count = 0;
private:
    string _id;
};

int main()
{
	student s1;
	cout << s1.tel << endl;
	return 0;
}

int main()
{
	student s1;
    // 把派生类赋值给基类的指针
	person* ptr = &s1;
    // 把派生类赋值给基类的引用
	person& rs = s1;
	cout << s1.tel << endl;
	cout << ptr->tel << endl;
	cout << rs.tel << endl;
    //cout << rs.count << endl; //error,person不含有count
	//基类不能赋值给子类
	person p1;
	//student s2 = p1; //error

	p1 = s1;
	return 0;
}

也就是person* 和 person& 只会把属于person的部分给切走,不属于他的部分他带不走,因此如果强行访问不属于person的内容,编译器会报错!!!

(2)基类不能赋值给派生类。基类(父类)是 "小" 的、通用的;派生类(子类)是 "大" 的、扩展的。直接把基类赋值给派生类,会破坏数据完整性、类型安全,程序会直接出错。可以认为派生类的数据被覆盖,导致数据丢失。

cpp 复制代码
//基类不能赋值给子类
person p1;
//student s2 = p1; // ×
p1 = s1;// √

**(3)基类的指针或者引用可以通过 ++强制类型转换++赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。来看两个例子:

示例1:基类的指针指向派生类对象

cpp 复制代码
// 基类
class Animal {
public:
	void speak() 
	{
		cout << "动物叫" << endl;
	}
};

// 派生类
class Dog : public Animal {
public:
	void speak()
	{
		cout << "汪汪汪" << endl;
	}
	// 派生类独有函数
	void watchDoor() 
	{
		cout << "小狗看门" << endl;
	}
};
int main() 
{
	// 1. 创建一个Dog对象,用基类指针指向它
	Animal* animal= new Dog();
	// 2. 强制把基类指针转成派生类指针 // √
	Dog* dog = (Dog*)animal;
	// 3. 安全调用派生类独有函数
	dog->speak();     // 正常输出:汪汪汪
	dog->watchDoor(); // 正常输出:小狗看门

	delete animal;
	return 0;
}

注意:这种写法animal虽然是切片,也就是animal本身只能看到(调用)基类成员,但是它指向的内存里100%存储着完整的Dog!!!强转之后完全可以访问Dog独有的成员变量/函数( 因为内存里本来就是完整的Dog,只是之前被基类指针 "遮住了")。

打个比方:带上口罩我叫老王(了解片面),但是去掉口罩我是王五(全面了解)。

示例2:基类的指针指向基类对象

cpp 复制代码
int main() {
    // 1. 创建一个 纯基类对象
    Animal* Pa= new Animal(); // 区别在这里
    
    // 2. 强制转成派生类指针(语法允许,但逻辑错误!)
    Dog* pdog = (Dog*)Pa; 

    // 3. 调用派生类函数 → 未定义行为!
    pdog->speak();     // 可能乱码、崩溃
    pdog->watchDoor(); // 大概率直接崩溃

    delete Pa;
    return 0;
}

注意:这样写很危险,animal指向的是纯Animal对象,内存里根本没有dog成员和函数。强行转换为dog,就相当于这里的animal里面啥都没有!!

3. 继承中的作用域

3.1 隐藏规则

(1)无论是基类还是派生类都拥有独立的作用域。

(2)如果基类和派生类拥有重名的变量/函数,优先访问派生类的变量/函数(也就是屏蔽基类对同名函数的访问),构成隐藏。(在派生类成员函数中,可以使用基类::基类成员显示访问)

(3)建议在实际场景中不要使用重名变量和对象。

cpp 复制代码
class person
{
public:
	int add(int x, int y)
	{
		return x + y;
	}
protected:
	int _num = 111;
	string _name = "xiaoli"; 
};

class student : public person
{
public:
	int add(int x, int y)
	{
		return x * y;
		//return person::add(x, y);
	}
	void Print()
	{
		cout << " 姓名:"<<_name<< endl;
		cout << " 身份证号:" << person::_num << endl;
		cout << " 学号: "<<_num<<endl;
		// 构成隐藏,如果在同一类域则构成重载
		// 隐藏的如果不指定,则会报错
		cout << add(10, 5) << endl;
		cout << person::add(10, 5) << endl;
	}
protected:
	int _num = 999;
};

int main()
{
	student s1;
	s1.add(1, 5);
	s1.Print();
	return 0;
};

在student中调用add,会优先走x*y,但当显示调用person::add的时候就会走person的add函数!

4. 继承和友元函数

  1. 什么是友元函数?它是一个不属于某个类的外部函数,但被这个类授权,可以直接访问类里的私有(private)和保护(protected)成员

说白话就是:声明你是我的朋友,你原来无法访问的私有变量可以被我访问!!!(可以用好朋友的东西)

  1. 友元关系不可以被继承,就比如我是你的朋友,但是你的朋友不一定是我的朋友!!!

比如:基类的友元不能访问派生类的私有成员和保护成员变量!!!

cpp 复制代码
class sub;
class add
{
public:
	friend void test(const add& a, const sub& s);
private:
	int _m = 1;
	int _n = 2;
public:
	static double id;
	int a = 10;
};
double add::id = 10;

class sub:public add
{
public:
	friend void test(const add& a, const sub& s);

	void print()
	{
		cout << a << endl; 
		cout << id << endl; 
        //cout << _n << endl; //不是基类的友元函数,无法访问!
	}
	//cout << _count << endl;
private:
	int _count = 99;

};

void test(const add& a,const sub& s)
{
	cout << a._m << endl;
	cout << a._n << endl;
	cout << s._count << endl;

}
int main()
{
	add a;
	sub s;
	cout << &a.id << endl;
	cout << &s.id << endl;

	cout << &a.a << endl;
	cout << &s.a << endl;
	return 0;
}

add函数无法访问sub的私有成员变量,虽然都是test的好朋友但是add和sub并不是好朋友!!

5. 继承与静态成员

父类定义的静态成员变量存储在静态区,也就是只有一份;

整个继承体系里面只有一个这样的成员,无论有多少个子类都不会复制!!

cpp 复制代码
class Father 
{
public:
	void test()
	{
		int s = 10;
	}
private:
	int a;  // 私有,照样占空间
	static int sum;
};
int Father::sum = 0;

class Son : public Father 
{
private:
	int b;
};

int main() 
{
	cout << sizeof(Father) << endl;  // 4
	cout << sizeof(Son) << endl;     // 8!证明多了一份 int
}

这里需要注意:类里面的静态成员变量是类里面声明,类外面定义!!

根据运行结果可以发现,静态成员只有一份!!

下节课会继续介绍继承,主要是派生类的默认成员函数,虚继承,组合与继承的知识!!如果对你有帮助的话,点赞收藏一下吧~

相关推荐
ZPC82102 小时前
ROS2 共享内存 SHM > UDP 速度
人工智能·算法·计算机视觉·机器人
fish_xk2 小时前
c++的list
开发语言·c++·list
三毛的二哥10 小时前
BEV:典型BEV算法总结
人工智能·算法·计算机视觉·3d
南宫萧幕11 小时前
自控PID+MATLAB仿真+混动P0/P1/P2/P3/P4构型
算法·机器学习·matlab·simulink·控制·pid
浪浪小洋12 小时前
c++ qt课设定制
开发语言·c++
charlie11451419112 小时前
嵌入式C++工程实践第16篇:第四次重构 —— LED模板,从通用GPIO到专用抽象
c语言·开发语言·c++·驱动开发·嵌入式硬件·重构
handler0112 小时前
Linux: 基本指令知识点(2)
linux·服务器·c语言·c++·笔记·学习
故事和你9112 小时前
洛谷-数据结构1-4-图的基本应用1
开发语言·数据结构·算法·深度优先·动态规划·图论
我叫黑大帅12 小时前
为什么map查找时间复杂度是O(1)?
后端·算法·面试