目录
模版的声明和定义不能分离,否则会报错.
写下面三个文件:
- Stack.h
-
- #pragma once
- #include
- using namespace std;
- template <class T>
- T Add(const T& left, const T& right);
-
-
-
- Stack.cpp
-
- #include"Stack.h"
- template <class T>
- T Add(const T& left, const T& right)
- {
- cout << 1 << endl;
- return left + right;
-
- }
- Test.cpp
-
-
-
- #include"Stack.h"
- int main()
- {
- Add(1,2);
- fun();
- return 0;
- }
运行结果:

如上图所示,模版的声明和定义分离会出现链接错误。
如果我们正常函数的定义和分离就不会出错,比如下图fun函数:
这是刚开始我们把代码加载进内存,运行之后先执行预处理,预处理把"Stack.h"展开。
展开之后就会在Stack.cpp里面找,但是Stack.cpp里面并没有对模版进行实例化,也就找不到地址:

为什么Add预处理阶段找不到地址呢?这是因为在Stack.cpp也就是定义里面Add没有被实例化:
Add是模版,没有实例化,在预编译阶段找不到地址,但是fun我们一开始就定义了是void类型,所以不管是预处理,编译,链接阶段都可以找到它的地址。
解决方案:
1.显示实例化:
缺点:换类型之后还要重新显示实例化,假设我此时这样写:
Add(1.0, 2.0);
就会报错:
1>Test.obj : error LNK2019: 无法解析的外部符号 "double __cdecl Add(double const &,double const &)" (??$Add@N@@YANABN0@Z),函数 _main 中引用了该符号
需要再加个类型显示实例化:
- template
- double Add<double>(const double& left, const double& right);
运行:
- 1
- 1
但是这样太麻烦了,干脆不要声明定义分离了,把Stack.cpp删掉,定义全部放到Stack.h里面:

继承就是一种复用:

继承格式:


举例:
-
-
- class Person
- {
- public:
- void Print()
- {
- cout << "name:" << _name << endl;
- cout << "age:" << _age << endl;
- }
- protected:
- string _name = "peter"; // 姓名
- int _age = 18;
- };
- // 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
- //Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
- //以看到变量的复用。调用Print可以看到成员函数的复用。
- class Student : public Person
- {
- protected:
- int _stuid; // 学号
- };
- class Teacher : public Person
- {
- protected:
- int _jobid; // 工号
- };
- int main()
- {
- Student s;
- Teacher t;
- s.Print();
- t.Print();
- return 0;
- }
- name:peter
- age:18
- name:peter
- age:18
如上图,teach类和student类都继承了person类的成员函数和成员变量
如果继承方式不写,class默认是私有,struct默认是公有:
-
-
- class Person
- {
- public:
- void Print()
- {
- cout << "name:" << _name << endl;
- cout << "age:" << _age << endl;
- }
- protected:
- string _name = "peter"; // 姓名
- int _age = 18;
- };
- // 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
- //Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
- //以看到变量的复用。调用Print可以看到成员函数的复用。
- class Student : Person
- {
- protected:
- int _stuid; // 学号
- };
- class Teacher : Person
- {
- protected:
- int _jobid; // 工号
- };
- int main()
- {
- Student s;
- Teacher t;
- s.Print();
- t.Print();
- return 0;
- }

不同类型的相近对象之间赋值的时候会进行隐式类型转化,隐式类型转换会产生临时变量。
如:
- double d = 2.2;
-
- int& r = d;
-
-

报错原因:
int 和double是相近类型,因为都表示大小,把double d赋值给int i,那么r对int d取地址实际上不是对d取地址,而是对临时变量取地址。
解决方法:
临时变量具有常性,加个const就行了:
const int& r = d;
那子类可不可以给父类呢?
-
- class Person{};
- class Student : public Person{};
- int main()
- {
- Student a;
- Person b = a;
- return 0;
- }
-
没有报错可以的,因为父子之间是一种很亲近的关系。
在public继承下,父子之间是一种 is a关系:
student is a person ,teacher is a person:

