c语言函数学习,从入门到掌握

提示:在本章节内容学习,小编希望大家对于c语言函数有所了解掌握,同时希望在跟随小编学习的过程中大家能自己动手敲代码。如果大家觉得小编讲的不错,可以抬起大家发财的手给作者一个赞点个关注,作者后续会持续更新有关c语言的内容知识梳理。如果有什么信息缺漏,大家可以在评论区给我留言,小编会及时回复以及更改。


前言

提示:在本章节内容,小编将会将c语言的函数部分系统的讲述一遍,希望大家拿起自己的电脑跟随小编一起开启函数的学习之旅:

本节需要用到的相关教材:

c语言库函数参考文档


提示:以下是本篇文章正文内容

一、函数是什么?

1、在计算机中,函数是一个大型程序中的某部分代码,由一个或者多个语句块组成。它负责完成某项特定任务,而是相比较于其他代码,具有相对独立性。
2、简单的说,函数是c语言的功能单位,实现一个函数可以通过封装一个函数来实现。所以在我们创建定义函数的时候一切要以功能为单位,根据功能去确定函数的参数与返回值。
3、c语言的程序其实是由无数个小的函数组合而成的,就是说当你在运行计算机的时候去计算一个很大的计算任务,它是由一个个小的函数去组成并完成这个计算任务的。


二、函数的分类

从函数定义角度来分类(也就是函数由谁实现):我们可以分为三大类

函数由谁实现
库函数由c库实现的
自定义函数程序员自己编写来实现的
系统调用操作系统实现的函数

这里我们介绍一下大家容易混淆的库函数的一些基本内容:
1、首先c语言标准中规定了c语言的各种标准,但是c语言它并不提供库函数;在c语言国际标准ANSIC规定了一些常用的函数的标准,被称为标准库

2、那什么是c语言库函数呢:不同的编译软件它会有编译器厂商,编译器厂商就会根据标准库的标准给出一系列函数的实现,这就称为
库函数

3,使用库函数,必须包含 #include对应的头文件


三、函数参数

1,函数的组成:

在这里插入图片描述

1,ret_type这里是函数的返回类型,有时候返回类型是void,表示什么都不返回
2,fun_name这里是函数名,有了函数名就方便调用了
3,(形式参数)这里是函数参数
4,{}里面的是函数体,也就是你写的这个函数的功能实现
5,带返回值的函数在定义函数的时候,必须带着返回值类型,在函数体里,必须有 return如果没有返回值类型,默认返回整型

2, 求两个数之间的较大值(用函数实现)

#define  _CRT_SECURE_NO_WARNINGS   
#include<stdio.h>  
//函数定义
int get_max(int x, int y)
{
	if (x > y)
		return x;
	else
		return y;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	//比较大小
	int x = get_max(a, b);   //函数的调用
	printf("%d\n", x);
}  
	printf("%d\n",x);  
}

在这里插入图片描述

接下来我们来分析一下这个代码:

首先main函数是整个程序的入口,我们从main函数进入
1.这里需要判断两个整数的大小,所以我们先定义两个整型,输入两个整数。
2.输入两个整数后,我们需要对两个整数进行判断,这里需要一个函数来对他进行判断
3.我们定义一个函数 get_max,我们在2中知道传入两个整数,所以它的参数位置为两个整型的变量,最后我们判断大小,判断完成我们需要返回较大值,较大值类型为a,b中一个,所以返回类型为整型。
4.我们在调试的可以观察到,x和y确实得到了a和b的值,但是x和y的地址和a和b的地址是不⼀样的,所以我们可以理解为形参是实参的⼀份临时拷⻉。

3,参数的分类

1,实际参数(实参):

真实传给函数的参数,叫做实参。
实参可以是:变量,常量,表达式,函数等。
无论实参是何种类型的量,在进行函数调用的时候,他们必须都得有确定的值,方便传这些值给形参。

2,形式参数(形参):

形参是变量,是被调函数的局部变量。
形式参数是指函数名后面括号中的变量,因为形参函数只有在函数被调用的过程中才被分配内存单元,所以叫形式参数。形式参数在函数调用完成后就被自动销毁了。因此形式参数只有在函数内部才有作用。

注意:实际参数和形式参数可以同名

3,举例分析形式参数和实际参数

int add(int a, int b)  //形式参数,简称形参
{
	return a + b;
}
int main()
{
	int x = 10;
	int y = 20;
	scanf("%d%d", &x, &y);
	int sum = add(x, y); //真实传递给add函数的参数,我们叫实参(实际参数)
	printf("%d ", sum);
	return 0;
}

在这里插入图片描述

