[C++ Primer Note1] 變數和基本型別
本筆記僅針對有一定程式設計基礎的讀者,不贅述通俗語法,而是聚焦於一些容易遺漏的細節和值得一錄的作者表述,並僅僅羅列陳述,儘量不夾帶私貨。同時部分原文所述很可能因為時間的發展以及環境不同而與實際情況略有出入,請儘管指出。
- 十進位制字面值的型別是int,long,long long尺寸最小的,八進位制和十六進位制字面值是能容納其數值的int,unsigned int,long,unsigned long,long long和unsigned long long中尺寸最小的
- 字面值常量的形式和值決定了它的資料型別
- 浮點型字面值表現為一個小數或科學計數法,預設為double型別
- 字元字面值為char型別,字串字面值是由常量字元構成的陣列
- nullptr是指標字面值(表示空指標,C++11 標準)
- C++中變數的初始化(建立時獲得值)和賦值完全不同 ,初始化是建立變數時賦予一個初始值,而賦值是把當前值擦除,以新值代替。
- C++中的物件往往指的是一塊能儲存資料並具有某種型別的記憶體空間,並不一定與類關聯在一起。(與Java中表述的物件不同)
- 如果定義時沒有指定初值,則變數被預設初始化,預設值由變數型別和定義變數的位置決定。
- 如果是內建型別的變數未顯式初始化,它的值由定義的位置決定。定義於任何函式體外被初始化為0,而在函式體內部則不被初始化,值是未定義的(危險!),建議初始化每一個內建型別 的變數。
- 每個類各自決定初始化物件的方式,是否允許不經初始化就定義物件也由類自己決定。
- 為了允許把程式拆分成多個邏輯部分來編寫,C++語言支援分離式編譯(seperate compilation ),即將程式分割成若干個檔案,每個檔案可被獨立編譯。
- 為了支援分離式編譯,C++將宣告和定義分開來。宣告使得名字為程式所知,一個檔案如果想使用別處定義的名字則必須包含對那個名字的宣告。而定義負責建立與名字關聯的實體(申請儲存空間,可能賦一個初始值等)
- 如果想宣告一個變數而非定義它,就在變數名前新增extern ,而且不要顯式初始化它。任何包含了顯式初始化的宣告即成為定義。
- C++是一種靜態型別 (statically typed )語言,其含義是在編譯階段檢查型別是否支援要執行的運算,前提是編譯器必須知道每個實體物件的型別。
- 使用作用域操作符:: 訪問全域性變數
- 一條宣告語句由一個基本資料型別和緊隨其後的一個宣告符(declarator)列表 組成,一種膚淺的認識是宣告符就是變數名,此時變數的型別就是宣告的基本型別,但其實還有更復雜的宣告符,它基於基本資料型別得到更復雜的型別,並把它指定給變數(比如指標和引用)。
- 在定義引用時,程式把引用和它的初始值繫結(bind)在一起,而不是像初始化一樣拷貝,一旦初始化完成,引用將和它的初始值物件一直繫結在一起,所以引用必須初始化 。
- 引用並非物件 ,它只是為一個已經存在的物件所起的一個別名 。且引用只能和物件繫結,而不能與某個字面值或者表示式計算結果繫結。
- 指標和引用不同,其本身就是一個物件,允許對其賦值和拷貝。如果指標指向了一個物件,通過解引用符*得出所指的物件。
- 空指標不指向任何物件,在試圖使用一個指標之前可以首先檢查它是否為空。
- 生成空指標的幾種方法:
// C++ 11 標準 int *p1 = nullptr; // 等價於 int *p1 = 0; int *p2 =0; // 需要 #include cstdlib int *p3 = NULL; // 等價於 int *p3 = 0;
nullptr是一種特殊型別的字面值,它可以被轉換成任意其他的指標型別。過去的程式還會用到一個名為NULL的預處理變數 (preprocessor variable )來給指標賦值,這個變數在cstdlib中定義,它的值就是0 。預處理變數不屬於名稱空間std,所以可以直接使用。當用到一個預處理變數時,前處理器會自動將它替換為實際值,因此用NULL初始化指標和用0初始化指標效果完全一樣 ,新標準下,現在的C++程式最好使用nullptr,避免NULL。
把int變數直接賦給指標是錯誤的操作,即使int變數的值恰好為0。
- 在大多數編譯器環境下,如果使用了未經初始化的指標,則該指標所佔空間的當前內容將被看作是一個地址值,訪問該指標相當於訪問一個不存在位置上的不存在物件,因此建議初始化所有的指標
- 指標可以用在條件表示式中,如果其值為0,條件取false
- void* 是一種特殊的指標型別,可以存放任意物件的地址。不能直接操作void* 指標所指的物件,因為我們並不知道這個物件是什麼型別,也就無法確定在其上的操作是否合法。
- 經常會有錯誤 觀點認為,型別修飾符(*或&)作用於本次定義的全部變數,但實際上形如 int* 並不是基本型別,int才是,* 只是修飾了緊隨其後的那一個變數而已。
- 宣告符中修飾符的個數並沒有限制,比如**(指向指標的指標),比如*&(指標的引用),一個訣竅在於從右向左 閱讀一個變數的定義,離變數名最近 的符號對變數的型別有最直接的影響。
- const 可以使物件一旦建立後其值不能改變,所以const物件必須初始化 。
- 當以編譯時初始化的方式定義一個const物件時(比如初始化為字面值而不是函式返回值),編譯器將在編譯過程中用到該變數的地方都替換成對應的值 。如果程式包含多個檔案,則每個用到了const物件的檔案都必須能訪問它的初始值才行,同時為了避免對同一變數的重複定義,預設情況下const物件僅在檔案內有效 ,當多個檔案出現了同名的const物件時,等同於在不同檔案中分別定義了獨立的變數。如果想讓const物件想其他物件一樣工作(一個檔案中定義,在其他檔案宣告並使用),只需要不管宣告還是定義都新增extern 關鍵字,這樣只需定義一次就可以了。
- const變數只能被同樣是const的引用引用,但是const引用也可以引用變數,但不能通過它修改繫結物件的值
- 初始化const引用時允許用任意表達式 作為初始值,只要能轉換成引用的型別即可。
double dval = 3.14; const int &ri = dval;
此處ri引用了一個int型的數,但dval卻是一個雙精度浮點數,為了確保讓ri繫結一個整數,編譯器把上述程式碼變成了如下形式:
const int temp = dval; // 由雙精度浮點數生成一個臨時的整型常量 const int &ri = temp; // 讓ri繫結這個臨時量
在這種情況下,ri綁定了一個臨時量(temporary)物件(編譯器用來暫存表示式求值結果而臨時建立的一個未命名的物件空間)。
如果ri不是const引用,就允許對ri賦值,但此時繫結的是一個臨時量而不是dval,因此C++將這種行為視作非法。
- const引用僅對引用可參與的操作做出了限定,對於引用繫結的物件本身不做限定
- 指向常量的指標(pointer to const)用於存放常量物件的地址,不同通過它去改變所指物件的值,但同樣可以指向非常量物件。
- 指標本身是物件,允許把指標本身定為常量,常量指標 (const pointer)必須初始化,定義時將 * 放在const關鍵字之前 。
- 所謂指向常量的指標或引用,其實只是它們的“自以為是 ”,自覺不去修改所指物件的值。
- 頂層const (top-level const):表示任意物件是常量,底層const (low-level const):表示所指的物件是常量。執行拷貝操作時,底層const的限制不可忽視。
- 常量表達式 是指值不會改變並且在編譯過程就能得到計算結果的表示式(比如字面值和用常量表達式初始化的const物件)。C++新標準規定,允許將變數宣告為constexpr 型別以便由編譯器來驗證變數的值是否是一個常量表達式,如果認定一個變數是一個常量表達式,就把它宣告成constexpr。
- constexpr把它所定義的物件置為了頂層const (特別注意指標)
- 類型別名是一個名字,它是某種型別的同義詞。有兩種方法可用於定義類型別名。
typedef double wages; // wages是double的同義詞 typedef wages base, *p; // base是double的同義詞,p是double*的同義詞
其中關鍵字typedef作為宣告語句中的基本資料型別,這裡的宣告符也可以包含型別修飾,從而由基本型別構造出複合型別來 。
新標準規定了一種新的方法,使用別名宣告(alias declaration)定義類型別名。
using SI = Sales_item; // SI是Sales_item的同義詞
如果某個類型別名指代的是複合型別或常量,那麼把它用到宣告語句會產生意想不到的後果。
typedef char *pstring; const pstring cstr=0;//cstr是一個指向char的常量指標
const是對給定型別 的修飾,而此處pstring的基本資料型別是指標 ,如果用char *替換重寫語句,資料型別就變成了char,* 成為了宣告符的一部分。所以前者聲明瞭指向char的常量指標 ,後者聲明瞭一個指向const char的指標。
- 程式設計時常需要把表示式的值賦給變數,這就要求在宣告變數時清楚知道表示式的型別,但這有時根本做不到。C++11新標準引入了auto 型別說明符,它能讓編譯器替我們去分析表示式所屬的型別。顯然,auto定義的變數必須有初始值。其次,auto一般會忽略掉頂層const,同時底層const會保留下來。
- 有時希望從表示式的型別推斷出要定義的變數的型別,但是不想用該表示式的值初始化變數。C++11新標準引入第二種型別說明符decltype ,它的作用是選擇並返回運算元的資料型別。
decltype(f()) sum=x; // sum的型別就是函式f的返回型別
如果decltype使用的表示式是一個變數,則返回該變數的型別(包括頂層const和引用在內)
如果decltype使用的表示式不是一個變數,則decltype返回表示式結果對應的型別。有些表示式將返回一個引用型別,一般這種情況發生時,意味著表示式結果物件能作為一條賦值語句的左值 。
如果變數名加上了一對括號,編譯器會把它當成一個表示式,從而decltype得到引用型別。
- struct體結束後必須寫一個分號,因為其後可以緊跟變數名(宣告符)表示對這型別物件的定義。一般來說,最好不要 把物件的定義和類的定義放在一起,這麼做相當於把不同實體的定義混在了一條語句中。
- 為了確保各個檔案中類的定義一致,類通常被定義在標頭檔案中,而且類所在標頭檔案的名字應該與類的名字一樣。
-
確保標頭檔案多次被包含仍能安全工作的常用技術是前處理器
(preprocessor),前處理器是在編譯之前執行的一段程式,可以部分地改變我們所寫的程式。之前已經用到了一項前處理器功能#include,當前處理器看到該標記會用指定的標頭檔案內容代替#include。
還有一項常見的預處理功能是標頭檔案保護符(header guard),標頭檔案保護符依賴於預處理變數。#define設定預處理變數,#ifdef和#ifndef則檢查預處理變數是否已經定義。
#ifndef SALES_DATA_H #define SALES_DATA_H #include <string> struct Sales_data{ std:string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; #endif
第一次之後包含Sales_data.h,編譯器都會忽略類的定義。
整個程式中的預處理變數都必須唯一,通常基於標頭檔案中類的名字構建保護符名字,一般全部大寫。