C語言變量及數據類型詳解


變量

變量(variable)可以理解成一塊內存區域的名字。通過變量名,可以引用這塊內存區域,獲取里面存儲的值。由於值可能發生變化,所以稱為變量,否則就是常量了。

變量名

變量名在 C 語言里面屬於標識符(identifier),命名有嚴格的規范。

  • 只能由字母(包括大寫和小寫)、數字和下划線(_)組成。
  • 不能以數字開頭。
  • 長度不能超過63個字符。

下面是一些無效變量名的例子。

$zj
j**p
2cat
Hot-tab
tax rate
don't

上面示例中,每一行的變量名都是無效的。

變量名區分大小寫,starStarSTAR都是不同的變量。

並非所有的詞都能用作變量名,有些詞在 C 語言里面有特殊含義(比如int),另一些詞是命令(比如continue),它們都稱為關鍵字,不能用作變量名。另外,C 語言還保留了一些詞,供未來使用,這些保留字也不能用作變量名。下面就是 C 語言主要的關鍵字和保留字。

auto, break, case, char, const, continue, default, do, double, else, enum, extern, float, for, goto, if, inline, int, long, register, restrict, return, short, signed, sizeof, static, struct, switch, typedef, union, unsigned, void, volatile, while

另外,兩個下划線開頭的變量名,以及一個下划線 + 大寫英文字母開頭的變量名,都是系統保留的,自己不應該起這樣的變量名。

變量的聲明

C 語言的變量,必須先聲明后使用。如果一個變量沒有聲明,就直接使用,會報錯。

每個變量都有自己的類型(type)。聲明變量時,必須把變量的類型告訴編譯器。

int height;

上面代碼聲明了變量height,並且指定類型為int(整數)。

如果幾個變量具有相同類型,可以在同一行聲明。

int height, width;

// 等同於
int height;
int width;

注意,聲明變量的語句必須以分號結尾。

一旦聲明,變量的類型就不能在運行時修改。

變量的賦值

C 語言會在變量聲明時,就為它分配內存空間,但是不會清除內存里面原來的值。這導致聲明變量以后,變量會是一個隨機的值。所以,變量一定要賦值以后才能使用。

賦值操作通過賦值運算符(=)完成。

int num;
num = 42;

上面示例中,第一行聲明了一個整數變量num,第二行給這個變量賦值。

變量的值應該與類型一致,不應該賦予不是同一個類型的值,比如num的類型是整數,就不應該賦值為小數。雖然 C 語言會自動轉換類型,但是應該避免賦值運算符兩側的類型不一致。

變量的聲明和賦值,也可以寫在一行。

int num = 42;

多個相同類型變量的賦值,可以寫在同一行。

int x = 1, y = 2;

注意,賦值表達式有返回值,等於等號右邊的值。

int x, y;

x = 1;
y = (x = 2 * x);

上面代碼中,變量y的值就是賦值表達式(x = 2 * x)的返回值2

由於賦值表達式有返回值,所以 C 語言可以寫出多重賦值表達式。

int x, y, z, m, n;

x = y = z = m = n = 3;

上面的代碼是合法代碼,一次為多個變量賦值。賦值運算符是從右到左執行,所以先為n賦值,然后依次為mzyx賦值。

C 語言有左值(left value)和右值(right value)的概念。左值是可以放在賦值運算符左邊的值,一般是變量;右值是可以放在賦值運算符右邊的值,一般是一個具體的值。這是為了強調有些值不能放在賦值運算符的左邊,比如x = 1是合法的表達式,但是1 = x就會報錯。

變量的作用域

作用域(scope)指的是變量生效的范圍。C 語言的變量作用域主要有兩種:文件作用域(file scope)和塊作用域(block scope)。

文件作用域(file scope)指的是,在源碼文件頂層聲明的變量,從聲明的位置到文件結束都有效。

int x = 1;

int main(void) {
  printf("%i\n", x);
}

