这章16号才看完,因为15号去辅导学弟了(虽然辅导了个空气),15号三国杀杀到半夜,人都给杀麻了……
7.1 复习函数的基本知识
要使用C++函数,必须完成如下工作:
提供函数定义
提供函数原型(在使用前声明函数名)
调用函数
函数是如何返回值的:通常,函数通过将返回值复制到指定的CPU寄存器或内存单元中来将其返回。随后,调用程序将查看该内存单元。返回函数和调用函数必须就该内存单元中存储的数据的类型达成一致。函数原型将返回值类型告知调用程序,而函数定义命令被调用函数应返回什么类型的数据
函数原型通常隐藏在include文件中
需要原型的原因:
在文件中查找效率低
不一定在文件中,可能无权访问函数代码
唯一避免使用函数原型的方法是在使用函数之前定义
原型的功能:
编译器正确处理函数返回值
编译器检查使用的参数数目是否正确
编译器检查使用的参数类型是否正确。如果不正确,则转换为正确的类型(如果可能的话)
仅当有意义时,原型化才会导致类型转换。例如,原型不会将整数转换为结构和指针。
在编译阶段进行的原型化被称为静态类型检查。可以看出,静态类型检查可以捕获许多在运行阶段非常难以捕获的错误
7.2 函数参数和按值传递
用于接受传递值得变量称为形参(parameter),传递给函数的值称为实参(argument)
一般情况下(即不加引用),函数将创建一个新的变量作为形参,相当于副本
7.3 函数和数组
int sum_arr(int arr[], int n);
方括号指出arr是一个数组,任意长度皆可;但实际上arr是一个指针!不过在编写函数其余部分时,可以将arr看成数组int sum_arr(int* arr, int n);
在C++中,当且仅当用于函数头或函数原型中,int* arr
和int arr[]
的含义才是相同的。它们都意味着arr是一个int指针,都指向数组的第一个元素传递常规变量时,函数将使用该变量的拷贝;但传递数组时,函数将使用原来的数组(因为传的是地址)
注意,地址值和数组的长度随系统而异,另外,有些C++实现以十进制而不是十六进制格式显示地址,有些编译器以十六进制显示地址时,会加上前缀
0x
;编写特定的函数来处理特定的数据操作是有好处的(程序的可靠性更高,修改和调试更为方便)
构思程序时,将存储属性与操作结合起来,便是朝OOP思想迈进了重要的一步
void show(const double arr[], int n);
常量指针,该指针不能修改指向的数据,保护原数组常量指针:
int age = 13;
const int* pt = &age;
实际上pt并不一定意味着它指向的值实际上就是一个常量,而只是意味着对pt而言,这个值是常量。
注意:不能将const变量的地址赋给常规指针,如果非要这么做,需要使用强制类型转换,详情见第15章;如果数据本身不是指针,则可以将const数据或非const数据的地址赋给指向const的指针,但只能将非const数据的地址赋给非const指针
尽可能使用const:将指针参数声明为指向常量数据的理由
避免由于无意间修改数据类型而导致的编程错误
使用const使得函数能够处理const和非const实参,否则将只能接受非const数据。如果条件允许,则应该将指针形参声明为指向const的指针。如下:非const指针可以作为const形参的实参,而且由于加了const,所以函数不能修改数组
1 2 3 4 5 6 7 8 9 10 11
#include<iostream> using namespace std; void fun(const int arr[]) { cout << arr[0]; } int main() { int* arr = new int[10]{1,2};//列表初始化,后面的都初始化为0 fun(arr); }
7.4 函数和二维数组
- 二维数组没有使用const,因为这种技术只能用于指向基本类型的指针,而ar2是指向指针的指针
7.5 函数和C-风格字符串
- C-风格字符串与常规char数组之间的一个重要区别是:字符串有内置的结束字符,不以空值字符结尾的char数组只是数组,而不是字符串
7.6 函数和结构
- 当结构比较小时,按值传递结构最合理
7.7函数和string对象
string对象与结构更相似
可以将一个对象赋给另一个对象
可以将对象作为完整的实体进行传递
需要多个字符串可以声明一个string数组
7.8 函数与array对象
- 模板array并非只能存储基本数据类型,还可以存储类对象
7.9 递归
- 每个递归调用都创建自己的一套变量(这就是为啥递归调多了会栈溢出)
7.10 函数指针
函数的地址是存储其机器语言代码的内存的开始地址
比如可以编写将另一个函数的地址作为参数的函数,它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数
函数指针的基础知识:
获取函数的地址:只要函数名(后面不跟参数即可)
声明函数指针:通常,需要声明特定类型函数的指针,可以先编写这种函数的原型,然后用(*pf)替换函数名,pf就是这类函数的指针
1 2 3 4 5
double pam(int); //指针类型声明: double (*pf)(int); pf = pam; void estimate(int lines, double (*pf)(int));
使用指针来调用函数:(*pf)扮演的角色与函数名相同;但实际上,C++也允许像使用函数名那样使用pf,第一种格式虽然不好看,但它给出了强有力的提示——代码正在使用函数指针
1 2 3
double x = pam(5); double y = (*pt)(5); double z = pt(5);
深入探讨函数指针:
在函数原型中,可以省略标识符。比如
const double ar[]
可简化为const double []
使用C++11的自动类型推断功能,代码要简单得多:
1 2 3
const double * f1(const double *, int); const double * (*p1)(const double *, int) = f1; auto p2 = f1;
函数指针数组:
const double * (*pa[3])(const double *, int)={f1,f2,f3};
[]的优先级高于,因此pa[3]表示pa是一个包含3个指针的数组,特征标为:const double*,int,返回类型为const double *
auto自动类型判断只能用于单值初始化,不能用于初始化列表
pa
指向函数数组第一个元素(函数指针)的指针,pa[0]
储存第一个函数指针。使用函数指针数组调用函数:1 2
const double * px = pa[0](av,3); const double * py = (*pa[1])(av,3);
除了auto外,C++还提供了其他简化声明的工具,比如关键字
typedef
能够创建类型别名,比如某个函数指针类型:typedef const double * (*p_fun)(const double *, int);
p_fun是一个函数指针的类型名了