目录
一、函数的概念
(一)概念
C语言中的函数是【完成某项特定任务的一段代码】;
(二)分类
① 库函数,如 printf ,特点是直接使用,不需要自己定义
② 自定义函数,由用户自己写的函数
二、库函数
(一)标准库
C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数;
C语言的国际标准(ANSI C)规定了一些常用的函数的标准,被称为标准库;
(二)库函数
不同的编译器厂商根据【ANSI提供的C语言标准】就给出了一系列函数的实现,这些函数就被称为库函数
(三)头文件
各种编译器的标准库中提供了一系列的库函数,这些库函数根据功能的划分,都在不同的头文件中进行了声明;
注:
库函数的使用,务必包含对应的头文件,若不包含则可能会出现一些问题
(四)相关链接
库函数相关头文件:https://zh.cppreference.com/w/c/headerC/C++官方的链接:https://zh.cppreference.com/w/c/headercplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
三、自定义函数
函数定义的一般形式如下,其中【大括号 { } 】包着的部分又被称为函数体:
注:
① 调试窗口按 【F11】 才进的去函数;
②【返回类型】是指 return 后面【返回值】的类型;
③ 函数【返回值】先放到寄存器,再把寄存器的值给主函数的变量;
④ 函数要求【返回值】,但是函数中没写 return 的【返回值】,那具体返回什么就不确定了;
⑤ 函数要求没有【返回值】,则【返回类型】要写 void,不能省略不写;如果没写函数的【返回类型】,编译器则认为返回的是【int类型】的值
四、实参和形参
(一)实参
在调用函数的时候,真实传递给函数的参数,称为实际参数,简称实参
(二)形参
1、概念
在函数定义部分,函数名后面的参数,称为形式参数,简称形参
2、形参的实例化
若不调用函数,函数的参数只会在形式上存在;
形式参数只有在函数被调用的过程中为了【存放实参传递过来的值】,才向内存申请空间,这个过程就是形参的实例化
(三)实参与形参的关系
① 形参要和实参的个数匹配
② 实参与形参的名字可以一样,也可以不同;
③ 实参和形参各自是独立的空间,地址是不一样的;所以可以理解为形参是实参的一份临时拷贝;
五、return 语句
函数中经常会出现 return 语句,下面是一些 return 语句的注意事项:
① return语句执行后,函数就彻底返回,后边的代码不再执行;
② return后面可以是数值,也可以是表达式;若是表达式,先执行表达式,再 return 结果;
③ return后面可以什么都没有,直接写【return ; 】,这种写法适合函数【返回类型是 void】 的情况;
④ 【return 返回值的类型】和函数定义的【返回类型】不⼀致,系统会自动将【return 返回值的类型】隐式转换为函数定义的【返回类型】,但不建议这样写代码;
int add(int x, double y)
{
return x/y
}
x/y是double类型,会被隐式转换为int类型
⑤ 如果函数中存在 if 等分支的语句,则要保证每种情况下都有【return 返回值; 】,否则会出现编译错误
六、数组做函数参数
重点知识:
① 实参写数组名即可(一维数组和二维数组都一样),形参写数组创建的形式,如果一维数组可以省略数组大小,但千万别省略了【中括号】,如果是二维数组,行可以省略,但是列不能省略 ;
void init_arr1(int arr1[])
{
函数体
}
void init_arr2(int arr2[][5])
{
函数体
}
② 数组在传参的时候,传的是数组首元素的地址,形参不会创建新的数组;形参操作的数组和实参的数组是同一个数组(指向同一段内存单元);
七、嵌套调用和链式访问
(一)嵌套调用
概念:函数之间的互相调用
例如:
假设我们计算某年某月有多少天?如果要函数实现,可以设计2个函数:
① is_leap_year():根据年份确定是否是闰年
② get_days_of_month():调用is_leap_year确定是否是闰年后,再根据月计算这个月的天数
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};
这里数组补0,是为了将输入的月份与下标对应,方便引用数组的天数
int day = days[m];
if (is_leap_year(y) && m == 2) 函数调用函数,嵌套使用
day += 1;
return day;
}
#include <stdio.h>
int main()
{
int y = 0;
int m = 0;
printf("请输入年与月:\n");
scanf("%d %d", &y, &m);
int d = get_days_of_month(y, m);
printf("%d年%d月有:%d天\n", y, m, d);
return 0;
}
注:
函数不能嵌套定义
(二)链式访问
概念:将一个函数的返回值作为另一个函数的参数,像链条一样将函数串起来就是函数的链式访问
例如:
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));
return 0;
}
八、函数的声明和定义
函数和变量都遵循 " 先声明,后使用 " 的原则,定义是一种特殊的声明
(一)在单个文件中
1、函数的定义
void print()
{
printf("I like pizza");
}
2、函数的声明
void print();
3、函数的调用
print();
① 把【函数定义】的第一行拿过来,末尾加个分号,然后把整个函数体( { } 包着的部分)去掉,得到的这一行,就是函数声明;
② 【函数定义】或者【函数声明】要放在【函数调用】的前面
(二)在多文件中
一般在企业中我们写代码时候,代码可能比较多,不会将所有的代码都放在一个文件中;我们往往会根据程序的功能,将代码拆分放在多个文件中;
一般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现是放在源文件(.c)文件中。
如下:
add.c 文件:
函数的定义(实现) int Add(int x, int y) { return x + y; }
add.h 文件:
函数的声明 int Add(int x, int y);
【函数定义(.c)】和【函数声明(.h)】作为一个模块 ⬆
【函数调用(.c)】作为另一模块 ⬇
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; }
注:
① 头文件包含:#include "文件名.h",目的是为了解决函数声明的问题 ;
② C语言标准库中的头文件用 < > ;
③ 自己创造的头文件用 " " ;
九、有意思的点
(一)"static" 和 "extern" 关键字
在说明 static 和 extern 前,先了解【作用域】和【生命周期】,这是理解 static 和 extern 的关键
1、作用域
是程序设计理念,理解为代码的有效范围
注:
局部变量的作用域是:变量所在的局部范围;
全局变量的作用域是:整个工程(项目)
2、生命周期
是变量的创建(申请内存)到变量销毁(收回内存)之间的一个时间段
注:
局部变量的生命周期是:进入作用域,变量创建,生命周期开始,出作用域生命周期结束;
全局变量的生命周期是:整个程序的生命周期
3、"extern" 关键字
在C语言中,"extern" 关键字用于声明一个本文件之外的【符号(全局变量或函数等)】,表示该【符号(全局变量或函数等)】是在其他文件中定义的,当前文件中只是引用它;
简单来说:一个全局的符号在A文件中定义,想在B文件中使用,就可以使用 " extern "进行声明,然后使用, 如下演示:
add.c
int a = 2018;
test.c#include <stdio.h> extern int a; 进行声明 int main() { printf("%d\n", a); return 0; }
这样做可以避免重复定义,同时让多个文件可以共享同一个符号(全局变量或函数等)
注:
① 变量的声明和创建的格式一样:
② 每个源文件(.c)都是分开编译的,所以在一个文件定义的全局变量,被另一个文件的函数直接引用,会显示未声明,若要使用,则用 " extern " 来声明;
③ 全局变量可以单独放在一个源文件里面(在编译器链接的时候会找全局变量的出处)
④ "extern" 是让【函数、全局变量等符号】在其他文件发扬光大的
4、"static" 关键字
" static " 关键字主要起限制作用
(1)修饰局部变量
先看两段代码:
代码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 1 1 1 1
test 函数中的局部变量 i 是每次进入 test 函数先创建变量(生命周期开始)并赋值为0,然后 ++,再打印,出函数的时候变量生命周期将要结束(释放内存)
代码2:
#include <stdio.h> void test() { static int i = 0; static修饰局部变量 i++; printf("%d ", i); } int main() { int i = 0; for(i=0; i<5; i++) { test(); } return 0; } 输出:1 2 3 4 5
从输出结果来看,i 的值有累加的效果,其实 test 函数中的 i 创建好后,出函数的时候是不会销毁的,重新进入函数也就不会重新创建变量,直接上次累积的数值继续计算
结论:
① static 修饰局部变量改变了变量的【生命周期】,生命周期改变的本质是改变了变量的【存储类型】,本来一个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的,生命周期就和程序的生命周期⼀样了,只有程序结束,变量才销毁,内存才回收,但是作用域不变!!
② 编译器在编译代码的时候,就为静态变量分配了地址,而不是进入函数创建变量(栈区:存放临时的变量,进函数就创建,出函数就销毁)
(2)修饰全局变量
代码1:
文件1:add.c int g_val = 2018;
文件2:test.c #include <stdio.h> extern int g_val; int main() { printf("%d\n", g_val); return 0; }
代码 1 可以正常运行
代码2:
文件1:add.c static int g_val = 2018;
文件2:test.c #include <stdio.h> extern int g_val; int main() { printf("%d\n", g_val); return 0; }
代码 2 在编译时会出现链接性错误
总结:
① 一个全局变量被static修饰,使得这个全局变量只能在本身所处的源文件内使用,不能在其他源文件内使用;
本质是全局变量默认带有外部链接属性,适当声明后另一个文件也可以使用,在 static 修饰全局变量的时候,改变了全局变量的链接属性,使得外部链接属性变成了内部链接属性,只能被自己所处的文件使用,看似是作用域变了,本质是连接属性变了。
② 如果一个全局变量,只想在本身所处的源文件内部使用,不想被其他文件发现,就可以用static 修饰
(3)修饰函数
总结:
① static 修饰函数和static修饰全局变量的作用是一模一样的,一个函数在整个工程都可以使用,被static修饰后,只能在自身所在的文件使用,其他文件无法正常的链接使用了;
本质是因为函数默认具有外部链接属性,使得函数在整个工程中只要适当的声明就可以被使用,但被 static 修饰后就变成了内部链接属性,使得函数只能在自己所在源文件内使用。
② 一个函数只想在本身所处的源文件内部使用,不想被其他源文件使用,就可以使用 static 修饰
以上博客仅供分享