有名字的代码块。

一、函数声明

函数和全局变量一样,可以一次定义,多次声明从而可以在多个文件中使用。
函数的声明也叫函数的原型(function prototype)。

  1. // fact.h:头文件
  2. int fact(int, int); // 函数声明
  3. // fact.cc:源文件
  4. #include "fact.h" // 源文件中包含头文件
  5. int fact(int a, int b) { // 函数定义
  6. return a + b;
  7. }
  1. 返回类型 函数名字(类型1 形参1,类型2 形参2,...,类型n 形参n) {
  2. 函数体
  3. }
  4. int fuck(int a){ // a:形参
  5. int b = 1; // 局部变量
  6. cout << a + b << endl;
  7. return a + b;
  8. // return语句完成两项工作:
  9. // 1、返回return语句中的值。
  10. // 2、将控制权从被调函数转移回主调函数。
  11. }
  12. // 通过调用运算符(),来执行函数。
  13. fuck(100); // 1、实参100初始化函数形参a
  14. // 2、控制权转移给被调函数,主调函数执行中断
  15. void func(const int i) {} // func 能够读取 i, 但是不能向 i 写值
  16. void func(int i) {} // 错误:重复定义了 func(int)

函数的调用完成两项工作:

  • 实参初始化新参
  • 控制权转移给被调用函数
    • 主调函数的执行被暂时中断,被调函数开始执行。

      二、参数传递

  • 引用传递
    • 形参是引用类型,是对实参的引用,对形参的操作就是对实参的操作,使用引用传递可以避免值拷贝代价。
  • 值传递
    • 形参是实参的值拷贝,形参的改变不影响实参
    • 指针类型形参是值传递,传递的是指针值也就是所指向对象的地址,所以对形参指针的操作也能改变实参所指对象。

      三、形参

  1. int fact(int a, int b){ // 形参列表
  2. return a + b;
  3. }
  4. int fact( ){ // 隐式定义空形参列表
  5. }
  6. int fact(void){ // 显示定义空形参列表(为了与C兼容)
  7. }
  8. fact(10); // 实参10初始化形参a。

const形参

const形参的初始化和const变量的初始化一样。尽量定义成const,数据安全。

  1. int i = 42;
  2. const int *cp = &i; //正确:但是 cp 不能改变 i )
  3. const int &r = i; //正确:但是 r 不能改变 i
  4. const int& r2 = 42; //正确:
  5. int *p = cp; //错误: p 的类型和 cp 的类型不匹配
  6. int &r3 = r; //错误 : r3 的类型和 r 的类型不匹配
  7. int &r4 = 42; //错误:不能用字面值初始化一个非常量引用
  8. int i = O;
  9. const int ci = i;
  10. string::size_type ctr = O;
  11. reset(&i); // 调用形 参类型是 int*的 reset 函数
  12. reset(&ci); // 错误:不能用指向 const int 对象的指针初始化 int* '
  13. reset(i); // 调用形参类型是 int& 的 reset函数
  14. reset(ci); // 错误:不能把普通引用绑定到 const 对象 ci 上
  15. reset(42); // 错误:不能把普通应用绑定到字面值
  16. reset(ctr); // 错误:类型不匹配, ctr 是无符号类型
  17. // 正确: find_char 的第一个形参是对常量的引用
  18. find_char("Hello World! 11",'o', ctr);

数组形参

数组将以指针方式传递,指针指向数组首地址。

  1. void print(const int*); // 等价
  2. void print(const int[]); // 等价
  3. void print(const int[10]); // 等价
  4. // 如何确定数组形参的范围
  5. void print(const char *cp); // C风格字符串,以空字符结束
  6. void print(const int *beg, const int *end); // 标准库规范,首尾迭代器限定范围
  7. void print(const int ia[], size_t size); // 显式指定数组大小
  8. // 数组的引用,必须括号。
  9. void print(int (&arr)[10]); // 数组的引用,限定了数组大小只能是10
  10. // 多维数组形参
  11. // 二维数组的指针,数组元素是10个int元素的数组。
  12. void print(int (*matrix)[10], int rowSize); // 等价
  13. void print(int matrix[][10], int rowSize ) // 等价
  14. // main函数,命令行选项(多维数组)
  15. // prog -d -o ofile data0,main函数会收到两个参数。
  16. // 下面两种等价,注意argv[0]是程序名字,argv[1]才是ofile
  17. int main (int argc , char *argv[]); // 数组元素是指针(C风格字符串)
  18. int main (int argc , char **argv); // 数组也用指针传递。第二个*是数组的元素。

