一篇文章,搞定函数

库函数

什么是库函数

不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现如printf、scanf、sqrt等等,这些函数被称为库函数

库函数的实现是包含在对应的头文件里的,所以在使用库函数的时候需要包含相应的头文件,比如:使用printf函数,需要包含头文件<stdio.h>;使用sqrt函数,需要包含<math.h>头文件。

自定义函数

自定义函数包含三部分:1.函数类型 2.函数名 3.函数参数 4.函数主体 5.return语句。

int sum(int a,int b)//函数类型  函数名   函数参数
{
	int ret=a+b;    //函数主体
	return ret;     //return语句
}

函数的类型

有返回值的情况

函数的类型其实就是函数返回的值的类型,它可以是c语言中的任何数据类型,包括:基本类型(int,double,float,char)和复合类型(结构体,联合体等)
比如:

int sum(int a,int b)
{
	return a+b;
}

这个函数的类型是int,它的返回值类型是也是int。

函数的类型和返回值类型不一致

当函数的类型和函数返回值的类型不一致时,系统会自动将返回的值隐式的转换成函数的类型。
在这里插入图片描述

sum是用来计算两数之和的一个函数,它的类型是double,但是返回值的类型是int,我们运行一下
在这里插入图片描述
很显然,当返回值ret和函数的类型不一致时,return会隐式的把返回值类型转换成函数的类型。
(如果不会隐式转换,那么在主函数的输出语句中将输出ret的值,但是我们实际运行后发现,它输出的值是0,这也证明了我前面所说的)

无返回值的情况

当函数不需要返回值的时候,函数的类型就是void,这表示该函数不需要返回任何值。
比如:

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

这个函数就不需要返回值,所以它是void类型,它的作用就是打印数组arr中存放的值。
当函数为无返回值的情况时,就不需要写return语句了,如果要写,可以写成如下形式:

void prin_tf(int arr[],int len)
{
	...省略
	return;
}

函数的参数

参数的分类

函数的参数分为实参形参
形参:在创建函数时在函数名后面的参数。

int sum(int a,int b);//这里的a和b就是形参

实参:在调用函数时,跟在函数名后面的参数。

int sum(int a,int b);
int main()
{
	int a=1,b=1;
	printf("%d",sum(a,b));//这里的a,b就是实参
}

那么实参和形参之间有什么关系呢?我们通过调试来观察。

//这是一段调用sum函数求和的代码
int sum(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}
int main()
{
    int a = 0;
    int b = 0;
    //输⼊
    scanf("%d %d", &a, &b);
    //调⽤加法函数,完成a和b的相加
    //求和的结果放在r中
    int r = sum(a, b);
    //输出
    printf("%d\n", r);
    return 0;
}

在vs里调试观察实参与形参的值和地址。
我们不难发现,实参与形参的值是相等的,但是地址不等,所以我们可以理解为:
形参是实参的一份临时拷贝。
在这里插入图片描述

数组做参数

在使⽤函数解决问题的时候,难免会将数组作为参数传递给函数,在函数内部对数组进⾏操作。
比如要将数组中的值全部赋为-1;

void set_arr(int arr[],int len)
{
	for(int i=0;i<len;i++)
	{
		arr[i]=-1;
	}
}
int mian()
{
	int arr[10]={1,2,3,4,5,6,7,8,9,10};
	set_arr(arr,10);
	for(int i=0;i<10;i++)
	{
		printf("%d",arr[i]);
	}
	return 0;
}

因为数组作为参数实际上传递的是地址,所以函数不需要返回值,函数的类型是void。
在数组作为参数时,要注意一下几点:

  • 函数的形参与实参个数、类型要匹配。
  • 函数的实参是数组,形参也可以写成数组的形式。
  • 形参如果是一维数组,数组的长度可以省略。
  • 形参如果是二维数组,数组的行数可以省略,但是列不能省略。
  • 数组传参,形参不会创建新的数组的。
  • 形参与实参的数组其实是同一个。

return语句

在函数的设计中,函数中经常会出现return语句,这⾥讲⼀下return语句使⽤的注意事项。
• return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式
的结果。
• return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
• return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
• return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
• 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。

函数的调用

嵌套调用

嵌套调用就函数之间相互调用。比如:

