C语言:函数

1、函数的概念

C语言中的函数(function),也叫子程序,就是一个完成某项特定任务的一小段代码。这段代码是有特殊的写法和调用方法的。C语言的程序其实是由无数个小的函数组合而成的,一个大的计算任务可以分解成若干个较小的函数来完成。一个函数如果能完成某项特定任务的话,这个函数也是可以复用的,提升了开发软件的效率。

在C语言中我们一般会见到两类函数:库函数   自定义函数

2、库函数

 2.1 标准库和头文件

C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数;C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库,不同的编译器厂商根据ANSI提供的C语言标准就给出了一系列函数的实现。这些函数就被称为库函数

例如我们已经熟悉的printf、scanf都是库函数。库函数也是函数,不过这些函数是现成的,只要学会了就能直接使用。有了库函数,一些常见的功能就不需要程序员自己实现了,一定程度提升了效率,同时库函数的质量和执行效率上都更有保障。

各种编译器的标准库中提供了一系列的库函数,这些库函数根据功能的划分,都在不同的头文件中进行了声明。

库函数相关的头文件:https://zh.cppreference.com/w/c/header

3、自定义函数

除去提供便利的库函数外,自定义函数则是为程序员写代码提供了更多的创造性。

 3.1 自定义函数的语法形式

ret_type fun_name(形式参数)
{

}

 ret_type 是函数返回类型,有时候返回类型可以是void,表示什么都不返回。

fun_name 是函数名,可以自定义,方便调用。

() 中的是形式参数,可以是void,表示函数没有参数。如果有参数则要交代清楚参数的类型、名字以及参数个数。

{} 中的是函数体,函数体就是完成计算的过程。

 3.2 自定义函数举例

自定义一个加法函数:

#include<stdio.h>

int Add(int x,int y)//加法函数
{
	return x + y;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);//输入
	int c = Add(a,b);//调用加法函数,将求和结果放入c中
	printf("%d\n",c);
	return 0;
}

4、形参和实参 

在函数使用过程中,把函数的参数分为形参和实参。

用上述代码举例:

int c = Add(a,b);//这里a,b为实参

实际参数就是真实传递给函数的参数。

int Add(int x,int y)//这里的x,y为形参
{
	return x + y;
}

只是定义Add函数,而不去调用的话,Add函数的参数x和y只是形式上存在的,不会向内存申请空间,叫形式参数。形参只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化

 4.1 形参和实参的关系 

虽然是实参传递给形参,但形参和实参各自都是独立的内存空间。

如上,在调试的时候可以观察到,x,y确实得到了a,b的值,但x,y的地址和a,b的地址是不一样的,所以可以理解为形参是实参的一份临时拷贝。

5、return语句

在函数的设计中,函数中经常会出现return语句,这里讲一下return语句使用的注意事项:

1.return后面可以是一个数值,也可以是一个表达式,如果是表达式则先执行表达式,再返回表达式的结果。

2.return后面也可以什么都没有,直接写return;这种写法适合void返回类型的函数。

3.return返回的值和函数返回类型不一样,系统会自动将返回的值隐式转换为函数的返回类型。

4.return语句执行后,函数就彻底返回,后面的代码不再执行。

5.如果函数中存在if等的分支语句,则要保证每种情况下都有return返回,否则会出现编译错误。

举例:判断一个整数是不是奇数。

#include<stdio.h>

int odd(int n)
{
	if (n % 2 == 1)
		return 1;
	else
		return 0;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	if (odd(n)==1)
		printf("是奇数\n");
	else
		printf("不是奇数\n");
	return 0;
}

6、数组做函数参数

在使用函数解决问题时,难免会将数组作为参数传递给函数,在函数内部对数组进行操作。

举例:写一个函数将一个整型数组的内容全部置为-1,再写一个函数打印数组的内容。

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组中元素个数
	set_arr(arr,sz);//重置函数
	print_arr(arr,sz);//打印函数
	return 0;
}

将数组以及元素个数作为参数传递给了set_arr和print_arr函数之后,先需要知道数组传参的几个重要知识点:

1.函数的形参个数要和函数的实参个数匹配。

2.函数的实参是数组,形参也可以写成数组形式。

3.形参如果是一维数组,数组大小可以省略不写。

4.形参如果是二维数组,行可以省略,列不能省略。

5.数组传参,形参是不会创建新的数组。

6.形参操作的数组和实参的数组是同一个数组。

接着就可以实现set_arr函数:

void set_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		arr[i] = -1;
	}
}

print_arr函数:

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

完整代码:

#include<stdio.h>

void set_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		arr[i] = -1;
	}
}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	set_arr(arr,sz);
	print_arr(arr,sz);
	return 0;
}

7、嵌套调用和链式访问

 7.1 嵌套调用

嵌套调用就是函数之间的互相调用,通过函数互相之间的有效调用,最终“组装”出一个相对大型的程序。

举例:计算某年某月有多少天。

分析:可以拆分为两个函数来完成

1. is_leap_year():根据年份确定是否为闰年

2. year_month_day:调用 is_leap_year 函数确定是否为闰年后,再根据月计算这个月的天数。

