要点
-
要使用函数,必须完成如下工作:①提供函数原型;②提供函数定义;③调用函数
-
C++对于返回值的类型有一定限制:不能是数组,但可以是其他任何类型——整数、浮点数、指针、甚至结构和对象(虽然C++函数不能直接返回数组,但可以将数组作为结构或对象的组成部分来返回)
-
函数原型:比如:
double add(double a, double b);
需要函数原型的原因:①原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型(如果有的话)以及参数的类型和数量告诉编译器;②如果不定义函数原型,编译器就需要在文件中查找被调用函数,这往往效率不高,因为编译器在搜索文件的剩余部分时必须停止对main()的编译;③更严重地是,函数甚至可能并不在文件中;④C++允许将一个程序放在多个文件中,单独编译这些文件,然后再将它们组合起来,这种情况下,编译器在编译main()时,可能无权访问函数代码
避免使用函数原型的唯一方法是,在首次使用前定义它,但这并不总是可行的。C++的编程风格是将main()放在最前面,因为它通常提供了程序的整体结构 -
函数原型不要求提供变量名,即使提供变量名也不要求原型中的变量名与定义中的变量名相同
-
C++原型与ANSI C原型的比较:①ANSI C中的原型是可选的,但在C++中原型必不可少; ②对于
void say_hi();
在C++中,括号为空与在括号中使用void是等效的——意味着函数没有参数;但在ANSI C中,括号为空意味着不指出参数——这意味着将在后面定义参数列表;③C++中,不指定参数列表时应使用省略号:void say_bye(...);
,这通常仅当与接受可变参数的C函数(如printf()
)交互时才需要这样做 -
原型的功能:原型确保:①编译器正确处理函数的返回值;②编译器检查使用的参数数目是否正确;③编译器检查使用的参数类型是否正确
-
自动类型转换并不能避免所有可能的错误。当较大的类型被自动转换为较小的类型时,有些编译器将发出警告,指出这可能丢失数据
-
仅当有意义时,原型化才会导致类型转换。比如,原型不会将整数转换为结构或指针
-
在编译阶段进行的原型化被称为静态类型检查(static type checking),它可捕获许多在运行阶段非常难以捕获的错误
-
C++标准使用参数(argument)来表示实参,使用参量(parameter)来表示形参
-
对于类似排列组合等以乘除为主的迭代计算,为了避免超越存储范围,可以将乘除运算交替进行
-
有些C++实现不支持
long double
类型,如果这样则使用double
或另想办法 -
在C++中,当且仅当用于函数头或函数原型中,
int* arr
和int arr[]
的含义才是相同的,它们都意味着arr是一个int指针。然而,数组表示法(int arr[]
)提醒用户,arr不仅指向int,还指向数组中的第一个int -
传递常规变量时——按值传递;传递数组时——引用传递。实际上这种区别并不违反C++按值传递的方法,对于传递数组,实际上传递的是数组的首元素地址,也是一个值,此值传递给了一个指针,只不过通过这个值可以对相应元素进行修改
-
字符串与其他数组作为实参传递给函数时的区别:①对于非字符串类型的数组,必须额外提供一个参数指出数组长度,而字符串可以通过
'\0'
来识别串尾,无需额外提供这种参数 -
如果传入函数的数组需要填充或修改,则不用const修饰;如果只是读取数组的内容而不修改,则可使用
const
实现对数组的保护,如显示数组:void show(const double str[], int n);
填充数组:int fill(double str[], int limit);
-
可以把非常量指针的值赋给常量指针
const type*
,但反过来不行:因为如果可以反过来,意味着被赋予值的非常量指针可以对变量值进行修改,这样const也就没意义了。如果一定要这么做,使用运算符const_cast
-
关于常量指针的几个微妙问题
pt的声明并不意味着它指向的值实际上就是一个常量,而只是意味着对pt而言,这个值是常量。可以直接通过age变量来修改age的值,但不能使用pt指针来修改它int age = 39; const int* pt = &age; *pt = 20; //INVALID age = 20; //VALID
进入两级间接关系时,与一级间接关系一样将const和非const混合的指针赋值方式将不再安全。如果允许这么做,那么就会有以下代码:
const int** pp2; int* p1; const int n = 13; pp2 = &p1; //not allowed, but suppose it were *pp2 = &n; //valid *p1 = 10; // valid, but changes const n
仅当只有一层间接关系时,才可以将非const地址或指针赋给const指针
-
尽可能使用const的理由:①可以避免由于无意间修改数据而导致的编程错误;②使用const使得函数能够处理const和非const实参,否则将只能接受非const数据
-
对于
void show_array(const double ar[], int n);
,这里的数组元素是基本类型,但如果是指针或指向指针的指针,则不能使用const -
函数与二维数组:以下表示二维数组的都等价:
int sum(int (*ar2)[4],int size);
、int sum(int ar2[][4],int size);
由于其中的ar2相当于指向指针的指针,故没有使用const -
变量pstr的作用域为buildstr函数内,因此该函数结束时,pstr(而不是字符串)使用的内存将会被释放,但由于函数返回了pstr的值,因此程序仍可以通过main()中的指针ps来访问新建的字符串。
char* buildstr(char c, int n){ char* pstr = new char[n+1]; pstr[n] = '\0'; while(n-->0){ pstr[n] = c; } return pstr; }
-
函数与结构:与数组名就是数组的数组的第一个元素不同的是,结构名只是结构的名称,要获得结构的地址,必须使用地址运算符&
-
string作为传递给函数的实参时,是按值传递;模板array也是如此
-
模板array并非只能存储基本数据类型,它还可以存储类对象
-
函数指针使用流程:①获取函数的地址;②声明一个函数指针;③使用函数指针来调用函数
-
注意:对于函数指针pf,如
double (*pf)(int);
,以下两种使用方法都可以:①double x =(*pf)(5);
②double x =pf(5);
-
可以使用auto或typedef对类型名字进行简化,这里我给出本章习题10的代码,基本就是函数指针的典型用法了,可以自行体会
#include<iostream>
#include<cstdlib>
using namespace std;
typedef double (*calc_fun)(double,double);
double add(double,double);
double sub(double,double);
double multiply(double,double);
double divide(double,double);
double calculate(double,double,calc_fun);
int main(){
calc_fun pf[4]{add, sub, multiply, divide};
double x,y;
cout<<"Please enter x and y (q to quit): ";
const char* calc_means[4]{"A+B= ","A-B= ","A*B= ","A/B= "};
while(cin>>x>>y){
for(int i=0; i<4; ++i){
cout<<calc_means[i];
cout<<(*pf[i])(x,y)<<endl;
}
cout<<"Next Round enter x and y (q to quit): ";
}
cout<<"Done\n";
return 0;
}
double add(double a, double b){
return a+b;
}
double sub(double a, double b){
return a-b;
}
double multiply(double a, double b){
return a*b;
}
double divide(double a, double b){
if(b == 0.0){
cout<<"Cannot devide By Zero!\n";
exit(-1);
}
return a / b;
}
习题
习题参考代码见我的github(上传后会把链接打上)
欢迎各位大佬们于评论区进行批评指正~