C++ Primer(第五版)讀書筆記 & 習題解答 --- Chapter 2

Chapter 2.1

1. 數據類型決定了程序中數據和操做的意義。ios

2. C++定義了一套基本數據類型,其中包括算術類型和一個名爲void的特殊類型。算術類型包含了字符、整型、布爾值以及浮點數。void無值也無操做,咱們不能定義一個void類型的變量。函數

3. 算術類型的尺寸,也就是該類型所佔的比特數,在不一樣機器上有所差異。下表列出了C++標準規定的最小尺寸,同時容許編譯器賦予這些類型更大的尺寸:佈局

因爲比特數的不一樣,一個類型所能表達的最大(最小)值也是不一樣的。性能

4. C++語言規定,一個int至少和一個short同樣大,一個long至少和一個int同樣大,一個long long至少和一個long同樣大。spa

5. 基本字符類型是char,一個char的尺寸應確保能夠存放機器基本字符集中任意字符對應的數字值,也就是說,一個char的尺寸和一個機器字節相同。指針

6. 其餘字符類型,如wchar_t、char16_t、char32_t用於擴展字符集。wchar_t類型確保能夠存放機器最大擴展字符集中的任意一個字符。char16_t和char32_t用於Unicode字符集(Unicode是用於表示全部天然語言中字符的標準)。code

7. 計算機以比特序列存儲數據。大多數計算機以2的整數次冪個比特做爲塊來處理內存,可尋址的最小內存塊稱爲「字節」。在C++語言中,一個字節要至少能容納機器基本字符集中的字符。存儲的基本單元稱爲「字」,一般由幾個字節組成。大多數機器的字節由8比特構成,字則由32比特或64比特構成。對象

8. 大多數計算機將內存中的每一個字節與一個數字(被稱爲地址)關聯起來。咱們可以使用某個地址來表示從這個地址開始的大小不一樣的比特串。爲了賦予內存中某個地址明確的含義,必須首先知道存儲在該地址的數據的類型。類型決定了數據所佔的比特數以及該如何解釋這些比特的內容。blog

9. 除去布爾型和擴展的字符型以外,其餘整型能夠劃分爲帶符號的和無符號的。字符型與其餘整型不一樣,被分爲三種:char、unsigned char和signed char。儘管有三種,可是其表現形式仍就只有兩種:帶符號的和無符號的。類型char會表現爲signed char仍是unsigned char是由編譯器決定的。生命週期

10. 無符號類型中全部比特都用來存儲值。對於有符號類型,C++標準並無規定應如何表示,可是約定了在表示範圍內正值和負值應該均勻分佈。

11. 下面是選擇使用哪一種類型的一些有用的準則: 

(1). 當明確知曉數值不可能爲負時,選用無符號類型
(2). 使用int來執行整數運算。short一般過小了而long通常和int的尺寸同樣。若是數值超出int的表示範圍,就使用long long
(3). 在算術表達式中不要使用char或bool。只有在存放字符或布爾值時才使用它們。由於char在一些機器上是有符號的,而在另外一些機器上倒是無符號的,因此若是使用char進行運算特別容易出問題。若是你須要一個不大的整數,就顯示指定爲signed char或unsigned char
(4). 選用double來執行浮點運算。float的精度一般不夠,而且雙精度浮點和單精度浮點的計算在性能開銷上相差無幾。事實上,在一些機器上,雙精度運算甚至比單精度還快。long double提供的精度一般是沒有必要的,而且它帶來的性能開銷也不容忽視

12. 當在程序的某處咱們使用了一種類型而對象應該取另外一種類型時,程序會自動進行類型轉換:

bool b = 42;             // b爲真
int i = b;               // i的值爲1
i = 3.14;                // i的值爲3
double pi = i;           // pi的值爲3.0
unsigned char c = -1;    // 假設char佔8比特,c的值爲255
signed char c2 = 256;    // 假設char佔8比特,c2的值是未定義的

類型所能表示的值的範圍決定了轉換的過程:

(1). 當咱們把一個非布爾算術類型賦值給一個布爾類型對象時,若是值是0,則結果爲false,不然結果爲true
(2). 當咱們把一個布爾類型賦值給其餘算術類型時,若是布爾值爲true,那麼結果就是1,不然結果就是0
(3). 當咱們把浮點類型賦值給整型對象時,值會被截斷。只會保留小數點以前的值
(4). 當咱們把整型類型賦值給浮點型對象時,小數部分將是0。若是該整數的比特數超過了浮點對象能夠容納的比特數的話,精度可能會丟失
(5). 當咱們給無符號類型賦值一個超出它表示範圍的數值時,結果是該值對無符號類型可以表示的數值的總數取模後的餘數。例如,8比特大小的unsigned char能夠表示0到255,總共256個數。所以,把-1賦值給它所得的結果是255
(6). 當咱們給帶符號類型賦一個超出它表示範圍的值時,結果是未定義的

