玉兰养生馆 發表於 2025-11-10 18:22:00

C语言中的数据存储

<h1 id="1数据类型">1.数据类型</h1>
<p>c语言自带的一些数据类型</p>
<pre><code>char      //字符型       1byte
short   //短整型       2byte
int       //整型         4byte
long      //长整型       4 or 8byte
long long //更长的整型    8byte
float   //单精度浮点数4byte
double    //双精度浮点数8byte
</code></pre>
<p>long在不同环境中字节大小不同(比如VS中是4byte)</p>
<h4 id="11类型的分类">1.1类型的分类</h4>
<p><strong>整型</strong></p>
<pre><code>char
        unsigned char
        signed char
short
        unsigned short
        signed short
int
        unsigned int
        signed int
long
        unsigned long
        signed long
long long
        unsigned long long
        signed long long
</code></pre>
<p>字符在内存中存放的是ASCII码值,所以也算作整型<br>
在写代码的时候,不说明类型是否有符号,默认为signed,char类型除外,它的默认取决于编译器<br>
如果在定义时,定义的为signed 整型,那么其在<strong>内存中存储的n位二进制的最高位为符号位,符号位为1表示负数,符号位为0表示正数</strong>,对于无符号数,最高位也是计数位,所以计数范围更大<br>
<strong>浮点型</strong></p>
<pre><code>float
double
long double
</code></pre>
<p><strong>构造类型</strong>(自定义类型)</p>
<pre><code>数组类型
结构体类型 struct
枚举类型enum
联合类型union
</code></pre>
<p><strong>指针类型</strong></p>
<pre><code>int* p1
void* p2
float* p3
</code></pre>
<p><strong>空类型</strong></p>
<pre><code>void
</code></pre>
<p>常见于函数传参,函数返回值类型,指针类型</p>
<h1 id="2整型在内存中的存储">2.整型在内存中的存储</h1>
<p>整型的取值范围在头文件&lt;limits.h&gt;中定义</p>
<p>整数的二进制表示形式有3种:<br>
<strong>原码:整数的二进制形式</strong>(多少位由类型所占多少字节空间决定)<br>
<strong>反码:原码符号位不变,其他位按位取反</strong><br>
<strong>补码:反码加一</strong><br>
<strong>正整数</strong>的原码,反码,补码相同,都等于原码<br>
<strong>负整数</strong>的三种形式才需要按上述方式计算</p>
<pre><code>int a = -5
//原码:10000000 00000000 00000000 00000101
        0x80 00 00 05
//反码:11111111 11111111 11111111 11111010
        oxff ff ff fa
//补码:11111111 11111111 11111111 11111011
        0xff ff ff fb
