关键字深度剖析,集齐所有关键字可召唤神龙?【一】

从这篇博客开始,新开了一个专栏,学习《C语言深度剖析》,并记录学习笔记,只要为了补充c语言知识,也是为了进一步提高

接下来是本书的第一章关键字
后面会分几次连载这一章

学完本篇章的目标就是达到

  • 学完C中全部核心关键字
  • 重点掌握需要理解的相关的关键字
  • 使用核心关键字,快速串完C中大量重点知识

在之前的学习中,一直认为C语言的关键字是有32个,其实那只是C90(C89) 的标准,也就是C99之前的数量,在后期的规则更新后,又新增了5个关键字,使得C语言能够完成更多任务。不过,目前主流的编译器,对C99 支持的并不好,我们后面默认情况,使用C90 即,认为32个。那么接下来让我们集齐32(37)颗"龙珠"召唤神龙🐉
image-20211212193112689

0.Intro

0.1 当我们写了一个c语言程序

当我们在Windows双击一个程序的时候,发生了什么过程,其实在双击了某个程序之后,程序会被加载到内存当中

其实,任何程序在运行之前都必须被加载到内存之中
于是我们提出两个问题

  1. 没有运行时程序在那里?
  • 程序与数据在硬盘里面
  1. 为什么我们的程序被加载要进入内存?
  • 因为快,CPU访问内存会很快,但是访问外设的话会很慢

0.2 变量定义的本质

  • 程序运行,需要加载到内存中

  • 程序计算,需要使用变量

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

0.3 变量声明的本质

在extern 关建字中细讲

小结:
定义与声明的区别
定义:开辟空间 定义只有一次
声明:告知 声明可以多次

0.4 关键字须知:

关键字不能自己创建

关键字不能作为变量名使用

autobreakcasecharconstcontinuedefaultdodoubleelseenum
externfloatforgotoifintlongregisterreturnshortsigned
sizeofstaticstructswitchtypedefunionunsignedvoidvolatilewhile

1.最宽宏大量的关键字 - auto

先回忆一下几个概念

  1. 局部变量:包含在代码块中的变量叫做局部变量。局部变量具有临时性。进入代码块,自动形成局部变量,退出代码块自动释放。(网上很多说函数中的变量是局部变量,不能说错,但说法是不准确的),
  2. 全局变量:在所有函数外定义的变量,叫做全局变量。全局变量具有全局性。
  3. 代码块:用{ }括起来的区域,就叫做代码块
  4. 作用域概念:指的是该变量的可以被正常访问的代码区域
  • 局部变量:只在本代码块内有效,局部和全部同名的时候,优先局部
  • 全局变量: 整个程序运行期间,都有效
  1. 生命周期概念:指的是该变量从定义到被释放的时间范围,所谓的释放,指的是曾经开辟的空间”被释放“
  • 局部变量: 进入代码块,形成局部变量[开辟空间],退出代码块,"释放"局部变量
  • 全局变量: 定义完成之后,程序运行的整个生命周期内,该变量一直都有效

1.1 解释auto

一般在代码块中定义的变量,即局部变量,默认都是auto修饰的,不过一般省略
默认的所有变量都是auto吗?不是,一般用来修饰局部变量,也就是修饰了之后的变量说生命周期只在本代码块中有效,auto不能修饰全局变量

image-20211212221739903后面我们所提到的,局部变量,自动变量,临时变量,都是一回事。我们统称局部变量

小结:已经很老,基本永不使用。

特别的:c++和c中的auto是不一样的

2. 最快的关键字 - register

其实,CPU主要是负责进行计算的硬件单元,但是为了方便运算,一般第一步需要先把数据从内存读取到CPU内,那么也就需要CPU具有一定的数据临时存储能力。注意:CPU并不是当前要计算了,才把特定数据读到CPU里面,那样太慢了。所以现代CPU内,都集成了一组叫做寄存器的硬件,用来做临时数据的保存。

2.1 寄存器

我们其实在基本上每一本操作系统的参考书第一章都能看到一个金字塔图

基本在顶端就是寄存器了

image-20211021134036293

图源来自:点击传送

cpu速度很快,一般都先从寄存器来拿数据进行处理,所以说

2.1.1 寄存器存在的本质

在硬件层面上,提高计算机的运算效率。因为不需要从内存里读取数据啦。

2.2 register修饰变量

那什么样的变量,可以采用register呢?

  1. 局部的(全局会导致CPU寄存器被长时间占用)
  2. 不会被写入的(写入就需要写回内存,后续还要读取检测的话,register的意义在哪呢?)
  3. 高频被读取的(提高效率所在)
  4. 如果要使用,请不要大量使用,因为寄存器数量有限

假如a是寄存器变量我们就用register修饰变量,一般修饰的这个变量是不可以使用&操作符的

image-20211212223452731
编译器报错:错误 1 error C2103: 寄存器变量上的“&”
注意,这里不是所有的编译器都报错,目前我们的vs2019是报错的。

小结:

该关键字,现在也不用管,因为现在的编译器,已经足够智能够进行比人更好的代码优化。

3. 最名不符实的关键字 - static

3.0 before static

头文件的必要性

创建c’语言文件的时候我们会创建

  1. .h:我们称之为头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header)
  2. .c: 我们称之为源文件,一般包含函数实现,变量定义等 (.c:c语言)

为什么要有头文件?

单纯的使用源文件在组织想项目结构的时候,项目越大越复杂的时候,维护成本会变得越来越高

头文件的存在会使得项目组织结构的时候,减少大型项目的维护成本问题

头文件一般都是会被多个源文件包含的,所以说解决重复包含的时候可以用

#pragma once

如何在头文件中完成声明?

首先要知道声明是不需要开辟空间的,不开辟空间就最好不要设定初始值,因为赋值或者初始化就是会开辟空间,所以说

  • 声明函数的时候,不需要函数体,且声明的时候,函数建议带上extern,不强制带上,因为编译器在识别的时候,理解没有函数体的函数为声明,同时最好怎么定义就怎么声明
  • 声明变量的时候,不能设置初始值,必须带上extern

小结:

  1. 全局变量可以跨文件访问
  2. 函数也可以跨文件访问

那么是否有在一个具体的场景中,有可能我们不想让全局变量或者函数跨文件访问,指向在本文件内部被访问?,于是引出static

3.1 static 关键字

在C语言中:
static是用来修饰变量和函数的

  1. 修饰局部变量-称为静态局部变量
  2. 修饰全局变量-称为静态全局变量
  3. 修饰函数-称为静态函数

3.2 修饰局部变量

void fun1()
{
	int i = 0;
	i++;
	printf("no static: i=%d\n", i);
}
void fun2()
{
	static int i = 0;
	i++;
	printf("has static: i=%d\n", i);
}
int main()
{
	for (int i = 0; i < 10; i++) {
		fun1();
		fun2();
	}
	return 0;
}

image-20211213181933924

static修饰局部变量只是改变了变量的生命周期,而不是作用域

image-20211213184046401让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。

举个例子:

void test()
{
	int a = 1;
	a++;
	printf("%d", a);
}
//最后打出来10个2 每次循环打出来一个2
int main()
{
	int count = 0;
	while (count< 10)
	{
		test();
		count++;
	}
	//循环10次
	return 0;
}

最后我打出来肯定是10个2,因为局部变量每次出了生命周期之后就销毁

但是假如我在test函数里面的原局部变量a用了一个修饰static

最后打出来就是

image-20211021203242437

由此结果可知,每次调用test函数都是上一次函数调用后留下的,因此就是打印了2-11

理解原因:

我们说,要了解原因应该知道,有三个重要的内存存放区域

静态区,堆区和栈区

栈区主要存放局部变量等
堆区主要管动态内存分配 malloc free calloc realloc
静态区主要存放全局变量,静态变量等
image-20211021204407143

其中全局变量和静态变量放在了静态区,而局部变量放在了栈区

static实质上改变了a的位置(栈–>静态),从而使得静态的局部变量出了其作用域也不销毁,那生命周期也改变,当程序结束之后该变量才会被销毁。

3.3 修饰全局变量和函数

修饰全局变量,该全局变量只能在本文件内被使用。

修饰函数,该函数只能在在本文件内被访问,不能在外部其他文件直接访问

我们说,假如我们要用来自其它部分的文件中的全局变量,是要先行声明的

extern之前提到过的关键字,专门用来声明外部符号

extern int g_val = 8848;
int main()
{
	printf("%d\n", g_val);
	return 0;
}

倘若我有两个源文件时,这里就涉及到了外部文件

image-20211021225451597

此时我用static修饰这个全局变量的话,就会报错

image-20211021211403554

看起来似乎好像是作用域变小了,实则本质上并不是。

一个全局变量能在其他文件内部能被使用,是因为全局变量有特殊的属性,即可以被外部链接。但是被static修饰后效果是外部链接属性变成了内部链接属性,因此该全局变量在其他文件中不能被使用。

小结:

static 修饰全局变量不会改变生命周期,但是会改变一个全局变量的链接属性

4. 最冤枉的关键字sizeof

4.0 before sizeof

回忆基本数据类型

image-20211213184402237

如何看待数据类型?

定义一个变量,是需要类型的,这个是基本语法决定的。那么,类型决定了:变量开辟空间的大小。

#include <stdio.h>

int main()
{
	//sizeof简单介绍,下个主题就讲
	printf("%d\n", sizeof(char)); //1
	printf("%d\n", sizeof(short)); //2
	printf("%d\n", sizeof(int)); //4
	printf("%d\n", sizeof(long)); //4
	printf("%d\n", sizeof(long long)); //8
	printf("%d\n", sizeof(float)); //4
	printf("%d\n", sizeof(double)); //8
	return 0;
}
  • 为什么要根据类型,开辟一块空间,直接将内存整体使用不好吗?

    不好。
    要知道任何时刻,都不是一个程序在运行,还有很多其他程序也在运行。你整块用了,让别人怎办?另外,你全都用了,一定需要在任何时刻,全部都用完吗?对于暂时不用,但是给你了,对计算机来讲,就是浪费。

  • C语言中,为什么会有这么多的类型?

    为了满足不同的计算场景。比如,整形计算,字符计算,字符串计算,浮点数计算等。

