一、C語言中的常量
1.1 生活中的數(shù)據(jù)
整數(shù): 168,188,520,1314
小數(shù): 1.68,1.88,5.20,13.14
字母: a/A,b/B,c/C,d/D
單詞: welcome,hello,world
1.2 生活中的數(shù)據(jù)在C語言中的描述
所謂常量,就是值永遠(yuǎn)不允許被改變的量,比如一年中有12個月、一天有24小時等。分類:
①: 整型常量 例如: 10,20,30,40,800,900,-1,-2,-3
②: 浮點(diǎn)數(shù)常量(小數(shù)) 3.1415926,4.88,3.14e8,3.14e-9
ps: 3.14e8 ==> 這里的
e
相當(dāng)于數(shù)學(xué)的底數(shù)10,8為冪,等價于數(shù)學(xué)中 3.14*108
③: 字符常量。在C語言中我們把字母叫做字符,字符用單引號引用。 例如: 'A','B','a','1','8'
ps: 在C語言中規(guī)定,每個字符有個對應(yīng)的
ASCII
的整數(shù)值與之對應(yīng)。? Linux 中查詢ASCII
碼的方法:man ascii
④: 字符串常量。在C語言中單詞我們叫做字符串,字符串用雙引號引用。 例如:"welcome","hello","world"
字符串常量以
""
引用起來,等價于多個字符的結(jié)合 +'\0'
,例如:"ABC"
<===>'A'
+'B'
+'C'
+'\0'
、"1234"
、"XYZ"
⑤: 標(biāo)識常量(宏定義)。功能:用宏名來代替某些常量數(shù)據(jù),在某些特殊的場合可以提高程序的可讀性。? 宏名替換后為常量,常大寫。 格式:
#define 標(biāo)識符號名 常量數(shù)據(jù)
// 舉例:
#define MAX 100
#define STR "This is a example"
二、C語言中的輸出函數(shù)
當(dāng)程序解決了問題之后,還要使用輸出語句將計算的結(jié)果顯示出來。本小節(jié)致力于使讀者掌握如何對程序的 輸出 進(jìn)行操作。
2.1 單字符輸出函數(shù) putchar()
單個字符操作,就是每次只能操作一個字符,輸出單個字符使用函數(shù) putchar()
。在C語言中,putchar()
函數(shù)是輸出字符數(shù)據(jù),它的作用是向顯示設(shè)備輸出一個字符。其語法格式如下:
int putchar(int ch); //參數(shù)ch是要進(jìn)行輸出的字符,可以是字符型變量或整型變量,也可以是常量和轉(zhuǎn)義字符
示例:
#include<stdio.h>
int main() {
// ps 多個 putchar('\n'); //換行 為了美觀
putchar('C'); //輸出字符C
putchar('1'); //輸出字符1
putchar('\103'); //輸出轉(zhuǎn)義字符 ==> C
putchar('\n'); //換行
putchar(97); //輸出 a
putchar('\n'); //換行
putchar('A'); //輸出 A
putchar('\n'); //換行
putchar('A' + 32); //輸出 a ==> 'A'(65) + 32 ==> 97(a)
putchar('\n'); //換行
return 0;
}
運(yùn)行結(jié)果如下圖所示:
2.2 多字符輸出函數(shù) puts()
字符串輸出就是將字符串輸出在控制臺上,例如一句名言、一串?dāng)?shù)字等等。在C語言中,字符串輸出使用的是 puts()
函數(shù),它的作用是輸出一個字符串到屏幕上。其語法格式如下:
//使用puts()函數(shù)時,先要在程序中添加stdio.h頭文件。其中,形式參數(shù)str是字符串指針類型,可以用來接收要輸出的字符串
int puts(char *str); // puts(const char *_Str);
ps: C語言中沒有字符串類型,想要表示字符串要用字符型數(shù)組和字符型指針,這兩種概念會在后續(xù)章節(jié)進(jìn)行詳細(xì)講解。
示例:使用 puts()
函數(shù)輸出一個字符串,代碼如下:
#include<stdio.h>
int main() {
puts("Welcome to Deloitte"); //輸出一個字符串常量
puts("放棄不難,但堅持一定很酷"); //輸出勵志語句
puts("633520"); //輸出數(shù)字字符串
puts("永無BUG"); //輸出中英文結(jié)合字符串
return 0;
}
運(yùn)行結(jié)果如下圖所示:
ps:這段語句是輸出一個字符串,之后會自動進(jìn)行換行操作。 puts()
函數(shù)會在字符串中判斷 \0
結(jié)束符,遇到結(jié)束符時,后面的字符不再輸出并且自動換行。例如:
2.3 格式化輸出函數(shù) printf()
2.3.1 牛刀小試
在 2.2小節(jié)
中,我們使用 puts()
函數(shù)來輸出字符串。puts()
函數(shù)是 output string
的縮寫,只能用來輸出字符串,不能輸出整數(shù)、小數(shù)、字符等,我們需要用另外一個函數(shù),那就是 printf()
函數(shù)。printf()
比 puts()
更加強(qiáng)大,不僅可以輸出字符串,還可以輸出整數(shù)、小數(shù)、單個字符等,并且輸出格式也可以自己定義,例如:
- 以十進(jìn)制、八進(jìn)制、十六進(jìn)制形式輸出;
- 要求輸出的數(shù)字占 n 個字符的位置;
- 控制小數(shù)的位數(shù)。
printf 是 print format 的縮寫,意思是 格式化打印。這里所謂的 打印 就是在屏幕上顯示內(nèi)容,與 輸出 的含義相同,所以我們一般稱 printf 是用來格式化輸出的。其語法格式如下:
printf(格式控制,輸出列表)
①: 格式控制是用雙引號括起來的字符串,也稱為轉(zhuǎn)換控制字符串。其中包括格式字符和普通字符。
// 1.1 格式字符:用來進(jìn)行格式說明的字符,作用是將輸出的數(shù)據(jù)轉(zhuǎn)換為指定的格式。格式字符通常以 `%` 開頭。
// 1.2 普通字符:需要原樣輸出的字符,包括雙引號內(nèi)的逗號、空格和換行符。
②: 輸出列表列出的是要進(jìn)行輸出的一些數(shù)據(jù),可以是常量、變量或表達(dá)式。
舉例:要輸出整型常量,代碼如下:
#include<stdio.h>
int main() {
/*
* 說明:
* ① "this is"字符串是普通字符 "%d" 是格式字符 表示輸出的是后面的10
* ② 執(zhí)行結(jié)果 this is 10
* */
printf("this is %d\n", 10);
// ps: 函數(shù)中的每個參數(shù)都必須按照給定的格式和順序依次輸出
printf("num1=%d, num2=%d\n", 10, 20);
return 0;
}
運(yùn)行結(jié)果如下圖所示:
ps: puts 輸出完成后會自動換行,而 printf 不會,要自己添加換行符,這是 puts 和 printf 在輸出字符串時的一個區(qū)別。
所謂換行,就是讓文本從下一行的開頭輸出,相當(dāng)于在編輯 Word 或者 TXT 文檔時按下回車鍵。
printf()
格式字符:
示例:使用格式輸出函數(shù) printf()
輸出不同類型的變量。
#include<stdio.h>
int main() {
printf("this int is: %d\n", 1314); //使用printf()函數(shù)輸出整型常量
printf("this char is: %c\n", 'A'); // 輸出字符型常量
printf("char=%c,%c,%c\n", 'X', 'Y', 'Z'); // 輸出字符型常量
printf("this float is: %f\n", 13.14); // 輸出小數(shù)(浮點(diǎn)型)常量
printf("this string is: %s\n", "I LOVE YOU"); // 輸出字符串常量
//輸出其他進(jìn)制數(shù) 比如:八進(jìn)制與十六進(jìn)制 10 ==> 8+2 1010
printf("10進(jìn)制的數(shù)字10等于8進(jìn)制的%o\n", 10); //12
// 輸出八進(jìn)制數(shù),若是加上#,會輸出對應(yīng)的標(biāo)志位0
printf("10進(jìn)制的數(shù)字10等于8進(jìn)制的%#o\n", 10); //012
printf("10進(jìn)制的數(shù)字10等于16進(jìn)制的%x\n", 10);//a
//輸出十六進(jìn)制數(shù)據(jù),若是加上#,會輸出對應(yīng)的標(biāo)志位0x
printf("10進(jìn)制的數(shù)字10等于16進(jìn)制的%#x\n", 10);//0xa
return 0;
}
運(yùn)行結(jié)果如下圖所示:
示例:在數(shù)據(jù)之中,宏定義是原樣替換
//03-define.c
#include <stdio.h>
#define N 10
#define M N + N
#define SUM M * M
//宏定義:在數(shù)據(jù)之中,宏定義是原樣替換
int main() {
printf("M = %d\n", M); //N + N--->10 + 10
printf("SUM = %d\n", SUM);//M * M --->N + N * N + N--->10 + 10 * 10 + 10 120
return 0;
}
運(yùn)行結(jié)果如下圖所示:
2.3.2 輕量進(jìn)階
格式控制符:
printf() 格式控制符的完整形式如下:
%[flag][width][.precision]type // [] 表示可選,可以省略
①: type 表示輸出類型,比如 %d,%f,%c,%lf
,type 就分別對應(yīng) d,f,c,lf
。ps: type 這一項(xiàng)必須有,這意味著輸出時必須要知道是什么類型。
②: width 表示最小輸出寬度,也就是至少占用幾個字符的位置;例如,%-9d
中 width
對應(yīng) 9
,表示輸出結(jié)果最少占用 9 個字符的寬度。當(dāng)輸出結(jié)果的寬度不足 width 時,以空格補(bǔ)齊(如果沒有指定對齊方式,默認(rèn)會在左邊補(bǔ)齊空格);當(dāng)輸出結(jié)果的寬度超過 width 時,width 不再起作用,按照數(shù)據(jù)本身的寬度來輸出。下面的代碼演示了 width 的用法:
#include<stdio.h>
int main() {
printf("%10d\n", 234); //234的指定輸出寬度為10,234的寬度為3,所以前邊要補(bǔ)上7個空格。
printf("%12f\n", 9.8); //9.8的指定輸出寬度為12,9.800000的寬度為8,所以前邊要補(bǔ)上4個空格。
printf("%4c\n", '@');
//"https://blog.csdn.net/xw1680?"的指定輸出寬度為8,
// "https://blog.csdn.net/xw1680?"的寬度為29,超過了8,所以指定輸出寬度不再起作用,而是按照實(shí)際寬度輸出。
printf("%8s\n", "https://blog.csdn.net/xw1680?");
return 0;
}
運(yùn)行結(jié)果如下圖所示:
③: .precision
表示輸出精度,也就是小數(shù)的位數(shù)。說明如下:
- 當(dāng)小數(shù)部分的位數(shù)大于 precision 時,會按照四舍五入的原則丟掉多余的數(shù)字。
- 當(dāng)小數(shù)部分的位數(shù)小于 precision 時,會在后面補(bǔ) 0。
- 用于整數(shù)時,.precision 表示最小輸出寬度。與 width 不同的是,整數(shù)的寬度不足時會在左邊補(bǔ) 0,而不是補(bǔ)空格。
- 用于字符串時,.precision 表示最大輸出寬度,或者說截取字符串。當(dāng)字符串的長度大于 precision 時,會截掉多余的字符;當(dāng)字符串的長度小于 precision 時,.precision 就不再起作用。
示例:
#include<stdio.h>
int main() {
// 000123456 123456
printf("data: 123456 ==> %.9d %.4d\n", 123456, 123456);
// 882.92 882.9237 882.9236720000
printf("data: 882.923672 ==> %.2lf %.4lf %.10lf\n", 882.923672, 882.923672, 882.923672);
printf("data: hello-world ==> %.5s %.15s\n", "hello-world", "hello-world");
return 0;
}
運(yùn)行結(jié)果如下圖所示:
④ flag 是標(biāo)志字符。例如,%#x
中 flag
對應(yīng) #
,%-9d
中 flags
對應(yīng) -
。下表列出了 printf() 可以用的 flag:
示例:
#include <stdio.h>
int main() {
// 當(dāng)以%10d輸出192時,是右對齊,所以在192前面補(bǔ)七個空格;當(dāng)以%-10d輸出192時,是左對齊,所以在192后面補(bǔ)七個空格
printf("m=%10d, m=%-10d\n", 192, 192); //演示 - 的用法
// 192 是正數(shù),以%+d輸出時要帶上正號;-943 是負(fù)數(shù),以%+d輸出時要帶上負(fù)號
printf("m=%+d, n=%+d\n", 192, -943); //演示 + 的用法
// 192 是正數(shù),以% d輸出時要在前面加空格;-943 是負(fù)數(shù),以% d輸出時要在前面加負(fù)號
printf("m=% d, n=% d\n", 192, -943); //演示空格的用法
//%.0f表示保留 0 位小數(shù),也就是只輸出整數(shù)部分,不輸出小數(shù)部分。
// 默認(rèn)情況下,這種輸出形式是不帶小數(shù)點(diǎn)的,但是如果有了#標(biāo)志,那么就要在整數(shù)的后面硬加上一個小數(shù)點(diǎn),以和純整數(shù)區(qū)分開
printf("f=%.0f, f=%#.0f\n", 84.342, 84.342); //演示#的用法
return 0;
}
運(yùn)行結(jié)果如下圖所示:
2.3.3 printf() 不能立即輸出的問題
printf() 有一個尷尬的問題,就是有時候不能立即輸出,請看下面的代碼:
#include<stdio.h>
#include<unistd.h>
int main() {
printf("Amo Xiang的博客");
sleep(5); //程序暫停5秒鐘
printf("https://blog.csdn.net/xw1680?spm=1010.2135.3001.5343\n");
return 0;
}
在 Linux 或者 Mac OS 下運(yùn)行該程序,會發(fā)現(xiàn)第一個 printf() 并沒有立即輸出,而是等待 5 秒以后,和第二個 printf() 一起輸出了,請看下面的動圖演示:
修改一下代碼,在第一個 printf() 的最后添加一個換行符,如下所示:
再次編譯并運(yùn)行程序,發(fā)現(xiàn)第一個 printf() 首先輸出(程序運(yùn)行后立即輸出),等待 5 秒以后,第二個 printf() 才輸出,請看下面的動圖演示:
從本質(zhì)上講,printf() 執(zhí)行結(jié)束以后數(shù)據(jù)并沒有直接輸出到顯示器上,而是放入了緩沖區(qū),直到遇見換行符 \n
才將緩沖區(qū)中的數(shù)據(jù)輸出到顯示器上。更加深入的內(nèi)容,我們將在后續(xù)的章節(jié)中詳細(xì)講解。
ps: Windows 和 Linux、Mac OS 的情況又不一樣。這是因?yàn)椋琖indows 和 Linux、Mac OS 的緩存機(jī)制不同。更加深入的內(nèi)容,我們將在后續(xù)的章節(jié)中詳細(xì)講解。要想破解 printf() 輸出的問題,必須要了解緩存,它能使你對輸入輸出的認(rèn)識上升到一個更高的層次,以后不管遇到什么疑難雜癥,都能迎刃而解。可以說,輸入輸出的
命門
就在于緩存。
小結(jié):在以后的編程中,我們會經(jīng)常使用 printf,說它是C語言中使用頻率最高的一個函數(shù)一點(diǎn)也不為過,每個C語言程序員都應(yīng)該掌握 printf 的用法,這是最基本的技能。
三、C語言中的變量
3.1 變量
現(xiàn)實(shí)生活中我們會找一個小箱子來存放物品,一來顯得不那么凌亂,二來方便以后找到。計算機(jī)也是這個道理,我們需要先在內(nèi)存中找一塊區(qū)域,規(guī)定用它來存放 數(shù)據(jù),并起一個好記的名字 (標(biāo)識符/變量名), 方便以后查找。這塊區(qū)域就是 小箱子,我們可以把數(shù)據(jù)放進(jìn)去了。C語言中這樣在內(nèi)存中找一塊區(qū)域:
數(shù)據(jù)類型 變量名;
int weight; // 舉例:這個語句的意思是:在內(nèi)存中找一塊區(qū)域,命名為 weight,用它來存放整數(shù)
不過 int weight;
僅僅是在內(nèi)存中找了一塊可以保存整數(shù)的區(qū)域,那么如何將 123,100,999
這樣的數(shù)字放進(jìn)去呢?C語言中這樣向內(nèi)存中放整數(shù):
weight = 123;
=
是一個新符號,它在數(shù)學(xué)中叫 等于號
,例如 1+2=3,但在C語言中,這個過程叫做 賦值(Assign)
。 賦值是指把數(shù)據(jù)放到內(nèi)存的過程。把上面的兩個語句連起來:
int weight;
weight = 123;
//把 123 放到了一塊叫做 weight 的內(nèi)存區(qū)域。等價于:
int weight = 123;
// weight 中的整數(shù)不是一成不變的,只要我們需要,隨時可以更改。更改的方式就是再次賦值,例如:
int weight;
weight = 123;
weight = 520;
weight = 1314;
第二次賦值,會把第一次的數(shù)據(jù) 覆蓋(擦除) 掉,也就是說,a 中最后的值是 1314
,123,520
已經(jīng)不存在了,再也找不回來了。因?yàn)?weight 的值可以改變,所以我們給它起了一個形象的名字,叫做 變量(Variable)。
int weight;
創(chuàng)造了一個變量 weight,我們把這個過程叫做變量定義。weight = 123; 把 123 交給了變量 weight,我們把這個過程叫做給變量賦值;又因?yàn)槭堑谝淮钨x值,也稱變量的初始化,或者賦初值。
可以先定義變量,再初始化,也可以在定義的同時進(jìn)行初始化。這兩種方式是等價的:
// 第一種方式
int weight;
weight = 123;
// 第二種方式
int weight = 123;
3.2 數(shù)據(jù)類型
數(shù)據(jù)是放在內(nèi)存中的,變量是給這塊內(nèi)存起的名字,有了變量就可以找到并使用這份數(shù)據(jù)。但問題是,該如何使用呢?我們知道,諸如數(shù)字、文字、符號、圖形、音頻、視頻等數(shù)據(jù)都是以二進(jìn)制形式存儲在內(nèi)存中的,它們并沒有本質(zhì)上的區(qū)別,那么,00010000 該理解為數(shù)字16呢,還是圖像中某個像素的顏色呢,還是要發(fā)出某個聲音呢?如果沒有特別指明,我們并不知道。
也就是說,內(nèi)存中的數(shù)據(jù)有多種解釋方式,使用之前必須要確定;上面的 int weight;
就表明,這份數(shù)據(jù)是整數(shù),不能理解為像素、聲音等。int
有一個專業(yè)的稱呼,叫做 數(shù)據(jù)類型(Data Type)。 顧名思義,數(shù)據(jù)類型用來說明數(shù)據(jù)的類型,確定了數(shù)據(jù)的解釋方式,讓計算機(jī)和程序員不會產(chǎn)生歧義。 在C語言中,有多種數(shù)據(jù)類型,例如:
這些是最基本的數(shù)據(jù)類型,是C語言自帶的,如果我們需要,還可以通過它們組成更加復(fù)雜的數(shù)據(jù)類型,后面我們會一一講解。為了讓程序的書寫更加簡潔,C語言支持多個變量的連續(xù)定義,例如:
數(shù)據(jù)類型 變量名1 = 值1, 變量名2 = 值2, 變量名3 = 值3...;
// 舉例
int weight = 129, age = 29, eyes = 200;
int a, b, c;
float m = 13.14, n = 5.20;
char p, q = '@';
// ps:連續(xù)定義的多個變量以逗號,分隔,并且要擁有相同的數(shù)據(jù)類型;變量可以初始化,也可以不初始化
3.3 數(shù)據(jù)的長度
所謂 數(shù)據(jù)長度(Length), 是指數(shù)據(jù)占用多少個字節(jié)。占用的字節(jié)越多,能存儲的數(shù)據(jù)就越多,對于數(shù)字來說,值就會更大,反之能存儲的數(shù)據(jù)就有限。多個數(shù)據(jù)在內(nèi)存中是連續(xù)存儲的,彼此之間沒有明顯的界限,如果不明確指明數(shù)據(jù)的長度,計算機(jī)就不知道何時存取結(jié)束。例如我們保存了一個整數(shù) 1000,它占用4個字節(jié)的內(nèi)存,而讀取時卻認(rèn)為它占用3個字節(jié)或5個字節(jié),這顯然是不正確的。所以,在定義變量時還要指明數(shù)據(jù)的長度。而這恰恰是數(shù)據(jù)類型的另外一個作用。數(shù)據(jù)類型除了指明數(shù)據(jù)的解釋方式,還指明了數(shù)據(jù)的長度。因?yàn)樵贑語言中,每一種數(shù)據(jù)類型所占用的字節(jié)數(shù)都是固定的,知道了數(shù)據(jù)類型,也就知道了數(shù)據(jù)的長度。 在32位環(huán)境中,各種數(shù)據(jù)類型的長度一般如下:
C語言有多少種數(shù)據(jù)類型,每種數(shù)據(jù)類型長度是多少、該如何使用,這是每一位C程序員都必須要掌握的,后續(xù)會一一進(jìn)行講解。舉例:
#include<stdio.h>
int main() {
int a = 100;
printf("a = %d. sizeof = sizeof(%ld)\n", a, sizeof(a));
printf("int = sizeof(%ld)\n", sizeof(int));
return 0;
}
運(yùn)行結(jié)果如下圖所示:
小結(jié): 數(shù)據(jù)是放在內(nèi)存中的,在內(nèi)存中存取數(shù)據(jù)要明確三件事情:數(shù)據(jù)存儲在哪里、數(shù)據(jù)的長度以及數(shù)據(jù)的處理方式。 變量名不僅僅是為數(shù)據(jù)起了一個好記的名字,還告訴我們數(shù)據(jù)存儲在哪里,使用數(shù)據(jù)時,只要提供變量名即可;而數(shù)據(jù)類型則指明了數(shù)據(jù)的長度和處理方式。所以諸如 int n;char c;float money;
這樣的形式就確定了數(shù)據(jù)在內(nèi)存中的所有要素。C語言提供的多種數(shù)據(jù)類型讓程序更加靈活和高效,同時也增加了學(xué)習(xí)成本。而有些編程語言,例如 Python,JavaScript
等,在定義變量時不需要指明數(shù)據(jù)類型,編譯器會根據(jù)賦值情況自動推演出數(shù)據(jù)類型,更加智能。除了C語言, Java,C++,C#
等在定義變量時也必須指明數(shù)據(jù)類型,這樣的編程語言稱為強(qiáng)類型語言。而 Python,JavaScript
等在定義變量時不必指明數(shù)據(jù)類型,編譯系統(tǒng)會自動推演,這樣的編程語言稱為弱類型語言。強(qiáng)類型語言一旦確定了數(shù)據(jù)類型,就不能再賦給其他類型的數(shù)據(jù),除非對數(shù)據(jù)類型進(jìn)行轉(zhuǎn)換。弱類型語言沒有這種限制,一個變量,可以先賦給一個整數(shù),然后再賦給一個字符串。ps:數(shù)據(jù)類型只在定義變量時指明,而且必須指明;使用變量時無需再指明,因?yàn)榇藭r的數(shù)據(jù)類型已經(jīng)確定了。
四、C語言中的整數(shù)
整數(shù)是編程中常用的一種數(shù)據(jù),C語言通常使用 int 來定義整數(shù)(int 是 integer 的簡寫),這在 三、C語言中的變量 中已經(jīng)進(jìn)行了詳細(xì)講解。
在現(xiàn)代操作系統(tǒng)中,int 一般占用 4 個字節(jié)(Byte)的內(nèi)存,共計 32 位(Bit)。如果不考慮正負(fù)數(shù),當(dāng)所有的位都為 1 時它的值最大,為 232-1 = 4,294,967,295 ≈ 43億,這是一個很大的數(shù),實(shí)際開發(fā)中很少用到,而諸如 1、99、12098 等較小的數(shù)使用頻率反而較高。使用 4 個字節(jié)保存較小的整數(shù)綽綽有余,會空閑出兩三個字節(jié)來,這些字節(jié)就白白浪費(fèi)掉了,不能再被其他數(shù)據(jù)使用?,F(xiàn)在個人電腦的內(nèi)存都比較大了,配置低的也有 8G,浪費(fèi)一些內(nèi)存不會帶來明顯的損失;而在C語言被發(fā)明的早期,或者在單片機(jī)和嵌入式系統(tǒng)中,內(nèi)存都是非常稀缺的資源,所有的程序都在盡力節(jié)省內(nèi)存。反過來說,43 億雖然已經(jīng)很大,但要表示全球人口數(shù)量還是不夠,必須要讓整數(shù)占用更多的內(nèi)存,才能表示更大的值,比如占用 6 個字節(jié)或者 8 個字節(jié)。
讓整數(shù)占用更少的內(nèi)存可以在 int 前邊加 short,讓整數(shù)占用更多的內(nèi)存可以在 int 前邊加 long,還不夠的話可以在 int 前面加兩個 long,例如:
short int a = 10;
short int b, c = 99;
long int m = 102023;
long int n, p = 562131;
long long int x = 12233720;
long long int y, z = 92949685;
//也可以將 int 省略,只寫 short、long 和 long long,如下所示:
short a = 10;
short b, c = 99;
long m = 102023;
long n, p = 562131;
long long x = 12233720;
long long y, z = 92949685; //這樣的寫法更加簡潔,實(shí)際開發(fā)中常用。
這樣 a、b、c 各自只占用 2 個字節(jié)的內(nèi)存,m、n、p 可能
占用 8 個字節(jié),x、y、z 各自占用 8 個字節(jié)。 int 是基本的整數(shù)類型,short、long 和 long long 是在 int 的基礎(chǔ)上進(jìn)行的擴(kuò)展,short 可以節(jié)省內(nèi)存,long 和 long long 可以容納更大的值。short、int、long 是C語言中常見的整數(shù)類型,long long 在某些場景中也會用到。其中,int 稱為整型,short 稱為短整型,long 稱為長整型,long long 稱為超長整形。
4.1 整型的長度
細(xì)心的讀者可能會發(fā)現(xiàn),上面我們在描述 short、int、long、long long 類型的長度時,只對 short 和 long long 使用肯定的說法,而對 int、long 使用了 一般
、可能
等不確定的說法。這種描述的言外之意是,short 和 long long 的長度是確定的,分別是 2 和 8 個字節(jié),而 int、long 的長度無法確定,在不同的環(huán)境下有不同的表現(xiàn)。一種數(shù)據(jù)類型占用的字節(jié)數(shù),稱為該數(shù)據(jù)類型的長度。 例如,short 占用 2 個字節(jié)的內(nèi)存,那么它的長度就是 2。實(shí)際情況也確實(shí)如此,C語言并沒有嚴(yán)格規(guī)定 short、int、long、long long 的長度,只做了寬泛的限制:
short 至少占用 2 個字節(jié)。
int 建議為一個機(jī)器字長。32 位環(huán)境下機(jī)器字長為 4 字節(jié),64 位環(huán)境下機(jī)器字長為 8 字節(jié)。
short 的長度不能大于 int,long 的長度不能小于 int,long long 不能小于 long。
總結(jié)起來,它們的長度(所占字節(jié)數(shù))關(guān)系為:
2 ≤ short ≤ int ≤ long ≤ long long
這就意味著,short 并不一定真的 短
,long 也并不一定真的 長
,它們有可能和 int 占用相同的字節(jié)數(shù)。同樣,long long 也不一定真的比 long 長,它們占用的字節(jié)數(shù)可能相同。在 16 位環(huán)境下,short 的長度為 2 個字節(jié),int 也為 2 個字節(jié),long 為 4 個字節(jié),long long 為 8 個字節(jié)。16 位環(huán)境多用于單片機(jī)和低級嵌入式系統(tǒng),在 PC 和服務(wù)器上已經(jīng)見不到了。對于 32 位的 Windows、Linux 和 Mac OS,short 的長度為 2 個字節(jié),int 為 4 個字節(jié),long 也為 4 個字節(jié),long long 為 8 個字節(jié)。PC 和服務(wù)器上的 32 位系統(tǒng)占有率也在慢慢下降,嵌入式系統(tǒng)使用 32 位越來越多。在 64 位環(huán)境下,不同的操作系統(tǒng)會有不同的結(jié)果,如下所示:
目前我們使用較多的 PC 系統(tǒng)為 Win7,Win10,Win11,Mac OS,Linux
,在這些系統(tǒng)中,short、int 和 long long 的長度都是固定的,分別為 2、4 和 8,大家可以放心使用,只有 long 的長度在 Win64 和類 Unix 系統(tǒng)下會有所不同,使用時要注意移植性。
4.2 sizeof操作符
獲取某個數(shù)據(jù)類型的長度可以使用 sizeof 操作符,如下所示:
#include <stdio.h>
int main()
{
short a = 10;
int b = 100;
int short_length = sizeof a;
int int_length = sizeof(b);
int long_length = sizeof(long);
int longlong_length = sizeof(long long);
printf("short=%d, int=%d, long=%d, longlong=%d\n", short_length, int_length, long_length, longlong_length);
return 0;
}
在 32 位環(huán)境以及 Win64 環(huán)境下的運(yùn)行結(jié)果為:
在 64 位 Linux 和 Mac OS 下的運(yùn)行結(jié)果為:
sizeof 用來獲取某個數(shù)據(jù)類型或變量所占用的字節(jié)數(shù),如果后面跟的是變量名稱,那么可以省略 ()
, 如果跟的是數(shù)據(jù)類型,就必須帶上 ()
。
4.3 不同整型的輸出
在之前 2.3 格式化輸出函數(shù) printf()
小節(jié)已經(jīng)講解過 printf() 函數(shù)的詳細(xì)用法,只不過是以常量來進(jìn)行演示,此小姐用變量來進(jìn)行演示。使用不同的格式控制符可以輸出不同類型的整數(shù),它們分別是:
%hd用來輸出 short int 類型,hd 是 short decimal 的簡寫
%d用來輸出 int 類型,d 是 decimal 的簡寫
%ld用來輸出 long int 類型,ld 是 long decimal 的簡寫
%lld用來輸出 long long int 類型,lld 是 long long decimal 的簡寫
下面的例子演示了不同整型的輸出:
#include <stdio.h>
int main()
{
short a = 10;
int b = 100;
long c = 9437;
long long d = 102023;
printf("a=%hd, b=%d, c=%ld, d=%lld\n", a, b, c, d);
return 0;
}
運(yùn)行結(jié)果如下圖所示:
在編寫代碼的過程中,我建議將格式控制符和數(shù)據(jù)類型嚴(yán)格對應(yīng)起來,養(yǎng)成良好的編程習(xí)慣。當(dāng)然,如果你不嚴(yán)格對應(yīng),一般也不會導(dǎo)致錯誤,例如,很多初學(xué)者都使用 %d
輸出所有的整數(shù)類型,請看下面的例子:
#include <stdio.h>
int main()
{
short a = 10;
int b = 100;
long c = 9437;
long long d = 102023;
printf("a=%d, b=%d, c=%d, d=%d\n", a, b, c, d);
return 0;
}
運(yùn)行結(jié)果仍然是:
當(dāng)使用 %d
輸出 short,或者使用 %ld
輸出 short、int,又或者使用 %lld
輸出 short、int 和 long 時,不管值有多大,都不會發(fā)生錯誤,因?yàn)楦袷娇刂品銐蛉菁{這些值。當(dāng)使用 %hd
輸出 int、long、long long,或者使用 %d
輸出 long、long long,又或者使用 %ld
輸出 long long 時,如果要輸出的值比較小(就像上面的情況),一般也不會發(fā)生錯誤,如果要輸出的值比較大,就很有可能發(fā)生錯誤,例如:
#include <stdio.h>
int main()
{
int m = 306587;
long n = 28166459852;
printf("m=%hd, n=%hd\n", m, n);
printf("n=%d\n", n);
return 0;
}
運(yùn)行結(jié)果為:
輸出結(jié)果完全是錯誤的,這是因?yàn)?%hd
容納不下 m 和 n 的值,%d
也容納不下 n 的值。讀者需要注意,當(dāng)格式控制符和數(shù)據(jù)類型不匹配時,編譯器會給出警告,提示程序員可能會存在風(fēng)險。編譯器的警告是分等級的,不同程度的風(fēng)險被劃分成了不同的警告等級,而使用 %d
輸出 short、long 和 long long 類型的風(fēng)險較低,如果你的編譯器設(shè)置只對較高風(fēng)險的操作發(fā)出警告,那么此處你就看不到警告信息。
4.4 不同整型的后綴
大家已經(jīng)學(xué)完了 short、int、long 和 long long 四種整數(shù)類型,假設(shè)程序中有一個整數(shù) 100,它是什么類型的呢?實(shí)際開發(fā)中用到的整數(shù),它們的值通常不會很大,類型默認(rèn)為 int。如果整數(shù)的值很大,需要占用比 int 更多的內(nèi)存,它的類型可能是 long、long long 等(還可能是無符號整形,后續(xù)會講)。舉個簡單的例子:
long a = 100;
int b = 294;
單純看 100 和 294 這兩個整數(shù),它們的類型都是 int。分析這兩行代碼:第一行:變量 a 的類型是 long,整數(shù) 100 的類型是 int,它們的類型不同,編譯器會先將 100 的類型轉(zhuǎn)換成 long,再將它賦值給 a;第二行:變量 b 的類型是 int,整數(shù) 294 的類型是 int,它們的類型相同,編譯器直接將 294 賦值給變量 b。關(guān)于數(shù)據(jù)類型的轉(zhuǎn)換,這里先簡單了解一下,我們將在后續(xù)的文章中深入探討。對于數(shù)值不是很大的整數(shù),我們也可以手動指定它們的類型為 long 和 long long,具體寫法是:
整數(shù)后面緊跟 l(小寫的 L)或者 L,表明它的類型是 long;
整數(shù)后面緊跟 ll(小寫的 LL)或者 LL,表明它的類型是 long long。
舉個簡單的例子:
int a = 10;
long b = 100L;
long long c = 1000LL;
short d = 32L;
其中,10 的類型是 int,100 和 32 的類型是 long,1000 的類型是 long long。我們習(xí)慣把 L、LL 這樣的類型標(biāo)識叫做整數(shù)的后綴。再次強(qiáng)調(diào),一個整數(shù)賦值給某個變量時,它們的類型不一定相同,比如將 100L 賦值給 b,它們的類型是相同的;再比如將 32L 賦值給 d,它們的類型是不同的,編譯器會先將 32 轉(zhuǎn)換為 short 類型,然后再賦值給 d。對于初學(xué)者,很少會用到數(shù)字的后綴,加不加往往沒有什么區(qū)別,也不影響實(shí)際編程。但是既然系統(tǒng)地學(xué)習(xí) C語言,這個知識點(diǎn)還是要掌握的,否則哪天看到別人的代碼這樣寫,你卻不明白怎么回事,那就尷尬了。
4.5 C語言中的二進(jìn)制數(shù)、八進(jìn)制數(shù)和十六進(jìn)制數(shù)
C語言中的整數(shù)除了可以使用十進(jìn)制,還可以使用二進(jìn)制、八進(jìn)制和十六進(jìn)制。
4.5.1 二進(jìn)制數(shù)、八進(jìn)制數(shù)和十六進(jìn)制數(shù)的表示
一個數(shù)字默認(rèn)就是十進(jìn)制的,表示一個十進(jìn)制數(shù)字不需要任何特殊的格式。但是,表示一個二進(jìn)制、八進(jìn)制或者十六進(jìn)制數(shù)字就不一樣了,為了和十進(jìn)制數(shù)字區(qū)分開來,必須采用某種特殊的寫法,具體來說,就是在數(shù)字前面加上特定的字符,也就是加前綴。
1) 二進(jìn)制: 二進(jìn)制由 0 和 1 兩個數(shù)字組成,使用時必須以 0b或0B
, 例如:
//合法的二進(jìn)制
int a = 0b101; //換算成十進(jìn)制為 5
int b = -0b110010; //換算成十進(jìn)制為 -50
int c = 0B100001; //換算成十進(jìn)制為 33
//非法的二進(jìn)制
int m = 101010; //無前綴 0B,相當(dāng)于十進(jìn)制
int n = 0B410; //4不是有效的二進(jìn)制數(shù)字
讀者請注意,標(biāo)準(zhǔn)的C語言并不支持上面的二進(jìn)制寫法,只是有些編譯器自己進(jìn)行了擴(kuò)展,才支持二進(jìn)制數(shù)字。換句話說,并不是所有的編譯器都支持二進(jìn)制數(shù)字,只有一部分編譯器支持,并且跟編譯器的版本有關(guān)系,有興趣的讀者可以自行測試。
2) 八進(jìn)制: 八進(jìn)制由 0~7 八個數(shù)字組成,使用時必須以0開頭(注意是數(shù)字 0,不是字母 o),例如:
//合法的八進(jìn)制數(shù)
int a = 015; //換算成十進(jìn)制為 13
int b = -0101; //換算成十進(jìn)制為 -65
int c = 0177777; //換算成十進(jìn)制為 65535
//非法的八進(jìn)制
int m = 256; //無前綴 0,相當(dāng)于十進(jìn)制
int n = 03A2; //A不是有效的八進(jìn)制數(shù)字
3) 十六進(jìn)制: 十六進(jìn)制由數(shù)字 0~9
、 字母 A~F
或 a~f
(不區(qū)分大小寫)組成,使用時必須以 0x或0X
開頭,例如:
//合法的十六進(jìn)制
int a = 0X2A; //換算成十進(jìn)制為 42
int b = -0XA0; //換算成十進(jìn)制為 -160
int c = 0xffff; //換算成十進(jìn)制為 65535
//非法的十六進(jìn)制
int m = 5A; //沒有前綴 0X,是一個無效數(shù)字
int n = 0X3H; //H不是有效的十六進(jìn)制數(shù)字
4) 十進(jìn)制: 十進(jìn)制由 0~9
十個數(shù)字組成,沒有任何前綴,和我們平時的書寫格式一樣,不再贅述。
4.5.2 二進(jìn)制數(shù)、八進(jìn)制數(shù)和十六進(jìn)制數(shù)的輸出
C語言中常用的整數(shù)有 short、int、long 和 long long 四種類型,通過 printf 函數(shù),可以將它們以八進(jìn)制、十進(jìn)制和十六進(jìn)制的形式輸出。上小節(jié)我們講解了如何以十進(jìn)制的形式輸出,本小節(jié)我們重點(diǎn)講解如何以八進(jìn)制和十六進(jìn)制的形式輸出,下表列出了不同類型的整數(shù)、以不同進(jìn)制的形式輸出時對應(yīng)的格式控制符:
十六進(jìn)制數(shù)字的表示用到了英文字母,有大小寫之分,要在格式控制符中體現(xiàn)出來:
%hx、%x、%lx 和 %llx 中的x小寫,表明以小寫字母的形式輸出十六進(jìn)制數(shù)
%hX、%X、%lX 和 %llX 中的X大寫,表明以大寫字母的形式輸出十六進(jìn)制數(shù)
八進(jìn)制數(shù)字和十進(jìn)制數(shù)字不區(qū)分大小寫,所以格式控制符都用小寫形式。如果你比較叛逆,想使用大寫形式,那么行為是未定義的,請你慎重:
有些編譯器支持大寫形式,只不過行為和小寫形式一樣
有些編譯器不支持大寫形式,可能會報錯,也可能會導(dǎo)致奇怪的輸出
注意,雖然部分編譯器支持二進(jìn)制數(shù)字的表示,但是卻不能使用 printf 函數(shù)輸出二進(jìn)制,這一點(diǎn)比較遺憾。當(dāng)然,通過轉(zhuǎn)換函數(shù)可以將其它進(jìn)制數(shù)字轉(zhuǎn)換成二進(jìn)制數(shù)字,并以字符串的形式存儲,然后在 printf 函數(shù)中使用 %s
輸出即可??紤]到讀者的基礎(chǔ)還不夠,這里就先不講這種方法了。舉例,以不同進(jìn)制的形式輸出整數(shù):
#include <stdio.h>
int main()
{
short a = 0b1010; //二進(jìn)制數(shù)字
int b = 012; //八進(jìn)制數(shù)字
long c = 0XA; //十六進(jìn)制數(shù)字
long long d = 10;
printf("a=%ho, b=%o, c=%lo, d=%llo\n", a, b, c, d); //以八進(jìn)制形似輸出
printf("a=%hd, b=%d, c=%ld, d=%lld\n", a, b, c, d); //以十進(jìn)制形式輸出
printf("a=%hx, b=%x, c=%lx, d=%llx\n", a, b, c, d); //以十六進(jìn)制形式輸出(字母小寫)
printf("a=%hX, b=%X, c=%lX, d=%llX\n", a, b, c, d); //以十六進(jìn)制形式輸出(字母大寫)
return 0;
}
運(yùn)行結(jié)果如下圖所示:
從這個例子可以發(fā)現(xiàn),一個數(shù)字不管以何種進(jìn)制來表示,都能夠以任意進(jìn)制的形式輸出。數(shù)字在內(nèi)存中始終以二進(jìn)制的形式存儲,其它進(jìn)制的數(shù)字在存儲前都必須轉(zhuǎn)換為二進(jìn)制形式;同理,一個數(shù)字在輸出時要進(jìn)行逆向的轉(zhuǎn)換,也就是從二進(jìn)制轉(zhuǎn)換為其他進(jìn)制。
輸出時加上前綴: 請讀者注意觀察上面的例子,會發(fā)現(xiàn)有一點(diǎn)不完美,如果只看輸出結(jié)果:
對于八進(jìn)制數(shù)字,它沒法和十進(jìn)制、十六進(jìn)制區(qū)分,因?yàn)榘诉M(jìn)制、十進(jìn)制和十六進(jìn)制都包含 0~7 這幾個數(shù)字。
對于十進(jìn)制數(shù)字,它沒法和十六進(jìn)制區(qū)分,因?yàn)槭M(jìn)制也包含0~9這幾個數(shù)字.如果十進(jìn)制數(shù)字中還不包含8和9,那么也不能和八進(jìn)制區(qū)分了
對于十六進(jìn)制數(shù)字,如果沒有包含 a~f 或者 A~F,那么就無法和十進(jìn)制區(qū)分,如果還不包含 8 和 9,那么也不能和八進(jìn)制區(qū)分了。
區(qū)分不同進(jìn)制數(shù)字的一個簡單辦法就是,在輸出時帶上特定的前綴。在格式控制符中加上 #
即可輸出前綴,例如 %#x,%#o,%#lX,%#ho
等,請看下面的代碼:
#include <stdio.h>
int main()
{
short a = 0b1010; //二進(jìn)制數(shù)字
int b = 012; //八進(jìn)制數(shù)字
long c = 0XA; //十六進(jìn)制數(shù)字
long long d = 10;
printf("a=%#ho, b=%#o, c=%#lo, d=%#llo\n", a, b, c, d); //以八進(jìn)制形似輸出
printf("a=%hd, b=%d, c=%ld, d=%lld\n", a, b, c, d); //以十進(jìn)制形式輸出
printf("a=%#hx, b=%#x, c=%#lx, d=%#llx\n", a, b, c, d); //以十六進(jìn)制形式輸出(字母小寫)
printf("a=%#hX, b=%#X, c=%#lX, d=%#llX\n", a, b, c, d); //以十六進(jìn)制形式輸出(字母大寫)
return 0;
}
程序運(yùn)行結(jié)果如下圖所示:
十進(jìn)制數(shù)字沒有前綴,所以不用加 #
。 如果你加上了,那么它的行為是未定義的,有的編譯器支持十進(jìn)制加 #
,只不過輸出結(jié)果和沒有加 #
一樣,有的編譯器不支持加#,可能會報錯,也可能會導(dǎo)致奇怪的輸出;但是,大部分編譯器都能正常輸出,不至于當(dāng)成一種錯誤。
4.6 C語言中的正負(fù)數(shù)及其輸出
在數(shù)學(xué)中,數(shù)字有正負(fù)之分。在C語言中也是一樣,short、int、long 和 long long 都可以帶上正負(fù)號,例如:
//負(fù)數(shù)
short a1 = -10;
short a2 = -0x2dc9; //十六進(jìn)制
//正數(shù)
int b1 = +10;
int b2 = +0174; //八進(jìn)制
int b3 = 22910;
//負(fù)數(shù)和正數(shù)相加
long c = (-9) + (+12);
long long d = 5 + (-3);
如果不帶正負(fù)號,默認(rèn)就是正數(shù)。符號也是數(shù)字的一部分,也要在內(nèi)存中體現(xiàn)出來。符號只有正負(fù)兩種情況,用 1 位(Bit)就足以表示;C語言規(guī)定,把內(nèi)存的最高位作為符號位。 以 int 為例,它占用 32 位的內(nèi)存,0~30 位表示數(shù)值,31 位表示正負(fù)號。如下圖所示:
在編程語言中,計數(shù)往往是從 0 開始,例如字符串 "abc123"
, 我們稱第 0 個字符是 a,第 1 個字符是 b,第 5 個字符是 3。這和我們平時從 1 開始計數(shù)的習(xí)慣不一樣,大家要慢慢適應(yīng),培養(yǎng)編程思維。C語言規(guī)定,在符號位中,用 0 表示正數(shù),用 1 表示負(fù)數(shù)。例如 int 類型的 -10 和 +16 在內(nèi)存中的表示如下:
short、int、long 和 long long 類型默認(rèn)都是帶符號位的,符號位以外的內(nèi)存才是數(shù)值位。C語言提供了 signed 關(guān)鍵字,加到數(shù)據(jù)類型的前面,可以強(qiáng)調(diào)此類型是帶符號位的,例如:
signed short a = -10;
signed int b = -100;
signed long c = -1000;
signed long long d = -10000;
以 short 為例,signed short 和 short 其實(shí)是同一種類型,所以我們一般會省略 signed,直接寫 short。short、int、long 和 long long 能表示正數(shù)和負(fù)數(shù),如果只考慮正數(shù),那么各種類型能表示的數(shù)值范圍 (取值范圍) 就比原來小了一半。但是在很多情況下,我們非常確定某個數(shù)字只能是正數(shù),比如班級學(xué)生的人數(shù)、字符串的長度、內(nèi)存地址等,這個時候符號位就是多余的了,就不如刪掉符號位,把所有的位都用來存儲數(shù)值,這樣能表示的數(shù)值范圍更大(大一倍)。C語言允許我們這樣做,如果不希望設(shè)置符號位,可以在數(shù)據(jù)類型前面加上 unsigned 關(guān)鍵字,例如:
unsigned short a = 10;
unsigned int b = 100;
unsigned long c = 1000;
unsigned long long d = 10000;
這樣,short、int、long 和 long long 中就沒有符號位了,所有的位都用來表示數(shù)值,正數(shù)的取值范圍更大了。這也意味著,使用了 unsigned 后只能表示正數(shù),不能再表示負(fù)數(shù)了。如果將一個數(shù)字分為符號和數(shù)值兩部分,那么不加 unsigned 的數(shù)字稱為有符號數(shù),能表示正數(shù)和負(fù)數(shù),加了 unsigned 的數(shù)字稱為無符號數(shù),只能表示正數(shù)。請讀者注意一個小細(xì)節(jié),如果是 unsigned int 類型,那么可以省略 int ,只寫 unsigned,例如:
unsigned n = 100; // 等價于: unsigned int n = 100;
無符號數(shù)的輸出: 無符號數(shù)可以以八進(jìn)制、十進(jìn)制和十六進(jìn)制的形式輸出,它們對應(yīng)的格式控制符分別為:
上一小節(jié)我們也講到了不同進(jìn)制形式的輸出,但是上節(jié)我們還沒有講到正負(fù)數(shù),所以也沒有關(guān)心這一點(diǎn),只是 籠統(tǒng)
地介紹了一遍?,F(xiàn)在本節(jié)已經(jīng)講到了正負(fù)數(shù),那我們就再深入地說一下。嚴(yán)格來說,格式控制符和整數(shù)的符號是緊密相關(guān)的,具體就是:
%d 以十進(jìn)制形式輸出有符號數(shù)
%u 以十進(jìn)制形式輸出無符號數(shù)
%o 以八進(jìn)制形式輸出無符號數(shù)
%x 以十六進(jìn)制形式輸出無符號數(shù)
那么,如何以八進(jìn)制和十六進(jìn)制形式輸出有符號數(shù)呢?很遺憾,printf 并不支持,也沒有對應(yīng)的格式控制符。在實(shí)際開發(fā)中,也基本沒有 輸出負(fù)的八進(jìn)制數(shù)或者十六進(jìn)制數(shù)
這樣的需求,我想可能正是因?yàn)檫@一點(diǎn),printf 才沒有提供對應(yīng)的格式控制符。下表全面地總結(jié)了不同類型的整數(shù),以不同進(jìn)制的形式輸出時對應(yīng)的格式控制符(–表示沒有對應(yīng)的格式控制符)。
有讀者可能會問,上一小節(jié)我們也使用 %o 和 %x 來輸出有符號數(shù)了,為什么沒有發(fā)生錯誤呢?這是因?yàn)椋寒?dāng)以有符號數(shù)的形式輸出時,printf 會讀取數(shù)字所占用的內(nèi)存,并把最高位作為符號位,把剩下的內(nèi)存作為數(shù)值位;當(dāng)以無符號數(shù)的形式輸出時,printf 也會讀取數(shù)字所占用的內(nèi)存,并把所有的內(nèi)存都作為數(shù)值位對待。對于一個有符號的正數(shù),它的符號位是 0,當(dāng)按照無符號數(shù)的形式讀取時,符號位就變成了數(shù)值位,但是該位恰好是 0 而不是 1,所以對數(shù)值不會產(chǎn)生影響,這就好比在一個數(shù)字前面加 0,有多少個 0 都不會影響數(shù)字的值。如果對一個有符號的負(fù)數(shù)使用 %o 或者 %x 輸出,那么結(jié)果就會大相徑庭,讀者可以親試??梢哉f,有符號正數(shù)的最高位是 0
這個巧合才使得 %o 和 %x 輸出有符號數(shù)時不會出錯。再次強(qiáng)調(diào),不管是以 %o、%u、%x 輸出有符號數(shù),還是以 %d 輸出無符號數(shù),編譯器都不會報錯,只是對內(nèi)存的解釋不同了。%o、%d、%u、%x 這些格式控制符不會關(guān)心數(shù)字在定義時到底是有符號的還是無符號的:
你讓我輸出無符號數(shù),那我在讀取內(nèi)存時就不區(qū)分符號位和數(shù)值位了,我會把所有的內(nèi)存都看做數(shù)值位
你讓我輸出有符號數(shù),那我在讀取內(nèi)存時會把最高位作為符號位,把剩下的內(nèi)存作為數(shù)值位
說得再直接一些,我管你在定義時是有符號數(shù)還是無符號數(shù)呢,我只關(guān)心內(nèi)存,有符號數(shù)也可以按照無符號數(shù)輸出,無符號數(shù)也可以按照有符號數(shù)輸出,至于輸出結(jié)果對不對,那我就不管了,你自己承擔(dān)風(fēng)險。下面的代碼進(jìn)行了全面的演示:
#include <stdio.h>
int main()
{
short a = 0100; //八進(jìn)制
int b = -0x1; //十六進(jìn)制
long c = 720; //十進(jìn)制
unsigned short m = 0xffff; //十六進(jìn)制
unsigned int n = 0x80000000; //十六進(jìn)制
unsigned long p = 100; //十進(jìn)制
//以無符號的形式輸出有符號數(shù)
printf("a=%#ho, b=%#x, c=%ld\n", a, b, c);
//以有符號數(shù)的形式輸出無符號類型(只能以十進(jìn)制形式輸出)
printf("m=%hd, n=%d, p=%ld\n", m, n, p);
return 0;
}
程序運(yùn)行結(jié)果如下圖所示:
對于絕大多數(shù)初學(xué)者來說,b、m、n 的輸出結(jié)果看起來非常奇怪,甚至不能理解。按照一般的推理,b、m、n 這三個整數(shù)在內(nèi)存中的存儲形式分別是:
當(dāng)以 %x 輸出 b 時,結(jié)果應(yīng)該是 0x80000001;當(dāng)以 %hd、%d 輸出 m、n 時,結(jié)果應(yīng)該分別是 -7fff、-0。但是實(shí)際的輸出結(jié)果和我們推理的結(jié)果卻大相徑庭,這是為什么呢?
注意,-7fff 是十六進(jìn)制形式。%d 本來應(yīng)該輸出十進(jìn)制,這里只是為了看起來方便,才改為十六進(jìn)制。
其實(shí)這跟整數(shù)在內(nèi)存中的存儲形式以及讀取方式有關(guān)。b 是一個有符號的負(fù)數(shù),它在內(nèi)存中并不是像上圖演示的那樣存儲,而是要經(jīng)過一定的轉(zhuǎn)換才能寫入內(nèi)存;m、n 的內(nèi)存雖然沒有錯誤,但是當(dāng)以 %d 輸出時,并不是原樣輸出,而是有一個逆向的轉(zhuǎn)換過程(和存儲時的轉(zhuǎn)換過程恰好相反)。也就是說,整數(shù)在寫入內(nèi)存之前可能會發(fā)生轉(zhuǎn)換,在讀取時也可能會發(fā)生轉(zhuǎn)換,而我們沒有考慮這種轉(zhuǎn)換,所以才會導(dǎo)致推理錯誤。那么,整數(shù)在寫入內(nèi)存前,以及在讀取時究竟發(fā)生了怎樣的轉(zhuǎn)換呢?為什么會發(fā)生這種轉(zhuǎn)換呢?參考文章:https://blog.csdn.net/xw1680/article/details/132417469
無符號數(shù)的后綴: C語言中的整數(shù)默認(rèn)都是有符號數(shù),它們的類型通常是 int,添加后綴 l(L)
或者 ll(LL)
可以指定它們的類型為 long 或者 long long。
通過給正整數(shù)添加以下幾種后綴,可以將它指定為無符號數(shù):
正整數(shù)后面緊跟 u 或者 U,表明它的類型是 unsigned int
正整數(shù)后面緊跟 ul 或者 UL,表明它的類型是 unsigned long
正整數(shù)后面緊跟 ull 或者 ULL,表明它的類型是 unsigned long long
請看下面的代碼:
unsigned int a = 10U;
unsigned long b = 100UL;
unsigned long long c = 1000ULL;
其中,100U 是 unsigned int 類型的無符號數(shù);100UL 是 unsigned long 類型的無符號數(shù);1000ULL 是 unsigned long long 類型的無符號數(shù)。對于初學(xué)者,很少會用到數(shù)字的后綴,加不加往往沒有什么區(qū)別,也不影響實(shí)際編程,但是既然學(xué)了C語言,還是要知道這個知識點(diǎn)的,萬一看到別人的代碼這么用了,而你卻不明白怎么回事,那就尷尬了。
4.7 C語言整數(shù)的取值范圍以及數(shù)值溢出
short、int、long 和 long long 是 C語言中常用的四種整數(shù)類型,分別稱為短整型、整型、長整型和超長整形。在現(xiàn)代操作系統(tǒng)中,short、int、long、long long 的長度分別是 2、4、4 或者 8、8,它們只能存儲有限的數(shù)值,當(dāng)數(shù)值過大或者過小時,超出的部分會被直接截掉,數(shù)值就不能正確存儲了,我們將這種現(xiàn)象稱為 溢出(Overflow)。 溢出的簡單理解就是,向木桶里面倒入了過量的水,木桶盛不了了,水就流出來了。要想知道數(shù)值什么時候溢出,就得先知道各種整數(shù)類型的取值范圍。
4.7.1 無符號數(shù)的取值范圍
計算無符號數(shù) (unsigned 類型) 的取值范圍(或者說最大值和最小值)很容易,將內(nèi)存中的所有位(Bit) 都置為 1 就是最大值,都置為 0 就是最小值。以 unsigned char 類型為例,它的長度是 1,占用 8 位的內(nèi)存,所有位都置為 1 時,它的值為 28 - 1 = 255,所有位都置為 0 時,它的值很顯然為 0。由此可得,unsigned char 類型的取值范圍是 0~255
。
char 是一個字符類型,是用來存放字符的,但是它同時也是一個整數(shù)類型,也可以用來存放整數(shù),請大家暫時先記住這一點(diǎn),更多細(xì)節(jié)我們將在 C語言變量和數(shù)據(jù)類型-下篇 一文中進(jìn)行介紹。
有讀者可能會對 unsigned char 的最大值有疑問,究竟是怎么計算出來的呢?下面我就講解一下這個小技巧。將 unsigned char 的所有位都置為 1,它在內(nèi)存中的表示形式為 1111 1111,最直接的計算方法就是:
20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 = 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = 255
這種 按部就班
的計算方法雖然有效,但是比較麻煩,如果是 8 個字節(jié)的 long 類型,那足夠你計算半個小時的了。我們不妨換一種思路,先給 1111 1111 加上 1,然后再減去 1,這樣一增一減正好抵消掉,不會影響最終的值。給 1111 1111 加上 1 的計算過程為:
0B1111 1111 + 0B1 = 0B1 0000 0000 = 28 = 256
可以發(fā)現(xiàn),1111 1111 加上 1 后需要向前進(jìn)位(向第 9 位進(jìn)位),剩下的 8 位都變成了 0,這樣一來,只有第 9 位會影響到數(shù)值的計算,剩下的 8 位對數(shù)值都沒有影響。第 9 位的權(quán)值計算起來非常容易,就是:29-1 = 28 = 256 然后再減去 1:
28 - 1 = 256 - 1 = 255
加上 1 是為了便于計算,減去 1 是為了還原本來的值;當(dāng)內(nèi)存中所有的位都是 1 時,這種 湊整
的技巧非常實(shí)用。按照這種巧妙的方法,我們可以很容易地計算出所有無符號數(shù)的取值范圍(括號內(nèi)為假設(shè)的長度):
4.7.2 有符號數(shù)的取值范圍
有符號數(shù)以補(bǔ)碼的形式存儲,計算取值范圍也要從補(bǔ)碼入手。我們以 char 類型為例,從下表中找出它的取值范圍:
我們按照從大到小的順序?qū)⒀a(bǔ)碼羅列出來,很容易發(fā)現(xiàn)最大值和最小值。淡黃色背景的那一行是我要重點(diǎn)說明的。如果按照傳統(tǒng)的由補(bǔ)碼計算原碼的方法,那么 1000 0000 是無法計算的,因?yàn)橛嬎惴创a時要減去 1,1000 0000 需要向高位借位,而高位是符號位,不能借出去,所以這就很矛盾。是不是該把 1000 0000 作為無效的補(bǔ)碼直接丟棄呢?然而,作為無效值就不如作為特殊值,這樣還能多存儲一個數(shù)字。計算機(jī)規(guī)定,1000 0000 這個特殊的補(bǔ)碼就表示 -128。
為什么偏偏是 -128 而不是其它的數(shù)字呢?首先,-128 使得 char 類型的取值范圍保持連貫,中間沒有 空隙
。 其次,我們再按照“傳統(tǒng)”的方法計算一下 -128 的補(bǔ)碼:-128 的數(shù)值位的原碼是 1000 0000,共八位,而 char 的數(shù)值位只有七位,所以最高位的 1 會覆蓋符號位,數(shù)值位剩下 000 0000。最終,-128 的原碼為 1000 0000。接著很容易計算出反碼,為 1111 1111。反碼轉(zhuǎn)換為補(bǔ)碼時,數(shù)值位要加上 1,變?yōu)?1000 0000,而 char 的數(shù)值位只有七位,所以最高位的 1 會再次覆蓋符號位,數(shù)值位剩下 000 0000。最終求得的 -128 的補(bǔ)碼是 1000 0000。-128 從原碼轉(zhuǎn)換到補(bǔ)碼的過程中,符號位被 1 覆蓋了兩次,而負(fù)數(shù)的符號位本來就是 1,被 1 覆蓋多少次也不會影響到數(shù)字的符號。你看,雖然從 1000 0000 這個補(bǔ)碼推算不出 -128,但是從 -128 卻能推算出 1000 0000 這個補(bǔ)碼,這么多么的奇妙,-128 這個特殊值選得恰到好處。負(fù)數(shù)在存儲之前要先轉(zhuǎn)換為補(bǔ)碼,從 -128 推算出補(bǔ)碼 1000 0000 這一點(diǎn)非常重要,這意味著 -128 能夠正確地轉(zhuǎn)換為補(bǔ)碼,或者說能夠正確的存儲。
關(guān)于零值和最小值
仔細(xì)觀察上表可以發(fā)現(xiàn),在 char 的取值范圍內(nèi)只有一個零值,沒有 +0 和 -0 的區(qū)別,并且多存儲了一個特殊值,就是 -128,這也是采用補(bǔ)碼的另外小小的優(yōu)勢。如果直接采用原碼存儲,那么 0000 0000 和 1000 0000 將分別表示 +0 和 -0,這樣在取值范圍內(nèi)就存在兩個相同的值,多此一舉。另外,雖然最大值沒有變,仍然是 127,但是最小值卻變了,只能存儲到 -127,不能存儲 -128 了,因?yàn)?-128 的原碼為 1000 0000,這個位置已經(jīng)被 -0 占用了。
按照上面的方法,我們可以計算出所有有符號數(shù)的取值范圍(括號內(nèi)為假設(shè)的長度):
4.7.3 數(shù)值溢出
char、short、int、long 和 long long 的長度是有限的,當(dāng)數(shù)值過大或者過小時,有限的幾個字節(jié)就不能表示了,就會發(fā)生溢出。發(fā)生溢出時,輸出結(jié)果往往會變得奇怪,請看下面的代碼:
#include <stdio.h>
int main()
{
unsigned int a = 0x100000000;
int b = 0xffffffff;
printf("a=%u, b=%d\n", a, b);
return 0;
}
程序運(yùn)行結(jié)果如下圖所示:
變量 a 為 unsigned int 類型,長度為 4 個字節(jié),能表示的最大值為 0xFFFFFFFF,而 0x100000000 = 0xFFFFFFFF + 1,占用 33 位,已超出 a 所能表示的最大值,所以發(fā)生了溢出,導(dǎo)致最高位的 1 被截去,剩下的 32 位都是0。也就是說,a 被存儲到內(nèi)存后就變成了 0,printf 從內(nèi)存中讀取到的也是 0。變量 b 是 int 類型的有符號數(shù),在內(nèi)存中以補(bǔ)碼的形式存儲。0xffffffff 的數(shù)值位的原碼為 1111 1111 …… 1111 1111,共 32 位,而 int 類型的數(shù)值位只有 31 位,所以最高位的 1 會覆蓋符號位,數(shù)值位只留下 31 個 1,所以 b 的原碼為:1111 1111 …… 1111 1111,這也是 b 在內(nèi)存中的存儲形式。當(dāng) printf 讀取到 b 時,由于最高位是 1,所以會被判定為負(fù)數(shù),要從補(bǔ)碼轉(zhuǎn)換為原碼:
[1111 1111 …… 1111 1111]補(bǔ)
= [1111 1111 …… 1111 1110]反
= [1000 0000 …… 0000 0001]原
= -1
最終 b 的輸出結(jié)果為 -1。
如果想要很好的理解本文后續(xù)所講的內(nèi)容,一定要仔細(xì)認(rèn)真閱讀文章 計算機(jī)組成原理之?dāng)?shù)據(jù)的表示和運(yùn)算(一): https://blog.csdn.net/xw1680/article/details/132417469
至此今天的學(xué)習(xí)就到此結(jié)束了,筆者在這里聲明,筆者寫文章只是為了學(xué)習(xí)交流,以及讓更多學(xué)習(xí)C語言的讀者少走一些彎路,節(jié)省時間,并不用做其他用途,如有侵權(quán),聯(lián)系博主刪除即可。感謝您閱讀本篇博文,希望本文能成為您編程路上的領(lǐng)航者。祝您閱讀愉快!
文章來源:http://www.zghlxwxcb.cn/news/detail-754255.html
????好書不厭讀百回,熟讀課思子自知。而我想要成為全場最靚的仔,就必須堅持通過學(xué)習(xí)來獲取更多知識,用知識改變命運(yùn),用博客見證成長,用行動證明我在努力。
????如果我的博客對你有幫助、如果你喜歡我的博客內(nèi)容,請點(diǎn)贊
、評論
、收藏
一鍵三連哦!聽說點(diǎn)贊的人運(yùn)氣不會太差,每一天都會元?dú)鉂M滿呦!如果實(shí)在要白嫖的話,那祝你開心每一天,歡迎常來我博客看看。
?編碼不易,大家的支持就是我堅持下去的動力。點(diǎn)贊后不要忘了關(guān)注
我哦!文章來源地址http://www.zghlxwxcb.cn/news/detail-754255.html
到了這里,關(guān)于C語言系統(tǒng)化精講(三):C語言變量和數(shù)據(jù)類型-上篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!