C++ 学习笔记(6)函数、局部静态对象、重载函数、内联函数、constexpr函数、调试帮助、函数匹配、函数指针
参考书籍:《C++ Primer 5th》
6.1 函数基础
6.1.1 局部对象
- 局部静态对象:在程序执行第一次经过该对象时初始化,直到程序终止才被销毁。
size_t count_calls()
{
static size_t ctr = 0;
return ++ctr;
}
int main()
{
for (size_t i = 0; i < 5; i++)
cout << count_calls() << endl;
return 0;
}
6.2 参数传递
- 形参的初始化方式和变量的初始化方式是一样的。
6.2.1 传值参数
- 指针形参:拷贝的是指针本身,两个指针指向同一个内容。
void reset(int *ip)
{
*ip = 0; // 修改指向内容为0
ip = 0; // 改变局部指针ip的指向
}
6.2.3 const形参和实参
- 当用实参初始化形参时会忽略掉顶层const。如下,传int参数时,下面两个重载函数都参数可以完全一样,相当于重定义。
void func(const int i) { cout << i + 1; } // 顶层const被忽略,可以传int 或 const int类型。
void func(int i) { cout << i * 3; } // 运行错误!相当于重复定义func(int)
- 尽可能使用常量引用:
- 常量:避免在函数内修改值。同时可以传入普通形参和常量形参。
- 引用:避免函数对参数拷贝。
6.2.4 数组形参
- 传递数组形参,实际上是传数组首元素的指针。
// 下面3条语句是完全等价的。如果传入数组或指针,运行时会报错:重定义。
void print(const int*);
void print(const int[]);
void print(const int[10]);
6.2.6 含有可变形参的函数
- initializer_list形参:可变参数模版,标准库类型。其元素永远是常量。
void msg(initializer_list<string> il)
{
for (auto beg = il.begin(); beg != il.end(); ++beg)
cout << *beg << " ";
cout << endl;
}
void main()
{
msg({ "a","c" }); // 输出:"a c"
system("pause");
}
6.3 返回类型和return语句
6.3.2 有返回值函数
- 返回值的方式和初始化一个变量或形参的方式完全一样。
- 不要返回局部对象的引用或指针:局部对象在函数调用结束时会被释放。
- 列表初始化返回值:
vector<string> process() { return{ "fuck","eveything" }; }
6.3.3 返回数组指针
- Type (* function(parameter_list)) [dimension]
- 数组类型 ( * 函数名 ( 参数列表 ) ) [ 数组大小 ]
func(int i) // 普通函数
(*func(int i)) // 对函数结果解引用
(*func(int i))[10] // 解引用得到一个大小为10的数组
int (*func(int i))[10] // 数组元素是int类型
- 使用尾置返回类型:适用于任何函数
auto func(int i) -> int(*)[10]; // 返回指针,指向含有10个整数的数组。
- 使用decltype。
int arr[] = { 1,2,3,4 };
decltype(arr) * arrPtr(int i); // 返回指针,指向含有4个整数的数组。
6.4 函数重载
- 顶层const参数和没有顶层const参数的声明是等价的。
int find(int i);
int find(const i); // 重定义于上面的函数。
int find(int *ip);
int find(int *const ip); // 重定义。顶层const。
- 底层const则可以区分函数重载(指针或引用):
int find(int& i);
int find(const int& i); // 新函数。底层const。
int find(int*);
int find(const int* i); // 新函数。底层const。
- const_cast 和重载:对于参数非常量的的引用,不能使用常量引用。可以在函数内使用const_cast转换。
// 返回结果是常引用。(参数可以是常量,可以是普通变量)
const string &shoterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
// 返回结果是普通引用。(参数只能是普通变量)
string &shoterString(string &s1, string &s2)
{
// 把普通引用强制转换成常引用,才调用前面的函数。
auto &r = shoterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
// 返回时,把常引用转回普通引用。
return const_cast<string&>(r);
}
6.5 特殊用途语言特性
6.5.1 默认实参
- 在给定作用域内,一个形参只能被赋予一次默认实参。即可以为之前没有默认值的参数设置默认实参,但不能重复设置已经赋予的默认参数。
void screen(int w, int h, string s) { cout << (w * h) << " " + s; }
void screen(int w, int h, string s = "stringgggg"); // 为第三个参数设置默认值
void screen(int w, int h, string s = "asad"); // 运行时错误,重复声明。
void screen(int w = 32, int h = 16, string s); // 补充前面两个参数的默认值
- 局部变量不能作为默认实参。除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参。
6.5.2 内联函数和constexpr函数
-
关键字
inline
,编译过程中,直接在函数调用点上展开。 -
内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。
-
很多编译器不支持内联函数的递归。
-
constexpr函数:能用于常量表达式的函数。函数的返回类型及所有形参的类型都得是字面值类型,函数体内有且只有一条return语句。
constexpr int getSize(int i) { return 2 * i; } // 正确。
constexpr int getSize(int i) { int a = i; return 2; } // 错误,不能有其他语句。
- 内联函数和constexpr函数可以在程序中多次定义,但多个定义必须完全一致,所以通常定义在头文件。
6.5.3 调试帮助
-
assert
是一种预处理宏(preprocessor marco)(预处理变量,类似内联函数,由预处理器管理而非编译器管理)。 -
assert(expr)
:expr为假(为0)时程序终止。 -
NDEBUG
预处理变量:如果定了这个变量,则assert什么也不做。相当于关闭调试状态。
void print()
{
#ifndef NDEBUG
cerr << __func__ << endl // 输出函数名
<< __FILE__ << endl // 文件路径
<< __LINE__ << endl // 当前行数
<< __DATE__ << endl // 日期
<< __TIME__ << endl; // 具体时间
#endif // !NDEBUG
}
6.6 函数匹配
-
候选函数(candidate function):对应的重载函数集。
-
可行函数(viable function):1.形参数量相等;2.每个实参类型与形参对应相同(或可以转换)。
-
如果有多个形参匹配,编译器依次检查每个实参,以确定最佳匹配,如果没找到将会报错(二义性)。需要条件:
- 该函数每个实参的匹配都不劣于其他可行函数需要的匹配。
- 至少有一个匹配优于其他可行函数。
6.6.1 实参类型转换
- 转换优先级排序如下:
- 一些小整形会直接提升到int:
void ff(int);
void ff(short);
ff('a'); // char直接提升到int,而非short
6.7 函数指针
- 即指针指向函数而非对象。指向函数类型由返回值和形参类型决定,与函数名无关。
bool pass(const int &score) { return score >= 60; }
bool(*pf)(const int &); // 定义函数指针,指向的函数类型。
void main()
{
pf = pass; // pf指向pass函数
pf = &pass; // 等价赋值语句,取地址符是可选的
cout << pf(58) << endl; // 等价调用pass(58)
cout << (*pf)(70) << endl; // 等价调用pass(70)
}