你真的对各个关键字熟悉了吗?详细再介绍,基础再提高(小白友好)上

前言

本文主要是针对C 语言中各个关键字的整理总结,当然了,关键字在各个语言中大多通用,其他语言也可参考。
适合刚进行语言学习的小白,或是在学习完一门语言(C语言)之后进行基础巩固和提高,文章中设计的关键字是以C语言为标准的,主要参考C90 标准,C99 标准也有涉及,相关代码使用VS2019 编译运行,部分知识参考自网络,书籍参考自《C语言深度解剖(第2版)》
这只是第一部分,我会陆续更新之后的内容的。

关键字分类

一般以C90标准,认为C语言关键字为32个,虽然后来C99 又增加了5个关键字,但是影响不大,任然以C90 标准为主。下面会主要介绍几个重要的关键字。

程序运行

编写程序,就是由 文本文件 -> 可执行程序(二进制文件),运行的就是可以双击运行的 .exe 文件

  1. 在win 中,双击的本质是运行程序,将程序(从硬盘)加载到内存中
  2. 任何程序在运行前都必须被加载到内存中
    1. 程序没有被加载的时候,在硬盘中
    2. 至于要加载到内存中是为例匹配CPU的速度

所有变量都要在内存的某个位置开辟变量

即变量就是在内存中开辟空间,以供程序使用

为什么要定义变量

在程序运行时,并不是所有数据都要立刻被计算执行,而是要暂时保存一下,在需要的时候再处理

定义与声明

定义:开辟空间使用,只能定义一次

声明:告知程序使用,声明可以多次

最宽宏大量的关键字—auto

auto 在缺省的情况下,编译器默认所有变量都是auto的

一般认为在代码块内(即 { } 范围内定义的为局部变量)定义的变量为局部变量,在内存的 栈 区保存

在代码块外定义的变量为全局变量,在内存的全局静态区保存

下面这个例子可以理解变量的作用域

#include <stdio.h>
int main()
{
	int x = 10;
	if (10 == x)
	{
		int y = 20;
		printf("code1: %d, %d\n", x, y);
	}
    //编译会报错,显示y 未定义,y只在代码块内有效
	printf("code2: %d, %d\n", x, y);

	return 0;
}

变量的生命周期

生命周期的定义:该变量从定义到被释放的时间范围,释放就是指曾经开辟的空间被释放

局部变量:进人代码块,形成局部变量【开辟空间】,退出代码块,【释放空间】

全局变量:定义完成之后,程序运行的整个生命周期内,该变量一直有效

如果全局变量与局部变量出现命名冲突,则优先使用局部变量的定义信息

#include <stdio.h>
int g_val = 100;	//定义一个全局变量
int main()
{
	int g_val = 200;
	printf(" %d \n", g_val);

	return 0;
}

auto 相关

一般在代码块中定义的变量,就是局部变量,默认为auto 修饰的,不过一般都是省略的,一般默认局部变量使用auto来修饰

auto 关键字不能用来修饰全局变量,会直接报错

#include <stdio.h>

int main()
{
	for (int i = 0; i < 10; i++)
	{
		printf("i = %d\n", i);
		if (1)
		{
			auto int j = 1;  //默认隐藏的auto 关键字
			printf("before: %d\n", j);

			j++;
			printf("after: %d\n", j);
		}

	}
	

	return 0;
}

最快的关键字—register

可以使用register 修饰的变量:

  1. 局部的(全局变量会导致CPU寄存器被长时间占用)
  2. 不会被写入的(写入需要些内存,就失去了register 的意义)
  3. 高频被读取的(为了提高效率)
  4. 如果要使用,请不要大量使用,因为寄存器数量有限

register修饰的变量,不能取地址,因为已经在寄存器区了

#include <stdio.h>
int main()
{
	register int pass = 10;		//定义一个存放在寄存器区的局部变量
	pass = 200;
	printf("%d\n", pass);

	return 0;
}

最名不副实的关键字—static

头文件的必要性

  • 单纯的使用源文件,组织项目结构的时候,项目越大越复杂的时候,维护成本就会变高,所以在实际开发的时候,就会使用头文件,减少大型项目的维护成本,所以可以多使用头文件来集中管理各个变量和函数

  • 一般头文件与实现文件同名,头文件要被多个源文件包含,可能会有被重复包含的问题,在VS中可以使用 #program once 来防止被重复包含

  • 主要包含系统的头文件,所有变量的声明,所有函数的声明,自定义的数据类型(结构体)等

  • 函数的定义和声明必须完全一致,方便后期查看阅读

test.h

#pragma once
#include <stdio.h>

