【C语言从不挂科到高绩点】15-C语言中的函数

Hello!彦祖们,俺又回来了!!!,继续给大家分享 《C语言从不挂科到高绩点》课程!! 本节将为大家讲解C语言中的函数:

本套课程将会从0基础讲解C语言核心技术,适合人群:

  1. 大学中开设了C语言课程的同学
  2. 想要专升本或者考研的同学
  3. 想要考计算机等级证书的同学
  4. 想要从事C/C++/嵌入式开发的同学

================点个关注吧================

=========================================

七、C 语言中的函数

7.1 函数的概述

7.1.1 函数的分类

C 语言程序是由函数组成的,我们写得程序都是由 main 函数开始执行。函数 C 语言程序的基本模块,用于完成特定功能的程序代码单元。

通常将完成某项功能的操作封装到一起,形成一个函数:比如可以将查询数组中是否包含某个元素的操作,封装成函数

函数从定义角度,可以分为系统函数和用户自定义函数:

  1. 系统函数(库函数):有编译系统提供的,用户不需要自己定义的函数,可以直接使用它们。比如:scanf(),printf()等
  2. 用户定义函数:用户自己定义,用来解决用户特定需求的函数

7.1.2 函数的作用

先看一段代码

cpp 复制代码
#include <stdio.h>

int main(){


	printf("----------乔峰走在路上遇到慕容复 ---------\n");
	printf("1.扎马步\n");
	printf("2.双手左推\n");
	printf("3.双手右推\n");
	printf("3.2.双手上下翻滚\n");
	printf("4.双手前腿\n");

	printf("一条龙出来了,慕容复嗝屁了\n");



	printf("--------乔峰走在路上遇到了慕容博-------------\n");	
	printf("1.扎马步\n");
	printf("2.双手左推\n");
	printf("3.双手右推\n");
	printf("3.2.双手上下翻滚\n");
	printf("4.双手前腿\n");

	printf("一条龙出来了,慕容博嗝屁了\n");


	printf("--------乔峰走在路上遇到了鸠摩智-------------\n");	
	printf("1.扎马步\n");
	printf("2.双手左推\n");
	printf("3.双手右推\n");
	printf("3.2.双手上下翻滚\n");
	printf("4.双手前腿\n");

	printf("一条龙出来了,鸠摩智嗝屁了\n");


	return 0;
}

当下的代码存在的问题: 如果乔峰遇到了 100 个敌人,那么释放降龙十八掌的代码,我们得写 100 次。假如释放降龙十八掌的方式改变了,这 100 次的代码都得变

当下的写法:代码的重复利用性不高,可维护性也不高。对于乔峰这种需要频繁使用降龙十八掌的情况,我们就可以把降龙十八掌封装成函数。

因此:对于一些操作需要重复进行的,并且可能存在修改的情况的。那么我们可以将这些操作封装成函数。

cpp 复制代码
#include <stdio.h>

void xlsbz(){
	printf("1.扎马步\n");
	printf("2.双手左推\n");
	printf("3.双手右推\n");
	printf("4.双手前腿\n");
}


int main(){


	printf("----------乔峰走在路上遇到慕容复 ---------\n");
	xlsbz();

	printf("一条龙出来了,慕容复嗝屁了\n");



	printf("--------乔峰走在路上遇到了慕容博-------------\n");	
	xlsbz();
	
	printf("一条龙出来了,慕容博嗝屁了\n");


	printf("--------乔峰走在路上遇到了鸠摩智-------------\n");	
	xlsbz();

	printf("一条龙出来了,鸠摩智嗝屁了\n");


	return 0;
}

【函数的作用】

通过代码的对比,我们可以看出函数可以让程序更加模块化,有利于阅读,修改和维护

函数是一段封装好的,可以重复利用的代码块,不需要编写大量的重复的代码,当功能需要拓展或者修改时,只需要对函数中的代码进行修改即可。

  1. 函数提高了代码的复用性
  2. 函数提高了代码的可维护性。

7.2 函数的定义

当系统提供的函数,满足不了需求的时候,这个时候我们就可以函数。

函数定义的格式:

cpp 复制代码
返回值类型   函数名(参数列表){
    实现函数的功能代码;

    return 结果;
}
  1. 返回值类型:就是执行这个函数得到结果的数据类型。看执行这个函数,是否需要得到一个结果,如果不需要返回值类型就写 void,如果需要一个结果,此处就写上结果的数据类型。比如:如果执行这个函数得到的是一个 int 类型的整数。那么此处就写 int。如果需要结果,在函数中需要使用 return 将结果返回
  2. 函数名:给函数取的名字,方便调用。函数名的命名规则要遵循标识符的命名规则。
  3. 参数列表:执行函数是否需要依赖其他的可变数据才能完成。如果需要,就将可变数据定义成变量作为参数,写到括号中。如果有多个数据,用逗号隔开。
    1. 固定不变的数据,直接定义在函数内
    2. 可变的数据,可以定义成函数的参数,由外界传入值到参数
  1. return 结果:如果返回值类型为 void,此处的 return 结果可以省略不写,也可以直接写 return; 如果返回值类型不为 void,此处 return 要返回一个和返回值类型对应的数据。

【案例】自定义函数,用来计算任意两个整数的和

cpp 复制代码
#include <stdio.h>

// 执行计算两个数的和的函数,前提是,需要知道计算哪两个数的和
// 这两个数是不确定的,有可能会计算1,2的和,也有可能会计算5,8的和,所以这两个数是可变的
// 对于可变的数据,可以定义成参数,写到括号中
// 那么,在调用函数的时候,必须指定两个参数的具体值
int add(int num1,int num2){
    // 具体函数的功能:计算两个数的和
    int result = num1+num2;

    // return 会将结果,返回给函数调用的地方
    return result;// 返回值类型是 int类型,那么此处就需要return 一个int类型的数据
}

int main(){

    // 函数调用,格式:  函数名(参数)
    // 参数: 函数在定义的时候,括号中写了多少个参数,在调用的时候,
    // 此处就需要传递几个参数值。参数值数据类型要和函数定义的时候参数类型一致

    // 如果函数有返回值类型,那么此处可以定义对应类型的变量,取接收函数返回的结果
    int res = add(3,4);//  3会传递给num1  4会传递给num2

    printf("计算的结果为:%d\n",res);
    
    return 0;
}

【练习】自定义一个计算圆面积的函数

cpp 复制代码
#include <stdio.h>

#include <math.h>

float area(float r){

	// float result = 3.14*r*r;
	// M_PI 是C语言系统提供的宏常量 pi
	printf("%lf",M_PI);
	float result = M_PI *r*r;
	// 在函数中,一旦执行了return语句,那么就意味着函数结束了
	return result;  // return如果还有其他语句,这些语句都不会再执行了

	printf("O(∩_∩)O哈哈~\n"); // return后面的代码,执行不到
}

int main(){
	float r = 0;
	printf("请输入半径:");
	scanf("%f",&r);
	// 调用area() 函数之后,函数中return结果,会返回到此处
	// 可以使用变量将返回的结果接收
	float res = area(r);  // 函数的返回值结果,如果不需要使用了,就可以不用接收
	printf("圆的面积为:%f\n", res);
	printf("圆的面积为:%f\n",area(r));

	return 0;
}

注意:函数会在执行 return 语句之后,就结束运行。如果 return 后面还有其他代码,那么这些代码都不会再执行。

【练习】定义一个计算 1+2+3+....+n 的和的函数

cpp 复制代码
#include <stdio.h>

// 定义一个计算1+2+3+...+n的和的函数

int sum(int n){

	int sum = 0;
	for (int i = 1; i <=n; ++i)
	{
		sum += i;
	}


	return sum;

}

int main(){

	printf("请输入一个整数:");
	int n=0;
	scanf("%d",&n);

	// 定义接收函数返回值的变量名时,需要避开函数名
	//int sum = sum(n);
	int result = sum(n);

	printf("1到%d累加的和为:%d\n", n,result);


	return 0;
}

函数定义时需要注意的问题:

  1. 函数必须在调用之前去定义或者声明。

比如:下面的情况,将函数的定义放在 main 后面

cpp 复制代码
#include <stdio.h>



int main(){

	printf("请输入一个整数:");
	int n=0;
	scanf("%d",&n);

	// 定义接收函数返回值的变量名时,需要避开函数名
	//int sum = sum(n);
	int result = sum(n);

	printf("1到%d累加的和为:%d\n", n,result);


	return 0;
}


