C语言深度剖析 -- 32个关键字(上)

C语言关键字

我们都知道C语言关键字有auto、if、int等等…但是我们知道C语言一共有多少个关键字呢?
一般来说,共有32个关键字(C90或C89标准);
但是后面C99又新增了5个关键字;但是目前主流的编译器,对C99好多并不支持;
所以,我们默认按照C90标准,即C语言共有32个关键字;

关键字说明
auto声明自动变量
short声明短整型变量或函数
int声明整形变量或函数
long声明长整型变量或函数
float声明单精度浮点数变量或函数
double声明双精度浮点数或函数
char声明字符型变量或函数
struct声明结构体变量或函数
union声明共用数据类型
enum声明枚举类型
typedef用以给数据类型取别名
const声明只读变量
unsigned声明无符号变量或函数
signed声明有符号变量或函数
extern声明变量是在其他文件中声明
register声明寄存器变量
static声明静态变量
volatile说明变量在程序执行中可被隐含的改变
void声明函数无返回值或无参数,声明无类型指针
if条件语句
else条件语句与分支语句(与if合用)
switch用于开关语句
case开关语句分支
for循环语句
do循环语句的循环体
while循环语句的循环条件
goto无条件跳转语句
continue结束当前一层循环,开始下一轮循环
break跳出当前循环
default开关语句中的“其他”分支
sizeof计算数据类型长度
return子程序返回语句循环条件(可带参数,也可不带)

我们人生中第一个C语言程序

//从vs中创建c语言项目
#include <stdio.h>
#include <windows.h>//此头文件仅仅是为了停屏

int main()
{
    printf("hello world!\n");
    system("pause");//pause停屏,我们可以看到终端
    return 0;
}

在windows系统中,我们双击的本质就是运行程序,将程序添加到内存当中
任何程序在运行之前都必须加载到内存当中
注意: 程序在没有加载的时候,是放在硬盘当中的;我们之所以加载到内存当中 -》快;
在这里插入图片描述

变量的定义与声明

  • 问题1:什么是变量?—在 内存中 开辟特定大小的空间,用来保存数据

  • 问题2:如何定义变量:类型 变量名 = 默认值;例如: int a = 10; double d = 3.14; char c = 'a';

  • 问题3:为什么要定义变量?—

计算机的诞生就是为了解决人类计算能力不足的问题;即计算机就是为了计算的。
而计算,就需要数据;
我们计算数据,并不是无时无刻都需要计算;例如:我们吃饭并不是所有饭菜都立刻吃掉的,我们需要碗和盘子,把饭菜放到里面,想吃的时候就吃;
那么,有人就会抬杠:为什么要盘子,不可以直接使用在锅里吃吗?可以,但是 效率低
总之:我们定义变量是为了在我们想用的时候可以立即拿出,不用的时候就放在那就可以了

  • 问题4:变量定义的本质?— 我们现在知道了:程序运行之前,我们需要把数据加载到内存当中,程序计算时,需要使用到变量;那么, 定义变量的本质就是在内存中开辟一片空间,用来保存数据
  • 问题5:变量声明的本质?— 我们举个例子:比如你和你的兄弟都喜欢一个女孩子翠花,但是你的胆子大,你就直接和翠花表白,翠花答应你了,这时候,你就可以告诉那个兄弟:翠花现在是我的女朋友,现在你们要保持距离。-》这里,表白就是定义,你和你兄弟说就是声明;声明的本质也就是广而告之

总结: 变量的定义:开辟空间的,定义只能定义一次(你不可能和翠花表白很多次,别人会被你吓跑的);变量的声明是告之,可以声明很多次;但是在声明的时候,我们就不可以进行赋值操作了(编译器会有可能把这个过程看成是定义过程)

变量的作用域与生命周期

  1. 作用域:指的是该变量可以被正常访问的代码区域(改变量的有效区域);这里我们需要知道:全局变量在整个程序运行期间都有效;局部变量只在本代码块内有效(用 {} 括起来的区域,就叫做代码块)
#include <stdio.h>
#include <windows.h>

int g_x = 100;//全局变量

