[C语言]关键字解析(一) auto, register, extern和static关键字

前言

C语言一共多少个关键字呢?一般网上会说是32个,但是这个都是 C90(C89) 的标准。其实 C99 后又新增了5个关键字。不过,目前主流的编译器,对 C99 支持的并不好,我们后面默认情况使用 C90 ,即认为32个.
在这里插入图片描述

这篇文章中我们不会讲解int,char,if,while等十分简单常见的关键字,而是讲解一些其他略微复杂的关键字

关于变量

由于C语言中关键字很多用来修饰变量,所以在此我们先来理解一下变量

什么是变量?
内存中开辟特定大小的空间,用来保存数据(重点是内存)

如何定义变量?

int x = 10char c = 'a';
double d = 3.14;
类型 变量名 = 默任值

为什么要定义变量(为什么)?
计算机是为了进行计算的.而计算,就需要数据.而要计算,任何一个时刻,不是所有的数据都要立马被计算.
换句话说,为何需要变量?因为有数据需要暂时被保存起来,等待后续处理

  1. 程序运行,需要加载到内存中
  2. 程序计算,需要使用变量

变量定义的本质:在内存中开辟一块空间,用来保存数据。(为何一定是内存:因为定义变量,也是程序逻辑的一部分,程序已经被加载到内存)

其中变量有作用域和生命周期的概念:
生命周期:指的是该变量从定义到被释放的时间范围,所谓的释放,指的是曾经开辟的空间”被释放“[时间层面]
生命周期与存储位置有关------栈区,堆区,静态区等
作用域:变量有效性的范围,就是用户自定义的变量可以使用的代码范围,它与变量定义的位置密切相关[空间层面]
作用域与编译器的搜索规则有关
正因为如此,变量被分为局部变量和全局变量
从作用域角度理解:
局部变量:包含在代码块中的变量叫做局部变量.局部变量具有临时性.进入代码块,自动形成局部变量,退出代码块自动释放。(代码块:用{}括起来的区域,就叫做代码块)
全局变量:在所有函数外定义的变量,叫做全局变量。全局变量具有全局性。
从生命周期角度理解:
局部变量: 进入代码块,形成局部变量[开辟空间],退出代码块,"释放"局部变量
全局变量: 定义完成之后,程序运行的整个生命周期内,该变量一直都有效

注意 😗* 对于全局变量,如果在函数中修改了值,则全局变量的值就会被改变,在其他函数中访问的就是修改后的值( 可以这样理解:全局变量在数据段的全局区,不属于堆和栈,所以在函数中每次调用时都并非在栈中入栈的变量,在函数中改变值大小也是变的全局区的值,所以出了函数后,值是会变的)
但是,如果在函数中定义了跟全局变量名称一样的局部变量,则函数中修改的是局部变量,全局变量的值是无法被修改的。

举个例子

#include <stdio.h>
#include <windows.h>
int g_x = 100; //全局变量
void F()
{
	 g_x = g_x + 1;
}
int main()
{
	F();  //调用了F()函数,全局变量+1,变为了101
	int x = 10; //x其实也是局部变量,main函数也是函数,也有代码块
	printf("x:%d\n", x);
	printf("%d", g_x);   //结果为101
	system("pause");
	return 0;
}

auto关键字一般在代码块中定义的变量,即局部变量,默认都是auto修饰的,不过一般省略
默认的所有变量都是auto吗?不是,一般用来修饰局部变量(因为局部变量是否用auto修饰都无所谓,所以auto关键字可有可无)
结论:auto关键字比较老,在近些年的标准中可以基本不用

auto关键字

变量其实可以按存储类型分成:静态变量和自动变量
在代码块外面的变量不属于堆栈的内存,存储在静态区中,称为静态变量。(全局变量)
在代码块内部的变量属于堆栈的内存,视为自动的,称为自动变量。一般用auto修饰(局部变量)
:所以也能发现其实一般在main函数里面的变量都是默认auto修饰.

int main()
{
	for (auto int i = 0; i < 10; i++)  //auto修饰
	{
		printf("i=%d\n", i);
		if (1)
		{
			auto int j = 0; //auto修饰
			printf("before: j=%d\n", j);
			j += 1;
			printf("after : j=%d\n", j);
		}
	}
	return 0;
}

