现在进入了c++高级课程了,前面5节是c++的语法基础,从现在开始就是c++提高篇了。
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
我们可以用函数模板来定义函数:
//模板技术,类型参数化,编写代码可以忽略类型。
template<class T> //这一个是函数模板的关键字,为了区别函数模板和普通函数
void MySwap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
//也可以吧class写成typename
template<typename T>
void MySwap1(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
//使用方法有两种:
int main(int argc, char **argv)
{
int a = 10;
int b = 11;
//1. 自动类型推导
printf("a b %d %d\n", a,b);
MySwap(a, b);
printf("a b %d %d\n", a,b);
MySwap1(a, b);
printf("a b %d %d\n", a,b);
//2.显示的指定类型
printf("a b %d %d\n", a,b);
MySwap<int>(a, b);
printf("a b %d %d\n", a,b);
return 0;
}
函数模板和普通函数在一起调用规则:
函数模板和普通函数的区别:
强制调用模板 用MySwap<>(a, b);
函数模板机制结论:
正如我们上面定义的函数模板一样,类也是有模板的,用法跟函数模板差不多:
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
参考菜鸟教程:https://www.runoob.com/cplusplus/cpp-templates.html
我觉得菜鸟教程 举的例子很不错,实现了一个栈,栈的元素确实不确定,挺好的,我这里就不写了,写了也是抄过来。
template<class T>
class Animal
{
public:
T m_age;
private:
};
//有两种继承的方式
//1.子类也是类模板
template<class T>
class cat : public Animal<T>
{
public:
private:
};
//2.可以实例化的类,把实际类型替换掉T
class cat1 : public Animal<int>
{
public:
private:
};
//使用方法
cat<int> c;
类模版在类内实现,这个比较简单,把例子发出来就可以了:
template<class T>
class Animal
{
public:
Animal(T age)
{
this->m_age = age;
}
void show(){
printf("Animal %d\n", m_age);
}
private:
T m_age;
};
//有两种继承的方式
//1.子类也是类模板
template<class T>
class cat : public Animal<T>
{
public:
cat() : Animal<T>(10)
{}
private:
};
类内部实现,可以直接使用,比较简单,但是c++支持多文件,所以有时候也需要在类外部实现。
实现一个类模板类外实现:
//类的声明
template<class T>
class Animal
{
public:
Animal(T age);
void show();
private:
T m_age;
};
template<class T>
class cat : public Animal<T>
{
public:
cat();
private:
};
//类的实现
template<class T> //函数上面添加模板声明
Animal<T>::Animal(T age){ //类名后面也要加
this->m_age = age;
}
template<class T>
void Animal<T>::show(){
printf("Animal %d\n", m_age);
}
template<class T>
cat<T>::cat() : Animal<T>(10)
{
}
当类外实现的时候遇上了友元函数,那就更难受了:
template<class T> class Animal;
template<class T> ostream& operator<<(ostream& os, Animal<T>& a);
template<class T>
class Animal
{
public:
Animal(T age);
void show();
//重载<<操作符
friend ostream& operator<<<T>(ostream& os, Animal<T>& a);
private:
T m_age;
};
template<class T>
ostream& operator<<(ostream& os, Animal<T>& a)
{
os << a.m_age << "年龄" << endl;
return os;
}
友元函数需要注意3个地方:
第一个首先要声明
第二个就是在类中声明的friend中需要在函数名后面加
第三个就是实现的时候,要跟其他的函数一样,要加template
需要更注意一点,类外实现,少用友元函数
想不到多文件实现这个类模板也这么难受
animal.h
#pragma once
#include
template<class T> class Animal;
template<class T> void show_aa(Animal<T>& a);
template<class T>
class Animal
{
public:
Animal(T age);
void show();
//重载<<操作符
friend void show_aa<T>(Animal<T>& a);
private:
T m_age;
};
//有两种继承的方式
//1.子类也是类模板
template<class T>
class cat : public Animal<T>
{
public:
cat();
private:
};
animal.cpp
#include "animal.h"
template<class T>
Animal<T>::Animal(T age){
this->m_age = age;
}
template<class T>
void Animal<T>::show(){
printf("Animal %d\n", m_age);
}
template<class T>
cat<T>::cat() : Animal<T>(10)
{
}
template<class T>
void show_aa(Animal<T>& a)
{
printf("gjah %d\n", a->m_age);
}
main.c
int main(int argc, char **argv)
{
//cat c;
//c.show();
//cout << c;
return 0;
}
如果把main.c中申请的对象屏蔽掉,就不会出现错误,如果打开,会出现以下错误:
这个跟类模板两次编译有关,因为是多文件的时候,是每个文件自己单独编译成.o文件,然后在链接器链接每个.o,才会生成可执行文件。
问题就出在编译成.o的时候,因为animal.cpp是一个函数模板,这时候编译的话,是属于第一次编译,只是检查函数模板是否有语法问题,结果这个cpp没有语法没问题,所以编译通过,生成.o文件,接下来main.c这个直接调用了模板函数,但是这个文件也没有声明,所以main.c中会标注一些符号,代表着这个需要到链接的时候由链接器寻找,所以也没有问题,编译通过。
这时候到链接器,链接器就拿到main.c中的cat::cat(),去各个.o中寻找有没没对应的函数,这时候animal.o中只有原来的函数模板,没有具体编译成函数,因为函数模板是需要两次编译的,一次是调用的时候,所以这时就没有找到对应的函数,所以链接的时候报错。
有解决方法,就是把包含的头文件修改成cpp文件即可编译完成,这时候main.cpp中就有函数模板的声明和调用了,就可以完成二次编译了。
但是正是的工程中,不会这么使用的,而是把函数模板的声明和实现写在一个文件中,这个文件的后缀就是hpp.
怪不得当初不知道hpp是什么意思,现在终于知道了。
template<class T>
class Animal
{
public:
Animal(T age);
void show();
//重载<<操作符
friend void show_aa<T>(Animal<T>& a);
static int aa;
private:
T m_age;
};
template<class T> int Animal<T>::aa = 0;
类模板中初始化静态变量跟也差不多,但是有一个问题是这个共享的变量aa,是跟那些类共享的。
其实我们类模板是一个具体类的一个模板,到具体使用的时候,还是需要定义一个具体类,这个具体类是不一样的,比如上面的类模板,我们可以定义Animal, Animal,这两种不同的类,当然还有其他的,这里就举两个例子,这两个类都是具体的类,所以静态变量aa,是Animal
T&& 是对右值取引用。
说到类型转换,c语言的类型转换是强制的,也最简单(type)变量,这样就可以强制转换。这样子存在很大的问题,不明显,并且没有类型检查,直接可以转,c++为了克服这些缺点,引进了4个新的类型转换操作符。
c++提供了四种类型转换,分别适用于其他场景 | |
---|---|
static_cast | 用于内置的数据类型转换。(具体也不清楚) |
dynamic_cast | 子类和父类之间的多态类型转换。(转换之前做类型检查) |
const_cast | 添加或去掉const属性转换 |
reinterpreter_cast | 强制类型转换。(想怎么转就怎么转) |
用法也简单:
类型转换操作符 <目标类型> 变量
一般情况下,不建议做类型转换。