// 定义一个计算1+2+3+...+n的和的函数

int sum(int n){

	int sum = 0;
	for (int i = 1; i <=n; ++i)
	{
		sum += i;
	}


	return sum;

}

sum 函数在 main 函数后面定义的,也就是在调用 sum 函数之后,才去定义。此时编译时,就会出现如下错误:

如果出现上图中的问题可能得原因:

  1. 调用函数的时候,函数名写错了

  2. 在调用之前没有声明或者定义。

  3. 在同一个项目中,函数名不能重复

比如:下面的代码中,有两个 sum 函数

cpp 复制代码
#include <stdio.h>


int sum(int num1,int num2){
    // 具体函数的功能:计算两个数的和
    int result = num1+num2;

    // return 会将结果,返回给函数调用的地方
    return result;// 返回值类型是 int类型,那么此处就需要return 一个int类型的数据
}
// 定义一个计算1+2+3+...+n的和的函数

int sum(int n){

	int sum = 0;
	for (int i = 1; i <=n; ++i)
	{
		sum += i;
	}


	return sum;

}

int main(){

	printf("请输入一个整数:");
	int n=0;
	scanf("%d",&n);

	// 定义接收函数返回值的变量名时,需要避开函数名
	//int sum = sum(n);
	int result = sum(n);

	printf("1到%d累加的和为:%d\n", n,result);


	return 0;
}

那么编译的时候,就会出现如下错误

7.3 函数的声明

C 语言中的代码从上到下依次执行,原则上函数的定义要出现在函数调用之前,否则就会出编译错误。但是实际上,经常会出现在函数定义之前使用它们,这个时候就需要提前声明函数

所谓的函数声明,就是告诉编译器,现在要使用这个函数,你现在找不到没关系,先不要报错,函数定义后面会补上。

函数的声明实际上就是去掉函数定义中的函数体,并在后面加上分号;

cpp 复制代码
返回值类型 函数名(参数列表);

【演示案例】

cpp 复制代码
#include <stdio.h>
#include <math.h>

// 函数声明: 返回值类型  函数名(参数列表);
float area(float r); // 函数声明和函数定义的返回值类型,函数名,参数列表要一致
// 在 函数调用之前,先声明area函数,告诉编译器,如果调用的时候找不到函数的定义没关系
// 先让编译通过,后续会补上函数的定义。

// 声明计算两个数和的函数
int add(int num1,int num2);

int main(){

	float result = area(3); // 调用函数
	printf("%f\n", result);
	return 0;
}


// 函数定义
float area(float r){

	// float result = 3.14*r*r;
	// M_PI 是C语言系统提供的宏常量 pi
	//printf("%lf",M_PI);
	float result = M_PI *r*r;
	// 在函数中,一旦执行了return语句,那么就意味着函数结束了
	return result;  // return如果还有其他语句,这些语句都不会再执行了

	printf("O(∩_∩)O哈哈~\n"); // return后面的代码,执行不到
}

int add(int num1,int num2){
    // 具体函数的功能:计算两个数的和
    int result = num1+num2;

    // return 会将结果,返回给函数调用的地方
    return result;// 返回值类型是 int类型,那么此处就需要return 一个int类型的数据
}

需要注意的问题:给了函数声明,后续一定得补上函数的定义。,比如下面代码中,只有 add 函数的声明,没有 add 函数的定义

cpp 复制代码
#include <stdio.h>
#include <math.h>

// 函数声明: 返回值类型  函数名(参数列表);
float area(float r); // 函数声明和函数定义的返回值类型,函数名,参数列表要一致
// 在 函数调用之前,先声明area函数,告诉编译器,如果调用的时候找不到函数的定义没关系
// 先让编译通过,后续会补上函数的定义。

// 声明计算两个数和的函数
int add(int num1,int num2);

int main(){

	float result = area(3); // 调用函数
	printf("%f\n", result);

	add(4,6); // 调用函数
	return 0;
}


