前言
本片继续学习C++,函数。
函数使用
函数是C++的基本编程模块,以便于程序的模块化和代码复用。
函数声明(函数原型)
要使用函数,首先要提供函数原型,即声明函数,格式为 TypeName FuncName (Parameters) 即 返回值类型 函数名 (参数列表)
函数原型为编译器提供了函数名,函数返回值类型,参数类型和数量,用以编译器检查和处理。下面是一个函数声明的例子:
int func1(int a, int b);
上面这个函数声明了一个fuc1函数,它依次接受两个int类型的参数,返回int类型。
函数声明中的参数名可以省略,上面的函数声明等价于:
int func1(int, int);
C++中,如果向函数传递了不同类型的参数(可转换),将强制转换为期望的类型;如果返回值与返回类型不相同(可转换),则强制转换为返回类型:
int func1(int a, int b) {
return a + b;
}
std::cout << func1(2.2, 3.3);
上面调用函数的时候,func(2.2, 3.3) 中的’a’=2, ‘b’=3, 返回值为5。
此外,C++允许不带返回值和没有参数的函数,使用void 关键字标识:
int func2(void);
void func3(int);
void func4();
函数定义
函数定义时,有返回值的函数,在定义时必须使用return 返回一个值,不带返回值的函数原型则不能使用return 。
函数内部可以再定义其它变量,但这些局部变量在函数外部是不可见的,函数生命周期结束后,这些局部变量就会被销毁。
函数定义示例:
int func2(void) {
int a;
return a;
}
void func3(int a) {
a *= 2;
}
注意:C++的函数返回值不能是数组和字符串,但可以是其它整数、浮点数、指针、结构体、对象等类型。(比较有趣的一点,虽然不能返回数组,但是可以把数组放到结构体或者对象里返回)
此外,函数声明和定义可以放在一起,此时函数调用必须放在定义的后面:
int func5(int a, int b) {
int c = a + b;
return c;
}
func5(2, 3);
此外,为了模块化,还是尽量把函数声明和定义分开。一般函数声明放在.h 头文件, 定义放在.cpp 源文件。
函数调用
在程序中,通过函数名和参数调用函数:
void main() {
int x=2;
int y=3;
int sum;
sum = func5(x, y);
}
调用函数的参数要与函数原型的参数个数相同,参数类型相同或者能够转换。调用带有返回值的函数时,可以使用变量把返回值保存下来。
函数参数,按值传递
C++中,调用函数的参数按值传递,参数以值的形式传到函数中。调用函数的参数叫实参,函数内部的参数叫形参。
传值
上面的程序sum = func5(x, y); 中,x, y是实参,a, b是形参。 调用函数func5时,创建形参a, b,把实参x, y的值赋给形参a, b,然后在函数体中使用形参进行计算。由于函数只传递实参的值而非变量的地址给形参,因此修改形参并不会影响实参。
下面这个示例函数并不能交换实参的值:
int paramFunc1(int a, int b) {
int temp = a;
a = b;
b = temp;
return 1;
}
函数体中的创建的形参和变量都是局部变量,在函数结束后将自动销毁。因此,示例函数中,a,b,temp在离开paramFunc1后都被销毁。
传址
由于函数参数的传递实质都是传值,因此在函数体内修改形参不会影响函数外的实参。
然而,如果给函数传递的参数是一个地址(指针),虽然修改形参的值仍然不会改变实参的值,但修改形参指针指向的值,实参指针指向的值也会发生改变。(直接修改了内存空间的存储数据)。
下面这个示例函数能够将实参指向的值进行交换:
int paramFunc2(int* pa, int* pb) {
int temp = *pa;
*pa = *pb;
*pb = temp;
return 1;
}
函数与数组
函数经常需要处理数组这样的复杂数据结构。
数组篇中提到,C++把数组名当作数组首元素的地址,并且可以通过指针处理数组。因此,函数的常见“传数组”方式,也是通过指针:
void arrayFunc1(int arr[], int n);
void arrayFunc2(int* arr, int n);
参数列表中的arr[], *arr 实际上都在声明arr是int 类型的指针。arr[] 更侧重于强调arr是数组的首元素地址,*arr 更侧重于强调arr是一个指针。还需要一个参数int n 指出数组的长度。
注意:只有在函数的参数列表中,才有数组表示法arr[]
将数组地址作为参数传给函数,节省了拷贝数组元素的时间和内存,但可能造成原始数组数据的错误修改。以后会记录使用const关键字保护函数实参的方法。
函数与字符串
函数如果要传递字符串参数,有两种方法。第一种方法与数组类似,传递字符串的地址:
int strFunc1(char* str);
int strFunc2(char str[]);
C++认为char数组名,字符串常量,char指针都表示字符串的首字符地址,而碰到的第一个空字符是停止符,因此函数参数不需要指出字符串的长度。
第二种传参方法,就是使用C++的string类,这个就非常简单了:
int strFunc3(string str);
传string对象是传值的方式,不会影响实参。
函数与结构体
函数传递结构体参数与传递普通类型参数其实是相似的,都是按值传递。
比较重要的一点是,如果结构体中有比较大的数据(数组,字符串等),可以通过传结构体的指针来节省拷贝时间和内存,并使用间接成员运算符-> 获得成员:
struct myStruct {
int a;
}
void structFunc(myStruct* st) {
st->a = 1;
*st.a = 1;
}
注意:传结构体的指针时,实参指向的结构体就能够被形参修改。
默认参数
C++中,函数可以设置默认参数,在函数声明时设置:
int newFunc(int a, int b, int threshold=5);
上面的函数,可以只提供参数a,b的值,而省略传递threshold的值,这样函数会使用threshold的默认值5,如果提供threshold的值,就会覆盖默认值。
注意1:函数声明时,带默认值参数的右边的参数必须都带参数值。下面的函数声明将报错:
注意2:函数声明和定义中,只有一个需要带默认值,两个都带默认值就会报错。建议只在函数声明时加入默认值。
注意3:即使函数带了默认值,仍然是根据参数个数从左往右一个一个传值的,不能跳过某个默认参数。
内联函数
内联函数是C++提高程序运行速度的一种方式。
程序运行时,操作系统把程序的二进制机器指令加载到计算机内存中运行,因此每条指令都有特定的内存地址。函数调用时,程序存储调用指令的地址,然后跳转到函数的内存地址,执行函数,然后跳回调用指令的位置。
关键字inline 把函数设定为内联函数。内联函数在编译时,不会被编译成函数被调用的指令,而是直接把内联函数体嵌入到调用处,类似于把函数在嵌入处重写。因此,内联函数在运行时,没有一般函数的跳转操作。
因此,内联函数的运行速度比常规函数快,但是内存占用多。如果有10个地方使用了内联函数,那么这个程序就有10个内联函数的代码块。
一般把内联函数的声明与定义结合,放在.h文件里:
inline int newFunc2(int a, int b) {
return a + b;
}
一般来说,功能简单(代码短),使用次数多的函数更适合作为内联函数;代码比较长,内含循环之类的就不适合。
此外,内联函数不能递归。
|