那既然是很亲近的关系,会进行隐式类型转换,那么肯定也会产生临时对象:
- Student a;
- Person& b = a;
![]()
没加const没有报错。难道自定义类型不会隐式类型转换吗?
下面代码可证明自定义类型也可以隐式类型转换:
- string c = "xxxx";
- string& d = "xxxx";

- string c = "xxxx";
- const string& d = "xxxx";

原因:

如下代码,父类和子类都有number变量,此时访问子类,打印出来的是子类的number:
- class Person
- {
- protected:
- string _name = "小李子"; // 姓名
- int _num = 111;//身份证号
- };
- class Student : public Person
- {
- public:
- void Print()
- {
- cout << " 姓名:" << _name << endl;
- cout << " 身份证号:" << _num << endl;
-
- }
- protected:
- int _num = 999; // 学号
- };
- void Test()
- {
- Student s1;
- s1.Print();
- };
- int main()
- {
- Test();
- return 0;
- }
-
- 姓名:小李子
- 身份证号:999
如果想访问父类的number,需要突破作用域:
- class Person
- {
- protected:
- string _name = "小李子"; // 姓名
- int _num = 111;//身份证号
- };
- class Student : public Person
- {
- public:
- void Print()
- {
- cout << " 姓名:" << _name << endl;
- cout << " 身份证号:" << Person::_num << endl;
-
- }
- protected:
- int _num = 999; // 学号
- };
- void Test()
- {
- Student s1;
- s1.Print();
- };
- int main()
- {
- Test();
- return 0;
- }
-
- 姓名:小李子
- 身份证号:111

如果父子类有两个同名成员函数,选哪个:
-
- // B中的fun和A中的fun不是构成重载,因为不是在同一作用域
- // B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
- class A
- {
- public:
- void fun()
- {
- cout << "func()" << endl;
- }
- };
- class B : public A
- {
- public:
- void fun(int i)
- {
-
- cout << "func(int i)->" << i << endl;
- }
- };
- void Test()
- {
- B b;
- b.fun(10);
- };
- int main()
- {
- Test();
-
- return 0;
- }
-
- 答案:B
- //会调用子类同名成员函数,隐藏父类同名成员函数
- 运行结果:func(int i)->10
如果我现在不带参:
- void Test()
- {
- B b;
- b.fun();
- };

假设我就是不想带参,那就调父类嘛,突破一下作用域:
- void Test()
- {
- B b;
- b.A::fun();
- };
运行结果:func()
自定义类型,如果我们不写,编译器会自动生成构造函数
派生类如果不写会调用父类的构造函数:
- class Person
- {
- public:
-
- Person()
- {
- cout << "Person()" << endl;
- }
- ~Person()
- {
- cout << "~Person()" << endl;
- }
-
- protected:
- string _name; // 姓名
- };
-
-
- class Student : public Person
- {
- public:
-
- protected:
- int _num; //学号
- };
-
-
- int main()
- {
- Student s1;
-
- return 0;
- }

-
- class Person
- {
- public:
- Person(const char* name = "peter")
- : _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 age)
- :_name()
- ,_age(age)
- {
- cout << "student const(student& s)" <
- }
-
- protected:
- int _age; //学号
- };
- void Test()
- {
- Student s1("张三",18);
-
- }
- int main()
- {
- Test();
- return 0;
- }
-
-

这是因为派生类初始化父类成员变量必须把父类当成一个对象,而不是直接去初始化父类的成员变量,如果派生类不写构造函数会默认调用父类构造函数,如果派生类想写构造函数必须显示调用父类构造函数。
改成如下:
-
- class Person
- {
- public:
- Person(const char* name = "peter")
- : _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 age)
- :Person(name)
- ,_age(age)
- {
- cout << "student const(student& s)" <
- }
-
- protected:
- int _age; //学号
- };
- void Test()
- {
- Student s1("张三",18);
-
- }
- int main()
- {
- Test();
- return 0;
- }
-
-

