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