在上面我们通过右边的监视窗口可以看到x和y是真实传递给add函数的参数,也就是实参,而a和b只是形式上存在的参数我们叫形参,这里为什么叫形参呢?事实上,如果我们只是定义了add函数,而不去调用的话,add函数的参数a和b只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形式的实例化,也就是说函数被调用的时候,形式参数会开辟一个空间来存放实际参数传来的值,而实际参数原先就已经存在了一个内存早就开辟好了的内存空间,两者所属的内存空间不是同一个。就第二张图的监视窗口我们可以看到,如果我们不调用add函数,但是他之前就存在x和y,在main函数运行的时候内存就会开辟空间去存放他,而add函数中x和y不会运行也就不会开辟空间。我们分别对a和b,x和y进行取地址,发现他们的地址不同,由此可以看出他们开辟的内存空间不是一致的。

4,解析延申

接下来我们通过一串代码来延申了解一下函数的参数类型

#define  _CRT_SECURE_NO_WARNINGS            
#include<stdio.h>           
//交换两个整型变量           
void swap(int x, int y)           
{
	int temp = 0;           
	temp = x;           
	x = y;           
	y = temp;           
}
int main()        
{
	int a = 0;        
	int b = 0; 
	scanf("%d%d",&a,&b); 
	//交换前 
	printf("交换前:x=%d y=%d\n", a, b); 
	swap(a, b); 
	//交换后 
	printf("交换后:x=%d y=%d\n", a, b); 

}

在这里插入图片描述

在这里我们可以发现,前后居然没有发生交换。这是因为传递给函数的a,b的地址与x,y的地址不同,他们都有各自的空间,如下图,他传递值给x,y,我们又在里面创建temp将x,y交换了值,但是它不影响a,b,因为他们是完全不同的空间。

在这里插入图片描述

提示:在这里a和b叫实参,x和y叫形参。将实参传给形参,形参将会是实参的一份临时拷贝,它们各自都有各自的独立空间,所以修改形参不会影响实参。所以在这里我们需要把实参的地址传给形参,这样通过地址指向实参的值改变形参就会影响到实参。

#define  _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
//交换两个整型变量
void swap(int* x, int* y)
{
	int temp = 0;
	temp = *x;
	*x = *y;
	*y = temp;

}
int main()
{
	int x = 0;
	int y = 0;
	scanf("%d%d",&x,&y);
	//交换前
	printf("交换前:x=%d y=%d\n", x, y);
	swap(&x, &y);
	//交换后
	printf("交换后:x=%d y=%d\n", x, y);

}

在这里插入图片描述

在这里我们可以发现值发生了交换。通过这里我们可以发现,当形参改变对实参不需要产生影响实参时候不需要传递地址,当形参修改要影响实参的时候需要传递地址。

5,数组传参 (重点)

数组在传参需要注
1,函数形式参数要和实参个数要匹配
2,函数的实参是数组,,形参也是可以写成数组形式的
3,形参如果是一维数组,数组大小可以省略不写
4,形参如果是二维数组,行可以省略,但是列不可以省略(最好都不省略)
5,数组传参,形参是不会创建新的数组的
6,形参操作的数组和实参操作的数组是同一个数组


四、函数调用

1,函数调用分类

传值调用: 像上边例子中的get_max函数

函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。

传址调用: 像上边例子中的swap函数

传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

2,代码练习

练习1:写一个函数判断一个函数是不是素数(只能被1和它本身整除的数)

int prime(int x)
{
	int n = 0;
	for (n = 2; n < x - 1; n++)
	{
		if (x % n == 0)  //此时x就不是素数
		{
			return 0;
		}
	}
	return 1;   //这个就是素数了
}
int main()
{
	int i = 0;
	scanf("%d", &i);
	int z = prime(i);
	if (z)
	{
		printf("奇数:%d", z);
	}
	return 0;
}

练习2:写一个函数判断是否为闰年(能被4整除并且不能被100整除 或者能被400整除就是闰年)

int leap_year(int x) 
{
	if (((x % 100 != 0) && (x % 4 == 0)) || (x % 400 == 0)) 
		return 1; 
	else 
		return 0; 
}
int main() 
{
	int year = 0; 
	scanf("%d", &year); 
	int m = leap_year(year); 
	if (m) 
		printf("闰年 =%d ", year); 
	return 0;
}

五、函数的嵌套调用和链式访问

1,函数的嵌套调用

函数与函数之间可以根据实际要求进行组合的,也就是相互调用

void new_line()
{
	printf("hehe\n");
}
void three_line()
{
	int i = 0;   
	for (i = 0; i < 3; i++)   
	{
		new_line();   
	}
		
}
int main()   
{
	three_line();   
	return 0;   
}

在这里小编创建了两个函数new_line和three_line,而小编在three_line中调用了new_line,这个就叫函数嵌套调用,在一个函数中调用另一个函数。但是函数的定义不能嵌套,即不能在一个函数体内定义另外一个函数,所有函数的定义都是平行的。

2,函数的链式访问

1,函数的链式访问依赖函数的返回值;就是把函数的返回值作为另一个函数的参数

#include<string.h>      
int main()      
{
	printf("%d\n",strlen("abcdef"));      
	return 0;      
}

在这里strlen是一个求字符串长度的函数,这个小编在之前的博客讲过,strlen求出字符串的长度然后返回给printf函数用%d格式打印出来,strlen返回值给printf做了一个参数,像链条一样把这两个函数给串起来叫做链式访问