int main()
{
    int x = 10;
    if (x)
    {
        int y = 20;//局部变量
        //y只能在代码块内有效,而全局变量g_x在哪里都有效
        printf("局部变量 y = %d, 全局变量 g_x = %d\n", y, g_x);
    }
    //printf("局部变量 y = %d\n", y);//错误,局部变量只在本代码块内有效
    
    system("pause");
    return 0;
}
  1. 生命周期:指的是改变量从定义到被释放的时间范围;全局变量:定义完成之后,程序运行的整个生命周期内,改变量一直有效;局部变量:进入代码块,形成局部变量,退出代码块,释放局部变量,生命周期结束;
  2. 作用域 VS 生命周期:例如:爱迪生对世界的贡献很大,爱迪生的生命周期就是从出生到死亡,而他并不是一直对世界作出贡献,他做出贡献的时间才是作用域

在这里插入图片描述

最宽宏大量的关键字 – auto

在c语言中,auto的用法很少且比较鸡肋,用处不大;但是在 C++ 中给auto赋予了很多的用处(配合STL);但是在C语言里面我们就暂且不说C++的知识了;

auto这个关键字一般是用来 修饰局部变量的 ,默认情况下都是auto修饰的(局部变量、自动变量、临时变量我们统称为局部变量)

#include <stdio.h>
#include <windows.h>

int main()
{
	for (auto i = 1; i < 10; i++)
	{
		printf("i = %d\n", i);
		if (1)
		{
			auto j = 0;//局部变量,这一个代码块结束之后,j又被赋值成0
			printf("before: j = %d\n", j);
			j += 1;
			printf("after: j = %d\n", j);//j是0/1循环的
		}
	}

	system("pause");
	return 0;
}

注意:这里面的auto都是可以省略的,在C语言中我们可以忽略这个关键字

最快的关键字 – register(寄存器变量)

我们学习过计组的话,就会知道计算机的系统结构;其中,CPU主要负责计算相关的硬件单元,但是为了方便计算,(解决CPU与内存之间速度差异的问题)一般第一步需要把数据从内存中读取到CPU中,也就是需要CPU具有一定的存储能力;注意:CPU可能并不是需要计算当前读入到里面的数据,只是为了提前存储好,想用就可以用了,不然的话,速度太慢了;
我们来看一下CPU存储金字塔:

在这里插入图片描述

距离CPU越近的存储硬件,速度越快

  • 问题1:什么是寄存器? – 如果我们学过计组的话,就会很清楚的明白,但是如果我们还没有学的时候,我们只需知道CPU内部集成了一组存储硬件,这组硬件叫做寄存器即可
  • 问题2:寄存器存在的本质? – 提高计算机的运行效率,就不需要从内存中读取数据了
  • 问题3:什么样的变量适合使用寄存器变量呢?
  1. 局部的(全局变量会导致CPU寄存器被长时间占用)
  2. 不会被写入的(写入需要回内存,后续还要读取检测的话,register就没有意义了)
  3. 高频率读取的(提高效率)
  4. 寄存器数量有限,不可以大量使用register关键字

注意:除了以上的内容,还有就是:register修饰的变量不可以取地址(地址是内存相关的概念)

demo:

#include <stdio.h>
#include <windows.h>

int main()
{
	register int a = 10;
	printf("&a = %p\n", &a);//报错 -- C2103	寄存器变量上的“& ”
	
	system("pause");
	return 0;
}

最名不符实的关键字 – static

在讲这个关键字之间,我们之前应该了解到,我们自己在写C语言项目的时候,项目会非常大且复杂,我们一般都是会写很多了源文件(.c文件)和一个头文件(.h);我们把所有用到的函数、变量、头文件、#define、struct、typedef等等都会放到 .h(头文件) 文件中,这样可以大大减少项目的维护成本;这里面又会牵扯到头文件重复包含的问题,我们先给一个解决方案,后面还会细讲#pragma once

我们先提一下extern这个关键字,当我们想跨源文件来使用某个变量或函数的时候,就必须使用到这个关键字例如:extern int g_val;函数可以直接在头文件声明,不需要使用 extern,但是最好也带上extern void show();

总结:变量声明必须带上extern,函数声明建议带上extern变量不可以跨文件访问,而函数可以跨文件访问

static的作用

  1. 修饰全局变量的时候,该全局变量只能在本文件内被使用,不可跨文件访问,也就是更改变量的作用域
  2. 修饰函数的时候,该函数只能在本文件内被使用,不可跨文件访问,也就是更改变量的作用域
  3. 修饰局部变量的时候,会更改局部变量的生命周期(不是作用域)

demo:

#include <stdio.h>
#include <windows.h>

void fun1()
{
	int i = 0;
	i++;
	printf("no static: i = %d\n", i);//输出10个1
}

