当前位置: 首页 > news >正文

【C++】继承

目录

1. 继承的概念及定义

1.1 继承的概念

1.2 继承定义

1.2.1 定义格式

1.2.2 继承基类成员访问方式的变化

1.3 继承类模板

2. 基类和派生类间的转换

3. 继承中的作用域

3.1 隐藏规则:

3.2 考察继承作用域相关选择题

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

3.2.2 下面程序的编译运行结果是什么()

4. 派生类的默认成员函数

4.1 4个常见默认成员函数

4.2 实现一个不能被继承的类

5. 继承与友元

6. 继承与静态成员

7. 多继承及其菱形继承问题

7.1 继承模型

7.2 虚继承

7.3 多继承中指针偏移问题:下面说法正确的是( ) C

7.4 IO库中的菱形虚拟继承

8. 继承和组合

8.1 继承和组合


1. 继承的概念及定义

1.1 继承的概念

在社会关系中,一个人往往会拥有不同的身份,基于不同身份,有用不同的信息,比如当学生有学号,成为工人有工号等,可能会有不同的外号,但是基于人的最根本的身份,他们都要自己唯一的名称、性别等属性。

面向对象的程序设计中,如果我们想要模拟实现这种一个人的不同身份,并存储对应信息,难道实现“人”、“学生”、“工人”这些模板时,我们每个模板中都有名称、年龄、性别这些信息吗?

仔细思考我们发现像名称、年龄、性别这些是基于人这个最根本的身份出发的,学生、工人等都是基于这个基础上产生的,那么对于这些通用信息,我们能否使用就人这个类的数据呢?对于学号、工人这些个性化的信息,我们就在学生、工人这些类具体存储呢?

C++中继承就提供了这种类层次的代码复用。

继承(inheritance)机制是面向对象程序设计中使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类,被继承的类叫做基类(一般根据两者的关系,父类与子类的称呼使用更多)。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

以前我们接触的多是函数层次的代码复用,继承是类设计层次的复用。在这个过程中原先父类的相关数据特性就像生物学上的继承一样被子类继承,同时子类又有它本身的特性。

下面我们看到没有继承之前我们设计了两个类Student和Teacher,Student和Teacher都有姓名/地址/电话/年龄等成员变量,都有identity身份认证的成员函数,设计到两个类里面就是冗余的。当然他们也有一些不同的成员变量和函数,比如老师独有成员变量是职称,学生的独有成员变量是学号;学生的独有成员函数是学习,老师的独有成员函数是授课。

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

下面我们公共的成员都放到Person类中,Student和teacher都继承Person,就可以复用这些成员,就不需要重复定义了,省去了很多麻烦。

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

1.2 继承定义

1.2.1 定义格式

下面我们看到Person是基类,也称作父类。Student是派生类,也称作子类。(因为翻译的原因,所以既叫基类/派生类,也叫父类/子类)

子类的类名称后面加上 :继承方式 基类名称,继承方式的符号与访问限定符一致,分为公有、保护、私有。

1.2.2 继承基类成员访问方式的变化

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它


2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的


3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在派生类都是不可见。基类的其他成员在派生类的访问方式 == Min(成员在基类的访问限定符,继承方式)权限大小上public > protected >private。


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

5. 由于C++出现时间早,没有其他参考,因此设计者一开始考虑了所有的可能情况,因此出现了上表中的9种情况,但是在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化
class Person
{
public:void Print(){cout << _name << endl;}
protected:string _name; // 姓名
private:int _age; // 年龄
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected:int _stunum; // 学号
};

1.3 继承类模板

namespace zlr
{//template<class T>//class vector//{};// stack和vector的关系,既符合is-a,也符合has-atemplate<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;
}

类模板这里需要说明的是即使语法层面上子类继承到父类的对象,但是在编译查找时还是会到父类中找对应的函数,但是类模板的按需实例化机制让未调用的函数不会实例化,那么这里子类查找时就会出现找不到的情况,我们直接以push_back()、pop_back()形式调用对应程序报错,因此我们要这里要指明对应类,显式调用函数接口,编译器才会将对应的父类的函数实例化,程序才不会报错。

对于构造与析构函数等特殊的函数,编译器会做特殊处理,自动调用(下文会说明),因此即使我们不显示调用,也实例化,不会报错