不写拷贝构造函数
子类拷贝默认调用父类的拷贝构造:
- Student s1("张三",18);
- Student s2(s1);

手动写拷贝构造
派生类写拷贝构造要先显示调用父类拷贝构造
- Student(const Student& s)
-
- :Person(s)//切片,把父类的成员变量切出来去给父类拷贝构造
- ,_age(s._age)
- {
- cout << "Student (const student&)" << endl;
- }

运算符重载
-
- Student& operator=(const Student& s)
- {
- if (&s != this)
- {
- operator=(s);
- _age = s._age;
- }
- cout << "student operator =(const student& s)" << endl;
- return *this;
-
- }
会栈溢出,一值调子类的operator:

这是因为子类隐藏了父类,要想调父类的必须要显示调用:
- Student& operator=(const Student& s)
- {
- if (&s != this)
- {
- Person::operator=(s);
- _age = s._age;
- }
- cout << "student operator =(const student& s)" << endl;
- return *this;
-
- }

析构函数
子类调用析构不用显示调用父类析构,系统会自动按照先子后父调用析构
-
- ~Student()
- {
- cout << "~Student()" << endl;
- }

演示:
在快结束时按fn+f11会调用析构,如下图显示:程序结束时先调用子类析构,再调用父类析构:

静态变量的继承
如图,子类继承父类的静态变量,我们把地址打印出来看看,发现是同一个地址,也就是并没有继承,而是直接拿来用了。
- class Person
- {
- public:
- Person() { ++_count; }
- protected:
- string _name; // 姓名
- public:
- static int _count; // 统计人的个数。
- };
- int Person::_count = 0;
- class Student : public Person
- {
- protected:
- int _stuNum; // 学号
- };
- class Graduate : public Student
-
- {
- protected:
- string _seminarCourse; // 研究科目
- };
- int main()
- {
-
- cout << " 人数 :" << &Person::_count << endl;
-
- cout << " 人数 :" << &Person::_count << endl;
- return 0;
- }

菱形继承
如下图,子类和父类中都有_name成员,我们现在访问_name成员会有二义性,编译器不知道我们要访问父类还是子类的_name。
- 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;
- a._name = "peter";
-
- return 0;
- }

解决方法
1:指定:
- class Person
- {
- public:
- string _name; // 姓名
- };
- class Student : public Person
- {
- protected:
- int _num; //学号
- };
- class Teacher : public Person
- /*
- 虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
- Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
- 方去使用。
- 虚拟继承解决数据冗余和二义性的原理
- 为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成
- 员的模型。*/
- {
- protected:
- int _id; // 职工编号
- };
-
- class Assistant : public Student, public Teacher
- {
- protected:
- string _majorCourse; // 主修课程
- };
- int main()
- {
-
- Assistant a;
- // 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
- a.Student::_name = "xxx";
- a.Teacher::_name = "yyy";
- return 0;
- }
2.虚继承
class Student :virtual public Person
class Teacher : virtual public Person
-
- class Assistant : virtual public Student, public Teacher
题目
解析:

所以选C
-
相关阅读:
Java方法中不使用的对象应该手动赋值为NULL吗?
[Spring Cloud] Ribbon介绍与定义负载均衡
【Java】HashMap、HashTable和ConcurrentHashMap的区别
Linux的进程管理
解决k8s node节点报错: Failed to watch *v1.Secret: unknown
数据结构-单链表
Docker-compose教程(安装,使用, 快速入门)
【23种设计模式】享元模式【⭐】
[附源码]java毕业设计图书馆自习室管理系统
MySQL数据库之备份与恢复
-
原文地址:https://blog.csdn.net/m0_65143871/article/details/133960310