结论:>auto关键字一般在代码块中定义的变量,即局部变量,默认都是auto修饰的,不过一般省略(不能修饰全局变量)
默认的有变量都是auto吗?不是,一般用来修饰局部变量(因为局部变量是否用auto修饰都无所谓,所以auto关键字可有可无)
auto关键字比较老,在近些年的C语言标准中可以基本不用
(C++中的auto应用与C语言不同)

register关键字

关于register关键字从字面意思就是寄存器的意思.
寄存器在cpu中的作用和本质就是在硬件层面上,提高计算机的运算效率.因为不需要从内存里读取数据
其实,CPU主要是负责进行计算的硬件单元,但是为了方便运算,一般第一步需要先把数据从内存读取到CPU内,那么也就需要CPU具有一定的数据临时存储能力。注意:CPU并不是当前要计算了,才把特定数据读到CPU里面,那样太慢了.所以现代CPU内,都集成了一组叫做寄存器的硬件,用来做临时数据的保存.其实这也是一种缓存技术,包括像程序运行时,硬盘上的数据要加载进内存,这其实也是一种缓存技术

下面展现了一组计算机中的存储金字塔
在这里插入图片描述
register 修饰变量意义在于尽量将所修饰变量放入CPU寄存区中,提高效率.
( 注意:这里是尽量,是向编译器发出请求,只会尽力让变量存储在CPU中。因此即使使用了register,CPU寄存器的数量有限,因此只有一部分变量可以存储在寄存器中。编译器会根据变量的类型、作用域、使用频率等因素决定哪些变量存储在寄存器中,哪些变量存储在内存中。)
注意:register修饰的变量,不能取地址&(因为已经放在寄存区中了嘛,地址是内存相关的概念)

register适合的运用场景:

  1. 局部的(全局会导致CPU寄存器被长时间占用)
    通常用于循环内部,在循环中频繁地访问变量,将变量存储在寄存器中提高运行速度。
  2. 不要大量使用,因为寄存器数量有限
int main()
{
	register int a = 0;
	a = 100; //register修饰也可以正常写入  
	printf("%d\n", a); //可以正常打印它的值
	printf("&a = %p\n", &a);//但是不能取地址!!!!!
	return 0;
}  

总结:因为现在的编译器,已经很智能了,能够进行比人更好的代码优化。早期编译器需要人为指定register,来进行手动优化,现在基本上不需要了。

extern关键字

extern在C语言中在修饰函数或者全局变量(不能修饰局部变量) 时,本质是用于在声明中修改标识符的链接属性(后面介绍的static也会有这个作用)😃通俗理解就是在告诉编译器在其他文件中存在这个函数或者变量,使其通过编译

这里拓展下标识符的链接属性(程序在编译时会让变量和函数形成符号)
其实extern 本质是用于在声明中修改标识符的链接属性(后面介绍的static也会有这个作用)
什么是链接属性?
链接属性决定了如何处理不同文件中出现的标识符。
标识符的链接属性有三种:external(外部链接属性) internal(内部链接属性)none(无链接属性)
1.external(外部链接属性)
标识符不论声明多少次、位于几个源文件都表示同一个实体
2. internal(内部链接属性)
标识符在一个源文件中的声明都指向同一个实体,但不同源文件的声明则是不同的实体
3.none(无链接属性)
none链接属性的标识符的多个声明都是独立不同的实体,局部变量就无链接属性
所以extern也不能修饰局部变量

当我们一个项目中往往有多个文件,而如果一个文件要使用另外一个文件中的变量,不用extern来修饰,则会报错未加以声明

//文件1  test.c
int g_val = 10; //变量名是这个的原因是:全局变量(global variable)的命名
 
//文件2 main.c
#include<stdio.h>
extern int g_val;    //extern声明外部符号g_val,为这个标识符指定外部链接属性
int main()
{
	printf("%d", g_val);
}

在上述文件2的代码中,不能写成

main.c文件
#include<stdio.h>
extern int g_val = 10; //这是错误的❌

这就需要提及变量的声明和定义的区别了

