Unit01 模板起源
01 C++为静态类型语言
- 这种语言有很多的数据类型(基本类型,类类型),在效率和类型安全上是无可比拟的。
- 但这种语言在很大程度上也给程序员编写通用代码带来瓶颈。
- 使程序员不得不为每一种数据类型编写完全相同或近乎完全相同的代码实现,虽然他们在抽象层面是一致的。
#include <iostream>
using namespace std;
int max_int(int x, int y) {
return x > y ? x : y;
}
double max_double(double x, double y) {
return x > y ? x : y;
}
string max_string(string x, string y) {
return x > y ? x : y;
}
int main()
{
int nx = 10, ny = 10;
cout << max_int(nx, ny) << endl;
double dx = 12.3, dy = 45.6;
cout << max_double(dx, dy) << endl;
string sx = "world", sy = "hello";
cout << max_string(sx, sy) << endl;
return 0;
}
02 宏可拜托数据类型的限制
- 宏是在预处理阶段针对代码的纯文本替换。
- 宏本身没有函数的语义(不会对数据类型进行检查)
- 因此借助参数宏虽然可以摆脱类型约束和限制,但同时也丧失了对数据类型的检查。
#include <iostream>
using namespace std;
#define Max(x, y) (x > y ? x : y)
int main()
{
int nx = 10, ny = 10;
cout << Max(nx, ny) << endl; //(nx>ny?nx:ny)
double dx = 12.3, dy = 45.6;
cout << Max(dx, dy) << endl; //(dx>dy?dx:dy)
string sx = "world", sy = "hello";
cout << Max(sx, sy) << endl; //(sx>sy?sx:sy)
char cx[256] = "world", cy[256] = "hello";
cout << Max(cx, cy) << endl; //类型不安全 (cx>cy?cx:cy)
return 0;
}
03 利用宏构建通用函数框架
- 通过实例化宏,让预处理器将这个宏代换为针对不同数据类型的真正函数。
- 将宏的通用性和函数的类型安全性完美结合起来。
#include <iostream>
using namespace std;
#define MAX(T) T max_##T(T x, T y){\
return x > y ? x : y;\
}
MAX(int) //int max_int(int x, int y) {return x > y ? x : y;}
MAX(double) //double max_double(double x, double y) {return x > y ? x : y;}
MAX(string) //int max_string(string x, string y) {return x > y ? x : y;}
#define Max(T) max_##T
int main()
{
int nx = 10, ny = 10;
cout << Max(int)(nx, ny) << endl; //max_int(nx, ny);
double dx = 12.3, dy = 45.6;
cout << Max(double)(dx, dy) << endl; //max_double(dx, dy);
string sx = "world", sy = "hello";
cout << Max(string)(sx, sy) << endl; //max_string(sx, sy);
char cx[256] = "world", cy[256] = "hello";
cout << Max(string)(cx, cy) << endl; //max_string(cx, cy);
return 0;
}
Unit02 函数模板
01 函数模板的定义
-
函数模板的定义形式:
template<class 类型形参1, class 类型形参2, …>
返回值类型 函数模板名(调用形参1, 调用形参2, …)
{
…
}例如:
template<class T> T Max(T x, T y) { return x > y ? x : y; }
-
可以使用任何标识符作为类型形参的名称,但使用 “T” 已经成为一种惯例,“T” 表示的是,调用者在使用这个函数模板时指定的任意数据类型。
02 函数模板的使用
- 使用函数模板必须对函数模板进行实例化
- 形式:函数模板名<类型实参1, 类型实参2, …>(调用实参1, …)
例如:Max<int>(123, 456);
Max<double>(12.3, 45.6);
Max<string>("hello", "world");
#include <iostream>
using namespace std;
//函数模板
template<class T>
T Max(T x, T y)
{
return x > y ? x : y;
}
int main()
{
int nx = 10, ny = 10;
cout << Max<int>(nx, ny) << endl;
double dx = 12.3, dy = 45.6;
cout << Max<double>(dx, dy) << endl;
string sx = "world", sy = "hello";
cout << Max<string>(sx, sy) << endl;
char cx[256] = "world", cy[256] = "hello";
cout << Max<string>(cx, cy) << endl;
return 0;
}
03 函数模板的分析
- 编译器并不是把函数模板编译成一个可以处理任何数据类型的单一实体。
- 编译器在实例化函数模板时根据类型实参从函数模板中产生一个真正的函数实体。
- 函数模板并不是一个函数实体,通过实例化才能产生真正的函数实体。
- 函数模板可以看成是编译器生成函数实体的一个依据而已。
- 这种用具体数据类型替换函数模板类型形参的过程叫做实例化,这个过程将产生一个函数模板的实例(函数实体)。
- 只要使用函数模板,就会自动引发编译器的实例化过程,因此程序员不需要额外地请求对函数模板的实例化。
04 实例化函数模板的条件
- 原则上来说可以使用任何类型来实例化函数模板,不管其为基本类型还是类类型。
- 但前提是这个类型必须支持函数模板所有要执行的操作。
例如:一个不支持 “>” 操作符的类型来实例化Max函数模板,编译器将报错误。
Unit03 函数模板扩展
01 二次编译
- 编译器对于函数模板都会进行两次编译
- 第一次编译发生在实例化函数模板之前(产生真正函数实体之前)只检查函数模板本身内部代码,查看基本词法是否正确。
- 函数模板内部出现的所有标识符是否均有声明。
- 对于已知类型的调用要查看调用是否有效。
- 对于未知类型调用认为都合理。
- 第二次编译发生在实例化函数模板之后(产生真正函数实体之后)结合所使用的类型实参,再次检查模板代码,查看所有调用是否真的均有效。
02 隐式推断类型实参
-
如果函数模板的调用形参和类型形参相关。
例如:
template<class T>T Max(T x, T y){...}
-
那么在实例化函数模板时即使不显示指明函数模板的类型实参,编译器也有能力根据调用实参的类型隐式推断出正确的类型实参的类型。
例如:
Max(123, 456);
=>Max<int>(123, 456);
-
获得和调用普通函数一致的语法表现形式。
-
三种情况不能做隐式推断
-
调用参数 和 类型参数 不完全相关
例如:
template<class T, class D>T Max(T x, T y){}
-
隐式推断不支持隐式类型转换
例如:
template<class T>T Max(T x, T y){...}
使用时:
Max(123, 45.6);
-
返回值类型不支持隐式推断。
03 函数模板的重载
-
普通函数和可实例化出该函数的函数模板构成重载关系。
在数据类型匹配度相同情况下编译器有限选择普通函数。
除非函数模板可以产生具有更好的数据类型匹配度的实例。
-
函数模板的实例化不支持隐式类型转换但普通函数支持。
在传递参数时如果需要编译器做隐式类型转换,则编译器选择普通函数。
-
可以在实例化时用 <> 强行通知编译器选择函数模板。