技術交流QQ群:1027579432,歡迎你的加入!
1.Cpp中的指針
- 每個變量都有一個內存位置,每一個內存位置都定義了可使用&地址運算符訪問的地址,它表示了變量在內存中的一個地址。
int var1; int var2[10]; cout << "變量var1的地址:" << &var1 << endl; // 訪問變量var1的在內存中的位置,使用&地址運算符,獲得變量在內存中的地址 cout << "變量var2的地址:" << &var2 << endl;
2.什么是指針?
-
指針也是一個變量,它存放的值是另一個變量的地址,即變量在內存中的位置,即內存中的直接地址。與其他變量或常量一樣,在使用指針存儲其他變量地址之前,必須對指針進行聲明,指針變量聲明的一般形式是:
數據類型 *指針變量名;
- 在上述指針的指針變量聲明過程中,數據類型必須是一個有效的C++數據類型,用來聲明指針變量的星號*與乘法中使用的星號是相同的。但是,在上面的聲明語句中,星號是用來指定一個變量是指針,有效的指針變量的聲明是:
int *p; float *p1; double *ptr; char *p2;
- 由于所有指針變量中存放的都是另一個變量在內存中的地址,所以不管指針指向的是int,float,char還是其他數據類型,指針變量的值實際數據類型都是一樣的!都是一個代表內存地址的十六進制數。不同數據類型的指針變量之間唯一的區別是:指針所指向的變量或常量的數據類型不同。32位機系統中,指針變量所占的字節數是sizeof(*p) = 4個字節。
3.C++中使用指針
- 指針變量使用時,會頻繁使用以下幾個操作:定義一個指針變量,把另外一個變量的地址賦值個指針,訪問指針變量中可用地址的值,這些操作都是通過*運算符來返回位于操作數所指定地址的變量的值。
int var = 20; // 普通變量的初始化 int *ip; // 指針變量的聲明 ip = &var; // 用指針變量存儲普通變量var的地址 cout << "var的值是:" << var << endl; cout << "普通變量var在內存中的地址: " << &var << endl; cout << "指針變量ip在內存中的地址: " << &ip << endl; cout << "通過訪問指針變量ip中存放的普通變量的地址來得到普通變量var的值: " << *ip << endl;
4.Cpp中指針詳解
-
C++中與指針有關的概念如下表:
與指針有關的概念.png
4.1 C++中的NULL指針
-
在指針變量聲明時,如果沒有明確的地址可以賦值時,為指針變量賦值一個NULL值是一個很好的習慣。NULL指針是一個定義在標準庫中的值為0的常量。如下面的程序:
int *ptr = NULL; cout << "指針變量ptr在內存中的地址:: " << &ptr << endl; cout << "ptr的值是: " << ptr << endl;
- 在大多數的操作系統上,程序不允許訪問地址為0的內存,因為該內存是操作系統保留的。然而,內存地址0有特別重要的意義,它表明該指針不指向一個可訪問的內存位置。但按照慣例,如果指針包含空值(零值),則假定它不指向任何東西。如果檢查一個空指針,需要使用if語句:
if(ptr) // 如果ptr非空,則完成 if(!ptr) // 如果ptr為空,則完成
- 因此,如果所有未使用的指針都被賦予空值,同時避免使用空指針,就可以防止誤用一個未初始化的指針。很多時候,未初始化的變量存有一些垃圾值,導致程序難以調試。
4.2 指針的算術運算
- 指針變量中存放的是一個用數值表示另一個變量的地址,可以對指針變量執行算術運算。對指針進行四個算術運算:++ -- - +,假設ptr是一個指向地址是1000的整型指針變量,是一個32位整數,對該指針執行算術運算ptr++后,ptr的指向位置是1004。因為ptr每增加一次,它都指向下一個整數位置,即當前位置往后移動4個字節。這個運算在不影響內存位置中實際值的情況下,移動指針到下一個內存位置。如果ptr執向的是一個地址是1000的char型字符,上面的運算會導致指針指向位置1001,因為下一個字符位置是1001。
- 遞增一個指針:在程序中使用指針代替數組,因為指針變量可以遞增,而數組不能遞增,因為數組名是一個指針常量,如下面的例子:
int a[MAX] = {10, 100, 200}; int *pp; pp = a; for (int i = 0; i<MAX; i++){ cout << "變量a[" << i << "]的地址是: " << &a[i] << endl; cout << "變量a[" << i << "]的地址是: " << pp << endl; cout << "變量a[" << i << "]的值是: " << a[i] << endl; cout << "通過訪問指針變量pp中存放的普通變量的地址來得到數組元素的值: " << *pp << endl; cout << "------------------------------------------------\n"; pp++; // 移動到下一個位置 }
- 遞減一個指針:對指針進行遞減運算,即把值減去其指向的數據類型的字節數,如下所示:
pt = &a[MAX - 1]; for (int i = MAX - 1; i >= 0; i--){ cout << "變量a[" << i << "]的地址是: " << &a[i] << endl; cout << "變量a[" << i << "]的地址是: " << pt << endl; cout << "變量a[" << i << "]的值是: " << a[i] << endl; cout << "通過訪問指針變量pt中存放的普通變量的地址來得到數組元素的值: " << *pt << endl; cout << "------------------------------------------------\n"; pt--; // 移動到下一個位置 }
- 指針的比較:指針可以用關系運算符進行比較,如果p1和p2指向兩個相關的變量,如同一個數組中的不同元素,則可以對p1和p2進行大小比較。
tt = a; int j = 0; while (tt <= &a[MAX - 1]){ cout << "變量a[" << j << "]的地址是: " << tt << endl; cout << "變量a[" << j << "]的值是: " << a[j] << endl; tt++; j++; }
4.3 指針vs數組
- 指針與數組是密切相關的,在很多情況下兩個是可以互換的。例如,通過一個指向數組開頭的指針,可以通過使用指針的算術運算或數組的索引來訪問數組,如下面例子:
int a[MAX] = {10, 100, 200}; int *pp; pp = a; for (int i = 0; i<MAX; i++){ cout << "變量a[" << i << "]的地址是: " << &a[i] << endl; cout << "變量a[" << i << "]的地址是: " << pp << endl; cout << "變量a[" << i << "]的值是: " << a[i] << endl; cout << "通過訪問指針變量pp中存放的普通變量的地址來得到數組元素的值: " << *pp << endl; cout << "------------------------------------------------\n"; pp++; // 移動到下一個位置 }
- 然而,指針與數組并不是完全可以互換的,如下面的例子:
int b[MAX] = {1, 11, 111}; cout << "b = " << b << endl; cout << "*b = " << *b << endl; for (int i = 0; i < MAX; i++){ *b = i; // 改變的是數組第一個元素的值 // b++; 數組名是一個指針常量,不能進行修改 *(b + 2) = 555; } // 不是說數組內的值不能修改,而是只要b的值(地址)不變,那么相應位置的數值改變了,也不會影響該數組的首地址。
4.4 指針數組
- 引入:一個由3個整數組成的數組
int c[MAX] = {6, 66, 666}; for (int i = 0; i < MAX; i++){ cout << "c[" << i << "]的值是: " << c[i] << endl; }
- 可能有一種情況是:希望數組存儲的是指向int/char或者其他數據類型的指針,下面是一個指向整數的指針數組的聲明:
int * ptr[3];
- 把ptr聲明為一個數組,由3個整數指針組成。因此,ptr中的每個元素,都是指向int類型的指針,見下面的例子:
int cc[MAX] = {6, 66, 666}; int *pc[MAX]; // 定義一個指針數組 for (int i = 0; i < MAX; i++){ pc[i] = &cc[i]; // 由于pc數組中的每個元素都是指向int類型的指針,所以這里賦值為cc[i]的每個元素的地址 } for (int i = 0; i < MAX; i++){ cout << "cc[" << i << "]的地址是: " << pc[i] << endl; cout << "cc[" << i << "] = " << *pc[i] << endl; }
- 也可以用一個指向字符的指針數組來存儲一個字符串列表,如下面例子:
const char *names[MAX] = { "Curry", "Harden", "Durant", }; // const char * names[MAX]是指針數組,它的本質是存儲指針的數組,存儲的元素是char類型的指針 for (int i = 0; i < MAX; i++){ cout << "names[" << i << "] = "<< names[i] << endl; // 輸出字符串的值 cout << "*names[" << i << "]的值是: " << *names[i] << endl; // 輸出指針所指向的字符串的首地址的值 cout << "names[" << i << "]的地址是: " << &names[i] << endl; // 輸出指針所指向的字符串的首地址 }
4.5 指向指針的指針(多級間接尋址)
-
指向指針的指針是一種多級間接尋址的形式,或者說是一個指針鏈。通常,一個指針中存儲的是另一個變量的地址,當再定義一個指向指針的指針時,第一個指針變量就包含了第二個指針變量的地址,第二個指針變量存儲的是另一個變量的地址。
指向指針的指針.png - 一個指向指針的指針變量必須進行聲明,即在變量名前加上兩個**,下面是一個指向int型的指針的指針:
int val = 3000; int *p1 = &val; int **p2 = &p1; // 指向int類型的指針的指針 int ***p3 = &p2; cout << "val的地址是: " << &val << endl; cout << "指針p1的地址是: " << &p1 << endl; cout << "指向指針p1的指針p2的地址是: " << &p2 << endl; cout << "指針指針p2的指針p3的地址是: " << &p3 << endl; cout << "--------------------------------------------------------\n"; cout << "指針p3中存放的是指針p2的地址: " << p3 << endl; cout << "指針p2中存放的是指針p1的地址: " << p2 << endl; cout << "指針p1中存放的是變量val的地址: " << p1 << endl; cout << "*p3得到的是p2的值即p1的地址: " << *p3 << endl; cout << "*(*p3)得到的是p1的值即a的地址: " << *(*p3) << endl; cout << "*(*(*p3))得到的是a的值: " << *(*(*p3)) << endl;
4.6 傳遞指針給函數
- C++中允許傳遞指針給函數,只需要簡單地聲明函數參數為指針類即可,下面的實例是傳遞一個long型指針給函數,并在函數內改變這個值:
void getSeconds(unsigned long *par){ *par = time(NULL); // 獲取當前秒數 } // 傳遞指針給函數 unsigned long sec; getSeconds(&sec); // 輸出實際值 cout << "number of seconds: " << sec << endl;
- 能接收指針作為參數的函數,也可以接收數組作為參數,如下所示:
double getAverage(int *arr, int size){ int i, sum = 0; double avg; for (i = 0; i < size; i++) sum += arr[i]; avg = double(sum) / size; return avg; } int balance[5] = {1, 2, 3, 4, 5}; double avg; // 傳遞一個指向數組的指針作為參數 avg = getAverage(balance, 5); cout << "平均值: " << avg << endl;
4.7 從函數返回指針
- 從函數返回指針,需要聲明一個返回指針的函數,如下所示:
int * myFunction(){ 語句; }
- 此外,C++不支持在函數外部返回局部變量的地址,除非定義的是局部變量static變量。
- 返回從函數返回指針的實例如下:
// 要生成和返回隨機數的函數 int * getRandom(){ static int r[10]; // 設置種子 srand((unsigned)time(NULL)); for (int i=0; i<10;i++){ r[i] = rand(); cout << "r[" << i << "]=" << r[i] << endl; } return r; } // 從函數返回一個指針 int *ppg; ppg = getRandom(); // 調用子函數,從子函數中返回一個指針 for (int i = 0; i < 10; i++) cout << "*(ppg + " << i << ") = " << *(ppg + i) << endl;
5.指針小結
- 指針的本質是變量,可以是各種數據類型,定義一個指針"*ip",其中"ip"需要賦于一個地址(可以用&符號獲取其他變量的地址再賦值給 ip),而"*ip"是一個具體的值,即讀取地址后獲得的值;&符號的意思是取地址,也就是返回一個對象在內存中的地址, *符號的意思是取得一個指針所指向的對象,也就是如果一個指針保存著一個內存地址,那么它就返回在那個地址的對象。
#include <iostream> using namespace std; int main() { int var = 20; int *ip; ip = &var; cout << "var的值:"; cout << var << endl; cout << "變量ip的儲存地址:"; cout << ip << endl; cout << "指針*ip的值:"; cout << *ip << endl; return 0; }
-
C++中允許聲明指向函數的指針,被稱為函數指針。函數指針的聲明類似于函數的聲明,只不過將函數名變成了(*指針名),定義方式: int (*fp)(int a); // 定義了一個指向函數(這個函數參數僅僅為一個int類型,函數返回值是int類型)的指針fp
int func(int b); { cout << b; return ++b; } // 定義一個函數指針 int(*p)(int); p = func; // 通過函數指針來調用函數 (*p)(5);