C 程式設計/變數
與大多數程式語言一樣,C 使用並處理變數。在 C 中,變數是執行程式使用的計算機記憶體地址的易於閱讀的名稱。變數透過允許您將易於記憶的標籤與儲存程式資料的記憶體地址相關聯,從而更輕鬆地儲存、讀取和更改計算機記憶體中的資料。與變數關聯的記憶體地址直到程式編譯並在計算機上執行後才會確定。
最初,最容易將變數想象成值的佔位符,就像數學中的那樣。您可以將變數視為等效於其分配的值。因此,如果您有一個變數i,它被初始化(設定為等於)4,那麼i + 1將等於5。然而,熟練的 C 程式設計師更多地關注幕後發生的看不見的抽象層:變數是可以在其中找到資料的記憶體地址的替代,而不是資料本身。當您學習指標時,您將對此點有更清晰的認識。
由於 C 是一種相對低階的程式語言,在 C 程式可以使用記憶體來儲存變數之前,它必須宣告用於儲存變數值的所需記憶體。這是透過宣告變數來完成的。宣告變數是 C 程式顯示它需要的變數數量、它們將被命名的名稱以及它們需要多少記憶體的方式。
在 C 程式語言中,在管理和使用變數時,瞭解變數的型別和這些型別的大小非常重要。型別的 size 是儲存一個此型別的值所需的計算機記憶體量。由於 C 是一種相當低階的程式語言,因此型別的 size 可能特定於所使用的硬體和編譯器——也就是說,使語言在一種機器上工作的機制可能與使語言在另一種機器上工作的機制不同。
C 中的所有變數都是型別化的。也就是說,每個宣告的變數都必須被分配為某種型別的變數。
以下是如何宣告一個整數的示例,我們將其命名為some_number。(注意行末的分號;編譯器使用它來區分程式中的一個語句與另一個。)
int some_number;
此語句告訴編譯器建立一個名為 some_number 的變數,並將其與計算機上的記憶體位置相關聯。我們還告訴編譯器將儲存在該地址上的資料型別,在本例中是integer。請注意,在 C 中,我們必須指定變數將儲存的資料型別。這使編譯器知道為資料分配多少總記憶體(在大多數現代機器上,int 的長度為 4 個位元組)。我們將在下一節中介紹其他資料型別。
可以使用一條語句宣告多個變數,如下所示
int anumber, anothernumber, yetanothernumber;
在早期版本的 C 中,變數必須在塊的開頭宣告。在 C99 中,可以任意混合宣告和語句——但這並不常見,因為很少有必要,一些編譯器仍然不支援 C99(可移植性),而且由於它不常見,可能會惹惱其他程式設計師(可維護性)。
在宣告變數後,您可以使用如下所示的語句在稍後將值分配給變數
some_number = 3;
將值分配給變數稱為初始化。上面的語句指示編譯器將數字“3”的整數表示形式插入與 some_number 關聯的記憶體地址。我們可以透過同時宣告和分配資料到記憶體地址來節省一些輸入
int some_new_number = 4;
您還可以將變數分配給另一個變數的值,如下所示
some_number = some_new_number;
或者使用一條語句將多個變數分配為相同的值
anumber = anothernumber = yetanothernumber = 8;
這是因為賦值x = y返回賦值的值,即 y。例如,some_number = 4返回 4。也就是說,x = y = z實際上是x = (y = z).
的簡寫
變數命名C 中的變數名由字母(大寫和小寫)和數字組成。下劃線字元(“_”)也是允許的。名稱不能以數字開頭。與某些語言(例如 Perl 和一些 BASIC 方言)不同,C 在變數名上不使用任何特殊字首字元。
foo
Bar
BAZ
foo_bar
_foo42
_
QuUx
一些有效的(但不太描述性的)C 變數名稱示例
2foo (must not begin with a digit)
my foo (spaces not allowed in names)
$foo ($ not allowed -- only letters, and _)
while (language keywords cannot be used as names)
一些無效的 C 變數名稱示例
正如最後一個例子所示,某些詞在語言中被保留為關鍵字,因此不能用作變數名。
不允許在同一 作用域中使用相同的名稱來命名多個變數。因此,在與其他開發人員合作時,您應該採取措施避免對全域性變數或函式名使用相同的名稱。一些大型專案遵循命名指南[1]來避免重複的名稱並確保一致性。
此外,還有一些名稱集,雖然不是語言關鍵字,但出於某種原因被保留。例如,C 編譯器可能會“幕後”使用某些名稱,這可能會給嘗試使用它們的程式帶來問題。此外,一些名稱為 C 標準庫中將來可能的使用而保留。確定哪些名稱被保留(以及它們在哪些上下文中被保留)的規則過於複雜,無法在此處描述[需要引用],作為初學者,您不必過分擔心它們。現在,只需避免使用以下劃線字元開頭的名稱即可。
C 變數的命名規則也適用於命名其他語言結構,例如函式名、結構標記和宏,所有這些都將在後面介紹。
字面量在程式中任何您顯式指定值而不是引用變數或其他形式資料的地方,該值被稱為字面量。在上面的初始化示例中,3 是一個字面量。字面量可以採用由其型別定義的形式(稍後將詳細介紹),也可以使用十六進位制(十六進位制)表示法直接將資料插入變數,而無論其型別如何[需要引用]。十六進位制數始終以0x開頭。不過,現在,您可能不必太擔心十六進位制。
四種基本資料型別該int型別以“整數”的形式儲存整數。一個整數通常是一個機器字的大小,在大多數現代家用 PC 上是 32 位(4 個位元組)。字面量的例子是諸如 1、2、3、10、100... 的整數(整數)。當int為 32 位(4 個位元組)時,它可以儲存 -2147483648 到 2147483647 之間的任何整數。一個 32 位字(數字)有 4294967296 種可能性(2 的 32 次方)來表示任何一個數字。
如果你想宣告一個新的 int 變數,使用int關鍵字。例如
int numberOfStudents, i, j = 5;
在這個宣告中,我們聲明瞭 3 個變數,numberOfStudents、i 和 j,j 在這裡被分配了字面量 5。
char 型別能夠儲存執行 字元集 的任何成員。它儲存與 int 相同型別的資料(即整數),但通常大小為 1 個位元組。位元組的大小由宏 CHAR_BIT 指定,它指定一個 char(位元組)中的位數。在標準 C 中,它永遠不能小於 8 位。型別為 char 的變數最常用於儲存字元資料,因此得名。大多數實現使用 ASCII 字元集作為執行字元集,但最好不要知道或關心它,除非實際值很重要。
字元字面量的例子是 'a'、'b'、'1' 等,以及一些特殊字元,如 '\0'(空字元)和 '\n'(換行符,回憶“Hello, World”。注意,char 值必須用單引號括起來。
當我們初始化一個字元變數時,我們可以透過兩種方式來實現。一種是首選的,另一種方式是 * **糟糕** 的程式設計實踐。
第一種方式是寫
char letter1 = 'a';
這是 * **好** 的程式設計實踐,因為它允許閱讀程式碼的人理解 letter1 從一開始就被初始化為字母 'a'。
第二種方式,當你編碼字母字元時 * **不應** 使用,是寫
char letter2 = 97; /* in ASCII, 97 = 'a' */
有些人認為這是一種非常 * **糟糕** 的做法,如果我們使用它來儲存一個字元,而不是一個小的數字,如果有人閱讀你的程式碼,大多數讀者被迫查詢編碼方案中與數字 97 對應的字元是什麼。最後,letter1 和 letter2 都儲存相同的東西——字母 'a',但第一種方法更清晰,更容易除錯,而且更直接。
有一件重要的事情需要提到,數字的字元與相應的數字不同,即 '1' 不等於 1。簡而言之,任何用 '單引號' 括起來的單個條目。
還有一種型別的字面量需要與 chars 結合解釋: **字串字面量**。字串是一系列字元,通常用於顯示。它們用雙引號括起來(" ",而不是 ' ')。字串字面量的例子是 "Hello, World!\n" 中的 "Hello, World" 示例。
字串字面量被分配給一個字元 **陣列**,陣列將在後面介紹。示例
const char MY_CONSTANT_PEDANTIC_ITCH[] = "learn the usage context.\n";
printf("Square brackets after a variable name means it is a pointer to a string of memory blocks the size of the type of the array element.\n");
float 是 **浮點數** 的縮寫。它儲存實數(整數和非整數)的不精確表示。它可以用於比最大可能的 int 大得多的數字。float 字面量必須以 F 或 f 結尾。例如:3.1415926f、4.0f、6.022e+23f。
需要注意的是,浮點數是不精確的。像 0.1f 這樣的某些數字不能精確地表示為 float,但會存在很小的誤差。非常大和非常小的數字將具有較低的精度,並且算術運算有時由於缺乏精度而不可結合或不可分配。儘管如此,浮點數最常用於逼近實數,並且對它們的運算在現代微處理器上非常有效。[2] 浮點運算 在維基百科上有更詳細的解釋。
float 變數可以使用float關鍵字宣告。float 的大小隻是一個機器字。因此,當需要比 double 提供的精度更低時,可以使用它。
該double和float型別非常相似。該float型別允許你儲存單精度浮點數,而double關鍵字允許你儲存雙精度浮點數——實數,換句話說。它的大小通常是兩個機器字,或者在大多數機器上是 8 個位元組。例如double字面量是 3.1415926535897932、4.0、6.022e+23 (科學記數法)。如果你使用 4 而不是 4.0,4 將被解釋為int.
float 和 double 之間的區別是因為兩種型別的大小不同。當 C 最初被使用時,空間非常有限,因此明智地使用 float 而不是 double 節省了一些記憶體。如今,由於記憶體更加自由可用,你很少需要像這樣節省記憶體——可能最好是一致地使用 double。事實上,當宣告 float 變數時,某些 C 實現使用 double 而不是 float。
如果你想使用 double 變數,使用double關鍵字。
如果你對任何變數實際使用的記憶體量有任何疑問(這適用於我們將在後面討論的型別,也是),你可以使用sizeof運算子來確定。為了完整起見,必須提到sizeof是 一元運算子,而不是函式。)它的語法是
sizeof object
sizeof(type)
上面的兩個表示式以位元組為單位返回指定物件和型別的 size。返回型別是size_t(在標頭檔案中定義<stddef.h>)它是一個無符號值。這是一個示例用法
size_t size;
int i;
size = sizeof(i);
size將設定為 4,假設CHAR_BIT定義為 8,並且整數為 32 位寬。的sizeof的結果是位元組數。
請注意,當sizeof應用於char時,結果是 1;也就是說
sizeof(char)
始終返回 1。
可以透過在資料型別前加上某些修飾符來更改任何資料型別的儲存。例如
long和short是允許資料型別使用更多或更少記憶體的修飾符。該int關鍵字不必跟在short和long關鍵字後面。這種情況最常見。一個short可以用在值落在比int更小的範圍內的情況,通常是 -32768 到 32767。一個long可以用於包含擴充套件範圍的值。不能保證short使用比int更少的記憶體,也不能保證long佔用比int更多的記憶體。只能保證 sizeof(short) <= sizeof(int) <= sizeof(long)。通常,short為 2 個位元組,int為 4 個位元組,並且long為 4 或 8 個位元組。現代 C 編譯器還提供long long,它通常是一個 8 位元組整數。
在上面描述的所有型別中,一個位用於指示值的符號(正或負)。如果你確定一個變數永遠不會儲存負值,你可以使用unsigned修飾符來使用該位來儲存其他資料,有效地將值的範圍加倍,同時強制這些值必須為正數。該unsigned說明符也可以在沒有尾隨int的情況下使用,在這種情況下,大小預設為int的大小。還有一個signed修飾符,它是相反的,但它不是必需的,除非用於某些char用法,而且很少使用,因為所有型別(除了char)預設情況下是有符號的。
該long修飾符也可以與double一起使用以建立一個long double型別。這種浮點型別可以(但不一定)具有比double型別更高的精度。
要使用修飾符,只需用資料型別和相關修飾符宣告一個變數
unsigned short int usi; /* fully qualified -- unsigned short int */
short si; /* short int */
unsigned long uli; /* unsigned long int */
當使用const限定符時,必須在宣告時初始化宣告的變數。然後不允許更改它。
雖然一個永遠不會改變的變數的概念可能看起來沒有用,但使用const有充分的理由。首先,許多編譯器可以在知道資料永遠不會改變時對資料執行一些小的最佳化。例如,如果你的計算需要 π 的值,你可以宣告一個pi的 const 變數,這樣其他人編寫的程式或其他函式就無法改變pi.
的值。注意,符合標準的編譯器在嘗試更改const變數時必須發出警告——但在這樣做之後,編譯器可以隨意忽略const限定符。
魔數
[edit | edit source]在編寫 C 程式時,你可能會傾向於編寫依賴於特定數字的程式碼。例如,你可能正在為一家雜貨店編寫程式。這個複雜的程式有成千上萬行程式碼。程式設計師決定在整個程式碼中用一個字面量來表示一罐玉米的成本,目前為 99 美分。現在,假設一罐玉米的成本變為 89 美分。程式設計師現在必須手動將每個 99 美分的條目更改為 89 美分。雖然這不是什麼大問題,考慮到許多文字編輯器的“全域性查詢替換”功能,但請考慮另一個問題:一罐青豆的成本最初也是 99 美分。為了可靠地更改價格,你必須檢視數字 99 的每次出現。
C 擁有某些功能來避免這種情況。這種功能大致相同,儘管一種方法可能在一種情況下比另一種方法更有用。
使用const關鍵字
[edit | edit source]該const關鍵字有助於消除幻數。透過宣告一個變數const corn在塊的開頭,程式設計師可以簡單地更改該常量,而不必擔心在其他地方設定值。
還有一種方法可以避免幻數。它比const更靈活,在許多方面也更有問題。它也涉及預處理器,而不是編譯器。看吧……
#define
[edit | edit source]在編寫程式時,你可以建立所謂的宏,這樣當計算機讀取你的程式碼時,它會將所有單詞的例項替換為指定的表示式。
以下是一個例子。如果你寫
#define PRICE_OF_CORN 0.99
當你想要,例如,列印玉米的價格時,你使用單詞PRICE_OF_CORN而不是數字 0.99 - 預處理器會將所有PRICE_OF_CORN的例項替換為 0.99,編譯器將解釋為字面量double 0.99。預處理器執行替換,即PRICE_OF_CORN被替換為 0.99,這意味著不需要分號。
重要的是要注意,#define 的功能基本上與許多文字編輯器/文字處理器的“查詢和替換”功能相同。
在某些情況下,#define 會造成危害,如果#define 是不必要的,通常最好使用const。例如,你可以#define一個宏DOG為數字 3,但是如果你嘗試列印該宏,認為DOG表示一個可以在螢幕上顯示的字串,程式將出錯。#define 也不考慮型別。它不考慮程式的結構,到處替換文字(實際上,忽略作用域),這在某些情況下可能有利,但可能是造成問題性錯誤的根源。
你將在本文後面看到#define 指令的更多示例。約定俗成地,將#define 的單詞全部大寫,這樣程式設計師就會知道這不是你宣告的變數,而是#define 的宏。不需要在#define 等預處理器指令末尾新增分號;實際上,如果你這樣做,某些編譯器可能會警告你程式碼中存在不必要的標記。
作用域
[edit | edit source]在基本概念部分,介紹了作用域的概念。重要的是要重新審視區域性型別和全域性型別之間的區別,以及如何宣告每種型別的變數。要宣告區域性變數,你將宣告放在變數被認為是區域性的塊的開頭(即在任何非宣告語句之前)。要宣告全域性變數,在任何塊之外宣告變數。如果一個變數是全域性的,那麼你的程式中的任何地方都可以讀取和寫入它。
全域性變數不被認為是良好的程式設計習慣,應該儘可能避免。它們會影響程式碼可讀性,造成命名衝突,浪費記憶體,並可能導致難以追蹤的錯誤。過度使用全域性變數通常是懶惰或設計糟糕的標誌。但是,如果存在區域性變數可能導致程式碼更加晦澀難懂和難以閱讀的情況,那麼使用全域性變數也不可恥。
其他修飾符
[edit | edit source]為了完整起見,這裡列出了標準 C 提供的更多修飾符。對於初學者來說,static 和 extern 可能有用。volatile 更受高階程式設計師關注。register 和 auto 基本上已被棄用,通常對初學者和高階程式設計師都沒有興趣。
static
[edit | edit source]static有時是一個有用的關鍵字。一個常見的誤解是它唯一的用途是讓變數留在記憶體中。
當將函式或全域性變數宣告為static 時,你無法透過其他檔案中的extern(見下文)關鍵字訪問該函式或變數。這被稱為靜態連結。
當將區域性變數宣告為static 時,它會像任何其他變數一樣建立。但是,當變數超出作用域(即它所屬的塊已結束)時,變數會保留在記憶體中,保留其值。變數會保留在記憶體中,直到程式結束。雖然這種行為類似於全域性變數,但靜態變數仍然遵循作用域規則,因此無法在其作用域之外訪問。這被稱為靜態儲存期限。
預設情況下,宣告為靜態的變數被初始化為零(或對於指標,為 NULL[3][4])。可以在宣告時明確地將它們初始化為任何常量值。初始化只進行一次,在編譯時進行。
你可以用(至少)兩種不同的方式使用靜態。考慮這段程式碼,並想象它在一個名為 jfile.c 的檔案中
#include <stdio.h>
static int j = 0;
void up(void)
{
/* k is set to 0 when the program starts. The line is then "ignored"
* for the rest of the program (i.e. k is not set to 0 every time up()
* is called)
*/
static int k = 0;
j++;
k++;
printf("up() called. k= %2d, j= %2d\n", k , j);
}
void down(void)
{
static int k = 0;
j--;
k--;
printf("down() called. k= %2d, j= %2d\n", k , j);
}
int main(void)
{
int i;
/* call the up function 3 times, then the down function 2 times */
for (i = 0; i < 3; i++)
up();
for (i = 0; i < 2; i++)
down();
return 0;
}
j 變數可以被 up 和 down 訪問,並且保留其值。k 變數也保留其值,但它們是兩個不同的變數,每個變數都在其作用域內。靜態變數是實現封裝的一種好方法,封裝是面向物件思維中的一個術語,它實際上意味著不允許對變數進行更改,除非透過函式呼叫。
執行上面的程式將產生以下輸出
up() called. k= 1, j= 1
up() called. k= 2, j= 2
up() called. k= 3, j= 3
down() called. k= -1, j= 2
down() called. k= -2, j= 1
static 變數的特性
1. Keyword used - static
2. Storage - Memory
3. Default value - Zero
4. Scope - Local to the block in which it is declared
5. Lifetime - Value persists between different function calls
6. Keyword optionality - Mandatory to use the keyword
extern
[edit | edit source]extern用於當一個檔案需要訪問另一個檔案中它可能沒有的變數時#include直接。因此,extern 不會為新變數分配記憶體,它只是向編譯器提供足夠的資訊來訪問另一個檔案中宣告的變數。
extern 變數的特性
1. Keyword used - extern
2. Storage - Memory
3. Default value - Zero
4. Scope - Global (all over the program)
5. Lifetime - Value persists till the program's execution comes to an end
6. Keyword optionality - Optional if declared outside all the functions
volatile
[edit | edit source]volatile 是一種特殊的修飾符型別,它通知編譯器變數的值可能會被除程式本身以外的外部實體更改。對於某些使用最佳化編譯的程式,這是必要的 - 如果一個變數沒有定義volatile那麼編譯器可能會假設某些涉及該變數的操作是安全的,可以最佳化掉,但實際上並非如此。volatile 在處理嵌入式系統(程式可能無法完全控制變數)和多執行緒應用程式時尤其相關。
auto
[edit | edit source]auto是一個修飾符,它指定一個“自動”變數,該變數在作用域內時自動建立,在作用域外時自動銷燬。如果你認為這聽起來很像你一直都在做的事情,當你宣告一個變數時,你是對的:塊中宣告的所有專案都是隱式“自動”的。因此,auto 關鍵字更像是瑣事問題的答案,而不是有用的修飾符,而且有很多非常有能力的程式設計師不知道它的存在。
automatic 變數的特性
1. Keyword used - auto
2. Storage - Memory
3. Default value - Garbage value (random value)
4. Scope - Local to the block in which it is defined
5. Lifetime - Value persists while the control remains within the block
6. Keyword optionality - Optional
register 是對編譯器的提示,嘗試透過在程式執行時將給定變數儲存在計算機 CPU 的暫存器中來最佳化其儲存。大多數最佳化編譯器都會這樣做,因此使用此關鍵字通常是多餘的。事實上,ANSI C 規定編譯器可以隨意忽略此關鍵字——而且許多編譯器確實如此。Microsoft Visual C++ 就是一個完全忽略 register 關鍵字的實現示例。
register 變數的特性
1. Keyword used - register
2. Storage - CPU registers (values can be retrieved faster than from memory)
3. Default value - Garbage value
4. Scope - Local to the block in which it is defined
5. Lifetime - Value persists while the control remains within the block
6. Keyword optionality - Mandatory to use the keyword
- ↑ 命名指南的示例包括 GNOME 專案 或使用 C 編寫的 Python 直譯器 的部分。
- ↑ 除了浮點數以外,還存在其他實數表示方法,但它們不是 C 中的基本資料型別。一些 C 編譯器支援 定點算術 資料型別,但這些型別不屬於標準 C。 GNU 多精度算術庫 等庫提供了更多用於實數和極大數的資料型別。
- ↑ [1] - 什麼是 NULL 以及如何定義它?
- ↑ [2] - 應該使用 NULL 還是 0?