2. 基类和派生类间的转换

public继承的派生类对象 可以赋值给 基类的指针 / 基类的引用(protected\private继承类外无法访问,因此没有这个概念)。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分


基类对象不能赋值给派生类对象。(对于派生类增加的成员,基类对象中也没有值可以赋)

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time TypeInformation)的dynamic_cast 来进行识别后进行安全转换。(ps:这个后面笔者多态类型转换再单独专门介绍,这里先提一下)

class Person
{
public:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};class Student : public Person
{
public:int _No; // 学号
};int main()
{Student sobj;// 派生类对象可以赋值给基类的对象是通过调用后面会讲解的基类的拷贝构造完成的Person pobj = sobj;// 1.子类对象可以赋值给父类对象/指针/引用,不属于类型转换Person* pp = &sobj;Person& rp = sobj;rp._name = "张三";//int i = 1;//double d = i;//const double& rd = i;//类型转换会产生临时对象//2.父类对象不能赋值给子类对象,这里会编译报错//sobj = (Student)pobj;// 这里简单了解一下Student* ps1 = dynamic_cast<Student*>(pp);//cout << ps1 << endl;pp = &pobj;Student* ps2 = dynamic_cast<Student*>(pp);cout << ps2 << endl;return 0;
}

注:之所以基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用,其实也是因为之前提到派生类可以赋值给基类,所以基类指针或者引用也可能指向一个派生类,这种指向派生类的基类的指针或者引用就可以赋值给派生类,指向基类的指针或者引用不可以赋值。

此外,在赋值过程当中虽然基类与派生类的类型不同,但是这里并没有发生类型转换产生临时对象,可以理解为编译器对切片这一操作特殊处理了。

3. 继承中的作用域

3.1 隐藏规则:

1. 在继承体系中基类和派生类都有独立的作用域


2. 派生类和基类中有同名成员派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在派生类成员函数中,可以使用 基类::基类成员 显示访问


3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。


4. 注意在实际中在继承体系里面最好不要定义同名的成员

// 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.2 考察继承作用域相关选择题

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

A. 重载 B. 隐藏 C.没关系

3.2.2 下面程序的编译运行结果是什么()

A. 编译报错 B. 运行报错 C. 正常运行

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();要调用A中的函数就需要显式调用return 0;
};

首先,需要明确的是函数重载要求的是在同一域内的同名函数,父类与子类有独立的域,因此A与B中的同名函数不构成重载,而是B的fun对A的fun构成隐藏。

因此,我们以B.fun()形式不管穿不传参都是调用B中的fun,而B中的fun是要传参的,如果我们不传参,程序就会报错。

如果我们想要调用A中的fun,我们必须显式调用。

4. 派生类的默认成员函数

4.1 4个常见默认成员函数

类的6个默认成员函数,默认的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员(在之前类文章中,我们了解到编译器默认生成的函数,对于内置类型的行为是不确定的,对于自定义类型是调用它的默认函数。而对于继承的父类成员可以视作一个整体对象,对于这个整体对象要求调用父类的默认函数,编译器会自动调用。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。


2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。由于拷贝构造是一种特殊的构造,一但构造函数显示写了,拷贝构造我们也必须显示写。

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


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


5. 派生类对象初始化先调用基类构造再调派生类构造


6. 派生类对象析构清理先调用派生类析构再调基类的析构


7. 因为多态中一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个笔者多态章节会介绍)。那么编译器会对析构函数名进行特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派生类析构函数和基类析构函数构成隐藏关系

注:一般情况下,如果我们的程序不涉及深拷贝,如果父类有对应的默认函数,那么我们仅使用编译器默认生成的就可以了,相关继承的父类成员,编译器会自动调用父类成员的默认函数进行处理。

对于子类与父类都是模板的情况,需要注意的是像构造、析构等函数,编译器默认生成时,在汇编层面实际上会自动调用,所以相关的父类函数会实例化,因此即使我们没有显式调用也不会报错。

继承自父类的成员在子类内部实际上声明在子类成员前面,因此先声明的成员在初始化列表先初始化,所以在初始化列表,父类成员作为整体先初始化。而在析构环节,为了保持栈上数据后进先出的特性,子类是先析构,父类后析构。