13. 若是表達式裏既有帶符號類型又有無符號類型,帶符號數會自動地轉換成無符號數。

14. 一個形如42的值被稱爲字面值常量。每一個字面值常量都對應一種數據類型,字面值常量的形式和值決定了它的數據類型。

15. 咱們能夠將整型字面值寫做十進制數、八進制數或十六進制數的形式:

20     // 十進制
024    // 八進制,以0開頭
0x14   // 十六進制,以0x或0X開頭
默認狀況下,十進制字面值是帶符號數,它的類型是int、long和long long中能容納字面值數值的尺寸最小的那個。而八進制和十六進制字面值既多是帶符號的也多是無符號的,它的類型是int、unsigned int、long、unsigned long、long long和unsigned long long中能容納字面值數值的尺寸最小 的那個。儘管整型字面值能夠存儲在帶符號類型中,但技術上來講,十進制字面值不會是負數。若是咱們寫-42,那麼負號其實不是字面值的一部分,它只是一個操做符,做用是對字面值取負。
浮點型字面值表現爲一個小數或以科學計數法表示的指數:
3.14159
3.14159E0
0. 0e0 .001

默認狀況下,浮點型字面值是一個double。

16. 由單引號括起來的一個字符稱爲char型字面值,雙引號括起來的零個或多個字符則構成字符串型字面值。編譯器會在每一個字符串型字面值的結尾處添加一個空字符('\0'),所以,字符串型字面值的實際長度要比它的內容多1。若是兩個字符串型字面值位置緊鄰而且僅由空格、縮進和換行符分隔,那麼它們其實是一個總體:

std::cout << "一個很長很長很長很長再長一點的字符串 "        
             "跨越了兩行噢" << std::endl;

17. C++語言規定的轉義序列包括:

咱們也可使用泛化的轉義序列,其形式是\x後緊跟1個或多個十六進制數字,或者\後緊跟1個、2個或3個八進制數字。其值表示的是字符對應的數值。

18. 咱們能夠經過下表中所列的前綴和後綴,來改變整型、浮點型和字符型字面值的默認類型:

 

Chapter 2.2

1. C++中的每一個變量都有其數據類型,數據類型決定着變量所佔內存空間的大小和佈局方式、該內存空間可以存儲的值的範圍、以及變量能參與的運算。

2. 當一次定義了兩個或多個變量時,對象的名字隨着定義也就立刻可使用了。所以,在同一條定義語句中,能夠用先定義的變量值去初始化後定義的其餘變量:

double price = 109.99, discount = price * 0.16;

3. 初始化與賦值是兩個徹底不一樣的操做。初始化的含義是建立變量時賦予其一個初始值。而賦值的含義是把對象的當前值擦除,用一個新值來替代。

4. 在C++11標準中,能夠用花括號來初始化變量,這種初始化形式被稱爲列表初始化:

int units_sold = {0}; int units_sold{0};

當列表初始化用於內置類型的變量時,有一個重要特色:若是初始值存在丟失信息的風險,則編譯器會報錯:

long double ld = 3.1415926536; int a{ld}, b = {ld};  // 編譯錯誤:存在丟失信息的風險
int c(ld), d = ld;    // 編譯正確:但值被截斷了

5. 若是定義變量時沒有指定初始值,則變量會默認初始化:

(1). 若是是內置類型的變量,當它定義於任何函數體以外時,將被初始化爲0。當它定義在函數體內部時,將不被初始化
(2). 每一個類各自決定其初始化對象的方式

6. 變量聲明規定了變量的類型和名字,在這一點上定義與之相同。但除此以外,定義還申請存儲空間,而且可能會爲變量賦一個初始值。

7. 聲明一個變量而非定義它,就在變量名前添加關鍵字extern:

extern int i;  // 聲明
int j;            // 定義

8. 任何包含了顯示初始化的聲明即成爲定義。所以,extern語句若是包含初始值也就再也不是聲明瞭:

extern double pi = 3.1416;  // 定義

在函數體內部,若是試圖初始化一個由extern關鍵字標記的變量,將引起錯誤。

9. 變量能且只能被定義一次,可是能夠被屢次聲明。

10. C++中的做用域有以下幾級:

全局做用域:全局做用域內的名字在整個程序的範圍內均可使用
類做用域:名字定義在類的內部
命名空間做用域:名字定義在命名空間內部
塊做用域:名字定義在塊的內部。從聲明位置開始直至聲明語句所在的做用域末端爲止都是可用的

11. 做用域能彼此包含,被包含的做用域稱爲內層做用域,包含着別的做用域的做用域稱爲外層做用域。做用域中一旦聲明瞭某個名字,它所嵌套着的全部做用域都能訪問該名字。同時,容許在內層做用域中從新定義外層做用域已有的名字。

 

