變數與型別

上一章展示的 "Hello World" 程式的用處相當有限。我們必須寫好幾行程式碼,編譯它們,然後執行生成的程式,只為了在螢幕上顯示一個簡單的句子。當然,自己直接輸入這個句子會快得多。

然而,程式設計並不僅限於在螢幕上列印簡單的文字。為了更進一步,為了能夠編寫能真正節省我們工作的有用程式,我們需要引入變數(variable)的概念。

讓我們想象一下,我讓你記住數字5,然後我讓你同時記住數字2。你剛剛在你的記憶中儲存了兩個不同的值(5和2)。現在,如果我讓你把我說的第一個數字加上1,你應該在記憶中保留數字6(也就是5+1)和2。然後,我們可以,例如,將這些值相減並得到4作為結果。

上面描述的整個過程就像是計算機處理兩個變數的比喻。同樣的過程可以用C++中的以下語句來表達:

1
2
3
4
a = 5;
b = 2;
a = a + 1;
result = a - b;

顯然,這是一個非常簡單的例子,因為我們只用了兩個小的整數值,但請考慮你的計算機可以同時儲存數百萬個這樣的數字,並對它們進行復雜的數學運算。

我們現在可以將變數定義為記憶體中用於儲存值的一部分。

每個變數都需要一個名稱來標識它並將其與其他變數區分開來。例如,在前面的程式碼中,變數名是 abresult,但我們本可以給這些變數起任何我們能想到的名字,只要它們是有效的C++識別符號。

識別符號

一個有效的識別符號是一個由一個或多個字母、數字或下劃線字元(_)組成的序列。空格、標點符號和符號不能成為識別符號的一部分。此外,識別符號必須總是以字母開頭。它們也可以以下劃線字元(_)開頭,但在大多數情況下,這類識別符號被認為是為編譯器特定的關鍵字或外部識別符號保留的,以及任何位置包含兩個連續下劃線字元的識別符號。在任何情況下,它們都不能以數字開頭。

C++使用許多關鍵字來標識操作和資料描述;因此,程式設計師建立的識別符號不能與這些關鍵字匹配。不能用於程式設計師建立的識別符號的標準保留關鍵字是:

alignas, alignof, and, and_eq, asm, auto, bitand, bitor, bool, break, case, catch, char, char16_t, char32_t, class, compl, const, constexpr, const_cast, continue, decltype, default, delete, do, double, dynamic_cast, else, enum, explicit, export, extern, false, float, for, friend, goto, if, inline, int, long, mutable, namespace, new, noexcept, not, not_eq, nullptr, operator, or, or_eq, private, protected, public, register, reinterpret_cast, return, short, signed, sizeof, static, static_assert, static_cast, struct, switch, template, this, thread_local, throw, true, try, typedef, typeid, typename, union, unsigned, using, virtual, void, volatile, wchar_t, while, xor, xor_eq

特定的編譯器也可能有額外的特定保留關鍵字。

非常重要: C++語言是“大小寫敏感”(case sensitive)的語言。這意味著用大寫字母寫的識別符號與用小寫字母寫的同名識別符號不等價。因此,例如,變數 RESULT 與變數 result 或變數 Result 是不一樣的。這是三個標識了三個不同變數的不同識別符號。

基本資料型別

變數的值以0和1的形式儲存在計算機記憶體中某個未指定的位置。我們的程式不需要知道變數儲存的確切位置;它可以簡單地透過它的名字來引用它。程式需要知道的是變數中儲存的資料型別。儲存一個簡單的整數和儲存一個字母或一個大的浮點數是不同的;儘管它們都用0和1表示,但它們的解釋方式不同,而且在許多情況下,它們佔用的記憶體量也不同。

基本資料型別是由語言直接實現的基本型別,代表了大多數系統原生支援的基本儲存單元。它們主要可以分為:
  • 字元型別: 它們可以表示單個字元,例如 'A''$'。最基本的型別是 char,它是一個單位元組的字元。還提供了用於更寬字元的其他型別。
  • 數值整數型別: 它們可以儲存一個整數值,例如 71024。它們有多種大小,並且可以是有符號(signed)或無符號(unsigned),這取決於它們是否支援負值。
  • 浮點型別: 它們可以表示實數值,例如 3.140.01,具有不同級別的精度,具體取決於使用三種浮點型別中的哪一種。
  • 布林型別: 布林型別,在C++中稱為 bool,只能表示兩種狀態之一:truefalse

