文章目录

  • 1️⃣数据类型详细介绍
    • 基本内置类型
    • 1.1数据类型的基本归类
      • ① 整形家族
      • ②浮点数家族
      • ③结构体家族
      • ④指针类型
      • ⑤ 空类型
      • 1.2整形在内存中的存储
  • 2️⃣2.1 原码、反码、补码
    • 2.2大小端介绍
      • ①什么是大小端?
      • ②为什么会有大小端?(本节不感兴趣可以跳过)
      • 百度2015年系统工程师笔试题:
  • 3️⃣实战练习Practice

1️⃣数据类型详细介绍

基本内置类型

char//1个字节short//2个字节int //4个字节long//4个字节,C语言规定long >=int即可long long //8个字节float//4个字节double//8个字节

1.1数据类型的基本归类

① 整形家族

>字符型 charunsigned charsigned char>短整型 short unsigned short signed short>整型 int unsigned int signed int >长整型 long unsigned long signed long//注意以下都是在C99标准中才被引进的>长整形 long long unsigned long long signed long long

为什么char也归于整形家族呢?因为char是字符类型,字符在内存中存储是按ASCII码值存储的。(注意:C99标准并未指定 char 类型是有符号还是无符号,这取决实现(编译器和硬件平台),不过常见的编译器下的char是signed char。具体可以通过limits.h头文件中 CHAR_MIN 的值来确认,如果 CHAR_MIN 为 0 则说明 char 类型被当作无符号整)
(float,double ,long double 浮点型表示范围可以通过float.h查看)

②浮点数家族

>单精度实型float>双精度实点型double

③结构体家族

>数组类型[]>结构体类型struct>枚举类型enum>联合体类型union

④指针类型

int* p //整形指针char* p //字符型指针float* p //单精度浮点型指针void* p// 空类型(无类型)指针

居然数组也是构造类型! 举个栗子~看下例:

int arr[10];char arr2[5];//arr arr2的类型是int [10] 和 char [5];
  • 因为数组的元素个数我们可以自己定义,数组类型我们也可以自己定义

⑤ 空类型

void 表示 空类型(5无类型)通常用作函数返回类型、函数参数、指针类型。比如 void test(void) //表示该test函数不需要返回类型,不需要参数

1.2整形在内存中的存储

一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型决定的。
我们接下来就分析分析整形到底是怎么在内存中存储的?

比如下例代码:

int a = 20;int b = -10;

毫无疑问内存为a变量分配了4个字节空间用来存储20,那如何存储呢?请接着往下看…

2️⃣2.1 原码、反码、补码

计算机中的的整形3种2进制表示方法:原码、反码、补码
3种方法均有符号位和数值位两个部分。
符号位用:0表示“正”,1表示“负”;
而数值位:正数的原码、反码、补码都相同

注释:后面把原码、反码、补码,简写成 原、反、补码。

  • 对于正整数和无符号整数原、反、补码都相同

但是!但是!但是!负整数的3种表示方法各不相同。

原码:直接将数值按照正数的形式转换成二进制就可以得到原码。
反码:原码的符号位不变,其他位按位取反。
补码:反码+1得到补码。
从补码得到原码: 补码-1得到反码,反码的符号位不变,其他位按位取反。
unsigned 的类型,最高位不再是符号位,而是数值域!

对于整形来说:数据存放在内存中都是以补码的形式进行存储。
那为什么呢?

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

从补码得到原码,还有一种方法:对补码直接进行取反得到反码,反码再+1,得到的就是原码(这就是为什么说补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路)

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

  • 举个栗子 ~
    例如:1 -1 =? 因为系统只有加法器所以转换为1+(-1)再进行计算,如果数据在内存中存储的是原码,那么我们来看看:
    如果用原码进行计算,那么结果就出人意料了!这时,有人就说啦,那用补码计算看看,
//1的原、反、补码相同//00000000000000000000000000000001//-1的原码//10000000000000000000000000000001//-1的反码——>原码符号位不变,其他位其他位依次按位取反//11111111111111111111111111111110//-1的补码——>反码+1,得到补码//11111111111111111111111111111111//——————————————————————————————————————————————————————//1+(-1)用补码进行计算//00000000000000000000000000000001 ——>1的补码//11111111111111111111111111111111 ——>-1的补码//100000000000000000000000000000000 ——>结果多了一位//**实际上只能存32位bit,进的1位,存不下,于是就丢了,最终得到的是0**

让我们来看看整形在内存中的存储吧

咦?我们可以看到对于a和b分别在内存中存储的是补码,但是我们发现了不对劲(上面显示的是16进制表现形式,但是实际在内存中存的是二进制)
这又是为什么呢?

2.2大小端介绍