class Person
{
public:Person(const char* name = "xxx"): _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拷贝构造默认生成的就够用了// 如果有需要深拷贝的资源,才需要自己实现Student(const Student& s):Person(s)//显式调用时,这里需要传一个父类的对象,我们上文提过子类对象可以传给父类的//引用(切片),因此,这里我们直接传子类引用就可以, _num(s._num), _addrss(s._addrss){// 深拷贝}// 严格说Student赋值重载默认生成的就够用了// 如果有需要深拷贝的资源,才需要自己实现Student& operator=(const Student& s){if (this != &s){// 父类和子类的operator=构成隐藏关系Person::operator=(s);_num = s._num;_addrss = s._addrss;}return *this;}// 严格说Student析构默认生成的就够用了// 如果有需要显示释放的资源,才需要自己实现// 析构函数都会被特殊处理成destructor() ~Student(){// 子类的析构和父类析构函数也构成隐藏关系// 规定:不需要显示调用,子类析构函数之后,会自动调用父类析构// 这样保证析构顺序,先子后父,显示调用取决于实现的人,不能保证// 先子后父//Person::~Person();//delete _ptr;}
protected:int _num = 1; //学号string _addrss = "西安市高新区";int* _ptr = new int[10];
};int main()
{Student s1("张三", 1, "西安市");Student s2(s1);Student s3("李四", 2, "咸阳市");s1 = s3;/*Person* ptr = new Person;delete ptr;*/return 0;
}

注:对于析构函数来说,无论父类析构函数是否显示的调用,编译器都会自动调用,因此,在上段代码中,我们写析构函数不能显示调用父类析构,否则容易造成对已释放空间析构,造成崩溃。

4.2 实现一个不能被继承的类

方法1:基类的构造函数私有,派生类的构成必须调用基类的构造函数,但是基类的构成函数私有化以后,派生类看不见就不能调用了,那么派生类就无法实例化出对象。
方法2:C++11新增了一个final关键字,final修改基类,派生类就不能继承了

// C++11的方法
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;
}

5. 继承与友元

友元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员 。

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

6. 继承与静态成员

基类定义了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;
}

7. 多继承及其菱形继承问题

7.1 继承模型

单继承:一个派生类只有一个直接基类时称这个继承关系为单继承


多继承:一个派生类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前面,后面继承的基类在后面,派生类成员在放到最后面


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

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

7.2 虚继承

很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有一些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之一,后来的一些编程语言都没有多继承,如Java。

为了处理数据冗余,C++设计了一个关键词virtual来解决,Assisant的冗余数据主要是Person有两份,一份来自Student,一份来自Teacher,那们我们就在Student与Teacher继承的Person类名称前加上virtual,那么Assisant在继承Student与Teacher的Person数据时,就相当于两份数据合并成一份,之后这份数据单独存放在Assisant中,不在存放在Student与Teacher内。

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";return 0;
}

我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,无论是使用还是底层都会复杂很多。当然有多继承语法支持,就一定存在会设计出菱形继承,像Java是不支持多继承的,就避开了菱形继承。

class Person
{
public:Person(const char* name):_name(name){}string _name; // 姓名
};
class Student : virtual public Person
{
public:Student(const char* name, int num):Person(name), _num(num){}
protected:int _num; //学号
};
class Teacher : virtual public Person
{
public:Teacher(const char* name, int id):Person(name), _id(id){}
protected:int _id; // 职工编号
};
// 不要去玩菱形继承
class Assistant : public Student, public Teacher
{
public:Assistant(const char* name1, const char* name2, const char* name3):Person(name3), Student(name1, 1), Teacher(name2, 2){}
protected:string _majorCourse; // 主修课程
};
int main()
{// 思考一下这里a对象中_name是"张三", "李四", "王五"中的哪一个?//编译器特殊处理,使用virtual这里Student与Teacher的构造函数都不会再//调用Person的构造,所以只有Person(name3)起作用,结果是王五Assistant a("张三", "李四", "王五");return 0;
}

对于virtual的使用,以上图中第一种情况为例,E中的冗余数据是来自B与C继承的A的部分,因此我们需要对B、C使用virtual(D中的A部分是严格上属于B的,继承的B的部分)

