C++ Primer: Functions
1. 函数基础
形参和实参
实参是形参的初始值. 形参和实参类型要一致, 顺序要对应. (但编译器具体按什么顺序求值则不确定)
局部对象
形参和函数内部的变量是局部变量, 仅在函数作用域可见.
所有函数体之外的对象存在于整个程序的生命周期, 直到程序退出.
自动对象
对于在函数执行时创建对象, 函数退出时销毁的对象称为 自动对象.
形参也是自动对象, 函数开始时为其申请空间, 函数退出时形参也被销毁.
局部静态对象
使用static
声明的局部对象称为 局部静态对象. 这种对象在函数执行时创建, 但函数退出时却不销毁, 直到程序退出时才销毁**, 生命周期贯穿整个程序.
函数声明
函数必须在使用前声明
函数可以有多次声明, 但只能有一次定义. 函数声明也称为 函数原型.
2. 函数参数
参数传递
- 引用传递, 形参相当于实参的别名
- 值传递, 形参和实参是独立的, 实参拷贝给形参
- 指针传递, 拷贝的指针的地址, 可以通过指针修改对象, 但形参和实参的指针地址是不同的
const形参和实参
形参有顶层const时, 实参传递常量对象或非常量对象都可以
// 变量初始化和形参初始化对比
int i = 0;
const int *cp = &i; // 正确, 但cp不能改变i
const int &r = i; // 正确, 但r不能改变i
const int &r2 = 42; // 正确
int *p = cp; // 错误, p的类型和cp类型不匹配
int &r3 = r; // 错误, r的类型和r3类型不匹配
int &r4 = 42; // 错误, 不能用字面值初始化一个引用
int i = 0;
const int ci = i;
string::size_type ctr = 0;
reset(&i); // 调用参数是int*的reset
reset(&ci); // 错误, 不能用指向const int的对象初始化int *
reset(i); // 调用参数时int&的reset
reset(ci); // 错误, 不能把普通引用绑定到const上
reset(42); // 错误, 不能把普通引用绑定到字面值上
reset(ctr); // 错误, 类型不匹配, size_t是无符号类型
尽量使用常量引用
数组形参
数组两个特殊的性质
- 数组不允许拷贝, 意味着不能通过值传递一个数组实参
- 数组会被转化为指针, 意味着传递一个数组实参时, 实际上传递的首元素指针
传递方式
- 使用标准库规范
void print(const int *beg, const int *end)
{
while(beg != end)
cout << *beg++ << endl;
}
int j[2] = {0, 1};
print(begin(j), end(j));
- 显式传递一个数组大小的实参数
void print(const int ia[], size_t size)
{
for (size_t i = 0; i < size; i++)
cout << ia[i] << endl;
}
int j[2] = {0, 1};
print(j, end(j) - begin(j));
当函数不需要写操作, 数组的形参应该是const类型的指针.
- 数组引用形参
void print(int (&arr)[10])
{
for (auto elem: arr)
cout << elem << endl;
}
&arr 括号不能少, 因为 f(int &arr[10]) 表示arr是引用的数组, f(int (&arr)[10]) 是具有10个整数的数组引用.
传递多维数组
c++没有真正的多维数组, 多维数组在c++中是数组的数组, 因此和数组一样, 多维数组指向数组的首元素
void print(int (*metrix)[10), int rowSize) { /**...**/};
main函数参数
- argc 表示元素数量
- **argv[] 表示多个c风格字符串的数组, 具体有多少个由argc决定
可变形参函数
initializer_list
是标准库类型, 表示特定类型值的数组
void err_msg(int err_code, initializer_list<string> errors) {
cout << "code " << err_code;
for (auto beg = erros.begin(); beg != errors.end(); beg++)
cout << *beg << " ";
cout << endl;
}
// initializer_list形参在传递实参时, 需要传递一个列表值
err_msg(500, {"e1", "e2", "e3"})
c++兼容了c的省略符号形参和varargs void f(param_list, ...);
3. 函数返回值
引用返回左值
char &get_val(string &str, string::size_type ix)
{
return str[ix];
}
int main()
{
string a("value");
cout << a << endl;
// get_val返回左值引用
get_val(a, 0) = 'V';
cout << a << endl;
}
列表初始化返回值
vector<string> process()
{
//...
return {"a", "b"};
}
返回数组指针
由于数组不能拷贝, 因此返回数组返回的是数组指针, 这种返回类型函数的声明方式可以有以下几种
- 使用typedef或using
typedef int arrT[10];
using arrT = int[10]; // 和typedef等价
arrT *func(int i); // 返回一个指向10个整数的数组的指针
- 不使用类型别名的声明
语法Type (*function (parameter_list))[dimension]
上面的可以声明为int (*func (int i))[10]
分解声明的每个部分的含义:
- func (int i), 表示函数调用需要一个int类型的实参
- (func(int i)), 表示对调用函数可以执行解引用*操作
- (*func (int i))[10], 表示调用func 解引用 得到一个大小是10的数组
- int (*func (int i))[10], 表示数组的类型是int
- 使用尾置返回类型(c++11)
auto func(int i);
- 使用decltype (c++11)
** 前提是知道了返回的数组指针指向哪个数组**
int odd[] = {1, 3, 5};
int even[] = {2, 4, 6};
decltype(odd) *arrPtr(int i)
{
return (i % 2) ? odd : event;
}
4. 函数重载
main函数不能重载
重载函数是名字相同, 但形参类型不同的一组函数集合
Record lookup(const Account&);
Record lookup(const Phone&);
Record lookup(const Name&);
const影响重载的识别吗
顶层const形参不会认为是不同的参数
Record lookup(Phone);
Record lookup(const Phone); // 重复声明
Record lookup(Phone *);
Record lookup(Phone * const); // 重复声明
底层const实现函数重载
Record lookup(Account &);
Record lookup(const Phone&); // 新函数, 常量引用
Record lookup(Account *);
Record lookup(const Account *); // 新函数, 常量指针
const_cast和重载
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() < s2.size()? s1 : s2;
}
// 重载函数, 非常量引用
string &shorterString(string &s1, string &s2)
{
// s1和s2都是非常量引用, 通过const_cast转为常量引用, 调用了shorterString重载函数
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
// 重载函数返回的类型是常量引用, 通过const_cast转为非常量引用返回
return const_cast<string&>(r);
}
作用域和重载
- 作用域内部不能重载函数名, 否则会造成未定义的行为
- 作用域外重载函数, 作用域内不受影响, 正常使用
5. 函数特殊特性
默认实参
应该在函数声明中就声明默认实参, 并放在合适的头文件中
默认实参行为: 调用函数没有传递参数则使用默认实参
默认实参声明
string screen(sz = 24, sz = 80, char);
默认实参声明一个参数只能声明一次
string screen(sz, sz, char = '');
string screen(sz, sz, char = '*'); // 错误, 重复声明
constexpr函数
constexpr变量定义时如果用到了函数, 则函数必须也是constexpr函数
char *s = "hello";
constexpr size_t size = strlen(s) + 1; // 错误, strlen不是constexpr函数
constexpr int new_size() { return 42; }
constexpr int foo = new_size() + 1; // 正确
constexpr size_t scale(size_t cnt) { return new_sz() * cnt };
int arr[scale(2)]; // 正确scale是常量表达式, 传递的cnt实参也属于常量
int i = 2;
int a2[scale(i)]; // 错误, i不是constexpr
内联函数
内联函数声明
inline const string &shorterString(const string &s1, const string &s2)
{
return s1.size() < s2.size()? s1 : s2;
}
声明内联函数, 编译器会尝试展开函数, 但不包装声明inline
的函数100%做成内联的.
内联函数适合优化规模较小、流程直接、频繁调用的函数
调试帮助
-
assert预处理宏
assert(expr)
如果expr为真, 输出信息并终止程序执行, 如果为假什么也不做
assert宏常用于检测不能发生的条件 -
预处理宏
-
__func__
, 函数名 -
__FILE__
, 文件名 -
__LINE__
, 行号 -
__TIME__
, 文件编译时间 -
__DATE__
, 文件编译日期
6. 函数指针
函数指针声明
// 原型函数
bool lengthCompare(const string &, const string &);
bool (*pf)(const string &, const string &);
使用函数指针
// 原型函数
pf = lengthCompare;
pf = &lengthCompare; // 取地址符是可选的
重载函数指针
重载函数可以有对应的函数指针, 编译器会决定选择哪个函数
指针作为函数形参
void userBigger(const string &s1, const string &s2,
bool pf(const string&, const string &);
void userBigger(const string &s1, const string &s2,
bool (*pf)(const string&, const string &); // 等价声明, 函数名会自动转换
返回函数指针
- 使用typedef或using
using F = (int *, int); // F是函数类型
using PF = int(*)(int *, int); // PF是函数指针类型
-
直接声明
int(*f1(int))(int *, int);
-
使用auto
auto f1(int);
-
使用decltype
string::size_type sumLength(const string&, const string &);
decltype(sumLength) *getFn(const string &); // 返回类型是一个函数指针