1. 數(shù)據(jù)類型詳細(xì)介紹
到目前為止,我們已經(jīng)掌握了C語言的基本內(nèi)置類型,如下:
char //字符數(shù)據(jù)類型 (1 byte)
short //短整型 (2 byte)
int //整形 (4 byte)
long //長整型 (4/8 byte)
long long //更長的整形 (8 byte)
float //單精度浮點(diǎn)數(shù) ( 4byte)
double //雙精度浮點(diǎn)數(shù) (8 byte)
每一種數(shù)據(jù)類型的大小不同,這也就決定了它所存儲(chǔ)的數(shù)據(jù)范圍也就不同,就比如char和int所存儲(chǔ)的數(shù)據(jù)范圍就不同,那么具體能存儲(chǔ)多少呢?相信大家看完本本章內(nèi)容,就能對(duì)每一種數(shù)據(jù)是怎么存儲(chǔ)在內(nèi)存中的,就會(huì)有了更加深刻的認(rèn)識(shí)。
首先,在C語言里我們把類型分為以下幾種:1、整數(shù)類型 2、浮點(diǎn)型
3、構(gòu)造類型(自定義類型) 4、指針類型 5、空類型
整數(shù)類形
//unsigned:無符號(hào)類型 signed:有符號(hào)類型
char
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]
在這里,unsigned是表示無符號(hào)類型,signed表示有符號(hào)類型,所謂無符號(hào)類型就是這個(gè)數(shù)沒有負(fù)數(shù),只有正數(shù)和0,而有符號(hào)就表示這個(gè)數(shù)有正有負(fù),大家來看這么一個(gè)例子:
從這里就可以看出,這里的c明明賦值為-1,但是存儲(chǔ)的卻顯示出一個(gè)很大的正數(shù),這是因?yàn)閏是一個(gè)unsigned類型的整數(shù),而這里,我們平常書寫的一些int,short、char,這其實(shí)都是signed int、signed short、signed char,只不過signed都被省略了。
浮點(diǎn)型
float
double
具體區(qū)別會(huì)在后面講到
構(gòu)造類型
構(gòu)造類型又叫自定義類型,主要包括:數(shù)組、結(jié)構(gòu)體類型(struct)、枚舉類型(enum)、聯(lián)合類型(union)
#include<stdio.h>
//結(jié)構(gòu)體
struct book
{
char name[20];
int price;
};
//枚舉enum即enumerate的縮寫,意思就是列舉
enum color { red = 1,blue = 2 };
int main()
{
//數(shù)組
int arr[] = { 1,2,3,4,5 };
struct book str = { "C語言程序設(shè)計(jì)",50 };
printf("%s %d\n",str.name,str.price);
printf("%d %d\n", red, blue);
return 0;
}
這里要注意一點(diǎn),就是結(jié)構(gòu)體成員訪問時(shí),結(jié)構(gòu)體變量名.結(jié)構(gòu)體成員、結(jié)構(gòu)體變量地址(指針)->結(jié)構(gòu)體成員。
指針類型
int* p1;
char* p2;
float* p3;
void* p4;
這里需要注意一點(diǎn),就是void*,它可以接受任意類型的指針,就像一個(gè)垃圾桶一般,char*,int*,short*等都可以接受,但是,正是因?yàn)樯抖伎梢越邮眨盟荒苤苯咏庖?,或者進(jìn)行指針的運(yùn)算,畢竟我們不確定到底接受的是幾個(gè)字節(jié)。
如果想要對(duì)void*類型進(jìn)行解引用或者運(yùn)算的話,必須先強(qiáng)制類型轉(zhuǎn)換,才可以使用,如下:
#include<stdio.h>
int main()
{
int a = 10;
void* p = &a;
//強(qiáng)制類型轉(zhuǎn)換為int*類型,再解引用
*(int*)p = 50;
printf("%d", a);
return 0;
}
空類型
空類型經(jīng)常用到函數(shù)的返回類型以及函數(shù)參數(shù)中
#include<stdio.h>
//這里的返回類型以及參數(shù)都是void空類型
void test(void)
{
printf("123");
}
int main()
{
test();
return 0;
}
了解這些內(nèi)容后,接下來開始講解整形在內(nèi)存中是如何存儲(chǔ)的
2. 整形在內(nèi)存中的存儲(chǔ)
我們知道,任何變量的創(chuàng)建都需要在內(nèi)存中開辟一塊空間,空間的大小是由它們的類型決定,那么,這些數(shù)據(jù)是如何在內(nèi)存中存儲(chǔ)的呢?且聽以下講解
首先我們要先了解到原碼、反碼、補(bǔ)碼
原碼、反碼、補(bǔ)碼
計(jì)算機(jī)中的整數(shù)有三種2進(jìn)制表示方法,即原碼、反碼和補(bǔ)碼。
三種表示方法均有符號(hào)位和數(shù)值位兩部分,符號(hào)位都是用0表示“正”,用1表示“負(fù)”,正數(shù)的原、反、補(bǔ)碼都相同。
負(fù)整數(shù)的三種表示方法各不相同。
原碼
直接將數(shù)值按照正負(fù)數(shù)的形式翻譯成二進(jìn)制就可以得到原碼。
反碼
原碼符號(hào)位不變,其余取反得到反碼
補(bǔ)碼
反碼+1得到補(bǔ)碼
整形存儲(chǔ)補(bǔ)碼的原因
對(duì)于整形來說:數(shù)據(jù)存放內(nèi)存中其實(shí)存放的是補(bǔ)碼。
因?yàn)槭褂醚a(bǔ)碼,可以將符號(hào)位和數(shù)值域統(tǒng)一處理,同時(shí),加法和減法也可以統(tǒng)一處理(CPU只有加法器)此外,補(bǔ)碼與原碼相互轉(zhuǎn)換,其運(yùn)算過程是相同的,不需要額外的硬件電路。
具體如下圖:
我們可以看到,如果存放的是原碼,計(jì)算的結(jié)果會(huì)有很大偏差,更別說反碼了。
為什么說補(bǔ)碼與原碼相互轉(zhuǎn)換,其運(yùn)算過程是相同的呢?
所以,整形在內(nèi)存中存儲(chǔ)的是補(bǔ)碼
3. 大小端字節(jié)序介紹及判斷
我們知道,整形在內(nèi)存中存儲(chǔ)的是補(bǔ)碼,大家再來看,假如要存儲(chǔ)-10
#include<stdio.h>
int main()
{
int a = -10;
return 0;
}
然而通過調(diào)試我們發(fā)現(xiàn),存儲(chǔ)的是f6 ff ff ff,這是為什么呢?這里就涉及到了大小端字節(jié)序的存儲(chǔ)
大端(存儲(chǔ))模式,是指數(shù)據(jù)的低位保存在內(nèi)存的高地址中,而數(shù)據(jù)的高位,保存在內(nèi)存的低地址
中;
小端(存儲(chǔ))模式,是指數(shù)據(jù)的低位保存在內(nèi)存的低地址中,而數(shù)據(jù)的高位,,保存在內(nèi)存的高地
址中。
大家可以看到,這里vs的存儲(chǔ)模式就是小端存儲(chǔ),因?yàn)樗训臀坏臄?shù)據(jù)存儲(chǔ)到內(nèi)存的低地址,把高位存儲(chǔ)在高地址。這里萬萬不可寫成6f ff ff ff ,因?yàn)閒 6占一個(gè)字節(jié)。
練習(xí)題(含筆試題)
習(xí)題1:
百度2015年系統(tǒng)工程師筆試題:
請(qǐng)簡述大端字節(jié)序和小端字節(jié)序的概念,設(shè)計(jì)一個(gè)小程序來判斷當(dāng)前機(jī)器的字節(jié)序。
思路:這里我們只需要知道它的低地址處存儲(chǔ)的是不是低位的數(shù)據(jù),就可以判斷是不是大小端了,就比如,如果是個(gè)int類型的數(shù)字,1,它的存儲(chǔ)的補(bǔ)碼為:00 00 00 01(16進(jìn)制方便調(diào)試觀察),低地址假如是01,就說明是小端,反之大端。完整答案如下:
小端字節(jié)序存儲(chǔ):把一個(gè)數(shù)值的低位字節(jié)內(nèi)容存放到內(nèi)存低地址處,高位字節(jié)內(nèi)容存放到內(nèi)存高地址處。
大端字節(jié)序存儲(chǔ):把一個(gè)數(shù)值的低位字節(jié)內(nèi)容存放到內(nèi)存到高地址處,高位字節(jié)內(nèi)容存放到內(nèi)存低地址處。
小程序如下:
#include <stdio.h>
int check_sys()
{
int i = 1;
//把i地址強(qiáng)制類型轉(zhuǎn)換為char*(解引用只能訪問1個(gè)字節(jié)),因?yàn)槲覀冎恍枰吹偷刂返淖止?jié)存儲(chǔ),然后解引用指向第一個(gè)字節(jié)存儲(chǔ)的內(nèi)容,
return (*(char*)&i);
}
int main()
{
//判斷
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
習(xí)題2:
以下代碼輸出什么?
#include <stdio.h>
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;
}
且看分析:
重點(diǎn)來分析這個(gè)unsigned char c
所以,最終的結(jié)果為-1 -1 255
習(xí)題3:
#include <stdio.h>
int main()
{
char a = -128;
printf(“%u\n”,a);
return 0;
}
%u:打印無符號(hào)整型,認(rèn)為內(nèi)存中存放的補(bǔ)碼對(duì)應(yīng)的是一個(gè)無符號(hào)數(shù)。
%d:打印有符號(hào)整型,認(rèn)為內(nèi)存中存放的補(bǔ)碼對(duì)應(yīng)的是一個(gè)有符號(hào)數(shù)。
-128存儲(chǔ)在內(nèi)存中的補(bǔ)碼為1000 0000(截?cái)?,打印無符號(hào)整數(shù)時(shí),整型提升,char為有符號(hào)數(shù),高位補(bǔ)符號(hào)位,即11111111 11111111 11111111 10000000,由于是打印無符號(hào)的整型,所以就是打印這個(gè)數(shù)對(duì)應(yīng)的十進(jìn)制
我們用計(jì)算器,可以算出,這個(gè)數(shù)對(duì)應(yīng)的十進(jìn)制是4294967168,打印結(jié)果是否如我們所想呢?
我們發(fā)現(xiàn),確實(shí)如此。
習(xí)題3:
#include <stdio.h>
int main()
{
char a = 128;
printf(“%u\n”,a);
return 0;
}
這里的a在內(nèi)存中存儲(chǔ)的是1000 0000(截?cái)啵?,打印時(shí)高位補(bǔ)符號(hào)位,這里由于發(fā)生了截?cái)?,符?hào)位變成了1,所以整形提升后的補(bǔ)碼為:11111111 11111111 11111111 10000000,打印結(jié)果依然是那個(gè)很大的數(shù)字。
習(xí)題4:
int i= -20;
unsigned int j = 10;
printf(“%d\n”, i+j);
//按照補(bǔ)碼的形式進(jìn)行運(yùn)算,最后格式化成為有符號(hào)整數(shù)
習(xí)題5:
unsigned int i;
for(i = 9; i >= 0; i - -)
{
printf(“%u\n”,i);
}
這里,我們注意,i為無符號(hào)整型,前面正常打印,9 8 7 6 5 4 3 2 1 0,但是當(dāng)i=0的時(shí)候,執(zhí)行完打印命令后還會(huì)進(jìn)行i- -,i此時(shí)變成了-1,注意!i是無符號(hào)整形,-1對(duì)應(yīng)的無符號(hào)整數(shù)是一個(gè)很大的正數(shù),所以循環(huán)還會(huì)一直進(jìn)行下去,陷入死循環(huán)!
相信到這里,應(yīng)該對(duì)整形的存儲(chǔ)有了較為清晰的認(rèn)識(shí)。接下來講解以下浮點(diǎn)型是如何在內(nèi)存中存儲(chǔ)的。
4. 浮點(diǎn)型在內(nèi)存中的存儲(chǔ)
像float、double、long double…都屬于浮點(diǎn)型,接下來將探究浮點(diǎn)型是如何在內(nèi)存中存儲(chǔ)的。
先來看這樣一段代碼:
int main()
{
int n = 9;
float *pFloat = (float *)&n;
printf("n的值為:%d\n",n);
printf("*pFloat的值為:%f\n",*pFloat);
*pFloat = 9.0;
printf("num的值為:%d\n",n);
printf("*pFloat的值為:%f\n",*pFloat);
return 0;
}
看到這個(gè)結(jié)果,相信很多人和我一樣都很吃驚,那么為何會(huì)這樣呢?且聽以下講解
根據(jù)國際標(biāo)準(zhǔn)IEEE(電氣和電子工程協(xié)會(huì)) 754,任意一個(gè)二進(jìn)制浮點(diǎn)數(shù)V可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^S表示符號(hào)位,當(dāng)S=0,V為正數(shù);當(dāng)S=1,V為負(fù)數(shù)。
M表示有效數(shù)字,大于等于1,小于2。
2^E表示指數(shù)位。
具體是什么意思呢?
舉例來說:
十進(jìn)制的5.0,寫成二進(jìn)制是 101.0 ,相當(dāng)于 1.01×2^2 。
那么,按照上面V的格式,可以得出S=0,M=1.01,E=2。
十進(jìn)制的-5.0,寫成二進(jìn)制是 -101.0 ,相當(dāng)于 -1.01×2^2 。那么,S=1,M=1.01,E=2。
再舉個(gè)例子:
v=5.5
二進(jìn)制101.1 即1.011*2^2,S=0,M=1.011,E=2
IEEE 754規(guī)定:
對(duì)于32位的浮點(diǎn)數(shù),最高的1位是符號(hào)位s,接著的8位是指數(shù)E,剩下的23位為有效數(shù)字M。
什么意思呢?具體如下:
對(duì)于64位的浮點(diǎn)數(shù),最高的1位是符號(hào)位S,接著的11位是指數(shù)E,剩下的52位為有效數(shù)字M。
IEEE 754對(duì)有效數(shù)字M和指數(shù)E,還有一些特別規(guī)定。
前面說過, 1≤M<2 ,也就是說,M可以寫成 1.xxxxxx 的形式,其中xxxxxx表示小數(shù)部分。
IEEE 754規(guī)定,在計(jì)算機(jī)內(nèi)部保存M時(shí),默認(rèn)這個(gè)數(shù)的第一位總是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的時(shí)候,只保存01,等到讀取的時(shí)候,再把第一位的1加上去。這樣做的目的,是節(jié)省1位有效數(shù)字。以32位浮點(diǎn)數(shù)為例,留給M只有23位,將第一位的1舍去以后,等于可以保存24位有效數(shù)字。
舉個(gè)例子,也就是說,在存儲(chǔ)5.5即101.1時(shí),M=1.011,但是存儲(chǔ)時(shí)只存儲(chǔ)1后面的011,等讀取的時(shí)候,再給011加上1即可1.011
至于指數(shù)E,情況就比較復(fù)雜。
首先,E為一個(gè)無符號(hào)整數(shù)(unsigned int)
這意味著,如果E為8位,它的取值范圍為0-255;如果E為11位,它的取值范圍為0~2047。但是,我們=知道,科學(xué)計(jì)數(shù)法中的E是可以出現(xiàn)負(fù)數(shù)的,所以IEEE 754規(guī)定,存入內(nèi)存時(shí)E的真實(shí)值必須再加上一個(gè)中間數(shù),對(duì)于8位的E,這個(gè)中間數(shù)是127;對(duì)于11位的E,這個(gè)中間數(shù)是1023。比如,2^10的E是10,所以保存成32位浮點(diǎn)數(shù)時(shí),必須保存成10+127=137,即10001001。
舉個(gè)例子,假如存儲(chǔ)float 5.5,還有double -5.5,應(yīng)該這樣存儲(chǔ):
具體真的是這樣存儲(chǔ)的嗎,我們來驗(yàn)證一下,就拿上面的5.5來說,存儲(chǔ)的二進(jìn)制為:
0100 0000 1011 0000 0000 0000 0000 0000 轉(zhuǎn)換成16進(jìn)制即
40 b0 00 00
我們前面已經(jīng)知道,vs是小端存儲(chǔ),所以存儲(chǔ)的應(yīng)該是00 00 b0 40 ,我們通過調(diào)試來驗(yàn)證一下:
由此可見,確實(shí)如此!
那么,該如何讀取呢?指數(shù)E從內(nèi)存中取出還可以再分成三種情況:
1、E不是全0,也不是全1
讀取時(shí)先將E-127(1023),然后在前面有效數(shù)字M前面加個(gè)1
就比如讀取上面的0 10000001 011 0000 0000 0000 0000 0000
首先S=0,E-127得到2,然后再011前面加個(gè)1,即1.011
就可以寫成(-1)^01.0112 ^2 即1.011*2 ^2
也就是101.1,轉(zhuǎn)換成十進(jìn)制5.5
2、E全為0
這時(shí),浮點(diǎn)數(shù)的指數(shù)E等于1-127(或者1-1023)即為真實(shí)值,
有效數(shù)字M不再加上第一位的1,而是還原為0.xxxxxx的小數(shù)。這樣做是為了表示±0,以及接近于
0的很小的數(shù)字。
因?yàn)?127+127才等于0,也就是00000000,即E=-127,也就是說原本的數(shù)是寫成x.xxx*2^-127,這個(gè)數(shù)已經(jīng)非常非常非常非常小了,2的-127次方,無限趨近于0
3、E全為1
這時(shí),如果有效數(shù)字M全為0,表示±無窮大(正負(fù)取決于符號(hào)位s)
大家來想一下,128+127=255,也就是E全為1,即11111111,也就是說E=128,即原來的數(shù)是x.xx*2^128,這個(gè)數(shù)如果是正數(shù),就超級(jí)超級(jí)超級(jí)大,趨近于正無窮,如果是個(gè)負(fù)數(shù),就非常非常小,趨近于負(fù)無窮!
學(xué)完后我們回過頭來再來看那個(gè)題:
首先,n的二進(jìn)制數(shù)為:
00000000000000000000000000001001(原、反、補(bǔ))
然后第一個(gè)打印,%d打印出來9,沒毛病
第二個(gè)打印,這里pFloat指向的是n的地址,也就是這個(gè)二進(jìn)制數(shù),而以浮點(diǎn)型的視角來看待這塊二進(jìn)制數(shù),這里的0就表示S,00000000表示E,后面的表示M,
這里E為全0,在上面就講到了,當(dāng) E為全0時(shí)表示的是一個(gè)無限接近0的數(shù)字0.0000000000000000000000000000000…,所以打印出來的是0.00000000
后面pFloat=9.0,這里我們寫出它存儲(chǔ)的二進(jìn)制位
9.0二進(jìn)制:1001.0,即1.0012^3
S=0,M=1.001,E=3
所存儲(chǔ)的二進(jìn)制應(yīng)為:
0 10000010 001 00000000000000000000
而假如以%d形式打印,我們就要把這個(gè)二進(jìn)制數(shù)看成是補(bǔ)碼
而看成補(bǔ)碼的話,0符號(hào)位為正,原反補(bǔ)相同,所以對(duì)應(yīng)原碼為
01000001000100000000000000000000
而這個(gè)原碼對(duì)應(yīng)的十進(jìn)制數(shù)為:1,091,567,616
所以第三個(gè)打印為1,091,567,616
第四個(gè)打印是以%f打印,打印浮點(diǎn)型小數(shù),所以打印結(jié)果還是9.0
因此,輸出結(jié)果為:9
0.00000000
1,091,567,616
9.0
結(jié)果確實(shí)如此,以上為數(shù)據(jù)在內(nèi)存中的存儲(chǔ)詳解,期待您的支持!文章來源:http://www.zghlxwxcb.cn/news/detail-826752.html
望諸君加油,不負(fù)自己!文章來源地址http://www.zghlxwxcb.cn/news/detail-826752.html
到了這里,關(guān)于【C語言進(jìn)階】——深入剖析數(shù)據(jù)在內(nèi)存中的存儲(chǔ)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!