以下是C++中基本型別的完整列表:
分組型別名稱*關於大小/精度的說明
字元型別char大小恰好為一個位元組。至少8位。
char16_t不小於 char。至少16位。
char32_t不小於 char16_t。至少32位。
wchar_t可以表示支援的最大字元集。
整數型別(有符號)signed charchar 大小相同。至少8位。
signed short int不小於 char。至少16位。
signed int不小於 short。至少16位。
signed long int不小於 int。至少32位。
signed long long int不小於 long。至少64位。
整數型別(無符號)unsigned char(與它們的有符號對應型別大小相同)
unsigned short int
unsigned int
unsigned long int
unsigned long long int
浮點型別float
double精度不低於 float
long double精度不低於 double
布林型別bool
Void 型別void無儲存空間
空指標decltype(nullptr)

* 某些整數型別的名稱可以省略其 signedint 部分進行縮寫——只有非斜體部分是必需的,斜體部分是可選的。例如,signed short int 可以縮寫為 signed shortshort int 或乾脆 short;它們都表示相同的基本型別。

在上述每個分組中,型別之間的區別僅在於它們的大小(即它們在記憶體中佔用的空間):每個分組中的第一個型別是最小的,最後一個是最大的,每個型別至少與同一組中它前面的型別一樣大。除此之外,同一組中的型別具有相同的屬性。

請注意,在上面的面板中,除了 char(其大小恰好為一個位元組)外,沒有任何基本型別指定了標準大小(最多隻有一個最小大小)。因此,該型別不被要求(並且在許多情況下不)恰好是這個最小大小。這並不意味著這些型別的大小不確定,而是意味著在所有編譯器和機器上沒有一個標準大小;每個編譯器實現可以為這些型別指定最適合程式將要執行的架構的大小。這種對型別相當通用的尺寸規定為C++語言提供了很大的靈活性,使其能夠適應在各種平臺(無論是現在還是未來)上以最佳方式工作。

上面的型別大小以位元(bit)為單位表示;一個型別擁有的位元越多,它可以表示的不同值就越多,但同時在記憶體中消耗的空間也越多。

大小可表示的唯一值數量備註
8位256= 28
16位65 536= 216
32位4 294 967 296= 232 (約40億)
64位18 446 744 073 709 551 616= 264 (約1800億億)

對於整數型別,擁有更多可表示的值意味著它們可以表示的值範圍更大;例如,一個16位的無符號整數可以表示0到65535範圍內的65536個不同值,而其有符號的對應型別在大多數情況下能夠表示-32768到32767之間的值。請注意,與無符號型別相比,有符號型別的正值範圍大約減半,因為16位中的一位用於表示符號;這是一個相對較小的範圍差異,很少能成為僅僅因為可表示的正值範圍而使用無符號型別的理由。

對於浮點型別,大小會影響其精度,因為它有更多或更少的位用於其有效數和指數。

如果型別的大小或精度不是問題,那麼通常選擇 charintdouble 來分別表示字元、整數和浮點值。它們各自組中的其他型別僅在非常特殊的情況下使用。

在特定系統和編譯器實現中,基本型別的屬性可以透過使用 numeric_limits 類來獲取(參見標準標頭檔案 <limits>)。如果出於某種原因需要特定大小的型別,庫在標頭檔案 <cstdint> 中定義了一些固定大小的類型別名。

上述型別(字元、整數、浮點和布林)統稱為算術型別。但還存在另外兩種基本型別:void,它表示型別的缺失;以及型別 nullptr,它是一種特殊的指標型別。這兩種型別都將在後面關於指標的章節中進一步討論。

C++支援基於上述基本型別的各種型別;這些其他型別被稱為複合資料型別,是C++語言的主要優勢之一。我們將在未來的章節中更詳細地看到它們。

變數的宣告

C++是一種強型別語言,要求每個變數在首次使用前都必須宣告其型別。這會通知編譯器為該變數在記憶體中保留多大的空間以及如何解釋其值。在C++中宣告一個新變數的語法很簡單:我們只需寫下型別,後跟變數名(即其識別符號)。例如:

1
2
int a;
float mynumber;

這是兩個有效的變數宣告。第一個聲明瞭一個型別為 int、識別符號為 a 的變數。第二個聲明瞭一個型別為 float、識別符號為 mynumber 的變數。一旦宣告,變數 amynumber 就可以在程式中它們作用域的其餘部分使用。
如果要宣告多個相同型別的變數,可以在單個語句中全部宣告,用逗號分隔它們的識別符號。例如:

1
int a, b, c;

這聲明瞭三個變數(abc),它們都是 int 型別,並且與以下寫法的含義完全相同:

1
2
3
int a;
int b;
int c;

為了看看變數宣告在程式中實際是什麼樣子,讓我們看一下本章開頭提出的關於你腦海記憶的例子的完整C++程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// operating with variables