以第二中情况为例,当我们不涉及菱形继承是时,我们就不要使用virtual,避免额外的开销(当然能不用菱形继承就不要使用菱形继承)

7.3 多继承中指针偏移问题:下面说法正确的是( ) C

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != 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;}

这里一题设计就是前文所提到子类赋值给父类指针或引用的切片问题,这里赋值给p1时将对应的Derive中Base1部分切片给p1,赋值给p2时就将对应的Derive中Base2部分切片给p2,因此,这里就发生了指针偏移的现象,p1指向Base1部分开始的地方,p2指向Base2部分开始的地方。p3就是指向Derive开始的地方,因此p1==p3!=p2,选C

7.4 IO库中的菱形虚拟继承

template<class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<CharT, Traits>
{};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits>
{};

8. 继承和组合

8.1 继承和组合

• public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象


• 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。(组合就是新类的接口是通过封装已实现类的接口实现的)

在日常中,我们会说大象是动物,大象这个概念属于动物这个概念,这个就是is-a,而我们不会说大象是动物组成的。继承就是子类的特性是继承父类的特性,并在其上拓展的,有所属关系,这个就是is-a.

而我们会说汽车由4个轮胎组成,我们不会说汽车是轮胎,这种概念本身没有所属关系,但是在具体事务上构成的关系我们叫做组合。如之前实现stack我们是在vector接口基础上封装出来的,stack是有vector构成的,我们把这就叫做组合。

继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对派生类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。


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


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

// 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>//stack是通过封装已实现的vector实现的,这就是组合
class stack : public vector<T>
{};
template<class T>
class stack
{
public:vector<T> _v;
};
int main()
{return 0;
}

相关文章:

【C++】继承

目录 1. 继承的概念及定义 1.1 继承的概念 1.2 继承定义 1.2.1 定义格式 1.2.2 继承基类成员访问方式的变化 1.3 继承类模板 2. 基类和派生类间的转换 3. 继承中的作用域 3.1 隐藏规则&#xff1a; 3.2 考察继承作用域相关选择题 3.2.1 A和B类中的两个func构成什么关…...

设计模式详解(十):策略模式——Strategy

什么是Strategy设计模式&#xff1f; Strategy模式是一种行为型设计模式&#xff0c;它定义了一系列算法&#xff0c;将每种算法封装到独立的类中&#xff0c;使它们可以互换。使用该模式&#xff0c;可以在不修改客户端代码的情况下动态地改变算法的行为。 为什么需要Strate…...

B4X编程语言:B4X控件方法汇总

1、AddNode、AddView方法 AddNode(Node As javafx.scence.Node,Left As Double,Top As Double,Width As Double,Height As Double) B4J控件 AddView(View As javafx.scence.Node,Left As Double,Top As Double,Width As Double,Height As Double) B4J的B4XView …...

如何阅读一本书

阅读很重要,我们真的会阅读吗? 这本书的初版是 1940年,时隔 80年,其内容仍然不过时。第一次读这本书时,给我最大的影响就是主题阅读,每次学习一个新理论、技术,都入手多本关于这项理论、技术的书籍,不同的作者,不同作者的写作背景、角度、目的,导致了风格不同的书籍,…...

vscode 排除文件夹搜索

排除的文件夹 node_modules/,dist/...

1. Flink自定义Source

一. Source 简介 DataStream是Flink的低级API&#xff0c;用于进行数据的实时处理&#xff0c;Flink编程模型分为Source、Transformation、Sink三个部分&#xff0c;如下图所示。 默认Flink提供了大量的内置Source&#xff0c;常见的Source如下&#xff1a; 基于文件的Sour…...

短信验证码burp姿势

首先声明&#xff0c;本文仅仅作为学习使用&#xff0c;因个人原因导致的后果&#xff0c;皆有个人承担&#xff0c;本人没有任何责任。 在之前的burp学习中&#xff0c;我们学习了图片验证码的突破&#xff0c;但是现实中还有很多短信验证码&#xff0c;在此我介绍几种短信验…...

scala的正则表达式3

贪婪模式与非贪婪模式 1.贪婪模式 贪婪模式是正则表达式的默认行为。在这种模式下&#xff0c;量词&#xff08;如*、、?、{n,m}&#xff09;会尝试匹配尽可能多的字符。例如&#xff0c;正则表达式".*"会匹配任意数量的任意字符&#xff0c;包括空字符。 2非贪婪…...

