含有可变形参的函数
有的时候,我们传入的参数的个数是不确定的,这个时候就需要使用到省略符形参initializer_list。
initializer_list是标准库类型,在该类型中的每一个元素都是常量,省略符形参只能出现在形参列表中的最后一个位置。
例如
void print(initializer_list<int> li) // 正确,函数中只有一个形参
{
for(auto &i : li) // 由于省略符形参也是模板类型,支持范围for循环
{
cout<<i<<endl;
}
}
void print(initializer_list<int> li,int i) // 错误,省略符形参只能在形参列表的末尾
{
// 操作
}
initializer_list提供的操作
initializer lst; // 默认初始化,空列表
initializerlst{a,b,c,…} // 列表的大小和初始值一样多,并且列表中的值都是const类型
lst2(lst)
lst2 = lst // 这两个只拷贝对象,但是不会拷贝列表中的数值,原始列表和拷贝后的对象共享
lst.size()
lst.begin()
lst.end()
不要返回局部指针或者引用
因为局部变量在函数执行结束后就被释放了,返回后的指针或引用也被释放,再次调用则报错
返回数组指针
因为数组是不能被返回的,但是我们可以通过指针的形式来返回一个数组的指针。
using arrt = int[10]; // 等价于 typedef int arrt[10];
arrt* func(inti); // 表示返回一个含有10个整数的数组的指针
声明一个返回数组指针的函数
int arr[10];
int *p1[10]; // 表示p1含有10个整型指针的数组
int (*p)[10] = arr; // p2是一个指针,指向含有10个整数的数组
在阅读代码的时候我们从内往外读取:
如 int (*fun(int i ))[10];
fun(int i ); // 表示一个函数
(*fun(int i )); // 表示函数返回值可以解引用
(*fun(int i ))[10] // 表示该函数解引用后得到一个大小为10的数组
int (*fun(int i ))[10] // 表示解引用后该数组是整数
使用decltype
如果我们一开始就知道函数返回的指针指向哪一个数组,那么可以直接使用decltype来声明返回类型
int arr[]= {1,2,3,4,5};
int arr2[] = {5,6,7,8,9};
decltype(arr) * printf(int i) // 表示的是返回一个指向5个整数的数组,不一定说是返回arr,只是返回arr的类型
{
return (i % 2) ? arr : arr2;
}
尾置返回类型
如果我们要求的函数返回类型太长,我们可以使用 -> 来将返回类型后置,空出来的部分使用auto来弥补。
auto func(int i ) -> int (*)[10];
函数的重载
函数重载的注意事项:
- 函数重载的时候只能够形参列表不同,依据返回类型不同不能发生重载
- 函数重载的时候会寻找一个最佳的匹配,如果找不到不匹配则编译器会报错,并且在函数重载的时候要避免二义性
函数重载与作用域
在不同的作用域中不能够重载函数,因为内层作用域的函数声明会屏蔽掉外层的重载函数。
string read();
void print(int i ,int u);
void print(char a,char b);
void text()
{
void print(int i ); // 产生新的作用域,屏蔽了外层的重载函数
print(1,2); // 错误,因为欸外层的作用域被屏蔽了,所以只能调用print(int i )
bool read = false; // 新作用域,屏蔽外层read
string s = read(); // 错误,read是一个bool变量,不是函数
}
函数匹配
函数发生重载的之后,编译器会找到最佳的精确的重载函数执行。
每一个重载函数都可以称为候选函数,而与之精确匹配的函数称之为可选函数。
函数重载的选择:
- 不发生隐式转换的优先
- 数组转化成指针
- 含有顶层const或删除顶层const
雷区:
void man(long i); void man(float); man(3.14); // 错误,产生二义性
这两个在使用的时候,由于隐式转换等级相同,double既可以转换成float也可以转换成long 所以产生二义性
const传参
void look(const int i); // 标记为1
void look(int i); // 标记为2
const int a =1;
int b = 2;
look(a); // 执行1中的函数
look(b); // 执行2中的函数
在同时含有形参的区别是否为const的时候,如果传入的实参是const类型则执行含有const的形参的函数,否则优先执行没有const形参的函数。
但是只有const形参的时候,也可以传入非const类型的实参,只是将int转化成 const int
雷区:
函数重载的时候不能引发二义性,否则编译器报错
二义性的产生:传入的各各形参在不同的重载函数中都能够精确的匹配,这样子的话就产生了二义性int f(int i,int u); int f(double i,double u); // 声明两个重载函数 f(4,3.5); // 调用函数,但是引发错误
因为第一个参数是int类型与第一个 f 的形参精确匹配,第二个参数的类型是double,与第二个 f 的形参精确匹配,导致编译器不知道应该调用哪一个,所以导致二义性
默认实参
再写入默认实参的时候,默认部分后面的参数也要进行初始化
void print(int i = 0,int u) // 错误,因为u在i的后面,i初始化后,u也必须初始化
{
// 操作
}
void print(int u,int i = 0) // 正确
{
// 操作
}
tips:尽量将不需要默认参数的形参放在前面
内联函数和constexpr函数
当我们在调用函数的时候,在系统中发生了很多的步骤,如先保存数据到寄存器,之后在恢复,这个过程增加了系统的开销,为了减小系统的开销,我们常常使用内联函数。
使用inline关键字声明内联函数 比如:
inline void print(int i ,int u)
{
return (i<u) ? i : u;
}
void main()
{
cout<<print(1,2)<<endl;
}
如果调用该函数的时候,直接在编译器中展开成
cout<< (1<2) ? 1 : 2<<endl;
一般来说,内联函数用来优化规模较小,流程直接的函数。
constexper函数是返回一个表达式常量,要求在这个函数中返回值只能是一个字面值,以及只有一个返回值。
调试帮助
assert预处理宏
assert做为预处理宏,使用一个表达式做为他的条件。
asser(expr);
assert在使用的时候需要包含 cassert头文件,在执行的时候优先执行括号里面的表达式,如果为0则终止程序,如果非0,则不做任何处理。
#incldue <cassert>
int main(int argc,char **argv)
{
cout << "程序执行" << endl;
assert(0);
cout << "并没结束" << endl;
}
assert一般用于检验确实不可能发生的事
NDEBUG 预处理变量
assert依赖于NDEBUG的状态,如果NDEBUG被定义则不执行assert命令,一般默认情况下NDEBUG是未定义的。使用#define NDEBUG 来定义。
使用#ifndef和#endif 来检查NDEBUG是否被定义。
void print()
{
#ifndef NDEBUG
cerr<<__func__<<endl; // __func__ 返回的是当前执行的函数的名称-> print
#endif
}
这段代码主要是检测NDEBUG是否被定义,如果没有被定义,则输出函数的名称。
预处理器定义了5个比较常用的程序调式的名字
- _FILE_ 存放文件名的字符串的字面值
- _LINE_ 存放当前程序行号的整型字面值
- _TIME_ 存放文件编译时间的字符串的字面值
- __DATE__存放文件编译日期的字符串字面值
- __func__存放当前执行的函数名称
函数指针
函数指针指向的是函数,而不是具体的对象。
bool comparestring(string &s,string &s2); // 比较两个字符串的大小
创建一个函数指针
bool (*pf)(string&,string&); // 未初始化
/*pf旁边的两个括号不能少,如果少了括号,则表示声明了一个返回bool指针的函数*/
使用函数指针
bool b1= pf(“hello”,“goodbye”); // 调用函数
函数指针可以指向0,但是不能指向和自己不同的函数类型
int comparnum(int i ,int u);
pf = comparnum; // 错误,不同的数据类型
pf = 0; // 正确
函数指针形参
函数指针跟数组一样不能直接做为实参传递,但是能够以指针的形式进行传递
void print(striing s1,string s2,
bool pf(string&,string&)); // 最后一个参数是传递函数指针
为了方便写入,可以使用decltype + typedef的形式另起一个名字
typedef decltype(comparestring) func; // 函数类型
typedef decltype(comparestring) *func // 创建函数指针
void useexper(string &,stribg&,*func); // 传入函数指针
返回函数指针的指针
把函数指针当成自定义的类型进行返回即可
using F= int(int *,int ); // 创建别名,普通函数类型
using PF = int(*)(int*,int); // 指针函数
PF f(int ); // 正确,可以返回指针函数类型
F f(int ); // 错误,不能够返回函数
F *f(int); // 正确,显式返回指针函数
/*等价于
int (*f1(int))(int *,int);
这句代码需要从内到外解析,f1是一个函数,前面有一个*,所以f1返回一个指针,由于本身具有形参列表,所以指针指向函数,该函数的返回类型是int
*/
尾置返回类型
auto f1(int) -> int(*)(int *,int);
如果一开始我们就明确我们需要返回的指针函数类型,那么可以直接使用auto或者decltype
decltype(comparestring )*getfun(const string&);
需要注意的是:decltype作用与某一个函数的时候返回的是一个非指针的函数类型,我们需要显式的将*加上,表示是一个指针函数