于灵动的变量变幻间:函数与计算逻辑的浪漫交织(下)
大家好啊,我是小象٩(๑òωó๑)۶
我的博客:Xiao Xiangζั͡ޓއއ
很高兴见到大家,希望能够和大家一起交流学习,共同进步。
这一节我们主要来学习单个函数的声明与定义,static和extern…
这里写目录标题
- 一、单个函数的声明与定义
- 1.1 单个文件
- 1.2 多个文件
- 二、static和extern
- 2.1 作用域和生命周期
- 2.2 static
- 2.3 static修饰局部变量
- 2.4 static修饰全局变量(extern声明外部符号)
- 2.5 static修饰函数
- 三、结尾
一、单个函数的声明与定义
1.1 单个文件
一般我们在使用函数的时候,直接将函数写出来就使用了。
咱们举之前练习过的一个例子来看,比如写一个函数判断一年是否是闰年。
#include<stdio.h>
int pan_duan_run_nian(int x)
{
if ((x % 4 == 0) && (x % 100 != 0) || (x % 400 == 0))
return 1;
else
return 0;
}
int main()
{
int year = 0;
scanf("%d", &year);
int a = pan_duan_run_nian(year);
if (a == 1)
{
printf("%d为闰年", year);
}
else
{
printf("%d不是闰年");
}
return 0;
}
在这个代码中,这一部分代表的是**函数的定义**:
关于函数的定义:
在C语言中,函数定义是编程中的一个核心概念,它允许你将代码组织成可重用和可维护的模块。函数定义的作用主要包括以下几个方面:
1、代码重用:通过定义函数,你可以避免在多个地方重复编写相同的代码。当你需要在程序中多次执行某个任务时,只需调用定义好的函数即可,从而提高了代码的重用性。
2、模块化编程: 函数将程序划分为多个逻辑模块,每个模块负责完成特定的任务。这种结构化的编程方式使得程序更易于理解和维护。
3、提高可读性:将复杂的程序逻辑分解为多个函数,每个函数都有明确的目的,这可以提高代码的可读性。阅读和理解一个由多个简单函数组成的程序,比阅读一个包含大量复杂逻辑的单体程序要容易得多。
4、便于调试: 模块化编程使得在程序出现问题时更容易定位错误。你可以单独测试每个函数,确定哪个函数存在错误,从而减少了调试的复杂性。
5、增强代码的可维护性: 当需要修改程序时,只需修改相关的函数,而不需要在整个程序中搜索和修改代码。这大大简化了维护过程,降低了维护成本。
6、促进代码协作:在大型项目中,多个开发人员可以独立地编写和测试各自的函数。通过定义清晰的函数接口,开发人员可以协同工作,确保代码的正确性和一致性。
7、实现递归(后面会学): 函数定义允许函数自身调用自身(递归),这是解决某些类型问题(如排序、搜索等)的有效方法。
#include<stdio.h>
int pan_duan_run_nian(int x)
{
if ((x % 4 == 0) && (x % 100 != 0) || (x % 400 == 0))
return 1;
else
return 0;
}
而这一部分代表着**函数的调用`**:
int a = pan_duan_run_nian(year);
然后,如果我们把函数的定义放在函数调用的后面,像这样:
int main()
{
int year = 0;
scanf("%d", &year);
int a = pan_duan_run_nian(year);
if (a == 1)
{
printf("%d为闰年", year);
}
else
{
printf("%d不是闰年");
}
return 0;
}
#include<stdio.h>
int pan_duan_run_nian(int x)
{
if ((x % 4 == 0) && (x % 100 != 0) || (x % 400 == 0))
return 1;
else
return 0;
}
这个代码在VS2022上编译,会出现下面的警告信息:
这是因为C语言编译器对源代码进行编译的时候,第一行往下扫描的,当遇到pan_duan_run_nian函数调用的时候,并没有发现前面有pan_duan_run_nian的定义,就报出了上述的警告。
把怎么解决这个问题呢?就是函数调用之前先声明一下pan_duan_run_nian这个函数,声明函数只要交代清楚:函数名,函数的返回类型和函数的参数。
如:pan_duan_run_nian(int x);这就是函数声明,在C语言中,函数声明(Function Declaration) 是 告诉编译器有关函数的存在、其返回类型、函数名以及参数类型和数量的声明 。函数声明通常位于函数定义之前或在其他文件中,以便编译器在函数调用之前知道函数的签名(即函数的接口)。函数声明中参数只保留类型,省略掉名字也是可以的。
函数声明的作用:
1、提前通知编译器:编译器在编译代码时,需要知道函数的签名,以便在函数调用时进行类型检查。函数声明提供了这一信息,使得编译器可以在函数实际定义之前进行编译。
2、避免链接错误:在多个文件的项目中,函数声明允许编译器在编译单独的源文件时知道其他文件中定义的函数的存在。这样,链接器(Linker)可以在链接阶段找到这些函数的实际定义。
3、提高代码可读性:函数声明提供了函数的接口信息,有助于其他程序员理解如何使用该函数。
所以我们给上面的函数加上声明,就会变成下面这样:
pan_duan_run_nian(int x);
#include<stdio.h>
int main()
{
int year = 0;
scanf("%d", &year);
int a = pan_duan_run_nian(year);
if (a == 1)
{
printf("%d为闰年", year);
}
else
{
printf("%d不是闰年");
}
return 0;
}
int pan_duan_run_nian(int x)
{
if ((x % 4 == 0) && (x % 100 != 0) || (x % 400 == 0))
return 1;
else
return 0;
}
结果便正常了
函数的调用一定要满足,先声明后使用;
函数的定义也是一种特殊的声明,所以如果函数定义放在调用之前也是可以的。
1.2 多个文件
一般在企业中我们写代码时候,代码可能比较多,不会将所有的代码都放在⼀个文件中;我们往往会根据程序的功能,将代码拆分放在多个文件中,也就是模块化处理。
注意:虽然一个工程会有多个文件,但main函数只能有一个
⼀般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现是放在源文件(.c)文件中。
我们来举个例子,我们用test.c来调用其它模块代码进行测试,我们用leap.h和leap.c分别来对于函数的声明和函数的定义
像这样:分别创建test.c和leap.c源文件,leap.h头文件
我们来举一个加法的例子:
我们先来看函数测试的雏形:
#include<stdio.h>
#include"leap.h" //你的头文件是leap.h
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int c = add(a, b);
printf("%d", c);
return 0;
}
我们把它放在test.c中
这里我们要注意要把leap.h头文件也包含进去#include"leap.h"
接着,我们看函数的声明:
int add(int x, int y);//函数的声明
我们把它放在leap.h头文件中
最后是函数的定义:
int add(int x, int y)//函数的定义
{
int d = x + y;
return d;
}
我们把它放在leap.c的源文件中
这样我们就写完了,运行看看结果:
没有问题
二、static和extern
static 和 extern 都是C语言中的关键字。
2.1 作用域和生命周期
**作用域(scope)**是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效(可用)的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
- 局部变量的作用域是变量所在的局部范围。
- 全局变量的作用域是整个工程(项目)。
生命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的一个时间段。
- 局部变量的生命周期是:进入作用域变量创建,生命周期开始,出作用域生命周期结束。
- 全局变量的生命周期是:整个程序的生命周期(就是程序从开始运行到结束)。
2.2 static
在C语言中,static关键字有多种用途,其中之一是用于修饰函数。当static关键字用于函数声明时,它改变了函数的链接属性(linkage)。
static函数的特点:
1、内部链接(Internal Linkage):
当一个函数被声明为static时,它的链接属性变为内部链接。这意味着该函数只能在声明它的源文件(.c文件)内部被访问。其他源文件无法直接调用这个函数,即使这些文件包含了该函数的声明。
这有助于封装和隐藏实现细节,防止其他文件直接访问和修改。
2、避免命名冲突:
由于static函数只能在其所在的文件内部被访问,因此不同源文件中的同名static函数不会引起命名冲突。
这允许开发者在不同的源文件中使用相同名称的函数,而无需担心命名冲突。
3、生命周期:
static关键字对函数的生命周期没有影响。函数的生命周期总是从程序开始执行到程序结束。static关键字仅影响函数的链接属性,即函数的可见性。
static 是 静态的 的意思,可以用来:
• 修饰局部变量
• 修饰全局变量
• 修饰函数
2.3 static修饰局部变量
我们先来看个例子:
#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;
}
结果是这样的:
如果我们这样改:
#include<stdio.h>
void test()
{
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的test函数中的局部变量i是每次进入test函数先创建变量(生命周期开始)并赋值为0,然后++,再打印,出函数的时候变量生命周期将要结束(释放内存)。
代码2中,我们从输出结果来看,i的值有累加的效果,其实test函数中的i创建好后,出函数的时候是不会销毁的,重新进入函数也就不会重新创建变量,直接上次累积的数值继续计算。
结论:static修饰局部变量改变了变量的生命周期(被static修饰后生命周期会变长),生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。 存储在静态区的变量和全局变量是⼀样的,生命周期就和程序的生命周期⼀样了,只有程序结束,变量才销毁,内存才回收。但是作用域不变的。
使用建议:未来一个变量出了函数后,我们还想保留值,等下次进入函数继续使用,就可以使用static修饰。
2.4 static修饰全局变量(extern声明外部符号)
extern 是用来声明外部符号的,如果一个全局的符号在A文件中定义的,在B文件中想使用,就可以使用 extern 进行声明,然后使用。
我们首先创建两个源文件,然后我们在其中一个源文件创建一个变量,然后另一个源文件用extern声明一下,像下面的做法一样:
int xiaofeixiang = 666;
#include<stdio.h>
int main()
{
extern int xiaofeixiang;//声明一下
printf("%d", xiaofeixiang);
return 0;
}
我们运行看看结果:
运行一切正常
如果我们这个时候在变量旁边添加static的话:
static int xiaofeixiang = 666;
这个时候我们如果运行的话,就会发现运行错误:
结论:
static相当于改变作用域一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。
本质原因是**全局变量默认是具有外部链接属性的**,在外部的文件中想使用,只要适当的声明就可以使用;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性 ,只能在自己所在的源文件内部使用了,其他源文件,即使声明了,也是无法正常使用的。
使用建议:如果一个全局变量,只想在所在的源文件内部使用,不想被其他文件发现,就可以使用static修饰。
2.5 static修饰函数
直接来个例子:
int add(int x, int y)
{
return x + y;
}
#include<stdio.h>
extern int add(int x, int y);
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int c = add(a,b);//声明一下
printf("%d",c );
return 0;
}
这里的extern是指声明外部函数的作用
我们运行一下,结果没有问题:
如果我们在函数前面加上static的话,会发生什么呢,我们来看看:
static int add(int x,int y);
看看结果,直接就报错了:
其实 static 修饰函数和 static 修饰全局变量是一模一样的,一个函数在整个工程都可以使用,被static修饰后,只能在本文件内部使用,其他文件无法正常的链接使用了。
本质是因为 函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个工程中只要适当的声明就可以被使用。但是 被 static 修饰后变成了内部链接属性,使得函数只能在自己所在源文件内部使用。
使用建议:一个函数只想在所在的源文件内部使用,不想被其他源文件使用,就可以使用 static 修饰。
三、结尾
这一课的内容就到这里了,下节课继续学习递归的其他一些知识
如果内容有什么问题的话欢迎指正,有什么问题也可以问我!