C语言函数讲解

1、函数的概念

其实我们再数学就见过函数了;比如一次函数y=kx+b;给一个任意的x,返回一个y;

在C语言我们也有函数,也叫子程序;函数就是完成一部分功能的代码;比如一个大任务,分成几个小任务;由多个函数实现出来;C语言程序其实是由多个函数组合而成的;

C语言中主要分为两类函数:库函数和自定义函数;

2、库函数

2.1标准库和头文件

C语言并不提供库函数,它只是提供某些函数的规定要实现什么功能;什么参数,什么返回类型。

然后编译器厂商就根据C语言提供的规定去实现一些库函数;那么这些规定就是标准;并把这些称为标准库;

那么要运用这些库函数,就要运用到头文件,库函数根据功能的划分,在不同的头文件进行定义,使用库函数,要使用对应的头文件;

那我们要查看头文件和库函数可以到

C/C++官⽅的链接:https://zh.cppreference.com/w/c/header

cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/

查看

double sqrt (double x)
//sqrt 是函数名
//double x 是函数的参数是double类型的
//double  sqrt函数的返回值是double类型的

2.1.1功能

Compute square root  计算平方根

Returns the square root of x.  返回平方根

2.1.2头文件

sqrt函数需要包含头文件;#include<math.c>

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<math.h>
int main()
{
	double x = 25.0;
	double r = sqrt(x);
	printf("%.1lf\n",r);
	return 0;
}

3、自定义函数

自定义函数是最重要的;可以让我们根据不同的情况,不同的要求去实现不同功能的函数;

3.1函数的语法

ret_type fun_name (形式参数)
{



}

ret_type;函数的返回值类型

fun_name;函数名

();括号里面是形式参数

{  };里面是函数体

函数就相当于一个工厂,你给它原材料(形式参数);工厂就会给你返回一个零件(计算结果);

那么ret_type就是计算结果的返回类型;函数也可以什么都不返回,那这个函数的返回类型就void 空;

fun_name函数名起的时候;要尽量由意义,不容易搞混;

如果函数由参数,那我们就要确定参数的类型,个数,名字;

比如我们定义一个加法函数;

输入add函数a,b的值;然后a,b的值赋给形参x ,y

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int add(int x, int y)  //函数返回值类型int  
{                      //函数名add
	int z = x + y;     //参数类型int
	return z;          //参数名x,y
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int r = add(a, b);   //加法函数
	printf("%d", r);
	return 0;
}

4、形参和实参

使用函数过程中,我们把函数的调用里面的参数就是实参;函数的定义里面的参数就是形参

int add(int x, int y)  函数定义   形参
{
	int z = x + y;
	return z;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int r = add(a, b);   函数调用   实参
	printf("%d", r);
	return 0;
}

4.1实参

实参就是调用函数时传给它的实际参数就叫实参;

4.2形参

就是函数定义时,用来接收的参数就叫形式参数,形参;

4.3实参和形参的关系

如果没有调用add函数,那么形参x,y会向内存申请空间来存放吗?

是不会的,你没有调用,形参不会申请内存空间,你调用了,形参才会向内存申请空间;

所以形参就是形式上的参数;

int add(int x, int y)
{
	int z = x + y;
	return z;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int r = add(a, b);
	printf("%d", r);
	return 0;
}

我们看一下a和b的地址,x和y的地址,它们的地址是不也一样,但它们的值是一样的;

形参就是实参一次拷贝;把实参的值拷贝过来;

注意,我们形参的个数可以有多个,但是要和实参的个数要保持一致;

形参的数据类型和实参的数据类型要一样;

形参的名字可以和实参的名字一样;

5、return语句

return语句就是返回的意思;

return后面可以跟一个变量或者是表达式;表达式会先执行,计算返回结果;

int add(int x, int y)
{
	return x + y;
}

比如打印函数;你不用return返回值;没什么可以返回的,函数的数据类型就是void

void menu()
{
	printf("hehehe\n");
}

假设返回的是一个浮点型小数,但是函数的返回值类型是int;那么它会隐形类型转换,返回3;

int add(n)
{

	return 3.4;
}

如果没有指定函数的返回类型,默认是int类型

add(n)
{


	return 3.4;
}

如果x>5就执行return语句;返回,后面的语句就不再执行;打印bababa;

void text(int x)
{
	if (x > 5)
		return;
	printf("hahaha");
}
int main()
{
	int n = 10;
	text(10);
	printf("bababa");
	return 0;
}

这个函数会发一个警告;意思就是如果是x<5,这种情况呢?

我们还需要考虑到这种情况;

