C++笔记归纳10:继承

继承

目录

继承

一、封装

二、继承的概念及定义

2.1.继承的概念

2.2.继承的定义

2.3.类模板继承类模板

三、赋值兼容转换

3.1.切片(切割)

3.2.隐藏

3.3.试题

四、子类的默认成员函数

4.1.构造函数

4.2.拷贝构造函数

4.3.赋值运算符重载

4.4.析构函数

五、继承与友元

六、继承与静态成员

七、多继承及其菱形继承问题

7.1.继承模型

八、虚继承

8.1.多继承的指针偏移问题

8.2.IO库中的菱形虚拟继承

九、继承和组合

9.1.继承关系

9.2.组合关系


一、封装

类的设计就是一种简单的封装

将数据与方法放在类中,直接访问的定义成公有,不想直接访问的定义为私有或保护

迭代器的设计也是一种简单的封装

vector,string,list,deque的迭代器在使用层面类似

屏蔽了底层的设计细节:原生指针,用类封装的节点指针或者更为复杂的结构...

二、继承的概念及定义

2.1.继承的概念

继承(inheritance): 面向对象程序设计使代码可以复用的最重要的手段

(复用:类似模板,将重复的程序交给编译器处理)

允许在保持原有类特性的基础上进行扩展

增加方法(成员函数)和属性(成员变量)

这样产生新的类称为子类

示例:

对于学生类老师类,有很多的方法与属性是一样的

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

class Student
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证
	void identity()
	{
		// ...
	}

	// 学习
	void study()
	{
		// ...
	}
protected:
	string _name = "peter"; // 姓名
	string _address; // 地址
	string _tel; // 电话
	int _age = 18; // 年龄
	int _stuid; // 学号
};

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

int main()
{
	return 0;
}

可以将这两个类中公共的成员放到个人类

学生类与老师类都继承个人类,就可以复用这些成员,不用重复定义

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

class Person
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证
	void identity()
	{
		cout << "void identity()" << _name << endl;
	}
protected:
	string _name = "张三"; // 姓名
	string _address; // 地址
	string _tel; // 电话
	int _age = 18; // 年龄
};


class Student : public Person
{
public:
	// 学习
	void study()
	{
		// ...
	}
	
protected:
	int _stuid; // 学号
};

class Teacher : public Person
{
public:
	// 授课
	void teaching()
	{
		//...
	}
protected:
	string title; // 职称
};

int main()
{
	Student s;
	Teacher t;
	
	s.identity();
	t.identity();
	
	return 0;
}

2.2.继承的定义

定义格式:

个人类是父类,也称作基类

学生类是子类,也称作派生类

继承方式:

  • 公有继承(public)
  • 保护继承(protected)
  • 私有继承(private)

访问限定符:

  • 公有访问(public)
  • 保护访问(protected)
  • 私有访问(private)

父类private成员在子类中不可见

不可见:父类私有成员继承到子类,但语法上限制子类的对象不管在类里还是类外都无法访问

父类protect成员在子类中可以被访问,但在类外无法被访问

(前提:子类不是private继承)

父类public成员由子类的继承方式来决定访问权限

|-----------|------------|---------------|-------------|
| 父类成员访问限定符 | 子类public继承 | 子类protected继承 | 子类private继承 |
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可见 | 不可见 | 不可见 |

**注:**可以通过公有的成员函数间接访问私有或者保护的对象

如果不显示写继承方式

  • class默认:私有继承
  • struct默认:公有继承

2.3.类模板继承类模板

在类模板继承另一个类模板时

如果要在子类模板中使用父类模板的成员

必须要显示告诉编译器该名称来自于父类

(通过类域限定符Base<T>::,this->,using声明 )

本质:

父类模板是依赖型类型

编译器在模板定义阶段不会查找其成员

只有显示标记,才能触发父类的按需实例化

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

//#define CONTAINER std::vector
//#define CONTAINER std::list
#define CONTAINER std::deque

namespace bit
{
	//template<class T>
	//class vector
	//{};

