C语言由基础至精通——函数

函数(function),用于完成某项特定任务的一小段代码,也称之为“子程序”。C语⾔中提供了两种函数:库函数自定义函数

一、库函数

(1)标准库与头文件

C语言标准规定了C语言的各种语法规则,约定了一些常用函数的标准,组成了标准库
这些约定的函数标准C语言本身是没有去实现的,是由不同的编译器厂商根据ANSI提供的C语言标准在编译器中提供这些函数的具体实现,这些函数就被称为库函数。如printf、scanf、strlen、sqrt函数等。
这些库函数根据功能的划分,都在不同的头文件中进行了声明。即在使用库函数时,需要包含对应的头文件
库函数的相关头文件https://zh.cppreference.com/w/c/header

(2)学习和查看工具

C/C++官⽅的链接:https://legacy.cplusplus.com/reference/clibrary/

(3)实例分析

#include <stdio.h>      /* printf 所需的头文件 */
#include <math.h>       /* sqrt 所需的头文件 */

int main ()
{
  double param, result;
  param = 1024.0;
  result = sqrt (param);  // sqrt函数, 开方
  printf ("%f\n", result );  // 输出:32.000000
  return 0;
}

二、自定义函数

(1)创建语法及使用

由程序员按照一定语法规则自己创建,实现某一特定功能的函数。语法规则如下:

ret_type fun_name(形式参数)
{
	// 函数体
}

ret_type:用来表示函数返回结果的类型,返回结果可以为空,返回类型为void
fun_name:用来表示函数的名称,即函数名
( ):小括号内部放入形式参数(形参),可以为空
{ }:大括号内部放入函数体,实现该函数功能的具体计算

例:自定义一个加法函数,实现两个整型变量的相加

#include <stdio.h>

// 自定义Add函数,用于计算两个整数的相加
int Add(int x, int y)  // int 返回结果为整型;Add 函数名; x,y为形式上的参数,简称 形参
{
    return x+y;  // 返回计算结果,为整型
}

int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);  // 输⼊两个整数
    // 调⽤Add函数,完成a和b的相加,并将返回结果赋值给 r
    int r = Add(a, b);  // a,b为真实传递给Add函数的参数,为实际参数,简称 实参
    printf("%d\n", r);
    return 0;
}

(2)形参与实参

实参:真实传递给函数的参数,位于函数调用时小括号内的参数,如上述代码中的int r=Add(a,b),这里的a,b即为真实传递给Add函数的参数,为实际参数,简称实参
形参:形式上的参数,位于函数定义时小括号内的参数,如上述代码中的int Add(int x,int y),Add 函数的参数x和y只是形式上存在的,不会向内存申请空间,不会真实存在的,为形式参数,简称形参。形参只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化
关系:形参和实参都有自己独立的内存空间,即它们的地址是不相同的,在参数传递过程中,只是将值进行了传递,其地址是不变的。

在这里插入图片描述

(3)return 语句

在设计函数时,会经常使用到return语句,表示返回的意思。

  • return 语句后可为⼀个数值,也可为⼀个表达式,若为表达式则先执行表达式,再返回表达式的结果,其后也可什么都没有,适用函数返回类型是void的情况。
return 10;  // 数值
return a+b;  // 表达式
return;   // 函数返回类型为 void
  • return 返回的值类型和函数返回的值类型不⼀致,系则统会自动将返回的值隐式转换为函数的返回类型
int test(){
	return 3.14;  // return 返回一个浮点数,而函数返回类型为整型,则会将 3.14 强制转换为 3
} 
  • return 语句执行后,函数就彻底返回,后边的代码不再执行。
  • 若函数中存在 if 等分支句,则要保证每种情况下都有return返回,否则会出现编译错误。
int test(){
	int a = 0;
	if(a==1)
		return 1;   // 当a不等于1的时候,函数就不知道返回什么,出现报错
}
  • C语言中,return语句只能返回一个值,不能返回多个值,如数组不可返回,但Java语言可以。

(4)数组传参

在设计函数时,有时会将数组作为参数传递给函数,在函数内部对数组进行操作。
如:设计两个函数,⼀个将整型数组的内容全部置为-1;⼀个打印重置后数组的内容。
一维数组的使用:

#include <stdio.h>