上面示例中,變量x是在文件頂層聲明的,從聲明位置開始的整個當前文件都是它的作用域,可以在這個范圍的任何地方讀取這個變量,比如函數main()內部就可以讀取這個變量。

塊作用域(block scope)指的是由大括號({})組成的代碼塊,它形成一個單獨的作用域。凡是在塊作用域里面聲明的變量,只在當前代碼塊有效,代碼塊外部不可見。

int a = 12;

if (a == 12) {
  int b = 99;
  printf("%d %d\n", a, b);  // 12 99
}

printf("%d\n", a);  // 12
printf("%d\n", b);  // 出錯

上面例子中,變量b是在if代碼塊里面聲明的,所以對於大括號外面的代碼,這個變量是不存在的。

代碼塊可以嵌套,即代碼塊內部還有代碼塊,這時就形成了多層的塊作用域。它的規則是:內層代碼塊可以使用外層聲明的變量,但外層不可以使用內層聲明的變量。如果內層的變量與外層同名,那么會在當前作用域覆蓋外層變量。

{
  int i = 10;

  {
    int i = 20;
    printf("%d\n", i);  // 20
  }

  printf("%d\n", i);  // 10
}

上面示例中,內層和外層都有一個變量i,每個作用域都會優先使用當前作用域聲明的i

最常見的塊作用域就是函數,函數內部聲明的變量,對於函數外部是不可見的。for循環也是一個塊作用域,循環變量只對循環體內部可見,外部是不可見的。

for (int i = 0; i < 10; i++)
  printf("%d\n", i);

printf("%d\n", i); // 出錯

上面示例中,for循環省略了大括號,但依然是一個塊作用域,在外部讀取循環變量i,編譯器就會報錯。

數據類型

C 語言的每一種數據,都是有類型(type)的,編譯器必須知道數據的類型,才能操作數據。所謂“類型”,就是相似的數據所擁有的共同特征,那么一旦知道某個值的數據類型,就能知道該值的特征和操作方式。

基本數據類型有三種:字符(char)、整數(int)和浮點數(float)。復雜的類型都是基於它們構建的。

字符類型

字符類型指的是單個字符,類型聲明使用char關鍵字。

char c = 'B';

上面示例聲明了變量c是字符類型,並將其賦值為字母B

C 語言規定,字符常量必須放在單引號里面。

在計算機內部,字符類型使用一個字節(8位)存儲。C 語言將其當作整數處理,所以字符類型就是寬度為一個字節的整數。每個字符對應一個整數(由 ASCII 碼確定),比如B對應整數66

字符類型在不同計算機的默認范圍是不一樣的。一些系統默認為-128127,另一些系統默認為0255。這兩種范圍正好都能覆蓋0127的 ASCII 字符范圍。

只要在字符類型的范圍之內,整數與字符是可以互換的,都可以賦值給字符類型的變量。

char c = 66;
// 等同於
char c = 'B';

上面示例中,變量c是字符類型,賦給它的值是整數66。這跟賦值為字符B的效果是一樣的。

兩個字符類型的變量可以進行數學運算。

char a = 'B'; // 等同於 char a = 66;
char b = 'C'; // 等同於 char b = 67;

printf("%d\n", a + b); // 輸出 133

上面示例中,字符類型變量ab相加,視同兩個整數相加。占位符%d表示輸出十進制整數,因此輸出結果為133。

單引號本身也是一個字符,如果要表示這個字符常量,必須使用反斜杠轉義。

char t = '\'';

上面示例中,變量t為單引號字符,由於字符常量必須放在單引號里面,所以內部的單引號要使用反斜杠轉義。

這種轉義的寫法,主要用來表示 ASCII 碼定義的一些無法打印的控制字符,它們也屬於字符類型的值。

  • \a:警報,這會使得終端發出警報聲或出現閃爍,或者兩者同時發生。
  • \b:退格鍵,光標回退一個字符,但不刪除字符。
  • \f:換頁符,光標移到下一頁。在現代系統上,這已經反映不出來了,行為改成類似於\v
  • \n:換行符。
  • \r:回車符,光標移到同一行的開頭。
  • \t:制表符,光標移到下一個水平制表位,通常是下一個8的倍數。
  • \v:垂直分隔符,光標移到下一個垂直制表位,通常是下一行的同一列。
  • \0:null 字符,代表沒有內容。注意,這個值不等於數字0。