extern int g_val = 100;		
//extern 表示仅仅只是声明,而不是定义,可以多次声明,但是不可以多次定义,所以在变量的声明时最好使用extern 修饰

void show();

test.c

#include "test.h"

void show()
{
	printf("hello world!\n");

}

main.c

#include "test.h"

//int g_val = 100;	//定义一个全局变量
//声明并没有开辟空间,没有办法赋值,所有变量在声明变量时,不能设置初始值
extern int g_val = 100;

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

多文件下,变量声明必须使用 extern 修饰,函数声明建议使用 extern(函数本身就有extern 属性)

extern的使用方法CSDN

static的好处

函数和全局变量都可以跨文件访问 在具体的应用场景中,我们是不想让某个变量/函数实现跨文件访问的

static 关键字修饰的全局变量,只能在本文件内被访问,不能被外部其他文件直接访问

static关键字修饰的函数,只能在本文件内被访问,不能在外部被其他文件直接访问

使用static关键字可以实现函数访问的加密,就像C++ 的封装,可以增加安全性

使用static 关键字可以很好的提供安全保证和项目维护

static修改的是什么?

static关键字限制的是作用域,而不是生命周期

static 修饰局部变量

使用static修饰的局部变量,具有 局部临时性,由函数调用开辟空间并初始化,函数结束释放空间

#include "test.h"

static void fun()
{
	int i = 0;
	i++;
	printf("i= %d , [ %p ] \n", i,&i);
}

int main()
{

	for (int i = 0; i < 10; i++)
	{
		fun();
	}

	return 0;
}

下面是两次的运行结果:
第一次运行结果
第二次运行结果

不同的运行结果是不一样的,可见其每次的地址空间是不同的,只是这里VS 在申请空间的时候,每次都把相同的地址空间都分配给了 i ,实际上是每次调用 fun 函数都在重新调用新的地址空间。

static 修饰局部变量

如果将上面例子的 i 变为static修饰的局部变量,观察运行结果:

#include "test.h"

static void fun()
{
	static int i = 0;
	i++;
	printf("i= %d , [ %p ] \n", i,&i);
}

int main()
{

	for (int i = 0; i < 10; i++)
	{
		fun();
	}

	return 0;
}