Chapter 2.3

1. 咱們沒法令引用從新綁定到另一個對象,所以引用必須初始化。

2. 引用並不是對象,它只是爲一個已經存在的對象所起的另一個名字。由於引用自己不是一個對象,因此不能定義引用的引用。

3. 引用只能綁定在對象上,而不能與字面值或某個表達式的計算結果綁定在一塊兒。

4. 指針與引用相比有幾個不一樣點:其一,指針自己就是一個對象,容許對指針賦值和拷貝,並且在指針的生命週期內它能夠前後指向幾個不一樣的對象。其二,指針無須在定義時賦初值。

5. 和其餘內置類型同樣,在塊做用域內定義的指針若是沒有被初始化,也將擁有一個不肯定的值。

6. 由於引用不是對象,沒有實際地址,因此不能定義指向引用的指針。

7. 指針存儲的值能夠是如下四種狀態之一:

(1). 指向一個對象
(2). 指向一個對象所佔空間末尾的下一個位置
(3). 空指針
(4). 無效指針

8. C++11引入了nullptr,能夠初始化一個指針,表示空指針。它是一種特殊類型的字面值,能夠被轉換成任意其餘的指針類型。

9. void*是一種特殊的指針類型,可用於存聽任意對象的地址。

10. 變量的定義包括一個基本數據類型和一組聲明符。在同一條定義語句中,雖然基本數據類型只有一個,可是聲明符的形式卻能夠不一樣:

// i是一個int類型的變量,p是一個int類型的指針,r是一個int類型的引用
int i = 0, *p = &i, &r = i;

11. 指針是對象,因此存在對指針的引用:

int i = 42;
int *p = nullptr;
int *&r = p;  // r是一個對指針p的引用
r = &i;       // r引用了指針p,因此就是令p存放i的地址
*r = 0;       // r引用了指針p,因此就是解引用指針p,獲得i,將i的值改成0

 

Chapter 2.4

1. 引用的類型必須與其所引用對象的類型一致,可是有兩個例外。第一個就是在初始化常量引用時容許用任意表達式做爲初始值,只要該表達式的結果能轉換成引用的類型便可。特別是,容許爲一個常量引用綁定很是量的對象、字面值,甚至是一個表達式:

int i = 42; const int &r1 = i; const int &r2 = 42; const int &r3 = r1 * 2;
double d = 3.14; const int &r4 = d;

2. 指針的類型必須與其所指對象的類型一致,可是有兩個例外。第一個就是容許令一個指向常量的指針指向一個很是量對象:

double dval = 3.14; const double *ptr = &dval;

3. 咱們使用名詞頂層const(top-level const)表示一個對象自己是常量,頂層const對任何數據類型都適用,如算術類型、類、指針等。而名詞底層const(low-level const)用於指針或引用這些複合類型的基本類型,例如表示指針所指的對象是一個常量,引用所綁定的對象是一個常量。

當執行對象的拷貝操做時,頂層const會被忽略:
int i = 0; const int ci = 42; i = ci;  // ci是頂層const,被忽略
拷貝操做並不會改變被拷貝對象的值,所以,拷入和拷出的對象是否是常量都沒什麼影響。
另外一方面,底層const卻不容忽視。當咱們拷貝一個對象時,拷入和拷出的對象必須具備相同的底層const資格,或者兩個對象的數據類型必須可以轉換,一般來講,很是量能夠轉換爲常量,反之則不行:
int i = 0; const int* const p = &i; // p3既是頂層const,又是底層const
int *p1 = p; // 錯誤:p包含底層const,但p1卻沒有
const int &r = i; // 正確:const int&能夠綁定到普通int上,反之int&卻不能夠綁定到const int上

4. 常量表達式是指值不會改變而且在編譯過程就能獲得計算結果的表達式。一個對象(或表達式)是否是常量表達式由它的數據類型和初始值共同決定:

const int max_files = 20; // 常量表達式
const int limit = max_files + 1; // 常量表達式
int staff_size = 27; // 不是常量表達式
const int sz = get_size(); // 不是常量表達式

C++11中,咱們能夠將變量聲明爲constexpr類型以便由編譯器來驗證變量的值是不是一個常量表達式。聲明爲constexpr的變量必定是一個常量,並且必須用常量表達式初始化。

5. 儘管指針能夠定義成constexpr,可是它的初始值卻受到嚴格的限制,必須是nullptr或者0,或者是存儲於某個固定地址中的對象。還必須明確的一點事,限定符constexpr僅對指針有效,與指針所指的對象無關:

const int *p = nullptr; // p是一個指向整型常量的指針
constptr int *q = nullptr; // q是一個指向整型變量的常量指針

 

Chapter 2.5

