C语言入门(十一)续:函数的深入认识
一:嵌套调用和链式访问
1.嵌套调用
嵌套调⽤就是函数之间的互相调⽤,每个函数就像⼀个乐⾼零件,正是因为多个乐⾼的零件互相⽆缝 的配合才能搭建出精美的乐⾼玩具,也正是因为函数之间有效的互相调⽤,最后写出来了相对⼤型的 程序。
问题:计算某年某月有多少天,例如2008年8月,它共有多少天(分闰年和平年)
部分代码如下:
int is_leap_year(int year)//判断函数,这里的return后面的数,只表示真假意义,C语言一般规定0为假,非0为真
{if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))//计算 闰年 return 1;//是闰年则返回1elsereturn 0;//不是闰年则返回0
}
判断函数,这里的return后面的数,只表示真假意义,C语言一般规定0为假,非0为真
int is_leap_year(int year) return 1;
return 0;
计算闰年的方法
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
部分代码如下:
int get_days_of_month(int year, int month)
{ //这里的参数可以和get那一串函数括号里面的不一样,不影响我们int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31, };//1-12月对应的天数int day = days[month];if (is_leap_year(year) && month == 2)day += 1;return day;
}
下面的代码是计算函数,return后面的数,只表示返回具体的天数(计算结果),这里返回值是day即我们需要的那个天数,如果是0那就没有意义了
int get_days_of_month(int year, int month)
return day;
如果是闰年则2月的天数加1 即29天
day += 1
1-12月对应的天数,因为下标是从0开始的,所以这里我们故意加个0进去,一一对应起来
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 1 0 11 12 月
利用下标来找某月所对应的天数,如果我们的month的下标是5,那么对应的就是5月,31天
int day = days[month];
计算是闰年的情况即2月是29天
if (is_leap_year(year) && month == 2)
主函数代码如下:
int main()
{int year = 0;int month = 0;scanf("%d %d", &year, &month);//输入 年 月int days = get_days_of_month(year,month);//获取某月的天数 printf("%d\n", days);//将天数打印出来return 0;
}
获取某月的天数
注意:这里用 _ 是因为函数名字中间不能用空格,如果不用空格让它们贴在一起虽然可以,但是不好看,这个()里面的代码是将year month 传给int get_days_of_month那一串函数里面去,然后算出我们的天数,然后我们将天数存入int days 里面去
int days = get_days_of_month(year,month);
完整代码如下:
int is_leap_year(int year)//判断函数,这里的return后面的数,只表示真假意义,C语言一般规定0为假,非0为真
{if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))//计算 闰年 return 1;//是闰年则返回1elsereturn 0;//不是闰年则返回0
}
//
//下面的代码是计算函数,return后面的数,只表示返回具体的天数(计算结果)
int get_days_of_month(int year, int month)//由于我们计算的是天数,所以我们的返回函数类型是 int 整型
{ //这里的参数可以和下面get那一串函数括号里面的不一样,不影响我们int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31, };//1-12月对应的天数
// 下标 0 1 2 3 4 5 6 7 8 9 10 11 12月 因为下标是从0开始的,所以这里我们故意加个0进去,一一对应起来int day = days[month];//利用下标来找某月所对应的天数,如果我们的month的下标是5,那么对应的就是5月,31天if (is_leap_year(year) && month == 2)//计算是闰年的情况即2月是29天day += 1;//如果是闰年则2月的天数加1 即29天return day;//这里返回值是day 即我们需要的那个天数,如果是0那就没有意义了
}
//
//
int main()
{int year = 0;int month = 0;scanf("%d %d", &year, &month);//输入 年 月int days = get_days_of_month(year,month);//获取某月的天数 // 这里用 _ 是因为函数名字中间不能用空格,如果不用空格让它们贴在一起虽然可以,但是不好看
// 上面这个()里面的代码是将year month 传给int get_days_of_month那一串函数里面去,然后算出我们的天数
// 然后我们将天数存入int days 里面去printf("%d\n", days);//将天数打印出来return 0;
}
闰年的输出结果:
平年的输出结果:
2.链式访问
所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数 的链式访问。
代码如下:
普通的计算字符长度
#include<string.h>//strlen函数需要的头文件
int main()
{int len = strlen("abcdef");//计算字符长度并将它存放到len里面去printf("%d\n", len);//将字符长度打印出来return 0;
}
strlen函数需要的头文件
#include<string.h>
输出结果:
前⾯的代码完成动作写了2条语句,把如果把strlen的返回值直接作为printf函数的参数呢?这样就是⼀ 个链式访问的例⼦了。
利用链式访问,计算出字符长度
代码如下:
int main()
{printf("%d\n", strlen("abcdef") );return 0;
}
输出结果:
练习:下面代码的输出结果是什么?
代码如下:
int main()
{printf("%d\n", printf("%d", printf("%d", 43) ) );printf("%d", printf("%d ", printf("%d ", 43) ) );return 0;
}
第一个:输出4321
原因:里面的先执行,将每一步的返回值慢慢的往外
printf函数返回的是打印在屏幕上的字符的个数。 上⾯的例⼦中,我们就第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的是第三个 printf的返回值。 第三个printf打印43,在屏幕上打印2个字符,再返回2 第⼆个printf打印2,在屏幕上打印1个字符,再放回1 第⼀个printf打印1 所以屏幕上最终打印:4321
printf("%d\n", printf("%d", printf("%d", 43) ) );
输出结果:
第二个:输出43 3 2
原因:这里我们会先打印出43 是2个字符,然后有1个空格,总共3个字符于是它的返回值是3并且返回到第二个printf里面去,于是打印出3 它是1个字符,然后后面还有一个空格,于是它是2个字符,同样返回值是2返回到第一个printf里面去,于是打印出2
注意:空格也算一个字符
printf("%d", printf("%d ", printf("%d ", 43) ) );
输出结果:
二:函数的声明和定义
1.单个⽂件
- 函数的定义
问题:判断一年是否是闰年
部分代码如下:
函数定义(main函数以上的代码)
int is_leap_year(int year)
{if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))//计算闰年{return 1;//是闰年则返回值为1}else{return 0;//不是闰年则返回值为0}return 0;
}
计算闰年的方法:
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
主函数代码如下:
int main()
{int year = 0;scanf("%d", &year);//输出年份int r = is_leap_year(year);//通过调用函数is_leap_year(year);将返回值穿到int r里面去if (r == 1){printf("闰年\n");}else{printf("非闰年\n");}return 0;
}
通过调用函数is_leap_year(year); 将返回值穿到int r里面去
int r = is_leap_year(year);
完整代码如下:
int is_leap_year(int year)
{if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))//计算闰年{return 1;//是闰年则返回值为1}else{return 0;//不是闰年则返回值为0}return 0;
}int main()
{int year = 0;scanf("%d", &year);//输出年份int r = is_leap_year(year);//通过调用函数is_leap_year(year);将返回值穿到int r里面去if (r == 1){printf("闰年\n");}else{printf("非闰年\n");}return 0;
}
非闰年的输出结果:
闰年的输出结果:
如果我们将定义函数和主函数main的位置进行调换,主函数在前面,定义函数在后面,那么我们的结果是什么呢?
答案是:编译器会出现警告,会报错误的
原因:这是因为C语⾔编译器对源代码进⾏编译的时候,从第⼀⾏往下扫描的,当遇到第7⾏的 函数调⽤的时候,并没有发现前⾯有 is_leap_year的定义,就报出了上述的警告。
- 函数的声明
函数的调⽤⼀定要满⾜,先声明后使⽤
怎么把怎么解决这个问题呢?就是函数调⽤之前先声明⼀下 is_leap_year is_leap_year这个函数,声明函数只要交代清 楚:函数名,函数的返回类型和函数的参数。 如: int is_leap_year(int y);这就是函数声明,函数声明中参数只保留类型,省略掉名字也是可以 的
声明后的完整代码如下:
int is_leap_year(int year);//有了它我们就可以将main函数与int is_leap_year(int year)函数调换位置,否则我们只能将main函数写在后面int main()
{int year = 0;scanf("%d", &year);//输出年份int r = is_leap_year(year);//通过调用函数is_leap_year(year);将返回值穿到int r里面去if (r == 1){printf("闰年\n");}else{printf("非闰年\n");}return 0;
}int is_leap_year(int year)
{if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))//计算闰年{return 1;//是闰年则返回值为1}else{return 0;//不是闰年则返回值为0}return 0;
}
在最前面有了它我们就可以将main函数与int is_leap_year(int year)函数调换位置,否则我们只能将main函数写在后面
int is_leap_year(int year);
2.多个文件
⼀般在企业中我们写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会 根据程序的功能,将代码拆分放在多个⽂件中。
⼀般情况下,函数的声明、类型的声明放在头⽂件(.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 都是C语⾔中的关键字。
static 是 静态的的意思,可以⽤来:
• 修饰局部变量
• 修饰全局变量 比特就业课
• 修饰函数
extern 是⽤来声明外部符号的。
在讲解 static 和 extern 之前再讲⼀下:作⽤域和⽣命周期。 作⽤域(scope)是程序设计概念,通常来说,⼀段程序代码中所⽤到的名字并不总是有效(可⽤) 的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。 1. 局部变量的作⽤域是变量所在的局部范围。 2. 全局变量的作⽤域是整个⼯程(项⽬)。 ⽣命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。 1. 局部变量的⽣命周期是:进⼊作⽤域变量创建,⽣命周期开始,出作⽤域⽣命周期结束。 2. 全局变量的⽣命周期是:整个程序的⽣命周期。
- static 修饰局部变量:
代码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;}
代码2如下:
#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;
代码1的输出结果:
代码2的输出结果:
对⽐代码1和代码2的效果,理解 static 修饰局部变量的意义。
代码1的test函数中的局部变量i是每次进⼊test函数先创建变量(⽣命周期开始)并赋值为0,然后 ++,再打印出函数的时候,变量⽣命周期将要结束(释放内存)。
代码2中,我们从输出结果来看,i的值有累加的效果,其实test函数中的i创建好后,出函数的时候是 不会销毁的,重新进⼊函数也就不会重新创建变量,直接上次累积的数值继续计算。
- static 修饰全局变量
代码如下
extern 是⽤来声明外部符号的,如果⼀个全局的符号在A⽂件中定义的,在B⽂件中想使⽤,就可以使用extern进⾏声明,然后使⽤
代码1正常,代码2在编译的时候会出现链接性错误。
结论: ⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤。 本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使 ⽤;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源 ⽂件内部使⽤了,其他源⽂件,即使声明了,也是⽆法正常使⽤的。 使⽤建议:如果⼀个全局变量,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤ static修饰。
- static 修饰函数
代码1是能够正常运⾏的,但是代码2就出现了链接错误。 其实 static 修饰函数和 static 修饰全局变量是⼀模⼀样的,⼀个函数在整个⼯程都可以使⽤, 被static修饰后,只能在本⽂件内部使⽤,其他⽂件⽆法正常的链接使⽤了。 本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个⼯程中只要适当的声 明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部 使⽤。
使⽤建议:⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤static 修饰