// 重置函数
void set_arr(int arr[], int len)
{
	for (int i = 0; i < len; i++) {
		arr[i] = -1;
	}
}
// 打印函数
void print_arr(int arr[], int len)
{
	for (int i = 0; i < len; i++) {
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度
	// 打印函数
	print_arr(arr, len);
	// 重置函数
	set_arr(arr, len);
	// 打印函数
	print_arr(arr, len);

	return 0;
}

二维数组的使用:

#include <stdio.h>

// 重置函数
void set_arr(int arr[2][3], int r, int c)
{
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			arr[i][j] = -1;
		}
	}
}
// 打印函数
void print_arr(int arr[2][3], int r, int c)
{
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
	
}

int main()
{
	int arr[2][3] = {1,2,3,4};
	int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度
	// 打印函数
	print_arr(arr, 2, 3);
	printf("-------------------\n");  // 分割符
	// 重置函数
	set_arr(arr, 2, 3);
	// 打印函数
	print_arr(arr, 2, 3);

	return 0;
}

注意事项:

  • 函数的形参和实参个数要匹配,即个数相同
  • 函数的实参是数组,形参也是可以写成数组形式的
  • 形参若为⼀维数组,数组大小可省略不写;若为⼆维数组行可省略,但列不能省略
  • 数组传参,形参是不会创建新的数组,且形参操作的数组和实参的数组是同⼀个数组

(5)嵌套调用与链式访问

嵌套调用,即函数之间的互相调用,可以在函数内部调用函数。
如:假设我们计算某年某月有多少天?,若要函数实现,可以设计2个函数:

  • is_leap_year():根据年份确定是否是闰年
  • get_days():调用is_leap_year确定是否是闰年后,再根据月份计算这个月的天数
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