可变形参

可变参数列表。
这里考虑的情况是:数量未知、类型相同。
原理:动态类型数组,用数组来实现,只不过数组的元素类型是用模板来动态确定。

具体是用标准库类型initializer_list,某种特定类型值的数组。定义在头文件initializer_list中。注意!它的元素是常量值,不可改变。

  1. /***************initializer_list提供的接口************/
  2. initializer_list<T> lst; // 默认初始化:T类型元素的空列表
  3. // lst的元素数量和初始值一样多;lst的元素是对应初始值的副本:列表中的元素是const
  4. initializer_list<T> lst{a,b,c...};
  5. lst2(lst) // 拷贝或赋值一个initializer_list对象不会拷贝列表中的元素,
  6. lst2 = lst // 拷贝后,原始列表和副本共享元素
  7. lst.size() // 列表中的元素数量
  8. lst.begin() // 返回指向 lst中首元素的指针
  9. lst.end() // 返回指向 lst 中尾元素下一位置的指针
  10. /********************可变形参例子************/
  11. void error_msg(int num, initializer_list<string> args) // 使用的时候必须用花括号
  12. {
  13. for(auto beg = args.begin() ; beg != il.end(); ++beg)
  14. cout << *beg << " ";
  15. cout << endl;
  16. }
  17. error_msg(0,{"fuck", "you", "mother", "fucker"}); // 必须用花括号括起来。
  18. // C风格的省略符形参,尽量少在C++中使用。
  19. void foo(parm_list,...); // 省略符形参这是C++为兼容C设置的。...必须在最后
  20. void foo(...); // 使用了C标准库varargs

默认实参

  1. typedef string::size_type size;
  2. // 默认实参。
  3. string screen( size ht= 24, size wid = 80, char backgrnd = '' );
  4. screen(); // 等价
  5. screen(24); // 等价
  6. screen(24, 80); // 等价
  7. screen(24, 80, ''); // 等价
  8. screen(, ,'?'); // 错误,只能省略尾部的实参。

四、局部对象

名字有作用域,对象有生命周期。
局部变量:
自动对象:到底定义所在块的末尾时销毁,。
局部静态变量:local static object
形参和函数内部定义的变量,且会隐藏函数外部同名的变量。

  1. int fact(int a) {
  2. int b; // 自动对象:到底定义所在块的末尾时销毁。
  3. // 执行默认初始化,因此内置类型默认都是未定值。
  4. static int c; // 局部静态变量,进行指初始化,初始化为0。
  5. // 多次调用只进行一次初始化。
  6. return a;
  7. }

五、返回值

  1. void swap(int& a, int& b){
  2. if(a == b)
  3. return; // return void类型。
  4. a ^= b;
  5. b ^= a;
  6. a ^= b;
  7. return; // 此处return,可以省略,编译器会自动添加。
  8. }
  9. int fact(int a){
  10. return a; // return expression类型。
  11. }
  12. int main(){
  13. ......
  14. return 0; // 可省略,编译器会自动添加这条语句。
  15. }

返回过程

两种返回情况

  • 返回一个值,和初始化变量/形参的过程完全一样,只不过这个返回值初始化的是一个临时变量,在函数调用点的位置。
  • 返回一个引用,没有初始化过程,就是返回某个变量的引用。