Kotlin设计模式之中介者模式

中介者模式&#xff08;Mediator Pattern&#xff09;用于定义一个对象&#xff0c;该对象封装了一组对象之间的交互方式。中介者使各对象不需要显式地相互引用&#xff0c;从而使其耦合松散&#xff0c;并可以独立地改变它们之间的交互。以下是Kotlin中实现中介者模式的方法&a…...

Android 车载虚拟化底层技术-Kernel 5.10 -Android12(multi-cards)技术实现

详细代码实现见 Android Display Graphics系列文章-汇总​​​​​​Android Display Graphics系列文章-汇总 Android Display Graphics系列文章-汇总 Android Display Graphics系列文章-汇总 本文主要包括部分&#xff1a; 一、Android12的Kernel 5.10版本 1.1 Kernel 5…...

ZZNUOJ_1726:算法提高 选择排序(C/C++/Java)

题目描述 排序,顾名思义,是将若干个元素按其大小关系排出一个顺序。形式化描述如下:有n个元素a[1],a[2],…,a[n],从小到大排序就是将它们排成一个新顺序a[i[1]]<a[i[2]]<…<a[i[n]]   i[k]为这个新顺序。   选择排序的思想极其简单,每一步都把一个最小…...

Python+OpenCV系列:模版匹配

文章目录 1. 模板匹配基本原理2. cv2.matchTemplate() 函数函数原型&#xff1a; 3. 模板匹配步骤4. 单目标模板匹配示例5. 多目标模板匹配多目标模板匹配示例代码解析&#xff1a; 6. 多模板匹配多模板匹配示例代码解析 7. 总结 模板匹配是一种在图像中寻找模板的位置的方法。…...

AWS云计算问答式知识库系统的实现

AWS平台上建立一个生成式人工智能专业文文档问答式网站&#xff0c;前端用Flask框架&#xff0c;后端用Fast API&#xff0c;前端调用后端的唯一API来完成大模型文档搜索的功能&#xff0c;使用AWS Bedrock里的Titan和Llama3模型&#xff0c;以及OpenAI 4.0 API来调用模型并提供…...

【C语言】库函数常见的陷阱与缺陷(4):内存内容操作函数

目录 一、memcmp函数 1.1. 功能与用法 1.2. 陷阱与缺陷 1.2.1. 比较范围限制问题 1.2.2. 数据类型兼容性隐患 1.2.3. 其它 1.3. 安全使用建议 1.4. 代码示例 二、memcpy函数 2.1. 功能与用法 2.2. 陷阱与缺陷 2.2.1. 缓冲区溢出风险 2.2.2. 重叠内存区域处理隐患 …...

Java 类与对象的详细讲解

引言 在Java中&#xff0c;类&#xff08;Class&#xff09;和对象&#xff08;Object&#xff09;是面向对象编程&#xff08;OOP&#xff09;的核心组成部分。通过它们&#xff0c;我们可以创建复杂的数据结构&#xff0c;并实现代码的重用性和可维护性。本文将深入探讨Java…...

Python爬虫之使用BeautifulSoup进行HTML Document文档的解析

BeautifulSoup 是一个用于解析 HTML 和 XML 文档的 Python 库&#xff0c;它为开发者提供了一种简单的方式来查找、遍历和修改文档树。BeautifulSoup 特别擅长处理不规则或格式不佳的标记语言&#xff0c;可以自动更正无效的 HTML&#xff0c;因此在网页抓取&#xff08;Web Sc…...

vue2 纯前端实现像chatGpt一样的文字流式输出

1、先展示一下效果啦 文字流输出 2、首先确定接口返回的是字符串&#xff0c;那就先调取接口 // 由于需求关系 有很多个文本需要一起逐字输出 所以这里就采用了Promise.all的形式 async getThreeTextarea() {let taskArray [ "重点项目文本","应用领域文本&qu…...

基于PHP课堂签到系统的设计与实现

摘 要 随着教育业的迅速发展和学生人数的不断增加&#xff0c;导致在班级登记制度中传统的“点到”方式不能适应学校的实际需要。从而需要设计一个好的课堂签到系统将会对课堂签到管理工作带来事半功倍的效果。文章着重介绍了基于实践应用的班级签到系统的开发流程&#xff0c…...