	// stack和vector的关系,既符合is-a,也符合has-a
	template <class T>
	class stack : public CONTAINER<T>
	{
	public:
		void push(const T & x)
		{
			// 基类是类模板时,需要指定一下类域,
			// 否则编译报错:error C3861: "push_back": 找不到标识符
			// 因为stack<int>实例化时,也实例化vector<int>了
			// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到
			CONTAINER<T>::push_back(x);
			//push_back(x);
		}
		void pop()
		{
			CONTAINER<T>::pop_back();
		}
		const T& top()
		{
			return CONTAINER<T>::back();
		}
		bool empty()
		{
			return CONTAINER<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;
}

三、赋值兼容转换

3.1.切片(切割)

公有继承的子类对象可以赋值给父类的对象/指针/引用

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

class Person
{
protected:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};

int main()
{
	Student sobj;
	//子类对象可以赋值给父类的指针/引用
	Person* pp = &sobj;
	Person& rp = sobj;
	//子类对象可以赋值给父类的对象是通过调用基类的拷贝构造完成的
	Person pobj = sobj;
	//父类对象不能赋值给子类对象,会编译报错
	sobj = pobj;
	return 0;
}

注:

父类对象不可以给子类对象

父类的指针/引用可以通过强制类型转换赋值给子类的指针/引用

**继承中的作用域:**在继承体系中,父类与子类都有独立的作用域

3.2.隐藏

子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问

(在子类成员函数中,可以用 父类::父类成员 来显示访问)

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

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
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;
};

**注:**如果是成员函数的隐藏,只要是函数名相同就构成隐藏,不会有函数重载

3.3.试题

A和B类中两个func构成什么关系(B)

A.重载

B.隐藏

**注:**函数重载要求在同一作用域

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

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();
	//子类对象指定父类的作用域就可以调用父类中的成员函数
	b.A::fun();
	return 0;
};

四、子类的默认成员函数

4.1.构造函数

子类的构造函数必须调用父类的构造函数,先初始化父类部分的成员,再初始化子类部分的成员

**注:**如果父类没有默认的构造函数,必须在子类的构造函数的初始化列表阶段显示调用

默认构造函数行为

**内置类型:**不确定(大概率为随机值)

**自定义类型:**调用自己的默认构造函数

继承父类成员看作一个整体对象,要求调用父类的默认构造函数

可以给缺省值进行初始化

如果父类没有默认构造函数,则会发生报错

子类不允许直接初始化父类的成员,必须调用父类的初始化函数

除非在子类默认构造函数的初始化列表中显示调用父类

4.2.拷贝构造函数

拷贝构造函数行为

**内置类型:**值拷贝(浅拷贝)

**自定义类型:**调用自己的拷贝构造函数(深拷贝),没有就为浅拷贝

**继承的父类成员:**调用父类的拷贝构造

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

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;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	Student(const char* name, int num, const char* addrss)
		:Person(name)
		, _num(num)
		, _addrss(addrss)
	{

	}
protected:
	int _num = 1; //学号
	string _addrss = "江西省南昌市";
};

int main()
{
	Student s("张三",111,"江西省");
	Student s1(s);
	return 0;
}

学生类的拷贝构造使用默认生成的就够用了

  • 内置类型_num可以直接浅拷贝
  • 自定义类型string可以调用自己的拷贝构造函数进行深拷贝
  • 继承父类的成员可以调用父类中的拷贝构造函数

只有学生类自身在有需要深拷贝的资源时,才需要自己实现拷贝构造函数

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

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;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	Student(const char* name, int num, const char* addrss)
		:Person(name)
		, _num(num)
		, _addrss(addrss)
	{
	}

    //如果显式写拷贝构造函数
	//传子类对象给父类时会发生赋值兼容转换,赋值给父类的引用,进行拷贝构造
	Student(const Student & s)
		: Person(s)
		, _num(s._num)
		,_addrss(s._addrss)
	{
		cout << "Student(const Student& s)" << endl;
	}
protected:
	int _num = 1; //学号
	string _addrss = "江西省南昌市";
};

int main()
{
	Student s("张三",111,"江西省");
	Student s1(s);
	return 0;
}

4.3.赋值运算符重载

与拷贝构造函数一样,不用显式写也可以直接调用

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

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;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	Student(const char* name, int num, const char* addrss)
		:Person(name)
		, _num(num)
		, _addrss(addrss)
	{
	}

	Student(const Student& s)
		: Person(s)
		, _num(s._num)
		, _addrss(s._addrss)
	{
		cout << "Student(const Student& s)" << endl;
	}

    //如果显示写赋值运算符重载函数
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			//子类与父类成员同名时构成隐藏,需要显式调用
			Person::operator=(s);
			_num = s._num;
			_addrss = s._addrss;
		}
		return *this;
	}
protected:
	int _num = 1; //学号
	string _addrss = "江西省南昌市";
};

int main()
{
	Student s("张三",111,"江西省");
	Student s1(s);
	Student s2("李四", 222, "江苏省");
	s1 = s2;
	return 0;
}

4.4.析构函数