代码实现如下:

#include<stdbool.h>
#include<stdio.h>

bool is_leap_year(int y)
{
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
		return true;
	else
		return false;
}

int year_month_day(int y,int m)
{
	int days[] = {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 d = days[m];
	if (is_leap_year(y) && m == 2)
	{
		d += 1;
	}
	return d;
}

int main()
{
	int year = 0;
	int month = 0;
	scanf("%d %d",&year,&month);
	int day = year_month_day(year,month);
	printf("%d\n",day);
	return 0;
}

7.2 链式访问

链式访问就是将一个函数的返回值作为另外一个函数的参数,像链条一样将函数串起来就是函数的链式访问。


#include<stdio.h>
#include<string.h>

int main()
{
    //size_t len = strlen("abcdef");
	printf("%zd\n", strlen("abcdef"));//不通过len,直接将strlen作为printf的参数
	return 0;
}

8、函数的声明和定义

 8.1 单个文件

一般在使用函数的时候,直接将函数写出来就可以使用了

举例:写一个函数判断一年是否为闰年。

#include<stdio.h>
#include<stdbool.h>

bool is_leap_year(int y)
{
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
		return true;
	else
		return false;
}

int main()
{
	int y = 0;
	scanf("%d",&y);
	is_leap_year(y);
	if (is_leap_year(y))
	{
		printf("是闰年\n");
	}
	else
		printf("不是闰年\n");
	return 0;
}

这种情况是函数的定义在函数调用之前。

但如果将函数的定义放在函数的调用之后呢?

#include<stdio.h>
#include<stdbool.h>

int main()
{
	int y = 0;
	scanf("%d", &y);
	is_leap_year(y);
	if (is_leap_year(y))
	{
		printf("是闰年\n");
	}
	else
		printf("不是闰年\n");
	return 0;
}

bool is_leap_year(int y)//函数的定义
{
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
		return true;
	else
		return false;
}

则会出现如下警告: 

解决问题的办法就是函数调用之前先声明is_leap_year这个函数,声明函数只要交代清楚:函数名,函数的返回类型和函数的参数。

改动如下:

#include<stdio.h>
#include<stdbool.h>
//先声明后使用
bool is_leap_year(int);//函数的声明

int main()
{
	int y = 0;
	scanf("%d", &y);
	is_leap_year(y);
	if (is_leap_year(y))
	{
		printf("是闰年\n");
	}
	else
		printf("不是闰年\n");
	return 0;
}

bool is_leap_year(int y)//函数的定义
{
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
		return true;
	else
		return false;
}

函数的调用一定要满足先声明后使用。

 8.2 多个文件

代码较多时,我们往往会根据程序的功能,将代码拆分放在多个文件中。

一般情况下,函数的声明、类型的声明放在头文件 .h 中,函数的实现放在源文件 .c 文件中。

Add.h

//函数的声明
int Add(int, int);

Add.c

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

test.c

#include"Add.h"//头文件调用函数
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);
	int c = Add(a,b);
	printf("%d\n",c);
	return 0;
}

 8.3 static 和 extern

staticextern 都是C语言中的关键字。

static 是静态的 意思,可以用来:

修饰局部变量;修饰全局变量;修饰函数。

extern 是用来声明外部符号的。

在继续探讨static和extern之前,先说明一下:作用域生命周期

作用域是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

1.局部变量的作用域是变量所在的局部范围。

2.全局变量的作用域是整个项目。

生命周期指的是变量的创建到变量的销毁之间的时间段。

1.局部变量的生命周期是:进入作用域变量创建,生命周期开始,出作用域生命周期结束。

2.全局变量的生命周期是:整个程序的生命周期。

举例:static修饰局部变量

//代码1
#include<stdio.h>
void test()
{
	int n = 1;
	n++;
	printf("%d ",n);//2 2 2 2 2
}

int main()
{
	int i = 0;
	for (i = 0;i < 5;i++)
	{
		test();
	}
	return 0;
}
//代码2
#include<stdio.h>
void test()
{
	static int n = 1;
	n++;
	printf("%d ", n);//2 3 4 5 6
}

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

代码1:test函数中的局部变量i是每次进入test函数先创建变量(生命周期开始)并赋值为0,然后i++再打印,出函数的时候变量生命周期结束(释放内存)。

代码2:test函数中的i创建好后,出函数并不会被销毁,直接上次累积的数值继续计算。

结论:static修饰局部变量改变了变量的生命周期,,生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储在内存的栈区的,但被static修饰后存储到了静态区。存储在静态区的变量和全局变量是一样的,只有程序结束,变量才销毁,内存才回收。

static修饰全局变量:

一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。本质原因是全局变量默认是具有外部链接属性的,当外部文件想使用,只要适当声明就可以使用;但全局变量被static修饰后,外部链接属性就变成了内部链接属性,只能在本源文件内使用,即使其他源文件声明了也是无法正常使用的。

举例:extern 声明外部符号

test2.c

int n = 20;//全局变量 - 外部链接属性

test.c

#include<stdio.h>
extern int n;

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值