C-关键字(上)

文章目录

          • 定义和声明
    • 变量
          • 声明周期vs作用域
          • 局部变量vs全局变量
      • register
          • 存储分级
          • register修饰变量
            • 什么变量可以采用register?
      • static
          • 多文件
          • static修饰
        • static+临时对象->全局性
        • C基本数据类型
      • sizeof
          • sizeof()求内置类型大小
          • sizeof()求自定义类型大小
          • 变量为什么要初始化?(面试题)
      • signed unsigned
        • 数据存取
          • 大小端
        • 数据范围
          • 练习题
      • if-else
          • bool类型
          • float和0比较
          • 指针变量和0比较
            • 强制类型转化

  1. 在win中,双击的本质就是运行程序,将程序加载到内存中。
  2. 在任何程序在被运行之前都必须加载到内存中。
  3. 程序没有被加载的时候,在硬盘当中
  4. 为什么要加载到内存中?因为快!
定义和声明

定义:开辟空间,只能有一次.

声明:是告知,可以有多次.

变量

  • 变量是什么?

内存中开辟特定大小的空间,用来保存数据。

比如在main函数中定义的临时变量,在程序运行之前是没有被开辟空间的,是在程序运行时才能在栈上开辟的。而程序在运行之前必须被加载到内存中,所以所有的变量本质都是在内存的某个位置开辟空间的,也就解释了为什么是在内存中开辟的一块空间存储数据.

  • 为什么要定义变量?

因为CPU计算处理内存中的数据,也是需要一步一步来的,将磁盘中的数据加载到内存中也是一块一块放的,所以数据需要暂时保存在变量中,便于后续系统处理.

  • 怎么定义变量?
int main()
{
    int a=10;//初始化(与生俱来)
    int b;
    b=10;//赋值(给已经开辟好空间的变量叫赋值,后天努力)
    return 0;
}
声明周期vs作用域

生命周期:描述生存时间长短,[定义,释放]

作用域:有效作用大小范围.变量名冲突:就近优先

局部变量vs全局变量

代码块{}中定义的变量,在函数栈空中开辟的.auto只能修饰局部变量auto int b=10;,区别于C++中的自动类型推导.

register

存储分级

距离CPU越近的存储单元,效率越高,单价越贵.CPU访问数据的时候,以最小的成本,达到最高的效率.

寄存器存在的本质:在硬件层面上,提高计算机的运算效率,因为不需要从内存中读取数据,因为缓存到寄存器中了.

C-关键字(上)_第1张图片

register修饰变量

尽量将所修饰的变量放到CPU寄存器中,提高效率.但是被register修饰的变量是在寄存器中开辟空间进行存储的,所以是没有地址的,也无法取地址(是针对内存的).但是可以是写入修改值的,因为已经是寄存器变量.

什么变量可以采用register?

C-关键字(上)_第2张图片

static

多文件

首先:声明并没有开辟空间,=100叫做赋值或者初始化,所有的变量声明的时候都不能设置初始值.

  • 为什么会对show()未声明而报警,但是仍然可以运行?

编译器编译时分别对两个源文件编译,由于互相看不到对方的数据所以会报警.但是在最后一步链接的时候,会将两个源文件形成的可执行文件整合为一个,然后根据函数地址找到对应函数,此时是能够看到的所以可以执行.

C-关键字(上)_第3张图片

main源文件想要使用test源文件中的变量和函数,需要对于变量进行一次声明才能使用,那么如果想要很多源文件中的变量,对于大型的项目就会很麻烦,所以我们把这些函数和变量的声明放到一个文件中整合,然后main.c再包含就完美了,所以产生了头文件.h文件.

C-关键字(上)_第4张图片

  • 头文件中#pragma once是为了避免头文件重复包含的问题
  • 在头文件中声明变量必须加上extern,因为存在二义性,会被认为是初始化了.函数声明建议加上extern,因为函数的定义是由函数体决定的.怎么定义的就怎么声明,把函数的参数都带齐全了,不带会存在警告.
  • 函数和全局变量是可以跨文件访问的.如果不想让全局变量被跨文件访问呢?
    • 在定义时+static
