Cpp:指針

2015/11/13 16:16更新:

為什么得到的喜歡數少于關注的人數呢?我分析了一下原因,難道是因為“喜歡”按鈕在文章的最底部?!希望 @簡叔 改進呀 =_=

下面是原文,,


指針跟迭代器類似,也可以對指針進行 解引用*) 和 自增++) 操作,其含義和迭代器類似。

指針用于指向對象,與迭代器類似,指針提供對其所指對象的間接訪問。不同在于:指針指向單個對象,而迭代器只能訪問容器內的元素。具體來說,指針保存的是另一個對象的地址:

string s("hello");
string *p = &s;        //指針 p 保存 s 的地址

& 是取地址符號,該符號只能用于左值。只有變量作為左值時,才能取其地址。

1、指針的定義和初始化


vector<int> *vp;    //指向 vector<int> 對象的指針
int *ip;            //指向 int 對象的指針
string *sp;         //指向 string 對象的指針

另一種風格的指針

int* ip;

但容易引起誤解,會認為 int* 是一種類型。但下例只有 ip1 是指針,ip2 是普通的整型變量

int* ip1, ip2;

指針的取值

int val = 1024;
int *ip1 = 0;        //ip1 不指向任何對象
int *ip2 = &val;    //ip2 指向val
int *ip3;            //ip3 未初始化
ip1 = ip2;            //ip1 指向 val
ip2 = 0;            //ip2 不指向任何對象

避免使用未初始化的指針

初始化的約束

對指針初始化或賦值只能使用下列四種類型的值:

  • 0常量表達式(編譯時能獲得0值得const對象或字面值常量0);
  • 類型匹配的對象的地址;
  • 另一對象之后的下一地址;
  • 同一類型的另一有效指針;

舉例:

int ival = 1;
double dval = 1.5;
int zero = 0;
const int czero = 0;
int *ip;
double *dp;

ip = ival;        //error
ip = zero;        //error
ip = czero;        //ok:編譯時可以獲得 0
ip = 0;            //ok:字面值常量 0
ip = &ival;        //ok

dp = ip;        //error:類型不匹配

當然 0 值還可以使用從C語言繼承下來的預處理器變量 NULL,它在 cstdlib 頭文件中定義,其值為 0。

int *ip = NULL;    //相當于 int *ip = 0;

NULL 不是標準庫中定義的,所以不需要 std::

void*指針

void* 指針可以保存任何類型對象的地址:

double dval = 3.14;
double *dp = &dval;
void *vp = &dval;     //ok
vp = dp;                  //ok

但 void* 指針只允許有限的操作:

  • 與另一指針比較;
  • 向函數傳遞 void* 指針,或函數返回 void* 指針;
  • 給另一個 void* 指針賦值。

不允許 void* 指針操作所指向的對象。

3、指針的操作


*操作符

string s1("hello");
string s2("world");
string *sp = &s1;
cout << *sp << endl;    //解引用
sp = &s2;                    //改變指針所指對象
cout << *sp << endl;    //解引用
*sp = "hello world";    //改變所指的內容
cout << *sp << endl;    //解引用

輸出結果:

hello
world
hello world

sp 的解引用可以獲得 s 的值,因為 sp 指向 s,所以給 *sp 賦值可以改變 s 的值。

指針和引用的比較

雖然引用(reference)和指針都可以間接訪問另一個值,但有區別:

  • 定義引用時沒有初始化時錯誤的;
  • 賦值行為的差異:給引用賦值修改的是該引用所關聯的對象的值,并不是與另一個對象關聯;
  • 引用一經初始化,就始終指向同一個特定的對象

指針的例子

int ival1 = 1024, ival2 = 2048;
int *ip1 = &ival1, *ip2 = &ival2;
ip1 = ip2;        //ip1 此時指向 ival2

而引用的例子

int &r1 = ival1; int &r2 = ival2;
r1 = r2;        //將 ival2 賦給 ival1

上面的修改只會修改引用所關聯的對象,并不會改變改變引用本身。并且修改后,兩個引用還是指向原來關聯的對象。

指向指針的指針

指針本身也是需要占內存的對象,所以指針也可以被指針訪問。