轉義寫法還能使用八進制和十六進制表示一個字符。

  • \nn:字符的八進制寫法,nn為八進制值。
  • \xnn:字符的十六進制寫法,nn為十六進制值。
char x = 'B';
char x = 66;
char x = '\102'; // 八進制
char x = '\x42'; // 十六進制

上面示例的四種寫法都是等價的。

整數類型

簡介

整數類型用來表示較大的整數,類型聲明使用int關鍵字。

int a;

上面示例聲明了一個整數變量a

不同計算機的int類型的大小是不一樣的。比較常見的是使用4個字節(32位)存儲一個int類型的值,但是2個字節(16位)或8個字節(64位)也有可能使用。它們可以表示的整數范圍如下。

  • 16位:-32,768 到 32,767。
  • 32位:-2,147,483,648 到 2,147,483,647。
  • 64位:-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。

signed,unsigned

C 語言使用signed關鍵字,表示一個類型帶有正負號,包含負值;使用unsigned關鍵字,表示該類型不帶有正負號,只能表示零和正整數。

對於int類型,默認是帶有正負號的,也就是說int等同於signed int。由於這是默認情況,關鍵字signed一般都省略不寫,但是寫了也不算錯。

signed int a;
// 等同於
int a;

int類型也可以不帶正負號,只表示非負整數。這時就必須使用關鍵字unsigned聲明變量。

unsigned int a;

整數變量聲明為unsigned的好處是,同樣長度的內存能夠表示的最大整數值,增大了一倍。比如,16位的signed int最大值為32,767,而unsigned int的最大值增大到了65,535。

unsigned int里面的int可以省略,所以上面的變量聲明也可以寫成下面這樣。

unsigned a;

字符類型char也可以設置signedunsigned

signed char c; // 范圍為 -128 到 127
unsigned char c; // 范圍為 0 到 255

注意,C 語言規定char類型默認是否帶有正負號,由當前系統決定。這就是說,char不等同於signed char,它有可能是signed char,也有可能是unsigned char。這一點與int不同,int就是等同於signed int

整數的子類型

如果int類型使用4個或8個字節表示一個整數,對於小整數,這樣做很浪費空間。另一方面,某些場合需要更大的整數,8個字節還不夠。為了解決這些問題,C 語言在int類型之外,又提供了三個整數的子類型。這樣有利於更精細地限定整數變量的范圍,也有利於更好地表達代碼的意圖。

  • short int(簡寫為short):占用空間不多於int,一般占用2個字節(整數范圍為-32768~32767)。
  • long int(簡寫為long):占用空間不少於int,至少為4個字節。
  • long long int(簡寫為long long):占用空間多於long,至少為8個字節。
short int a;
long int b;
long long int c;

上面代碼分別聲明了三種整數子類型的變量。

默認情況下,shortlonglong long都是帶符號的(signed),即signed關鍵字省略了。它們也可以聲明為不帶符號(unsigned),使得能夠表示的最大值擴大一倍。

unsigned short int a;
unsigned long int b;
unsigned long long int c;

C 語言允許省略int,所以變量聲明語句也可以寫成下面這樣。

short a;
unsigned short a;

long b;
unsigned long b;

long long c;
unsigned long long c;

不同的計算機,數據類型的字節長度是不一樣的。確實需要32位整數時,應使用long類型而不是int類型,可以確保不少於4個字節;確實需要64位的整數時,應該使用long long類型,可以確保不少於8個字節。另一方面,為了節省空間,只需要16位整數時,應使用short類型;需要8位整數時,應該使用char類型。

整數類型的極限值

有時候需要查看,當前系統不同整數類型的最大值和最小值,C 語言的頭文件limits.h提供了相應的常量,比如SCHAR_MIN代表 signed char 類型的最小值-128SCHAR_MAX代表 signed char 類型的最大值127

為了代碼的可移植性,需要知道某種整數類型的極限值時,應該盡量使用這些常量。

  • SCHAR_MINSCHAR_MAX:signed char 的最小值和最大值。
  • SHRT_MINSHRT_MAX:short 的最小值和最大值。
  • INT_MININT_MAX:int 的最小值和最大值。
  • LONG_MINLONG_MAX:long 的最小值和最大值。
  • LLONG_MINLLONG_MAX:long long 的最小值和最大值。
  • UCHAR_MAX:unsigned char 的最大值。
  • USHRT_MAX:unsigned short 的最大值。
  • UINT_MAX:unsigned int 的最大值。
  • ULONG_MAX:unsigned long 的最大值。
  • ULLONG_MAX:unsigned long long 的最大值。

整數的進制

C 語言的整數默認都是十進制數,如果要表示八進制數和十六進制數,必須使用專門的表示法。

八進制使用0作為前綴,比如0170377

int a = 012; // 八進制,相當於十進制的10

十六進制使用0x0X作為前綴,比如0xf0X10

int a = 0x1A2B; // 十六進制,相當於十進制的6699

有些編譯器使用0b前綴,表示二進制數,但不是標准。

int x = 0b101010;

注意,不同的進制只是整數的書寫方法,不會對整數的實際存儲方式產生影響。所有整數都是二進制形式存儲,跟書寫方式無關。不同進制可以混合使用,比如10 + 015 + 0x20是一個合法的表達式。

printf()的進制相關占位符如下。

  • %d:十進制整數。
  • %o:八進制整數。
  • %x:十六進制整數。
  • %#o:顯示前綴0的八進制整數。
  • %#x:顯示前綴0x的十六進制整數。
  • %#X:顯示前綴0X的十六進制整數。
int x = 100;
printf("dec = %d\n", x); // 100
printf("octal = %o\n", x); // 144
printf("hex = %x\n", x); // 64
printf("octal = %#o\n", x); // 0144
printf("hex = %#x\n", x); // 0x64
printf("hex = %#X\n", x); // 0X64

浮點數類型

任何有小數點的數值,都會被編譯器解釋為浮點數。所謂“浮點數”就是使用 m * be 的形式,存儲一個數值,m是小數部分,b是基數(通常是2),e是指數部分。這種形式是精度和數值范圍的一種結合,可以表示非常大或者非常小的數。

浮點數的類型聲明使用float關鍵字,可以用來聲明浮點數變量。

float c = 10.5;

上面示例中,變量c的就是浮點數類型。

float類型占用4個字節(32位),其中8位存放指數的值和符號,剩下24位存放小數的值和符號。float類型至少能夠提供(十進制的)6位有效數字,指數部分的范圍為(十進制的)-3737,即數值范圍為10-37到1037

有時候,32位浮點數提供的精度或者數值范圍還不夠,C 語言又提供了另外兩種更大的浮點數類型。

  • double:占用8個字節(64位),至少提供13位有效數字。
  • long double:通常占用16個字節。

注意,由於存在精度限制,浮點數只是一個近似值,它的計算是不精確的,比如 C 語言里面0.1 + 0.2並不等於0.3,而是有一個很小的誤差。

if (0.1 + 0.2 == 0.3) // false

C 語言允許使用科學計數法表示浮點數,使用字母e來分隔小數部分和指數部分。

double x = 123.456e+3; // 123.456 x 10^3
// 等同於
double x = 123.456e3;

上面示例中,e后面如果是加號+,加號可以省略。注意,科學計數法里面e的前后,不能存在空格。

另外,科學計數法的小數部分如果是0.xx.0的形式,那么0可以省略。

0.3E6
// 等同於
.3E6

3.0E6
// 等同於
3.E6

布爾類型

C 語言原來並沒有為布爾值單獨設置一個類型,而是使用整數0表示偽,所有非零值表示真。

int x = 1;
if (x) {
  printf("x is true!\n");
}

上面示例中,變量x等於1,C 語言就認為這個值代表真,從而會執行判斷體內部的代碼。

C99 標准添加了類型_Bool,表示布爾值。但是,這個類型其實只是整數類型的別名,還是使用0表示偽,1表示真,下面是一個示例。

_Bool isNormal;

isNormal = 1;
if (isNormal)
  printf("Everything is OK.\n");

頭文件stdbool.h定義了另一個類型別名bool,並且定義了true代表1false代表0。只要加載這個頭文件,就可以使用這幾個關鍵字。

#include <stdbool.h>

bool flag = false;

上面示例中,加載頭文件stdbool.h以后,就可以使用bool定義布爾值類型,以及falsetrue表示真偽。

字面量的類型

字面量(literal)指的是代碼里面直接出現的值。

int x = 123;

上面代碼中,x是變量,123就是字面量。

編譯時,字面量也會寫入內存,因此編譯器必須為字面量指定數據類型,就像必須為變量指定數據類型一樣。

一般情況下,十進制整數字面量(比如123)會被編譯器指定為int類型。如果一個數值比較大,超出了int能夠表示的范圍,編譯器會將其指定為long int。如果數值超過了long int,會被指定為unsigned long。如果還不夠大,就指定為long longunsigned long long

小數(比如3.14)會被指定為double類型。

字面量后綴

有時候,程序員希望為字面量指定一個不同的類型。比如,編譯器將一個整數字面量指定為int類型,但是程序員希望將其指定為long類型,這時可以為該字面量加上后綴lL,編譯器就知道要把這個字面量的類型指定為long

int x = 123L;

上面代碼中,字面量123有后綴L,編譯器就會將其指定為long類型。這里123L寫成123l,效果也是一樣的,但是建議優先使用L,因為小寫的l容易跟數字1混淆。

八進制和十六進制的值,也可以使用后綴lL指定為 Long 類型,比如020L0x20L

int y = 0377L;
int z = 0x7fffL;

如果希望指定為無符號整數unsigned int,可以使用后綴uU

int x = 123U;

LU可以結合使用,表示unsigned long類型。LU的大小寫和組合順序無所謂。

int x = 123LU;

對於浮點數,編譯器默認指定為 double 類型,如果希望指定為其他類型,需要在小數后面添加后綴f(float)或l(long double)。

科學計數法也可以使用后綴。

1.2345e+10F
1.2345e+10L

總結一下,常用的字面量后綴有下面這些。

  • fFfloat類型。
  • lL:對於整數是long int類型,對於小數是long double類型。
  • llLL:Long Long 類型,比如3LL
  • uU:表示unsigned int,比如15U0377U

u還可以與其他整數后綴結合,放在前面或后面都可以,比如10UL10ULL10LLU都是合法的。

下面是一些示例。

int           x = 1234;
long int      x = 1234L;
long long int x = 1234LL

unsigned int           x = 1234U;
unsigned long int      x = 1234UL;
unsigned long long int x = 1234ULL;

float x       = 3.14f;
double x      = 3.14;
long double x = 3.14L;

溢出

每一種數據類型都有數值范圍,如果存放的數值超出了這個范圍(小於最小值或大於最大值),需要更多的二進制位存儲,就會發生溢出。大於最大值,叫做向上溢出(overflow);小於最小值,叫做向下溢出(underflow)。

一般來說,編譯器不會對溢出報錯,會正常執行代碼,但是會忽略多出來的二進制位,只保留剩下的位,這樣往往會得到意想不到的結果。所以,應該避免溢出。

unsigned char x = 255;
x = x + 1;

printf("%d\n", x); // 0

上面示例中,變量x1,得到的結果不是256,而是0。因為xunsign char類型,最大值是255(二進制11111111),加1后就發生了溢出,256(二進制100000000)的最高位1被丟棄,剩下的值就是0

再看下面的例子。

unsigned int ui = UINT_MAX;  // 4,294,967,295
ui++;
printf("ui = %u\n", ui); // 0
ui--;
printf("ui = %u\n", ui); // 4,294,967,295

上面示例中,常量UINT_MAX是 unsigned int 類型的最大值。如果加1,對於該類型就會溢出,從而得到0;而0是該類型的最小值,再減1,又會得到UINT_MAX

溢出很容易被忽視,編譯器又不會報錯,所以必須非常小心。

for (unsigned int i = n; i >= 0; --i) // 錯誤

上面代碼表面看似乎沒有問題,但是循環變量i的類型是 unsigned int,這個類型的最小值是0,不可能得到小於0的結果。當i等於0,再減去1的時候,並不會返回-1,而是返回 unsigned int 的類型最大值,這個值總是大於等於0,導致無限循環。

為了避免溢出,最好方法就是將運算結果與類型的極限值進行比較。

unsigned int ui;
unsigned int sum;

// 錯誤
if (sum + ui > UINT_MAX) too_big();
else sum = sum + ui;

// 正確
if (ui > UINT_MAX - sum) too_big();
else sum = sum + ui;

上面示例中,變量sumui都是 unsigned int 類型,它們相加的和還是 unsigned int 類型,這就有可能發生溢出。但是,不能通過相加的和是否超出了最大值UINT_MAX,來判斷是否發生了溢出,因為sum + ui總是返回溢出后的結果,不可能大於UINT_MAX。正確的比較方法是,判斷UINT_MAX - sumui之間的大小關系。

下面是另一種錯誤的寫法。

unsigned int i = 5;
unsigned int j = 7;

if (i - j < 0) // 錯誤
  printf("negative\n");
else
  printf("positive\n");

上面示例的運算結果,會輸出positive。原因是變量ij都是 unsigned int 類型,i - j的結果也是這個類型,最小值為0,不可能得到小於0的結果。正確的寫法是寫成下面這樣。

if (j > i) // ....

sizeof 運算符

sizeof是 C 語言提供的一個運算符,返回某種數據類型或某個值占用的字節數量。它的參數可以是數據類型的關鍵字,也可以是變量名或某個具體的值。

// 參數為數據類型
int x = sizeof(int);

// 參數為變量
int i;
sizeof(i);

// 參數為數值
sizeof(3.14);

上面的第一個示例,返回得到int類型占用的字節數量(通常是48)。第二個示例返回整數變量占用字節數量,結果與前一個示例完全一樣。第三個示例返回浮點數3.14占用的字節數量,由於浮點數的字面量一律存儲為 double 類型,所以會返回8,因為 double 類型占用的8個字節。

sizeof運算符的返回值,C 語言只規定是無符號整數,並沒有規定具體的類型,而是留給系統自己去決定,sizeof到底返回什么類型。不同的系統中,返回值的類型有可能是unsigned int,也有可能是unsigned long,甚至是unsigned long long,對應的printf()占位符分別是%u%lu%llu。這樣不利於程序的可移植性。

C 語言提供了一個解決方法,創造了一個類型別名size_t,用來統一表示sizeof的返回值類型。該別名定義在stddef.h頭文件(引入stdio.h時會自動引入)里面,對應當前系統的sizeof的返回值類型,可能是unsigned int,也可能是unsigned long

C 語言還提供了一個常量SIZE_MAX,表示size_t可以表示的最大整數。所以,size_t能夠表示的整數范圍為[0, SIZE_MAX]

printf()有專門的占位符%zd%zu,用來處理size_t類型的值。

printf("%zd\n", sizeof(int));

上面代碼中,不管sizeof返回值的類型是什么,%zd占位符(或%zu)都可以正確輸出。

如果當前系統不支持%zd%zu,可使用%u(unsigned int)或%lu(unsigned long int)代替。

類型的自動轉換

某些情況下,C 語言會自動轉換某個值的類型。

賦值運算

賦值運算符會自動將右邊的值,轉成左邊變量的類型。