// 函数定义
float area(float r){

	// float result = 3.14*r*r;
	// M_PI 是C语言系统提供的宏常量 pi
	//printf("%lf",M_PI);
	float result = M_PI *r*r;
	// 在函数中,一旦执行了return语句,那么就意味着函数结束了
	return result;  // return如果还有其他语句,这些语句都不会再执行了

	printf("O(∩_∩)O哈哈~\n"); // return后面的代码,执行不到
}

此时在编译的时候,就会出现如下错误:

这个错误就是找不到 add 函数的引用。

函数的声明,实际上就是函数定义去掉函数体后的内容。

7.4 函数的原型

函数的声明通常也称为函数的原型。

所谓的函数原型,就相当于告诉了编译器有关函数的信息,让编译器知道函数的存在,以及存在形式。即使函数暂时没有定义,编译器也知道该如何使用它,所以编译不会出错。

在写函数声明(函数的原型)时,函数的参数名是可以省略不写的。可以直接写成如下方式:

cpp 复制代码
#include <stdio.h>
#include <math.h>

// 函数声明(函数原型)时,函数的参数列表中的参数名可以省略
float area(float); 
// 声明计算两个数和的函数
int add(int ,int);

int main(){

	float result = area(3); // 调用函数
	printf("%f\n", result);

	add(4,6); // 调用函数
	return 0;
}


// 函数定义
float area(float r){

	float result = M_PI *r*r;
	
	return result;  /

	printf("O(∩_∩)O哈哈~\n"); // return后面的代码,执行不到
}

int add(int num1,int num2){
    // 具体函数的功能:计算两个数的和
    int result = num1+num2;

    // return 会将结果,返回给函数调用的地方
    return result;// 返回值类型是 int类型,那么此处就需要return 一个int类型的数据
}

7.5 函数的形参和实参

7.5.1 形参和实参的概述

如果将函数比作一台机器,那么参数就是原材料,返回值就是最终的产品,从一定程度上讲函数的作用就是根据不同的参数产生出不同的返回值。

C 语言中函数的参数会在两个地方出现,分别是函数定义的时候以及函数调用的时候。

  1. 函数定义的地方的参数称为形式参数。
  2. 函数调用的地方的参数称为实际参数。

形式参数(形参):在函数定义中出现的参数,可以看成一个占位符,它没有数据 ,只有等到函数调用的时候,接收传递进来的数据,所以称为形式参数。

实际参数(实参):函数在调用的时候给出的具体数据的参数。它包含实际的数据。这个数据会传递给形式参数。会被函数内部的代码使用。因为有实际的数据,所以称为实际参数。

形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。

cpp 复制代码
#include <stdio.h>

// 函数定义时,写在括号中的参数,称为形参
int sum(int m){  // 此处的m是形式参数  ,此时没有具体的值

	int result = 0;
	for (int i = 0; i <=m; ++i)
	{
		result += i;
	}

	m = 100;  // 修改形式参数的值,形式参数改变了,不会影响实际参数
	return result;
}// 当函数结束之后,出了这个大括号,形式参数m的内存空间以及值都会被释放掉
// 所以形式参数只能在当前函数内部使用,在当前函数外部使用不了



int main(){
	printf("请输入一个值:");
	int m = 0;
	scanf("%d",&m); // 输入3

	// 函数调用的时候,括号中写的参数是实际参数
	int result = sum(m); // 此处的n是实际参数,包含了具体值
	// 函数发生调用时,实际参数会将值传递给形式参数
	// 当实际参数将值传递给形式参数之后,实际参数和形式参数之间就没有联系了。

	printf("result=%d\n",result);
	printf("m=%d\n",m);
	return 0;
}

需要注意的问题 [ 面试题:形式参数和实际参数的区别 ]:

  1. 当前函数参数传递的方式,是以值传递的方式进行的。实际参数将值传递给形式参数。这种传递方式:形式参数的改变不会影响实际参数。在函数内部修改形式参数的值,不会影响函数外部的实际参数的值。
  2. 形式参数只有在函数调用的时候(程序运行起来调用函数)的时候才会分配内存。函数调用结束之后,立刻释放内存。所以形式参数只能在函数内部使用,不能在函数外部使用。
  3. 函数调用中发生的数据传递是单向的。只能将实参的值传递给形参,不能把形参的值传递给实参。
  4. 一旦实际参数完成数据传递,也就是将值给到形参。实参和形参就没有关系了。所以在函数调用过程中,形式参数的改变不会影响实际参数。
  5. 形式参数和实际参数名字可以相同,但是他们之间是相互独立的,互不影响。实际参数在所调用的函数的外部有效,形式参数在调用的函数内部有效。