int ival = 1024;
int *ip = &ival;
int **ipp = &ip;
cout << ival << endl;
cout << *ip << endl;
cout << **ipp << endl;

結果

1024
1024
1024

可以用三種方式輸出ival的值。

最后舉一個例子

int i = 10, j =10;
int *p1 = &i, *p2 = &j;
cout << *p1 << endl;
cout << *p2 << endl;
*p2 = *p1 * *p2;            //改變 p2 所指的內容
cout << *p1 << endl;
cout << *p2 << endl;
*p1 *= *p1;                //改變 p1 所指的內容
cout << *p1 << endl;
cout << *p2 << endl;

結果

10
10
10
100
100
100

4、使用指針訪問數組


指針與數組密切相關。特別是在表達式中使用數組名時,改名字會自動轉換為指向數組第一個元素的指針:

int val[] = {0, 1, 2, 3};
int *p = val;    //p 指向 val[0]
p = &val[3];    //p 指向 val[3]

指針的算術運算

上面的 p = &val[3]; 使用下標操作,也可以通過 指針的算術操作(pointer arithmetic) 來獲取指定的地址:

p = val;            //p 指向 val[0]
int *p2 = p + 3;    //p2 指向 val[3]

指針的算術操作只有在計算過后的新指針還是指向同一數組的元素才算合法,且不能越界,比如上面 int *p2 = p + 3; 改成 int *p2 = p + 4; 就會出錯,因為數組 val 的大小為 4,最大的下標為 3。

兩個指針之間還可以做減法

ptrdiff_t n = p2 - p1;    //n = 3

p1p2 之間相差3個對象,所以 n = 3n 是標準庫類型(library type) ptrdiff_t 類型。與 size_t 類型一樣,ptrdiff_t 也是一種與機器相關類型,在cstddef頭文件中定義。

允許在指針上加減 0,使指針保持不變。

解引用和指針算術操作之間的相互作用

在指針上加上一個整數值,其結果仍是指針。允許在這個結果上直接進行解引用操作,而不必先把它賦給一個新的指針:

int last = *(val + 3);    //相當于 val[3]

需要寫括號,如果寫成 int last = *val + 3; 則相當于 val[0] + 3

下標和指針

使用下標訪問數組時,它實際上是使用下標訪問指針:

int val[] = {0,1,2,3,4};
int i = val[0]    //val 指向數組 val[] 的第一個元素

另一個例子

int *p = &val[2];    //ok: p 指向第二個元素
int j = p[1];        //ok: p[1] 相當于 *(p + 1), j = val[3]
int k = p[-2];        //ok: p[-2] 相當于 val[0]

計算數組的超出末端指針

vector 類型提供的end操作將返回指向超出 vector 末端位置的一個迭代器。類似的,可以計算數組的超出末端指針的值:

const size_t arr_size = 5;
int arr[arr_size] = {1,2,3,4,5};
int *p = arr;
int *p2 = p + arr_size;    //ok:超出末端的指針

C++允許計算數組或對象的超出末端的地址,但不允許對此地址進行解引用操作。而計算數組超出末端之后或數組首地址之前的地址都是不合法的。

p2 不能解引用操作,但能與其他指針比較,或者用作指針算術表達式的操作數。

輸出數組元素

const size_t arr_size = 5;
int arr[arr_size] = {1,2,3,4,5};
for (int *begin = arr, *end = arr + arr_size; begin != end; ++begin){
    cout << *begin << "," << endl;
}

指針是數組的迭代器。上面的程序與迭代器程序非常相似,事實上,內置類型具有標準庫容器的許多性質,指針就是數組的迭代器。

5、指針與const限定符


兩種類型:

  • 指向const對象的指針;
  • const指針;

指向const對象的指針

如果指針指向的是const對象,則不再能夠使用指針來修改對象的內容。為了保證這個特性,C++語言強制要求指向const對象的指針也必須具有const特性:

const double *cdp;

cdp 是指向一個double類型const對象的指針,const限定了 cdp 指針所指向的對象,而并非 cdp 本身。即 cdp 不是const類型,在定義時不一定需要給它初始化;如果有需要的話,允許給 cdp 重新賦值,使其指向另一個const對象,但不能通過 cdp 修改所指對象的值:

*cdp = 2;    //error: *cdp might be const

把一個const對象的地址賦給一個普通的、非const對象的指針也會導致編譯錯誤:

const double pi = 3.14;
double *dp = π            //error: dp is a plain pointer
const double *cdp = π    //ok: cdp is a pointer to const

不能使用 void* 指針保存const對象的地址,而必須使用const void*指針保存:

const int val = 2;
const void *cvp = &val;        //ok: cvp is const
void *vp = &val;            //error: val is const

允許將非const對象賦給指向const對象的指針,例如:

double dval = 3.14;
const double *cdp = &dval;    //ok: 但是不能通過指針 cdp 改變 dval 的值

盡管 dval 不是 const 對象,但任何企圖通過指針 cdp 修改其值得行為都會導致錯誤。

事實上,也有辦法通過指向const對象指針改變所指的非const對象的值:

double dval = 3.14;
const double *cdp = &dval;    //ok: 但是不能通過指針 cdp 改變 dval 的值
*cdp = 3.14159;                //error: 不能通過 cdp 改變所指對象的值
double *dp = &dval;            //ok:dp 可以指向非const對象
*dp = 3.14159;                //ok
cout << *cdp << endl;        //此時會輸出:3.14159

可以這樣理解指向const對象的指針:自以為指向const對象的指針。但并不能保證所指向的對象一定是const對象。

const指針

這種指針本身不能修改:

int ival = 0;
int *const icp = &ival;    //icp 是const指針

這樣理解:icp 是指向int對象的const的指針。跟其他const對象類似,const指針的值不能修改,意思就是不能使 icp 指向其他對象。任何企圖給const指針賦值的行為都會出錯(即使是賦它本身的值也一樣):

icp = icp;    //error: icp is const

并且 const指針在定義時必須初始化

const指針所指對象的值能否被該指針修改完全取決于該對象的類型,例如 icp 指向一個普通的非 const int 型的對象,則可以使用 icp 修改該對象的值:

*icp = 1;

指向const對象的const指針

const double pi = 3.14159;
const double *const cdcp = π

上面的意思是既不能修改 pi 的值,也不能修改 cdcp 所指的對象。

指針和typedef

在typedef中使用指針往往會帶來意外的結果,下面是一個幾乎所有初學者都會搞錯的問題:請問 cstr 變量是什么類型?

typedef string *sp;
const sp cstr;

簡單的回答是:const sp 類型的指針。進一步:const sp 所表示的真實類型是什么?可能會認為是

const string *cstr;    //error

但這是錯誤的,原因是:聲明const sp時,const修飾的是 sp 類型,而 sp 是一個指針。所以等價于

string *const cstr;

理解const聲明:

//s1 和 s2 都是const
string const s1;
const string s2;

用typedef寫const類型定義時,const限定符加在類型前面容易引起誤解

string s;
typedef string *sp;
//下面三種定義時等價的
const sp cstr1 = &s;    //容易誤解
sp const cstr2 = &s;
string *const cstr3 = &s;

舉例

    int i = -1;
    const int ic = i;                  //ok
    const int *pic = ?             //ok
    int *const cpi = ?            //error
    const int *const cpic = ?    //ok

END.


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,541評論 1 51
  • 307、setValue:forKey和setObject:forKey的區別是什么? 答:1, setObjec...
    AlanGe閱讀 1,592評論 0 1
  • 話題:正確本身的價值 1+1=2,E=mc2兩個式子同樣都是正確的。可我們思考“正確”本身的價值時,第一個式子正確...
    卷卷皮閱讀 274評論 1 3
  • 光澤照亮的地方背后暗黑色系蔓延的地方就會越多,我們唯有堅定自己的內心往前沖,往前走,不回頭。 我們都知道努力的意義...
    阿俊xi閱讀 389評論 0 1
  • 與其浪費時間在想有的沒的,不如想方設法的去賺錢,去賺大錢!遠離一切讓人墮落、腐爛的東西!! 在賺錢的過程中,想要的...
    小Yan林閱讀 133評論 0 0