LLMs之APE:基于Claude的Prompt Improver的简介、使用方法、案例应用之详细攻略

LLMs之APE&#xff1a;基于Claude的Prompt Improver的简介、使用方法、案例应用之详细攻略 目录 Prompt Improver的简介 0、背景痛点 1、优势 2、实现思路 Prompt优化 示例管理 提示词评估 Prompt Improver的使用方法 1、使用方法 Prompt Improver的案例应用 1、Kap…...

51c深度学习~合集9

我自己的原文哦~ https://blog.51cto.com/whaosoft/12750420 #傅里叶特征 (Fourier Feature&#xff09;与核回归 位置编码背后的理论解释 本文探讨了位置编码背后的理论基础&#xff0c;特别是傅里叶特征&#xff08;Fourier Feature&#xff09;与核回归&#xff08;Kern…...

前端流式播放TTS语音:技术细节与实现

摘要 本文将介绍如何在前端实现流式播放文本到语音&#xff08;TTS&#xff09;的语音&#xff0c;并加入确保语音播放不重叠的改进方案。我们将探讨使用Web Audio API和WebSocket进行实时语音播放的技术细节&#xff0c;并提供相应的代码示例。 一、引言 在之前的讨论中&am…...

数据结构面试题整理

1. 简述什么是数据结构&#xff1f; 数据结构就是一种组织和存储数据的方式&#xff0c;使得我们可以高效地访问和修改数据。就像你整理房间一样&#xff0c;不同的物品有不同的收纳方式&#xff0c;数据也有不同的存储和管理方法。 比如&#xff0c;数组就像一个排成一排的抽…...

前端实现浏览器自定义滚动条

前言&#xff1a; 最近有个项目&#xff0c;产品觉得浏览器默认滚动条太丑了。想美化一下&#xff0c;比如自定义颜色&#xff0c;加上圆角&#xff0c;宽高都要更改一下。我查了资料和文档总结了一下 写法&#xff0c;特此记录以便之后使用。 浏览器滚动条api 总结&#xff…...

Oracel 统计信息收集

1. 常规的通过PLSQL客户端进行统计信息收集&#xff0c;需要指定用户名和表名 begin dbms_stats.gather_table_stats(ownname > &owner, tabname > &tablename, estimate_percent > DBMS_STATS.AUTO_SAMPLE_SIZE, cascade > TRUE, degree > 4, m…...

openpnp - Too many misdetects - retry and verify fiducial/nozzle tip detection

文章目录 openpnp - Too many misdetects - retry and verify fiducial/nozzle tip detection概述笔记环境光最好弱一些在设备标定时&#xff0c;吸嘴上不要装绿色屏蔽片如果吸嘴不在底部相机中间&#xff0c;先检查设置底部相机坐标调整底部相机坐标 吸嘴校验的细节底部相机坐…...

Windows安装WSL子系统及docker,以及WSL和docker配置、使用及问题解决