2,来个练习求输出结果

#include<string.h>
int main()
{
	printf("%d\n",printf("%d", printf("%d", 43)));
	return 0;
}

在这里插入图片描述

在这里可以发现他的输出结果为4321,为什么呢?printf函数返回的是打印在屏幕上的字符的个数。
上⾯的例⼦中,我们就第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的是第三个printf的返回值。
第三个printf打印43,在屏幕上打印2个字符,再返回2
第⼆个printf打印2,在屏幕上打印1个字符,再放回1
第⼀个printf打印1
所以屏幕上最终打印:4321.


六、函数的声明与定义

1,单文件函数的声明

1,告诉编译器有一个函数叫什么,参数是什么,返回类型又是什么。但是具体存不存在,函数决定不了。
2,函数的声明一般出现在函数的使用前,要满足先声明后使用
3,函数的声明一般放在头文件中。

接下来通过代码来描述,我们接着用上次求闰年的代码,只不过我们把函数定义放在main函数后面

int main()   
{
	int year = 0;   
	scanf("%d", &year);   
	int m = leap_year(year);   
	if (m)   
		printf("闰年 =%d ", year);   
	return 0;   
}
int leap_year(int x)   
{
	if (((x % 100 != 0) && (x % 4 == 0)) || (x % 400 == 0))   
		return 1;   
	else   
		return 0;   
}

在这里插入图片描述

在这里我们可以发现程序报出了一个警告,这是因为c语⾔编译器对源代码进⾏编译的时候,从第⼀⾏往下扫描的,当扫描到main函数中的leap函数时候,编译器他不认识,所以发出警告;这个问题该如何解决呢;我们只需要在程序也就是main函数上方给leap函数一个声明就可以,函数声明包含返回类型,参数类型和函数名,记住要加一个分号哦。

//函数的声明
int leap_year(int x);
int main()
{
	int year = 0;   
	scanf("%d", &year);     
	int m = leap_year(year);     
	if (m)     
		printf("闰年 =%d ", year);     
	return 0;     
}
//函数的定义     
int leap_year(int x)     
{
	if (((x % 100 != 0) && (x % 4 == 0)) || (x % 400 == 0))     
		return 1;     
	else     
		return 0;     
}

2,多文件函数的声明

一般情况下,函数的声明,类型的声明我们放在头文件中,函数的定义,也就是函数功能的实现我们放在源文件中
假如我们需要调用的函数不在同一个文件之中,也就是如下图一样的场景
在这里插入图片描述

在这里我们想实现一个加法函数的功能实现,此时我们需要建立一个源文件对函数功能的实现,在建立一个头文件对函数的声明。在这里我们可以发现下面爆出警告告诉我们Add函数并未声明,这时因为我们在头文件中对加法函数的声明,在源文件中使用必须包含器头文件才行,因为是自己建立的头文件,所以我们包含头文件用双引号。

在这里插入图片描述

3,多文件书写的好处

  1. 逻辑清晰
  2. 方便多人协同
  3. 适当的隐藏代码

在这里肯定很疑惑为啥要多个文件书写?这是因为我们以后走上工作岗位啥的,我们需要完成项目,项目之中肯定要实现多个功能,就拿王者来举例子,我们想要实现人物走动,人物放技能等多个功能实现,假如我们实现这些功能放在同一个文件当中,那么后面我们在游戏之中发现bug要去修改就得从几千万甚至几亿行代码中一个个去找么,此时我们多文件就可以将多个功能进行划分开,要修改哪些地方的bug到对应的功能源文件中去寻找。这就是逻辑清晰,但是这么多功能实现就靠一个人那得多累啊,多人协同完成不同的功能模块,可以效率完成游戏代码的逻辑。

4,函数的定义

1,函数定义是指函数的具体实现,交代函数的功能实现。
2,函数的定义也是⼀种特殊的声明,所以如果函数定义放在调⽤之前也是可以的。


七,函数的递归

1,什么是递归?

程序调用自身的编程技巧称为递归
递归作为一种算法,它的主要思考方式在于:把大事化小

2,递归的两个必要条件

1递归存在限制条件,当满足这个限制条件的时候,递归便不在继续,为什么这么说呢,根据递归的概念,程序调用自身的编程技巧称为递归,函数在不断调用自己,不断在寻找自己,却跳不出来,所以它会一直递归下去成为一个死循环。

2,每次递归调用之后需要越来越接近这个限制条件

3,代码学习

//接受一个整型值(无符号),按照顺序打印它的每一位
//1234
//print(123) 4
//print(12) 3 4
//print(1) 2 3 4
//1 2 3 4
void print(unsigned int x)
{
	if (x > 9) 
	{
		 print(x / 10);
	}
	printf("%d ", x % 10);
}
int main()
{
	unsigned int i = 0;
	scanf("%u", &i);
	print(i);
	return 0;
}
  • 73
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值