c语言关键字理解和再认识(上)(1)

简介: 前言首先我们来了解一下,代码和计算机的关系。我们在编译器vs2022中写的代码叫做文本代码,因为它是放在一个文件夹中的,是一个文件,而这个文本代码具体和我们的计算机是怎么联系起来的。下面我们就好好探究一下这个关系。首先介绍了文本代码是什么的问题,那么文本代码对应的是什么?其实它就是一个可执行程序(可执行程序就是二进制程序,可执行程序本质上也是个文件)。如图:

前言

首先我们来了解一下,代码和计算机的关系。

我们在编译器vs2022中写的代码叫做文本代码,因为它是放在一个文件夹中的,是一个文件,而这个文本代码具体和我们的计算机是怎么联系起来的。下面我们就好好探究一下这个关系。

首先介绍了文本代码是什么的问题,那么文本代码对应的是什么?其实它就是一个可执行程序(可执行程序就是二进制程序,可执行程序本质上也是个文件)。如图:

28c6830c1f345ff5f2644bed59ce487d.png

上面这个.exe后缀的文件在debug文件中)


当我们双击这个后缀为.exe这个文件时,就可以弹出可执行程序,你可以看看这个.exe这个文件的属性,它就是一个应用程序。

在这里双击程序就很容易解释了,那么我们来了解一下双击程序的本质什么?


双击一个软件就是把它打开,本质就是把将执行的数据加载到内存当中,让计算机运行。任何程序在被执行之前都被加载到内存当中

那么又有两个问题了,

  1. 没有被加载到内存之前数据是在哪的?
  2. 为什么要把程序加载到内存中?

第一个问题,没被加载到内存之前数据其实是在硬盘中。第二个问题,因为内存速度更快,运行效率更高,而硬盘速度慢。


这里大家可以了解了解冯诺依曼体系。这里我就不过多赘述了,初步了解就行。

初步了解之后,我们在来看看变量和内存是什么关系。


变量是什么?变量就是在内存中开辟空间。

变量的定义和声明又是什么?

extern a;//声明
int a = 10;//定义
char c = 'c';//定义
double = 3.0;//定义

为什么要定义变量,而不是直接拿内存直接用呢?


我们用图来分析



10aca8b46eb7d079dbeb20a935fbf202.png

当数据传给内存时,传的数据很多,但是这是cpu还是对数据一个一个的进行计算,并不是一下次全部计算,因为cpu空间很小,不可能一次子拿来计算,所以,为什么要定义变量这里就可以解决这个问题了,就是要把数据暂时保存起来,等待后续处理(这里的变量就叫做临时变量)。


以上就是初步了解计算机内存和我们的代码的关系。下面我们来步入正题。

关键字深度理解

register关键字

register关键字是用来干什么的呢?

register是用来尽量((这里的尽量的意思是有可能被录入内存中,也有可能被录入寄存器中)向寄存器申请空间,把变量放进寄存器中,这样运行效率更快。


那么,那些变量可以用register修饰呢?

局部变量(全局变量会占用寄存器长的时间)。

不会被写入的(写入的就需要返回内存,后续需要的检测的话,那么register就毫无意义了)。

高频出现的变量(提高效率)。

不建议register大量去定义变量,因为寄存器是有限的内存。


那么我们可不可以用去地址符号来访问被register定义的变量呢?


答案是不行的,我们用代码来检验。

33b06188cdc42abae5b9f752be86ad33.png

我们可以看到,可变参数已经拥有了一个指针(指针反应的就是一个地址),那么我们知道了我们的register修饰的变量不能访问地址,原因就是已经这个变量已经有了地址。


就现在的编译器而言,register这个关键字用的很少了,编译器现在很强大了,我们不需要用register来优化了,现在的编译器可以自动调整。

static关键字

你真的了解static关键字吗?我自认为我了解这个关键字,结果我才知道我了解的是皮毛,我问什么这么说呢,接下来我们一起来再初始static这个关键字。

前言:了解多文件

顾名思义,多文件就是在源文件中创建多个文件。

首先,我们来了解一下多文件是干啥的,在我们写代码的时候,首先会有一个main函数,而这个main函数中有函数,有变量,等等。那么我们可以把函数的定义放在别的源文件中,有的变量也可以放在别的源文件中,这样我们阅读代码就更加轻松,后期维护也方便。不然把所有内容放在有main函数的源文件中,这样内容太多,没有结构感,后期维护很困难。(头文件是以.h后缀的)

那么我们的头文件中具体有哪些内容呢?

1.函数声明

2.变量声明

3.#define以及#typedef等等


这么多内容,一个大项目中有多次声明,我们应该怎么样去解决呢?

方法一:首先在我们的头文件中顶部加上

#pragma once

#pragma once
//变量声明
extern int global;
//库函数声明
#include <stdio.h>
//自定义函数声明
extern int func()
//#define宏定义声明
#define PI 3.14
//等等

注意声明两字。虽然变量不带extern是没有错误的,但是我们在头文件养成习惯,把它加上,这里变量为什么不用声明也可以支持运行呢,原因是变量在这里被定义了,只要是定义在运行的时候,调取项目中所有文件时就不会报错。那么函数用不用带上声明呢,我建议是要带上的,首先,不带上extern声明是不影响的,原因是函数定义是看有不有函数体,有函数体就是定义,没有函数体就是声明,因此在有文件中函数只是给了声明,没有函数体。


总之,头文件就是为了后期更加容易维护,写代码也更加快捷,不需要重复一件事情很多遍。

正言

函数可以跨文件访问

全局变量可以跨文件访问

static修饰全局变量,该变量只在本文件内被访问,不能被其他文件直接访问,可以被函数间接访问。可见的改的是作用域而不是生命周期。

static修饰函数,只能在本文件中被访问,不能再别的文件被直接访问,只能间接访问。可见改的是作用域而不是生命周期。

static修饰局部变量,改变了生命周期,作用域并没有改变。

static修饰是用来增强安全性的,原因是别人修改你的代码的时候,你用static封装一样,你在这个文件中能改,在除了这个文件的其他文件就不能改了。

  1. 这里是函数间接访问。

9615e32d53241bce90b7bc2de4e8e074.png

sizeof关键字

#include <stdio.h>
int main()
{
    int a = 10;
    printf("%d\n",sizeof(a));
    printf("%d\n",sizeof(int));
    printf("%d\n",sizeof a); //判断是否为函数
    printf("%d\n",sizeof int); //err
    return 0;
}

通过这段代码我们知道了sizeof是操作符或者关键字,并不是函数,函数的结构是:函数名(); 显然第三种写法是对的就说明sizeof是操作符或者关键字,而不是函数。

#include <stdio.h>
int main()
{
    //三十二位下
    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)); //12
    return 0;
}

总结:sizeof关键字就是求内置类型的大小和自定义类型的大小(指针变量、数组、指针数组)。

当应用于静态维度数组时,sizeof返回整个数组的大小。sizeof操作符不能返回动态分配的数组或外部数组的大小。另外,当数组名取整个数组大小有两种情况,一种是取地址数组,另外一种就是sizeof(数组名)。

编程命名规范

  1. 命名应该简单易懂。便于记忆和阅读。
  2. 用最短的长度传递最多的信息。
  3. 大驼峰命名
  4. 尽量避免名字中出现数字编号。
  5. 程序当中不得出现仅靠大小写区分的相似的标识符。
    例如:
int c,C;//禁止
int i,I;//禁止
  1. 一个函数名禁止被用于其他之处。
    例如:
#include <stdio.h>
void fun()
{
    printf("hello world!\n");
}
int main()
{
    fun();
    int fun = 100;//禁止
    return 0;
}
  1. 所有的宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。
    例如:


#define MAX 100
#define FILE_PATH 20  
  1. 定义变量的同时千万不要忘了初始化。
  2. 不同类型数据之间的运算要注意精度扩展问题,一般低精度数据将向高精度数据扩展

命名非常重要,基本个人素养。

数据存储

unsigned和signed关键字

unsigned和signed通常都是修饰整型类型的。

unsigned——无符号,signed——有符号。

unsigned char
signed char
unsigned int 
signed int
unsigned short int
signed short int
unsigned long int
signed long int

原码、反码、补码

下面来解决数据在内存当中是如何存储的?

有符号

任何数据在计算机中都被转化成为二进制。为什么呢?原因是计算机只认识二进制,并且计算机中储存的整数必须是补码。

为什么必须是补码?

使用补码可以将符号和数值域统一处理;同时,加法和减法也可以统一处理(CPU中只有加法器ACC)

如果一个数是有符号数,并且是正数,那么原码=反码=补码。

例如:

//三十二位
#include <stdio.h>
int main()
{
    int a  = 10;
    //a=0000 0000 0000 0000 0000 0000 0000 1010(原码=反码=补码)
    //0x0000000A(十六进制)
    return 0;
}

如果一个数是有符号的,并且是负数,那么补码=原码取反(符号位不变)+1

#include <stdio.h>
int main()
{
    int b = -20;
    //1000 0000 0000 0000 0000 0000 0001 0100(原码)
    //1111 1111 1111 1111 1111 1111 1110 1011(反码)反码是原码符号位不变其余的取反
    //1111 1111 1111 1111 1111 1111 1110 1100(补码)补码是反码加一且符号位要参与运算
    //0xFFFFFFFFEC(十六进制)
    return 0;
}

无符号

没有符号位则原码=反码=补码,直接存储。

unsigned int a = 10; //OK
unsigned int b = -20; //OK

在印象中第二个肯定是错的,但是它是对的。 注意

首先,数据是先转化为二进制补码后再放进b这个空间中,b只是提供一个空间,并不在意它存的数据,所以这个unsigned并没有影响。也就是说存的时候,数据和类型没有关联。

那么这里的变量类型什么时候有区别?

数字带上类型才有意义。

例如:

1111 1111 1111 1111 1111 1111 1111 1110

这个二进制没有说是原码还是反码还是补码,它就是没意义的。

当我们有不同类型定义时,它的结果也是不同的。

那么我们来看看类型不同取的是不是一样的。

#include <stdio.h>
int main()
{
    unsigned int a = -10;
    printf("%u\n",a); //结果是4294967286
    //那么4294967286=1111 1111 1111 1111 1111 1111 1111 0110
    printf("%d\n",a); //结果是-10
    //-10 = 1111 1111 1111 1111 1111 1111 1111 0110
    return 0;
} 
//结果都是一样的,只是%u格式出来的结果是4294967286而%d格式出来的结果是-10,对应的二进制都是一眼的,说明了不同的类型是有区别的,类型决定了如何解释空间内部保存的二进制序列。

总结:

变量的存储过程:字面的数据必须先转化为补码,在放进空间中(先开辟空间再转化)。所以,所谓符号位,完全看数据本身是否携带±号,和变量是否有符号无关!

变量取的过程:取数据一定是先看对应的变量类型,然后才决定要不要看最高符号位。如果不需要,直接二进制转成十进制,如果需要,则需要转成原码,然后才能识别(当然,最高符号位在哪里,又要明确大小端)。

#include <stdio.h>
int main()
{
    unsigned int a  = -10;
    printf("%u\n", a); //4294967286
    signed int b = -10;
    printf("%d\n", b); //-10
    return 0;
}

两个变量先存,再转化为补码,存到内存空间中的是补码形式。然后取。

这里先看变量b,首先取是先看类型,是有符号的,看最高符号位,在判断是原码还是反码还是补码。最高符号位是1,是负数,负数是补码,然后转换成原码输出,得到-10。

再看变量a,首先看类型,是无符号类型,不用看符号,直接把二进制转化十进制,得到4294967286。

再举出一个例子:

signed int a = 10;

首先存进去,补码是0000 0000 0000 0000 0000 0000 0000 1010

然后取,首先看类型,类型是signed有符号,再看最高符号位,为0,是正数,判断是原码,直接转化为10。

补码如何转化位原码?

方法一:原码等于补码减一符号位不变按位取反。

例如:

1111 1111 1111 1111 1111 1111 1110 1100(补码)

1111 1111 1111 1111 1111 1111 1110 1011(反码)

1000 0000 0000 0000 0000 0000 0001 0100(原码)

方法二:原码等与补码符号位不变其余按位取反加一。

例如:

1111 1111 1111 1111 1111 1111 1110 1100(补码)

1000 0000 0000 0000 0000 0000 0001 0011

1000 0000 0000 0000 0000 0000 0001 0100(原码)


计算机这里肯定用的是第二种方法,这样硬件只需要一种硬件电路就可以解决,简化了。

二进制快速转化口诀

十进制转化为二进制

1=2^0

10=2^1

100=2^2

1000=2^3

规律就是1后面有几个零就是二的几次方,假如1后面有n个零则是2^n。

67怎么转为二进制呢?

67=64+2+1=2^6 + 2^1 + 2^0=100 0011

二进制转化为十进制

1001000011=2^9 + 2^6 + 2^1 + 2^0=512+64+2+1=579










相关文章
|
6月前
|
存储 程序员 C语言
C语言关键字是什么?什么是关键字?什么是字符和ascll码值
C语言关键字是什么?什么是关键字?什么是字符和ascll码值
|
4月前
|
C语言
|
4月前
|
存储 C语言
C语言中static关键字的作用与用法解析
C语言中static关键字的作用与用法解析
|
5月前
|
存储 C语言
C语言中的typedef关键字:为类型定义新名称
C语言中的typedef关键字:为类型定义新名称
|
5月前
|
C语言
深入探索C语言中的sizeof关键字
深入探索C语言中的sizeof关键字
|
5月前
|
存储 编译器 C语言
C语言中的关键字与标识符详解
C语言中的关键字与标识符详解
|
6月前
|
算法 编译器 API
C语言易混淆、简单算法、结构体题目练习、常见关键字总结-1
C语言易混淆、简单算法、结构体题目练习、常见关键字总结
|
6月前
|
安全 编译器 C语言
C语言中的const关键字
C语言中的const关键字
50 2
|
6月前
|
存储 C语言
【C语言】数据:数据类型关键字
【C语言】数据:数据类型关键字
|
6月前
|
存储 编译器 C语言
c语言中static关键字的作用
c语言中static关键字的作用