声明:告诉编译器变量的名称和类型,使编译器知道存在着这个变量和类型, 不分配内存地址.
定义:为变量 分配内存地址,并且你可以接着为变量赋初始值(这就是变量的初始化)
正因为是否分配内存空间的因素,所以变量定义只能一次,而声明可以多次

所以,如上面代码,extern修饰变量并赋值的时候,那我们到底是否分配内存空间,显然是矛盾且不合理的
并且我们也不能在文件2中又对g_val进行定义(文件1和2都对其定义),会导致重定义

但是下面这样是对的,道理和我上面讲全局变量和局部变量时提到的 注意 中是一样的

#include<stdio.h>
extern int g_val;    
int main()
{
	g_val = 11; //同时修改了文件1全局变量的值
	printf("%d", g_val);
}

此外还需要注意一点的是,如果当你在main函数的文件去使用其他文件的函数并且不加以声明时,你是能够继续运行起来的,如下图在这里插入图片描述
程序通过了,但是会有下图的警告⚠

在这里插入图片描述
这是因为程序的链接步骤,就如同你平时使用经常使用的printf()函数,其实你也并没有定义,#include<stdio.h>只是声明了有这个函数,并未定义.而printf()函数的定义是在程序最后一步的链接过程中的库里面.所以同理,main.c在编译时这两个文件互相看不到对方,编译器会发出警告(上图时main.c的编译结果),但是在链接时,两个源文件链接成一个时,编译器在其他文件中发现了这个函数,所以就能够运行起来

extern的弊端及优化
一个项目中有多个文件需要大量引用外部变量或函数的时候,如果每个文件中都大量使用extern,这样做编译效率很低。因此,我们通常将所有的全局变量和常用函数都写在一个.c(.cpp)文件中,然后把extern都放在一个.h文件(这就是头文件),这样其他文件需要使用外部变量和函数时,就只需要用 #include"xxx.h" 的格式包含其中的头文件.

下图是一个队列,就使用了三个文件,左边.c文件中 白色圈出来的就是函数的定义,中间.h文件中红色圈出的就是函数声明,最后在右边文件的main函数中绿色部分就是使用这些外部函数
在这里插入图片描述

( 注意:最好不要在头文件进行定义,然后被其他文件#include. 因为引用头文件的#include命令就是原封不同的把头文件中的内容复制一遍在#include的位置。所以如果多次在其他文件#include"xxx.h" , 会有重复定义的错误)

static关键字

static中文意思为静态的,其实关键作用就两点

1.static修饰的局部变量会从堆栈区存放在静态区

代码一
void fun()
{
	int i = 0;
	i++;
	printf("%d",i);
}
int main()
{
	for(int i = 0;i<10;i++)
	{
		fun();
	}	
	return 0;
}     //最终代码会打印出来 1 1 1 1 1 1 1 1 1 1

代码二
void fun()
{
	static int i = 0;//加入了static
	i++;
	printf("%d",i);
}
int main()
{
	for(int i = 0;i<10;i++)
	{
		fun();
	}	
	return 0
}     //最终代码会打印出来 1 2 3 4 5 6 7 8 9 10

这就是因为代码一,fun()函数中的变量 i (在栈区)随函数入栈出栈,所以每次循环后,再次调用函数, 变量 i 又为0,又从0开始+1;
而在代码二中,fun()函数的变量i用static修饰后,改变了其存储类型,全局区(静态区)保存,函数调用完后出栈,但是变量 i 此时并不会,因为压根不在栈区,所以每次调用函数都在原有基础上+1,

2.static修饰全局变量和修饰函数,将它们的外部链接属性改为内部链接属性(说白了就是只有在自己的文件中使用),不能被外部其他文件直接访问(注意是直接!!!)

//文件1  test.c
#include<stdio.h>
static int g_val = 10;
void show()
{
	printf("%d",g_val); //static修饰,可以在自己内部文件的show函数中使用
}

//文件2  main.c
#include<stdio.h>
show()
int main()
{
	show();   //这个文件声明了show()函数,调用show函数,相当于间接使用了g_val
}

另外static也有一些其他巧妙的用处
默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00(栈区变量默认以CC填充,堆区变量默认以CD填充)。如初始化一个稀疏矩阵,我们可以通过static把所有元素都置0(而不是用代码一个一个置为0),然后把不是0的几个元素赋值。

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kklovecode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值