4.1 被冤枉的sizeof

冤枉点:常年被误认为库函数

其实不是,下面一个不报错说明了sizeof的实质

	printf("%d\n", sizeof a);

4.2 求自定义类型的大小

sizeof 是可以计算自定义类型的大小的

int main()
{
	int* p = NULL;
	int arr[10];
	int* test[3];
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(test));
}

image-20211213191102673

5. signed、unsigned关键字

5.0 before signed and unsigned

5.0.1 回忆一下原码反码补码

这块知识之前在c语言进阶栏目中提到过
C语言突破进阶-数据的存储

之前在操作符一章中也有谈到过数据在内存中存储的时候有三种储存形式

整数的三种二进制表达形式

  • 原码

  • 反码

  • 补码

对于整数的3种形式我们有这样一个结论:

image-20211111195950022

下面对一个整数5来举例

image-20211111200054762

再对-5来举例

image-20211111200311163

从这个例子我们可以看到

原码在第32位中的0和1用来表示符号位,0表示正数,1表示负数,反码同样

反码相当于原码的符号位不变,其他位按位取反得到的就是反码

补码就是反码最低位+1

用VS调试看内存我们就可以直观看到

image-20211111200939196

这里的ffff就是16进制的-1也就是二进制下32个1,因为16进制下一个f相当于15,因此说明内存存储的方式是利用补码

5.0.2 大端小端字节序

大端与小端本质在于数据核空间按照字节为单位的一种映射关系

大端字节序:

一个数据的低字节的数据放在高地址处,高字节序的内容,放在了低地址处,这种存储方式叫做大端字节序

小端字节序:

一个数据的低字节的数据放在低地址处,高字节序的内容,放在了高地址处,这种存储方式叫做小端字节序

更细的总结敬请阅读C语言突破进阶-数据的存储

小结:

深入理解变量内容的存入和取出

:字面数据必须先转成补码,在放入空间当中。所以,所谓符号位,完全看数据本身是否携带±号。和变量是否有符号无关!
:取数据一定要先看变量本身类型,然后才决定要不要看最高符号位。如果不需要,直接二进制转成十进制。如果需要,则需要转成原码,然后才能识别。(当然,最高符号位在哪里,又要明确大小端)

  • 为什么都是补码
    在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

5.1 整形取值范围

unsigned char: [0,2^8^-1]
signed char : [-2^7^, 2^7^-1] //char等价

为什么?特定数据类型,能表示的数据取值范围(范围由多个连续数据构成),本质是多位bit位形成的排列组合的的个数。

总结规律:整数的取值范围
无符号:[0,2n-1]
有符号:[-2(n-1), 2(n-1)-1]

5.2 一个小栗子

int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}

输出应该是255,那是为什么?

首先我们要知道一个char类型变量能放什么数值

image-20211126104425159

我们可以转化为一个⚪来记忆

image-20211126105032061

于是现在我们再来看这道题目要求数组中strlen也就是要找到\0,找字符0之前出现的多少个字符就是255,也就是说这个⚪我逆时针走一圈到0有多少个数字**

  • 相同道理有符号short就是-32768-32767

还有一个方法可以直接看到这些数的范围

//在这个头文件里面
#include<limits.h>
#define CHAR_BIT      8
#define SCHAR_MIN   (-128)
#define SCHAR_MAX     127
#define UCHAR_MAX     0xff
#define MB_LEN_MAX    5
#define SHRT_MIN    (-32768)
#define SHRT_MAX      32767
#define USHRT_MAX     0xffff
#define INT_MIN     (-2147483647 - 1)
#define INT_MAX       2147483647
#define UINT_MAX      0xffffffff
#define LONG_MIN    (-2147483647L - 1)
#define LONG_MAX      2147483647L
#define ULONG_MAX     0xffffffffUL
#define LLONG_MAX     9223372036854775807i64
#define LLONG_MIN   (-9223372036854775807i64 - 1)
#define ULLONG_MAX    0xffffffffffffffffui64

5.3 再来一个小栗子

int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);
	return 0;
}

最后输出的是-10
image-20211125232050892

5.4 还有一个小栗子

根据之前的知识来看为什么下面的一个程序停不下来呢

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}

其实VS已经智能的给出报错原因了

image-20211126101645025

永远不可能比0小!

好了今天的内容就到这里了哈!!!

今天的“龙珠”集齐了6颗

很多关键字都是之前讲过的,今天再来一遍,当作复习巩固

下次继续,请持续关注

干净又卫生,别忘了一键三连

  • 12
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

言之命至9012

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

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

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

打赏作者

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

抵扣说明:

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

余额充值