基础知识
使用函数必须完成三项工作:提供函数定义;提供函数原型;调用函数。 库函数是已经定义和编译好的函数,同时可以使用标准库头文件提供其原型,因此只需正确地调用这种函数即可。
定义函数:
函数可以分为没有返回值的函数和有返回值的函数。没有返回值的函数被称为void函数。
void functionName (parameterList)
{
statement (s)
return;
}
typeName functionName (parameterList)
{
statements
return value;
}
parameterList
指定了传递给函数的参数类型和数量。可选的返回语句标记了函数的结尾,否则,函数将在右花括号处结束。有返回值的函数,必须使用返回语句将值返回给调用函数。值本身可以是常量、变量,也可以是表达式,只是类型必须为typeName
类型或者可被转换成typeName
。(例如,如果声明的返回类型是double,而函数返回一个int表达式,则该int值将被强制转换成double类型) C++的返回值类型不能是数组,但可以将数组作为结构或对象组成部分来返回。 通常,函数通过将返回值复制到指定的CPU寄存器或内存单元来将其返回。随后,调用程序将查看该内存单元。返回函数和调用函数必须就该内存单元中存储的数据的类型达成一致。函数原型将返回值类型告知调用程序,而函数定义命令被调用函数应返回什么类型的数据。
函数原型和函数调用
原型描述了函数到编译器的接口,它将函数返回值的类型以及参数的类型和数量告诉编译器。 函数原型是一条语句,因此必须以分号结束。 函数原型不要求提供变量名。通常在原型的参数列表中,可以包括变量名,也可以不包括。原型的变量名相当于占位符,因此不必与函数定义中的变量名相同。 函数原型可以极大地降低程序出错的几率。原型可以确保:1)编译器正确处理函数返回值;2)编译器检查使用的参数数目是否正确;3)编译器检查使用的参数类型是否正确。如果不正确,则转换成正确的类型(如果可能的话)。 通常,原型自动将被传递的参数强制转换为期望的类型。仅当有意义时,原型化才会导致类型转换(如:原型不会将整数转换为结构或指针)。 在编译阶段进行的原型化被称为静态类型检查。可以看出,静态类型检查可捕获许多在运行阶段非常难以捕获的错误。
函数参数和按值传递
C++通常按值传递参数,这意味着将数值参数传递给函数,而后者将其赋给一个新的变量。 用于接收传递值的变量被称为形参;传递给函数的值被称为实参。C++标准使用参数(argument)来表示实参,使用参量(parameter)来表示形参,因此参数传递将参量赋给参数。 函数可以有多个参数。在调用函数时,只需用逗号将这些参数分开即可。 默认情况下,C++函数按值传递参数,这意味着函数中定义的形参是新的变量。因此C++通过使用拷贝,保护了原始数据的完整性。
函数和数组
// 函数头
int sum_arr ( int arr[], int n );
// arr实际上并不是数组,而是一个指针。但是我们可以将它看做数组
大多数情况下,C++和C语言一样,将数组名视为指针。C++将数组名解释为第一个元素的地址。 cookies == &cookies[0]
以上规则有两种情况例外:1)数组声明使用数组名来标记存储位置;2)对数组名使用sizeof将得到整个数组的长度(以字节为单位)。 在C++中,当且仅当用于函数头或函数原型中,int *arr和int arr[]的含义才是相同的,他们都意味着arr是一个int指针。 获取数组长度:
//int arr[]
int len = sizeof(arr) / sizeof(int);
将指针(包括数组名)加1,实际上是加上了一个与指针指向的类型的长度(以字节为单位)相等的值。对于遍历数组而言,使用数组加法和数组下标是等效的。 函数传递常规变量时,函数将使用该变量的拷贝;但传递数组时,函数将使用原来的数组。 将数组地址作为参数可以节省复制整个数组所需的时间和内存。 一般来说,函数传递参数时需要确保显示函数不修改原始数据,除非函数的目的就是修改传递给它的数据。使用普通参数时,这种保护将自动实现,因为C++按值传递数据,而且函数使用数据的拷贝。不过,接收数组名的函数将使用原始数据。为了防止函数无意中修改数组内容,可在声明形参的时候使用关键字const。
int sum_arr ( const int arr[], int n );
//该声明表示指针arr指向的是常量数据,可以使用像arr[0]这样的值,但不能修改。sum_arr()将数组视为只读数据。
对于处理数组的C++函数,必须将数组汇总的数据种类、数组的起始位置和数组中元素数量提交给它;传统的C/C++方法是,将指向数组起始处的指针作为一个参数,将数组长度作为第二个参数(指针指向数组的位置和数据类型)。还有另一个方法,即指定元素区间(range),可以通过传递两个指针来完成:一个指针标识数组的开头,另一个指针标识数组的尾部。
指针和const
可以用两种不同的方式将const关键字用于指针。 第一种方法是让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值。
int age = 39;
const int * pt = &age;
//pt指向一个const int(这里是39),因此不能使用pt来修改这个值。也就是说,*pt的值为const,不能被修改。
//这个声明并不意味着它指向的值实际上就是一个常量,只是意味着对pt而已,这个值是常量。可以直接通过修改age变量来修改age的值,但不能使用pt指针来修改它。
我们可以将常规变量的地址赋给常规指针;可以将常规变量的地址赋给了指向const的指针;还可以将const变量的地址赋给质量const的指针。但是不能将const的地址赋给常规指针。 如果数据类型本身并不是指针,则可以将const数据或非const数据的地址赋给指向const的指针,但只能将非const数据的地址赋给非const指针。 尽可能使用const:这样可以避免由于无意间修改数据而导致的编程错误;使用const使得函数能够处理const和非const实参,否则将只能接受非const数据。 如果条件允许,则应将指针形参声明为指向const的指针。
int sloth = 3;
const int * ps = &sloth; // a pointer to const int
int * const finger = &sloth; // a const pointer to int
//最后一个声明中,由于const的位置不同,这种声明格式使得finger只能指向sloth,但允许使用finger来修改sloth的值。
//中间的声明不允许使用ps来修改sloth的值,但允许将ps指向另一个位置。简而言之,finger和*ps都是const,而*finger和ps不是。
函数和二维数组
eg:
int data[3][4] = {{1,2,3,4},{9,8,7,6},{2,4,6,8}};
int total = sum(data, 3);
int sum( int ( *arr ) [4], int size );
int sum( int arr[][4], int size );
上述两个原型都指出,arr是指针而不是数组。指针类型指出,它指向由4个int组成的数组。 int *arr[4]; //声明一个由4个指向int的指针组成的数组,声明的是数组 int (*arr)[4]; //声明一个指向由4个int组成的数组的指针,声明的是指针 等价于 int arr[][4]
函数和C-风格字符串
如果要将字符串作为参数传递给函数,则表示字符串的方式有三种:1)char数组。2)用引号括起的字符串常量(也称字符串字面值)。3)被设置为字符串的地址的char指针。 上述三种选择的类型都是char指针(char* ),实际上传递的是字符串第一个字符的地址。这意味着函数原型应将其表示字符串的形参声明为 char* 类型。 C-风格字符串与常规char数组之间的一个重要区别是,字符串有内置的结束字符(包含字符,但不以空值字符结尾的char数组只是数组,而不是字符串)。这意味着不必将字符串长度作为参数传递给函数,函数可以检测字符串的每一个字符,知道遇到结尾的空值字符为止。
while ( *str )
{
statements
str++;
}//处理字符串中字符的标准方式
函数无法返回一个字符串,但可以返回字符串的地址,这样效率更高。
函数和结构
与数组不同的是,结构名只是结构的名称,要获得结构的地址,必须使用地址操作符&; 结构可以按值传递,也可以传递地址来在函数中处理结构。 当结构比较小时,按值传递结构最合理。 可以通过传递结构的地址而不是整个结构来节省复制结构的时间和空间。
递归
C++函数有一个特点——可以调用自己(与C语言不通的是,C++不允许main()调用自己),这种功能被称为递归。
void recurs (argumentlist)
{
statements1
if(test)
recurs(arguments)
statements2
}
函数指针
函数也有地址,函数的地址是存储其机器语言代码的内存的开始地址。 与直接调用另一个函数相比,通过传递函数地址的方式来调用另一个函数的方式比较笨拙,但它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。 步骤:1)获取函数的地址;2)声明一个函数指针;3)使用函数指针来调用函数。 获取函数地址:使用函数名即可(后面不跟参数)。
int think();
process(think); // 传递的think函数地址
thought(think());//传递的think函数返回值
声明函数指针:声明指向函数的指针必须指定指针指向的函数类型,这意味着声明应指定函数的返回类型以及函数的特征标(参数列表)。
double pam(int);//函数原型
double (*pf)(int);//对应的函数指针
使用指针来调用函数:(*pf)与函数名相同,因此使用(*pf)时,只需将它看做函数名即可
double x = pam(4);
double y = (*pf)(5);
//实际上,C++也允许像使用函数名那样使用pf
double y = pf(5)