摘要:的理解和區別代表有符號,整數在內存中存儲的二進制位的最高位為符號位,表示負數,表示正數。那接下來我們來學習數據在所開辟的內存空間時如何存儲的。請看下面例子為什么內存中存儲的是補碼對于整數來說數據存放內存中其實存放的是補碼。
其實我們已經學過了基本的內置類型,如下:
類型 | 說明 | 長度 |
---|---|---|
char | 字符型 | 1個字節 |
short | 短整型 | 2個字節 |
int | 整型 | 4個字節 |
long | 長整型 | 4個字節 |
long long | 長長整型 | 8個字節 |
float | 單精度浮點型 | 4個字節 |
double | 雙精度浮點型 | 8個字節 |
用戶自定義的類型,例如:我們學過的結構體類型。
1.使用這個類型開辟內存空間的大小(大小決定了使用范圍)
比如說使用char類型創建的變量,開辟的內存空間是1個字節,而使用int類型創建的變量,開辟的內存空間是4個字節。
2.如何看待看待內存空間的視角
比如我們創建以下變量:
int a = 10;
float f = 10.0f;
這里的a和f所占內存大小都是4個字節,但是我們在看待a的時候,因為a的類型是int,所以我們會把a看做整型,而在看待f的時候,因為f的類型是float,所以我們會把b看做浮點數(而非整型)。
整型家族包括一下類型:
unsigned (無符號) signed(有符號)
我們在編寫代碼聲明變量時,[int] 可以省略不寫,例如:
unsigned short a = 0;或者 unsigend short int a = 0;兩種寫法等價。
注意:char也算到整型家族里面,因為字符在存儲的時候,存儲的是字符所對應的ASCII值(整數),例如:字符 ‘a’ 對應的ASCII值為97。
unsigned / signed的理解和區別:
signed代表有符號,整數在內存中存儲的二進制位的最高位為符號位,1表示負數,0表示正數。
unsigned代表無符號,均為正數,最高位是有效位,不為符號位。
例如char類型,一個char是一個字節 = 8bit,假如一個整數內存中存放的二進制的補碼為11111111, 如果是signed char,則最高位1是符號位,表示 - 1,如果是unsigned char,則最高位是有效位,不為符號位,它的值是一個正數255。
unsigned char與signed char兩者的取值范圍:
通過同樣的方法,我們也可以推算出其他類型的取值范圍。
注:char的類型到底是unsigned char還是signed char是不確定的,取決于編譯器。 (大部分編譯器下,char是 signed char)
其他類型(short、int、long)默認都是有符號的(signed)。
short == signed short
int == signed int
long == signed long
如果要定義成無符號的,則必須自己聲明出來,例如:
unsigned short
unsigned int
unsigned long
浮點數家族包含兩類,均是小數(浮點數沒有signed 和unsigned區分)
float - - - 單精度浮點型(4字節)
double - - - 雙精度浮點型(8字節,精度更高)
整型和浮點型屬于內置類型:
構造類型屬于自定義類型。
構造類型包含:
注:數組的元素類型和元素個數不一樣,則數組類型也會發生變化。(比如:int [10]與int [5]元素個數不一樣)所以數組類型也是自定義類型(自己構造的類型)
指針類型有:
指針類型的使用以及意義,我們在學習初階指針時,詳細講過:忘記的同學可以點擊去查看:
【C語言初階】初始指針
void 表示空類型(無類型)
通常應用于函數的返回類型、函數的參數、指針類型。
例如:
我們現在可以知道一個變量的創建是要在內存中開辟空間的。空間的大小是根據不同的類型而決定的。
那接下來我們來學習數據在所開辟的內存空間時如何存儲的。
源碼、反碼、補碼的概念我們在講位操作符和移位操作符時,已經提到過,現在我們在這在進行詳細的講解。
計算機中的整數有三種表示方法,即原碼、反碼和補碼。
三種表示方法均有符號位和數值位兩部分,符號位都是用0表“正”,用1表示“負”,而數值位有分兩種情況,正整數三種表示方法相同,負整數的三種表示方法各不相同。
舉例:如圖
解析:
對于正整數:正整數的源碼、反碼、補碼相同。
對于負整數:
源碼:
直接將數字按照正負數的形式翻譯成二進制就可以。
反碼:
將原碼的符號位不變,其他位依次按位取反就可以得到了。
補碼:
反碼+1就得到補碼。
請看下面例子:
對于整數來說:數據存放內存中其實存放的是補碼。
為什么呢?
我們首先看一下1-1這個例子:
①先按照原碼的方式去計算:
②接下來用補碼來進行計算:
在計算機系統中,數值一律用補碼來表示和存儲。原因在于,使用補碼,可以將符號位和數值位統一處理;
同時,加法和減法也可以統一處理(CPU只有加法器)此外,補碼與原碼相互轉換,其運算過程是相同的,不需要額外的硬件電路。
怎么理解補碼與原碼相互轉換,其運算過程是相同的?(以下運算,符號位均不變)
原碼->取反 + 1->補碼
補碼->取反 + 1->原碼
根據我們講過的
原碼通過符號位不變其他位按位取反得到反碼,然后反碼+1得到補碼(原碼->取反 + 1->補碼)
反過來,我們想通過補碼得到源碼,則只需:
補碼減一得到反碼,然后反碼的符號位不變,其他位按位取反得到源碼(補碼 -> - 1 取反->原碼)
那么,我們還有一種方法可以通過補碼得到源碼:
首先補碼的符號位不變,其他位按位取反得到反碼,然后反碼+1得到源碼(補碼->取反 + 1->原碼)
例如 - 1:
11111111 11111111 11111111 11111111 - 補碼
補碼->取反 + 1->原碼
10000000 00000000 00000000 00000000 - 取反
10000000 00000000 00000000 00000001 - +1
補碼 -> - 1 取反->原碼
11111111 11111111 11111111 11111110 - -1
10000000 00000000 00000000 00000001 - 取反
最終得到的結果均是:10000000 00000000 00000000 00000001
首先請看下面調試情況:
我們a輸入的16進制形式是11223344,在內存中存儲的時候卻是44332211,可以發現是倒著存儲的,而且是以字節為單位存儲的,地址是從左往右依次遞增的。
其實數據在內存中的存儲形式可以分兩種,如圖:
總結:
大端字節序存儲:是指把一個數字的低位字節的內容存放在內存的高地址中,把高位字節的內容,存放在內存的低地址中;
小端字節序存儲:是指把一個數字的低位字節的內容存放在內存的低地址中,把高位字節的內容,存放在內存的高地址中;
這是因為在計算機系統中,我們是以字節為單位的,每個地址單元都對應著一個字節,一個字節為8bit。
但是在C語言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器)。
另外,對于位數大于8位的處理器,例如16位或者32位的處理器,由于寄存器寬度大于一個字節,那么必然存在著一個如果將多個字節安排的問題。
因此就導致了大端存儲模式和小端存儲模式。
例如一個16bit的short型x,在內存中的地址為ox0010),x的值為0×1122,那么0x11為高字節,0x22為低字節。對于大端模式,就將0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,剛好相反
我們常用的x86結構是小端模式,而KEIL c51則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以由硬件來選擇是大端模式還是小端模式。
請簡述大端字節序和小端字節序的概念,設計一個小程序來判斷當前機器的字節序。
解答:
大端字節序存儲:是指把一個數字的低位字節的內容存放在內存的高地址中,把高位字節的內容,存放在內存的低地址中;
小端字節序存儲:是指把一個數字的低位字節的內容存放在內存的低地址中,把高位字節的內容,存放在內存的高地址中;
如何判斷大端、小端(字節序)呢?
思路:
假如我們利用 int a = 1;來判斷當前機器的字節序.
a = 1,用16進制顯示是 0x 00 00 01,則:
大端字節序存儲形式為:00 00 01
大端字節序存儲形式為:01 00 00
所以只需將a的第一個字節內容拿出來,判斷其是1還是0,1為小端,0為大端。
代碼實現:
#include int main(){ int a = 1; //0x 00 00 01 --- 16進制顯示 //小端:01 00 00 //大端:00 00 01 char* p = (char*)&a; if (*p == 1) { printf("小端/n"); } else { printf("大端/n"); } return 0;}
我們將判斷大小端的功能封裝成函數形式:
int check_sys(){ int a = 1; //0x 00 00 01 --- 16進制顯示 //小端:01 00 00 //大端:00 00 01 char* p = (char*)&a; if(*p == 1) return 1; else return 0;}int main(){ //如果是大端,返回0 //如果是小端,返回1 int ret = check_sys(); if (1 == ret) { printf("小端/n"); } else { printf("大端/n"); } return 0;}
我們可以對這個函數進行優化:
int check_sys(){ int a = 1; char* p = (char*)&a; return *p;}
再繼續優化:
int check_sys(){ int a = 1; return *(char*)&a;}
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;}
運行結果:
代碼分析:
2、下面這段代碼的結果是什么?
#include int main(){ char a = -128; printf("%u/n",a); return 0; }
運行結果:
代碼分析:
3、下面這段代碼的結果是什么?
#include int main(){ char a = 128; printf("%u/n",a); return 0; }
運行結果:
代碼分析:
4、下面這段代碼的結果是什么?
#include int main(){ int i = -20; unsigned int j = 10; printf("%d/n", i + j); return 0;}
運行結果:
代碼分析:
5、下面這段代碼的結果是什么?
#include int main(){ unsigned int i; for (i = 9; i >= 0; i--) { printf("%u/n", i); } return 0;}
運行結果:死循環
為了能更好的觀察到代碼細節的變化,我們加上Sleep(1000),每打印一個數據都暫停1000ms = 1s。
#include #include int main(){ unsigned int i; for (i = 9; i >= 0; i--) { printf("%u/n", i); Sleep(1000); } return 0;}
代碼分析:
為什么會發生死循環呢?
unsigned int i
i為無符號整型,恒>=0,循環語句的判斷條件恒成立。
當 i一直減到-1時,存放入unsigned int i 中會變為無符號整型,變為一個非常大的正數。
6、下面這段代碼的結果是什么?
#include #include int main(){ char a[1000]; int i; for (i = 0; i<1000; i++) { a[i] = -1 - i; } printf("%d", strlen(a)); return 0;}
運行結果:
代碼分析:
我們利用strlen函數求字符數組a的字符串長度,意思就是求在 ‘/0’ ,之前字符的個數。而 ‘/0’ 的ASCII碼值為0 。所以就是求0之前的字符個數。
我們在上方講到,char類型的取值范圍為 - 128 — 127,如圖:
我們可以發現這么一個規律,當我們從上往下依次加1,當加到127時,再加1就變成-128,當我們繼續加1,直到-1時,再加1,就會發現,值又變回0,所以這可以看成是一個循環。請看下面這幅圖,更方便理解:
我們可以看出,代碼是每次減1進行賦值,所以:arr[0] = -1, 從 - 1 —> 0要經過 - 1, - 2, - 3,…… - 128, 127, 126……3, 2, 1, 0…
128 + 127 = 255個數字,將最后一位的/0排除(strlen在計算字符串長度的時候,遇到/0停止且不計算/0的長度),所以最后的結果是255。
7、下面這段代碼的結果是什么?
#include unsigned char i = 0;int main(){ for (i = 0; i <= 255; i++) { printf("hello world/n"); } return 0;}
運行結果:和第五題問題的原理相似,死循環打印Hello world
分析:
因為,unsigned char 的取值范圍是 0~255,如圖:
當我們 i 自增到255時,再加1,就會發生截斷,值又變回0了。
所以條件判斷部分恒<=255,從而造成死循環。
1、浮點型數據基礎知識
常見的浮點數:3.14159,1E10(1.0 x 10^10)
浮點數家族包括:float、double、long double類型
浮點數表示的范圍 : float.h中定義
整型家族表示的范圍:limits.h中定義
2、如何查看float.h和limits.h呢?
查看limits.h文件方法:
這里就是所有整型家族表示的范圍。
查看float.h文件方法:
我們可以先找到VS2019安裝的最外層文件夾,在該文件下搜索float.h
找到對應文件后,可以將文件拖進VS2019打開查看
3、舉例
下面通過一個例子,來了解浮點型再內存中的存儲
#include 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;}
運行結果:
num 和 *pFloat 在內存中明明是同一個數,為什么浮點數和整數的解讀結果會差別這么大?
浮點數存儲規則
根據國際標準IEEE(電氣和電子工程協會) 754,任意一個二進制浮點數V可以表示成下面的形式:
例如:
十進制的5.5,寫成二進制是101.1
為什么5.5,寫成二進制是101.1,而不是101.101呢?如圖:
因為101.1小數點后面的1數表示2^-1,即1/2。以此類推,越往后,就分別表示:2 ^ -2,2 ^ -3…
所以5.5的二進制利用科學計數法可以寫為:1.011*2^2。對應上面的公式則表示為:
(-1) ^ 0 * 1.011 *2^2
可以得出:
S = 0
M = 1.011
E = 2
再比如:
十進制的9.0,寫成二進制是1001.0,利用科學計數法可以寫為 :1.001*2^3
對應上面的公式則表示為:(-1) ^ 0 * 1.001 *2^3
可以得出:
S = 0
M = 1.001
E = 3
IEEE754規定:
對于32位的浮點數(float),最高的1位是符號位s,接著的8位是指數E,剩下的23位為有效數字M。
對于64位的浮點數(double),最高的1位是符號位S,接著的11位是指數E,剩下的52位為有效數字M
了解了這些之后,那M和E具體是如何存儲的呢?
IEEE 754對有效數字M和指數E,還有一些特別規定。
前面說過, 1≤M<2 ,也就是說,M可以寫成 1.xxxxxx 的形式,其中xxxxxx表示小數部分。
IEEE754規定,在計算機內部保存M時,默認這個數的第一位總是1,因此可以被舍去,只保存后面的xxxxxx部分。
比如保存1.01的時候,只保存01,等到讀取的時候,再把第一位的1加上去。這樣做的目的,是節省1位有效數字。
以32位浮點數為例,留給M只有23位,將第一位的1舍去以后,等于可以保存24位有效數字
至于指數E,情況就比較復雜。
首先,E為一個無符號整數(unsigned int)
這意味著,如果E為8位,它的取值范圍為0 ~ 255;如果E為11位,它的取值范圍為0~2047。但是,我們知道,科學計數法中的E是可以出現負數的,所以IEEE754規定,存入內存時E的真實值必須再加上一個中間數,對于8位的E,這個中間數是127;對于11位的E,這個中間數是1023。比如,2^10的E是10,所以保存成32位浮點數時,必須保存成10+127=137,即10001001
然后,指數E從內存中取出還可以再分成三種情況:
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
E全為0
這時,浮點數的指數E等于1 - 127(或者1 -1023)即為真實值,有效數字M不再加上第一位的1,而是還原為0.xxxxxx的小數。這樣做是為了表示±0,以及接近于0的很小的數字。(規定的)
E全為1
這時,如果有效數字M全為0,表示±無窮大(正負取決于符號位s)
關于浮點數的表示規則就這么多。
了解以上規則之后,請看下面例子,觀察浮點數的存儲:
#include int main(){ float f = 5.5f;//不加f,編譯器默認存儲double類型 //101.1 --- 二進制表示形式 //(-1)^0 * 1.011 * 2^2 //S=0 //M=1.011 //E=2 +127 存儲(129) //0 10000001 01100000000000000000000 //S E M //0100 0000 1011 0000 0000 0000 0000 0000 //40 B0 00 00 --- 以16進制存儲 //我們可以按F10調試查看內存 return 0;}
可以直觀看出,在內存中存儲的結果與我們運算的結果一致,這里倒著存儲,是因為小端字節序存儲方式。
解析上方例題:
#include int main(){ int n = 9; 00000000000000000000000000001001 float *pFloat = (float *)&n; //*pFloat---是以浮點數的視角去訪問n的四個字節,就會認為n的4個字節中放的是浮點數,所以n以浮點數存儲的形式為: //0 00000000 00000000000000000001001 //E為全0 //所以E直接就是 1-127 = -126 //M = 0.00000000000000000001001 //結果為:0.00000000000000000001001 * 2^-126 無窮小的數字 printf("n的值為:%d/n", n);//9 printf("*pFloat的值為:%f/n", *pFloat);//0.000000 打印結果小數點及其后六位(精度) *pFloat = 9.0; //*pFloat是以浮點數的視角觀察n的4個字節的 //所以以浮點數的形式存儲9.0 //1001.0 --- 9.0的二進制表示形式 //(-1)^0 * 1.001*2^3 //S=0 //E = 3 //M = 1.001 //存儲結果: //0 10000010 00100000000000000000000 printf("num的值為:%d/n", n); //以有符號的形式打印整數 //將01000001000100000000000000000000看作一個有符號的整數,所以它的源反補相同,打印結果為1091567616 printf("*pFloat的值為:%f/n", *pFloat);//以浮點型打印,所以值就為9.000000 return 0;}
好了,到此數據的存儲就講完了,希望大佬幫忙點評呀 ?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/119108.html
摘要:在符號位中,表示正,表示負。我們知道對于整型來說,內存中存放的是該數的補碼。在計算機系統中,數值一律用補碼來表示和存儲。表示有效數字,。規定對于位的浮點數,最高的位是 ...
摘要:還不清楚原碼反碼補碼的可以到語言從入門到入土操作符篇中的移位操作符處學習一下。比如原碼反碼補碼原碼顯示值補碼數據存放內存中其實存放的是補碼補碼的表示與存儲在計算機系統中,數值一律用補碼來表示和存儲。 ...
閱讀 1292·2021-11-16 11:44
閱讀 3758·2021-10-09 10:01
閱讀 1745·2021-09-24 10:31
閱讀 3833·2021-09-04 16:41
閱讀 2510·2021-08-09 13:45
閱讀 1209·2019-08-30 14:08
閱讀 1775·2019-08-29 18:32
閱讀 1640·2019-08-26 12:12