#include <iostream>
using namespace std;

int main ()
{
  // declaring variables:
  int a, b;
  int result;

  // process:
  a = 5;
  b = 2;
  a = a + 1;
  result = a - b;

  // print out the result:
  cout << result;

  // terminate the program:
  return 0;
}
4

如果除了變數宣告本身之外,還有其他東西讓你覺得有點奇怪,不要擔心。其中大部分內容將在接下來的章節中更詳細地解釋。

變數的初始化

在上面的例子中,當變數被宣告時,它們的值是不確定的,直到它們第一次被賦值。但是,一個變數可以從它被宣告的那一刻起就有一個特定的值。這被稱為變數的初始化

在C++中,有三種方法可以初始化變數。它們都是等效的,也反映了該語言多年來的演變:

第一種,被稱為C-like初始化(因為它繼承自C語言),包括在變數名後附加一個等號,後跟變數被初始化的值:

型別 識別符號 = 初始值;
例如,要宣告一個名為 xint 型別變數,並在宣告的同時將其初始化為零,我們可以這樣寫:

1
int x = 0;

第二種方法,被稱為建構函式初始化(由C++語言引入),將初始值括在圓括號(())中:

型別 識別符號 (初始值);
例如:

1
int x (0);

最後,第三種方法,被稱為統一初始化,與上述方法類似,但使用花括號({})代替圓括號(這是由2011年C++標準修訂版引入的):

型別 識別符號 {初始值};
例如:

1
int x {0};

在C++中,這三種初始化變數的方式都是有效且等價的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// initialization of variables

#include <iostream>
using namespace std;

int main ()
{
  int a=5;               // initial value: 5
  int b(3);              // initial value: 3
  int c{2};              // initial value: 2
  int result;            // initial value undetermined

  a = a + b;
  result = a - c;
  cout << result;

  return 0;
}
6

型別推導:auto 和 decltype

當一個新變數被初始化時,編譯器可以根據初始化表示式自動推斷出變數的型別。為此,只需使用 auto 作為變數的型別說明符:

1
2
int foo = 0;
auto bar = foo;  // the same as: int bar = foo; 

在這裡,bar 被宣告為 auto 型別;因此,bar 的型別就是用來初始化它的值的型別:在這種情況下,它使用 foo 的型別,即 int

未初始化的變數也可以使用 decltype 說明符進行型別推導:

1
2
int foo = 0;
decltype(foo) bar;  // the same as: int bar; 

這裡,bar 被宣告為與 foo 具有相同的型別。

autodecltype 是最近新增到語言中的強大功能。但它們引入的型別推導功能旨在用於無法透過其他方式獲取型別,或使用它能提高程式碼可讀性的情況。上面的兩個例子可能都不屬於這兩種用例。事實上,它們很可能降低了可讀性,因為在閱讀程式碼時,必須去查詢 foo 的型別才能真正知道 bar 的型別。

字串簡介

基本型別代表了程式碼可能執行的機器所處理的最基本的型別。但C++語言的一大優勢是其豐富的複合型別集,而基本型別僅僅是這些複合型別的構建塊。

string 類就是一個複合型別的例子。這種型別的變數能夠儲存字元序列,例如單詞或句子。這是一個非常有用的功能!

與基本資料型別的第一個區別是,為了宣告和使用這種型別的物件(變數),程式需要包含定義該型別的標準庫標頭檔案(標頭檔案 <string>):

// my first string
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string mystring;
  mystring = "This is a string";
  cout << mystring;
  return 0;
}
This is a string

正如你在上一個例子中看到的,字串可以用任何有效的字串字面量來初始化,就像數值型別變數可以用任何有效的數值字面量來初始化一樣。與基本型別一樣,所有初始化格式對字串都有效:

1
2
3
string mystring = "This is a string";
string mystring ("This is a string");
string mystring {"This is a string"};

字串也可以執行基本資料型別能執行的所有其他基本操作,比如在宣告時不帶初始值,以及在執行期間改變其值:

// my first string
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string mystring;
  mystring = "This is the initial string content";
  cout << mystring << endl;
  mystring = "This is a different string content";
  cout << mystring << endl;
  return 0;
}
This is the initial string content
This is a different string content

注意:插入 endl 運算子會結束行ends the line),即列印一個換行符並重新整理流。

string 類是一種複合型別。正如你在上面的例子中所看到的,複合型別的使用方式與基本型別相同:宣告變數和初始化它們使用相同的語法。

有關標準C++字串的更多詳細資訊,請參見 string 類參考。
Index
目錄