static修饰
  • static修饰全局变量,该变量只能在本文件内被访问,不能被其他文件直接访问.

  • 如果函数被static修饰,该函数只能在本文件内访问.会出现链接时报错.

  • static修饰局部变量,更改局部变量的生命周期,把临时的改成全局的.但是作用域仍然没有变,还是在局部代码块中.初始化的动作,只会初始化一次.

//函数调用开辟空间并初始化
//函数结束释放空间
//临时变量i每次都被释放然后重新设置为0
//+static int i=0 
static void func()
{
	//int i = 0;
	//i++;
	//printf("%d\n",i);//1111111111
	static int i = 0;
	i++;
	printf("%d\n",i);//12345678910
}
int main()
{
	for (int i = 0; i < 10; i++)
	{
		func();
	}
	//show();
	//printf("val= %d\n",val);
	return 0;
}
  • 为什么C语言全局变量和函数是需要跨文件访问的?

大型项目多个文件之间需要进行多个文件交互.如果不能跨文件,成本就会增加.所以C语言默认支持跨文件,但是总有些需要隐藏的数据,所以用static不让其他文件看到.

static+临时对象->全局性

  • C为什么临时变量具有临时性?全局变量具有全局性?

被static修饰之后,变量存储位置从栈区变为全局数据区,使得声明周期发生改变.

C-关键字(上)_第5张图片

  • C程序地址空间是内存吗?不是(是OS给进程画的饼,虚拟空间)

C基本数据类型

C-关键字(上)_第6张图片

sizeof

sizeof()求内置类型大小

确定一种类型对应开辟空间的大小.

C-关键字(上)_第7张图片

  • 验证sizeof是操作符
int main()
{
	int a = 10;
	printf("%d\n", sizeof a);//基本事实足以说明sizeof 是关键字或者操作符不是函数
	printf("%d\n", sizeof (a));
	printf("%d\n", sizeof (int));
	return 0;
}
  • C 为何要有类型:本质是对内存合理划分,按需索取内存空间.
  • 类型为什么要有这么多种?场景不同决定计算方式不同,开辟空间大小.用最小的成本解决各种多样化的场景.
sizeof()求自定义类型大小
int main()
{
	int* p = NULL;
	int arr[10];
	int* test[10];
	int(*test1)[10];
	printf("%d\n",sizeof(p));
	printf("%d\n",sizeof(arr));
	printf("%d\n",sizeof(test));
	printf("%d\n",sizeof(test1));
}
变量为什么要初始化?(面试题)

Linux环境的gcc编译器分配是0.vs环境会编译失败.理论上是不初始化就会被分配一个随机值.

存数据根据你提供的初始化的数据转化为补码然后放到任意类型声明的空间中.

读数据根据空间类型决定是否看最高位以及是否转化补码,如果是随机值,可能存在类型和数据不匹配的情况,造成误会.比如unsigned int b = -10;无符号类型存了一个有符号数,存可能没什么,但是读取的时候可能会出现问题.

  • vs环境会自动把小数看成是double类型
	float s = 3.14f;//不加f会发生截断
	double d = 3.14;

signed unsigned

数据存取

  • 数据在所开辟内存中到底是如何存储的呢?
    • 无符号数:没有符号,则原反补相同,直接存就行.
    • 有符号数

C-关键字(上)_第8张图片

  • 整形存储的本质

b提供空间,你只需要将二进制序列放进来就行,具体是什么没人关心.在将数据保存在空间b之前已经被转化为二进制了.

你要保存的数据形式是原反补那种类型是由初始化时的正负号决定的,b只是提供了空间,和b的数据类型是无关的.放的时候左右两边的类型是不重要的,因为二进制早就转化完了.

C-关键字(上)_第9张图片

  • 那么这里的变量什么时候起效果?

单纯的一串数字数据是没有意义的,数据是需要带上类型才是有意义的.所以是在读取的时候存在意义,类型决定如何解释空间内部保存的二进制数字.(大小端存储和读取导致)

C-关键字(上)_第10张图片

结论:

  • 变量存:字面数据必须先转成补码,在放入空间当中。所以,所谓符号位,完全看数据本身是否携带±号。和变量是否有符号无关!

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

大小端

C-关键字(上)_第11张图片

  • 小端如何存?

C-关键字(上)_第12张图片

  • 小端如何取?

    • 先看大小端
    • 再看自身类型
    • 如果是有符号数,再读符号位,需要将补码->原码

数据范围

有符号数char类型是8bit,数据范围是[-128,127].short[-2^15,2^15-1] int[-2^32,2^32-1]

C-关键字(上)_第13张图片

C-关键字(上)_第14张图片

练习题
  1. strlen()

答案是255长度,因为遇到0就结束了.

a[0]=-1 a[127]=-128 a[128]=127 a[255]=0

[0~255] 应该是256个数,最后一个数是0,strlen在遇到0时自动停止,只会记录前面的255个数.也就是说字符串中0不是数据长度之一,只是分割标识符而已,但是占用一个字节要注意.

C-关键字(上)_第15张图片

  1. 类型决定我们如何取解释内存中二进制的序列的含义.

C-关键字(上)_第16张图片

  1. 无符号类型数死循环问题

C-关键字(上)_第17张图片

  • 初始化时推荐

C-关键字(上)_第18张图片

if-else

C语言中0为假,非0为真(1 2 -1).

  1. 先执行()中的表达式or函数,得到真假结果(true false逻辑结果 )
  2. 根据真假结果进行条件判定
  3. 根据判定结果进行分支功能
bool类型

C语言中是没有bool false true类型的.C89 C90(主流)是没有的,C99是有_Bool类型的.为了C++的兼容性,提供头文件#include,占用1个字节.

大写的BOOL TRUE FALSE是微软提供的标准,只能在vs系列编过,不能跨平台使用,可移植性差.

	bool flag = 0;
	if (flag == 0)//不推荐,和int a==0表达式容易混淆
	{
		printf("1\n");
	}
	if (flag == false)//不推荐,只有包含了头文件才行C99
	{
		printf("2\n");

	}
	if (!flag)//推荐,直接进行判定功能(跳过第一步的计算表达式结果)
	{
		printf("3\n");
	}
float和0比较
  • 浮点数在内存中存储,并不是完整保存的,在十进制转化为二进制的过程中是有可能存在精度损失的.损失不是一味地减少,还有可能是增多,浮点数本身是存在算不尽的情况的,所以会存在四舍五入之类的.

  • 浮点数在进行比较的时候不能直接用==进行比较判断,因为存在精度损失.

C-关键字(上)_第19张图片

  • 限定范围进行比较,系统#include提供:DBL_EPSILON double最小精度

C-关键字(上)_第20张图片

  • 和0比较,只需要x的精度是在最小精度DBL_EPSILON范围之内就行.
  • 要不要+=DBL_EPSILON呢?别加
#include
int main()
{
	double d = 3.6;
	if (fabs(d - 3.6) < DBL_EPSILON)
	{
		printf("You can see me!\n");
	}
	//double类型和0进行比较
	double a = 0.00000000000000001;
	if (fabs(a) < DBL_EPSILON)
	{
		printf("a == 0\n");
	}
	return 0;
}
指针变量和0比较

在数值上,这三个值是相同的,都是0.当’\0’->'0’时,打印的是字符0的ASCII码值48.

C-关键字(上)_第21张图片

强制类型转化

强制类型转化并不改变内存中的二进制组合,而是改变读取他的形式类型.

真实的转化是真的改变内存中的数据.

所以,其实NULL的底层就是0,只是为了给用户更好的区分好看.

	int* p = NULL;
	if (p == 0)//误会是Int类型
	{
	}
	if (!p)//误以为是bool类型
	{
	}
	if (NULL == p)//推荐,立马能看出来p是指针,然后避免赋值=,所以放前面
	{
	}

你可能感兴趣的:(C/C++,c语言)