#include<stdio.h>
int is_leap_year(int y)
{
 if(((y%4==0)&&(y%100!=0))||(y%400==0))
 return 1;
 else
 return 0;
}
int get_days_of_month(int y, int m)
{
 int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 int day = days[m];
 if (is_leap_year(y) && m == 2)
 day += 1;
 
 return day;
}

int main()
{
 int y = 0;
 int m = 0;
 scanf("%d %d", &y, &m);
 int d = get_days_of_month(y, m);
 printf("%d\n", d);
 return 0;
}

在main函数里调用get_days_of_month函数得到月份的天数,在get_days_of_month函数里调用is_leap_year函数判断是否是闰年。

但是需要注意一点:函数是不能嵌套定义的。

链式调用

所谓的链式调用就将一个函数的返回值作为另一个函数的参数,想条链子一样将函数串起来。
比如:

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

这段代码的输出结果你知道是什么吗?
:43 2 1
printf函数返回的是打印在屏幕上的字符的个数,第一个printf输出43,返回一个2,第二个printf输出一个2,返回一个1,那最后一个printf输出的就是1了;

函数的定义和声明

单个文件

假设我们要写一个函数判断是否是闰年:

#include <stido.h>
//判断⼀年是不是闰年
int is_leap_year(int y)//这块是函数的定义
{
 if(((y%4==0)&&(y%100!=0)) || (y%400==0))
 return 1;
 else
 return 0;
}

int main()
{
 int y = 0;
 scanf("%d", &y);
 int r = is_leap_year(y);//这里是函数的调用
 if(r == 1)
 printf("闰年\n");
 else
 printf("⾮闰年\n");
 return 0;
}

此时,程序是能正常运行的,但是当我们把函数的定义放在主函数后面,或者说放在函数调用的后面:

#include <stido.h>
int main()
{
 int y = 0;
 scanf("%d", &y);
 int r = is_leap_year(y);
 if(r == 1)
 printf("闰年\n");
 else
 printf("⾮闰年\n");
 return 0;
}

//判断⼀年是不是闰年
int is_leap_year(int y)
{
 if(((y%4==0)&&(y%100!=0)) || (y%400==0))
 return 1;
 else
 return 0;
}

此时程序依旧可以正常运行,但是会报出警告:

在这里插入图片描述
这是因为c语言编译器是从第一行往后扫描的,当遇到函数调用时,并没有发现之前有函数的定义,于是发出这个警告,那么我们该如何解决这个问题呢?
很简单,只需要在前面对函数进行声明一下就好了:

#include <stido.h>
int is_leap_year(int y)//函数声明
int main()
{
 int y = 0;
 scanf("%d", &y);
 int r = is_leap_year(y);
 if(r == 1)
 printf("闰年\n");
 else
 printf("⾮闰年\n");
 return 0;
}

/判断⼀年是不是闰年
int is_leap_year(int y)
{
 if(((y%4==0)&&(y%100!=0)) || (y%400==0))
 return 1;
 else
 return 0;
}

此时代码就不会报警告了。

多个文件

一般企业中,我们不会把代码全放在一个文件下进行编写,往往会
根据程序的功能,将代码拆分放在多个⽂件中。
把函数的声明放在.h头文件下,把函数的实现放在.c的源文件下。
不如:
在这里插入图片描述
在add.c文件下的代码

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

在add.h文件下的代码:

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

在test.c文件下的代码:

#include <stdio.h>
#include "add.h"
int main()
{
 int a = 10;
 int b = 20;
 //函数调⽤
 int c = Add(a, b);
 printf("%d\n", c);
 return 0;
}

static和extern

static:是静态的意思。
extern:是用来声明外部符号的。

当用static修饰局部变量
代码1

#include <stdio.h>
void test()
{
 //static修饰局部变量
 static int i = 0;
 i++;
 printf("%d ", i);
}
int main()
{
 int i = 0;
 for(i=0; i<5; i++)
 {
 test();
 }
 	return 0;
}

代码2:

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

对比代码1和代码2,我们发现

代码1输出了5次1,而代码2输出的是“1 2 3 4 5”

static修饰局部变量改变了变量的生命周期,其本质是改变了变量的存储类型。原本局部变量是存储在栈区的,现在存储在了静态区,静态区的变量和全局变量一样,生命周期是整个程序,程序什么时候结束,变量什么时候消亡。

当用static修饰全局变量时:
static会将全局变量的外部链接属性改为内部链接属性,从而该全局变量只能在本文件下使用。

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值