7.5.2 数组作为函数的参数

当函数的参数是数组是,需要注意的问题是,不能在函数内部计算数组类型形式参数的长度。因为形式参数只有在调用的时候才会分配内存,所以在编译的时候无法使用 sizeof 去计算字节大小。比如下面的代码:

cpp 复制代码
#include <stdio.h>


// 编写一个函数,计算数组中元素累加的和
int sum(int ary[5] ){  // ary是形参,只有在函数被调用的时候才会分配内存

	// 求数组的长度
    // 编译时,ary形参,没分配到内存,所以无法使用sizeof去计算
	int len = sizeof(ary)/sizeof(int); 

	int sum = 0;
	for (int i = 0; i < len; ++i)
	{
		sum += ary[i];
	}
	return sum;
}

int main(){

	int ary[5] = {1,2,3,4,5};
	int result = sum(ary);
	printf("result=%d\n", result);
	return 0;
}

编译时,就会出现如下错误:

解决办法:通常情况下,如果数组要作为参数传递到函数时,函数的形参除了要加数组以外,同时还需要将数组的长度一起传递过去

cpp 复制代码
#include <stdio.h>


// 编写一个函数,计算数组中元素累加的和
int sum(int ary[] ,int len){ // 数组作为参数时,将数组长度一起传递过去
	int sum = 0;
	for (int i = 0; i < len; ++i)
	{
		sum += ary[i];
	}
	return sum;
}

int main(){

	int ary[5] = {1,2,3,4,5};
	// 先计算数组的长度
	int len = sizeof(ary)/sizeof(int);
	int result = sum(ary,len);
	printf("result=%d\n", result);
	return 0;
}

另外特别注意的是:当数组作为形式参数时后,函数调用时,传递不是数组中所有的元素数据,而是数组的地址值 。函数调用时,会将实际参数的地址复制给形式参数。形式参数和实际参数操作的是同一个内存空间

此时如果在函数中通过形式参数,修改内存空间中的数据,实际上也就是修改了实际参数内存空间中的数据。

cpp 复制代码
#include <stdio.h>

// 数组作为函数参数时,传递的是数组的地址值
// 函数调用的时候,会将实际数组参数的地址值给到形式数组参数
void change_ary(int ary[]){
	// 形式参数和实际参数共用一个地址空间
	ary[0] = 123; 
	// 此处修改形式参数的空间内容,实际上也就修改了实际参数的空间内容
}

int main(){
	int ary[] = {1,2,3};

	printf("修改前:%d\n", ary[0]);// 1
	printf("修改前地址:%p\n",ary);
	change_ary(ary);

	printf("修改后:%d\n",ary[0] ); // 123
	printf("修改后地址:%p\n",ary);

	return 0;
}

运行效果

此种情况下:如果修改形式数组参数的地址,不会影响实际数组参数地址。

cpp 复制代码
#include <stdio.h>

// 数组作为函数参数时,传递的是数组的地址值
// 函数调用的时候,会将实际数组参数的地址值给到形式数组参数
void change_ary(int ary[]){
	// 形式参数和实际参数共用一个地址空间
	ary[0] = 123; 
	// 此处修改形式参数的空间内容,实际上也就修改了实际参数的空间内容
}


void change_ary_p (int *ary){ // int * ary,是一个指向数组的指针
	printf("修改前:形式参数的地址,%p\n", ary);

	// 定义了新的数组
	int arr[] = {6,7,8};

	// 让形式参数指向新的数组(修改形式参数的地址)
	ary = arr;

	printf("修改后:形式参数的地址,%p\n", ary);

}

int main(){
	int ary[] = {1,2,3};

	printf("修改前:%d\n", ary[0]);// 1
	printf("修改前,实际参数的地址:%p\n",ary);
	// change_ary(ary);
	change_ary_p(ary);
	printf("修改后:%d\n",ary[0] ); // 123
	printf("修改后,实际参数的地址:%p\n",ary);

	return 0;
}

运行结果:

会发现,我们在函数内部修改了形式参数的地址值,并没有影响实际参数的地址值。

当数组作为参数传递到函数中,传递的不是数组中的具体值,而是数组的地址值。

总结:

  1. 如果参数是 int,float,double 等等这种基本类型的值,传递的是实际参数本身的数据值。无法通过修改形式参数的值来修改实际参数的值。
  2. 如果参数是数组,那么传递的就是实际参数数组的地址值,可以通过形式参数数组,修改实际参数数组中的数据,但是无法修改实际参数的地址。

7.6 函数的返回值

7.6.1 void 关键字

如果执行这个函数不需要返回结果,那么在函数定义的时候,返回值类型可以写成 void

当函数的返回值类型为 void 的时候,执行完这个函数之后,return 可加可 不加,如果要加 return,直接写 return; 即可

cpp 复制代码
#include <stdio.h>

// 函数返回值类型写成void,表示执行完这个函数,不需要返回结果
void say(){

	printf("哈哈\n");
}


void say1(){
	printf("┭┮﹏┭┮\n");

	return ; // 如果返回值类型是void,那么此时可以加return
	// 当程序执行return的时候。表示函数就已经结束了
	// return后面的代码都不会执行
}

如果函数返回值时 void 类型的,那么在函数调用的时候,就不需要用变量去接收返回值。

cpp 复制代码
int main()
{
	say();  // 调用返回值类型为void的函数,不需要定义变量接收返回值
	say1();
	return 0;
}

7.6.2 有返回值的情况

如果函数定义了返回值,函数执行完毕之后需要将最终的结果用 return 返回给函数调用的位置。至于这个返回值,可以根据用户的需要,可以用可以不用。

如果不用,调用函数时可以不用定义变量去接收返回值。

如果需要使用,必须定义一个和返回值类型相同的变量去接收。(如果能发生自动类型转换且数据不丢失的类型也可以),比如:函数的返回值类型是 int 类型,可以定义 int 类型变量接收,也可以定义 long 类型或者 long long 类型的变量去接收。

cpp 复制代码
#include <stdio.h>

// 返回值类型为int类型
int demo(){

	// printf("O(∩_∩)O哈哈~\n");
	printf("abcdefg\n");
	//定义了返回值类型,此处就需要加return,指定返回结果
	return 10.89;//返回值类型是int类型,此处的10.89就被转换成int类型

	//return "666";// 错误
	// 简单点:返回值是什么类型,return后面就写什么类型的值
}

// 返回值类型是float类型
float area(float r){

	return 3.14*r*r;
}

int main(){

	int num = demo();
	printf("%d\n",num );

	//当返回值类型是float类型
	//此时可以定义一个float类型的变量去接收函数返回的结果
	float result = area(3);
	printf("float:%f\n", result);

	// 也可以定义一个double类型去接收
	// area(3)结果是float类型,此时就会自动转换成double类型
	double result1 = area(3);
	printf("double:%lf\n", result1);
	return 0;
}

分享不易,耗时耗力,喜欢的同学给个关注和赞吧

承接毕设指导,技术答疑,学习路上想要找私人教练的同学可以私信我

更多学习资料,公众号:墨轩学习网,B站:墨轩大楼


另有下图需求的也记得私信我哟,专业班子

相关推荐
做人不要太理性18 分钟前
【C++】深入哈希表核心:从改造到封装,解锁 unordered_set 与 unordered_map 的终极奥义!
c++·哈希算法·散列表·unordered_map·unordered_set
程序员-King.27 分钟前
2、桥接模式
c++·桥接模式
chnming198731 分钟前
STL关联式容器之map
开发语言·c++
VertexGeek34 分钟前
Rust学习(八):异常处理和宏编程:
学习·算法·rust
石小石Orz35 分钟前
Three.js + AI:AI 算法生成 3D 萤火虫飞舞效果~
javascript·人工智能·算法
程序伍六七44 分钟前
day16
开发语言·c++
小陈phd1 小时前
Vscode LinuxC++环境配置
linux·c++·vscode
火山口车神丶1 小时前
某车企ASW面试笔试题
c++·matlab
jiao_mrswang1 小时前
leetcode-18-四数之和
算法·leetcode·职场和发展
qystca2 小时前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法