要注意符号位不变
</code></pre>
<p><strong>内存中存放整数时存放的其二进制补码</strong><br>
事实上,原码和补码互换的运算逻辑是一致的,补码等于原码取反加一,而原码也等于反码取反加一(可见如今的计算机能有今天,要历经多少天才的发明)</p>
<h4 id="22大小端">2.2大小端</h4>
<p>我们在编译器中看内存窗口时,会看到数据在内存按地址的存放顺序,那数据在内存中存放的逻辑是怎样的呢?<br>
有下面两种存放方式<br>
字节序:字节数据在内存中的存放顺序<br>
<strong>大端字节序存储</strong>:指将高位字节存储在内存的低地址,而低位字节存储在高地址<br>
<strong>小端字节序存储</strong>:指将低位字节存储在内存的低地址,而高位字节存储在高地址</p>
<h4 id="23整型提升">2.3整型提升</h4>
<p>整型在存放和计算过程中还会涉及到整型提升和截断,可以看我另一篇博客</p>
<h1 id="3-浮点数在内存中的存储">3. 浮点数在内存中的存储</h1>
<p>浮点数的表示范围在头文件&lt;float.h&gt;中定义</p>
<p>如果想明白这个问题,我们必须清楚小数的概念</p>
<h4 id="31二进制小数">3.1二进制小数</h4>
<p>所有的实数按照能否用分数表示分为两大类<br>
1.有理数(整数,分数)<br>
2.无理数(不能表示成任何分数形式的数)<br>
而小数,是实数的一种位值制的书写形式,而不同进制决定了这种书写的具体规则,本质上是用整数部分+小数部分的统一格式表示<br>
你可能会问,说这么复杂,和标题有什么关系?<br>
我们来分析一下小数的书写规则<br>
<strong>在十进制中(基数为10)</strong><br>
小数点的左边分别为10<sup>0</sup>(个位) ,10<sup>1</sup>(十位),10<sup>2</sup>(百位)....小数点右边为10<sup>-1</sup>(十分位),10<sup>-2</sup>(百分位),10<sup>-3</sup>(千分位)....<br>
小数点每左移一位,数值<em>10<sup>-1</sup><br>
<strong>在二进制中(基数为2)</strong><br>
小数点的左边分别为2<sup>0</sup>(个位),2<sup>1</sup>(二位),2<sup>2</sup>(四位)...,小数点右边为2<sup>-1</sup>(1/2),2<sup>-2</sup>(1/4),2<sup>-3</sup>(1/8)....<br>
小数点每左移一位,数值</em>2<sup>-1</sup><br>
<strong>在16进制中(基数为16)</strong><br>
小数点的左边分别为16<sup>0</sup>(个位),16<sup>1</sup>(16位),16<sup>2</sup>(256位)...,小数点右边为16<sup>-1</sup>(1/16),16<sup>-2</sup>(1/256),16<sup>-3</sup>(1/4096)....<br>
小数点每左移一位,数值*16<sup>-1</sup><br>
。。。。。。<br>
<strong>任何一种进制都有其自己的整数部分和小数部分的表示形式</strong>,无论是有理数还是无理数,而分数就是分数,不受进制的影响</p>
<p>想必你现在已经明白小数的定义了,还理解了二进制小数部分的运算规则</p>
<h4 id="32不同进制间的小数表示">3.2不同进制间的小数表示</h4>
<p>我们知道,不同进制之间的整数部分可以相互转换,小数部分也是可以的<br>
但对于小数部分存在着转换的限制<br>
比如十进制小数转化为二进制<br>
<strong>一个分数能写成有限小数,在不同进制中条件是不同的</strong><br>
在十进制,分数能写成有限小数,只有一个条件,把分母化成最简形式后,分母的质因数(既是质数,又是某数的因数)只能是2或5<br>
1/2 = 0.5<br>
1/5 = 0.2<br>
1/10 = 0.1<br>
但1/3,1/7不能化为十进制的有限小数<br>
在二进制中,分数能写成有限小数,最简分母的质因数只有2<br>
1/2 = 0.1<br>
1/4 = 0.01<br>
1/8 = 0.001<br>
你会发现1/10(质因数包含2和5),在二进制中无法精准表示,这也解释了为什么有些十进制小数无法用二进制来精确表示(0.1就不行)这是因为二进制小数的分母的质因数只有2,没有5,所以他不能表示十进制中最简分母质因数包含2和5的小数<br>
其他进制也是同理,当然不同进制间的小数转换,这是个复杂的问题,甚至包含有限小数转无限小数,这里只探讨十进制和二进制,这也是主题要用到的</p>
<h4 id="33二进制小数的别样表示">3.3二进制小数的别样表示</h4>
<p>我们给出一个十进制浮点数10.5<br>
转换为二进制是1010.1<br>
它可以写成1.0101*2<sup>3</sup>(类似于科学计数法就比如123.456可以写成1.23456 *10<sup>2</sup>)<br>
还可以写成(-1)<sup>0</sup> *1.0101 *2<sup>3</sup><br>
我们发现,对于任意的一个二进制浮点数(不考虑不能写成二进制小数的十进制小数)都可以写成<br>
v=(-1)^S *M *2<sup>E</sup><br>
这里v代表浮点数,S代表符号位,M代表有效数字,E代表指数位<br>
当S为1时表示负数,S为0时表示正数<br>
E控制小数点的位置<br>
M是存储的有效部分,整数部分不存储,读取时默认为1,只存小数部分(二进制使用这种计数法,整数位只可能是1)<br>
既然任何一种二进制浮点数都可以表示成这种形式,那么浮点数在内存中的存储只需存储3个变量S,E,M</p>
<h4 id="34浮点数的存储">3.4浮点数的存储</h4>
<p>对于32位的浮点数IEEE规定<br>
S占1bit(最高位)<br>
E占8bit(中间)<br>
M占23bit(最后)<br>
<img src="https://img2024.cnblogs.com/blog/3723376/202511/3723376-20251110171427849-478294410.jpg"></p>
<p>对于64位的浮点数来说<br>
S占1bit<br>
E占11bit<br>
M占52bit</p>
<p>对于M,IEEE754规定,在计算机保存M时,默认这个数的第一位总是1,因此舍去,只保留后面的小数部分,当读取的时候,再把1加上<br>
对于E,首先其存储时是unsigned int,但实际情况中E有可能是负数,所以IEEE754规定,在存储E的时候要加上一个偏移量,把计算的结果存进去,单精度的是127,双精度的是1023<br>
比如我要存-1进去,实际存放的E是126<br>
存储的位数不够32位或64,在后面补0<br>
5.5f<br>
101.1<br>
(-1)<sup>0</sup> *1.011 *2<sup>2</sup><br>
s = 0,M =1.011 e=2 (129)<br>
32位 0 10000001 011 00000000000000000000<br>
0x40 b0 00 00(显示成16进制)<br>
这就是5.5在float类型变量中的存储</p>
<h4 id="35浮点数取出">3.5浮点数取出</h4>
<h6 id="当e不为全0或者不全为1">当E不为全0或者不全为1</h6>
<p>把e存储的值减去偏移量,再把m拿出来最前面补上1,再按上面的计算公式进行计算</p>
<h6 id="当e为全0">当E为全0</h6>
<p>把e直接看作1-127或1-1023,m不再补1,而是补0看作0.xxxxx的小数<br>
这样做是为了表示接近于0的小数和正负0</p>
<h6 id="当e为全1">当E为全1</h6>
<p>如果m全为0,表示正负无穷大</p><br><br>
来源:https://www.cnblogs.com/827-s/p/19203063
頁: [1]
查看完整版本: C语言中的数据存储