目录
一、C语言中函数的概念
1. C语言是由一个个函数构成的,每一个函数用于完成程序中某个特定的任务。
2. 采用函数的好处:①可以拆分任务 ②可以重复使用。
3. C语言中的函数分为库函数和自定义函数两大类。
二、库函数
2.1 库函数的概念
1. 库函数是一些可以现成使用的函数,在使用库函数时要提前对该库函数所包含的头文件进行声明。
2. 补充:C语言规定了库函数的语法标准,这些标准组成了标准库,但C语言并不提供现成的库函数,这些库函数的实现由不同的编译器厂商实现,函数的实现细节可能有差异,但程序员在使用时基本不影响。
2.2 怎样自己学习库函数_以sqrt函数为例
从上至下:
① 函数的语法形式
② 函数的功能、
③ 函数的参数说明
④ 函数返回值说明
⑤ 函数使用时的代码举例(含所需包含的头文件)
⑥ 与之类型的其他库函数
三、自定义函数
3.1 自定义函数的语法形式
//语法形式:ret_type fun_name(形式参数)
{
}
1. ret_type:函数的返回类型(什么类型都可以,也可以什么也不返回void,如果返回类型不写则会默认返回类型为int类型)。
2. fun_name:函数的名称。
3. (形式参数):形式参数可以没有(空或void),也可以有1个或多个,这些形参采用什么类型都可以(如果形参为空,在函数调用时强制传参,该函数也会被正常调用)。
4. { } 代码块里写自定义函数实现的过程。
3.2 自定义函数的举例
//用Add函数分装加法功能
#include <stdio.h>
int Add(int x, int y)//x y为形参
{
return x + y;
}
int main()
{
int a = 0, b = 0;
scanf("%d %d", &a, &b);
int ret = Add(a, b);//a b为形参
printf("%d\n", ret);
return 0;
}
3.3 自定义函数的实参
1. 在函数调用时,传递给函数的参数被称为实参,传参时不用注明变量的类型。
3.4 自定义函数的形参
1. 在函数定义时( )中的参数被称为形参,形参在函数定义时要注明各各参数是什么类型的。
2. 补充:函数在没有被调用时,自定义函数的形参变量并没有在内存中申请空间,只有在自定义函数被调用后为了复制实参的值才会向内存申请空间。
3.5 自定义函数的实参和形参的关系
1. 形参是实参的一份临时拷贝,对形参的修改并不会改变实参的数值,形参和实参可同名。
3.6 自定义函数中的return语句
1. 自定义函数中如果返回类型为void可以省略return语句,也可以写成void;
2. return语句后面可以是一个数值,也可以是一个表达式,如果是表达式则会先执行表达式再返回表达式的值。
3. return返回的值与自定义函数的返回类型相冲突时,自定义函数返回类型为尊,返回值将会被隐形强制转换。
4. return语句执行后,自定义函数中return语句后面的代码将不会再被执行。
5. 如果自定义函数中存在分支语句,则要保证每个分支下都有return语句,否则会出现编译错误。
3.7 数组做自定义函数的参数
1. 数组传参,形参是不会创建新数组的,形参操作的数组和实参的数组是同⼀个数组。
2. 函数的形参个数要和函数的实参个数匹配,在传参时把数组的大小一并传过去。
3. 数组传参时,实参部分写成数组名,形参写成数组的形式即可(数组名其实是数组首元素的地址,数组传参的本质是传址调用而不是传值调用)。
4. 形参如果是⼀维数组,数组⼤⼩可以省略不写。
5. 形参如果是⼆维数组,⾏的大小可以省略,但列的大小不能省略。
//用Print函数分装打印数组的功能
#include <stdio.h>
void Print(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
Print(arr, sz);
return 0;
}
3.8 函数的嵌套调用
1. 函数的嵌套调用就是函数中调用函数。
2. 函数之间可以嵌套调用,但不可以嵌套定义。
//【计算某年某⽉有多少天】
#incluide <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;
}
3.9 函数的链式访问
1. 函数的链式访问就是将一个函数的返回值作为另一个函数的参数,实现像链条一样将函数串起来的效果。
#include <stdio.h>
#include <string.h>
//示例1
int main()
{
printf("%zd\n", strlen("abcdef"));
return 0;
}
#include <stdio.h>
#include <string.h>
//示例2
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
四、函数的定义、声明和使用
4.1 单文件中
1. 只需用到.c单个文件写代码时,一般将自定义函数的函数定义写在被调用时所在的函数之前,这样在调用自定义函数时,就可以不需要对调用的自定义函数进行声明,因为函数的定义是一种特殊的函数声明形式。
2. 如果把自定义函数的函数定义写在被调用时所在的函数之后了,这样在调用自定义函数时,就必须要对调用的自定义函数进行声明,在整个文件开头声明就行,声明的格式是函数定义的第一行后面加一个封号。
//【判断⼀年是不是闰年】
#include <stido.h>
//示例1:定义在前
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>
//示例2:定义在后
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;
}
4.2 多文件中
1. ⼀般在企业中写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会根据程序的功能,将代码拆分放在多个(.h)和(.c)⽂件中。
2. ⼀般情况下,函数的声明、类型的声明放在头⽂件(.h)中,函数的定义是放在源⽂件(.c)
⽂件中。
3. 如下图中所示,假设企业要完成一个计算器的实现程序,分为了4个模块,每个模块又有对应的(.c)和(.h)文件,(.c)文件中实现函数的定义,(.h)文件中实现函数的声明,在有main函数的(.c)文件中如果想要调用这些不同模块的函数的话,在文件开头写上包含对应的头文件即可,但< >要换为" ",例如,#include "Add.h",头文件的包含本质上也是一种函数的声明。
4. 优点:①逻辑清晰 ②方便协作 ③把一个模块又分为(.c)和(.h)文件后也方便代码的隐藏(未来可以只给别人看(.h)文件)
五、关键字static和extern
概念:
① static是“静态的”意思,可以用来修饰局部变量、全局变量、函数。
② extern是用来修饰外部符号的,这里的外部指的是一个程序中不同(.c)文件(extern可以声明外部全局变量,也可以声明外部定义的自定义函数,格式例如,extern int arr;extern int Add (int a, int b); 声明外部的自定义函数时也可以直接写成例如,int Add (int a, int b); 因为函数的声明本身就自带extern,但最好还是写上)
补充:
① 作用域:⼀段程序代码中所⽤到的名字并不总是有效的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。
1. 局部变量的作⽤域是变量所在的局部范围
2. 全局变量的作⽤域是整个⼯程(项⽬)
② 生命周期:指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。
1. 局部变量的⽣命周期是:进⼊作⽤域变量创建,⽣命周期开始,出作⽤域⽣命周期结束。 2. 全局变量的⽣命周期是:整个程序的⽣命周期。
5.1 static修饰局部变量
概念:static修饰局部变量改变了变量的⽣命周期,⽣命周期改变的本质是改变了变量的存储类型,本来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的,⽣命周期就和程序的⽣命周期⼀样了,只有程序结束,变量才销毁,但被修饰的局部变量的作⽤域不变。
使⽤建议:未来⼀个变量出了函数后,我们还想保留值,等下次进⼊函数继续使⽤,就可以使⽤static 修饰。
总之,static修饰局部变量,这个局部变量的生命周期与全局变量的生命周期一样了,但作用域还是原来的作用域。
5.2 static修饰全局变量
概念:⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤。本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使⽤;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源⽂件内部使⽤了,其他源⽂件,即使声明了,也是⽆法正常使⽤的。
使⽤建议:如果⼀个全局变量,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤ static修饰。
总之,static修饰全局变量,这个全局变量本来可以在整个程序的所有文件(.c)中用extern声明一下就可以用了,但被static修饰后只能在自己所在的(.c)文件中使用,其他的(.c)文件中即使声明了也没用。
5.3 static修饰函数
概念:static 修饰函数和 static 修饰全局变量是⼀模⼀样的,⼀个函数在整个⼯程想使用都可以使⽤,只需要适当声明就可以,但被static修饰后,只能在本⽂件内部使⽤,其他⽂件⽆法正常的链接使⽤了。本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个⼯程中只要适当的声 明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部 使⽤。
使⽤建议:⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修饰。
总之,static修饰函数,函数和全局变量一样本来也可以在整个程序的所有文件(.c)中用extern声明一下,或者在(.h)文件中声明,再在想调用它的(.c)文件中声明一下这个函数所在的(.h)就可以用了,但被static修饰后只能在自己所在的(.c)文件中使用,其他的(.c)文件中即使声明了也没用。
本篇已完结。。。。。。