第6章 - 函数
1. 函数基础
1.1 函数的形参列表
-
形参列表中的形参通常用都好隔开,即使哥哥形参的类型一样,也必须把两个类型都写出来 int f3(int v1, v2) {/* ... */} // 错误
int f4(int v1, int v2) {/* ... */} // 正确
-
函数的返回值不能是数组,但可以是指向数组或函数的指针
1.2 局部对象
-
生命周期 (lifetime) -
局部变量(local variable) :形参和函数体内部定义的变量 -
自动对象(automatic object) :只存在于快执行期间的对象,形参 是一种自动对象 -
局部静态对象(local static object) :在程序的执行路径第一次经过对象定义语句时初始化,知道程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响 // 统计自己被调用的次数
size_t count_calls() {
static size_t ctr = 0; // ctr 是局部静态对象,调用结束后,
return ++ctr; // ctr 仍然有效
}
// 输出从1到10(包括)的数字
int main() {
for (size_t i = 0; i != 10; ++i)
cout << count_calls() < endl;
return 0;
}
1.3 函数声明(函数原型 function prototype)
-
函数的声明不包括函数体,所以可以省略形参 void print(vector<int>::const_iterator beg, vector<int>::const_iterator end);
-
函数应该在头文件中声明而在源文件中定义 -
含有函数声明的头文件应该被包含到定义函数的源文件中
1.4 分离式编译(separate compilation) 略
2. 参数传递
2.1 传引用形参
2.2 const 形参和实参
-
允许定义若干具有相同名字的函数,前提是不同函数的形参列表有明显区别 void fcn(const int i) {/* .. */} // fcn 能读取 i,但不能向 i 写值
void fcn(int i) {/* ... */} // 错误:重复定义了 fcn(int)
-
可以使用非常量初始化一个底层 const 对象,但是不能用底层 const 对象来初始化一个非常量
2.3 数组形参
-
数组的两个性质:
-
数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外信息
-
使用标记指定数组长度:适用于有明显结束标记且该标记不会与普通数据混淆的情况 -
使用标准库规范:传递指向数组首元素和尾后元素的指针 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[] = {0, 1};
print(j, end(j) - begin(j));
-
数组引用形参 void print (int (&arr)[10]) {
for (auto elem : arr)
cout << elem << endl;
}
-
多维数组 void print(int (*matrix)[10], int rowSize) {/* ... */}
// int (*matrix)[10]; 指向含有10个整数的数组的指针
- 多维数组的首元素本身就是一个数组,指针就是一个指向数组的指针;
- 多维数组第二位(以及后面所有维度)的大小都是数组类型的一部分,不能省略
2.5 main 处理命令函选项
// 假设 main 函数位于可指向文件 prog 内, 向程序传递下面的选项
prog -d -o ofile data0
int main(int argc, char *argv[]) { ... }
argv[0] = "prog"; // 或者 argv[0] 也可以指向一个空字符串
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;
- 第二个形参
argv 是一个数组, 它的元素是指向C风格字符串的指针; 第一个形参 argc 表示数组中字符串的数量 - 使用
argv 中的实参时, 可选的实参从 argv[1] 开始, argv[0] 保存程序的名字, 而非用户输入
2.6 含有可变形参的函数 略
3. 返回类型和 return 语句
3.1 有返回值的函数
-
引用返回左值 char &get_val(string &str, string::size_type ix) {
return str[ix];
}
int main() {
string s("a value");
cout << s << endl;
get_val(s, 0) = 'A'; // 将 s[0] 的值改为 A
cout << s << endl;
return 0;
}
- 如果函数返回类型是常量引用, 则不能给调用的结果赋值
-
函数可以返回花括号包围的值的列表 -
递归 (recursive function): 函数调用了自身 // 计算 val 的阶乘
int factorial(int val) {
if (val > 1)
return factorial(val - 1) * val;
return 1;
}
3.2 返回数组指针
-
使用类型别名 typedef int arrT[10]; // arrT 是一个类型别名, 表示的类型是含有10个整数的数组
using arrT = int[10]; // 等价于上一条语句
// 声明 func 函数, 该函数返回一个指向含有10个整数的数组的指针
arrT* func(int i);
-
不使用类型别名 int arr[10]; // arr 是一个含有10个整数的数组
int *p1[10]; // p1 是一个含有10个指针的数组, 指针指向整数
int (*p2)[10]; // p2 是一个指针,指向含有10个整数的数组
// 和上面的声明一样, 定义返回数组指针的函数时, 数组的维度必须跟在函数名字之后, 例如
int (*func(int i))[10];
func(int i) 表示调用 func 函数时需要一个 int 类型的实参(*func(int i)) 表示可以对函数调用的结果执行解引用操作(*func(int i))[10] 表示解引用得到的是一个大小是10的数组int (*func(int i))[10] 表示数组中的元素是 int 类型 -
使用尾置返回类型 // func 接受一个 int 类型的参数, 返回指向含有10个整数的数组的指针
auto func(int i) -> int(*) [10]
-
使用 decltype int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
// 返回一个指针, 指向含有5个整数的数组
decltype(odd) *arrPtr(int i) { // 对函数调用的结果解引用得到的是 odd 类型
// 的数组
return (i % 2) ? &odd : &even; // 返回一个指向数组的指针
}
4. 函数重载
-
重载函数(overloaded function) 同一作用域内的几个函数名字相同但形参列表不同 -
不允许两个函数除了返回类型外其他所有的要素都相同 -
顶层 const 不影响传入函数的对象: 一个拥有顶层 const 的形参无法和另一个没有顶层 const 的形参区分开来 -
如果函数的形参是某种类型的指针或引用, 则通过区分其指向的对象是否为常量可以实现函数重载, 此时的 const 是底层的 -
const_cast 和重载 string::size_type reset(const string &s) {
auto ret = s.size();
for (decltype(ret) i = 0; i != s.size(); ++i) {
if (s[i] == '.')
ret = i;
}
return ret;
}
// 重载函数 shorterString, 返回值不是 从const string &, 而是 string &
const string &shorterString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
string &shorterString(string &s1, string &s2) {
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
return const_cast<string&>(r);
}
-
如果在内层作用域中声明名字, 它将隐藏外层作用域中声明的同名实体
5. 特殊用途语言特性
5.1 默认实参 default argument
typedef string::size_type sz;
string screen(sz ht = 24, sz = wid = 80, char backgrnd = ' ');
- 一旦某个形参被赋予了默认值, 它后面的所有形参都必须有默认值
- 在给定的作用域中一个形参只能被赋予一次默认实参,
- 通常应该改在函数声明中指定默认实参, 并将该声明放在合适的头文件中
- 局部变量不能作为默认实参
5.2 内联函数和 constexpr 函数
-
内联 (inline) 函数可避免函数调用的开销, 在函数的返回类型前面加上关键字 inline 就可以将函数声明成内联函数 inline const string &
shortString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
- 内联说明知识像编译器发出的一个请求, 编译器可以选择忽略这个请求
- 一般来说, 内敛机制用于优化规模较小, 流程直接, 调用频繁的函数
-
constexpr 函数: 能用于常量表达式的函数
- 函数的返回类型及所有形参的类型都得是字面值类型
- 函数体中必须有且只有一条
return 语句 - 为了能在编译过程中随时展开,
constexpr 函数被隐式地定义为内联函数 - 允许
constexpr 函数的返回值并非一个常量 -
调试帮助
6. 函数指针
-
函数指针指向的函数而非对象, 指向某种特定类型, 函数的类型由它的返回类型和形参类型共同决定, 与函数名无关 // 比较两个 string 对象的长度
bool lengthCompare(const string &, const string &);
// pf 指向一个函数, 该函数的参数是两个 const string 的引用, 返回值是 bool 类型
bool (*pf)(const string &, const string &); // 未初始化
-
*pf 代表 pf 是一个指针 -
后面的形参列表表示 pf 指向的是函数 -
左侧的 bool 表示函数的返回值是 bool 类型 -
*pf 两侧的括号不能省略, 不写括号时 // 声明一个名为 pf 的函数, 其返回值类型为 bool*
bool *pf(const string &, const string &);
-
使用函数指针
-
把函数名作为一个值使用时, 该函数自动得转换成指针 pf = lengthCompare; // pf 指向名为 lengthCompare 的函数
pf = &lengthCompare; // 等价于上一条语句
-
可以直接使用指向函数的指针调用该函数, 无须提前解引用指针 // 以下三条语句等价
bool b1 = pf("hello", "goodbye");
bool b2 = (*pf)("hello", "goodbye");
bool b3 = lengthCompare("hello", "goodbye");
-
typedef // Func 和 Func2 等价, 为函数类型
typedef bool Func(const string &, const string &);
typedef decltype(lengthCompare) Func2;
// FuncP 和 FuncP2 等价, 为指向函数的指针
typedef bool (*FuncP)(const string &, const string &);
typedef decltype(lengthCompare) *FuncP2;
-
using using F = int(int*, int); // F 是函数类型, 不是指针
using PF = int(*)(int *, int); // PF 是指针类型
|