天天看點

C++ Primer Plus學習筆記之複合類型(下)

作者:好先生FX

前言

個人覺得學習程式設計最有效的方法是閱讀專業的書籍,通過閱讀專業書籍可以建構更加系統化的知識體系。 一直以來都很想深入學習一下C++,将其作為自己的主力開發語言。現在為了完成自己這一直以來的心願,準備認真學習《C++ Primer Plus》。 為了提高學習效率,在學習的過程中将通過釋出學習筆記的方式,持續記錄自己學習C++的過程。

本篇前言

本章首先将介紹除類以外的所有複合類型,以及将介紹new和delete及如何使用它們來管理資料。另外,還将簡要地介紹string類,它提供了另一種處理字元串的途徑。

七、指針和自由存儲空間

指針是一個變量,其存儲的是值的位址,而不是值本身。通過對變量應用位址運算符(&),可以獲得正常變量的位址。代碼如下:

// address.cpp -- 使用 & 操作符查找位址
#include <iostream>
int main()
{
    // 作者:好先生FX
    using namespace std;
    int age = 18;
    cout << "age 變量的值等于" << age << "\n";
    cout << "age 變量的存儲位址是" << &age << endl;
    return 0;
}
           

運作結果如下:

age 變量的值等于18
age 變量的存儲位址是0xf00d5ff69c
           

*運算符稱為間接值(indirect value)或解除引用(dereferencing)運算符将其應用于指針,可以得到該位址處存儲的值(這和乘法使用的符号相同,C++根據上下文來确定所指的是乘法還是解除引用)。

// pointer.cpp -- 我們的第一個指針變量
#include <iostream>
int main()
{
    using namespace std;
    int age = 18;// 聲明一個變量
    int *p_age;// 聲明指向int類型的指針
    p_age = &age; // 将int類型的位址賦給指針
    cout << "age 變量的值等于" << age ;
    cout << ",*p_age 的值是" << *p_age ;
    cout << ",p_age 的值是" << p_age << endl;
    // 使用指針改變變量值
    *p_age = *p_age + 1;
    cout << "現在 age 變量的值等于" << age << endl;
    return 0;
}
           

運作結果如下:

age 變量的值等于18,*p_age 的值是18,p_age 的值是0x20237ffad4
現在 age 變量的值等于19
           

提示:

*運算符兩邊的空格是可選的,對編譯器來說沒有任何差別。以下四種寫法都是等價的:

int*p_age;
int *p_age;
int* p_age;
int * p_age;
           

1、聲明和初始化指針

對每一個指針變量名,都需要使用一個 * ,int *是一種複合類型,是指向int的指針。

int *p1, p2;
           

以上代碼表示的是,聲明一個指針(p1)和一個int變量(p2)。

double *p_price;
           

以上代碼,聲明了一個指向double的指針,是以編譯器知道*p_price是一個double類型的值。和數組一樣,指針都是基 于其他類型的。

雖然p_age和p_price指向兩種長度不同的資料類型,但這兩個變量本身的長度通常是相同的。

可以在聲明語句中初始化指針。在這種情況下,被初始化的是指針,而不是它指向的值。也就是說下面的語句将p_age(而不是*p_age)的值設定為&age:

int age = 18;
int *p_age = &age;
           

2、指針的危險

在C++中建立指針時,計算機将配置設定用來存儲位址的記憶體,但不會配置設定用來存儲指針所指向的資料的記憶體。

int *p_age;
*p_age = 18;
           

p_age指向的地方可能并不是要存儲18這個值得地方,一定要在對指針應用解除引用運算特(*)之前,将指針初始化為一個确定的、适當的位址。

3、指針和數字

指針不是整型、雖然計算機通常把位址當作整數來處理。

要将數字值作為位址來使用,應通過強制類型轉換将數字轉換為适當的位址類型,代碼如下:

int *p_age;
p_age = (int*)0x3041bffbe4;
           

注意:

p_age是int值的位址并不意味着p_age本身的類型是int。例如,在有些平台中,int類型是個2位元組值,而位址是個4位元組值。

4、使用new來配置設定記憶體

通過new運算符,可以為需要的類型資料配置設定一個長度正确的記憶體塊,并傳回該記憶體的位址。

為一個資料對象 (術語“資料對象”比“變量”更通用,它指的是為資料項配置設定的記憶體塊。這裡的“對象”不是“面向對象給程”中的對象,而是一樣“東西”。) 獲得并指定配置設定記憶體的通用格式如下:

typeName *pointer_name = new typeName;
           

new typeName告訴程式,需要适合存儲typeName類型的記憶體。

在C++中,值為0的指針被稱為空指針。

5、使用delete釋放記憶體

通過delete運算符,可以在使用完記憶體後,能夠将其歸還給記憶體池。

int * ps = new int;
delete ps;
           

上述代碼會釋放ps指向的記憶體,但不會删除指針ps本身。一定要配對的使用new和delete;否則将發生記憶體洩漏(memory leak)。

6、使用new來建立動态數組

使用new和delete時,應遵守以下規則:

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

建立一個包含10個包含int元素的數組然後釋放記憶體,可以通過如下代碼:

int * psome = new int [10];
delete [] psome;
           

由于psome指向數組的第一個元素,是以*psome是第一個元素的值。要通路第二個及之後的元素方括号加數字可以通過如下代碼:

var p1 = psome[0];//通路第一個元素
var p2 = psome[1];//通路第二個元素
...
var p10 = psome[9];//通路第十個元素
           

對指針psome進行算術運算,可以修改指針指向的位址。例如通過如下代碼,可以将psome[0]指向數組的第二個元素:

psome = psome + 1;
var p1 = psome[0];//通路第二個元素
           

八、指針、數組和指針算術

指針和數組基本等價的原因在于指針算術(poimter aritmetic)和 C++内部處理數組的方式。

将整數變量加1後,其值将增加1; 但将指針變量加1後,增加的量等于它指向的類型的位元組數。

數組表達式psome[1],C++編評器将該表達式看作是*(psome+1),也就是先對指針進行算術運算移動位址至第二個元素的位址,然後通過*對指針進行解引用得到其中的值。

在多數情況下,C++将數組名視為數組的第1個元素的位址。

一種例外情況是,将sizeof運算符用于數組名用時,此時将傳回整個數組的長度(機關為位元組)。

建立動态結構時不能将成員運算符句點用于結構名,因為這種結構 沒有名稱,隻是知道它的位址。C++專門為這種情況提供了一個運算符:箭頭成員運算符(->)。該運算符由連字元和大于号組成,可用于指向結構的指針,就像點運算符可用于結構名一樣。

根據用于配置設定記憶體的方法,C++有3種管理資料記憶體的方式:自動存儲、靜态存儲和動态存儲(有時也叫作自由存儲空間或堆)。

  • 自動存儲:在函數内部定義的正常變量使用自動存儲空間,被稱為自動變量(automatic variable),這意味着它們在所屬的函數被調用時自動産生,在該函數結束時消亡。
  • 靜态存儲:是整個程式執行期間都存在的存儲方式。使變量成為靜态的方式有兩種:一種是在函數外面定義它;另一種是在聲明變量時使用關鍵字static。
  • 動态存儲:new和delete運算符提供了一種比自動變量和靜态變量更靈活的方法。它們管理了一個記憶體池,這在C++中被稱為自由存儲空間 (free store)或堆 (heap)。該記憶體池同用于靜态變量和自動變量的記憶體是分開的。

九、類型組合

本章介紹了數組、結構和指針。

從結構開始:

struct Person // 聲明結構
{
    char name[20];
    int age;
};           

建立這種類型的變量:

Person p1;           

然後使用成員運算符通路其成員:

p1.name = "張三";           

可建立指向這種結構的指針:

Person * pp1 = &p1;           

該指針設定為有效位址後,就可以使用間接成員運算符來通路成員:

pp1->name = "張三";           

建立結構數組:

Person persons[3];           

然後,可以使用成員運算符通路元素的成員:

persons[0].name = "張三";           

由于數組名是一個指針,是以可以直接使用間接成員運算符:

(persons + 1)->name = "李四";// 與persons[1].name = "李四";相同           

可建立指針數組:

Person * ppersons[3] = {  &p1 , &p2 , &p3 };           

通過間接成員運算符通路:

cout << pperson[0]->name << endl;
           

建立指向數組的指針:

const Person ** pppersons = ppersons;
           

C++11提供了可以推斷類型的auto關鍵字,可以更友善地聲明指向數組的指針:

auto apppersons = ppersons;
           

由于apppersons是一個指向結構指針的指針,是以*apppersons是一個結構指針,可通過間接成員運算符通路元素:

cout << (*apppersons)->name << endl;
cout << *(apppersons+1)->name << endl;
           

十、數組的替代品

模闆類vector和array是數組的替代品。

1、模闆類vector

模闆類vector類似于string類,也是一種動态數組。可以在運作階段設定vector對象的長度,可在末尾附加新資料,還可在中間插入新資料。基本上,它是使用new建立動态數組的替代品。實際上,vector類确實使用new和delete來管理記憶體,但這種工作是自動完成的。

首先,要使用vector對象,必須包含頭檔案vector。其次,包含頭檔案 包含在名稱空間std中。再次,模闆使用不同的文法來指出它存儲的資料類型。最後,vector類使用不同的文法來指定元素數。下面是一些示例;

#include <vector>
...
using namespace std;
vector<int> vi;//建立一個int類型長度為0的數組
int n;
cin >> n;
vector<double> vd(n);//建立一個有n個元素的double類型數組
           

由于vector對象可自動調整長度,是以可以将的切始長度設定為零。但要調整長度,需要使用vector包中的各種方法。

一般而言,下面的聲明建立一個名為vt的vector對象,它可存儲n_elem個類型為typeName的元素:

vector<typeName> vt (n_elem);
           

其中參數n_elem可以是整型常量,也可以是整型變量、

2、模闆類array(C++11)

vector類的功能比數組強大,但付出的代價是效率稍低,如果您需要的是長度固定的數組,使用數組是更佳的選擇,但代價是不那麼友善和安全。基于上述情況,C++11新增了模闆類array,它也位于名稱空間std中。與數組一樣,array對象的長度也是固定的,也使用棧(靜态記憶體配置設定),而不是自由存儲區,是以其效率與數組相同,但更友善,更安全。要建立array對象,需要包含頭檔案array。array對象的建立文法與 vector稍有不同:

#include <array>
...
using spacename std;
array<int,3> ai;//建立包含5個int類型的array對象
array<double,4> ad = {1.1 , 2.2 , 3.3 , 4.41};
           

建立一個名為arr包含n_elem個類型為typeName的array對象:

array<typeName , n_elem> arr;
           

與vector對象不同的是,n_elem不能是變量。

3、比較數組、vector對象和array對象

  • 無論是數組、vector對象還是array對象,都可使用标準數組表示法來訪同各個元素。
  • array對象和數組存儲在相同的記憶體(即棧)中,而vector對象存儲在另個區域(自
  • 自存儲區或堆)中。
  • 可以将一個array對象賦給另一個array對象;而對于數組,必須逐元素複制資料。

繼續閱讀