在Windows操作系统中,Ubuntu子系统(也称为Windows Subsystem for Linux, WSL)为开发者提供了一个在Windows环境下运行Linux环境的平台。然而,有时用户在按照Ubuntu子系统或者使用WSL时,可能会遇到各种问题,下面总结一下解决方式。 想要在Windows上安装Docker(实际上是基…...

光伏电站建设成本利润估算

​截至2024年9月底,全国光伏发电装机容量达到7.7亿千瓦,同比增长48.4%。其中集中式光伏4.3亿千瓦,分布式光伏3.4亿千瓦。2024年前三季度,全国光伏发电量6359亿千瓦时,同比增长45.5%。全国光伏发电利用率97.2%,同比下降1.1个百分点.早在今年2月份,中国光伏行业协会名誉理…...

TimesFM(Time Series Foundation Model)安装(2)

TimesFM&#xff08;Time Series Foundation Model&#xff09;安装简介 readme TimesFM&#xff08;Time Series Foundation Model&#xff09;安装简介&#xff08;1&#xff09;-CSDN博客https://blog.csdn.net/chenchihwen/article/details/144359861?spm1001.2014.3001…...

【Golang】Go语言编程思想(六):Channel,第六节,并发编程模式

并发模式 下例重新对 channel 的用法进行回顾&#xff1a; package mainimport ("fmt""math/rand""time" )func msgGen(name string) chan string {c : make(chan string)go func(name string) { // 在这个 goroutine 当中向外发送数据i : 0fo…...

Blue Ocean 在Jenkins上创建Pipeline使用详解

BlueOcean是Jenkins的一个插件,它提供了一套可视化操作界面来帮助用户创建、编辑Pipeline任务。以下是对BlueOcean中Pipeline操作的详细解释: 一、安装与启动BlueOcean 安装:在Jenkins的“系统管理”->“插件管理”->“可选插件”中搜索“BlueOcean”,然后点击“Ins…...

【数字花园】个人知识库网站搭建:②本地部署数字花园

目录 [[数字花园]]的构建原理包括三个步骤&#xff1a;五个部署方案数字花园网站的本地部署方案数字花园网站本地手动部署方案1. 获取网站源码2.2 安装 Node.js 3. 项目部署3.1 安装项目依赖3.2 构建项目3.3 启动http服务器 4. 本地预览5. 在笔记更新后&#xff1a;更新本地源码…...

监听H5页面在微信浏览器异常退出

参考文章 onBeforeUnmount(() > {unNormalExit(); });//---------------------------异常退出---------------------- function unNormalExit() {enterOrExitRoom({type: 37,roomId: roomId.value,userId: userId.value,nickName: name.value,loginUserType: 2, //0 专家 1…...

【汽车】-- 常见的汽车悬挂系统

汽车悬挂系统是车辆的重要组成部分&#xff0c;其主要功能是连接车轮和车身&#xff0c;减缓路面颠簸对车身的影响&#xff0c;提高行驶的平顺性、舒适性和操控性。以下是常见的汽车悬挂系统类型及其特点&#xff1a; 1. 独立悬挂系统 每个车轮可以独立上下运动&#xff0c;不…...

路由器、二层交换机与三层交换机的区别与应用

路由器、二层交换机和三层交换机是常见的网络设备&#xff0c;常常协同工作。它们都可以转发数据&#xff0c;但在功能、工作层级以及应用场景上存在差异。 1. 工作层级 三者在OSI模型中的工作层级不同&#xff1a; 路由器&#xff1a; 工作在 网络层&#xff08;第三层&#…...

CPO-CNN-LSTM-Attention、CNN-LSTM-Attention、CPO-CNN-LSTM、CNN-LSTM四模型对比多变量时序预测

CPO-CNN-LSTM-Attention、CNN-LSTM-Attention、CPO-CNN-LSTM、CNN-LSTM四模型对比多变量时序预测 目录 CPO-CNN-LSTM-Attention、CNN-LSTM-Attention、CPO-CNN-LSTM、CNN-LSTM四模型对比多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测任务中实现…...

xss原理分析与剖析

001 URL编码 URL只允许用US-ASCII字符集中可打印的字符(020—0x7x)&#xff0c;其中某些字符在HTTP协议里有特殊的意义&#xff0c;所以有些也不能使用。这里有个需要注意的&#xff0c;加号代表URL编码的空格&#xff0c;%20也是。 URL编码最长见的是在用GET/POST传输时&…...

如何使用PageAdmin CMS网站管理系统搭建网站?

一、什么叫CMS&#xff1f; CMS&#xff0c;即内容管理系统&#xff0c;是一种位于网站前台界面与后台数据库之间的软件系统。它能够实现网站内容的发布、编辑、管理、维护等操作&#xff0c;使得非专业人员也能轻松进行网站内容的更新与维护。 二、如何使用PageAdmin CMS网站管…...

Linux: shell: bash: set -x;调试使用

man bash set -x -x After expanding each simple command, for command, case command, select command, or arithmetic for command, display the expanded value of PS4, followed by the command and its expanded arguments or associated word list. 这个可以帮助将变量…...

高级排序算法(二):归并排序与堆排序详解

引言 在上一章中&#xff0c;我们探讨了高效的快速排序及其分治思想。这一次&#xff0c;我们将继续探索两种同样重要的排序算法&#xff1a;归并排序&#xff08;Merge Sort&#xff09; 和 堆排序&#xff08;Heap Sort&#xff09;。 它们与快速排序一样&#xff0c;都是O(…...

doxygen–自动生成文档工具

原文地址&#xff1a;doxygen–自动生成文档工具 – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 简介 doxygen是软件开发中广泛使用的文档生成工具。它可以从源代码注释中自动生成文档&#xff0c;解析类、函数、参数相关信息&#xff0c;并生…...

5G学习笔记之SNPN系列之ID和广播消息

目录 1. 概述 2. SNPN ID 3. SNPN广播消息 1. 概述 SNPN&#xff1a;Stand-alone Non-Public Network&#xff0c;独立的非公共网络&#xff0c;由NPN独立运营&#xff0c;不依赖与PLMN网络。 SNPN不支持的5GS特性&#xff1a; 与EPS交互 emergency services when the UE acce…...

登录授权的实现:json web token + redis + springboot

文章目录 引言I token实现思路传统JWT TOKEN认证方式改进的JWT TOKEN认证方式redis设计II java代码实现登录接口退出登录接口登录之后接口(token解析和校验)III 常见问题400引言 应用场景: 登录认证 I token实现思路 传统JWT TOKEN认证方式 RESTful API TOKEN认证方式:…...

AlexNet:开启深度学习图像识别新纪元

一、引言 在深度学习的璀璨星空中&#xff0c;AlexNet 无疑是一颗极为耀眼的明星。它于 2012 年横空出世&#xff0c;并在 ImageNet 竞赛中一举夺冠&#xff0c;这一历史性的突破彻底改变了计算机视觉领域的发展轨迹&#xff0c;让全世界深刻认识到深度卷积神经网络在图像识别任…...

鸿蒙面试-----鸿蒙组件生命周期详解

不同颜色的是必须要会的&#xff0c;黑色的参考一下&#xff0c;只写了一些用过的还有很多组件的生命周期都没写 组件的生命周期有那些&#xff1f; 我了解的有、系统组件的生命周期、页面和自定义组件的生命周期、UIAbility组件的生命周期&#xff0c;Navigation组件生命周期…...

Meta Llama 3.3 70B:性能卓越且成本效益的新选择

Meta Llama 3.3 70B&#xff1a;性能卓越且成本效益的新选择 引言 在人工智能领域&#xff0c;大型语言模型一直是研究和应用的热点。Meta公司最近发布了其最新的Llama系列模型——Llama 3.3 70B&#xff0c;这是一个具有70亿参数的生成式AI模型&#xff0c;它在性能上与4050…...

asp.net老项目运维,出现的问题6之数据库

数据库会有很多张表&#xff0c;表内一般会有自增列&#xff0c;如果想统一管理这个自增数值&#xff0c;可以使用如下方法&#xff0c;放在一个存储过程中&#xff0c;想用的时候调用存储过程即可生成数据库层面的全局唯一值&#xff1a; create procedure [dbo].[P_getSeqID…...

WADesk 升级 Webpack5 一些技术细节认识5和4的区别在哪里

背景 升级过程中发现有很多新的知识点&#xff0c;虽然未来可能永远都不会再遇到&#xff0c;但是仍然是一次学习的好机会&#xff0c;可以让自己知道&#xff0c;打包软件的进化之路&#xff0c;和原来 Webpack 4 版本的差异在哪里。 移除的依赖记录 babel/register: 在 Nod…...

什么是JVM即时编译

什么是JVM即时编译 即时编译是JVM的核心功能&#xff0c;他让java在性能上不输于C/C JVM&#xff08;Java Virtual Machine&#xff09;即 Java 虚拟机&#xff0c;是一种用于执行 Java 字节码的虚拟计算机。它是 Java 程序的运行核心&#xff0c;提供了一个独立于底层操作系统…...

《经验分享 · 软考系统分析师》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…...

HC-SR04 超声波测距模块驱动总结

一、基本原理 1.1. 引脚功能 VCC电源 (需要5V直流电源)GND地Trig信号触发脉冲 (>10us TTL脉冲)Echo输出TTL电平信号, 电平持续时间与距离程正比 1.2. 电气特性 工作电压5V工作电流15mA工作频率40kHz射程范围0.02 ~ 4m (参考值,不同厂家质量可能也不一样)测量精度1mm (理…...