int text(int x)
{
	if (x > 5)
		return 1;
}
int main()
{
	int k = 10;
	printf("%d",text(10));
	return 0;

int text(int x)
{
	if (x > 5)
		return 1;
	else
		return 2;
}
int main()
{
	int k = 10;
	printf("%d",text(10));
	return 0;
}

6、数组做函数的参数

比如我们把一个函数的内容都为-1;然后函数打印出来;

void set_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		arr[i] = -1;
	}
}
void printf_arr(int arr[],int sz)
{
	int j = 0;
	for (j = 0; j < sz; j++)
	{
		printf("%d ",arr[j]);
	}
	printf("\n");
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf_arr(arr, sz);  //打印数组
	set_arr(arr,sz);      //赋值数组
	printf_arr(arr,sz);   
	return 0;

我们写一个set_arr函数把数组的每个元素赋值为-1;我们给个arr数组名做参数,既然每个元素都要赋值,那就需要遍历每个元素;需要元素的个数sz做参数;

到形参部分;你给一个数组,那形参也要有个数组,数组的大小可以不写,

打印部分就用printf_arr函数,跟set_arr函数基本差不多;

数组做参数,实际上给的是数组的地址给形式参数;所以形式参数(被调函数)可以改变main(主调函数)里的数组元素;并且数组做参数,形参不会给数组申请内存空间创建新的数组;所以不用在形式参数上写上数组的大小;

注意数组函数;

1.形式参数的个数要个实参的参数个数要相同;

2.实参是数组,那形参也要写成数组的形式;

3.数组是一维数组,大小可以省略

4.数组是二维数组,列不可以省略

5.数组做形参,不会创建新的数组,不用写数组的大小

6.形参数组跟实参数组其实是同一个数组;

7、嵌套使用和链式访问

7.1嵌套使用

函数嵌套是函数之间互相调用,一个函数中调用另一个函数;

写一个某年某月有几天的函数;输入年份,月份,判断有几天;我们知道每个月份都是固定的,出来二月,如果是闰年二月就有29天;那么我们就在判断有几天的函数里,再调用一个判断闰年的函数,这叫叫做函数的嵌套;mian函数调用scanf函数和printf函数;函数定义之间是不能嵌套的,你不能把函数的定义放在另一个函数里面;

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int year(int y)  //判断是否是闰年,是返回1;否返回0
{
	return ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0);
}
int year_month(int y, int m)
{
	//31,28,31,30,31,30,31,31,30,31,30,31
	//   29
	int day[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	//            0  1  2  3  4  5  6  7  8  9 10  11  12
	int r = day[m];
	if (year(y) == 1 && m == 2)
		r++;
	return r;
}
int main()
{
	int year = 0;
	int month = 0;
	scanf("%d %d",&year,&month);
	int day = year_month(year,month);
	printf("%d\n",day);
	return 0;
}

7.2链式访问

链式访问就是把一个函数的返回值当成另一个函数的参数;

比如,我们把printf函数的返回值当成另一个printf函数的参数;

printf函数的返回值,printf打印几个数,就返回几个数;

第四个函数,打印43,返回2;第三个函数打印2;返回1;第二个函数打印1;返回1;第一个函数打印1,返回1;结果就是43211;


int main()
{
	printf("%d",printf("%d",printf("%d",printf("%d",43))));
	//结果:43211
	return 0;
}

8、函数的声明和定义

8.1单个文件

假设我们再写一个函数的时候,把函数放在了后面,编译器会给一个警告;因为程序在执行的时候,是从上面往下面开始编译,当你用这个函数的时候,你把函数放在后面,编辑器调用函数之前没有见过这个函数,所以会发生一个警告;

注意我们你在使用函数之前要 

先声明后使用

把声明发在函数的前面就可以;你可以放在main函数前面后者就放在函数上面都行;

int add(int x, int y);//函数的声明

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);  //函数调用
	int r = add(a, b);
    printf("%d\n",r);
}

int add(int x, int y)  //函数定义
{
	return x + y;
}

那之前我们把函数的定义直接放在前面为什么也可以?

你定义都给它看了;函数体都给它看了,那肯定是可以的;其实函数的定义也是一种特殊的声明;

8.2多个文件

在一些正儿八经的工程中一定多文件去写这个工程的;

我们把函数的定义放在.c文件,函数的声明放在.h文件,当你想调用这个函数的时候,只需要包含这个函数的头文件就可以调用;

注意库函数的头文件是<  >这样写的

自定义函数是”  “双引号写的;

 add.c

int add(int x, int y)  //函数定义
{
	return x + y;
}

 add.h

int add(int x, int y);//函数的声明

main.c

#include<stdio.h>
#include "add.h"

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);  //函数调用
	int r = add(a, b);
	printf("%d\n",r);
}

