一、本地相关环境配置
1、vscode配置
(1)安装和相关插件安装
(6)调试
1、编译设置(修改task.json文件中编译器的设置,以支持编译C++代码)
2.2 使用tuple_index(tu, i);的方法获取元组tu中的第i个元素,该函数时是要自己写的
2.2.1 模板参数固定类型,类似template<int n, typename T0, typename... Ts>---n即为非模板参数
一、本地相关环境配置
1、vscode配置
(1) 安装vscode编辑器
下载速度可能很慢,解决方法如下:
在浏览器或者下载软件中就可以看到这么一个下载地址了,将其复制下来(如下图箭头所指)。
然后将红框内的部分更换为如下内容:
vscode.cdn.azure.cn
下载速度就会很快了
(2)安装gcc编译器
编译工具我们选用gcc(全称GNU Compiler Collection 意思是GNU编译器套件),不过不是原版的gcc,而是它在Windows下的特制版MinGW(全称Minimalist GNU on Windows)。它实际上是将GCC 移植到了 Windows 平台下,并且包含了 Win32API ,因此可以将源代码编译为可在 Windows 中运行的可执行程序。而且还可以使用一些 Windows 不具备的,Linux平台下的开发工具。MinGW又分为MinGW-w64 与 MinGW ,区别在于 MinGW 只能编译生成32位可执行程序,而 MinGW-w64 则可以编译生成 64位 或 32位 可执行程序。MinGW 现已被 MinGW-w64 所取代,且 MinGW 也已停止了更新。
下载地址:
https://sourceforge.net/projects/mingw-w64/files/
下载下面的这个:
下载下来后是一个压缩文件,将它解压缩(解压缩软件推荐Bandizip)得到mingw64文件夹,然后把它拖动到一个合适的位置(或者直接解压缩到这个位置),地址中不要有中文,推荐C:Program Files
压缩完后可以去如下路径下查看:
C:Program Filesmingw64in
里面有很多后缀名是.exe 的可执行程序,这些就是开发时所需的工具,如:gcc.exe 是C语言程序的编译器,g++.exe 是C++语言的编译器,gdb.exe 是用来调试程序的 debug 工具。
将该路径添加到用户环境变量(注意:不是系统环境变量),方法如下:
我的电脑右击->属性
环境变量是 Windows 系统中用来指定运行环境的一些参数,它包含了关于系统及当前登录用户的环境信息字符串。当用户运行某些程序时,系统除了会在当前文件夹中寻找某些文件外,还会到环境参数的默认路径中去查找程序运行时所需要的系统文件。
输入:gcc --version验证是否安装成功,如下则说明安装成功
(3)vscode和gcc的关联?
现在思考一个问题,我们搭的这套环境中编辑器选的是vscode,但理论上任何能处理文本的编辑器都能用来写代码,比如Windows自带的记事本,你可以在桌面新建一个txt文件,命名为hello,然后用记事本写个helloworld程序进去,再把这个文件后缀改成.c,这就是一个源代码文件了,我们该如何对它进行编译运行呢?答案是通过命令行,我们已经安装了编译器套装并把它添加进了环境变量,现在可以使用gcc命令了,但并不是在cmd中执行命令行,而是在vscode中执行命令行。
一般的,我们写一个hello.c文件,然后在cmd中执行gcc -o hello hello.c生成hello.exe可执行文件,然后再在命令行中输入hello.exe
运行程序 。
这样每次都用命令行太麻烦了,我们希望用更快捷的方式执行这一过程,但记事本不是专门给你写代码的,它不能提供这样的配置,但是vscode就不一样了,专门写代码的编辑器当然有专门的方式让你快捷地编译运行。这是通过.vscode文件夹下的json配置文件实现的。
这些json文件怎么写是由vscode开发团队规定的(感兴趣可以去看官方的文档),其中一个是tasks.json,task是任务的意思,我们的编译和运行就是我们想要vscode执行的任务,为此我们要在tasks.json里写两个task:Build
和Run
(这里为什么不是Compile
呢?是因为从源码到可执行的过程中不仅是编译(Compile),还有预编译、链接等过程,用构建(Build)来表述更合适)。除了编译和运行,我们还需要进行调试(Debug),这个就不是通过task来实现的了,而是通过launch.json
文件来实现。
汉化:
C/C++插件,这是对语言的支持插件
后重启vscode
(5)创建相关文件
在E盘创建一个text文件夹,后用vscode打开,创建.vscode文件夹,并在.vscode文件夹中创建tasks.json
和launch.json两个文件
如下:
其中两个json文件内容如下:

{
"version": "2.0.0",
"tasks": [
{//这个大括号里是‘构建(build)’任务
"label": "build", //任务名称,可以更改,不过不建议改
"type": "shell", //任务类型,process是vsc把预定义变量和转义解析后直接全部传给command;shell相当于先打开shell再输入命令,所以args还会经过shell再解析一遍
"command": "gcc", //编译命令,这里是gcc,编译c++的话换成g++
"args": [ //方括号里是传给gcc命令的一系列参数,用于实现一些功能
"${file}", //指定要编译的是当前文件
"-o", //指定输出文件的路径和名称
"${fileDirname}\bin\${fileBasenameNoExtension}.exe", //承接上一步的-o,让可执行文件输出到源码文件所在的文件夹下的bin文件夹内,并且让它的名字和源码文件相同
"-g", //生成和调试有关的信息
"-Wall", // 开启额外警告
"-static-libgcc", // 静态链接libgcc
"-fexec-charset=GBK", // 生成的程序使用GBK编码,不加这一条会导致Win下输出中文乱码
"-std=c11", // 语言标准,可根据自己的需要进行修改,写c++要换成c++的语言标准,比如c++11
],
"group": { //group表示‘组’,我们可以有很多的task,然后把他们放在一个‘组’里
"kind": "build",//表示这一组任务类型是构建
"isDefault": true//表示这个任务是当前这组任务中的默认任务
},
"presentation": { //执行这个任务时的一些其他设定
"echo": true,//表示在执行任务时在终端要有输出
"reveal": "always", //执行任务时是否跳转到终端面板,可以为always,silent,never
"focus": false, //设为true后可以使执行task时焦点聚集在终端,但对编译来说,设为true没有意义,因为运行的时候才涉及到输入
"panel": "new" //每次执行这个task时都新建一个终端面板,也可以设置为shared,共用一个面板,不过那样会出现‘任务将被终端重用’的提示,比较烦人
},
"problemMatcher": "$gcc" //捕捉编译时编译器在终端里显示的报错信息,将其显示在vscode的‘问题’面板里
},
{//这个大括号里是‘运行(run)’任务,一些设置与上面的构建任务性质相同
"label": "run",
"type": "shell",
"dependsOn": "build", //任务依赖,因为要运行必须先构建,所以执行这个任务前必须先执行build任务,
"command": "${fileDirname}\bin\${fileBasenameNoExtension}.exe", //执行exe文件,只需要指定这个exe文件在哪里就好
"group": {
"kind": "test", //这一组是‘测试’组,将run任务放在test组里方便我们用快捷键执行
"isDefault": true
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": true, //这个就设置为true了,运行任务后将焦点聚集到终端,方便进行输入
"panel": "new"
}
}
]
}
launch.json