与拷贝构造函数一样,不用显式写也可以直接调用

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

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(const char* name, int num, const char* addrss)
		:Person(name)
		, _num(num)
		, _addrss(addrss)
	{
	}

	Student(const Student& s)
		: Person(s)
		, _num(s._num)
		, _addrss(s._addrss)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
			_addrss = s._addrss;
		}
		return *this;
	}
	//析构函数都会被特殊处理成destructor()
	~Student()
	{
		//子类的析构与父类的析构也构成隐藏关系
		//Person::~Person();
        //不需要显示调用,子类析构函数
        //编译器会在子类调用完析构函数后
        //自动调用父类的析构函数,保证先子后父
	}
protected:
	int _num = 1; //学号
	string _addrss = "江西省南昌市";
};

int main()
{
	Student s("张三",111,"江西省");
	Student s1(s);
	Student s2("李四", 222, "江苏省");
	s1 = s2;
	return 0;
}

注:

在所有的类中,析构函数都会被特殊处理成destructor()

这会导致子类与父类发生隐藏

  • 子类对象初始化先调用父类构造再调用子类构造(先父后子)
  • 子类对象析构清理先调用子类析构再调用父类析构(先子后父)

但如果显示调用,则会造成先父后子,不能保证先子后父

实现一个不能被继承的类

**法1:**父类的构造函数私有

子类的构成必须调用父类的构造函数

但是父类的构成函数私有化以后

子类不可见,无法完成实例化对象

**法2:**C++11新增final关键字,定义为最终类

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

class Base final
{
public:
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
	
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;
}

五、继承与友元

友元关系不能被继承

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

//前置声明
class Student;

class Person
{
public:
	//Person中的Display为友元函数
	//在类外定义时,可以使用类中protected中的成员变量
	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;
	//Display函数是Person的友元
	//但友元无法被继承
	//所以在Student中无法获取protected中的成员变量
	cout << s._stuNum << endl;
}

int main()
{
	Person p;
	Student s;
	// 编译报错:error C2248: "Student::_stuNum": 无法访问 protected 成员
    // 解决方案:Display也变成Student 的友元即可
	Display(p, s);

	return 0;
}

六、继承与静态成员

父类定义了static静态成员

整个继承体系里面只有一个这样的成员

无论派生多少子类,都只有一个static成员实例

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

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;

    //还可以用对象进行指定访问
    //cout << p._count << endl;
    //cout << s._count << endl;
	
	return 0;
}

七、多继承及其菱形继承问题

7.1.继承模型

**单继承:**一个子类只有一个直接父类

**多继承:**一个子类有两个或两个以上的直接父类

实现一个蔬菜类和水果类

番茄既属于蔬菜也属于水果

**菱形继承:**数据冗余与二义性

Assistant对象内会包含两份独立的Person子对象

一份来自Student继承链,一份来自Teacher继承链

二义性:

当访问Assistant对象中的_name时

编译器不知道要访问的是Student继承的_name,还是Teacher继承的_name

数据冗余:

Assistant里有两个Person子对象

在内存中会存放两个_name,一个在Student,一个在Teacher

这两个_name的空间是独立的,所以一个Assistant对象会有两中类的信息

但实际想要的是一个Assistant对象中

只能存放一个学生的信息,或者一个老师的信息

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#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()
{
	Assistant a;
	// 编译报错:error C2385: 对"_name"的访问不明确
	//a._name = "peter";
	
	//需要显示指定访问哪个基类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
	cout << sizeof(a) << endl;
	return 0;
}

**补充:**IO流就是一个经典的菱形继承

八、虚继承

**虚继承:**子类共享一个父类中的成员变量(内存空间相同)

**非虚继承:**子类各自都有一个父类中的成员变量(内存空间各自独立)

哪个类(公共父类)会产生数据冗余与二义性,就在继承时给父类加上virtual

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

class Person
{
public:
	string _name; // 姓名
	/*int _tel;
	int _age;
	string _gender;
	string _address;*/
	// ...
};

//虚继承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";
	//指定访问
	a.Student::_name = "小李";
	a.Teacher::_name = "老李";
    //此时a对象中的名字为老李
    cout << a._name << endl;
    cout << a.Student::_name << endl;
    cout << a.Teacher::_name << endl;
	return 0;
}

最好不要设计菱形继承

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

class Person
{
public:
	Person(const char* name)
		:_name(name)
	{}
	string _name; // 姓名
	/*int _tel;
	int _age;
	string _gender;
	string _address;*/
	// ...
};

//虚继承Person类
class Student : virtual public Person
{
public:
	Student(const char* name,int num = 0)
		:Person(name)
		,_num(num)
	{}
protected:
	int _num; //学号
};

//虚继承Person类
class Teacher : virtual public Person
{
public:
	Teacher(const char* name, int id = 1)
		:Person(name)
		, _id(id)
	{}
protected:
	int _id; // 职工编号
};

