函数模板高级用法
- 1.分文件编写的优点
- 2.普通函数的分文件编写
- 3.函数模板的分文件编写
- 4.细节提示
- 5.函数模板应用高级
- decltype推导类型
- 函数后置返回类型
- 6.总结
函数模板讲完后,C++全部的函数类型我们就接触的差不多了。今天给做一些关于函数份文件编写的知识点补充。
1.分文件编写的优点
在以往的学习中,我们将函数全部放在一个.cpp文件中,随着任务量的变化,我们的代码越写越多,功能也越写越乱。如果这是在实际生产中,后面需要维护代码也会变得非常困难。如果我们可以把所有的函数和功能分门别类,既方便根据需求找到函数,又能让主函数代码量减少很多,维护起来也就方便的多了。
那么如何实现这个功能呢?
(PS:本节内容以Visual Studio软件进行演示,如果大家用的是VScode,可以参考下节内容)
2.普通函数的分文件编写
对于普通函数而言,我们只需要在任务目录下创建一个.h后缀的头文件和一个同名的.cpp后缀的源文件,将函数声明和文件的引用放在头文件里,将函数的定义放在源文件里就可以了:
如果大家是在主界面新建的C++空项目,右边栏会这样显示:
我们在"头文件"中创建.h,在"源文件"中创建同名的.cpp即可。
然后再回到主函数(2.cpp)中引用头文件,就可以实现函数分文件了:
3.函数模板的分文件编写
文件模板是一个函数的描述,没有实体,如果我们还按普通函数的处理方式分文件编写,2.cpp文件将不能正常运行(大家可以自行尝试)。应当将函数通用模板整个放到头文件中避免这个问题,但是当函数模板存在具体化时,函数模板的具体化又应当与普通函数的处理方式相同:
运行2.cpp的结果为:
因为具体函数是不允许有通用类型参数的,所以具体函数和普通函数处理方法相同也就不足为奇了。
4.细节提示
1.在public.h文件中已经引用的内容,主函数引用了public.h的情况下就没必要再次引用
2.引用public.h可以使用绝对路径,即
# include"C:/c++/project1/public.h"
同理,使用这样的方式也可以引用其他位置的自定义头文件
5.函数模板应用高级
decltype推导类型
在之前的讲述中,我们已经知道了除了参数的类型以外,任何编写代码的过程中遇到的不确定类型的变量都可以使用auto去声明。但是这里我们给大家介绍另一种确定类型的方法:decltype。其语法如下
decltype(expression) var
意思是推导出表达式的类型并定义一个同类型变量var,且表达式的内容不会被执行。其规则为:
1.当表达式是一个标识符且不带括号时,var与表达式内容同类型:
int a=10;
decltype(a) var;
我们可以把鼠标移动到var上查看推导出来的类型:
2.如果表达式是函数(或其他什么)的调用,则var的类型与返回值相同(返回值不能是void类型否则会报错):
void* function() // 这里仅为证明void*类型作为返回值的函数可以作为decltype中的表达式。
// 但是没有return不正规,大家还是要规范编程
{
cout<<"这里是function函数"<<endl;
}
int main()
{
int a=10;
decltype(function()) var;
}
// 输出为:
运行文件终端什么都没有,这说明function函数没有被调用。
这个功能实现方式与auto就有所区别了,如果我们使用auto去定义函数指针:
auto *var=function();
var会被定义成指向function的函数指针,但是函数会被调用。当然auto也可以仅依靠函数名推理出类型:
int main()
{
auto var=function;
}
这样var就成了指向function的函数指针了,运行这段代码函数也不会被调用。
3.如果表达式是一个函数名或带有赋值性质的语句1或括号括起来的变量,var的类型是表达式的引用:
int main()
{
int a=10,b;
decltype(function) var;
decltype((a)) var2=a;
// decltype(a=1+3) var2=b;
}
这段代码中,var就是void* ()类型,var2是int类型的引用类型(这里不懂的小伙伴先往下看)。如果我们把var前面加上*,就是声明了一个函数指针var:
decltype(function) *var;
var=function;
var();
// 输出为:这里是function函数
引用就是联动多个变量,有点像python的列表2。观察下面代码,如果我们修改var2,那么var2联动变量的值也会随之修改:
int main()
{
int a=10,b=20;
decltype(++a) var2=b; // 因为++a实际上是表达式a=a+1,含有赋值语句,
// 因此这里的b是a(赋值语句的左值)类型引用的类型
// decltype(a=1+3) var2=b;
var2=30;
cout<<b<<" "<<a<<endl;
}
// 输出为:30 10
多数情况下,decltype声明的变量没必要直接定义,但如果推理出的类型是引用类型就必须在声明时定义了。事实上,即使我们用确定的类型名定义引用类型也需要在声明时定义才能通过编译:
int a=10;
int &ra=a;
4.如果上面情况都不符合,decltype推到结果是表达式类型:
int main()
{
int x=10,y=20;
decltype(3) var;
decltype(x+y) var2;
decltype(3+1) var3;
}
这里var、var2、var3都被推导为int类型。
函数后置返回类型
这里的内容在C++14标准后就没有学习的意义了,不感兴趣的小伙伴可以直接看最后的例子。
如果我们写的函数不知道返回值具体类型,该怎么办呢?C++允许在定义(和声明)函数后规定返回值数据类型,其示例如下:
int fun(int a,int b)->int
这个看起来没什么用,但如果不知道具体返回值类型就可以用到了:
template <typename T>
auto fun(T a,T b)->decltype(a+b)
这样就利用自动推导解决了返回值类型的问题了。然而之前我们解决这个问题是直接定义返回值类型为auto,看起来没必要这么麻烦。这是因为C++14标准对函数返回类型推导规则做了优化。下面我们再来规范的写一个可以实现交换两个变量并返回两变量和功能的函数模板:
template <typename T>
auto SWAP(T &a,T &b)->decltype(a+b) // 这样的判断结果不是引用类型
// 等同于 auto fun(T a,T b) 写法
{
decltype(a+b) tmp=a; // 亦可写作auto temp = a
// 但这样写更保险
a=b;
b=tmp;
return a+b;
}
int main()
{
string a="San",b="Zhang";
cout<<SWAP(a,b)<<"\n"<<a<<"\n"<<b<<endl;
}
// 输出为:ZhangSan
// Zhang
// San
6.总结
这节内容总算是告一段落了,我们重点讲解的是函数分文件编写和decltype标识推导未知类型。decltype方法虽然比auto复杂很多,但能实现的功能以及安全性上也有更好的表现效果,这两种方式可以说是各有千秋。后续我们一起继续学习类模板~
当表达式为带有赋值性质语句时,推断依赖于这个表达式左值的类型 ↩︎
在Python中,假有列表a=[1,2,3,4],后续有语句b=a,那么这句话就会被解读为b和a同时代表这个列表,而不是b单独复制了一份a的内容。通过b去修改列表内容和通过a去修改列表内容效果是完全一样的。 ↩︎