非类型模板参数
解决问题 定义类型的时候 的类型固定数据大小
template <class T,int N>
class Arry
{
public:
private:
T _a[N];
}
模板的特化
[特殊化] : 对于某些类型的特殊化处理
template <class T>
bool IsEqual(T& left,T& right){
return left == right;
}
template<> // 字符串类型的特殊化
bool IsEqual<char *>(char*& left,char*& right)
{
return strcmp(left,right);
}
类模板特化
将一种类型具体化
template<class T1, class T2>
class usu{
public:
void tet(){
cout << "usually" << endl;
}
private:
T1 A;
T2 B;
};
template<>
class usu<int ,char>{
public:
void tet()
{
cout << "no usually" << endl;
}
private:
int a;
char c;
};
// use
int main(){
usu<int,int> aa;
usu<int,char> avbn;
aa.tet();
avbn.tet();
return 0;
}
偏特化 包含特化的模板 不是全特化
模板的分离编译
项目工程中一般将函数或者类声明放到.h中
将函数或者类定义放到.cpp中
优点 : 方便查看和维护
模板不能分离编译
为什么
编译过程
预处理
展开头文件
去注释
替换宏
条件编译
编译
检查代码 生成汇编代码
汇编
将汇编代码转换二进制的机器码
链接
生成 a.out
将目标文件合并在一起
编译时F1F2有声明 所以编译过链接要去找Func.o,而不会找到模板类的链接,所以函数模板不会分离编译
为什么找不到模板类的链接?
因为编译器在编译的时候不知道模板类的函数变量类型 所以 无法将函数实例化出来 ,报错链接错误.
解决方法 显示实例化
template
void F2<int>(const int &x);
这样可以链接上 int 类型的函数
其他类型并不可以
用什么实例化什么 不方便
怎么解决根源问题?
不要分离编译
继承
public:
void print()
{
cout << name << "-------" << age << endl;
}
//protected:
string name = "Person";
int age = 18;
};
class student:public Person{
public :
protected:
int _stu_id;
};
int main() {
student su1;
su1.name = "ewq";
su1.print();
return 0;
}
继承的三种方式
public 继承
private 继承
portected 继承
赋值问题
子类的对象可以赋值给父类的对象/指针/引用
person pobg = sobg;
person pp = &sobg;
person rp = sobg;
基类的对象不能直接赋值给派生类
当父类和子类同时有同名成员时候,子类成员优先 隐藏了父类的成员如果要访问父类 要指定作用域
继承构造函数的问题
如何设计不能被继承的类 ?
设计一个类 构造函数私有化 就不可以被继承
友元关系不能被继承
单继承 多继承 菱形继承
单继承:一个子类只有一个父类的继承关系叫单继承
多继承:一个子类 有两个或者以上的直接父类的继承关系为多继承
菱形继承:多继承的特殊情况
菱形继承会导致一些数据冗余和二义性的问题
二义性问题 可以使用指定作用域来解决
数据冗余问题 可以使用虚拟继承解决问题
虚拟继承:(virtual inheritance)
当一个类通过虚拟继承继承一个类的时候,这个类叫做虚基类,虚拟继承确保在多重继承中,即使类被多次继承,也只会存在一个实例
虚拟继承的机制
虚拟继承通过虚基表指针,和虚基表实现
虚基表指针: 当你使用虚拟继承的时,派生类会有一个虚基表指针,它指向虚基表
虚基表: 里面存储的是需基类的偏移量,用定位需基类的位置
普通继承 和 虚拟继承的区别
在普通的多重继承中,每个派生类都各自拥有基类的一份拷贝,因此在菱形继承中基类会被继承多次.,虚拟继承则不同,无论有多少派生类继承虚基类最终只会有一份虚基类实例
普通多重继承示例
class A { public: int x; }; class B : public A {}; class C : public A {}; class D : public B, public C {}; // D类将拥有A类的两个实例。
虚拟继承示例
class A { public: int x; }; class B : virtual public A {}; class C : virtual public A {}; class D : public B, public C {}; // D类中A类的实例只会有一份。
虚拟继承的构造和析构
虚基类构造函数 -> 直接基类构造函数 -> 派生类构造函数
这种顺序的设计目的是确保虚基类的构造函数不会被多次调用,以避免不必要的重复构造和潜在的资源浪费。
虚基类的构造函数:只会在最底层派生类的构造函数中被调用一次。
直接基类的构造函数:在虚基类之后初始化。
派生类的构造函数:最后执行。
析构函数是和构造顺序是相反的
多态:当使用虚继承时,如果派生类重写了虚基类的虚函数,最终调用的函数版本是由最后一个派生类重写的版本覆盖。
一般最好不要设计成多继承 菱形继承
继承 和 组合
问题 : 使用继承还是组合?
符合is-a 就用继承
符合has-a就用组合
如果子类是父类的特化,并且子类将继承父类大部分或全部属性和行为,继承可能是一个好选择。
如果类之间的关系更像是部分和整体的关系,或者你希望保持类的独立性和灵活性,组合可能是更好的选择。
多态
多种形态 不同的用户调用会有不同形态
多态的构成条件:
必须通过基类的指针或者因引用调用函数
被调用的函数必须是虚函数,并且派生类必须对基类的虚函数进行重写
虚函数 和 继承 都 使用 virtual 关键字但是是不同的功能
虚函数 : 被virtual函数修饰的类关键字是虚函数
经典问题:
#include <iostream>
using namespace std;
class person{
public:
virtual void BuyTacket()
{
cout << " cout in Virtual" << endl;
}
private:
int _A; // 4字节
char _ch; // 1字节 //补充4字节
};
int main()
{
int s = sizeof(person);
cout << s << endl;
return 0;
}
应该是8字节? 此类还包含一个虚函数表 一般虚函数表占8字节
虚函数的重写
派生类中有一个和基类完全一样的相同的虚函数(派生类虚函数) 与基类虚函数的返回值类型函数名称参数列表完全相同,称子类的虚函数重写了基类的虚函数
重写虚函数的时候派生类不加virtual
关键字时,虽然依然可以构成重写,但是这样写法并不规范 不建议这样使用
多态的两个必要条件
1, 虚函数的重写,满足多态1的条件之一
2,父类对象的指针或者引用去调用虚函数
3,满足多态和调用对象的类型无关,和指向的对象有关,哪个对象去掉用 谁就是他的虚函数
4, 不满足多态 和函数调用对象的类型有关,类型是什么调用 就是谁的虚函数
虚函数重写的两个例外
#include <iostream>
using namespace std;
class A{
public:
virtual void func(int val = 1)
{
cout << "A>" << val << endl;
}
virtual void test(){func();}
};
class B :public A
{
void func(int val = 0)
{
cout <<"B>" << val << endl;
}
};
int main()
{
B b ;
b.test();
return 0;
}
/// 输出1?
// 子类虚函数重写只是重写了函数体
关键字1 final指定虚函数不能重写
关键字2 override 检查派生类虚函数是否重写某个基类的虚函数如果没有则报错
抽象类
包含纯虚函数 ,不能实例化出对象 ,也叫做接口类 叫做抽象类
纯虚函数的作用
强制子类去完成重写
表示抽象类型
如何实现的指向谁就调用谁的虚函数?
多态是在运行时指向的对象的虚表中查找要调用的虚函数的地址来进行调用
虚函数表实际上是指针数组
多态是调用指向谁 就调用谁的虚函数
虚函数存在哪?
代码段
虚函数表(虚表)存在哪?
虚函数表中存储的虚函数指针
虚表存放在代码段
#include <iostream>
using namespace std;
class b1{
public:
virtual void func(){
cout << "hello world" << endl;
}
};
void test()
{
b1 a;
printf("vftptr = %p\n",*(int*)&a);
int i = 0;
int p1 = &i;
int p2 = new int;
char *p3 = "hello world";
printf("栈变量:%p\n",p1);
printf("堆变量:%p\n",p2);
printf("代码段常量:%p\n",p3);
printf("代码段函数地址:%p\n",&b1::func);
printf("代码段函数地址:%p\n",&test);
}
int main()
{
test();
return 0;
}
静态绑定 和 动态绑定
int i = 0;
double d = 1.1;
//静态绑定 静态的多态 编译的时候确定虚函数的地址
f1(i);
f1(d);
// 动态绑定 动态的多态 运行的时候去 虚表中z
Base *p = new Base;
p->Func1();
p = new Derive;
p->func1();
单继承函数和多继承函数中的虚基表
编译器是看不到虚基表的
可以使用以下方法看
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
cout << " 虚表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
虚函数 和 虚继承
虚函数 虚函数 重写条件之一
多态原理 : 虚函数地址 放到多态的虚基表内,多态指向谁调用本质上是是 运行到对象虚表找要调用的虚函数
虚继承 解决菱形继承中的数据冗余 和二义性
原理 将虚 基类的 放到公共位置 虚基表中存的是偏移量 来确定虚基类 对象的位置
都用到了vritual关键字 但是没有一点关系