运行结果:
![static修饰局部变量的运行结果](https://img-blog.csdnimg.cn/219b10d918f74cf1a7d14ee9c9377338.png#pic_center

可见,static修饰局部变量,更改局部变量的声明周期,将临时变量变为全局的生命周期,可以用下面这个例子来加深对生命周期和作用域的理解:

#include "test.h"


int* p = NULL;
static void fun()
{
	static int a = 100;
	p = &a;
}

int main()
{
	printf("%d\n", a);		//会报错,a 是未定义的标识符,应为它只是有 全局的声明周期,而没有全局的作用域
	fun();		//如果是局部变量,那么在函数调用结束时就会被释放,但是结果并没有
	printf("%d\n", *p);		//获取指针的解引用,如果该函数的局部变量已经被释放,得到的应该是随机值,而不是100

	return 0;
}

下面是运行结果:
在这里插入图片描述

内存布局

在这里插入图片描述

基本数据类型

sizeof 关键字:编译时确定一种类型在开辟空间时的大小

	printf("size: %d\n", sizeof(char));		//1
	printf("size: %d\n", sizeof(short));	//2
	printf("size: %d\n", sizeof(int));		//4
	printf("size: %d\n", sizeof(long));		//4
	printf("size: %d\n", sizeof(long long));	//8
	printf("size: %d\n", sizeof(float));		//4
	printf("size: %d\n", sizeof(double));		//8

确定大小的方法:

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

C语言中类型的使用

C语言要使用内存,本质是对内存进行合理划分,按需索取

就是为了满足不同的计算需求,要使用合适的变量来保存,由于计算机硬件资源有限,为了要满足不同的应用场景,解决不同的应用场景的计算方法也不同,需要的空间也是不同的,本质就是使用最小的成本,解决各种多样化的场景问题

变量命名规则

变量命名直观可以阅读,要做到 见名知意,推荐使用大小驼峰命名法,使用数字、字母、下划线的组合命名

  • 推荐使用数字、字母、下划线的组合进行命名
  • 命名长度不宜过长,较长的单词可以选择去掉“元音”进行缩写,英文词尽量不缩写,特别是专业名词,在同一系统中对同一个单词必须使用相同的表达方法,并且注明其意思
  • 大驼峰命名法:标识符由多个词组成时,每个词的第一个字母大写,其余全部小写:int CurretnVal;
  • 尽量避免名字中出现数字编号,除非逻辑上确实需要编号,比如驱动开发时的 引脚 命名
  • 对在多个文件之间共同使用的全局变量或函数要加范围限定符(可以使用模块名(缩写)作为范围限定符)
  • 变量命名前缀可以参考 [ 匈牙利命名法 ],但是现在不是很推荐,仅使用 g 前缀来表示全局变量就可以了
  • 程序中不得出现以大小写区分的变量名
  • 所有宏定义、枚举常量、只读变量 全部用大写字母命名,用下划线分割单词 : #define INT_MAX 100
  • 对于习惯问题,局部变量可以采用通用的命名方式,但是仅限于 n ,j ,k 等作为循环变量使用
    一般习惯上使用n ,m ,i ,j ,k 等表示 int 类型的变量;
    c ,ch 等表示字符型变量;
    a 表示数组;
    p 表示指针;
    仅仅作为习惯,除了 i ,j ,k 等可以作为表示循环变量之外,别的字符变量名尽量不要使用
  • 结构体等被定义时,必须要有明确的结构体名,所有的 结构 和 联合 的类型在转换单元的结尾应该是完整的,结构或联合的完整声明应该包含在任何涉及结构的转换单元之内
  • 定义变量的同时一定要进行初始化,定义变量时编译器并不一定清空了该块内存,它的值可能是无效数据或随机值,VS编译器甚至不能通过编译,gcc 编辑器可以通过编译
  • 不同类型数据之间的运算要注意精度扩展问题,一般 低精度 向 高精度 数据扩展
  • 禁止使用八进制的常数(0 除外,因为严格意义上来讲 0 也是八进制数 )和八进制的转义字符
    在计算机中,任何以 0 开头的数字都认为是 八进制格式的数字(十六进制的 0x 不算 ) ,所以我们写固定长度的数字的时候是有一定风险的。

最冤枉的关键字sizeof

sizeof 可以用来求一些数据类型的大小

	int* p = NULL;
	int arr[10];
	int* test[3];

	printf("%d\n", sizeof(p));	//指针变量的大小 4
	printf("%d\n", sizeof(arr));	//数组的大小,不是数组的首地址的大小, 40
	printf("%d\n", sizeof(test));	//指针数组的大小,数组内有三个指针,每个指针大小为4,  12

数据存储的概念

数据在计算机内部已二进制形式存储,在计算机中任何数据都被转化为二进制存储

存储的本质

unsigned int b=-10;

定义变量并进行初始化,在进行变量存储时,一定是现有空间,再有内容,所以需要先把内容转换为二进制,再放进去

十进制数:-10

二进制原码:1000 0000 0000 0000 0000 0000 0000 1010

二进制反码:1111 1111 1111 1111 1111 1111 1111 0101

二进制补码:1111 1111 1111 1111 1111 1111 1111 0110

但是整形在存储时,空间是不关心内容的,在将数据保存在空间内时,数据已经被转化为二进制,所以是可以放到 unsigned int 申请到的空间中的,并且不会有任何问题,而 unsigned int 在读取时才会起限制作用,即如何解释空间内部的信息时,进行表示

unsigned int a = -10;
	printf("整形输出:a = %d\n", a);
	printf("无符号输出:a = %u\n", a);

	//输出结果:
	//整形输出:a = -10
	//无符号输出:a = 4294967286

//这里的 a 是无符号数,在 以 %d 进行输出使,可以正常解释为 int 型进行输出,就得到了 -10,但是如果是以无符号数进行输出时,%u,存储在计算机内部的二进制补码,就被认为是其原码了,因为是无符号数,原码、反码、补码都一样,所以将其补码作为原码转换为十进制就进行输出了,就得到了 4294967286

变量存储过程:字面数据必须 先转换为补码,再放入空间中 ,所以,所谓的符号位,完全看数据本身是否携带 + - 号,和变量是否有符号无关

取得变量的过程:取数据一定要先看变量本身的类型,然后才决定要不要看最高符号位,如果不需要,直接二进制转换为十进制,如果需要则需要转换为原码,然后才可以识别(要识别最高符号位在哪里,还需要明确大小端)

大小端问题

什么是大小端

在内存中,每个字节在内存中都有不同的存储地址,所有地址都是不相同的,有大小之分,即地址高低之分

数据在存储到内存中时,也是以字节为单位,分成若干块,即分为高权值位和低权值位

而如何将数据存储到内存中,就是大小端问题要研究的地方了
在这里插入图片描述

大端存储:

按照字节为单位,低权值位数据存储在高地址处

小端存储

按照字节为单位,高权值位数据存储在低地址处
在这里插入图片描述

如果 记忆的话,可以:小端存储就是:小小小 --> 端:安装字节为单位,低权值()数据存储在低地址(

大小端如何影响数据存储:

大小端存储本质上是数据空间 按照字节为单位的一种隐射关系

如果以大端存储来存储数据,就需要以大端存储来取出数据
在这里插入图片描述

以小端存储(32位机器)为例:

存储:将数据转换为32位二进制数(左边为高权值,右边为低权值) --> 在内存中开辟一块对应大小的空间 --> 将高权值 位 存储到高地址空间,低权值位 存储到低地址空间 --> 存储完成
取值:先看自身类型,确定是否需要符号位等 --> 判断是大端存储还是小端存储,从内存中取出数据,由符号位判断是否需要符号 --> 得到二进制数 --> 转换为对应进制数据,显示出来
在这里插入图片描述

几道编程题,来加深对于以上知识的理解

1.字符数组的长度

下面的用来计算长度:

printf("%d\n", '\0');
char a[1000];
for (int i = 0; i < 1000; i++)
{
    a[i] = -1 - i;

}
printf("%d\n", strlen(a));	//返回字符串的长度
/*
	运行结果:
	0
	255
*/

**这里要注意的是,数组a 是字符型的,每个元素的大小为 一个字节,一个字节能够存储的十进制数的范围是 -128~127 , 所以在 for 循环中,当 i = 127时,该元素的值为-128,还可以存储,当i = 128时,在进行计算存储时,就是 -1 和 -128 做加法运算,转换为补码进行计算,就是 1111 1111 和 1000 0000 做和,得到的二进制数为 1 0111 1111 由于只有一个字节(8位),所以就要发生截断,存储的是 0111 1111 , 所以转化为十进制就是127,下一个当 i = 129时,该元素的十进制值就是 126了,==后面随着 i 的增大,a[i] 的值就有逐渐变小,一直到 a[255] = 0 , 由于a[i] 是字符型变量,所以它就被解释为 ASCII 码了,而十进制数字0对应的 ASCII码值就是 \0 所以该字符数字在输出长度的时候,就只到 \0 到结束, 得到的输出结果就是 255 **

2.不同类型的差异

int i = -20;
unsigned int j = 10;
printf("以有符号数输出结果:%d\n", i + j);
printf("以无符号数输出结果:%u\n", i + j);
/*
	输出结果:
	以有符号数输出结果:-10
	以无符号数输出结果:4294967286
*/

这里如果以有符号数数进行输出的话,就是简单的十进制数字的计算问题,就很好理解了,结果就是 -10了,但是,如果是以无符号数进行输出,就需要考虑数字运算在计算机内部的计算过程了,首先将 -20 和 10 在计算机内部的存储的二进制数据表示出来,即将十进制转化为32位二进制数,然后将其转化为补码,准备进行计算,分别是: 1111 1111 1111 1111 1111 1111 1110 1100 和 0000 0000 0000 0000 0000 0000 0000 1010,两个二进制补码进行算术加法运算,得到结果为1111 1111 1111 1111 1111 1111 1111 0110,转化为原码就可以得到:1000 0000 0000 0000 0000 00000 00000 1010,如果是以有符号来进行转换的就是十进制的 -10 ,如果是以无符号数进行解释的话,就是直接将这个二进制数解释为十进制数,就是十进制的 4294967286所以数据的存储形式都是一样的,只是不同类型的解释不同,结果就不同

所以在实际编程中,可以将无符号数在定义时,在其后面添加 u 后缀

unsigned int a = 23u;
printf("a=%d", a);
//运行结果:a=23

计算机内部的存储的二进制数据表示出来,即将十进制转化为32位二进制数,然后将其转化为补码,准备进行计算,分别是: 1111 1111 1111 1111 1111 1111 1110 1100 和 0000 0000 0000 0000 0000 0000 0000 1010,两个二进制补码进行算术加法运算,得到结果为1111 1111 1111 1111 1111 1111 1111 0110,转化为原码就可以得到:1000 0000 0000 0000 0000 00000 00000 1010,如果是以有符号来进行转换的就是十进制的 -10 ,如果是以无符号数进行解释的话,就是直接将这个二进制数解释为十进制数,就是十进制的 4294967286所以数据的存储形式都是一样的,只是不同类型的解释不同,结果就不同

所以在实际编程中,可以将无符号数在定义时,在其后面添加 u 后缀

unsigned int a = 23u;
printf("a=%d", a);
//运行结果:a=23

这只是第一部分,我会陆续更新之后的内容的。
感谢观赏,慢慢提高

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值