注意,不要返回局部变量的引用或指针。

  1. string fuck(const string& word) {
  2. return word; // 用word拷贝构造了一个临时的string对象在调用点:line 7
  3. }
  4. const string& fuckRef(const string& word){
  5. return word; // 返回了word的引用,没有触发拷贝构造。
  6. }
  7. const string& errRef(){
  8. string ret("asdf");
  9. return ret; // 错误,返回了局部变量的引用。
  10. }
  11. int main(){
  12. auto shit = fuck("asdfsadf"); // 整个过程调用了一次拷贝构造函数。
  13. string str = "asdfas";
  14. auto moth = fuckRef(str); // moth是str的引用,没有触发拷贝构造。
  15. }

若返回引用,则返回的是一个左值;其他情况,返回的是一个右值。很好理解,返回的引用肯定是返回函数外部对象引用,而返回值则拷贝构造了一个临时变量,这是右值。右值可以理解为由编译器创建和销毁的变量。

返回列表初始化

可以返回一个列表,函数的返回类型就会进行列表初始化。可以实现多返回值的效果,不过类型必须全部相同。
返回vector,那列表的元素不限制。
返回内置类型,如int,那列表只能有一个元素。

  1. vector<string> process(){
  2. // expected 和 actual 是 string 对象
  3. if (expected.empty())
  4. return{}; //返回一个空vector对象
  5. else if(expected == actual )
  6. return{"functionX", "okay"}; //返回列表初始化的 vector 对象
  7. else
  8. return{"functionX", expected, actual } ;
  9. }
  10. int process(){
  11. return {1}; // 返回类型是内置类型,列表中只能一个值。
  12. }

返回数组指针

  1. /***************类型别名来表示数组指针**********/
  2. typedef int arrT[10]; // arrT 是一个类型别名,它表示的类型是含有 10 个整数的数组
  3. using arrT = int[10]; // arrT 的等价声明, 参见 2.5. 1 节(笫 60 页)
  4. arrT* func(int i) ; // func 返回一个指向含有 10 个整数的数组的指针
  5. /***************函数返回数组指针的公式*****************/
  6. Type (*function(parameter_list))[dimension]
  7. int (*func(int i)) [10]; //例子:返回int[10]数组的指针。
  8. //解读方法
  9. //1、func(int i)表示调用 func 函数时需要一个 int 类型的实参。
  10. //2、(*func(int i))意味着我们可以对函数调用的结果执行解引用操作 。
  11. //3、(*func(int i))[10] 表示解引用 func 的调用将得到一个大小是10的数组。
  12. //4、int(*func(int i)) [10] 表示数组中的元素是int类型 。
  13. /***************使用 尾置返回类型 来返回数组指针*****************/
  14. auto func(int i) -> int(*)[10]; // 前面用auto代替。
  15. /***************使用 decltype 来返回数组指针*****************/
  16. int odd[] = {1, 3, 5, 7, 9};
  17. int even[] = {0, 2, 4, 6, 8};
  18. decltype(odd) *arrPtr(int i); //返回一个指向数组的指针

六、函数重载(overload)

在相同作用域, 名字相同,形参列表不同的几个函数,叫重载函数。所以,重载的唯一关注点就是形参列表的差异,返回值类型不是考量范围。成员函数的this指针也属于参数列表的一员,因此引用限定符也可以重载。

以下是重载考量标准:

  • 形参数量不同。
  • 形参类型不同。
  • 有无底层const
    • const int *是底层const
    • int * const是顶层const
    • const int是顶层const
  • 引用限定符

