一、函数(核心)
1.1.明确:
任何C程序,C源文件都包含两部分内容:一对的变量(包括数组)和一堆的函数
1.2.函数概念:
函数就是一堆语句的组合,用以实现一些相对独立并且具有一定通用性的功能
分析:没有函数的代码极其繁琐啰嗦,代码重复重复的编写,加大开发的工作量期望:只需将以上重复的代码写一遍即可,其他文件只需使用即可,减少开发的工作量
方案:
vim add.c
//编写一个加法函数add
int add(int x, int y)
{
if(x < 0 || y < 0) {
printf("请重新输入正数.\n");
return -1; //让程序结束,不能继续运行
}
//只有输入的数是正数才能加
sum = a + b;
printf("sum = %d\n", sum);
return sum;
}
vim main1.c
int main(void)
{
int a;
int b;
int sum = 0;
scanf("%d%d", &a, &b);
sum = add(a, b);
return 0;
}
vim main2.c
int main(void)
{
int a;
int b;
int sum = 0;
scanf("%d%d", &a, &b);
sum = add(a, b);
return 0;
}
vim main3.c ....main250.c 只需很轻松的用add函数即可
1.3.函数特点:
1.由一条或者多条语句组成
2.可以重复使用
1.4.函数使用三步骤:
a)函数声明:
1.函数声明的功能:告诉编译器,将来这个函数可以给别人或者自己使用,函数声明是不分配内存空间的
2.函数声明的语法:extern 返回值数据类型 函数名(形参表);
建议:函数声明加extern(提高代码的可读性),理论上可以不加
3.函数声明特点:
1.如果函数定义在函数调用之前,可以省略函数声明,否则必须声明
2.该声明的没有声明,那么编译器gcc就会给个默认的函数什么,,形式:
extern int 函数名();
3.函数名的命名遵循标识符的命名规则
b)函数定义:
1.函数定义功能:就是一个函数的具体实现过程,里面会包含一堆的语句,将来可以给别人或者自己使用,函数定义会分配内存
2.函数定义的语法:
返回值数据类型 函数名(形参表)
{
一堆的函数体语句;
} //花括号的作用就是圈语句用
例如:
int add(int x, int y) //x=100,y=200
{
return x + y;
}
x = 250; //报错
y = 251; //报错
int main(void)
{
sum = add(100, 200) = 300; //main函数使用add函数
return 0;
}
1.5 函数定义特点:
1.返回值数据类型:就是函数运行完毕要给使用这个函数的代码返回一个数,那么这个数必然有对应的数据类型,如果函数没有返回值,返回值数据类型用void关键字。
2.形参表:就是一堆的变量定义,这些变量只能在这个函数体内部使用,出了函数就不能用,形参的值由使用这些函数的代码来赋值,形参的变量有多个,用逗号,分开,如果使用函数的代码不想给函数传递参数,函数定义的时候形参表写void
建议:形参表变量定义的个数不要超过4个,要小于等于4个,否则影响函数使用效率
例如:
int add(int x, int y, int z, int m) {} //好
int add(int x, int y, int z, int m, int n) {} //代码的执行效率降低
3.函数的返回值:
如果函数需要返回一个数字,用关键字return,例如:return 返回值
切记:如果忘记了写return 返回值,gcc将来会返回一个随机数
注意:返回值的数字的类型和函数定义时的返回值数据类型要一致,如果不一致,会进行隐式转换,可能造成数据的丢失
例如:
int add(int x, int y)
{
int sum = x + y;
return sum;
}
如果函数没有返回值,可以不用写return或者写return后面不加内容!
例如:
void add(int x, int y)
{
printf("%d\n", x+y);
return; //或者不写
}
c)函数调用:
1.函数调用功能:俗称使用函数,调用函数,访问函数
2.函数调用语法:接收函数返回值的变量 = 函数名(实参表);
3.函数调用特点:
实参表:就是给函数的形参表赋的值
例如: sum = add(100, 200);//100,200就是实参,将来给add函数的x,y
实参的内存空间和形参的内存空间是独立的,地址是不一样的,但是里面存储的数据是一样的!
d)函数使用的形式:
形式1:无参无返回值
例如: void 函数名(void)
{
函数语句;
}
形式2:无参有返回值
例如:int 函数名(void)
{
函数语句;
}
形式3:有参(1个形参)无返回值
例如:void 函数名(int x)
{
函数语句;
}
形式4:有参(多个形参,建议小于等于4个)无返回值
例如:void 函数名(int x, int y, int m, int n)
{
函数语句;
}
形式5:有参有返回值
例如:int 函数名(int x, int y, int m, int n)
{
函数语句;
}
形式6:参数不定有返回值(不建议这么做)
例如:int 函数名()
{
函数语句;
}
e)return关键字和exit函数
return关键字:实现函数返回
exit函数:让程序强制结束,为了使用此函数需要添加头文件:#include <stdlib.h>
f)实参和形参
形参就是实参的一份拷贝,实参给形参传递参数时,就是将实参里面的数字拷贝一份给形参
1.5.函数和数组
a)之前的代码都是研究函数如何操作变量,也就是函数如何操作通过变量分配的内存
b)函数访问数组编程公式
/*定义访问数组的函数*/
void 函数名(数组的首地址, 数组的长度)
{
可以查看数组元素
可以修改数组元素
}
例如:
void array_function(int a[], int lenght)
{
//查看数组元素的值
printf("%d\n", a[下标]);
//修改数组元素的值
a[下标] = 新值;
}
形参:int a[]本质就是一个数组的首地址,而不是数组,因为这么定义数组语法是不通过的,函数通过这个地址访问元素时,可以当成数组来用
明确:不能用变量,因为如果是变量,形参是实参的拷贝,只能改变形参改变不了实参,要想改变,目前只能用函数和数组技巧
2.作用域和可见性
2.1.明确:C语言变量按照数据类型(占内存大小)分:12种(char....double)
2.2.明确:C语言变量按照作用域和可见性分两类:局部变量和全局变量
2.3.局部变量定义:定义在函数内部的变量
例如:
void A (void)
{
int a = 250; //局部变量
}
2.4.全局变量定义:定义在函数之外的变量
例如:
int g_b = 520; //全局变量
void A (void)
{
int a = 250; //局部变量
}
int g_c = 521; //全局变量
2.5.static关键字
如果定义变量时前面加static关键字修饰,表示此变量为静态变量
如果定义变量是前面没有加static关键字修改,表示此变量为非静态变量
例如:
int a = 250; //非静态变量
static int a = 250; //静态变量
2.6.结论:最终C语言变量按照作用域和可见性来分,最终分四大类:
局部非静态变量/局部静态变量/全局非静态变量/全局静态变量
2.7.详解局部非静态变量特点:
a)形式1:
void A(void)
{
printf("a = %d\n", a); //不可以,报错,gcc编译时报没有定义的错误
int a = 250; //定义局部非静态变量
printf("a = %d\n", a); //可以
}
b)形式2:
void A(void)
{
if(1) {
int a = 250;
printf("a = %d\n", a); //可以
}
printf("a = %d\n", a); //不可以,报错,gcc编译时报没有定义的错误
}
c)函数的形参
void A(int a) //a在整个函数体内都可以用,出了函数不能用
d)局部非静态变量特点
1.此变量使用范围:从定义的地方开始依次向下直到最近的花括号结束
注意:if...else/switch...case/for/while/do-while
2.此变量分配的内存生命周期:从定义的地方操作系统就会给变量分配内存,直到最近的花括号操作系统立马收回变量的内存,只能等下一次运行这些代码才能给变量重新分配内存
2.8.详解局部静态变量特点:
a)形式1:
void A(void)
{
printf("a = %d\n",a);//不可以,报错,gcc编译时报没有定义的错误
static int a = 250; //局部静态变量
printf("a = %d\n",a); //可以
}
b)形式2:
void A(void)
{
if(1) {
static int a = 250; //局部静态变量
printf("a = %d\n",a); //可以
}
printf("a = %d\n",a);//不可以,报错,gcc编译时报没有定义的错误
}
c)局部静态变量特点:
1.此变量使用范围:从定义的地方开始到最近的花括号结束
2.此变量分配的内存生命周期:从定义的地方开始分配内存直到程序结束,一次分配,程序不结束,下次不会分配,接着上一次的使用,程序不结束,下次就不会重新分配内存
3.由衷建议:少用此类变量
2.9.详解全局非静态变量:
a)形式1
void B(void)
{
printf("g_a = %d\n", g_a); //不可以,gcc报没有定义的错误
}
int g_a = 10; //定义初始化全局非静态变量
void A(void)
{
printf("g_a = %d\n", g_a); //可以,函数内部访问全局非静态变量
}
void C(void)
{
printf("g_a = %d\n", g_a); //可以,函数内部访问全局非静态变量
}
...
b)形式2:全局非静态变量定义和使用在不同的源文件中进行
切记:如果一个源文件定了一个全局非静态变量,另一个源文件想直接访问这个全局非静态变量,只需声明这个全局非静态变量即可
声明全局非静态变量的语法:extern 数据类型 全局非静态变量名;
结论:一旦声明,就可以访问别的文件定义好的全局非静态变量了
vim var1.c 添加如下内容
int g_a = 250; //定义全局非静态变量
//定义A函数
void A(void)
{
printf("A函数:g_a = %d\n", g_a); //查看打印全局非静态变量
}
//定义B函数
void B(void)
{
g_a = 520; //修改全局非静态变量
}
vim var2.c 添加如下内容
/*定义D函数*/
void D(void)
{
printf("%d\n", g_a); //gcc编译报错,没有定义
}
//由于跨文件访问函数,声明函数
extern void A(void);
extern void B(void);
//为了使用var1.c的全局非静态变量,声明它
extern int g_a;
/*定义函数C*/
void C(void)
{
printf("%d\n", g_a); //可以访问
}
int main(void)
{
A();
B();
A();
printf("%d\n", g_a);
return 0;
}
编译命令:gcc -o var var1.c var2.c //将var1.c和var2.c编译生成一个可执行文件var
c)切记:全局非静态变量特点
1.此变量的使用范围:分两种情况
1.如果是本文件访问,范围是从定义的地方开始依次向下所有的函数都可以访问,前面的函数无法访问
2.如果是不同文件(跨文件)之间访问,范围是从声明的地方开始依次向下所有的函数都可以访问,前面的函数无法访问
2.此变量分配的内存生命周期:从启动,运行程序时操作系统就会为其分配内存,直到程序结束,操作系统将其内存回收
3.切记:全局非静态变量实际开发时少用,慎用,易出现乱序访问效果!如果非要用,一定要记得互斥访问保护,但是这种互斥保护又降低了代码的执行效率
2.10.详解全局静态变量:
a)形式:全局静态变量的定义和使用访问都是在同一个文件,并且此全局静态变量只能用于本文件,其他源文件不可访问
vim var3.c
void B(void)
{
printf("g_a = %d\n", g_a); //不可以,gcc报没有定义的错误
}
static int g_a = 10; //定义初始化全局静态变量,只能用于var3.c中
void A(void)
{
printf("g_a = %d\n", g_a); //可以,函数内部访问全局非静态变量
}
void C(void)
{
printf("g_a = %d\n", g_a); //可以,函数内部访问全局非静态变量
}
vim var4.c
extern void A(void); //声明函数A
extern void B(void); //声明函数B
extern int g_a; //声明全局静态变量g_a
int main(void)
{
A();
B();
printf("g_a = %d\n", g_a); //gcc报错
return 0;
}
编译:gcc -o var var3.c var4.c
b)全局静态变量特点:
1.此变量使用范围:只能用于定义变量的文件中(var3.c),并且是从定义的地方开始依次向下所有的其他函数都可以访问,之前的函数不可访问
2.此变量分配内存的生命周期:从启动,运行程序时操作系统就会为其分配内存直到程序结束,操作系统将其内存回收
3.全局静态变量同样要少用,慎用,如果迫不得已非要访问,记得要互斥访问,但是它比全局非静态变量发生乱序现象的概率要小!
2.11.static关键字总结(笔试题必考):
1.static修饰的全局变量只能用于本文件,其余文件不可访问
2.static修饰的函数(定义函数时)只能用于本文件,其余文件不可调用访问
3.static修饰的变量和函数将来用起来相对比较安全,起到了间接保护的作用,乱序发生的概率降低了