//教授助理
class Assistant : public Student, public Teacher
{
public:
	Assistant(const char* name1,const char* name2,const char* name3)
		:Student(name1)
		,Teacher(name2)
		,Person(name3)
	{}
protected:
	string _majorCourse; //主修课程
};

int main()
{
	//初始化name3,name为王五,name1和name2都没有作用
	Assistant a("张三","李四","王五");
	return 0;
}

8.1.多继承的指针偏移问题

1.下面说法正确的是(C)

A.p1 == p2 == p3

B.p1 < p2 < p3

C.p1 == p3 != p2

D.p1 != p2 != p3

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

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;
}

2.下面说法正确的是(E)

A.p1 == p2 == p3

B.p1 < p2 < p3

C.p1 == p3 != p2

D.p1 != p2 != p3

E.p2 == p3 != p1

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

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

8.2.IO库中的菱形虚拟继承

九、继承和组合

9.1.继承关系

public继承 是一种is-a的关系

每个子类的对象都一个父类的对象

继承是一种白箱复用

在继承中,父类的成员一般定义为保护与公有

派生的类都可以直接使用,一定程度地破坏了父类的封装

父类的改变对子类有很大影响,父子间的依赖关系强,耦合度高

9.2.组合关系

组合 是一种has-a的关系

B组合了A,每个B对象中都一个A对象

组合是一种黑箱复用

组合的类,它的对象内部细节是不可见的

比如栈组合了链表,链表的结构是访问不到的

只能调用它公有的成员函数

所以组合类之间依赖关系弱,耦合度低

优先使用组合,而不是继承

组合的耦合度低,代码维护性好

但不绝对:

类之间的关系就时候继承

多态的实现也必须用继承

类之间的关系既适合继承(is-a)也适合组合(has-a)时,就用组合

示例:

  • 学生和人:继承
  • BMW和Benz与车:继承
  • 轮胎和车:组合
  • stack和vector:既符合继承,也符合组合(优先组合)
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;

// Tire(轮胎)和Car(⻋)更符合has-a的关系
class Tire {
protected:
	string _brand = "Michelin"; // 品牌
	size_t _size = 17; // 尺⼨
};
class Car {
protected:
	string _colour = "⽩⾊"; // 颜⾊
	string _num = "陕ABIT00"; // ⻋牌号
	Tire _t1; // 轮胎
	Tire _t2; // 轮胎
	Tire _t3; // 轮胎
	Tire _t4; // 轮胎
};
class BMW : public Car {
public:
	void Drive() { cout << "好开-操控" << endl; }
};

// Car和BMW/Benz更符合is-a的关系
class Benz : public Car {
public:
	void Drive() { cout << "好坐-舒适" << endl; }
};

template < class T>
class vector
{};

// stack和vector的关系,既符合is-a,也符合has-a
template < class T>
class stack : public vector < T>
{};

template < class T>
class stack
{
public:
	vector<T> _v;
};

int main()
{
	return 0;
}

补充:

**黑盒测试:**不了解底层实现,从功能角度测试

**白盒测试:**了解底层实现,从代码运行逻辑角度测试

**覆盖度:**测试的指标

假设有if else if else三段代码,至少设计三个测试用例来覆盖三个分支

**UT(单元测试):**对软件中最小可测试单元进行验证,确保单个模块正确

**CI(自动化测试系统):**检查测试用例的覆盖度

**低耦合:**模块与模块的关系越弱越好

(类开放的成员越少越好)

**高内聚:**一个模块内的关系越紧密越好,确保模块只做一件事

相关推荐
csbysj20201 小时前
TypeScript String
开发语言
田梓燊1 小时前
最长的连续序列到底怎么写
算法·哈希算法·散列表
Xudde.1 小时前
班级作业笔记报告0x02
笔记
小温冲冲1 小时前
QML vs Qt Widgets:深度对比与选型实战指南
开发语言·c++·qt
Hello_Embed1 小时前
LVGL 入门(一):环境搭建与源码获取
笔记·stm32·单片机·嵌入式·lvgl
smchaopiao2 小时前
C++20概念(Concepts)入门指南
开发语言·c++·算法
一叶落4382 小时前
LeetCode 21. 合并两个有序链表(C语言详解 | 链表经典题)
c语言·数据结构·c++·算法·leetcode·链表
挠头猴子2 小时前
c++中常用的运算符优先级
java·开发语言·c++
我是唐青枫2 小时前
C#.NET Memory 深入解析:跨异步边界的内存视图与高性能实战
开发语言·c#·.net