6. 函数上 函数上复习使用函数需要提供函数定义提供函数原型调用函数。函数定义函数分为两类有返回值的函数无返回值的函数。C对于有返回值的函数有一定限制不能是数组但可以将数组作为结构体或是对象的组成部分返回。返回类型若不匹配编译器会报错除非满足隐式类型转换规则。函数原型和函数调用函数原型是一条语句必须以分号结尾函数原型不要求提供变量名但需要提供类型C/C都允许不写函数声明但是必须是单文件且定义在调用之前。C99及以上和C不允许使用隐式函数声明。C 语言老标准允许省略类型默认 int新标准已禁用C 不兼容这个规则函数和数组大多数情况下C和C语言一样也将数组名视为指针。由于使用的是指针而不是原始数组的副本所以被调函数是有能力修改原始数据的。如果想要保护数据则可以用const保护数组使用数组区间的函数传统C/C的方法是传入数组起始指针再传入数组元素数量。还有一种方法是指定元素区间使用两个指针一个指向头部一个指向尾部。例如STL方法使用超尾概念来定义区间。指针使用constconst在前*在后为常量指针本质是指针指向常量的指针不可以修改指向的值但可以修改指向*在前const在后为指针常量本质是常量指针类型的常量可以改变指向数据的值不能改变指向。C禁止将const的地址赋给非const的指针。允许非const的指针赋给const指针。不过两层间接关系混合const指针可以通过间接关系修改数据不再安全。const int a 10; const int* p1 a; // 合法const 数据地址 → const 一级指针 ​ // 错误试图将 const int* 的地址直接赋给 int**二级指针无 const int** p2 p1; // 编译器报错错误信息类似const int a 10; const int* p1 a; ​ // 显式强转强制将 const int* 的地址转为 int** 赋给 p2 int** p2 (int**)p1; // 编译器放行强转压制了错误 ​ // 试图通过二级指针修改 const 数据 a **p2 30; // 未定义行为可能出现的结果 // 1. 输出 10编译器优化const 数据被放在只读内存修改无效 // 2. 输出 30修改了内存但破坏了 const 语义后续代码可能出错 // 3. 程序崩溃写入只读内存触发内存保护错误。函数和二维数组在C/C中多维数组做参数的时候必须明确二维及以上的大小第一位的大小可省略。例如:void func(int arr[][4], int row) void func(int (*arr)[4], int row) ;//注意[]优先级高于*如果在形参中写上了行数调用函数时没有强制的行数限制。编译器会无视它。缺陷列数编译期固定函数无法通用处理不同列宽的二维数组不方便使用 const 保护数组内容拓展现代 C 解决方案std::vector std::vector int 、std::array消除该限制。函数和C风格字符串由于C类型的字符串有既定的结束字符所以设置形参的时候可以不设置代表字符串长度的形参。三种C字符串char数组、char指针、字符串字面量。传参时全部退化为char*函数和结构体结构体可直接作为函数的形参或实参用法与普通变量一致 。函数调用时会创建结构体的完整副本。若结构体体积较大会导致程序效率下降可以考虑使用结构体指针。函数和string对象形参使用const string s。传递的是原对象的引用不允许修改。形参使用string s传递的是原数据的引用并且可以修改。const string s/string s使用的原数据的副本。函数与arrray对象C11定长数组优先使用「const 引用」尽量避免「按值传递」按 const 引用传递const arrayT, N arrT是类型N是数组长度传递 array 对象的只读引用无拷贝仅传地址禁止修改数组内部数据按非 const 引用传递arrayT, N arr按值传递arrayT, N arr组成部分作用array容器模板名T, NT为数值类型N为固定大小arr变量名array并非只能存储基本类型还可以存储对象。vector对象动态数组void display(const vectorstring vec);递归和C\C语法上允许main()函数调用自己但极不推荐。递归存在函数调用开销、栈空间开销深度极大时优先改用循环迭代。#include iostream const int Len 66; const int Divs 6; void subdivide(char ar[], int low, int high, int level); ​ int main() { char ruler[Len]; int i; for (i 1; i Len - 2; i) ruler[i] ;//把1~64标记为 ruler[Len - 1] \0; int max Len - 2; int min 0; ruler[min] ruler[max] |;//把边缘标记为|0和64 std::cout ruler std::endl;//打印数组屏幕第一行 for (i 1; i Divs; i)// { subdivide(ruler, min, max, i); std::cout ruler std::endl; for (int j 1; j Len - 2; j)//清空数组 ruler[j] ; } ​ return 0; } ​ //当递归至level为0时停止递归。会把中间中间的中间……标记为| void subdivide(char ar[], int low, int high, int level) { if (level 0) return; int mid (high low) / 2; ar[mid] |; subdivide(ar, low, mid, level - 1); subdivide(ar, mid, high, level - 1); }函数指针和数据类似函数也有地址。函数指针的语法是返回值类型 (*指针变量名)(参数类型列表)括号不能省略否则为指针函数。函数名pt()和函数指针(*pt)()等价,和数组名与数组首地址类似。//写法1完整写法 void estimate(int lines, double (*pf)(int)); //写法2数组形式仅形参位置可用 void estimate(int lines, double pf(int));函数指针数组函数指针数组是「存储多个函数指针的数组」语法返回值类型 (*数组名[数组大小])(参数类型列表)// pa是数组数组内每个元素是const double* (*)(const double*, int) 类型函数指针 const double *(*pa[3])(const double *, int) {f1,f2,f3};注auto 不能直接定义数组类型。但可以简化复杂指针声明极大减少手写复杂类型的出错概率使用typedeftypedef可以用来创建类型别名只能用来操作类型不能操作实体/标识符。int (*func_arr[4])(int, int)//等价于int (*(func_arr[4]))(int, int) // 简化写法1typedef别名C/C通用 typedef int (*CalcFunc)(int, int);//补充 简化写法2using别名C11更直观 using CalcFunc int (*)(int, int);