1. C++11引入了一種新的定義類型別名的方法:

using MyInt = int;

2. 當指代複合類型的類型別名與const一塊兒使用時,可能會產生意想不到的結果:

using pstring = char*; const pstring cstr = nullptr; // cstr是指向char的常量指針

來看上述代碼,當遇到一條使用了類型別名的聲明語句時,不少人經常會錯誤的把類型別名替換成它原本的樣子來理解它:

const char *cstr = nullptr;

這種理解是錯誤的。聲明語句中用到pstring時,其基本數據類型是char*,是一個指針。而用char*替換重寫了以後,基本數據類型就變成了char。因此,在理解使用類型別名的聲明語句時,要特別注意這一點。

3. C++11引入了auto類型說明符,用它就能讓編譯器替咱們去分析表達式所屬的類型。auto定義的變量必須有初始值。使用auto也能在一條語句中聲明多個變量,但由於一條聲明語句只能有一個基本數據類型,因此該語句中全部變量的初始基本數據類型都必須一致:

auto i = 0, *p = &i; // 正確:i是整數,p是整型指針
auto sz = 0, pi = 3.14; // 錯誤:sz和pi的類型不一致

4. 使用auto的時候要注意如下一些規則:當引用被用做初始值時,真正參與初始化的實際上是引用對象的值。此時編譯器以引用對象的類型做爲auto的類型:

int i = 0, &r = i; auto a = r; // a是一個整數

auto通常會忽略掉頂層const,而保留底層const:

const int ci = 42; auto b = ci; // b是一個整數(ci的頂層const特性被忽略)
auto e = &ci; // e是一個指向整數常量的指針(對常量對象取地址是一種底層const)

設置一個類型爲auto的引用時,初始值中的頂層const會被保留:

const int ci = 42;
auto &r = ci; // r是一個常量引用

5. C++11標準引入了類型說明符decltype,它的做用是返回它的操做數的類型:

decltype(f()) sum = x;

在上面的代碼中,編譯器並不會調用函數f,而是使用當函數f被調用時的返回值類型做爲sum的類型。decltype處理頂層const和引用的方式與auto不一樣,若是decltype使用的表達式是一個變量,則它會返回該變量的類型(包括頂層const和引用):

const int ci = 0, &cj = ci; decltype(ci) x = 0; // x的類型是const int
decltype(cj) y = x; // y的類型是const int&,綁定到x

若是decltype使用的表達式不是一個變量,則它將返回表達式結果對於的類型。一些表達式會致使decltype產生一個引用類型,一般來講,返回引用類型的表達式是那種能產生一條賦值語句的左值的表達式:

int i = 42, *p = &i, &r = i; decltype(r + 0) b; // b是int類型
decltype(*p) c; // 錯誤:c是int&類型,必須初始化

正如咱們所看到的,*p獲得指針p所指的對象,而且該對象能夠賦值,因此,decltype(*p)產生的是int&,而不是int。

有一種狀況須要特別注意,若是decltype使用的是一個不加括號的變量,則獲得的結果就是該變量的類型。若是給變量加上了一層或多層括號,編譯器就會把它當成是一個表達式,而變量是一種能夠做爲賦值語句左值的特殊表達式,所以,這樣decltype就會獲得引用類型: 
int i = 42;
decltype((i)) d = i; // d是int&

 

Chapter 2.6

1. C++11標準規定,能夠爲類的數據成員提供一個類內初始值:

class CItem { unsigned unitsSold = 0; };

2. 預處理器是在編譯以前執行的一段程序。

3. 預處理變量有兩種狀態:已定義和未定義。#define指令把一個名字設定爲預處理變量。預處理變量不遵循C++語言中關於做用域的規則,因此預處理變量在整個程序中必須惟一。

 

Exercises Section 2.1.3 

Q_1. Using escape sequences, write a program to print 2M followed by a newline. Modify the program to print 2, then a tab, then an M, followed by a newline.

A_1. 

#include <iostream>

int main()
{
    std::cout << "2\115" << '\12';

    return 0;
}
#include <iostream>

int main()
{
    std::cout << '2' << '\t' << '\115' << '\n';

    return 0;
}

 

Exercises Section 2.3.2

Q_1. Write code to change the value of a pointer. Write code to change the value to which the pointer points.

A_1. 

#include <iostream>

int main()
{
    int value = 0;
    int *pValue = &value;

    // 更改指針所指對象的值
    *pValue = 100;

    // 更改指針的值
    pValue = nullptr;

    return 0;
}

 

Exercises Section 2.4.4 

Q_1. Is the following code legal or not? If not, how might you make it legal?

A_1. 

int null = 0, *p = null; // 非法,不能用整型變量null來初始化指向整型變量的指針p
// 修正: int null = 0, *p = &null;
相關文章
相關標籤/搜索