一个大的工程,会分为几个模块;比如一个计算器;分成几个模块;A,做乘法;B,做除法;

C,做加法;D,做减法;它们分别把每个功能实现是出来,写成.c\.h文件;再由E去把它们整合起来,只要写程序的时候包含它们写的.h文件,头文件;就能使用这个函数;实现这个工程;

那么这样做的话,效率高,模块化;还能对重要的代码进行隐藏;

9、static和extern

static和extern都是C语言里面的关键字;

static是静态的意思;它们修斯局部变量;全局变量;函数;

exterm是声明外部符号;

首先我们讲一下作用域和生命周期

作用域就是一个变量能够被使用的范围;

局部变量:它的作用域就是变量所在的局部范围;

比如变量a只能在括号里面使用,不能再括号外面使用;所以外面的printf不会打印,还会报错;

int main()
{
	{
		int a = 6;
		printf("%d\n",a);
	}
	printf("%d\n",a);
	return 0;
}

全局变量;它的作用就是整个工程;

都能打印b;

int b = 1; //全局变量

int main()
{
	{
		int a = 6;
		printf("%d\n",a);
		printf("%d\n",b);
	}
	printf("%d\n",b);
	return 0;
}

生命周期指的是,这个变量的创建(申请内存)到变量销毁(内存回收)的时间段;

局部变量的生命周期:进入作用域创建变量,生命开始,出作用域变量销毁,生命结束;

全局变量的生命周期:程序从mian函数开始执行,main可以使用全局变量,程序结束,全局变量销毁,也就是说全局变量的生命周期就是整个程序的生命周期;

9.1static修饰局部变量

我们分析下面代码,循环5次,打印5次text函数,本以为是6 7 8 9 10;结果是6 6 6 6 6;

为什么呢?因为变量a是一个局部变量。它的作用域只是在text函数里面;第一次循环进来,变量a创建,a=5;a++;a=6;打印6;然后变量销毁,退出text函数;变量没有保存数值;第二循环进来也是一样的,出了text函数生命周期就结束了。

text()
{
	int a = 5;
	a++;
	printf("%d ",a);
}

int main()
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		text();
	}
	return 0;
}

但是当static修饰局部变量的时候,结果为6 7 8 9 10;为什么呢?

我们倒退一下,打印了6,能理解,但是打印7;那么变量就是6啊,a++;打印7啊;下次a=7;a++;才打印8啊;那就说明static修饰后的局部变量是保存上次的数值的;这么看,局部变量的生命周期延长了;

text()
{
	static int a = 5;  //static修斯局部变量
	a++;
	printf("%d ",a);
}

int main()
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		text();
	}
	return 0;
}

 本质上;static修饰的局部变量改变它的存储类型,本来是放在栈区,static修饰后就放在了静态区;这样使局部变量的生命周期和全局变量的生命周期一样长了,但是它的作用域使不变的;

 9.2static修饰全局变量

假设我们在别个文件有一个全局变量,想调用,我们可以用extern声明外部符号,对它进行一个声明,就能使用了。

main.c

int a = 8;

text.c

//extern--声明外部符号
extern int a;

int main()
{
	printf("%d",a);
	return 0;
}

假设static修饰全局变量;那当调用的时候就会报错;

//static修饰全局变量
static int a = 8;

因为全局变量是有外部链接属性,能被其他的函数调用,但是当static修饰全局变量的时候,就把它的外部链接属性,变为内部链接属性,就只有在本源文件内使用,外部文件不能使用,只有自己能用,别人不能用;即使你已经extern声明了,也不能使用;

使用这个的话,就想这个全局变量只有在这个自己的文件内使用,不想给别的文件使用,就可以用static修饰全局变量;

9.3static修饰函数

其实static修饰全局变量和static修饰函数是一样的

假设在别的文件定义了一个函数,想调用的时候,你可以写上一个.h头文件,把函数的声明写在里面,然后调用头文件,这是一种方法,或者是extern声明外部符号,声明函数,跟头文件一样也是对函数进行一种声明;

add.c

int add(int x, int y)  //函数定义
{
	return x + y;
}

text.c

extern int add(int x, int y);//函数的声明

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);  //函数调用
	int r = add(a, b);
	printf("%d\n",r);
}

 当static修饰函数时,调用函数会报错;

static int add(int x, int y)  //函数定义
{
	return x + y;
}

本质上跟static修饰全局变量是一样的;函数是有外部链接属性的,能够被别的文件所调用,当static修饰函数是,就把函数的外部链接属性变为内部链接属性,只能在自己的文件内使用,别的文件不能调用;

如果想就在自己的文件内使用函数,别的文件不能使用,那就使用static修饰全局变量;

感谢观看!感谢指正! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值