函数上
复习:使用函数需要提供函数定义,提供函数原型,调用函数。
函数定义
函数分为两类:有返回值的函数,无返回值的函数。
C++对于有返回值的函数有一定限制,不能是数组,但可以将数组作为结构体或是对象的组成部分返回。
返回类型若不匹配,编译器会报错,除非满足隐式类型转换规则。
函数原型和函数调用
函数原型是一条语句,必须以分号结尾;
函数原型不要求提供变量名,但需要提供类型;
C/C++都允许不写函数声明,但是必须是单文件且定义在调用之前。C99及以上和C++不允许使用隐式函数声明。
C 语言:老标准允许省略类型(默认 int),新标准已禁用,C++ 不兼容这个规则;
函数和数组
大多数情况下C++和C语言一样,也将数组名视为指针。
由于使用的是指针而不是原始数组的副本,所以被调函数是有能力修改原始数据的。如果想要保护数据,则可以用const保护数组;
使用数组区间的函数
传统C/C++的方法是,传入数组起始指针,再传入数组元素数量。
还有一种方法是指定元素区间,使用两个指针,一个指向头部,一个指向尾部。例如STL方法使用超尾概念来定义区间。
指针使用const
const在前*在后为常量指针,本质是指针,指向常量的指针,不可以修改指向的值,但可以修改指向
*在前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对象(C++11)(定长数组)
优先使用「const 引用」尽量避免「按值传递」
按 const 引用传递(
const array<T, N>& arr)(T是类型,N是数组长度)传递 array 对象的只读引用,无拷贝(仅传地址),禁止修改数组内部数据
按非 const 引用传递(array<T, N>& arr)
按值传递(array<T, N> arr)
| 组成部分 | 作用 |
|---|---|
| array | 容器模板名 |
| <T, N> | T为数值类型,N为固定大小 |
| arr | 变量名 |
array并非只能存储基本类型,还可以存储对象。
vector对象(动态数组)
void display(const vector<string>& 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 不能直接定义数组类型。但可以简化复杂指针声明,极大减少手写复杂类型的出错概率
使用typedef
typedef可以用来创建类型别名,只能用来操作类型,不能操作实体/标识符。
int (*func_arr[4])(int, int);//等价于int (*(func_arr[4]))(int, int) // 简化写法1:typedef别名(C/C++通用) typedef int (*CalcFunc)(int, int);
//补充: 简化写法2:using别名(C++11+,更直观) using CalcFunc = int (*)(int, int);