在學(xué)習(xí)C/C++的過(guò)程中,指針常常讓初學(xué)者感到困惑。其實(shí),指針并沒(méi)有那么復(fù)雜,理解了它的基本原理和使用方法之后,你會(huì)發(fā)現(xiàn)它不過(guò)是一個(gè)存儲(chǔ)地址的變量而已。
一、變量的本質(zhì)
實(shí)際上,所有的變量都是一個(gè)地址,并不是小白理解的int a 是一個(gè)變量而int* a是一個(gè)地址。實(shí)際上所有的變量都是一個(gè)地址。
變量的作用是為了指向一塊存儲(chǔ)空間,修改和讀取內(nèi)存中的數(shù)據(jù)。而指向存儲(chǔ)空間需要的是一段真實(shí)內(nèi)存地址,而操作系統(tǒng)為了避免真實(shí)內(nèi)存沖突,所以每個(gè)進(jìn)程必須使用虛擬內(nèi)存地址,由硬件內(nèi)存管理單元和操作系統(tǒng)共同決定虛擬地址和真實(shí)地址的映射。
因此,所有的變量實(shí)際上都是一個(gè)虛擬內(nèi)存地址。而int a這個(gè)變量的虛擬內(nèi)存地址指向的是一個(gè)可以存儲(chǔ)一個(gè)int格式數(shù)據(jù)的內(nèi)存空間,而int* a這個(gè)變量的虛擬內(nèi)存地址指向的是一個(gè)虛擬內(nèi)存地址,這個(gè)虛擬內(nèi)存地址指向的是一個(gè)可以存儲(chǔ)一個(gè)int格式數(shù)據(jù)的內(nèi)存空間。
你會(huì)發(fā)現(xiàn)上面這段話,有兩段一模一樣的描述。如果你看懂了上面這段話,就可以理解int* a儲(chǔ)存的其實(shí)是一個(gè)int類(lèi)型變量的原型。實(shí)際上int** a儲(chǔ)存的就是一個(gè)int*型變量的原型。
你始終要知道的是,所有變量都是一個(gè)虛擬內(nèi)存地址,區(qū)別就是指向的內(nèi)存空間作用不同而已,int和int*沒(méi)有任何本質(zhì)上的不同。
二、指針的基本原理
1. 什么是指針?
在C/C++中,指針是一種變量,它存儲(chǔ)的是另一個(gè)變量的地址。簡(jiǎn)單來(lái)說(shuō),普通變量存儲(chǔ)的是數(shù)據(jù)本身,而指針存儲(chǔ)的是數(shù)據(jù)所在的內(nèi)存地址。
2. 指針的定義和使用
指針變量的定義需要指定指針?biāo)赶虻臄?shù)據(jù)類(lèi)型,語(yǔ)法如下:
數(shù)據(jù)類(lèi)型* 指針變量名;
例如:
int a = 10; // 定義一個(gè)整型變量
int* p = &a; // 定義一個(gè)指向整型的指針變量,并將a的地址賦值給它
在上面的代碼中:
-
&a表示取變量a的地址。 -
p是一個(gè)指針變量,它存儲(chǔ)了變量a的地址。
你可以通過(guò)*p來(lái)訪問(wèn)指針指向的變量的值:
cout << *p << endl; // 輸出10,表示訪問(wèn)指針p指向的變量a的值
3. 指針的本質(zhì)
指針本質(zhì)上是一個(gè)普通變量,只不過(guò)它存儲(chǔ)的是一個(gè)地址。理解了這一點(diǎn),就不難理解為什么指針可以和地址、內(nèi)存操作聯(lián)系起來(lái)。
例如:
cout << p << endl; // 輸出p存儲(chǔ)的地址(a的地址)
cout << *p << endl; // 輸出p指向地址中存儲(chǔ)的值(a的值)
4. 解引用運(yùn)算符和取址符
int a = 10;
int* p = &a;
cout << *p << endl;
*是一個(gè)運(yùn)算符,可以讀取p這個(gè)變量?jī)?chǔ)存的地址指向的值。
在非形參的定義中,&是取地址符上面的&a實(shí)際上就是拿到變量a的原型,文章開(kāi)頭說(shuō)過(guò)所有的變量其實(shí)都是一個(gè)地址(注意a雖然原型是個(gè)地址但a代表的地址是不可修改的,它被賦值給了一個(gè)指針p,而指針儲(chǔ)存的地址是可以修改的,他是指針類(lèi)型變量的值,修改了這個(gè)值并不會(huì)影響變量a)。
二、指針的使用場(chǎng)景
指針不僅是C/C++的核心,也是程序設(shè)計(jì)中非常重要的一部分。以下是指針的常見(jiàn)使用場(chǎng)景:
1. 動(dòng)態(tài)內(nèi)存分配
在C++中,可以使用指針動(dòng)態(tài)分配內(nèi)存:
int* p = new int(42); // 動(dòng)態(tài)分配一個(gè)整數(shù)并初始化為42
cout << *p << endl; // 輸出42
delete p; // 釋放內(nèi)存
動(dòng)態(tài)分配內(nèi)存時(shí),必須用delete釋放,否則可能會(huì)導(dǎo)致內(nèi)存泄漏。
2. 通過(guò)指針修改變量的值
指針可以用來(lái)間接修改變量的值:
void modify(int* p) {
*p = 20; // 修改指針指向的變量的值
}
int main() {
int a = 10;
modify(&a); // 將a的地址傳遞給函數(shù)
cout << a << endl; // 輸出20
return 0;
}
3. 指針與函數(shù)
指針可以用來(lái)實(shí)現(xiàn)函數(shù)參數(shù)的傳址調(diào)用:
void swap(int* x, int* y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main() {
int a = 5, b = 10;
swap(&a, &b); // 通過(guò)指針交換a和b的值
cout << a << " " << b << endl; // 輸出10 5
return 0;
}
三、C++中的引用
1. 什么是引用?
引用是C++中一種更高級(jí)的功能,可以看作是指針的“語(yǔ)法糖”。引用本質(zhì)上是一個(gè)變量的別名,它提供了一種更安全、更簡(jiǎn)潔的方式來(lái)操作變量。
引用的定義語(yǔ)法:
數(shù)據(jù)類(lèi)型& 引用名 = 變量名;
例如:
int a = 10;
int& ref = a; // 定義一個(gè)引用ref,作為變量a的別名
此時(shí),ref和a是同一個(gè)實(shí)體,修改ref的值會(huì)直接影響a:
ref = 20; // 修改ref的值
cout << a << endl; // 輸出20
2. 引用的使用場(chǎng)景
- 作為函數(shù)參數(shù):引用可以實(shí)現(xiàn)函數(shù)參數(shù)的傳址調(diào)用,同時(shí)避免使用指針的繁瑣語(yǔ)法。
void modify(int& ref) {
ref = 30; // 修改引用ref的值
}
int main() {
int a = 10;
modify(a); // 直接將變量傳遞給引用參數(shù)
cout << a << endl; // 輸出30
return 0;
}
- 作為函數(shù)返回值:引用可以用來(lái)返回一個(gè)變量的引用,從而直接操作原變量。
int& getValue(int& ref) {
return ref;
}
int main() {
int a = 10;
int& b = getValue(a);
b = 40; // 修改b的值,等同于修改a
cout << a << endl; // 輸出40
return 0;
}
四、數(shù)組與指針的關(guān)系
1. 數(shù)組的內(nèi)存布局
數(shù)組在內(nèi)存中是連續(xù)存儲(chǔ)的,數(shù)組名本質(zhì)上是一個(gè)指向數(shù)組第一個(gè)元素的"指針"。很多教程會(huì)把數(shù)組和指針劃上關(guān)系,實(shí)際上我們前文已經(jīng)論證過(guò),實(shí)際上數(shù)組也就是一種變量類(lèi)型,所有的變量名實(shí)際上都是指向第一個(gè)元素的"指針",只不過(guò)不是數(shù)組的話,它本身就只有一個(gè)元素而已。
int arr[3] = {1, 2, 3};
cout << arr << endl; // 輸出數(shù)組首元素的地址
cout << &arr[0] << endl; // 同樣輸出數(shù)組首元素的地址
2. 用指針遍歷數(shù)組
指針可以用來(lái)遍歷數(shù)組。很多教程會(huì)把下面的代碼來(lái)表示數(shù)組和指針的關(guān)系,實(shí)際上那是因?yàn)閿?shù)組申請(qǐng)的時(shí)候,虛擬內(nèi)存地址是連續(xù)的,所以可以通過(guò)運(yùn)算符來(lái)指向下一個(gè)單元。如果這不是一個(gè)數(shù)組,你一樣可以修改p中存儲(chǔ)的地址,只不過(guò)那樣就脫離和原變量的關(guān)系甚至造成程序異常。
int arr[3] = {1, 2, 3};
int* p = arr; // 指針指向數(shù)組首元素
for (int i = 0; i < 3; i++) {
cout << *(p + i) << " "; // 使用指針訪問(wèn)數(shù)組元素
}
3. 指針與數(shù)組名的區(qū)別
雖然數(shù)組名可以看作指針,但它與普通指針有一些區(qū)別:
- 數(shù)組名是常量指針,不能修改。這一點(diǎn)我們之前也提過(guò),變量本身所代表的地址,是不能修改的
- 普通指針可以動(dòng)態(tài)指向其他地址,而數(shù)組名始終指向數(shù)組的起始地址。
例如:
int arr[3] = {1, 2, 3};
int* p = arr; // 正確
p++; // 正確,可以移動(dòng)指針
arr++; // 錯(cuò)誤,數(shù)組名是常量,不能修改
4. 二維數(shù)組與指針
二維數(shù)組的每一行可以看作是一個(gè)一維數(shù)組,指針可以用來(lái)訪問(wèn)二維數(shù)組:
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*p)[3] = arr; // 定義一個(gè)指向含有3個(gè)元素的數(shù)組的指針
cout << p[0][1] << endl; // 輸出2
cout << p[1][2] << endl; // 輸出6
這里int (*p)[3]實(shí)際上可以重新賦值其它數(shù)組,比如再聲明一個(gè)int arr2[9][3]一樣可以賦值給p
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*p)[3] = arr; // 定義一個(gè)指向含有3個(gè)元素的數(shù)組的指針
cout << p[0][1] << endl; // 輸出2
cout << p[1][2] << endl; // 輸出6
// 定義一個(gè)新數(shù)組
int arr2[9][3] = {
{10, 11, 12}, {13, 14, 15}, {16, 17, 18},
{19, 20, 21}, {22, 23, 24}, {25, 26, 27},
{28, 29, 30}, {31, 32, 33}, {34, 35, 36}
};
// 讓 p 指向 arr2
p = arr2;
// 驗(yàn)證 p 是否正確指向 arr2
cout << p[0][1] << endl; // 輸出11
cout << p[8][2] << endl; // 輸出36
五、總結(jié)
指針是C/C++中非常強(qiáng)大的工具,理解了它的本質(zhì)——存儲(chǔ)地址的變量,就能更好地掌握它的用法。C++中的引用作為指針的更高層次封裝,提供了更簡(jiǎn)潔的語(yǔ)法和更高的安全性。
關(guān)鍵點(diǎn)總結(jié):
- 任何變量名實(shí)際上都代表一個(gè)虛擬地址,無(wú)論是整數(shù),指針,數(shù)組,復(fù)合結(jié)構(gòu),本質(zhì)上都是一樣的。
- 指針是存儲(chǔ)地址的變量,
*和&是操作指針的關(guān)鍵。 - C++中的引用是指針的語(yǔ)法糖,更易用且更安全。
- 指針和數(shù)組可以結(jié)合使用進(jìn)行靈活操作。