以下不是重载考量标准:

  • 返回值类型。
  • 形参顶层const
    • 也好理解,比如int const p的形参,其实int和int*const实参都可以传给这个形参,所以这个const并没有把形参区分开来。
  1. void print(const char *cp); // 重载
  2. void print(const int *beg, const int *end); // 重载:形参类型和数量不同
  3. void print(const int ia[], size_t size); // 重载:形参类型和数量不同
  4. Record lookup(Phone);
  5. Record lookup(const Phone); // 错误,重复声明:顶层const不是重载。
  6. Record lookup(Phone*);
  7. Record lookup(Phone* const); // 错误,重复声明:顶层const不是重载。
  8. Record lookup(Account&);
  9. Record lookup(const Account&); // 重载:底层const
  10. Record lookup(Account*);
  11. Record lookup(const Account*); // 重载:底层const
  12. //const_cast下面这种重载就非常有用。
  13. const int& fuck(const int&, const int&);
  14. int& fuck( int&, int&);
  15. int& fuck(int& a, int& b){
  16. // 这里直接调用了const int&重载版本的函数,这样就非常安全高效。
  17. const int& ret = fuck(const_cast<const int&>(a), const_cast<const int&>(b));
  18. return const_cast<const int&>(ret);
  19. }
  20. int fuck(double);
  21. int fuck(int[]);
  22. int fuck(string)
  23. {
  24. int fuck(int); // 在局部作用域定义,屏蔽了所有外部重载
  25. fuck(1); // fuck(int)
  26. fuck(1.2); // fuck(int),隐式转换
  27. fuck("adf"); // 错误,匹配不到
  28. }

函数匹配

  1. /************函数匹配规则******************/
  2. void f();
  3. void f(int) ;
  4. void f(int, int) ;
  5. void f(double, double = 3.14);
  6. f(5, 4.2); // 调用void f(double, double)
  7. //1、确定重载函数集(同名、可见)
  8. // 4个f函数
  9. //2、实参和形参匹配
  10. // 数量相等
  11. // 类型匹配(精确、可转换)
  12. // f( int, int )
  13. // f( double, double )
  14. //
  15. //3、选出“最佳匹配”函数(实参和形参类型越接近,越好)
  16. // a、 精确匹配
  17. // 类型相同
  18. // 数组、函数对应指针
  19. // 向实参添加顶层const或者从实参删除底层const
  20. // b、const转换实现匹配
  21. // c、类型提升实现匹配
  22. // d、算术类型转换、指针转换实现匹配
  23. // e、类类型转换实现匹配
  24. //
  25. // f( int, int ),第一个int精确,第二个int要转换
  26. // f( double, double ),第一个double要转换,第二个double精确
  27. //
  28. //4、匹配的函数不止有一个,编译器报错(二义性),否则就找出最佳匹配。
  29. // f( int, int )
  30. // f( double, double )
  31. // 这两个都匹配,所以编译器报错。
  32. /***************例子*********************/
  33. void ff(int);
  34. void ff(short) ;
  35. ff('a'); // char提升成int;调用f(int)
  36. void manip(long);
  37. void manip(float);
  38. manip(3.14); // 错误:二义性调用
  39. Record lookup(Account&);
  40. Record lookup(const Account&) ;
  41. const Account a;
  42. Account b;
  43. lookup(a); // 调用 lookup (const Account&)
  44. lookup(b); // 调用 lookup (Account&)

七、内联函数(inline)

设计目的:让一些高频使用的简单函数在调用处导入函数体代码,这样做可以避免函数调用的栈开销。
将函数声明为inline是告诉编译器,这个函数可以考虑内联,当然如果你故意让一个很复杂的函数内联,显然是不会成功的 。

  1. inline const string & fuck(const string &sl, const string &s2 ){
  2. return sl.size() <= s2.size() ? sl : s2
  3. }

八、constexpr函数

能用于常量表达式的函数,参数和返回值都是字面值类型,且函数体有且只有一条return语句。
注意以上条件并不是必须的,如果不满足,就是一个普通函数。
一般放在头文件。

  1. constexpr int new_sz() {
  2. return 42 ;
  3. }
  4. constexpr int foo = new_sz (); // 正确:foo是一个常量表达式
  5. // 如果cnt、new_sz是常量表达式,则scale是constexpr函数。
  6. constexpr size_t scale(size_t cnt) { return new_sz() * cnt;)
  7. int arr[scale(2)]; // 正确:scale(2)是常量表达式
  8. int i = 2; // i不是常量表达式
  9. int a2[scale(i)]; // 错误:scale(i)不是常量表达式