(1)浮點數賦值給整數變量

浮點數賦予整數變量時,C 語言直接丟棄小數部分,而不是四舍五入。

int x = 3.14;

上面示例中,變量x是整數類型,賦給它的值是一個浮點數。編譯器會自動把3.14先轉為int類型,丟棄小數部分,再賦值給x,因此x的值是3

這種自動轉換會導致部分數據的丟失(3.14丟失了小數部分),所以最好不要跨類型賦值,盡量保證變量與所要賦予的值是同一個類型。

注意,舍棄小數部分時,不是四舍五入,而是整個舍棄。

int x = 12.99;

上面示例中,x等於12,而不是四舍五入的13

(2)整數賦值給浮點數變量

整數賦值給浮點數變量時,會自動轉為浮點數。

float y = 12 * 2;

上面示例中,變量y的值不是24,而是24.0,因為等號右邊的整數自動轉為了浮點數。

(3)窄類型賦值給寬類型

字節寬度較小的整數類型,賦值給字節寬度較大的整數變量時,會發生類型提升,即窄類型自動轉為寬類型。

比如,charshort類型賦值給int類型,會自動提升為int

char x = 10;
int i = x + y;

上面示例中,變量x的類型是char,由於賦值給int類型,所以會自動提升為int

(4)寬類型賦值給窄類型

字節寬度較大的類型,賦值給字節寬度較小的變量時,會發生類型降級,自動轉為后者的類型。這時可能會發生截值(truncation),系統會自動截去多余的二進制位,導致難以預料的結果。

int i = 321;
char ch = i; // ch 的值是 65 (321 - 256)

上面例子中,變量chchar類型,寬度是8個二進制位。變量iint類型,將i賦值給ch,后者只能容納i(二進制形式為101000001,共9位)的后八位,前面多出來的二進制位被丟棄,保留后八位就變成了01000001(十進制的65,相當於字符A)。

浮點數賦值給整數類型的值,也會發生截值,浮點數的小數部分會被截去。

double pi = 3.14159;
int i = pi; // i 的值為 3

上面示例中,i等於3pi的小數部分被截去了。

混合類型的運算

不同類型的值進行混合計算時,必須先轉成同一個類型,才能進行計算。轉換規則如下:

(1)整數與浮點數混合運算時,整數轉為浮點數類型,與另一個運算數類型相同。

3 + 1.2 // 4.2

上面示例是int類型與float類型的混合計算,int類型的3會先轉成float3.0,再進行計算,得到4.2

(2)不同的浮點數類型混合運算時,寬度較小的類型轉為寬度較大的類型,比如float轉為doubledouble轉為long double

(3)不同的整數類型混合運算時,寬度較小的類型會提升為寬度較大的類型。比如short轉為intint轉為long等,有時還會將帶符號的類型signed轉為無符號unsigned

下面例子的執行結果,可能會出人意料。

int a = -5;
if (a < sizeof(int))
  do_something();

上面示例中,變量a是帶符號整數,sizeof(int)size_t類型,這是一個無符號整數。按照規則,signed int 自動轉為 unsigned int,所以a會自動轉成無符號整數4294967291(轉換規則是-5加上無符號整數的最大值,再加1),導致比較失敗,do_something()不會執行。

所以,最好避免無符號整數與有符號整數的混合運算。因為這時 C 語言會自動將signed int轉為unsigned int,可能不會得到預期的結果。

整數類型的運算

兩個相同類型的整數運算時,或者單個整數的運算,一般來說,運算結果也屬於同一類型。但是有一個例外,寬度小於int的類型,運算結果會自動提升為int

unsigned char a = 66;

if ((-a) < 0) printf("negative\n");
else printf("positive\n");

上面示例中,變量a是 unsigned char 類型,這個類型不可能小於0,但是-a不是 unsigned char 類型,會自動轉為 int 類型,導致上面的代碼輸出 negative。

再看下面的例子。

unsigned char a = 1;
unsigned char b = 255;
unsigned char c = 255;

