前言
個人覺得學習程式設計最有效的方法是閱讀專業的書籍,通過閱讀專業書籍可以建構更加系統化的知識體系。 一直以來都很想深入學習一下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對象;而對于數組,必須逐元素複制資料。