九、函数指针

指向函数地址的指针,类型由函数签名决定(返回类型、形参列表)。
函数指针间不存在转换,只有相同与不同。
函数指针对于函数必须是精确匹配(返回类型、形参类型、形参数量)

  1. /*************函数指针格式***************/
  2. // Type: 返回值类型
  3. //funcptr: 函数指针名字,注意必须圆括号否则就是一个返回指针的函数
  4. // 指针的&符、*符都是可选,也就是说
  5. // funcptr = &func 等价于 funcptr = func
  6. // funcptr() 等价于 (*funcptr)()
  7. // ...: 参数列表
  8. Type (*funcptr)(...);
  9. /*************定义声明***************/
  10. bool func(int); // 函数原型
  11. bool (*pf)(int); // 函数指针,指向上面类型的函数
  12. bool *fuck(int); // 不是函数指针,返回bool*类型的函数。
  13. /*************赋值、初始化***************/
  14. pf = func; // 等价,指向func
  15. pf = &func; // 等价,&符可选
  16. /*************调用***************/
  17. bool bl = pf(1); // 等价
  18. bool b2 = (*pf)(1); // 等价
  19. bool b3 = func(1); // 等价
  20. /*************作为参数***************/
  21. void fuck(int, bool pf(int)); // 自动转成函数指针
  22. void shit(int, bool (*pf)(int)); // 与上等价
  23. fuck(1, func); // 函数自动转成指针。
  24. shit(1, func);
  25. /*************作为返回值***************/
  26. // 数组不可复制、函数不能拷贝、但是函数指针可以复制。
  27. int FuncProtoType(int);
  28. using Func = int(int); // Func是函数
  29. using FuncP = int(*)(int); // FuncP是函数指针
  30. FuncP f1(int); // 正确
  31. Func f1(int); // 错误:不能返回一个函数,这种情况,编译器不会自动转换。
  32. Func *f1(int); // 正确
  33. //同上,从里往外分析。
  34. //1、f1(int),f1是一个函数
  35. //2、*f1(int),f1返回一个指针
  36. //3、int (*)(int),指针解引用,得到函数int()(int)
  37. int (*f1(int))(int); // 等价
  38. auto f1(int)->int(*)(int); // 等价
  39. decltype(FuncProtoType) *f1(int); // 等价
  40. /*************类型别名、decltype简化***************/
  41. typedef bool Func(int); // Func是函数类型
  42. typedef decltype(func) Func2; // 同上
  43. typedef bool (*FuncP)(int) ; // FuncP是函数指针类型
  44. typedef decltype(func) *FuncP2 ; // 同上
  45. void ok(int, Func); // Func自动转成函数指针类型
  46. void ok(int, FuncP2); // 同上

十、调试技巧

assert

预处理宏,类似内联函数,定义在assert头文件中,受预处理变量NDEBUG控制。注意!assert只是调试代码。

  1. // 在C中,定义在<assert.h>中
  2. // 在C++中,定义在<assert>中
  3. #define NDEBUG // 关闭assert,在头文件之前定义,则assert将不生效。相当于生产环境
  4. // 注释本句,则开启assert。
  5. #include <assert>
  6. assert(expr); // expr返回值为false,则立即终止程序。
  7. // expr返回值为true ,则什么也不做。

NDEBUG

预处理变量,用于表示程序是否关闭调试状态。可以以此为开关编写自己的调试代码。

  1. #define NDEBUG // 关闭调试状态,注释则开启调试状态。
  2. void fuck(){
  3. #ifndef NDEBUG // 程序调试代码
  4. std::err << __func__ << std::endl; // 当前函数名字
  5. std::err << __FILE__ << std::endl; // 文件名
  6. std::err << __LINE__ << std::endl; // 当前行号
  7. std::err << __TIME__ << std::endl; // 编译时间
  8. std::err << __DATE__ << std::endl; // 编译日期。
  9. #endif
  10. }