void fun2()
{
	static i = 0;
	i++;
	printf("has static: i = %d\n", i);//输出1-10;更改了i的生命周期
}

int main()
{
	for (int i = 0; i < 10; i++)
	{
		//fun1();
		fun2();
	}

	system("pause");
	return 0;
}

总结:static修饰局部变量,变量的生命周期会变成全局周期(作用域不变)

补充一下C语言寻址空间的知识

在这里插入图片描述

基本内置数据类型 – char、short、int、long、float、double

基本数据类型有以下几种:

在这里插入图片描述

我们这里先讲解基本的内置类型:

这里,我们只需要搞懂一个问题:为什么语言要有好多种数据类型,直接将内存整体使用不好吗?
在任何时候,都不是只有我们当前的程序在运行,还有其他很多种程序在运行,你把这块内存占用了,其他的程序并不知道你正在使用这块内存,他不知道是不是可以使用;还有,你把这块内存全用了,你的程序并不是无时无刻都在使用,当不需要使用的时候,你就是在浪费这块内存了;
做月饼的例子:月饼的外表有各种各样的,我们只需要使用不同的模子就可以制造出各种各样的月饼了;
在这里插入图片描述
所以,C语言中,为什么有那么多的类型,就是为了满足不同的场景
比如:我们想计算我们的工资的时候,4个字节的int就足够了,没有必要开大

那么,我们这里就需要知道,这些类型到底在内存中占用多少内存大小呢?

在这里插入图片描述

注意:上面的单位都是字节

最冤枉的关键字 – sizeof

我们为什么称sizeof为最冤枉的关键字?就是因为常年别人都把他误认为是函数,这是错误的,sizeof是C语言中的关键字
我们只需要记住sizeof是计算类型或变量在内存中占多大空间的就可以了,上面我们已经计算过了,至于sizeof与指针的内容,我们会在后面指针的部分讲到;

注意1:

int a = 10;
printf("%d\n", sizeof(a));//对
printf("%d\n", sizeof a);//对
printf("%d\n", sizeof(int));//对
printf("%d\n", sizeof int);//错误

注意2:sizeof后面的括号里是不允许有其他操作的,就算写也不起效果

int a = 10;
printf("%d\n", sizeof(a++));
printf("%d\n", a);//a还是10

signed、unsigned关键字(整形在内存中的存储)

浮点数(float、double)都是有符号的,浮点数没有unsigned

我们需要先理解原码、反码、补码的概念:

  1. 计算机中的有符号数有三种表示方法,原码、反码、补码
  2. 三种表示方法均有符号位与数值位,符号位都是用0表示正,1表示负,位数值位的表示各不相同
  3. 正数与无符号数(unsigned)的原码、反码、补码都相同
  4. 负数原码:直接将二进制按照正负数的形式翻译成二进制就可以了
  5. 负数反码:将原码的符号位不变,其他为按位去反就可以了
  6. 负数补码:反码+1就得到补码
  7. 对于整形来说:数据存放内存中其他存放的是补码(后面会讲)

我们知道,一个变量的创建是现在内存中开辟空间的,空间的大小是由类型决定的
那么,数据开辟好了空间,在内存中到底是怎样存储的呢?
在这里插入图片描述

我们在定义类型的时候,默认前面省略了 signed

我们现在来看个问题?unsigned int b = -10;这个写法是否正确呢?
答案?这样写是没有问题的,是正确的的;整形在存储的时候,是不关心内容的

在这里插入图片描述
我们来看下面这个例子:

在这里插入图片描述
我们a虽然是有符号的整形,但是我们取出来的时候,按照有符号的整形打印他就是-10,而我们把它当成无符号的整形取出来的时候,就是那个值了;同理b;所以我们发现:我们定义变量,给他类型,只是给他开辟了特定的空间,到底存放什么内容我们不用管,但是取出来的时候,我们就必须看他是什么类型,或者你按照什么类型给他取出来的

下面还有几个小的知识点,我们来看看吧~

  1. 我们知道了原码怎么转向补码的,但是我们怎么反过来把补码转成原码呢?在这里插入图片描述
  2. 二进制快速转化口诀:在这里插入图片描述

到这里,我们已经知道了我们的数据是存放到内存里面,按照补码形式进行存储的,那么我们来看一下到底是怎样存的呢?
我们以int a = 10;来看一下

在这里插入图片描述
我们发现在vs中,我们的变量最后在内存中是这样存储的,我们来分析一下?

10的补码是:0000 0000 0000 0000 0000 0000 0000 1010
在计算中存储的是十六进制:0x00 00 00 0A
为什么我们在内存中看到的是反过来的呢?0x0a 00 00 00

这里我们需要了解大小端的概念:我们定义好变量后,会开辟一片空间,将变量的二进制补码写入到内存中,那么我们如何写进去呢?是从前往后写,还是从后往前写呢?这里没有特定的要求,不管我们怎么放,只要使用同等的条件去取就可以了?由此,产生了两种不同的厂商,分为大端和小端,目前比较流行的是小端。

在这里插入图片描述

对于大小端这件事,对于每个人是无法达到一致的,每个人都有每个人的看法,我们都没有足够的理由说服对方;
对于数据的存与取,我们除了要看上面我们学的看数据类型,这里还要加一个看是使用大端存储,还是小端存储不管哪种存储方式,我们只要使用同等条件去取,都可以!

我们怎么判别我们电脑使用大端还是小端呢?(2015年百度一道笔试题)

#include <stdio.h>
#include <windows.h>

int check_sys()
{
	int i = 1;
	return (*(char*)&i);
}

int main()
{
	int ret = check_sys();
	if (ret == 1) printf("小端\n");
	else printf("大端\n");
	system("pause");
	return 0;
}

上面输出的结果是小端;我们来解释一下return (*(char*)&i);这一段代码:我们知道 i 放到内存中的十六进制数是:0x01 00 00 00;我们只需要看 i 的第一个字节内容,如果是1就是小端,如果是0就是大端;那么我们怎么才可以拿到 i 的第一个字节呢?char* 的指针正好就是访问一个字节的地址,我们完整的代码就是:

int i = 1;
char* p = (char*)&i;
if(*p == 1) printf("小端\n");
else printf("大端\n");

为什么变量采用的都是补码来进行存储?

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

整形取值范围(很重要)
简单为例,我们就以 signed char 为例,其它的和这类似!

在这里插入图片描述
那么为什么 1000 0000 就表示成-128呢?我们来看看char c = -128;在内存中表示多少

在这里插入图片描述
我们可以发现char类型是可以存放-128这个数据的,而128就不可以,这是为什么呢?

在这里插入图片描述
我们可以总结一下各种数据类型的取值范围:

  • char: [-2 ^ 7, 2 ^ 7 - 1]
  • short: [-2 ^ 15, 2 ^ 15 - 1]
  • int: [-2 ^ 31, 2 ^ 31 - 1]

好了,到这里我们整形数据存储的知识就学的就差不多了,我们来做几个题目检验一下成果吧!

例题一:

#include <stdio.h>
#include <windows.h>


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

第一点:我们首先要知道 strlen 这个库函数是计算字符串长度的,遇到 ‘\0’ 截至,并且 ‘\0’ 不计算在长度内;'\0’对应的ASCII码值就是0。例如:char a[5] = {3, ‘h’, 5, 0, ‘e’};则 strlen(a)的答案就是3,只计算0前面的字符串长度,且0不包括在内。

第二点:我们需要知道当变量取值范围超过最大值时,之后的值该是多少?

在这里插入图片描述
因此,这道题的答案就是255!

例题二:

#include <stdio.h>
#include <windows.h>


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

在这里插入图片描述

例题三:

#include <stdio.h>
#include <windows.h>


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

这个题我们要注意:i是个无符号整形变量,打印的时候,会依次打印9 、8、7、6、5…0,当打印到0的时候,i 再减1,就相当于 i + (-1);也就是:
在这里插入图片描述
这个数按照 unsigned int 来打印的话就是 2 ^ 32 - 1 那么大
其实变量的取值范围就是按照之前我给大家画的那个圆来记,以 signed char 来说:
在这里插入图片描述

当变量取到头的时候,会再循环一圈,以此类推…

还有我们在定义unsigned类型的时候,最后最好加个 u,就像定义 float类型在后面加个 f一样,例如:unsigned int u = 10u;

还有一点,大家在这里的时候,记得与整形提升不要搞混淆

在这里插入图片描述
c前面的 c + 1会把char类型的变量整形提升到 int 型,再进行加法运算
整形提升:在表达式计算时,各种整形首先要提升为 int 类型;表达式的整形运算要在CPU的相应器件内进行,CPU运算器的操作数的字节长度一般是 int 类型的字节长度

这一节,我们就讲到这里了,下面一直会持续学习的,大家一起加油

努力提升自己,永远比仰望别人更有意义

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值