当前位置:网站首页>1.非类型模板参数 2.模板的特化 3.继承讲解
1.非类型模板参数 2.模板的特化 3.继承讲解
2022-07-31 01:23:00 【韩悬子】
1.非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
#include<iostream>
using namespace std;
#define n=100;
template<class T>
class stack
{
private:
T _a[n];
int _top;
};
int main()
{
stack<int> st1;//100
stack<double> st2;//500
return 0;
}
看到上面代码我想要开栈开int开100,double开500,但是不行啊,n被定义成了100做不到啊
但是我们我们可以这样,在模板里面加个参数,到时实例化的时候就是为是我们想开的大小,而这个n就是非类型模板参数
#include<iostream>
using namespace std;
template<class T,size_t n>
class stack
{
private:
T _a[n];
int _top;
};
int main()
{
stack<int,100> st1;//100
stack<double,500> st2;//500
return 0;
}
而这个n上面我也讲了是常量,不能更改
验证
可以以看到st1和st2,里面的100和500没被引用
有些类型是不能当非类型模板参数的string和double都不能当,能当的大部分都是整型,char也能当
2.模板的特化
看我们下面前面二个打印没错,但是为什么第三个就有问题了,7月16怎么可能小于7月15
这个错误其实很简单,他们只是比较了地址了而已,要是我们要求这里不能用来解引用p1和*p2怎么解决
我们来解决这个问题可以用模板的特化来解决
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
//针对某些类型要特殊化处理
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
前二个调用的都是正常模板,而第三个我们调用的是特化模板,不过除了这种办法,也可以专门写一个在这个函数的重载来解决
对于模板特化,函数模板可以避免,但是类模板不行,
比如我们要一个类模板,一边是int,另一边是double的参数类模板就只能使用特化了
上面图片写的是全特化,除了全特化,还有半特化和偏特化,有点像半缺省
偏特化
下面的意思是只要是指针就会匹配,如果只有int*,int就走原生版本,必须要求二个指针,要不然就不访问,除了指针,其实还有引用
3.继承
比如要你写个图书管管理系统,你会发现很多角色有的信息是相同的,比如每个人都有年龄,学生都有学号,地址等等
这个时候我们就可以用继承来解决,设计一个公共的类,再搞一个私类,把公共的类继承到私类里面
继承格式
1继承的关系
总结
- 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
- 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected> private。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
- 在实际运用中一般使用都是public继承,几乎很少使protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强
2.基类和派生类对象赋值转换
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去
int main()
{
Person p;
Student s;
s._name = "张三";
s._age = 18;
s._sex = "男";
// 子类对象给父类 对象/指针/引用 -- 语法天然支持,没有类型转换
p = s;
Person& rp = s;
Person* ptrp = &s;
rp._age++;
ptrp->_age++;
cout << rp._age << endl;
return 0;
}
Person& rp = s;这个相当于子类中父类的别名
Person* ptrp = &s;这个相当于子类中父类的指针
如果上面没理解就和下面图一起看了理解,简单来讲,只能调用子类中父类的参数,还要注意子类对象给父类 对象/指针/引用 时没有类型转换
如果我们把子类的公有的改成保护,他还支持吗?
可以看到不支持了,因为父类是公有子类是保护,所以变成了保护,而切片过去为什么不支持,也没什么好讲的保护本来就不支持类型转换
这里还有注意的是父类赋值给子类是不支持的,不过指针可以
3.继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问) - 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意在实际中在继承体系里面最好不要定义同名的成员。
下列代码会打印什么是111还是999
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
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; // 学号
};
int main()
{
Student s1;
s1.Print();
return 0;
};
运行结果
可以看到打印的是999,就近原则,有点像局部原则,先在子类找到,就不去父类找了,这个就叫隐藏,但是如果我就像想访问父类的怎么办
这个时候可以指定作用域
如果是类里面的函数呢?
class A
{
public:
void fun()
{
cout << "A::func()" << endl;
}
};
class B : public A
{
public:
void fun()
{
cout << "B::func()"<< endl;
}
};
int main()
{
B b;
b.fun();
return 0;
};
其实和前面的成员函数一样
如果想调用父类的呢?
下面A::fun 和 B::fun 的关系是什么
class A
{
public:
void fun()
{
cout << "A::func()" << endl;
}
};
class B : public A
{
public:
void fun(int)
{
cout << "B::func()"<< endl;
}
};
int main()
{
B b;
b.fun();
return 0;
};
一开始如果不知道隐藏的话,大部分人都会讲上面是重载吧!不过上面的关系是隐藏,函数重载有个最重要的要求:必须要求在同一个作用域,同名
4.派生类的默认成员函数
“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?
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()
{
cout << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name = "", int num = 0)
:_num(num)
,Person(name)
{
cout << "Student(const char* name = "", int num = 0)" << endl;
}
protected:
int _num; //学号
};
int main()
{
Student s1;
return 0;
}
运行结果
可以看到我们调用了父类的构造,再调用了子类的构造函数,最后再调用了父类的析构函数
子类构造函数原则:
1.调用父类构造函数初始化继承自父类成员
2.自己再初始化自己的成员 – 规则参考普通类析构、拷贝构造、复制重载也类似
子类的构造函数和析构
// s1 = s3
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s);
_num = s._num;
}
cout << "Student& operator=(const Student& s)" << endl;
return *this;
}
~Student()
{
Person::~Person();
cout << "~Student()" << endl;
}
这里要注意的是Person::operator=(s);不能写成operator=(s);要不然就会栈溢出,为什么这么写也很好理解,先构造父类的早构造子类的,按照这样,析构应该也是这样的,
代码
class Person
{
public:
Person(const char* name)
: _name(name)
{
cout << "Person()" << endl;
}
~Person()
{
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 = 0)
:_num(num)
,Person(name)
{
cout << "Student(const char* name = "", int num = 0)" << endl;
}
// s1 = s3
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s);
_num = s._num;
}
cout << "Student& operator=(const Student& s)" << endl;
return *this;
}
~Student()
{
Person::~Person();
cout << "~Student()" << endl;
cout << endl;
}
protected:
int _num; //学号
};
int main()
{
Student s1("李四",4);
Student s2(s1);
Student s3("王五", 2);
s1 = s3;
return 0;
}
运行结果
这里面一共构造了三个函数,前面没有问题,但是后面看到析构子类的析构了三次,但是父类的析构了6次,把写在子类的父类析构给删了,发觉又没问题了,这其实说明了,为了保证析构顺序先子后父,子类构造结束后父类析构会自动析构,所以不需要我们自己调用父类析构
5.复杂的菱形继承及菱形虚拟继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
class Person
{
public:
string _name; // 姓名
int _a[10000];
};
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";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
cout << sizeof(a) << endl;
return 0;
}
上面这个a是多继承 a._name = “peter”;这样子它不知道访问谁的name,会报错
解决也很很简单指定作用域就行了
而数据冗余就没解决了,数据冗余带来的问题是空间浪费,比如person里面的name
不过要是加上virtual(变成虚继承)就可以避免这个问题
浪费少了整整一半
边栏推荐
猜你喜欢
Meta元宇宙部门第二季度亏损28亿 仍要继续押注?元宇宙发展尚未看到出路
【网络安全】文件上传靶场通关(1-11关)
ROS Action communication
prometheus 监控概述
typescript9 - common base types
typescript13 - type aliases
ShardingSphere's unsharded table configuration combat (6)
ShardingSphere之水平分库实战(四)
typescript15- (specify both parameter and return value types)
小黑leetcode之旅:117. 填充每个节点的下一个右侧节点指针 II
随机推荐
typescript17 - function optional parameters
35. Reverse linked list
DOM系列之 client 系列
In Google Cloud API gateway APISIX T2A and T2D performance test
Teach you how to configure Jenkins automated email notifications
ros2知识:在单个进程中布置多个节点
Dispatch Center xxl-Job
Sping.事务的传播特性
MySql data recovery method personal summary
Yolov7实战,实现网页端的实时目标检测
ShardingSphere之读写分离(八)
Artificial Intelligence and Cloud Security
Word 表格跨页,仍然显示标题
Ticmp - 更快的让应用从 MySQL 迁移到 TiDB
typescript11-数据类型
The Meta Metaverse Division lost 2.8 billion in the second quarter, still want to continue to bet?Metaverse development has yet to see a way out
斩获BAT、TMD技术专家Offer,我都经历了什么?
typescript10-commonly used basic types
tkinter模块高级操作(二)—— 界面切换效果、立体阴影字效果及gif动图的实现
关于Redis相关内容的基础学习