if ((a - 5) < 0) do_something();
if ((b + c) > 300) do_something();

上面示例中,表達式a - 5b + c都會自動轉為 int 類型,所以函數do_something()會執行兩次。

函數

函數的參數和返回值,會自動轉成函數定義里指定的類型。

int dostuff(int, unsigned char);

char m = 42;
unsigned short n = 43;
long long int c = dostuff(m, n);

上面示例中,參數變量mn不管原來的類型是什么,都會轉成函數dostuff()定義的參數類型。

下面是返回值自動轉換類型的例子。

char func(void) {
  int a = 42;
  return a;
}

上面示例中,函數內部的變量aint類型,但是返回的值是char類型,因為函數定義中返回的是這個類型。

類型的顯式轉換

原則上,應該避免類型的自動轉換,防止出現意料之外的結果。C 語言提供了類型的顯式轉換,允許手動轉換類型。

只要在一個值或變量的前面,使用圓括號指定類型(type),就可以將這個值或變量轉為指定的類型,這叫做“類型指定”(casting)。

(unsigned char) ch

上面示例將變量ch轉成無符號的字符類型。

long int y = (long int) 10 + 12;

上面示例中,(long int)10顯式轉為long int類型。這里的顯示轉換其實是不必要的,因為賦值運算符會自動將右邊的值,轉為左邊變量的類型。

可移植類型

C 語言的整數類型(short、int、long)在不同計算機上,占用的字節寬度可能是不一樣的,無法提前知道它們到底占用多少個字節。

程序員有時控制准確的字節寬度,這樣的話,代碼可以有更好的可移植性,頭文件stdint.h創造了一些新的類型別名。

(1)精確寬度類型(exact-width integer type),保證某個整數類型的寬度是確定的。

  • int8_t:8位有符號整數。
  • int16_t:16位有符號整數。
  • int32_t:32位有符號整數。
  • int64_t:64位有符號整數。
  • uint8_t:8位無符號整數。
  • uint16_t:16位無符號整數。
  • uint32_t:32位無符號整數。
  • uint64_t:64位無符號整數。

上面這些都是類型別名,編譯器會指定它們指向的底層類型。比如,某個系統中,如果int類型為32位,int32_t就會指向int;如果long類型為32位,int32_t則會指向long

下面是一個使用示例。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  int32_t x32 = 45933945;
  printf("x32 = %d\n", x32);
  return 0;
}

上面示例中,變量x32聲明為int32_t類型,可以保證是32位的寬度。

(2)最小寬度類型(minimum width type),保證某個整數類型的最小長度。

  • int_least8_t
  • int_least16_t
  • int_least32_t
  • int_least64_t
  • uint_least8_t
  • uint_least16_t
  • uint_least32_t
  • uint_least64_t

上面這些類型,可以保證占據的字節不少於指定寬度。比如,int_least8_t表示可以容納8位有符號整數的最小寬度的類型。

(3)最快的最小寬度類型(fast minimum width type),可以使整數計算達到最快的類型。

  • int_fast8_t
  • int_fast16_t
  • int_fast32_t
  • int_fast64_t
  • uint_fast8_t
  • uint_fast16_t
  • uint_fast32_t
  • uint_fast64_t

上面這些類型是保證字節寬度的同時,追求最快的運算速度,比如int_fast8_t表示對於8位有符號整數,運算速度最快的類型。這是因為某些機器對於特定寬度的數據,運算速度最快,舉例來說,32位計算機對於32位數據的運算速度,會快於16位數據。

(4)可以保存指針的整數類型。

  • intptr_t:可以存儲指針(內存地址)的有符號整數類型。
  • uintptr_t:可以存儲指針的無符號整數類型。

(5)最大寬度整數類型,用於存放最大的整數。

  • intmax_t:可以存儲任何有效的有符號整數的類型。
  • uintmax_t:可以存放任何有效的無符號整數的類型。

上面的這兩個類型的寬度比long longunsigned long更大。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM