【1】使用浮点数进行相等和不相等的比较操作是有问题的。浮点数的相等比较,一般总是使用两者相减的值是否落在0的领域中来判断的。
【1】函数形参传递引用给了函数超限的权力,函数循着传递的引用名而既读又写地访问了引用的空间,而那一片引用空间并不是函数所拥有的。在指针和引用参数上可以加const修饰,以此限制函数体中对参数的写操作,即:
Vector<int> add(const vector<int>& a,const vector<int>& b){
for(int i=0;i<a.size();++i)
a[i]+=b[i]; //错;a[i]不能做左值
return a;
}
【2】一个程序要运行,要先将可执行程序文件装载到计算机的内存中。装载是操作系统掌控的,操作系统将程序装入内存后形成一个随时可以运行的进程空间,改空间分为四个区域:代码区、全局数据区、堆区、栈区;其中:
1.代码区存放程序的执行代码,也即是索引了的一个个函数块代码,它由函数定义块的编译得到。
2.全局数据区存放全局数据、常量、文字量、静态全局量和静态局部量;
3.堆区存放动态内存,供程序随机申请使用;
4.栈区存放函数数据区(即局部数据区),它动态地反映了程序运行中的函数状态,其运动轨迹正好用来观察函数的调用与返回,从而研究其函数机制;局部变量只要未经初始化,其值总是不确定的;
除了代码区域,其他都是数据区域,对这些数据区域理论上指针可以通过强制类型转换的方法绕过编译器的检查而访问(指针的强行访问);
【3】函数调用时,C++做以下工作:
1. 建立被调函数的栈空间,栈空间的大小由函数定义体中的数据量多少决定;
2. 保护调用函数的运行状态和返回地址;
3. 传递参数;
4. 将控制权转交给被调函数;
5. 函数运行完成后,复制返回值到函数数据块底部;
6. 恢复调用函数的运行状态;
7. 返回调用函数;
【4】指向进程空间数据区域的指针,称为数据指针,指向代码区域的指针称为指向函数的指针,简称函数指针,注意,与返回指针类型的函数不同,该函数称为指针函数;指向函数的指针变量不同于指向数据类型的指针变量,不能作++或--运算;
【5】函数是以参数个数、参数类型、参数顺序甚至返回类型的不同来区分不同类型的。函数的类型表示即函数声明去掉函数名;
1. int (*gp)(int); //定义一个int (int)类型的函数指针gp,就是把指针名放在返回类型和括号之间;
上面是定义了一个函数指针,不是声明,而且容纳函数指针名的括号不能省,表示*gp是一个整体,它描述的是一个指针;
2. int*gp(int);
表示声明一个含有一个整数参数的整数指针函数,等价于 int* gp(int a);
3.因为函数指针本身也是一种数据类型,即:int(*)(int)是int(int)型函数的指针类型,其中的(*)的括号也是不能省略的,是数据类型就可以作为函数参数,如 void f(int *a,int*b,int(*)(int));
4.函数指针的定义形式看起来比较复杂,所以通常采用typedef来简化。例如:
typedef int(*Fun)(int a,int b);
5.函数名即为函数指针;
【6】简略函数指针表示
1. int(); 是一个无名函数声明,表示一个函数类型;
2. int (*pf)(); 是一个无名函数指针;
3. int (*pf)(); 是一个函数指针定义,pf是函数指针名,该指针的函数类型是int()。
4. int (*func(int))(); 是一个函数声明,即*func(int)的类型是int()。
#include <stdio.h>
#define NULL 0
#define ASGN 1
#define MUL 2
int asgn(int* a, int b) {
return *a = b;
}
int mul(int* a, int b) {
return *a * b;
}
int (*func(int op))(int*, int) {
switch (op) {
case ASGN:
return &asgn;
case MUL:
return &mul;
}
return NULL;
}
int main() {
int i = 0xFEED, j = 0xBEEF;
printf("%x\n", func(ASGN)(&i, j)); // beef
printf("%x\n", func(MUL)(&i, j)); // 8e67a321
printf("%x, %x\n", i, j); // beef, beef
return 0;
}
【7】函数指针的意义:
1.函数类型不能作为参数,如果参数中给出了一个函数类型,则自动转换为函数指针。这种现象称为蜕变。
2.可以同理使用函数引用:
void g();
typedef void Fun();
Fun & f=g;
f();
3. 函数指针使得C++可以沟通其他语言编写的程序,通过函数指针挂接,方便地将其他语言写就的函数和过程引入C++中来;
4. 事实上,因为函数代码是跨进程的,所以通过函数指针可以越过本地进程,通过动态链接库的方式访问共享性质的其他进程(服务器),执行其函数,甚至操作系统函数。
【8】递归函数在运行中,其调用与被调用函数的指令代码是同一个函数副本,只不过各个不同运行中的调用点作为状态的一部分,在栈中被分别保护了起来。因此是C++的函数机制决定论递归操作的可能性与形式;
递归条件应先测试,后递归调用。无条件递归的逻辑错误编译器是检查不出来的,要靠程序员自己把握;
大多数递归函数都能用非递归函数来替代。理论上已经有了递归函数到非递归函数的形式转换方法;在时间上,执行函数的调用与返回的次数明显要大于非递归函数,在空间上,栈空间资源也会遭到空前的劫掠,递归函数在时空开销上的不利局面势必影响性能;非递归函数虽然效率高,但有时却比较难编程,而且相对来说可读性差;
【9】只要参数个数不同,参数类型不同,参数顺序不同,函数就可以重载。只有返回类型不同不允许重载;
C++按下列三个步骤的先后顺序找到匹配并调用函数:
1. 寻找一个严格匹配,如果找到了,就用那个函数;
2. 通过相容类型的隐式转换寻求一个匹配,如果找到了,就用那个函数;
3. 通过用户定义的转换寻求一个匹配,若能查出有唯一的一组转换,就用那个函数;
【10】C++可以给函数声明中的参数使用默认参数值。这样在函数调用时,对应的实参就可以省略。如:
void delay(int a=2); //函数声明时
delay(); //等于调用 delay(2)
一般来说,默认参数值总是在函数声明时描述的,在又有声明又有定义时,默认参数只能置身于声明中;
函数参数默认值只能从后往前设置,如:
void func(int a=q,int b,int c=3,int d=4); //错:b和a的位置违规
void func(int a,int b=2,int c=3,int d=4); //ok
调用时的实参按位置解析,默认实参也只能从后往前逐个替换尾部的“缺漏”,如:
func(2,15,,20); // 错:d不默认则c也无资格默认
【11】函数的声明和定义中,都可以省略形参名,如果在函数定义中也省略形参名,则函数体中就不能使用这个形参了。若开始用了一个函数参数,后来发现不需要用它,就可以将它无名化而不需要改动那些调用该函数的以前版本的代码。特别地,在C++异常机制中,捕获异常是通过类型匹配的方式,知道了类型也就知道了处理的方式,所以常常利用参数省略名称来实现其免于传递的简洁性。
如果两个重载函数做基本相同的事,只不过参数个数不同而已,则还不如用默认参数值的方法来做。如果一个参数的值用来确定不同的操作,则用重载函数较好。