函数基础
- 一个典型的函数定义包括:返回类型、函数名字、由零个或多个形参组成的列表以及函数体
- 我们通过调用运算符来执行函数。调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针;圆括号之内是一个用逗号隔开的实参列表,我们用实参初始化函数的形参。调用表达式的类型就是函数的返回类型。
编写函数
例:
| |
| int fact(int val) { |
| int ret = 1; |
| while (val > 1) { |
| ret *= val --; |
| } |
| return ret; |
| } |
调用函数
- 要调用 fact 函数,必须提供一个整数值,调用得到的结果也是一个整数
| int main() { |
| int j = fact(5); |
| cout << "5! is " << j << endl; |
| return 0; |
| } |
形参和实参
- 实参是形参的初始值。实参的类型必须与对应的形参类型匹配。
| |
| fact("hello"); |
| fact(); |
| fact(42, 10, 0); |
| fact(3.14); |
函数的形参列表
| void f1() {}; |
| void f2(void) {}; |
- 形参列表中的形参通常用逗号隔开,其中每个形参都是含有一个声明符的声明
| int f3(int v1, v2) {}; |
| int f4(int v1, int v2) {}; |
函数返回类型
- 大多数类型都能用作函数的返回类型。一种特殊的返回类型是 void,它表示函数不返回任何值。
- 函数的返回类型不能是数组类型或函数类型。
局部对象
自动对象
- 只存在于块执行期间的对象成为自动对象。当块的执行结束后,块中创建的自动对象的值就变成未定义的了
局部静态对象
- 可以将局部变量定义成 static 类型从而获得这样的对象
- 局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。
| size_t count_calls() { |
| static size_t ctr = 0; |
| return ++ ctr; |
| } |
| |
| int main() { |
| |
| for (size_t i = 0; i != 10; ++ i) { |
| cout << count_calls() << endl; |
| } |
| |
| return 0; |
| } |
每次执行 count_calls() 函数时,变量 ctr 的值都已经存在并且等于函数上一次退出时 ctr 的值。
- 如果局部静态变量没有显式的初始值,它将执行值初始化,内置类型的局部静态变量初始化为 0.
函数声明
- 和其他名字一样,函数的名字也必须在使用之前声明,函数只能定义一次,但是可以声明多次。
分离式编译
例:
Chapter6.h
| #pragma once |
| #ifndef CHAPTER6_H |
| #define CHAPTER6_H |
| |
| int fact(int a); |
| |
| #endif |
fact.cc
| #include "Chapter6.h" |
| |
| int fact(int a) { |
| int res = 1; |
| for (int i = 1; i <= a; i++) res *= i; |
| return res; |
| } |
factMain.cc
| #include <iostream> |
| |
| #include "Chapter6.h" |
| |
| int main() { |
| |
| int n; |
| std::cin >> n; |
| |
| std::cout << fact(n) << std::endl; |
| |
| return 0; |
| } |
参数传递
- 当形参是引用类型时,我们说它对应的实参被引用传递或者函数被传引用调用
- 当实参的值被拷贝给形参时,实参和形参是两个相互独立的对象。我们说这样的实参被值传递或者函数被传值调用
传值参数
- 当初始化一个非引用类型的变量时,初始值被拷贝给变量。对变量的改动不会影响初始值。
指针形参
- 指针的行为和其他非引用类型一样,当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后两个指针是不同的指针,因为指针是我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值。
| void reset(int *ip) { |
| *ip = 0; |
| ip = 0; |
| } |
| |
| int i = 42; |
| reset(&i); |
| cout << "i = " << i << endl; |
使用指针形参交换两个整数的值
| void swap_number(int *a, int *b) { |
| int t = *a; |
| *a = *b; |
| *b = t; |
| } |
| |
| int main() { |
| int a, b; |
| cin >> a >> b; |
| swap_number(&a, &b); |
| cout << a << ' ' << b << endl; |
| return 0; |
| } |
传引用参数
| void reset(int &i) { |
| i = 0; |
| } |
| |
| int j = 42; |
| reset(j); |
| cout << "j = " << j << endl; |
使用引用避免拷贝
- 拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括 IO 类型在内根本不支持拷贝操作)。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。
| |
| bool isShorter(const string &s1, const string &s2) { |
| return s1.size() < s2.size(); |
| } |
如果函数无需改变引用形参的值,最好将其声明为常量引用
使用引用形参返回额外信息
| string::size_type find_char(const string &s, char c, string::size_type &occurs) { |
| auto ret = s.size(); |
| occurs = 0; |
| for (decltype(ret) i = 0; i != s.size(); ++ i) { |
| if (s[i] == c) { |
| if (ret == s.size()) |
| ret = i; |
| ++ occurs; |
| } |
| } |
| return ret; |
| } |
const 形参和实参
顶层 const 作用于对象本身
- 和其他初始化过程一样,当用实参初始化形参时会忽略掉顶层 const。换句话说,形参的顶层 const 被忽略掉了。当形参有顶层 const 时,传给它常量对象或者非常量对象都是可以的。
| void fcn(const int i) {}; |
| void fcn(int i) {}; |
指针或引用形参与 const
- 形参的初始化变量方式和变量的初始化方式是一样的
- 我们可以使用非常量初始化一个底层 const 对象,但是反过来不行;同时一个普通的引用必须用同类型的对象初始化
| int i = 42; |
| const int *cp = &i; |
| const int &r = i; |
| const int &r2 = 42; |
| int *p = cp; |
| int &r3 = r; |
| int &r4 = 42; |
| |
| int i = 0; |
| const int ci = i; |
| string::size_type ctr = 0; |
| reset(&i); |
| reset(&ci); |
| reset(i); |
| reset(ci); |
| reset(42); |
| reset(ctr); |
| |
| find_char("Hello World!", 'o', ctr); |
尽量使用常量引用
- 把函数不会改变的形参定义成(普通的)引用是一种比较常见的错误,这么做带给函数的调用者一种误导,既函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大地限制函数所能接受的实参类型。
6.17
| #include <iostream> |
| #include <string> |
| #include <vector> |
| #include <cstring> |
| #include <cstddef> |
| #include <iterator> |
| #include <stdexcept> |
| |
| #include "Chapter6.h" |
| |
| using std::string; |
| using std::cin; |
| using std::cout; |
| using std::endl; |
| using std::vector; |
| using std::begin; |
| using std::end; |
| using std::cerr; |
| |
| bool check(const string &str) { |
| for (string::size_type i = 0; i != str.size(); ++ i) { |
| if (isupper(str[i])) return true; |
| } |
| return false; |
| } |
| |
| void turn(string &str) { |
| for (string::size_type i = 0; i != str.size(); ++ i) { |
| str[i] = tolower(str[i]); |
| } |
| } |
| |
| int main() { |
| |
| string str; |
| cin >> str; |
| |
| if (check(str)) { |
| turn(str); |
| } |
| |
| cout << str << endl; |
| |
| return 0; |
| } |
数组形参
数组的两个性质
- 不允许拷贝数组
- 使用数组时(通常)会将其转换成指针
| |
| |
| void print(const int*); |
| void print(const int[]); |
| void print(const int[10]); |
| |
| |
| int i = 0, j[2] = {0, 1}; |
| print(&i); |
| print(j); |
如果我们传给 print 函数的是一个数组,则实参自动地转换成指向数组首元素的指针,数组的大小对函数的调用没有影响
- 和其他使用数组的代码一样,以数组作为形参的函数也必须确保使用数组时不会越界
使用标记指定数组长度
- 管理数组实参的第一种方法是要求数组本身包含一个结束标记,例如 C 风格字符串
| void print(const char *cp) { |
| if (cp) { |
| while (*cp) { |
| cout << *cp ++; |
| } |
| } |
| } |
使用标准库规范
| void print(const int *beg, const int *end) { |
| while (beg != end) { |
| cout << *beg ++ << ' '; |
| } |
| cout << endl; |
| } |
| |
| int main() { |
| int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| print(begin(a), end(a)); |
| return 0; |
| } |
显式传递一个表示数组大小的形参
| void print(const int a[], size_t size) { |
| for (size_t i = 0; i != size; ++ i) cout << a[i] << ' '; |
| cout << endl; |
| } |
| |
| int main() { |
| int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| print(a, end(a) - begin(a)); |
| return 0; |
| } |
数组形参和 const
- 当函数不需要对数组元素执行 写操作的时候,数组形参应该是指向 const 的指针。只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。
数组引用形参
- 数组大小是构成数组类型的一部分,所以只要不超过维度,在函数体内就可以放心地使用。但是这一用法也无形中限制了 print 函数的可用性,我们只能将函数作用于大小为 10 的数组。
| void print(int (&arr)[10]) { |
| for (auto elem : arr) { |
| cout << elem << ' '; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| int main() { |
| int i = 0, j[2] = {0, 1}; |
| int k[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| print(&i); |
| print(j); |
| print(k); |
| return 0; |
| } |
传递多维数组
- 将多维数组传递给函数时,真正传递的是指向数组首元素的指针。因为处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针。数组第二维的大小都是数组类型的一部分,不能省略。
| void print(int (*matrix)[10], int rowSize) {} |
| |
| |
| |
| |
| |
| |
| |
| void print(int matrix[][10], int rowSize) {}; |
6.22 交换两个 int 指针
| void swap_pointer(int *(&pa), int *(&pb)) { |
| int *pt = pa; |
| pa = pb; |
| pb = pt; |
| } |
| |
| int main() { |
| int a = 1, b = 2; |
| int *pa = &a, *pb = &b; |
| swap_pointer(pa, pb); |
| cout << *pa << ' ' << *pb << endl; |
| return 0; |
| } |
main:处理命令行选项
| #include <iostream> |
| #include <string> |
| #include <vector> |
| #include <cstring> |
| #include <cstddef> |
| #include <iterator> |
| #include <stdexcept> |
| |
| #include "Chapter6.h" |
| |
| using std::string; |
| using std::cin; |
| using std::cout; |
| using std::endl; |
| using std::vector; |
| using std::begin; |
| using std::end; |
| using std::cerr; |
| |
| int main(int argc, char *argv[]) { |
| |
| |
| |
| string str; |
| for (int i = 0; i != argc; ++ i) str += argv[i]; |
| cout << str << endl; |
| |
| return 0; |
| } |
含有可变形参的函数
initializer_list
标准库类型
- 可变参数模板(16.4介绍)
initializer_list 形参
initializer_list
是一种标准库类型,用于表示某种特定类型的值的数组。定义在initializer_list
头文件中
提供的操作
| initializer_list<T> lst; 默认初始化;T 类型元素的空列表 |
| initializer_list<T> lst{a, b, c, ...}; lst 的元素数量和初始值一样多; |
| lst 的元素是对应初始值的副本; |
| 列表中的元素是 count |
| lst2(lst) 拷贝或赋值一个 initializer_list 对象不会拷贝列表中的元素;拷贝后, |
| lst2 = lst 原始列表和副本共享元素 |
| lst.size() 列表中的元素数量 |
| lst.begin() 返回指向 lst 中首元素的指针 |
| lst.end() 返回指向 lst 中为元素下一位置的指针 |
定义对象
| initializer_list<string> ls; |
initializer_list
对象中的元素永远是常量值,我们无法改变 initializer_list
对象中元素的值
使用如下形式编写输出错误信息的函数,使其可以作用于可变数量的实参
| void error_msg(std::initializer_list<string> li) { |
| for (auto beg = li.begin(); beg != li.end(); ++ beg) cout << *beg << ' '; |
| cout << endl; |
| } |
**如果想向initializer_list
形参中传递一个值的序列,则必须把序列放在一对花括号内
| string expected, actual; |
| if (expected != actual) { |
| error_msg({"functionX", expected, actual}); |
| } else { |
| error_msg({"functionX", "okay"}); |
| } |
**含有initializer_list
形参的函数也可以同时拥有其他形参
| void error_msg(ErrCode e, std::initializer_list<string> li) { |
| cout << e.msg() << endl; |
| for (const auto &elem : li) cout << elem << ' '; |
| cout << endl; |
| } |
| if (expected != actual) { |
| error_msg(ErrCode(42), { "functionX", expected, actual }); |
| } else { |
| error_msg(ErrCode(0), { "functionX", "okay" }); |
| } |
省略符形参
- 省略符形参只能出现在形参列表的最后一个位置,它的形式无外乎一下两种
| void foo(parm_list, ...); |
| void foo(...); |
**第一种形式指定了 foo 函数的部分形参的类型,对应于这些形参的实参将会执行正常的类型检查。省略符形参所对应的实参无须类型检查。在第一种形式中,形参声明后面的逗号是可选的。
返回类型和 return 语句
- return 语句有两种形式
return ;
return expression;
无返回值函数
- 没有返回值的 return 语句只能用在返回类型是 void 的函数中
- 一个返回类型是 void 的函数也能使用 return 语句的第二种形式,不过此时 return 语句的 expression 必须是另一个返回 void 的函数。
| |
| void f(int a, int b) { |
| cout << a << ' ' << b << endl; |
| } |
| |
| void turn(int &a, int &b) { |
| int t = a; |
| a = b; |
| b = t; |
| return f(a, b); |
| } |
| |
| int main() { |
| int a, b; |
| cin >> a >> b; |
| turn(a, b); |
| return 0; |
| } |
有返回值函数
- 只要函数的返回类型不是 void,则该函数内的每条 return 语句必须返回一个值。return 语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型。
值是如何被返回的
- 返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果
- 如果函数返回引用,则该引用仅是它所引对象的一个别名
| const string &shoterString(const string &s1, const string &s2) { |
| return s1.size() <= s2.size() ? s1 : s2; |
| } |
不要返回局部对象的引用或指针
- 函数完成后,它所占用的存储空间也随之被释放掉。因此函数终止意味着局部变量的引用将指向不再有效的内存区域
| const string &manip() { |
| string ret; |
| if (!ret.empty()) return ret; |
| else return "Empty"; |
| } |
- 返回局部对象的指针也是错误的。一旦函数完成,局部对象被释放,指针将指向一个不存在的对象
返回类类型的函数和调用运算符
- 调用运算符的优先级与点运算符和箭头运算符相同,并且也符合左结合律
| |
| auto sz = shortString(s1, s2).size(); |
引用返回左值
- 调用一个返回引用的函数得到左值,其他返回类型得到右值
| 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'; |
| cout << s << endl; |
| return 0; |
| } |
如果返回类型是常量引用,我们不能给调用的结果赋值,这一点和我们熟悉的情况是一样的
列表初始化返回值
- 如果函数返回的是内置类型,则花括号包围的列表最多包含一个值,而且该值所占空间不应该大于目标类型的空间。如果函数返回的是类类型,由类本身定义初始值如何使用。
| vector<string> process() { |
| |
| |
| if (expected.empty()) return {}; |
| else if (expected == actual) return {"functionX", "okay"}; |
| else return {"functionX", expected, actual}; |
| } |
主函数 main 的返回值
- 允许 main 函数没有 return 语句直接结束。如果控制到达了 main 函数的结尾处而且没有 return 语句,编译器将隐式地插入一条返回 0 的 return 语句。
- main 函数的返回值可以看做是状态指示器。返回 0 表示执行成功,返回其他值表示失败,其中非 0 值的具体含义依机器而定。
- 为了使返回值与机器无关,
cstdlib
头文件定义了两个预处理变量,我们可以使用这两个变量分别表示成功与失败
| int main() { |
| |
| if (some_failure) return EXIT_FAILURE; |
| else return EXIT_SUCCESS; |
| } |
递归
- 函数调用自身,(一定有某条路径是不包含递归调用的,否则会递归循环)
| int factorial(int val) { |
| if (val > 1) return factorial(val - 1) * val; |
| return 1; |
| } |
返回数组指针
- 因为数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或引用
| typedef int arrT[10]; |
| using arrT = int[10]; |
| |
| arrT* func(int i); |
声明一个返回数组指针的函数
函数形式
| Type (*function(parameter_list)) [dimension] |
| |
| int (*func(int i)) [10]; |
func(int i)
表示调用 func 函数时需要一个 int 类型的实参
(*func(inr i))
意味着我们可以对函数调用的结果执行解引用操作
(*func(int i)) [10]
表示解引用 func 的调用将得到一个大小是 10 的数组
int (*func(int i)) [10]
表示数组中的元素是 int 类型
使用尾置返回类型
- 尾置返回类型跟在形参列表后面并以一个 -> 符号开头。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个 auto
| |
| auto func(int i) -> int(*)[10]; |
例:
| int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| |
| auto func() -> int(*)[10] { |
| return &a; |
| } |
| |
| int main() { |
| int (*b)[10] = func(); |
| for (int i = 0; i != 10; ++ i) cout << *(*b + i) << ' '; |
| cout << endl; |
| return 0; |
| } |
使用 decltype
- 如果我们知道函数返回的指针将指向哪个数组,就可以使用 decltype 关键字声明返回类型
| int odd[] = {1, 3, 5, 7, 9}; |
| int even[] = {0, 2, 4, 6, 8}; |
| |
| decltype(odd) *arrPtr(int i) { |
| return (i % 2) ? &odd : &even; |
| } |
| |
| int main() { |
| |
| int (*b)[5] = arrPtr(3); |
| for (int i = 0; i != 5; ++ i) cout << *(*b + i) << ' '; |
| cout << endl; |
| |
| return 0; |
| } |
decltype 并不负责把数组类型转换成对应的指针,所以 decltype 的结果是个数组,要想表示 arrPtr 返回指针还必须声明时加一个 * 符号
6.37
| typedef string s[10]; |
| using s = string[10]; |
| |
| string ss[10]; |
| string (&func()) [10] {} |
| s &func() {} |
| auto func() -> string(&)[10] {} |
| decltype(ss) &func() {} |
函数重载
- 如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数
main函数不能重载!!!
定义重载函数
- 要在形参数量或形参类型上有所不同,不允许两个函数除了返回类型外其他所有的要素都相同
| Record lookup(const Account&); |
| bool lookup(const Account&); |
判断两个形参的类型是否相异
| |
| Record lookup(const Account &acct); |
| Record lookup(const Account&); |
| |
| typedef Phone Telno; |
| Record lookup(const Phone&); |
| Record lookup(const Telno&); |
重载和 const 形参
- 顶层 const 不影响传入函数的对象,一个拥有顶层 const 的形参无法和另一个没有顶层 const 的形参区分开来
| Record lookup(Phone); |
| Record lookup(const Phone); |
| |
| Record lookup(Phone*); |
| Record lookup(Phone* const); |
- 如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的 const 是底层的
| Record lookup(Account&); |
| Record lookup(const Account&); |
| |
| Record lookup(Account*); |
| Record lookup(const Account*); |
**因为 const 不能转换成其他类型,所以我们只能把 const 对象(或指向 const 的指针)传递给 const 形参,相反的,因为非常量可以转换成 const,所以上面 4 个函数都能作用于非常量对象或者指向非常量对象的指针。
const_cast 和重载
| 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); |
| } |
| |
| int main() { |
| const string a, b; |
| string c, d; |
| const string &x = shorterString(a, b); |
| string &y = shorterString(c, d); |
| return 0; |
| } |
如果不加下面的重载,我们可以对两个非常量的 string 实参调用这个函数,但返回的结果仍然是 const string 的引用
调用重载的函数
函数匹配也叫重载确定,指一个过程,在这个过程中我们把函数调用与一组重载函数中的某一个关联起来
现在需要掌握的是,当调用重载函数时有三种可能的结果
- 编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码
- 找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误信息
- 有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误,成为二义性调用
重载与作用域
- 如果我们在内部作用域中声明名字,它将隐藏外层作用域中声明的同名实体。在不同的作用域中无法重载函数名
| string read(); |
| void print(const string&); |
| void print(double); |
| void fooBar(int ival) { |
| bool read = false; |
| string s = read(); |
| |
| void print(int); |
| print("Value: "); |
| print(ival); |
| print(3.14); |
| } |
特殊用途语言特性
默认实参
- 默认实参:在很舒缓的很多次调用中它们都被赋予了一个相同的值
- 一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值
| typedef string::size_type sz; |
| string screen(sz ht = 24, sz wid = 80, char backgrnd = ' '); |
使用默认实参调用函数
- 如果想使用默认实参,只要在调用函数的时候省略该实参就可以了
| string window; |
| window screen(); |
| window screen(66); |
| window screen(66, 256); |
| window screen(66, 256, '#'); |
- 函数调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参(靠右位置)。
| window = screen(, , '?'); |
| window = screen('?'); |
默认实参声明
注意:在给定的作用域中一个形参只能被赋予一次默认实参
| string screen(sz, sz, char = ' '); |
| string screen(sz, sz, char = '*'); |
| string screen(sz = 24, sz = 80, char); |
默认实参初始值
- 局部变量不能作为默认实参,除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参
| |
| sz wd = 80; |
| char def = ' '; |
| sz ht(); |
| string screen(sz = ht(), sz = wd, char = def); |
| string window = screen(); |
- 用作默认实参的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时
| void f2() { |
| def = '*'; |
| sz wd = 100; |
| window = screen(); |
| } |
内联函数和 constexpr 函数
内联函数可避免函数调用的开销
| inline const string &shorterString(const string &s1, const string &s2) { |
| return s1.size() <= s2.size() ? s1 : s2; |
| } |
| |
| cout << shorterString(s1, s2) << endl; |
| |
| cout << (s1.size() <= s2.size() ? s1 : s2) << endl; |
constexpr 函数
- constexpr 函数是指能用于常量表达式的函数
| constexpr int new_sz() { return 42; } |
| constexpr int foo = new_sz(); |
我们允许 constexpr 函数的返回值并非一个常量
| |
| constexpr size_t scale(size_t cnt) { return new_sz() * cnt; } |
| |
| int arr[scale(2)]; |
| int i = 2; |
| int a2[scale(i)]; |
把内联函数和 constexpr 函数放在头文件内
调试帮助
assert 预处理宏
表达式
首先对 expr 求值,如果表达式为假(即0),assert 输出信息并终止程序的执行。如果表达式为真(即非0),assert 什么也不做。
在cassert
头文件中
NDEBUG 预处理变量
- 可以使用一个 #define 语句定义 NDEBUG,从而关闭调试状态。
**除了用于 assert 外,也可以使用 NDEBUG 编写自己的条件调试代码。如果 NDEBUG 未定义,将执行 #ifndef 和 #endif 之间的代码,如果定义了 NDEBUG,这些代码将被忽略
| void print(const int ia[], size_t size) { |
| #ifndef NDEBUG |
| cerr << __func__ << ": array size is " << size << endl; |
| #endif |
| } |
| |
| __func__ 存放函数的名字 |
| __FILE__ 存放文件名的字符串字面值 |
| __LINE__ 存放当前行号的整型字面值 |
| __TIME__ 存放文件编译时间的字符串字面值 |
| __DATA__ 存放文件编译日期的字符串字面值 |
| |
函数匹配
确定候选函数和可行函数
- 本次调用对应的重载函数集中的函数称为候选函数
- 从候选函数中选出能被这组实参调用的函数成为可行函数
寻找最佳匹配(如果有的话)
含有多个形参的函数匹配
如果有且只有一个函数满足下列条件,则匹配成功
- 该函数每个实参的匹配都不劣于其他可行函数需要的匹配
- 至少有一个实参的匹配优于其他可行函数提供的匹配
- 如果在检查了所有实参之后没有任何一个函数脱颖而出,则该调用是错误的。编译器将报告二义性调用的信息
实参类型转换
- 精准匹配,包括一下情况:
- 实参类型和形参类型相同
- 实参从数组类型或函数类型转换成对应的指针类型
- 向实参添加顶层 const 或者从实参中删除顶层 const
- 通过 const 转换实现的匹配
- 通过类型提升实现的匹配
- 通过算数类型转换或指针转换实现的匹配
- 通过类类型转换实现的匹配
函数指针
| |
| bool lengthCompare(const string &, const string &); |
该函数的类型是 bool(const string &, const string &)
- 要想声明一个可以指向该函数的指针,只需用指针替换函数名即可
| |
| bool (*pf)(const string &, const string &); |
使用函数指针
- 当我们把函数名作为一个值使用时,该函数自动地转换成指针。
| pf = lengthCompare; |
| pf = &lengthCompare; |
- 我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针
| bool b1 = pf("hello", "goodbye"); |
| bool b2 = (*pf)("hello", "goodbye"); |
| bool b3 = lengthCompare("hello", "goodbye"); |
- 在指向不同函数类型的指针间不存在转换规则。我们可以为函数指针赋一个 nullptr 或者值为 0 的整型常量表达式,表示该指针没有指向任何一个函数。
| string::size_type sumLength(const string &, const string &); |
| bool cstringCompare(const char *, const char *); |
| pf = 0; |
| pf = sumLength; |
| pf = cstringCompare; |
| pf = lengthCompare; |
重载函数的指针
| void ff(int*); |
| void ff(unsigned int); |
| |
| void (*pf1)(unsigned int) = ff; |
| void (*pf2)(int) = ff; |
| double (*pf3)(int*) = ff; |
函数指针形参
- 和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。
| |
| void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &)); |
| |
| void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &)); |
| |
| useBigger(s1, s2, lengthCompare); |
| |
| |
| typedef bool Func(const string &, const string &); |
| typedef decltype(lengthCompare) Func2; |
| |
| typedef bool (*FuncP)(const string&, const string&); |
| typedef decltype(lengthCompare) *FuncP2; |
| |
| void useBigger(const string&, const string&, Func); |
| void useBigger(const string&, const string&, FuncP2); |
返回指向函数的指针
| using F = int(int*, int); |
| using FP = int(*)(int*, int); |
| |
| PF f1(int); |
| F f1(int); |
| F *f1(int); |
| int (*f1(int))(int*, int); |
| auto f1(int) -> int(*)(int*, int); |
将 auto 和 decltype 用于函数指针类型
- 将 decltype 作用于某个函数时,它返回函数类型而非指针类型。因此我们显式地加上 * 以表名我们需要返回指针,而非函数本身
| string::size_type sumLength(const string&, const string&); |
| string::size_type largeLength(const string&, const string&); |
| |
| decltype(sumLength) *getFcn(const string&); |
6.56
| |
| #include <iostream> |
| #include <string> |
| #include <vector> |
| #include <cstring> |
| #include <cstddef> |
| #include <iterator> |
| #include <stdexcept> |
| #include <initializer_list> |
| #include <cstdlib> |
| #include <cassert> |
| |
| #include "Chapter6.h" |
| |
| using std::string; |
| using std::cin; |
| using std::cout; |
| using std::endl; |
| using std::vector; |
| using std::begin; |
| using std::end; |
| using std::cerr; |
| |
| using FP = int(*)(int, int); |
| |
| vector<FP> v; |
| |
| int jia(int a, int b) { |
| return a + b; |
| } |
| |
| int jian(int a, int b) { |
| return a - b; |
| } |
| |
| int cheng(int a, int b) { |
| return a * b; |
| } |
| |
| int chu(int a, int b) { |
| return a / b; |
| } |
| |
| int main() { |
| |
| v.push_back(&jia); |
| v.push_back(&jian); |
| v.push_back(&cheng); |
| v.push_back(&chu); |
| |
| int a = 2, b = 3; |
| for (const FP x : v) { |
| cout << x(a, b) << endl; |
| } |
| |
| return 0; |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!