// 判断闰年
int is_leap_year(int y)
{
	if ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0)
	{
		return 1;  // 是闰年,返回 1
	}
	else
	{
		return 0;  // 不是闰年,返回 0
	}
}
// 计算天数
/*         1月  2月  3月  4月  5月  6月  7月  8月  9月  10月  11月  12月
*   闰年:  31   29   31  30   31   30   31   31  30   31   30    31
*  非闰年: 31   28   31  30   31   30   31   31  30   31   30    31
*/
int get_days(int y,int m)
{
	int days[13]= { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	int day = days[m];
	if (is_leap_year(y) && m == 2)  // 调用is_leap_year()函数判断是否为闰年
		day += 1;   // 为闰年,且为2月时天数加 1
	return day;
}

int main()
{
	int year = 0;
	int month = 0;
	printf("请输入年份与月份:");
	scanf("%d %d", &year,&month);
	printf("%d 年 %d 月有 %d 天!\n", year, month, get_days(year, month));

	return 0;
}

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

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

int main()
{
    printf("%d\n", strlen("abc"));  // strlen()函数,返回字符串长度; 输出结果: 3
    return 0;
}
#include <stdio.h>

int main()
{
	// printf()函数成功打印后返回屏幕上的字符个数
    printf("%d", printf("%d", printf("%d", 43)));  // 输出结果: 4321
    return 0;
}

注:printf函数返回的是成功打印在屏幕上的字符个数
printf(“%d”, 43),返回值为 2;printf(“%d”, printf(“%d”, 43)),返回值为 1;
故printf(“%d”, printf(“%d”, printf(“%d”, 43))),在屏幕上输出结果为 4321。

三、函数的声明与定义

在C语言中,对于变量,需要先声明再使用;同理,对于函数,也需要先声明再使用
对于单个文件

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int main()
{
    int a, b;
    scanf("%d %d", &a, &b);
    printf("%d\n", Add(a,b));
    return 0;
}

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

如,上述代码中,运行程序后会出现警告信息,但不影响程序执行的结果。之所以会出现警告信息,是因为编译器是从上往下扫描执行代码的,在调用Add函数时还未扫描到Add函数的定义(声明),就会出现警告,但最后扫描到Add函数后,程序的执行结果不会有影响。

在这里插入图片描述
解决警告信息方法:在main()函数前先声明函数,包括函数名,函数的返回类型和函数的参数。语法如下:

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

注:函数的定义也是⼀种特殊的声明,若函数定义放在调用之前也是可以的,就不需要重复声明了。

对于多个文件:函数的声明、类型的声明放在头文件(.h)中,函数的实现放在源文件(.c)中。

在这里插入图片描述
源文件在调用自己写的函数时,需包含头文件进行函数的声明,语法为

#include "Add.h"  // 头文件放在双引号中

四、static 与 extern

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

作用域(scope):是一个程序设计概念,通常来说,⼀段程序代码中所用到的名字并不总是有效(可用)的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
1.局部变量的作用域是变量所在的局部范围。即一个大括号{ }里定义的变量(局部变量)只能在该大括号{ }里(局部范围)使用,出了该大括号{ }就无法使用了。
2.全局变量的作用域是整个工程。即在整个项目代码中,全局变量都可以使用。

生命周期:指变量创建(申请内存)到变量销毁(收回内存)之间的⼀个时间段。
1.局部变量的生命周期进入作用域生命周期开始出作用域生命周期结束
2.全局变量的生命周期:整个程序的生命周期。

extern是用来声明外部符号的,如果⼀个全局的符号是在A文件中定义的,在B文件中想使用,就可以使用extern进行声明,然后使用。

在这里插入图片描述

static静态的的意思,可以修饰局部变量全局变量、和函数
static 修饰 局部变量

在这里插入图片描述
两种代码的分析
代码1:该test函数中的局部变量i是每次进入test函数先创建变量(生命周期开始)并赋值为0,然后++,再打印,函数的时候变量生命周期就会结束(释放内存)。
代码2:test函数中的局部变量i创建好后,出函数的时候是不会销毁的,重新进⼊函数也就不会重新创建变量,直接上次累积的数值继续计算。
结论static 修饰局部变量改变了变量的生命周期,生命周期改变的本质改变了变量的存储类型,局部变量本来是存储在内存的栈区的,但是被static修饰后存储到了内存的静态区,此时局部变量的生命周期就与程序的生命周期⼀样了,只有程序结束,变量才销毁,内存才回收,但作用域不变
注:局部变量一般存储在内存的栈区,全局变量存储在内存的静态区

在这里插入图片描述
static 修饰 全局变量:⼀个全局变量被static修饰会使得这个全局变量就只能在本源文件内使用,不能在其他源⽂件内使用。本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使用,只要适当的声明就可以使用;但是全局变量被static修饰之后外部链接属性就变成了内部链接属性,只能在自己所在的源⽂件内部使用,其他源⽂件,即使声明了,也是无法正常使用。

在这里插入图片描述

static 修饰 函数:与修饰全局变量相似,一个函数本身在整个工程代码的所有文件中都可以使用,但被static修饰后的函数就只能在其所在的源文件内部使用,其它文件就不能使用该函数。本质是因为函数默认具有外部链接属性,使得函数在整个工程中只要适当的声明就可以被使用。但是被static修饰后变成了内部链接属性,使得函数只能在自己所在的源文件内部使用

五、代码隐藏

⼀般在企业中写代码的时候,代码可能比较多,不会将所有的代码都放在⼀个⽂件中,往往会根据程序的功能,将代码拆分放在多个文件中。⼀般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现放在源文件(.c)中。
隐藏过程:右键项目名----->选择属性----->修改配置类型为静态库(.lib)----->点击应用,然后确定
原理将头文件和源文件编译成静态库形式,静态库里为二进制编码,从而实现隐藏。
在这里插入图片描述
应用确定后,重新生成一下解决方案,就会在我们项目里的Debug文件中生成.lib的静态库文件了。

在这里插入图片描述
.lib的静态库文件默认在项目下的Debug文件中。
在这里插入图片描述

外人使用时,只需将静态库(.lib)和头文件给到他即可,无需展示源代码,从而保护自己的代码。

外人使用过程:
1.将所给的静态库(.lib)和头文件放到自己的项目文件中,复制粘贴即可
在这里插入图片描述
2.在自己的项目文件中,选择“头文件”,然后添加“现有项”,选择第一步粘贴进项目里的头文件即可
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/149d95ec32dc43cba40efeaf46205f67.png
3.在源文件中引入头文件和静态库即可使用。

#include "Add.h"  // 引入头文件
#pragma comment(lib,"add.lib")  // 导入静态库

在这里插入图片描述

六、代码练习

一、实现一个函数is_prime,判断一个数是不是素数,并利用该函数打印100到200之间的素数。

二、实现一个函数判断year是不是闰年。

三、实现一个函数,打印乘法口诀表,口诀表的行数和列数自己指定。

四、创建一个整形数组,完成对数组的操作:1.实现函数init() 初始化数组为全0;2.实现print()打印数组的每个元素3.实现reverse() 函数完成数组元素的逆置。

参考代码:https://gitee.com/zuiltd/c-language/commit/e5f0b441bf6452982a1236938d59807bf07abe8e

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值