C++学习 八、函数

前言

本片继续学习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(); // 参数列表中的void可省略

函数定义

函数定义时,有返回值的函数,在定义时必须使用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; // equal
}

注意:传结构体的指针时,实参指向的结构体就能够被形参修改。

默认参数

C++中,函数可以设置默认参数,在函数声明时设置:

int newFunc(int a, int b, int threshold=5);

上面的函数,可以只提供参数a,b的值,而省略传递threshold的值,这样函数会使用threshold的默认值5,如果提供threshold的值,就会覆盖默认值。

注意1:函数声明时,带默认值参数的右边的参数必须都带参数值。下面的函数声明将报错:

// int newFunc(int a, int threshold=5, int b) // error!

注意2:函数声明和定义中,只有一个需要带默认值,两个都带默认值就会报错。建议只在函数声明时加入默认值。

注意3:即使函数带了默认值,仍然是根据参数个数从左往右一个一个传值的,不能跳过某个默认参数。

内联函数

内联函数是C++提高程序运行速度的一种方式。

程序运行时,操作系统把程序的二进制机器指令加载到计算机内存中运行,因此每条指令都有特定的内存地址。函数调用时,程序存储调用指令的地址,然后跳转到函数的内存地址,执行函数,然后跳回调用指令的位置。

关键字inline把函数设定为内联函数。内联函数在编译时,不会被编译成函数被调用的指令,而是直接把内联函数体嵌入到调用处,类似于把函数在嵌入处重写。因此,内联函数在运行时,没有一般函数的跳转操作。

因此,内联函数的运行速度比常规函数快,但是内存占用多。如果有10个地方使用了内联函数,那么这个程序就有10个内联函数的代码块。

一般把内联函数的声明与定义结合,放在.h文件里:

inline int newFunc2(int a, int b) {
	return a + b;
}

一般来说,功能简单(代码短),使用次数多的函数更适合作为内联函数;代码比较长,内含循环之类的就不适合。

此外,内联函数不能递归。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值