關(guān)于指針
C/C++中的指針通常來(lái)說(shuō)有兩個(gè)屬性:
1. 指向變量
2. 指向?qū)ο蟮牡刂泛烷L(zhǎng)度
指針其實(shí)就是存儲(chǔ)被指向變量的地址,并不保存其長(zhǎng)度;
而且存的這個(gè)地址僅是變量的首地址,并不是該變量占據(jù)內(nèi)存的所有地址空間。如:
int a=3;
int *p=&a;
目前大多數(shù)的C/C++編譯環(huán)境中,整型int數(shù)據(jù)占4個(gè)字節(jié)的空間,如上圖所示。所以指針p存儲(chǔ)的地址(即指向的地址)為1號(hào)內(nèi)存單元(首地址)。
當(dāng)需要讀取一個(gè)例如int型數(shù)據(jù)時(shí),編譯器根據(jù)指針的類(lèi)型從指針指向的地址開(kāi)始向后尋址。指針類(lèi)型不同則尋址范圍也不同,比如:
int*從指定地址向后尋找4字節(jié)作為變量的存儲(chǔ)單元;
double*從指定地址向后尋找8字節(jié)作為變量的存儲(chǔ)單元。
說(shuō)到這里可能大家就會(huì)有一個(gè)問(wèn)題:由于計(jì)算機(jī)內(nèi)部的地址是整型數(shù)字,那么為什么不干脆用一個(gè)整型變量存儲(chǔ)地址,還要發(fā)明指針變量呢?
如果我們從指針實(shí)現(xiàn)的角度講,指針就是一個(gè)整型變量,它存儲(chǔ)的是一個(gè)地址值,沒(méi)有任何附加信息。目前為止貌似沒(méi)有什么問(wèn)題,其實(shí)不然。
就拿上述的代碼:如果用一個(gè)整型變量b存儲(chǔ)a的地址,即int b=&a。當(dāng)對(duì)b加1時(shí),得到的新的地址相當(dāng)于對(duì)a的首地址加1,即&a+1,由于int占連續(xù)的四個(gè)存儲(chǔ)單元(默認(rèn)),此時(shí)b存儲(chǔ)的是第二塊存儲(chǔ)單元的地址,所以根據(jù)變量b存儲(chǔ)的地址,將無(wú)法完整的讀出變量a的值,導(dǎo)致錯(cuò)誤。而通過(guò)指針變量,可以解決這類(lèi)問(wèn)題:如果對(duì)上述代碼中的指針p加1的話(huà),實(shí)際上是p+sizeof(int),一次性增加了4個(gè)存儲(chǔ)單元。
PS.指針本身所占據(jù)的內(nèi)存區(qū) :
指針本身占了多大的內(nèi)存?你只要用函數(shù)sizeof(指針的類(lèi)型)測(cè)一下就知道了。
指針的作用是用來(lái)對(duì)內(nèi)存空間進(jìn)行尋址,在32位機(jī)上,所有指針類(lèi)型變量占用內(nèi)存字節(jié)數(shù)都為4,因?yàn)?2位機(jī)是按32位尋址的。如果在64位機(jī)上,指針占用內(nèi)存大小就是:8個(gè)字節(jié)。
無(wú)類(lèi)型指針void*
void *vp;
void*是一種特別的指針,因?yàn)樗鼪](méi)有指向的類(lèi)型,或者說(shuō)不能根據(jù)這個(gè)類(lèi)型判斷出指向?qū)ο蟮拈L(zhǎng)度。void *指針具有以下特點(diǎn):
- 任何指針(包括函數(shù)指針)都可以賦值給void指針;
type *p;
vp=p;
//不需轉(zhuǎn)換
//只獲得變量/對(duì)象地址而不獲得大小
2. void指針賦值給其他類(lèi)型的指針時(shí)都要進(jìn)行轉(zhuǎn)換;
type * p=(type *)vp;
//轉(zhuǎn)換類(lèi)型也就是獲得指向變量/對(duì)象大小
3. void指針在強(qiáng)制轉(zhuǎn)換成具體類(lèi)型前,不能解引用;
*vp
//錯(cuò)誤
//因?yàn)関oid指針只知道,指向變量/對(duì)象的起始地址
//而不知道指向變量/對(duì)象的大小(占幾個(gè)字節(jié))所以無(wú)法正確引用
4. void指針不能參與指針運(yùn)算,除非進(jìn)行轉(zhuǎn)換。
(type*)vp++;
//等價(jià)于:vp=vp+sizeof(type)
void*的作用
- 傳參:通用類(lèi)型
可以作為函數(shù)模板,鏈表等參數(shù)的通用參數(shù)。在使用時(shí),只需要強(qiáng)制類(lèi)型轉(zhuǎn)換就可以。
例如內(nèi)存操作函數(shù)memcpy和memset的函數(shù)原型分別為:
void* memcpy(void *dest, constvoid *src, size_t len);
void* memset(void *buffer, int c, size_t num);
這樣,任何類(lèi)型的指針都可以傳入memcpy和memset中,這也真實(shí)地體現(xiàn)了內(nèi)存操作函數(shù)的意義,因?yàn)樗僮鞯膶?duì)象僅僅是一片內(nèi)存,而不論這片內(nèi)存是什么類(lèi)型。
- 強(qiáng)制類(lèi)型轉(zhuǎn)換
有時(shí)候由于重載等的干擾,導(dǎo)致需要轉(zhuǎn)換成void *,來(lái)進(jìn)行取地址。
例如,(void *)obj.member,就可以取到member的地址;直接&(obj.member)取到的實(shí)際上是obj的開(kāi)始地址。
- 指向0的地址
(void *)0,指向全是0的地址,相當(dāng)于NULL。
下面舉一個(gè)使用void*指針的demo:
#include<iostream>
#include<string>
using namespace std;
typedef struct tag_st
{
string id;
float fa[2];
}ST;
int main()
{
ST* P = new ST;
P->id = "hello!";
P->fa[0] = 1.1;
P->fa[1] = 2.1;
ST* Q = new ST;
Q->id = "world!";
Q->fa[0] = 3.1;
Q->fa[1] = 4.1;
void * plink = P;
*(ST*)(plink) = *Q; //plink要先強(qiáng)制轉(zhuǎn)換一下,目的是為了讓它先知道要覆蓋的大小
//P的內(nèi)容被Q的內(nèi)容覆蓋
cout << P->id << " " << P->fa[0] << " " << P->fa[1] << endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
在寫(xiě)這個(gè)例子的時(shí)候發(fā)生一點(diǎn)小插曲:把第24行:* (ST * )(plink) = * Q; 寫(xiě)成了(ST * )(plink) = Q;結(jié)果編譯報(bào)錯(cuò),剛開(kāi)始以為是void *指針不能被兩次賦值而引發(fā)的錯(cuò)誤。經(jīng)過(guò)研究,得知是因?yàn)閺?qiáng)制類(lèi)型轉(zhuǎn)化操作(目標(biāo)類(lèi)型是引用時(shí)除外)不能作為左值,所以編譯器才會(huì)報(bào)錯(cuò)。另附上一篇介紹左值和右值的博客helloworld的博客。
注:關(guān)于void*指針的介紹,參考于wangicter的博客WangIcter的專(zhuān)欄。
|