①什么是大小端?

  • 大端(存储)模式:是指数据的低位保存在内存的高地址处,而数据的高位保存在内存的低地址处。
  • 小端(存储)模式:是指数据的低位保存在内存的低地址处,而数据的高位保存在内存的高地址处。
    (大小端之分建立在存储字节在1个字节以上,一个字节不存在大小端之分)

例如:

②为什么会有大小端?(本节不感兴趣可以跳过)

(本节不感兴趣可以跳过)

  • 为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8个bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体编译器),另外,对于维数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
  • 例如:一个16bit的short型的a变量,
    (假设)在内存中的地址为0x0010,a的值为0x1122,那么0x11为高字节,0x22为低字节。对于大端模式,就将0x11放在低地址处,即0x0010中,0x22放在高地址处,即0x11中。小端模式,恰好相反。我们常用的x86结构是小端模式,而KEIL C51 则为大端。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

百度2015年系统工程师笔试题:

请简述大小端节序和小端节序的概念,设计一个小程序来判断当前机器的字节序 (10分)

  1. 解答:

1.概念:略(在上面已经讲解过啦,可以翻上去再看看)
2. 程序思路:定义一个int型变量a,并初始化为1,(16进制表现形式是0x00 00 00 01)
**如果当前机器是小端字节序存储模式,那么存储的方式从低地址到高地址就是01 00 00 00;如果是大端模式,从低地址到高地址就是00 00 00 01。所以只要判断01的存储位置就可以判断大小端。这里可以使用指针进行解引用操作,char*1次访问的字节是1个字节,不过变量a是int类型,&a要用int * p来存放,但是int *访问一次地址是4个字节,显然不行,所以要对&a进行强制类型转换为(char * ),这样进行解引用操作,一次就只访问一个字节了。

//代码1#include int main(){int a = 1;//16进制位0x00 00 00 01char* p = (char*)&a;if (*p)//如果是小端,*p为01 则判断条件为真。{printf("小端\n");}else{printf("大端\n");}return 0;}

上面这段代码方便大家容易理解,这段代码还可以优化

//代码2#include int inspect_small()//协议:小端返回1,大端返回0;{int a = 1;return *(char*)&a;}int main(){if (inspect_small())//如果是小端,函数返回值为1 则判断条件为真{printf("小端\n");}else{printf("大端\n");}return 0;}

3️⃣实战练习Practice

①练习题一:

判断下列代码输出结果:

1.//输出什么?#include int main(){char a= -1;signed char b=-1;unsigned char c=-1;printf("a=%d,b=%d,c=%d",a,b,c);return 0;}

输出结果:-1 ,-1 ,255
为什么c的输出结果不是你想的那样呢?下面让我们来看看为什么。

②练习题二:

2.下面程序输出什么?#include int main(){char a = -128;printf("%u\n",a);//输出4294967168return 0;}

③练习三:

3.#include int main(){char a = 128;printf("%u\n",a);return 0;}

这道题跟练习题一相似,就不做解释了。
④练习四:

4.int i= -20;unsignedintj = 10;printf("%d\n", i+j);//-10//按照补码的形式进行运算,最后格式化成为有符号整数


⑤练习五:

5.unsigned int i;//i是无符号类型,任何值都大于等于0for(i = 9; i >= 0; i--)//所以判断恒成立{printf("%u\n",i);//输出9 8 7 6 5 4 3 2 1 0 4294967295 4294967294..}

为什么呢?当i=-1时,以%u打印其实打印的是-1的补码
⑥练习六:

6.int main(){char a[1000];int i;for(i=0; i<1000; i++) {a[i] = -1-i;//-1 - 0 = -1..-2..-3..-4.. }//strlen()在求字符串长度时只要遇到‘\0’(ASCII码值是0)也就是遇到0就会停下来;printf("%d",strlen(a));//输出255——>127个元素+128个元素(负数)=255return 0;}

char 类型存储范围是-128~127


⑦练习题七:

7.#include unsigned char i = 0;//i的范围是0~255int main(){for(i = 0;i<=255;i++)//所以循坏条件恒成立 {printf("hello world\n");//死循坏打印 }return 0;}

强调:在使用unsigned 数时一定要谨慎使用,一不小心就是各种bug!

抛砖引玉:浮点数在内存中怎么存储的呢?我们可以在float.h头文件下查看数据范围。

看下列:

#include int main(){int n = 9;float* pFloat = (float*)&n;printf("n的值为:%d\n", n);//9printf("*pFloat的值为:%f\n", *pFloat);//0.000000 *pFloat = 9.0;printf("n的值为:%d\n", n);//1091567616printf("*pFloat的值为:%f\n", *pFloat);//9.000000return 0;}

输出结果和我们想的大不相同,这是为什么呢?
预知后事如何,请看下回分解~~~
如果你觉得文章不错,记得点赞+分享喔~如果有地方不对,欢迎随时评论指出 ~