{
"version": "0.2.0",
"configurations": [
{//这个大括号里是我们的‘调试(Debug)’配置
"name": "Debug", // 配置名称
"type": "cppdbg", // 配置类型,cppdbg对应cpptools提供的调试功能;可以认为此处只能是cppdbg
"request": "launch", // 请求配置类型,可以为launch(启动)或attach(附加)
"program": "${fileDirname}\bin\${fileBasenameNoExtension}.exe", // 将要进行调试的程序的路径
"args": [], // 程序调试时传递给程序的命令行参数,这里设为空即可
"stopAtEntry": false, // 设为true时程序将暂停在程序入口处,相当于在main上打断点
"cwd": "${fileDirname}", // 调试程序时的工作目录,此处为源码文件所在目录
"environment": [], // 环境变量,这里设为空即可
"externalConsole": false, // 为true时使用单独的cmd窗口,跳出小黑框;设为false则是用vscode的内置终端,建议用内置终端
"internalConsoleOptions": "neverOpen", // 如果不设为neverOpen,调试时会跳到“调试控制台”选项卡,新手调试用不到
"MIMode": "gdb", // 指定连接的调试器,gdb是minGW中的调试程序
"miDebuggerPath": "C:\Program Files\mingw64\bin\gdb.exe", // 指定调试器所在路径,如果你的minGW装在别的地方,则要改成你自己的路径,注意间隔是\
"preLaunchTask": "build" // 调试开始前执行的任务,我们在调试前要编译构建。与tasks.json的label相对应,名字要一样
}]
}
然后创建如下文件:
编译(构建),用快捷键ctrl+shift+B,
你会发现终端面板打开了,显示如下:
注意必须要有bin文件夹,否则编译不通过
运行前,需要设置一个快捷键,设置方法如下:
点击左下角小齿轮->键盘快捷方式->搜索任务
->找到运行测试任务
,点击左侧加号添加键绑定,这里我们设为Ctrl+Shift+R,
按下Ctrl+Shift+R,貌似会重新编译,然后运行,效果如下:
(6)调试
要将上面program对应的路径修改如下:
否则会报错如下:
找不到要调试的可执行文件的名字
如 当前打开的是test文件夹,当前的打开的是main.c,并有test / first / second / main.c
那么此变量代表的是 first / second / main.c
${fileBasename} 当前打开的文件名+后缀名,不包括路径
${fileBasenameNoExtension} 当前打开的文件的文件名,不包括路径和后缀名
${fileDirname} 当前打开的文件所在的绝对路径,不包括文件名
${fileExtname} 当前打开的文件的后缀名
${cwd} the task runner's current working directory on startup
以上,参考博客:https://zhuanlan.zhihu.com/p/147366852
二、modern-cpp(C++11、13、14、17)
1、编译设置(修改task.json文件中编译器的设置,以支持编译C++代码)
使用的编译器:
gcc 8.1.0
1、找不到iostream头文件
文件名后缀必须时.cpp,如果是.c文件使用gcc8.1.0会报错找不到iostream头文件
2、cc1plus.exe: warning: command line option '-std=c11' is valid for C/ObjC but not for C++
task.json中要修改编译命令为g++
备注:一般的,gcc是用来编译c代码的,如果要用gcc去编译c++代码,得手动链接上C++的库,如要使用gcc去编译hello.cpp要使用如下命令:
gcc -l stdc++ hello.cpp
但是如果使用g++去编译C++代码就不会出现上面的问题了
使用的task.json如下:

{ "version": "2.0.0", "tasks": [ {//这个大括号里是‘构建(build)’任务 "label": "build", //任务名称,可以更改,不过不建议改 "type": "shell", //任务类型,process是vsc把预定义变量和转义解析后直接全部传给command;shell相当于先打开shell再输入命令,所以args还会经过shell再解析一遍 "command": "g++", //编译命令,这里是gcc,编译c++的话换成g++ "args": [ //方括号里是传给gcc命令的一系列参数,用于实现一些功能 "${file}", //指定要编译的是当前文件 "-o", //指定输出文件的路径和名称 "${fileDirname}\bin\${fileBasenameNoExtension}.exe", //承接上一步的-o,让可执行文件输出到源码文件所在的文件夹下的bin文件夹内,并且让它的名字和源码文件相同 "-g", //生成和调试有关的信息 "-Wall", // 开启额外警告 "-static-libgcc", // 静态链接libgcc "-fexec-charset=GBK", // 生成的程序使用GBK编码,不加这一条会导致Win下输出中文乱码 "-std=c++17", // 语言标准,可根据自己的需要进行修改,写c++要换成c++的语言标准,比如c++11 ], "group": { //group表示‘组’,我们可以有很多的task,然后把他们放在一个‘组’里 "kind": "build",//表示这一组任务类型是构建 "isDefault": true//表示这个任务是当前这组任务中的默认任务 }, "presentation": { //执行这个任务时的一些其他设定 "echo": true,//表示在执行任务时在终端要有输出 "reveal": "always", //执行任务时是否跳转到终端面板,可以为always,silent,never "focus": false, //设为true后可以使执行task时焦点聚集在终端,但对编译来说,设为true没有意义,因为运行的时候才涉及到输入 "panel": "new" //每次执行这个task时都新建一个终端面板,也可以设置为shared,共用一个面板,不过那样会出现‘任务将被终端重用’的提示,比较烦人 }, "problemMatcher": "$gcc" //捕捉编译时编译器在终端里显示的报错信息,将其显示在vscode的‘问题’面板里 }, {//这个大括号里是‘运行(run)’任务,一些设置与上面的构建任务性质相同 "label": "run", "type": "shell", "dependsOn": "build", //任务依赖,因为要运行必须先构建,所以执行这个任务前必须先执行build任务, "command": "${fileDirname}\bin\${fileBasenameNoExtension}.exe", //执行exe文件,只需要指定这个exe文件在哪里就好 "group": { "kind": "test", //这一组是‘测试’组,将run任务放在test组里方便我们用快捷键执行 "isDefault": true }, "presentation": { "echo": true, "reveal": "always", "focus": true, //这个就设置为true了,运行任务后将焦点聚集到终端,方便进行输入 "panel": "new" } } ] }
其余两个json文件不变
2、元组(tuple)
2.1 元组的基本概念和使用方法

1 #include<iostream> 2 #include<tuple> 3 using namespace std; 4 5 tuple<int,double,string> f() 6 { 7 return make_tuple(1, 2.0, "hello"); 8 } 9 int main() 10 { 11 auto [x,y,z] = f(); 12 13 cout<< x <<","<< y <<"," <<"z" <<endl; 14 15 return 0; 16 }
创建一个远足的方法:
tuple<int,double,string> t = tuple<int,double,string>(1,2.0,"hello"); //创建一个tuple元组t
可以使用auto去定义元组t
auto t = tuple<int,double,string>(1,2.0,"hello"); //创建一个tuple元组t
那么如何获取元组t中的值呢?
1、使用std::get<变量类型>(元组变量名)方法

1 #include<iostream> 2 #include<tuple> 3 using namespace std; 4 5 int main() 6 { 7 auto t = tuple<int,double,string>(1,2.0,"hello"); //创建一个tuple元组t 8 9 cout << get<string>(t) << endl; 10 cout << get<double>(t) << endl; //get方法在名称空间std中 11 cout << get<int>(t) << endl; 12 13 return 0; 14 }
2、使用tie拆包方法

1 #include<iostream> 2 #include<tuple> 3 using namespace std; 4 5 int main() 6 { 7 int t1 = 0; 8 double d1 = 0.0; 9 string s1 = ""; //必须先定义tie中使用的变量 10 11 tie(t1, d1 ,s1) = tuple<int,double,string>(1,2.0,"hello"); //创建一个tuple元组t 12 13 cout << t1 << endl; 14 cout << d1 << endl; //get方法在名称空间std中 15 cout << s1 << endl; 16 17 return 0; 18 }
3、使用make_tuple方法创建元组,get<元组内元素索引>或tie拆包方法获取元组中的值

1 #include<iostream> 2 #include<tuple> 3 using namespace std; 4 5 int main() 6 { 7 /* 1、使用make_tuple创建元组,tie拆包方法获取元组内的值 */ 8 int t1 = 0; 9 double d1 = 0.0; 10 string s1 = ""; //必须先定义tie中使用的变量 11 12 tie(t1, d1 ,s1) = make_tuple(1,2.0,"hello"); //使用make_tuple就可以不用写元组内元素的类型 13 14 cout << t1 << endl; 15 cout << d1 << endl; //get方法在名称空间std中 16 cout << s1 << endl; 17 18 /* 2、使用tuple创建元组,get方法获取元组内元素的值 */ 19 auto t = tuple<string,int,double>("I'm tiple", 111, 1.0); 20 cout << get<string>(t) << endl; //获取元组t内类型为string元素的值,C++14支持,必须使用tuple创建元组 21 cout << get<int>(t) << endl; //获取元组t内类型为double元素的值,C++14支持 22 cout << get<double>(t) << endl; //获取元组t内类型为int元素的值,C++14支持 23 24 /* 3、使用make_tuple创建元组,get<index>(元组名字)方法获取元组内元素的值 */ 25 auto t2 = make_tuple("world", 12, "hello"); 26 cout << get<0>(t2) <<"," << get<1>(t2) << "," << get<2>(t2) << endl; //使用get<index>(元组名字)方法获取元组元素的值 27 28 return 0; 29 }
运行结果:
2.2 使用tuple_index(tu, i);的方法获取元组tu中的第i个元素,该函数时是要自己写的
2.2.1 模板参数固定类型,类似template<int n, typename T0, typename... Ts>
如下函数模板:
1 template<int n = 0, typename T0, typename... Ts> 2 void my_print(T0 value, Ts... args) 3 { 4 //在该函数体内即可使用变量n 5 } 6 7 //使用方法如下: 8 my_print("hello", 122,12.2); //不指定模板参数n的值,此时将n将使用默认值0,此外“hello”传递给调用参数value, 122, 12.2传递给调用参数包args 9 my_print<13>("hello", 122,12.2); //将13传给模板参数中的n,“hello”传递给调用参数value, 122, 12.2传递给调用参数包args
其中n的类型也可以使用auto,即如下:
1 template<auto n, typename T0, typename... Ts> 2 void my_print(T0 value, Ts... args) 3 { 4 //在该函数体内即可使用变量n 5 }
此时n没有使用默认值,因为n的类型时不定的(auto)不可以指定默认值。
用例:

1 #include <iostream> 2 3 template<int n = 0, typename T0, typename... Ts> 4 void my_print(T0 value, Ts... args) 5 { 6 std::cout << n << ","; //从模板参数中传递而来 7 std::cout << value << std::endl; 8 if constexpr (sizeof...(args) > 0) 9 { 10 my_print(args...); //由于这里没有指定模板参数n的值,故下一次调用的时候就会使用n的默认值0 11 } 12 } 13 14 int main() 15 { 16 my_print<1228>("hello","12.2"); 17 18 return 0; 19 }
执行结果:
1228,hello
0,12.2
2.2.2 std::in_place_index
2.2.3 std::variant
3、变长参数函数
2.3.1 sizeof...()计算变长参数的个数
变长参数函数的形式和使用sizeof...()计算变长参数的个数

1 #include<iostream> 2 using namespace std; 3 4 //变长参数函数 5 template<typename... Ts> 6 void magic(Ts... args) 7 { 8 cout << sizeof...(args) << endl; //使用sizeof...来计算变长参数的个数 9 } 10 11 int main() 12 { 13 magic(21); //打印1 14 magic(11,22); //打印2 15 magic(11,2.0,"hello world"); //打印3 16 17 return 0; 18 }
运行结果:
使用的编译命令:
g++ e: extCPPsrcmain.cpp -o e: extCPPsrcinmain.exe -g -Wall -static-libgcc -fexec-charset=GBK -std=c++2a
2.3.2 递归法展开变长参数

1 #include<iostream> 2 using namespace std; 3 4 //普通模板函数 5 template<typename T0> 6 void magic(T0 arg) 7 { 8 cout << arg << endl; 9 } 10 11 //变长参数函数 12 template<typename T0, typename... Ts> 13 void magic(T0 t, Ts... args) 14 { 15 cout << t << ","; 16 magic(args...); //需要特别注意的时这里要写agrs...只写agrs会报错 17 } //等到args只包含一个元素时,执行普通模板函数 18 19 int main() 20 { 21 magic(11,2.1,"hello world"); //打印3 22 23 return 0; 24 } 25 26 /* 27 第一次运行magic(11,2.1,"hello world");会执行变长参数magic,将11赋值给t,将2.1,"hello world"赋值给args... 28 后打印t 29 后执行magic(2.1,"hello world");,执行变长参数magic,将2.1赋值给t,将"hello world"赋值给args... 30 后打印t 31 后执行magic("hello world");此时会执行普通模板函数magic(T0 arg),将"hello world"赋值给arg并打印 32 33 */
运行结果
使用的编译命令:
g++ e: extCPPsrcmain.cpp -o e: extCPPsrcinmain.exe -g -Wall -static-libgcc -fexec-charset=GBK -std=c++2a
扩展:

1 #include<iostream> 2 using namespace std; 3 4 //普通模板函数 5 template<typename T0> 6 void magic(T0 arg) 7 { 8 cout << arg << endl; 9 } 10 11 //变长参数函数 12 template<typename T0, typename... Ts> 13 void magic(T0 t, Ts... args) 14 { 15 cout << t << ","; 16 magic(args...); //需要特别注意的时这里要写agrs...只写agrs会报错 17 } //等到args只包含一个元素时,执行普通模板函数 18 19 int main() 20 { 21 magic(22); //直接执行普通模板函数magic(T0 arg) 22 magic(11,2.1,"hello world"); //打印3 23 24 return 0; 25 }
2.3.3 变参模板展开--C++17支持
上面的递归法展开函数模板的缺点是必须提供一个终止函数,那么在C++17中增加了变参模板展开的支持,于是你可以在一个函数中完成 magic函数编写:

1 #include<iostream> 2 using namespace std; 3 4 template<typename T0, typename... Ts> 5 void magic(T0 t, Ts... args) 6 { 7 cout << t << ","; 8 if constexpr (sizeof...(args) > 0) 9 { 10 magic(args...); 11 } 12 } 13 14 int main() 15 { 16 magic(22); //直接调用magic(T0 t, Ts... args),将T0为int型、22赋值给t,args内参数为空 17 magic(1.1, 22, "Hello world"); //将T0定义为double、1.1赋值给t;22,"Hello world"赋值给args 18 19 return 0; 20 }
需要注意的是:对于变参函数模板,
1 template<typename T0, typename... Ts> 2 void magic(T0 t, Ts... args) //args可以接收多个参数,也可以接收0个参数,如调用magic(11)的时候,args接收到的参数个数即为0 3 { 4 //... 5 }
n、关键字 m2n
n1、constexpr
C++11 提供了 constexpr 让用户显式的声明函数、变量或对象构造函数在编译期会成为常量表达式,constexpr定义的变量对应的值不会改变,并且在编译过程就能得到计算结果的表达式,即constexpr定义的不是一个变量,而是一个(常量)表达式;而const定义的是一个变量,只不过编译器不允许去修改const变量的值。
常量表达式的优点是将计算过程转移到编译时期,那么运行期就不再需要计算了,程序性能也就提升了。
如下使用变量去定一个数是非法的,而使用宏定义去定义一个常量,即合法
1 int len = 10; 2 char arr[len]; //非法 3 4 #define LEN 10 5 char arr[LEN]; //合法
那么可以使用constexpr使len成为一个常量表达式,进而可以去定义数组,
1 constexpr int len = 10; //此时,此时len是一个常量表达式 2 char arr[len]; //合法
参考博客:地址
n2、if constexpr
1 #include<iostream> 2 using namespace std; 3 4 //普通模板函数 5 template<typename T0> 6 auto magic(T0 arg) 7 { 8 if constexpr (is_integral<T>::value) //如果T是int,is_integral<T>::value值为true 9 { 10 return arg + 1; 11 } 12 else 13 { 14 return arg + 0.01; 15 } 16 } 17 18 int main() 19 { 20 magic(22); 21 magic(1.1); 22 23 return 0; 24 }
在编译后,代码将会变成如下形式:
1 #include<iostream> 2 using namespace std; 3 4 int magic(int t) 5 { 6 return t + 1; 7 } 8 9 double magic(double t) 10 { 11 return t + 0.01; 12 } 13 14 int main() 15 { 16 magic(22); 17 magic(1.1); 18 19 return 0; 20 }
上面代码可以正常运行,不会报错,即 int magic(int t)和 double magic(double t)构成了重载的关系
三、《C++ templates》第二版
3.1 第一章函数模板
3.1.1函数模板定义和模板类型推断
函数模板的定义

1 #include<iostream> 2 using namespace std; 3 4 template<typename T> 5 T max(const T & a, const T & b) 6 { 7 return a > b ? a : b; 8 } 9 10 int main() 11 { 12 ::max(11,22); //合法,T被推断为int; 加上作用域限制符::,即表示使用自己定义的max,否则会使用std::max 13 ::max(1.5, 2.3); //合法,T被推断为double 14 15 //::max(11,25.1); //非法,T要被推断为int还是double,即出现了歧义 16 17 ::max<double>(11, 25.1); //合法,此时显式的指出T的类型为double,则int类型的11将会被转换为double类型传给a 18 19 ::max(static_cast<double>(11), 25.1); //合法,使用static_cast()函数将int类型的11强制转换为double类型 20 21 return 0; 22 }
类型推断
对于如下模板函数伪代码,ParamType不一定就是T,因为ParamType可能是T经过修饰符修改后得到的,如ParamType可能是 const T &。根据ParamType是指针(引用)、一般形式进行讨论
1 template<typename T> 2 void f(ParamType param); 3 f(expr);
1、ParamType是指针(引用)
- 如果expr的类型是引用,忽略引用部分。
- 然后将expr的类型同ParamType进行模式匹配来最终决定T。
1 template<typename T> 2 void f(T ¶m); 3 4 int x = 27; //x为int 5 const int cx = x; //cx为const int 6 const int & rx = x; //rx为指向const int额引用 7 8 f(x); //T被推断为int,param的类型被推断为 int & 9 f(cx); //T被推断为const int,param的类型被推断为const int & 10 f(rx); //T被推断为const int(这里的引用会忽略),param的类型被推断为const int &
参考博客:地址
3.1.2当函数模板有多个调用参数时,返回类型如何确定?
在此之前,需要明确两个概念:模板参数和调用参数
模板参数,定义在函数模板前面的尖括号里:
template<typename T> // T 是模板参数
调用参数,定义在函数模板名称后面的圆括号里:
T max (T a, T b) // a 和 b 是调用参数
考虑如下模板函数
1 template<typename T1, typename T2> 2 ?max(T1 a, T2 b) 3 { 4 return a > b ? a : b; 5 }
该模板函数即支持不同实参类型,如下
1 ::max(11, 22.5); //返回值类型为double 2 ::max(11, 10.5); //返回类型为int
但是返回值类型是T1还是T2呢?因此可以引入第三个模板参数RT作为返回值类型
1 template<typename T1, typename T2,typename RT> 2 RT max(T1 a, T2 b) 3 { 4 return a > b ? a : b; 5 } 6 7 //调用需要显式的指出所有模板参数类型 8 ::max<int, double, double>(11, 22.5); //显式定义T1为int、T2为double、RT为double 9 ::max<int, double, int>(11, 10.5); //显式定义T1为int、T2为double、RT为int
但是也可以将模板参数RT放在最前,只显式的指出模板参数RT的类型,其他调用参数T1和T2根据传入的参数做类型推断即可,如下:
1 template<typename RT, typename T1, typename T2> 2 RT max(T1 a, T2 b) 3 { 4 return a > b ? a : b; 5 } 6 7 ::max<double>(11, 22.5); //RT的类型被显式定义为double,T1和T2根据传入参数做推断 8 ::max<int>(11, 10.5); //RT的类型被显式定义为int,T1和T2根据传入参数做推断
3.1.3 返回类型判断之尾返回类型--C++11支持
使用C++11中支持的尾返回类型来判断返回值的类型
1 #include<iostream> 2 using namespace std; 3 4 template<typename T1, typename T2> 5 auto max(T1 v1, T2 v2)->decltype(v1 > v2 ? v1 : v2) 6 { 7 return v1 > v2 ? v1 : v2; 8 } 9 10 int main() 11 { 12 cout << ::max(11,22) << endl; 13 cout << ::max(2.1, 3.2) <<endl; 14 cout << ::max("abc", "bbb") <<endl; 15 16 return 0; 17 }
此外,在C++14中支持将" ->decltype(v1 > v2 ? v1 : v2) "去掉,即下面也是可以的
1 #include<iostream> 2 using namespace std; 3 4 template<typename T1, typename T2> 5 auto max(T1 v1, T2 v2) 6 { 7 return v1 > v2 ? v1 : v2; 8 } 9 10 int main() 11 { 12 cout << ::max(11,22) << endl; 13 cout << ::max(2.1, 3.2) <<endl; 14 cout << ::max("abc", "bbb") <<endl; 15 16 return 0; 17 }
执行结果:
22 3.2 abc
3.1.4 普通函数和模板函数构成重载
当 普通函数和模板函数构成重载,且参数类型二者均满足时,普通函数的优先级高于模板函数
1 #include<iostream> 2 using namespace std; 3 4 int max(int a, int b) 5 { 6 cout << "(" << a << "," << b << ")" << " ,non temlate fun is called "; 7 return a > b ? a : b; 8 } 9 10 template<typename T1, typename T2> 11 auto max(T1 v1, T2 v2) //使用了C++14中的返回值类型判断方法 12 { 13 cout << "(" << v1 <<"," <<v2 << ")" <<" ,template func is called "; 14 return v1 > v2 ? v1 : v2; 15 } 16 17 int main() 18 { 19 ::max(11,22); //模板函数和非末班函数都符合,但非模板函数优先级大于模板函数,故调用非模板函数 20 ::max(2.1, 3.2); //非模板函数参数类型不合适,故调用的是模板函数 21 ::max<>(11,22); //显式的指定了模板列表(虽然是空的),但会调用模板函数 22 ::max<double, double>(11,22); //显式的制定了参数类型,且11和22会被转换为double类型 23 ::max(2.0, 3.0); //调用模板函数,T会被实例化为double类型 24 ::max('a', 'b'); //调用模板函数,T会被实例化为char 25 ::max("abc", "bcd"); //调用模板函数,T会被实例化为string 26 cout << ::max('a', 11) <<endl; //由于模板函数不允许,由执行结果可见,调用的是模板函数,将T实例化为了char 27 28 29 return 0; 30 }
执行结果:
(11,22) ,non temlate fun is called (2.1,3.2) ,template func is called (11,22) ,template func is called (11,22) ,template func is called (2,3) ,template func is called (a,b) ,template func is called (abc,bcd) ,template func is called (a,11) ,template func is called 97