指針對于學習C/C++來說是座難以翻越的大山,我們有太多的腦細胞前仆後繼死在了這上面,然而指針也是C++的精華所在,越不過指針這一關,那C++自然也沒法往下學,關于C++中的if判斷語句和for、while、do...while等語句就不做具體總結。
一、記憶體與位址
要了解指針必然要先了解指針所操作的對象,指針也屬于變量,它裡面存儲的就是記憶體中的位址。
記憶體與位址的基本關系如下圖:

當我們用C++規定好的規則寫好代碼,再編譯後運作,最終結果輸出在螢幕上,這其中需要計算機各種資源的協助,記憶體就是這些計算機資源的配置設定這和管理者,你的程式需要計算,記憶體調用計算資源;你需要進行檔案的讀寫,記憶體為你建立檔案讀寫流;當你需要建立網絡連接配接,記憶體調用計算機的網絡資源......
一般我們是看不到記憶體的工作過程的,在有了指針後,我們可以實作對記憶體的操作,這很讓人激動,但有時要是操作不當也很危險,記憶體中有我們計算機運作的重要資源,要是動了他們,電腦癱瘓,系統崩潰也不是不可能。
二、指針的基本使用
指針的使用十分靈活,用途也很廣,同時也有很多操作細節,光看書和聽課很難了解指針,自己實際操作再結合記憶體位址分析,才能更好的了解指針,下面是一些指針的基本使用方法,應該還有不少用法,大家可以自己去探索和挖掘。
#include<iostream>
using namespace std;
void main()
{
int price = 203; //先定義一個普通變量
//int *pprice = &price; //這種*号在變量名左上方的方式也是正确的,看個人習慣
int* pprice = &price; //這就是聲明了一個指針,*現在表示該變量是指針變量
//&号就是取位址符号,将price變量在記憶體中的位址賦給指針
//pprice就是個位址,會輸出一串十六進制的數字,這裡的*号為間接運算符,可以取得對應位址中存放的值
cout << "位址:" << pprice << " 位址中的值:" << *pprice << endl;
}
你可以重複運作該程式幾次,會發現每次輸出的位址值都不一樣,這是因為每次記憶體配置設定的位址是不固定的,這些位址是空閑和可用的就行。
可以定義任意類型的指針,基本資料類型、複合類型、函數、類等,類型越複雜,你對指針的了解也要越詳盡。下面是關于複合類型指針的用法:
#include<iostream>
#include<vector>
#include<string>
using namespace std;
void main()
{
vector<int> myVector;
vector<int>* pVector = &myVector; //定義vector<int>類型的指針,并将
pVector->push_back(100); //可以通過 -> 來調用指針中對象的變量、方法
pVector->push_back(200);
cout << myVector[0] << " " << myVector[1] << endl; //檢視使用指針來添加元素是否成功
struct Student //定義結構體
{
int age;
bool isBoy;
string name;
};
Student student; //執行個體化一個學生結構體
Student* pStudent = &student; //定義結構體指針
pStudent->age = 18; //使用指針為結構體變量指派
pStudent->isBoy = true;
pStudent->name = "xiaoming";
//輸出結構結構體中的值
cout << "年齡:" << student.age << " 是否為男孩:" << student.isBoy << " 姓名:" << student.name << endl;
//通過指針通路結構體中的值
cout << "年齡:" << pStudent->age << " 是否為男孩:" << pStudent->isBoy << " 姓名:" << pStudent->name << endl;
}
關于空類型指針:
空類型指針有點類似與我們之前提到的auto關鍵字,auto可以接收任意類型的變量,同樣的void*也可以接收任意類型的指針。
#include<iostream>
using namespace std;
void main()
{
void* pValue1 = nullptr; //空類型的指針
int value1 = 1200;
pValue1 = &value1; //這種指針會接收任何其他類型的指針
//cout << *pValue1 << endl; //然而當你想通路該指針時必需為其強制轉換指針類型
cout << *((int*)pValue1) << endl; //這種方式才對
}
三、指針與常量
關于指針與常量之間有不少細節,因為記憶體配置設定的位址是變動的,而常量又是始終不變的。
#include<iostream>
using namespace std;
void main()
{
//1.指向常量的指針
const int myValue = 110;
//定義一個指向常量的指針,該指針指向的内容不可修改,但該指針可以指向其他位址
const int* pValue = &myValue;
//輸出為110
cout << *pValue << endl;
//這裡又有一個常量
const int myChange = 990;
//還是使用上面的指針去指向這個位址
pValue = &myChange;
//輸出為990
cout << *pValue << endl;
//2.常量指針
int data = 123;
//現在該指針為常量指針,意味着該指針指向的位址不可更改,它隻能指向data變量所在的位址
int* const pConstPoint = &data;
cout << *pConstPoint << endl;
//但data不是常量,它可以被更改,
data = 999;
//輸出此時data修改後的值,為999
cout << *pConstPoint << endl;
//3.指向常量的常量指針
const int myValue2 = 888;
//這就是指向常量的常量指針,指針所指的位址不可變,位址中的值也不可變,這裡有兩個const
const int* const pConstValue = &myValue2;
}
- 常量不可更改,但常量指向該常量的指針可以修改;
- 常量指針所指位址不可更改,但該位址中存放的值可以更改;
- 當指針為常量指針,指向的位址中存放的也是常量時,這兩者都不可修改。
四、指針與數組
數組也屬于複合資料類型,但用數組指針可以實作指針的運算。
#include<iostream>
using namespace std;
void main()
{
//其實數組本身就是一個儲存了數組首個元素位址的變量
int myArray[10];
//直接輸出數組數組的變量名,此時輸出的就是myArray[0]的位址,這兩者相等
cout << myArray << " 首個元素的位址:" << &(myArray[0]) << endl;
//這裡給數組指針指派需要使用{}
int* pArray[] {myArray};
//也可以這樣指派,最終這兩者的操作方式都一樣
int* pArray2 = myArray;
//通過循環給數組中的每個元素指派
for (int i = 0; i < 10; i++)
{
myArray[i] = i * 10;
}
//周遊檢視數組中的元素
cout << "正常周遊方式:";
for (int i = 0; i < 10; i++)
{
cout << myArray[i] << " ";
}
cout << endl;
//現在我們通過操作指針周遊數組
cout << "指針周遊方式:";
//數組指針其實是有兩層位址,
for (int i = 0; i < 10; i++)
{
//pArray指針指向的位址中存放的也是位址,這個位址中才存有我們的資料
cout << "指針所在位址:" << pArray + i
<< " 指針所指向的位址: " << *(pArray + i)
<< " 位址中的值: " << *((*pArray) + i) << endl;
}
int a = 100;
//通過指針修改數組中的值,注意指派的得是變量
*(*pArray) = a;
cout << myArray[0] << endl;
}
這裡有指針的加減運算,還涉及到指針的指針,可能有點繞,但這也隻是指針的基礎知識,我會用圖來解釋:
記憶體會為數組配置設定一段連續的記憶體空間來存儲他們,這一段空間的位址也是連續的,我們可以通過移動指針到相應的位址,再擷取該位址中存放的值。
關于指針和數組的内容還有不少,這裡想講清楚篇幅就太大了,大家可以仔細查閱相關的内容。
五、動态配置設定記憶體
動态的記憶體配置設定是在程式運作期間配置設定存儲資料所需要的記憶體,不是在編譯時配置設定預定義的記憶體空間,這讓我們可以更加靈活地使用計算機記憶體,減小浪費。
運算符new和delete
#include<iostream>
using namespace std;
void main()
{
double* pvalue = nullptr;
pvalue = new double; //為該指針申請一份記憶體空間
*pvalue = 3.1415; //現在我們可以在該指針所指的位址空間中寫入資料了
delete pvalue; //釋放該指針所指的記憶體,現在那塊記憶體可以被重新配置設定了
pvalue = nullptr; //最後還要重新設定下指針,因為他原來所指向的位址已經不歸他所有了
double* pdata{ new double[20] }; //申請一份數組空間
delete[] pdata; //這是釋放位址空間的寫法
pdata = nullptr; //不要忘了重新設定指針
}
雖然用法很簡單,但在實際開發中使用的時機和方式都是需要仔細思量的,而且動态記憶體配置設定是存在風險的,可能會出現兩種問題:
1、記憶體洩漏:這也是最常見的問題,在你new配置設定空間時,使用完該記憶體後沒有釋放它,就會出現記憶體洩漏, delete釋放記憶體是十分重要的。
2、自由存儲區的碎片:當你的程式頻繁地配置設定和釋放記憶體是有可能會出現這個問題,不過現在基本不常見,由于 現在計算機的虛拟記憶體提供了很大的記憶體,想詳細了解的同學可以百度或維基。
六、引用
六、引用
既然有了指針,就必然會牽扯到引用,這兩者看上去很相似,實際上完全不同,指針操作的是某個對象的位址,而引用隻是作為某個對象的别名。
#include<iostream>
using namespace std;
void main()
{
int myValue = 34;
int& rvalue = myValue; //& 定義引用,類似于指針,但與指針完全不同
//引用是一個名稱,可以用作某個對象的别名
cout << rvalue << endl; //這裡可以直接通路引用中的值,不用像指針需要解除引用
rvalue += 66;
cout << rvalue << " " << myValue << endl; //引用的修改,原來的變量值也會修改
int* pvalue = &rvalue; //将引用的位址交給指針
*pvalue = 888; //通過位址修改引用的值
cout << rvalue << " " << myValue << endl; //原始變量也一樣修改了,是以說rvalue隻是myValue的别名
}
雖然現在看上去引用有些雞肋,但它也提供了一些異常強大的功能,有些時候沒有引用,一些操作就無法完成,這個隻能以後詳細講了。
總結:指針時C++基礎學習的一大難點,很多時候,如非必要,最好不要使用指針,一是使用指針存在一定風險,二是當指針用多了也會很亂,二重指針還好,三重四重指針真的可怕。