首页 > 编程语言 >C++ 类继承

C++ 类继承

时间:2024-09-11 16:23:58浏览次数:12  
标签:继承 子类 C++ class Person 父类 public


一、继承

1.继承的概念和意义

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有
类特性的基础上进行扩展,产生新的类,称子类。

例如下面的Student 类和Teacher类,它们都有人名,性别,电话,年龄,住址等信息。不同的是Student类有学号之分,而老师有职称之分。由此我们可以先定义一个Person 类,后面再让Student类和Teacher类继承Person类 从而达到代码复用的效果。

struct Person
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证
	void identity()
	{
		cout << "void identity()" << _name << endl;
	}
protected:
	string _name = "张三";
	// 姓名
	int _age = 18;
	// 年龄
	string _address;
	// 地址
	string _tel;
	//电话
};
struct Student: public Person
{
public:
	// 学习
	void study()
	{
		// ...
	}
protected:
	int _stuid;
	//学号
};
class Teacher : public Person
{
public:
	// 授课
	void teaching()
	{
		//...
	}
protected:
	string title;
	// 职称
};

2.定义格式

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

3.继承父类成员访问方式变化

(1)首先就是父类的private成员不管子类以何种方式继承都不可见的。不可见不是说没有继承到,只是说不管在子类的类外还是类内都不可以访问。

(2)剩余的继承方式总结一句就是父类和子类的访问限定符中取访问权限更小的。而public > protected > private。

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

(4)要注意就是一般来不会用private限定继承方式因为代码的可维护性会不太好。

4.继承类模板

#include<iostream>
#include<vector>
using namespace std;
namespace bit
{
	// stack和vector的关系,既符合is-a,也符合has-a
	template<class T>
	class stack : public std::vector<T>
	{
	public:
		void push(const T& x)
		{
			// ⽗类是类模板时,需要指定⼀下类域,
			// 否则编译报错:error C3861: “push_back”: 找不到标识符
			// 因为stack<int>实例化时,也实例化vector<int>了
			// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到
			vector<T>::push_back(x);
			//push_back(x);
		}
		
			void pop()
		{
			vector<T>::pop_back();
		}
		const T& top()
		{
			return vector<T>::back();
		}
		bool empty()
		{
			return vector<T>::empty();
		}
	};
}
int main()
{
	bit::stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}
	return 0;
}

二、父类和子类对象赋值兼容转换

public继承的子类对象可以赋值给父类的对象/父类的指针/父类的引用。这里有形象的说法叫切片或切割,意思就是把子类中父类的那部分切来赋值给父类对象。

但是父类对象不能赋值给子类对象。

class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int
_age; // 年龄
};
class Student : public Person
{
public :
int _No ; // 学号
};
int main()
{
Student sobj ;
// 1.⼦类对象可以赋值给⽗类对象/指针/引⽤
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//2.⽗类对象不能赋值给⼦类对象,这⾥会编译报错
sobj = pobj;
return 0;
}

三、继承中的作用域

1.在继承体系中父类和子类都有独立作用域。

2.若子类和父类中有同名函数,子类将屏蔽父类对同函数的访问,这种情况叫隐藏。(在子类成员函数中可以用父类名:父类成员函数进行访问)。

3.注意函数名相同即构成隐藏,因在命名时尽量避免同名函数。

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 = 999; // 学号
};
int main()
{
	Student s1;
	s1.Print();
	return 0;
};

四、子类的默认成员函数

1.子类的构造函数初始化对象时,必须先调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认的构造函数,则必须在子类构造函数的初始化列表时显示调用。

2.子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化。

3.子类的operator=必须要调用父类的operator=完成父类的复制。需要注意的是子类的operator=隐藏了父类的operator=,所以显示调用父类的operator=需要指定父类作用域。

4.子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序。

5.子类对象初始化先调用父类构造再调子类构造。

6.子类对象析构清理先调用子类析构再调父类的析构。

五、继承与友元

友元关系不能继承,也就是说父类友元函数不能访问子类成员。

若想访问则在子类中也加入友元声明。

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;
	// 编译报错:error C2248: “Student::_stuNum”: ⽆法访问 protected 成员
	// 解决⽅案:Display也变成Student 的友元即可
	Display(p, s);
	return 0;
}

六、继承与静态成员

父类定义了static静态成员,则整个继承体系里面只有⼀个这样的成员。无论派生出多少个子类,都只有⼀个static成员实例。

class Person
{
public:
string _name;
static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNum;
};
int main()
{
Person p;
Student s;
// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的
// 说明⼦类继承下来了,⽗⼦类对象各有⼀份
cout << &p._name << endl;
cout << &s._name << endl;
// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的
// 说明⼦类和⽗类共⽤同⼀份静态成员
cout << &p._count << endl;
cout << &s._count << endl;
// 公有的情况下,⽗⼦类指定类域都可以访问静态成员
cout << Person::_count << endl;
cout << Student::_count << endl;
return 0;
}

七、多继承和菱形继承问题

1.继承模型

(1)单继承:一个子类只有⼀个直接父类时称这个继承关系为单继承。

(2)多继承:一个子类只有两个即以上的直接父类时称这个继承关系为多继承。

(3)菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下面的对象成员模型构造,可以看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。支持多继承就⼀定会有菱形继承,像Java就直接不支持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。

#include<iostream>
using namespace std;
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; // 主修课程
};
int main()
{
	// 编译报错:error C2385: 对“_name”的访问不明确
	Assistant a;
	a._name = "peter";
	// 需要显⽰指定访问哪个⽗类的成员可以解决⼆义性问题,但是数据冗余问题⽆法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
	return 0;
}

2.虚继承

#include<iostream>
using namespce std:
class Person
{
public:
	string _name; // 姓名
};
// 使⽤虚继承Person类
class Student : virtual public Person
{
protected:
	int _num; //学号
};
// 使⽤虚继承Person类
class Teacher : virtual public Person
{
protected:
	int _id; // 职⼯编号
};
// 教授助理
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
int main()
{
	// 使⽤虚继承,可以解决数据冗余和⼆义性
	Assistant a;
	a._name = "peter";
	return 0;
}

3.多继承中指针偏移问题

就如下面的代码,Derive类继承了Base1类和Base2类,当实例化Derive d 时,将它分别赋值给Base1类指针p1和Base2类指针p2,在定义一个Derive类指针p3,就会发现p1==p3。同时若先继承Base2类,则p2==p3。

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main()
{
  Derive d;
  Base1* p1 = &d;
  Base2* p2 = &d;
  Derive* p3 = &d;
  return 0;
}

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base2, public Base1 { public: int _d; };
int main()
{
  Derive d;
  Base1* p1 = &d;
  Base2* p2 = &d;
  Derive* p3 = &d;
  return 0;
}

八、继承与组合

1.public继承是⼀种is-a的关系。也就是说每个子类对象都是⼀个父类对象。

2.继承允许你根据父类的实现来定义子类的实现。这种通过生成子类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类可见
但继承⼀定程度破坏了父类的封装,父类的改变,对子类有很大的影响。子类和父类间的依赖关系
很强,耦合度高。
3.组合是⼀种has-a的关系。假设B组合了A,每个B对象中都有⼀个A对象。

4.对象组合是类继承之外的另⼀种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

5.优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is-a)那就用继承,另外要实现多态,也必须要继承。类之间的关系既适合用继承(is-a)也适合组合(has-a),就用组合。


下面是本章节的思维导图

标签:继承,子类,C++,class,Person,父类,public
From: https://blog.csdn.net/2401_86239408/article/details/142107066

相关文章

  • 中国电科网安校园招聘:C++工程师
      本文介绍2024届秋招中,中国电子科技网络信息安全有限公司的C/C++开发工程师岗位一面的面试基本情况、提问问题等。  2024年10月投递了中国电子科技网络信息安全有限公司的C/C++开发工程师岗位,并不清楚所在的部门。目前完成了一面,在这里记录一下一面经历。  这一次面试面......
  • C++最强功能之一指针
    最是人间留不住,朱颜辞镜花辞树。                            ——《蝶恋花·阅尽天涯离别苦》【清】王国维今天我们来说一说这个C++区别其他语言最明显的功能,指针。C++的指针可以说是功能强大,很多游戏的外挂中核......
  • C++ 不要将有符号整数和无符号整数相加
    一有符号整数和无符号整数相加时,把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。unsignedintn=300;intm=-500;cout<<m+m<<'\n';cout<<n+m<<'\n';输出:-1000//正确4294967096//错误结果做个类型......
  • 南沙C++信奥老师解一本通题:1203:扩号匹配问题
    ​【题目描述】在某个字符串(长度不超过100)中有左括号、右括号和大小写字母;规定(与常见的算数式子一样)任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序,找到无法匹配的左括号和右括号,输出原来字符串,并在下一行标出不能匹配的括号。不能匹配的左括号用"$"标......
  • C++题目收集2
    这是本专栏的的第二篇收录集,我们一起来看一看那些有意思的题目,拓宽自己的思路。本期的题目有一些难,所以数目少一点。题目一:约瑟夫环#include<iostream>#include<vector>intjosephus(intn,intm){std::vector<int>people(n);for(inti=0;i<n;++i)......
  • 《C++ Primer Plus》学习day3
    C++11新增的内容:char16_t和char32_tchar16_t:无符号,16位,使用前缀u表示char_16字符常量和字符串常量;char32_t:无符号,32位,使用前缀U表示char32_t常量浮点类型C++有三种浮点类型:float、double、longdouble头文件cfloat中对对浮点数进行了限制:比如最低有效位......
  • C++入门 一(命名空间,缺省参数,超详细!!!)
    文章目录C++与C语言的区别命名空间(Namespace)缺省参数C++与C语言的区别C++在保留了C语言所有特性的基础上增加了面向对象编程的支持,并引入了更多的高级特性和工具来提高代码的可读性、可维护性和可扩展性。而C语言则更加简洁和底层,适用于对性能要求极高或对内存管理......
  • c++引用
    c++指针和引用的区别指针和引用在C++中都用于间接访问变量,但它们有一些区别:指针是一个变量,它保存了另一个变量的内存地址;引用是另一个变量的别名,与原变量共享内存地址。指针(除指针常量)可以被重新赋值,指向不同的变量;引用在初始化后不能更改,始终指向同一个变量。指针可以为......
  • C++入门知识
    命名空间为什么会有命名空间?解决C语言中命名冲突的问题。(std是所有c++库的命名空间)解决方法:采用域作用限定符(::):指定编译器搜索的位置。编译器默认的搜索顺序:先局部再全局。命名空间域大体有4种域:全局域、局部域、命名空间域、类域。命名空间域的特点:1.不会影响生命......
  • 挑战不可能篇1——洛谷28分钟14道CCF GESP C++ 一级上机题&洛谷14道题题解
    扯谈今天继续挑战不可能:洛谷28分钟14道题这我个人认为不简单,算上编译、提交、命名等杂七杂八的东东之后,只剩下了大约1分钟/题。本次挑战的是CCFGESPC++一级上机题.这竟然能成功!下面附上每一题第一题第二题第三题第四题第五题第六题第七题第八题第九题第十题......