C++ Primer Plus 第四章 複合類型 學習筆記

第四章 複合類型

1. 數組概述

1.1 數組的定義

數組(array)是一種數據格式,能夠存儲多個同類型的值。每個值都存儲在一個獨立的數組元素中,電腦在記憶體中依次存儲數組的各個元素。

數組聲明的三個特點:

  • 存儲在每個元素中的值的類型
  • 數組名
  • 數組中的元素數

C++中可以通過修改簡單變數的聲明,添加中括弧(其中包含元素數目)來完成數組聲明。

例如:

short days[24]; // 一天有24個小時

1.2 數組的聲明

聲明數組的的一般語法格式為:

// 數組類型 數組名字[數組的大小]
int score[4]; // 四個人的分數,整型數組

數組的大小是指定元素的數目,必須是整型常數或const值,也可以是常量表達式(8*sizeof(int))

1.3 複合類型的數組

可以使用其他的類型來創建(C語言使用術語:派生類型

數組的用途,可以單獨訪問數組元素,方法是:使用下標索引對元素進行編號。從0開始編號

編譯器不會檢查下標是否有效,所以要注意下標合法性,避免程式異常問題。
C++使用索引的方括弧表示法來指定數組元素。

1.4 數組的初始化規則

1.只有在定義數組時才能初始化,此後不能使用,也不能將一個數值賦給另一個數組

2.初始化數組時,提供的值少於數組的元素數目。

3.如果只對數組的一部分進行初始化,則編譯器把其他元素設置為0。

4.如果初始化為{1}而不是{0},則第一個元素被設置為1,其他元素都被設置為0.

5.如果初始化數組方括弧內([])為空,C++編譯器將計算元素個數。 例如:short things[] = {1,3,5,7};

1.5 C++11數組初始化方法

C++11將使用大括弧的初始化(列表初始化)作為一種通用的初始化方式,可用於所有類型。

在C++中列表初始化就增加了一些功能:

  • 初始化數組時,可省略等號(=)
double earnings[4] {1.2e4,1.6e4,1.1e4,1.7e4};
  • 可不在大括弧內包含任何東西,這會將所元素都設置為零。
unsigned int const[10] = {};

float balances[100] {};
  • 列表初始化禁止縮窄轉換。
long num[] = {25,92,3.0}; // 浮點數轉換為整型是縮窄操作

例子:

#include<iostream>

using namespace std;

int main()
{
    // 創建一個名字為yams的數組,包含了3個元素,編號是0~2.
    int yams[3];
    yams[0] = 7;
    yams[1] = 8;
    yams[2] = 6;

    // 使用逗號分隔的值列表(初始化列表),然後用花括弧括起來即可。
    // 列表中的空格是可選的,如果沒有初始化函數中定義的數組,其元素值也是不確定。
    int yamcosts[3] = {1,2,3};

    cout<<"yams 數組是:"<<yams[0]+yams[1]+yams[2]<<endl;
    cout<<"yams[1] = "<<yams[1]<<endl;
    int total = yams[0] * yamcosts[0] + yams[1] * yamcosts[1];
    total = total + yams[2] * yamcosts[2];
    cout<<"total yam = "<<total<<endl;

    // sizeof運算符返回類型或數據對象的長度(單位為位元組)。
    // 如果將sizeof運算符用於數組名,得到的是整個數組的位元組數。
    // 如果sizeof用於數組元素,得到的是元素的長度(單位為位元組)。
    cout<<"\n yams數組的大小 = "<<sizeof(yams)<<" Bytes.\n";
    cout<<"一個元素的大小 = "<<sizeof(yams[0])<<" Bytes.\n";

    return 0;
}

2. 字元串

字元串是存儲在記憶體的連續位元組中的一系列字元。

2.1 C++處理字元串的兩種方式:

  • C語言,常常被稱為C-風格字元串(C-style String)

    以空字元(\0,ASCII碼對應為0)來標記字元串的結尾。

  • 基於String類庫的方法

存儲在連續位元組中的一系列字元意味著可以將字元串存儲在char數組中。其中每個字元都位於自己的數組元素中。

使用引號括起來的字元串,這種字元串叫 字元串常量(String constant)字元串字面值(string literal)

字元串常量(使用雙引號)不能與字元常量(使用單引號)互換。

例如:

char name[] = "Soler";

字元串結尾的空字元不用直接顯式包括,機器在鍵盤輸入,將字元串讀入到char類型中,會在結尾自動加上空字元

⚠️注意:確定了存儲字元串所需的最短數組時,不要忘記把結尾的空字元包括在內。

2.2 字元串常量的拼接

方法:直接兩個引號括起來的字元串合併為一個。任何兩個由空白(空格、製表符和換行符)分隔的字元串常量都將自動拼接成一個。

cout<<"My name is " "Soler HO.\n" 

2.3 在數組中使用字元串

將字元串存儲到數組的常用方法:

  • 將數組初始化為字元串常量
  • 將鍵盤或文件輸入讀入到數組中。
#include <iostream>
#include <cstring> /*提供strlen()函數*/
using namespace std;

const int Size = 15;

int main()
{
    char name1[Size];
    char name2[Size] = "C++owboy";
    // 字元串的拼接
    cout<<"Howdy!I'm "<< name2;
    cout<<"!,What's your name?\n";
    cin>>name1;

    cout<<"Well, "<<name1<<",your name has : "<<strlen(name1)<<" letters and is stored!\n" ;
    cout<<"In an array of "<<sizeof(name1)<<" Bytes\n";
    cout<<"Your iniatial is "<<name1[0]<<".\n"; // name1數組中的第一個元素
    name2[3] = '\0';
    cout<<"Here are the first 3 characters of my name:"<<name2<<endl;

    return 0;
}

strlen() 函數sizeof()運算符的區別

  • strlen()函數

    • 返回的是存儲在數組中的字元串的長度,而~~不是數組本身的長度~~
    • strlen()只計算可見的字元,而不把空字元計算在內
  • sizeof() 運算符

    • 指出變數數據類型位元組大小
    • 可用於獲取類、結構、共用體和其他用戶自定義數據類型的大小。

2.4 讀取一行字元串的輸入

解決沒有逐行讀取輸入的缺陷。

istream中提供了面向行的類成員函數:getline()get() 函數

2.4.1 面向行的輸入:getline()

使用通過回車鍵輸入的換行符來確定輸入結尾。使用 cin.getline()

函數有兩個參數:

  • 第一個參數:存儲輸入行的數組名稱
  • 第二個參數:要讀取的字元數(注意包含結尾的空字元(\0))。

格式:

cin.getline(name,ArSize);

2.4.2 面向行的輸入:get()

getline() 函數類似,接受的參數相同,解釋參數的方式也相同,並讀到行尾

區別:get() 讀取並丟棄換行符,將其留在輸入隊列中。

格式:

cin.get(name,ArSize);

get() 將兩個類成員函數拼接(合併):

cin.get(name,ArSize).get();

⚠️注意:get() 函數讀取空行後設置會失效,輸入會被阻斷。可用如下恢復:

cin.clear();

混合輸入數字和面向行的字元串會導致的問題:無法輸入地址。

解決方法:直接使用get()進行讀取之前丟棄換行符。

3. string類

string類位於名稱空間std中,所以需要提供using指令或者是直接使用std::string進行引用。

要使用string類,必須在程式中包含頭文件string中。

string類定義隱藏了字元串的數組性質。

3.1 string對象的方式

使用string對象的方式和使用字元數組相同。

  • C-風格字元串來初始化string對象中。
  • 使用cin來將鍵盤輸入存儲到string對象中。
  • 使用cout來顯示string對象。
  • 可以使用數組表示方法來訪問存儲在string1對象中的字元。

賦值 —— 不能將一個數組賦給另一個數組,但可以將一個string對象賦另一個string對象。

char char01[20];                // 創建一個空列表
char char02[20] = "Jason";      // 創建一個初始化數組

string str01;                   // 創建一個空的string對象
string str02 = "Soler Ho";      // 創建一個初始化的string對象

char01 = char01;                // 不可執行,一個數組不能賦值給另一個數組
str01 = str02;                  // 可執行,可將一個string對象賦給另一個string對象。

3.2 複製、拼接和附加

string類簡化字元串合併操作。

  • 利用運算符 + 將兩個string對象合併起來。
string str01;                  
string str02 = "Soler Ho";

string = str01 + str02;
  • 可以使用運算符 += 將字元串附加到string對象的末尾
string str01;                  
string str02 = "Soler Ho";

str01 += str02;

4. 結構簡介

結構是用戶定義的類型,而結構聲明定義了類型的數據屬性

定義類型之後,就直接創建類型的變數。

結構比數組靈活,同一個結構中可以存儲多種類型的數據。

4.1 創建結構的步驟:

  • 定義結構描述 —— 描述並標記能夠存儲在結構中的各種數據類型

  • 按描述創建結構變數(結構數據對象)。

4.2 結構的定義:

struct(關鍵字) 類型名(標記成為新類型的名稱)
{
    結構成員1;
    結構成員2;
    結構成員3;
};//(結束結構聲明)

對於結構中的成員,使用成員運算符(.)來進行訪問各個成員。

4.3 結構的初始化(C++11)

  • 與數組一樣,列表的初始化用於結構,且等號(=)可有可無
infor Soler_infor {"Soler HO",55,168}; // 在C++11中,= 號可以省略
  • 如果大括弧內未包含任何東西,各個成員都將設置為零。
infor Soler_infor {};
  • 不允許縮窄轉換

✅ 小Tips:C++允許在聲明結構變數時省略關鍵字struct。

4.4 成員賦值

成員賦值(memberwise assignment):可以使用賦值運算符(=)將結構賦另一個同類型的結構。這樣結構中的每個成員都將被設置為另一個結構中相應成員的值。即使成員是數組。這種方式就是成員賦值

5. 共用體

共用體(union),也叫做聯合(union)。一種 構造數據類型

關鍵字:union

聯合(union):將不同類型的數據在一起共同佔用同一段記憶體

存儲不同的數據類型,但只能同時存儲其中的一種類型

示例:

union sample
{
    int int_val;
    long long_val;
    double double_val;
};

5.1 結構體和共用體的區別

  • 結構可以同時存儲int、long和double
  • 共用體只能存儲int、long和double三種。
  • 含義不同。
  • 關鍵字不同
    • 結構體:struct
    • 共用體:union

5.2 共用體的用途:

  • 當數據使用兩種格式或更多格式(但不會同時使用)時,可以節省空間。
    • 嵌入式系統編程(如控制烤箱、MP3播放器),記憶體非常寶貴。
  • 常用於作業系統數據結構或硬體數據結構。

5.3 匿名共用體

匿名共用體(anonymous union)沒有名稱,其成員將成為位於相同地址處的變數。

6. 枚舉

C++的enum工具提供了另一種創建符號常量的方式,可以代替const,允許定義新類型,但必須有嚴格限制。

使用enum的語法格式與結構的使用類似。

enum color{red,orange,yellow,green,blue,voilet};

6.1 設置枚舉量的值

enum week{Monday = 1,Tuesday = 2;Wednesday = 3;Thursday = 4};

指定的值必須是整數。也可以只顯示定義其中一些枚舉量的值

如果第一個變數未初始化,默認為0。後面沒有被初始化的枚舉量的值將比其前面的枚舉量大1。也可以創建多個值相同的枚舉量。

enum {zero,null = 0,numero_one,one = 1};

6.2 枚舉的取值範圍

每個枚舉都有取值範圍的上限,通過強制類型轉換,可以將取值範圍中的任何整數值賦給枚舉常量,即使這個值不是枚舉值。

6.3 取值範圍的定義

  • 找出上限,需要知道枚舉量的最大值。
    • 找到大於最大值的,最小的2的冪,減去1,得到就是取值範圍的上限。
  • 計算下限,知道枚舉量的最小值。
    • 如果不小於0,則取值範圍的下限為0,否則,採用尋找上限方式相同的方式,但是要加上負號。

對於選擇使用多少空間來存儲枚舉由編譯器決定。

7. 指針和自由空間

對於地址顯示結果是十六進位表示法,因為都是常常描述記憶體的表示法

  • 指針與C++基本原理

    面向對象編程和傳統的過程性編程的區別,OOP強調的是運行階段(而不是編譯階段)進行決策。

    • 運行階段:程式正在運行是,取決於不同的情況。
    • 編譯階段:編譯器將程式組合起來時。堅持原先設定的安排

指針用於存儲值的地址。指針名表示的是地址。

*運算符稱為間接值或解除引用運算符,將其應用於指針,得到該地址處存儲的值。

7.1 聲明和初始化指針

指針的聲明必須指定指向的數據的類型

int *p_updates; 

*p_updates 的類型是int,所以*運算符被用於指針,所以p_updates變數必須是指針。

運算符*兩邊的空格是可選的。

int *ptr; /*該情況強調:*ptr是一個int類型的值。*/

int* ptr; /*該情況強調:int* 是一種類型,指向int的指針。*/

在C++中,int*是一種複合類型,是指向int的指針

double *tax_ptr;

7.2 指針的危險

在C++創建指針時,電腦將分配用來存儲地址的記憶體,但是不會分配用來存儲指針所指向的數據的記憶體

⚠️注意:一定要在對指針應用解除引用運算符(*)之前,將指針初始化為一個確定的、適當的地址

7.3 指針和數字

整數可以加減乘除等運算,而指針描述的是位置

C++語言數字不能作為地址使用,如果要把數字當地址來使用,應通過強制類型轉換將數字轉換為適當的地址類型。

7.4 使用new分配delete釋放記憶體

指針在運行階段 分配未命名的記憶體以存儲值。然後使用記憶體來訪問記憶體。

C語言中,使用 庫函數malloc()來分配記憶體。C++中使用 ———— new運算符。

7.4.1 要注意使用delete進行記憶體的釋放

需要記憶體時,直接使用new來請求,這是記憶體管理數據包的一個方面。

如果使用了delete運算符,使得在使用完記憶體後,能夠將其歸還給記憶體池,這是有效使用記憶體的關鍵。

使用delete時,後面要加上指向記憶體塊的指針。

int * ps = new int; // 使用new進行記憶體分配
 ...
delete ps; // 使用delete進行記憶體的釋放

⚠️注意點:

1.使用delete釋放ps的記憶體,但是不會刪除指針ps本身

2.只能用delete來釋放使用new分配的記憶體,但是如果是空的指針使用delete是安全的。

使用delete的關鍵:用於new分配的記憶體不是要使用於new的指針,而是用於new的地址

❌警告:不能創建兩個指向同一個記憶體塊的指針。會增加錯誤地刪除同一個記憶體塊兩次的可能性。

7.5 使用new創建動態數組

C++中,創建動態數組,只需要將數組的元素類型元素數目告訴new即可。必須在類型名後面加上方括弧,其中包含了元素數目。

通用格式:

Type_name *pointer_name = new Type_name[num_element];
//例子
int * psome =new int[10]; // 創建10個int元素的數組

new運算符會返回第一個元素的地址

如果使用完new分配的記憶體,使用delete進行記憶體的釋放。

delete [] psome; // 進行記憶體的釋放

delete和指針直接的方括弧告訴程式,應釋放整個數組,不僅僅是指針指向的元素。

delete中的方括弧的有無取決於使用new時的方括弧有無

對於指針數組的使用,直接可以按照普通數組的使用即可。

7.6 使用new和delete時,要遵循的規則

  • 不要使用delete來釋放不是new分配的記憶體。
  • 不要使用delete釋放同一個記憶體塊兩次。
  • 如果使用new[]數組分配記憶體時,則應使用delete[] 來釋放。
  • 如果使用new[]為一個實體分配記憶體,則應使用delete(沒有方括弧)來釋放。
  • 對空指針使用delete時很安全。

8. 指針、數組和指針算術

指針和數組基本等價的原因:指針算術(pointer arithmetic)C++ 內部處理數組的方式

  • 整數變數 + 1,其增加1
  • 指針變數 + 1,增加的量等於它指向的類型的位元組數
    獲取數組地址的兩種方式
double * pw = wages; // 數組名 = 地址 ;將pw聲明為指向double類型的指針。然後將其初始化為wages - - - wages數組中第一個元素的地址。

short * ps = &wages[0]; // 使用地址操作;使用地址運算符來將ps指針初始化為stacks數組的第一個元素。

8.1 指針問題小結

8.1.1 聲明指針

要聲明指向特定類型的指針,語法格式:

TypeName *pointerName;
// 例子
double * pn; // pn 指向一個double類型
char * ps;  // ps 指向一個char類型

8.1.2 給指針賦值

將記憶體地址賦給指針。可以對變數名應用 & 運算符,來獲得被變數名的記憶體地址,new運算符返回未命名的記憶體的地址。

示例:

double * pn;  // pn 指向一個double類型
double * pa; // pa 指向一個double類型

char * pc; // pc 指向一個char類型
double bubble = 3.2; 

pn = &bubble; // 把bubble的地址賦值給 pn
pc = new char; // 新建char地址並分配給pc

8.1.3 對指針解除引用

對指針解除引用意味著獲得指針指向的值

  • 方法1:對指針應用解除引用間接值運算符(*)來解除引用。
cout<<*pn;
*pc = 's';
  • 方法2:使用數組表示法不可以對未初始化為適當地址的指針解除引用

8.1.4 數組名

多數情況下,C++將數組名視為數組的第一個元素的地址

int tacos[10]; // 此時的tacos同樣也是&tacos[0]

8.1.5 指針算術

C++中允許指針和整數相加。加1 的結果等於原來的地址值加上指向的對象佔用的總位元組數

也可以將一個指針減去另一個指針,獲得兩個指針的差。得到一個整數,僅當兩個指針指向同一個數組(也可以指向超出結尾的一個位置)時,這種情況會得到兩個元素的間隔。

8.1.6 數組的動態聯編和靜態聯編

使用數組聲明來創建數組時,將採用靜態聯編,即數組長度在編譯時設置。

int tacos[10] // 靜態聯編

使用new[]運算符創建數組時,將採用動態聯編(動態數組),即將在運行時為數組分配空間,其長度為運行時設置。

使用這類數組後,要使用delete[]釋放所佔用的記憶體。

8.1.7 數組表示法和指針表示法

使用方括弧數組表示法等同於對指針解除引用

數組名和指針變數也是一樣。所以對於指針和數組名,既可以使用指針表示法,也可以使用數組表示法

int * pt = new int [10];
*pt = 5;
pt[0] = 6;
pt[9] = 5;
int coats[10];
*(coats + 4) = 12;

8.2 指針和字元串

數組名是第一個元素地址

如果給cout提供一個字元的地址,則它將從該字元開始列印,直到遇到空字元為止。

在cout和多數C++表達式中,char數組名char指針以及用引號括起來的字元串常量都被解釋為字元串第一個字元的地址

不要使用字元串常量或未被初始化的指針來接收輸入

在字元串讀入程式時,應使用已分配的記憶體地址。該地址不是數組名,也可以使用new初始化過的指針。

strcpy()接受兩個參數,第一個:目標地址,第二個:要複製的字元串的地址

要確定目標空間有足夠的空間來存儲副本。

8.3 使用new創建動態結構

對於在指定結構成員時,句點運算符箭頭運算符的選擇時:

  • 如果結構標識符是結構名,則使用句點運算符(.)
  • 如果標識符是指向結構的指針,則使用箭頭運算符(->)

把new用於結構的兩個步驟

  • 創建結構

    要創建結構,需要同時使用結構類型和new。

  • 創建訪問其成員。

8.4 C++管理數據記憶體的方式

  • 自動存儲
    在函數內部定義的常規變數使用自動存儲空間,稱為自動變數

    只在特定函數被執行時存在。

自動變數時一個局部變數,作用域為包含它的程式碼塊。通常存儲在中,遵循後進先出(LIFO)

  • 靜態存儲

    • 變數稱為靜態的方式
      • 在函數外面定義
      • 在聲明變數時使用關鍵字static。

    整個程式執行期間都存在的存儲方式(存在於程式的整個生命周期)。

  • 動態存儲
    記憶體池(自由存儲空間或堆)用於靜態變數和自動變數,且記憶體是分開的。

  • 執行緒存儲(C++11特性)

9. 數組替代品 — 模板類

模板類vectorarray是數組的替代品。

9.1 模板類vector

模板類vector類似於string類,也是一種動態數組

  • vector對象包含在vector頭文件中。
  • vector包含在名稱空間std中,使用using編譯指令using聲明std::vector
  • 模板使用不同的語法來指出它存儲的數據類型
  • vector類使用不用的語法來指定元素數

9.2 模板類array(C++11)

位於名稱空間std中,與數組一樣,array對象的長度固定,也使用棧(靜態記憶體分配),而不是自由存儲區

頭文件 array。

9.3 數組、vector和array的區別

無論是數組、vector對象還是array對象,都可使用標準數組表示法來訪問各個元素。

地址可知,array對象和數組存儲在相同的記憶體區域(即棧)中,vector對象存儲在自由存儲區域或堆中。

可以將一個array對象賦給另一個array對象,對於數組,必須逐個元素複製數據。

Github地址://github.com/SolerHo/cpp-Primer-Plus-6e-Notes/blob/master/Chapter04/README.md

第四章 學習筆記完畢,如有大佬在文中發現錯誤,請指出,謝謝

Tags: