C語言程序設計筆記---015
- C語言數據的存儲
- 1、數據類型的意義
- 1.1、unsigned與signed數據類型例程1
- 1.2、補碼與原碼相互轉換例程2
- 2、大小端的介紹
- 2.1、大小端的例程1
- 2.2、大小端的例程2 --- 判斷當前編譯器環境屬于大端或小端
- 3、綜合練習題探究數據的存儲
- 3.1、練習題1
- 3.2、練習題2
- 3.3、練習題3
- 3.4、練習題4
- 3.5、練習題5
- 3.6、練習題6
- 3.7、練習題7
- 4、浮點數在內存中的存儲
- 4.1、浮點數存儲規則
- 4.1.1、IEEE(電氣電子工程師協會) 擬定的754標準
- 4.1.2、另外IEE(電氣電子工程師協會)754對有效數字M和指數E,還有一些特定的規定
- 4.2、指數E的規定
- 4.2.1、指數E的存入規定
- 4.2.2、指數E的取出規定三種情況
- (1).E不全為0或不全為1
- (2).E全為0
- (3).E全為1
- 5、經典例子詳解
- (1)、**以整型的形式存儲,以浮點數的取出**:
- (2)、**以浮點數型的形式存儲,以整型的取出**:
- 6、結語
C語言數據的存儲
前言:
常見數據類型回顧
char
short
int
long
long long
float
double
那么有沒有字符串類型呢? ---- 沒有
字符在內存中存儲的是字符的ASCLL碼值,所以字符類型歸于整型家族
/知識點匯總/
1、數據類型的意義
使用對應類型內存空間的大小(大小決定了適用范圍)
unsigned ----- 無符號位
signed ----- 有符號位
值得注意的是。char — 默認為unsigned 還是 signed 由編譯器決定
計算機能夠處理的是二進制數據
整型和浮點型數據在內存中也是以二進制的形式進行存儲的
整型的二進制表示形式有三種:原碼、補碼、反碼
正整數:原碼、反碼、補碼相同
負整數:原碼、反碼、補碼需要計算;補碼等于原碼取反加1
最后不管是正整數還是負整數,在內存中的存儲都是補碼的二進制序列
1.1、unsigned與signed數據類型例程1
#include <stdio.h>
int main()
{int a = -10;//4個字節 --- 32bit位//1000 0000 0000 0000 0000 0000 0000 1010 ----- -10原碼//1111 1111 1111 1111 1111 1111 1111 0101 ----- -10反碼//1111 1111 1111 1111 1111 1111 1111 0110 ----- -10補碼//最高位是符號位(1負,0正)unsigned int b = -10;//1111 1111 1111 1111 1111 1111 1111 0110//此時的b,被unsigned int 修飾此時的最高位就不作為符號位了return 0;
}
小結:
對于整形:數據存放內存中其實放的是補碼
為什么呢?
因為,在計算機系統中,數值一律用補碼形式來表示和存儲。原因在于,使用補碼,可以將符號位與數據位統一處理。
同時,加法和減法也可以統一處理(CPU只有加法器)此外,補碼與原碼相互轉換,其運算過程是相同的,不需要額外的電路
1.2、補碼與原碼相互轉換例程2
#include <stdio.h>
int main()
{//1-1//1+(-1)//0000 0000 0000 0000 0000 0000 0000 0001 --- 1原碼//1000 0000 0000 0000 0000 0000 0000 0001 --- -1原碼//當用原碼計算時://1000 0000 0000 0000 0000 0000 0000 0010 --- 原碼1+(-1),發現達不到想要的結果//所以再來常識補碼相加//0000 0000 0000 0000 0000 0000 0000 0001 --- 1原碼、反碼、、補碼相等//1000 0000 0000 0000 0000 0000 0000 0001 --- -1原碼//1111 1111 1111 1111 1111 1111 1111 1110 --- -1反碼//1111 1111 1111 1111 1111 1111 1111 1111 --- -1補碼//補碼相加://0000 0000 0000 0000 0000 0000 0000 0001 --- 1補碼//1111 1111 1111 1111 1111 1111 1111 1111 --- -1補碼//1 0000 0000 0000 0000 0000 0000 0000 0000 --- 保留后面32位bit --- 0 return 0;
}
2、大小端的介紹
大端 — 大端字節序存儲
把一個數據的低位字節處的數據存放在內存的高位地址處,把一個數據的高位字節處的數據存放在內存的低位地址處。
小端 — 小端字節序存儲
把一個數據的低位字節處的數據存放在內存的低位地址處,把一個數據的高位字節處的數據存放在內存的高位地址處。
2.1、大小端的例程1
#include <stdio.h>
int main()
{int a = 0x11223344;//大小端的不同,存放的順序就不同//11 22 33 44 --- 大端存儲//44 33 22 11 --- 小端存儲printf("%p\n",&a);//當前VS2019屬于小端存儲return 0;
}
2.2、大小端的例程2 — 判斷當前編譯器環境屬于大端或小端
#include <stdio.h>
int check_sys()
{int a = 1;//只需判斷當前屬于小端還是大端,所以賦值變量a=1,判斷內存中的01的位置即可//char* p = (char*)&a;//判斷只需要一個字節,第一個字節即可if (*p == 1){ return 1;}else return 0;//return *p;return *(char*)&a;
}
int main()
{if (1 == check_sys()){printf("小端\n");}else{printf("大端\n");}return 0;
}
3、綜合練習題探究數據的存儲
3.1、練習題1
#include <stdio.h>
int main()
{char a = -1;//1000 0000 0000 0000 0000 0000 0000 0001 --- -1原碼//1111 1111 1111 1111 1111 1111 1111 1110 //1111 1111 1111 1111 1111 1111 1111 1111 ---- -1補碼//1111 1111 ---- char -1//因為要以%d格式打印,需要整型提升且以原碼打印//整型提升(以符號位提升)//1111 1111 1111 1111 1111 1111 1111 1111 --- 整型提升,此時任然為補碼//1000 0000 0000 0000 0000 0000 0000 0000//1000 0000 0000 0000 0000 0000 0000 0001 --- 原碼 == 補碼取反加1//以%d打印 --- -1signed char b = -1;//1111 1111 1111 1111 1111 1111 1111 1111 ---- -1補碼//1111 1111 ---- char -1補碼//整型提升(以符號位提升)//1111 1111 1111 1111 1111 1111 1111 1111 --- 整型提升,此時任然為補碼//1000 0000 0000 0000 0000 0000 0000 0000//1000 0000 0000 0000 0000 0000 0000 0001 --- 原碼 == 補碼取反加1//以%d打印 --- -1unsigned char c = -1;//1111 1111 1111 1111 1111 1111 1111 1111 ---- -1補碼//1111 1111 ---- char -1補碼 ---- %d --->255//整型提升(無符號位提升補0)//0000 0000 0000 0000 0000 0000 1111 1111 --- 整型提升,此時任然為補碼//無符號判定為正整數,原碼 = 反碼 = 補碼//以%d打印 --- 255printf("a = %d,b = %d,c = %d",a,b,c);//整形數據以補碼存儲//%d 是以10進制得形式打印有符號的整型數據,以原碼打印return 0;
}
3.2、練習題2
#include <stdio.h>
int main()
{char a = -128;//1000 0000 0000 0000 0000 0000 1000 0000 --- -128//1111 1111 1111 1111 1111 1111 0111 1111 //1111 1111 1111 1111 1111 1111 1000 0000 --- -128補碼//數據截斷://1000 0000 --- char -128補碼//整型提升://1111 1111 1111 1111 1111 1111 1000 0000 -128補碼//以%u打印,無符號數,所以://1111 1111 1111 1111 1111 1111 1000 0000 ----4294967168 原碼 = 補碼 = 反碼printf("%u\n",a);//4294967168return 0;
}
3.3、練習題3
#include <stdio.h>
int main()
{char a = 128;//0000 0000 0000 0000 0000 0000 1000 0000 --- 128 原碼//1111 1111 1111 1111 1111 1111 0111 1111 ---- 反碼//1111 1111 1111 1111 1111 1111 1000 0000 --- -128補碼//數據截斷://1000 0000 --- char -128補碼//整型提升://1111 1111 1111 1111 1111 1111 1000 0000 補碼//以%u打印,無符號數,所以://1111 1111 1111 1111 1111 1111 1000 0000 ----4294967168 原碼 = 補碼 = 反碼printf("%u\n",a);//4294967168return 0;
}
小結:
char類型數據范圍:-128~127
char – 假設是有符號的char — signed char
范圍就是:-128~127 單位字節,1個字節 = 8個bit
//0000 0000 ---- 0
//0000 0001 ---- 1
//0000 0010 ---- 2
//… …
//0111 1111 ---- 127
//1000 0000 ---- -128
//10000001 ---- -127
//1000 0010 ---- -126
//… …
//1111 1111 ---- -1
如圖所示:
注意:
首位依然代表符號位
內存中存的二進制序列
內存中存儲的補碼
char – 假設是無符號的char — usigned char
0~255
同理:都會數據截斷,將保存正確的字節
3.4、練習題4
#include <stdio.h>
int main()
{int i = -20;//1000 0000 0000 0000 0000 0000 0001 0100 ---- -20原碼//1111 1111 1111 1111 1111 1111 1110 1011 ---- -20反碼//1111 1111 1111 1111 1111 1111 1110 1100 ---- -20補碼(因為整形數據以補碼二進制序列保存)unsigned int j = 10;//0000 0000 0000 0000 0000 0000 0000 1010 ---- 10原碼、反碼、補碼相等printf("%d\n",i+j);//-10//1111 1111 1111 1111 1111 1111 1110 1100 ---- -20補碼//0000 0000 0000 0000 0000 0000 0000 1010 ---- 10原碼、反碼、補碼相等//1111 1111 1111 1111 1111 1111 1111 0110 ---- i+j 補碼//以%d,打印一個有符號的整數://1000 0000 0000 0000 0000 0000 0000 1001 --- 補碼取反//1000 0000 0000 0000 0000 0000 0000 1010 --- -10 補碼取反+1,以原碼打印return 0;
}
3.5、練習題5
#include <stdio.h>
int main()
{unsigned int i = 0;//unsigned int范圍0~255恒大于0與i >= 0,恒成立for (i = 9; i >= 0; i--){printf("%u\n",i);//死循環}return 0;
}
3.6、練習題6
#include <stdio.h>
#include <string.h>int main()
{char a[1000];int i = 0;for (i = 0; i < 1000; i++){a[i] = -1 - i;}printf("%d",strlen(a));//統計\0之前的個數//-1 -2 -3 -4 .... -128 127 126 125 ... 5 4 3 2 1 0結束 ---- 255個//因為char類型范圍是:-128 ~ 127,所以其他數都會被數據截斷以這個范圍的數保存return 0;
}
3.7、練習題7
#include <stdio.h>unsigned char i = 0;//unsigned char范圍0~255與i <= 255恒成立int main()
{for (i = 0; i <= 255; i++){printf("666\n");//死循環}return 0;
}
綜上所述:
當擴展到其他的整型數據類型同理可得:
short — 2個字節 — 16bit
signed short范圍:-32678~32767
unsigned short范圍:0~65535
int — 4個字節 — 32bit
signed int范圍:-2147483648~2147483647
unsigned int范圍:0~4294967295
…
補充:為什么char 屬于整型呢?
字符在內存中存儲的是字符的ASCLL碼值,所以字符類型歸于整型家族
4、浮點數在內存中的存儲
常見的浮點數類型:
float
double
long double
說明:由于浮點數的存儲較為復雜,先用一個典型的例子作為引入探究。
#include <stdio.h>
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("num的值為:%d\n",n);//1091567616printf("* pFloat值為:%f\n", *pFloat);//9.000000return 0;
}
我們通過此代碼的輸出結果,產生了與預想結果不同的輸出;那么為什么會是以這樣的數據輸出呢?
那么我們就繼續探討一下,為何會有如此的結果呢?
4.1、浮點數存儲規則
4.1.1、IEEE(電氣電子工程師協會) 擬定的754標準
根據IEEE(電氣電子工程師協會) 擬定的754標準,任意一個二進制浮點數,以V表示,均可表示為一下形式:
標準格式: (-1)^S * M * 2^E
(-1)^S 表示符號位,當S = 0,V為正數;當S = 1,V為負數
M表示有效數字,范圍:大于等于1,小于2
2^E表示指數位
浮點數標準格式:V = (-1)^S * M * 2^E
舉個例子:
5.5(十進制)
5.5(二進制):101.1 — 以權重計算,以小數點分割(小數點右邊0.5 == 1/(2^1))
科學表示格式: (-1)^0 * 1.011 * 2^2
S = 0 ;
M = 1.011 ;
E = 2
注意:因為需要滿足IEEE 754標準,所以我們將M的小數點,向左移動了兩位,使其滿足 1<M<2 的標準范圍;其次指數位E也就得到了 2
再舉個例子:同理
9.0(十進制)
9.0(二進制):1001.0
科學計數法形式:(-1)^0 * 1.001* 2^3
S = 0;
M = 1.001;
E = 3
對于浮點數的數據存儲規定:
(1)、對于32位的浮點數,最高的1位是符號位S,接著的8位是指數位E,剩下的23位是有效數字位M
即: 1 8 23
(2)、對于64位的浮點數,最高的1位是符號位S,接著的11位是指數位E,剩下的52位是有效數字位M
即: 1 11 52
4.1.2、另外IEE(電氣電子工程師協會)754對有效數字M和指數E,還有一些特定的規定
首先,前面知道了M范圍:1<M<2,也就是說可以寫成1.XXXXXXX的形式,其中的XXXXXXX表示小數部分
所以在IEEE 754中規定,在計算機內部保存M時,默認這個數的第一位是1,那么因此可以將該位的1暫時舍去,等讀取時再默認自動添加上。
這樣的目的就是節省1位有效數字,提升效率,意義在于使得浮點數的精度更高。
4.2、指數E的規定
4.2.1、指數E的存入規定
然后對于指數E的規定就更為復雜一些,看看下面這個例子:
0.5(十進制)
0.1(二進制)
科學計數法表示為:(-1)^0 * 1.0* 2^(-1)
S = 0
M = 1.0
E = -1
通過這里發現,與IEEE 754 規定的E為無符號數沖突,此時出現了負數-1
那么如何通過剛才提到的另外的特殊規定解決呢?
緊接著IEE(電氣電子工程師協會)科學家提出了,指數E存入的規定:
首先,當E為一個無符號整數(unsigned int) 這意味著,如果E為8位(32位環境),它的取值范圍為0-255;如果E為11位(64位環境),它的取值范圍為0~2047。
但是,我們剛剛的例子知道,科學計數法中的E是會出現負數的,
**所以IEEE 754規定,存入內存時E的真實值必須再加上一個中間數,對于8位的E,這個中間數是127;對于11位的E,這個中間數是1023。**這樣進行一個特定的運算,才能保留計算精度,保證數據的完整性和真實性。
比如,2 ^ 10的E是10,所以保存成32位浮點數時,必須保存成10 + 127 = 137,即10001001。
#include <stdio.h>
int main()
{float f = 5.5;//5.5(十進制)//101.1(二進制)//(-1)^0*1.011*2^2(科學表達式)//S = 0; --- 1bit//M = 1.011 --- 8bit//E = 2 --- 23bit//存儲二進制為(在學過IEEE 754標準規定后可知): -- 32位環境(S)0 (E)2 + 127 --> 1000 0001 (M)011 (根據符號位補0)00000 00000 00000 00000//即:0 1000 0001 011 0000 0000 0000 0000 0000//0100 0000 1011 0000 0000 0000 0000 0000//十六進制://0x 40 b0 00 00printf("%f\n",f);printf("%p\n",&f);return 0;
}
如圖所示:
當我們了解的E指數位數據的存入標準,那么我們又是如何取出使用呢?
4.2.2、指數E的取出規定三種情況
指數E從內存中取出規定還可以再分成三種情況:
1.E不全為0或不全為1
2.E全為0
3.E全為1
(1).E不全為0或不全為1
這時,浮點數就采用下面的規則表示,即指數E的計算值減去127(或1023),得到真實值,再將有效數字M前加上第一位的1。
比如: 0.5(1/2)的二進制形式為0.1,由于規定正數部分必須為1,即將小數點右移1位,則為1.0*2^(-1),其階碼為-1+127=126,表示為01111110,而尾數1.0去掉整數部分為0,補齊0到23位00000000000000000000000,則其二進制表示形式為:
0 01111110 00000000000000000000000
(2).E全為0
這時,浮點數的指數E等于 1-127(或者1-1023)即為真實值,有效數字M不再加上第一位的1,而是還原為0.xxxxxx的小數。
這樣做是為了表示 ±0,以及接近于0的很小的數字(理解為無窮小)。
(3).E全為1
這時,如果有效數字M全為0,表示 ±無窮大(正負取決于符號位s)
現在學習了以上知識點,再回顧上面那道題
5、經典例子詳解
(1)、以整型的形式存儲,以浮點數的取出:
首先,這里先定義了一個整型的正整數變量 n = 9,在上面知識點講到,正整數的原碼。反碼、補碼相等。
所以,我們代碼中以%d格式打印 n的值,不言而喻等于 9,正常輸出;
然后我們看到代碼中定義了一個浮點型的指針變量 pFloat ,并令n的地址強轉后賦值給它,然后我們根據上面提到的IEEE(電氣電子工程師協會) 擬定的754標準和規則,知道了浮點數的存儲方式。所以,當n的地址強轉為浮點型時,立馬通過n變量的二進制序列,知道采用標準的科學計數法表示:
0(S) 0000 0000(E) 00000000000000001001(M)
S = 0 ,E = 0 ,M = 0.00000000000000001001
由于E為全0的情況,通過標準規定知道,它的真實存儲形式需要使,E = 1 - 127 = -126,且我們舍去的最高位的1不再還原回來。
最后我們將還原為真實的 pFloat 值以科學表示法表示為:
(-1)^0 * 0.00000000000000001001* 2^(-126)
不難看出是一個巨小的數,無限接近于0,無窮小,約等于0
所以當我們以%f的格式打印時,結果自然而然就為:0.000000
(2)、以浮點數型的形式存儲,以整型的取出:
接下來,我們繼續探究后面的代碼,當我們將9.0一個浮點型的數據存入一個float* 變量 pFloat時,通過上面的講解不難理解,以浮點型存入,以%f格式打印,就正常輸出9.000000;
那么重點講解的是,當以浮點型存入,以%d格式輸出時的情況:
首先,我們知道了IEE 754標準,立馬反應知道以科學表示格式表示,所以:
二進制轉換大家都會就不多贅述,(十進制)9.0轉換為二進制得到 1001.0,再以科學計數法表示為:
(-1)^0 * 1.001* 2^3
S = 0,E = 3,M = 1.001
又因為當前編譯器環境是32位,所以:執行 1 8 23標準(見上文)
0(S) 1000 0010(E) 001 000000000000000000000(M補0,1暫時舍去)
即:0 1000 0010 001 000000000000000000000
以二進制表示:
0100 0001 0001 0000 0000 0000 0000 0000
以十六進制表示:
0x41 10 00 00
以十進制%d打印表示:
1091567616
如圖所示:
代碼部分:
#include <stdio.h>
int main()
{int n = 9;//0000 0000 0000 0000 0000 0000 0000 1001 --- 9原碼、反碼、補碼相等float* pFloat = (float*)&n;//當編譯器識別此時為float類型時,就自動識別為://0 0000 0000 00000000000000001001//S = 0//E = 0 ---全0的情況,E = (1 -127)= -126 ,且M不再添加舍去的最高位1//M = 0.00000000000000001001//科學表示法://(-1)^0*0.00000000000000001001*2^(-126) ---- 接近于無窮小得約等于0//以整型的形式存儲,以浮點數的取出:printf("n的值為:%d\n",n);//9printf("*pFloat值為:%f\n",*pFloat);//0.000000//以浮點數型的形式存儲,以整型的取出:*pFloat = 9.0;//9.0//1001.0//(-1)^0*1.001*2^3//S = 0//M = 1.001 //E = 3 (3+127)----> 1000 0010//二進制序列://0 1000 0010 001 000000000000000000000//0100 0001 0001 0000 0000 0000 0000 0000 --- float形式存入,且原碼、反碼、補碼相同printf("num的值為:%d\n",n);//以%d形式打印,1091567616printf("* pFloat值為:%f\n", *pFloat);//9.000000return 0;
}
(當前編譯器以小端存儲方式)驗證結果,如圖所示:
6、結語
半畝方糖一鑒開,天光云影共徘徊。
問渠哪得清如許?為有源頭活水來。–朱熹(觀書有感)