C/C++|c++学习笔记(六、模板和类型转换)

现在进入了c++高级课程了,前面5节是c++的语法基础,从现在开始就是c++提高篇了。
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
6.1 函数模板
我们可以用函数模板来定义函数:

//模板技术,类型参数化,编写代码可以忽略类型。 template//这一个是函数模板的关键字,为了区别函数模板和普通函数 void MySwap(T &a, T &b) { T temp = a; a = b; b = temp; } //也可以吧class写成typename template 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(a, b); printf("a b %d %d\n", a,b); return 0; }

函数模板和普通函数在一起调用规则:
  1. 函数模板可以像普通函数那样可以被重载
  2. c++编译器优先考虑普通函数(如果普通函数满足的话)
  3. 如果函数模板可以产生一个更好的匹配,那么选择模板
  4. 可以通过空模板实参列表的语法限定编译器只能通过模板匹配(不是很懂)
函数模板和普通函数的区别:
  1. 函数模板不允许自动类型转化
  2. 普通函数能够自动类型转化
强制调用模板 用MySwap<>(a, b);
函数模板机制结论:
  1. 编译器并不是把函数模板处理成能够处理任何类型的函数
  2. 函数模板通过具体类型产生不同函数
  3. 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
6.2 类模板
6.2.1 类模板 正如我们上面定义的函数模板一样,类也是有模板的,用法跟函数模板差不多:
template class Stack { private: vector 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
我觉得菜鸟教程 举的例子很不错,实现了一个栈,栈的元素确实不确定,挺好的,我这里就不写了,写了也是抄过来。
6.2.2 类模板和继承
template class Animal { public: T m_age; private: }; //有两种继承的方式 //1.子类也是类模板 template class cat : public Animal { public: private: }; //2.可以实例化的类,把实际类型替换掉T class cat1 : public Animal { public: private: }; //使用方法 cat c;

6.2.3 类模板类外实现 类模版在类内实现,这个比较简单,把例子发出来就可以了:
template 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 cat : public Animal { public: cat() : Animal(10) {}private: };

类内部实现,可以直接使用,比较简单,但是c++支持多文件,所以有时候也需要在类外部实现。
实现一个类模板类外实现:
//类的声明 template class Animal { public: Animal(T age); void show(); private: T m_age; }; template class cat : public Animal { public: cat(); private: }; //类的实现 template//函数上面添加模板声明 Animal::Animal(T age){//类名后面也要加 this->m_age = age; }template void Animal::show(){ printf("Animal %d\n", m_age); }template cat::cat() : Animal(10) {}

【C/C++|c++学习笔记(六、模板和类型转换)】当类外实现的时候遇上了友元函数,那就更难受了:
template class Animal; template ostream& operator<<(ostream& os, Animal& a); template class Animal { public: Animal(T age); void show(); //重载<<操作符 friend ostream& operator<<(ostream& os, Animal& a); private: T m_age; }; template ostream& operator<<(ostream& os, Animal& a) { os << a.m_age << "年龄" << endl; return os; }

友元函数需要注意3个地方:
第一个首先要声明
第二个就是在类中声明的friend中需要在函数名后面加
第三个就是实现的时候,要跟其他的函数一样,要加template
需要更注意一点,类外实现,少用友元函数
6.2.4 多文件类模板外实现 想不到多文件实现这个类模板也这么难受
animal.h
#pragma once#include template class Animal; template void show_aa(Animal& a); template class Animal { public: Animal(T age); void show(); //重载<<操作符 friend void show_aa(Animal& a); private: T m_age; }; //有两种继承的方式 //1.子类也是类模板 template class cat : public Animal { public: cat(); private: };

animal.cpp
#include "animal.h"template Animal::Animal(T age){ this->m_age = age; }template void Animal::show(){ printf("Animal %d\n", m_age); }template cat::cat() : Animal(10) {}template void show_aa(Animal& 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中申请的对象屏蔽掉,就不会出现错误,如果打开,会出现以下错误:
C/C++|c++学习笔记(六、模板和类型转换)
文章图片

这个跟类模板两次编译有关,因为是多文件的时候,是每个文件自己单独编译成.o文件,然后在链接器链接每个.o,才会生成可执行文件。
问题就出在编译成.o的时候,因为animal.cpp是一个函数模板,这时候编译的话,是属于第一次编译,只是检查函数模板是否有语法问题,结果这个cpp没有语法没问题,所以编译通过,生成.o文件,接下来main.c这个直接调用了模板函数,但是这个文件也没有声明,所以main.c中会标注一些符号,代表着这个需要到链接的时候由链接器寻找,所以也没有问题,编译通过。
这时候到链接器,链接器就拿到main.c中的cat::cat(),去各个.o中寻找有没没对应的函数,这时候animal.o中只有原来的函数模板,没有具体编译成函数,因为函数模板是需要两次编译的,一次是调用的时候,所以这时就没有找到对应的函数,所以链接的时候报错。
有解决方法,就是把包含的头文件修改成cpp文件即可编译完成,这时候main.cpp中就有函数模板的声明和调用了,就可以完成二次编译了。
但是正是的工程中,不会这么使用的,而是把函数模板的声明和实现写在一个文件中,这个文件的后缀就是hpp.
怪不得当初不知道hpp是什么意思,现在终于知道了。
6.2.5 类模板碰到static成员
template class Animal { public: Animal(T age); void show(); //重载<<操作符 friend void show_aa(Animal& a); static int aa; private: T m_age; }; template int Animal::aa = 0;

类模板中初始化静态变量跟也差不多,但是有一个问题是这个共享的变量aa,是跟那些类共享的。
其实我们类模板是一个具体类的一个模板,到具体使用的时候,还是需要定义一个具体类,这个具体类是不一样的,比如上面的类模板,我们可以定义Animal, Animal,这两种不同的类,当然还有其他的,这里就举两个例子,这两个类都是具体的类,所以静态变量aa,是Animal这个家族类共有一个,Animal这个家族类也共有一个。
T&& 是对右值取引用。
6.3 类型转换
说到类型转换,c语言的类型转换是强制的,也最简单(type)变量,这样就可以强制转换。这样子存在很大的问题,不明显,并且没有类型检查,直接可以转,c++为了克服这些缺点,引进了4个新的类型转换操作符。
c++提供了四种类型转换,分别适用于其他场景
static_cast 用于内置的数据类型转换。(具体也不清楚)
dynamic_cast 子类和父类之间的多态类型转换。(转换之前做类型检查)
const_cast 添加或去掉const属性转换
reinterpreter_cast 强制类型转换。(想怎么转就怎么转)
用法也简单:
类型转换操作符 <目标类型> 变量
一般情况下,不建议做类型转换。

    推荐阅读