本文是本人在學(xué)習(xí) C語(yǔ)言的過(guò)程中所積累的對(duì) C語(yǔ)言指針的感悟,可能會(huì)有些地方描述不準(zhǔn)確,還請(qǐng)指出。本文遵循一般文章結(jié)構(gòu),從簡(jiǎn)單到難,從基本概念到抽象總結(jié)。適合任何任何學(xué)習(xí) C語(yǔ)言的人群。
我們提供的服務(wù)有:成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、來(lái)賓ssl等。為超過(guò)千家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢(xún)和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的來(lái)賓網(wǎng)站制作公司一、指針的概念指針的值就是某一個(gè)變量的內(nèi)存地址,指針變量就是用來(lái)存放某個(gè)變量的內(nèi)存地址的變量,和廣義的變量沒(méi)有什么區(qū)別。
在同一CPU構(gòu)架下,不同類(lèi)型的指針變量所占用的存儲(chǔ)單元長(zhǎng)度是相同的。這是因?yàn)椴僮飨到y(tǒng)的位數(shù)與其所能支持的大內(nèi)存有直接的關(guān)系。由于計(jì)算機(jī)是按照字節(jié)尋址的,如在 32 位操作系統(tǒng)下,32位比特位一共能描述 2^32 個(gè)狀態(tài),一個(gè)狀態(tài)標(biāo)記大小為 1B(一般定義8位(bit,比特)為一字節(jié)),所以一共有 2^32*1B = 4GB。因此 32 位系統(tǒng)所能支持的大內(nèi)存為 4GB。而對(duì)于 64 位操作系統(tǒng)(目前主流操作系統(tǒng)),所能支持的大內(nèi)存為 2^64*1B = 17179869184GB,這是一個(gè)很大的數(shù),基本上可以支持任何現(xiàn)實(shí)中任意存在的內(nèi)存。
而指針最小就是以字節(jié)為單位進(jìn)行指引,因此對(duì)于 32 位操作系統(tǒng),它也要是 32 位的,即 4 字節(jié)大??;而對(duì)于 64 位操作系統(tǒng),它就得是 64 位 即 8 字節(jié)大小。
我們可以寫(xiě)一個(gè)程序驗(yàn)證下:
#includeint main() {int a = 4;
int* p = &a;
printf("%zu\n", sizeof(p));
// 指針變量占據(jù) 8 個(gè)字節(jié)
printf("%p\n", p);
// 變量 a 存放在以?xún)?nèi)存地址為0x16f85f71c開(kāi)頭的內(nèi)存單元中
}
輸出結(jié)果為:
8
0x16f85f71c
在 C/C++語(yǔ)言中,指針一般被認(rèn)為是指針變量,指針變量的內(nèi)容存儲(chǔ)的是其指向的對(duì)象的首地址,指向的對(duì)象可以是變量(指針變量也是變量),數(shù)組,函數(shù)等占據(jù)存儲(chǔ)空間的實(shí)體。
二、指針詳解以下的例子由易到難,每次增長(zhǎng)一顆星。
(一)★#includeint main() {int a = 0, b = 1;
int *p1 = &a, *p2 = &b;
printf("%p\n%p\n", p1, p2);
return 0;
}
輸出為:
0x16bacb718
0x16bacb714
這是一個(gè)很簡(jiǎn)單的例子,程序中創(chuàng)建了兩個(gè)局部變量,然后利用指針輸出它們的地址。其中&
是取地址符,取出來(lái)以后賦給兩個(gè)指針變量p1、p2,并將其打印出來(lái)。
細(xì)心的話(huà)可以發(fā)現(xiàn)這兩個(gè)地址由大到小,相差為 4。這說(shuō)明棧中的這兩個(gè)變量恰好相鄰,且一個(gè)int
類(lèi)型的變量占用 4B大小空間。而且還說(shuō)明??臻g的增長(zhǎng)方向是由大到小的,當(dāng)然我們的主題是指針,這里不再贅述。
#includeint main() {int a = 0;
int* p = &a;
printf("通過(guò)指針修改前 a 的值為:%d\n", a);
*p = 10;
printf("通過(guò)指針修改后 a 的值為:%d\n", a);
return 0;
}
輸出為:
通過(guò)指針修改前 a 的值為:0
通過(guò)指針修改后 a 的值為:10
這里就比上面稍微多了一點(diǎn)東西,就是可以通過(guò)指針去修改被該指針?biāo)傅淖兞康闹?。換句話(huà)說(shuō),就是間接修改變量的值。其中*p
的含義為解引用,指代的就是變量a
。
#includeint main() {int a = 0, b = 1;
int *p1 = &a, *p2 = &b;
printf("a在%p\n", p1);
printf("b在%p\n", p2);
int **pp1 = &p1, **pp2 = &p2;
printf("a的指針p1在%p\n", pp1);
printf("a的指針p1在%p\n", pp2);
return 0;
}
輸出為:
a在0x16da73718
b在0x16da73714
a的指針p1在0x16da73708
a的指針p1在0x16da73700
可以看到,指針變量也有一個(gè)內(nèi)存地址,它指向的是一個(gè)指針變量的內(nèi)存地址。那么,我們同樣可以通過(guò)二級(jí)指針對(duì)一級(jí)指針作出修改:
#includeint main() {int a = 0, b = 1;
int *p1 = &a, *p2 = &b;
printf("a在%p\n", p1);
printf("b在%p\n", p2);
int **pp1 = &p1, **pp2 = &p2;
printf("a的指針p1在%p\n", pp1);
printf("b的指針p2在%p\n", pp2);
// 交換兩個(gè)二級(jí)指針
int** temp = pp1;
pp1 = pp2;
pp2 = temp;
printf("a的指針p1在%p\n", pp1);
printf("b的指針p2在%p\n", pp2);
return 0;
}
輸出為:
a在0x16fa0b718
b在0x16fa0b714
a的指針p1在0x16fa0b708
b的指針p2在0x16fa0b700
a的指針p1在0x16fa0b700
b的指針p2在0x16fa0b708
這里需要提一點(diǎn),就是當(dāng)一個(gè)變量(前提是一個(gè)變量)為表達(dá)式左值時(shí),它代表一個(gè)變量,當(dāng)其為右值時(shí),它代表變量的值。關(guān)于左值右值的概念,不再贅述。
交換前的指針關(guān)系:
交換后的指針關(guān)系:
我們還可以嘗試通過(guò)pp2
來(lái)直接修改變量a
的值:
#includeint main() {int a = 0, b = 1;
int *p1 = &a, *p2 = &b;
printf("a在%p\n", p1);
printf("b在%p\n", p2);
int **pp1 = &p1, **pp2 = &p2;
printf("a的指針p1在%p\n", pp1);
printf("a的指針p1在%p\n", pp2);
// 交換兩個(gè)二級(jí)指針
int** temp = pp1;
pp1 = pp2;
pp2 = temp;
printf("a的指針p1在%p\n", pp1);
printf("a的指針p1在%p\n", pp2);
**pp2 = 10;
printf("修改后a的值為:%d\n", a);
return 0;
}
輸出為:
a在0x16b1ff718
b在0x16b1ff714
a的指針p1在0x16b1ff708
a的指針p1在0x16b1ff700
a的指針p1在0x16b1ff700
a的指針p1在0x16b1ff708
修改后a的值為:10
其中二級(jí)指針pp2
解引用了兩次,才與a
等價(jià)。這里的例子大量使用了二級(jí)指針,即int**
類(lèi)型的變量。具體來(lái)說(shuō),指針類(lèi)型有多種,比如int* p
,int** p
,int*** p
等等。
前面幾節(jié)都是很簡(jiǎn)單的概念,接下來(lái)幾節(jié)將會(huì)引入數(shù)組、字符串等來(lái)對(duì)指針的使用進(jìn)行更深的闡述。
(1)關(guān)于數(shù)組名#includeint main() {int a[3] = {1, 2, 3};
printf("%p\n", a);
printf("%zu\n", sizeof(a));
}
輸出為:
0x16fc9b708
12
可以看到,數(shù)組名 a 是一個(gè)指針類(lèi)型的值,它實(shí)際上指向的是大小為 12B 的連續(xù)空間的首地址。數(shù)組名其實(shí)是一個(gè)常量,因此它不能當(dāng)做左值。當(dāng)我們修改它的值時(shí),它會(huì)提示array type 'int [3]' is not assignable
,意思是這個(gè)變量不可被修改。另外,a 的大小為 12B 這表明和普通的指針并不一樣,或者a并不是簡(jiǎn)單的指針。因此,很多參考書(shū)上說(shuō)“數(shù)組名本質(zhì)上就是一個(gè)指針”的說(shuō)法是完全錯(cuò)誤的。
我們剛提到,數(shù)組名是一個(gè)常量,因此我們可以利用一個(gè)指針變量來(lái)訪(fǎng)問(wèn)內(nèi)存單元:
#includeint main() {int a[3] = {1, 2, 3};
int* p = a;
for (int i = 0; i< 3; i++) {printf("%d ", *(p + i));
}
return 0;
}
輸出為:
1 2 3
其中p+i
的含義是,指針指向了p
所指的內(nèi)存再向右偏移i
個(gè)類(lèi)型為int
的內(nèi)存單元。之所以是int
,是因?yàn)槲覀兟暶鞯氖?code>int類(lèi)型的指針。比如:
#includeint main() {int a[3] = {1, 2, 3};
int* p = a;
printf("%p\n", p);
printf("%p\n", p + 1);
printf("%p\n", p + 2);
long *q = a;
printf("%p\n", q);
printf("%p\n", q + 1);
return 0;
}
輸出為:
0x16f1c3708
0x16f1c370c
0x16f1c3710
0x16f1c3708
0x16f1c3710
可以看到,因?yàn)?code>long類(lèi)型占 8 個(gè)字節(jié)(long
的定義為不少于int
類(lèi)型的大小,有些計(jì)算機(jī)系統(tǒng)long
和int
等價(jià),但大部分long
類(lèi)型都占 8 字節(jié)),int
占 4 個(gè)字節(jié),所以long
類(lèi)型的指針偏移的長(zhǎng)度為int
類(lèi)型的兩倍。同樣,我們可以搞點(diǎn)稍微復(fù)雜的事情:
#includeint main() {int a[3] = {1, 2, 3};
printf("數(shù)組的首地址為:%p\n", a);
int* p = (int*)(&a + 1);
int* q = a + 3;
printf("此時(shí) p 指向:%p\n", p);
printf("此時(shí) q 指向:%p\n", q);
return 0;
}
輸出為:
數(shù)組的首地址為:0x16b25f708
此時(shí) p 指向:0x16b25f714
此時(shí) q 指向:0x16b25f714
這說(shuō)明,&a + 1
的偏移量為3個(gè) int 類(lèi)型大小。這里就需要格外注意,指針偏移一個(gè)單位是參考哪一種類(lèi)型的變量指針的。這里參考的是int a[3]
,也就是說(shuō),a的數(shù)據(jù)類(lèi)型為int[3]
,偏移一次當(dāng)然偏移 12B了,因?yàn)?code>int[3]的大小就是 12B!
我們就可以很簡(jiǎn)單地預(yù)見(jiàn)以下的程序輸出:
#includeint main() {int a[3] = {1, 2, 3};
int* p = (int*)(&a + 1);
printf("%d\n", *(p - 1));
return 0;
}
輸出為:
3
因?yàn)?code>p指向的是數(shù)組最后一個(gè)元素的下一個(gè)內(nèi)存單元,又因?yàn)?code>p是int
類(lèi)型的指針,因此一個(gè)偏移量大小為int
,所以p-1
之后,就指向了數(shù)組的最后一個(gè)元素。
字符串是一個(gè)重點(diǎn),也是一個(gè)難點(diǎn)。但是只要掌握指針基本概念,把握字符數(shù)組和字符串的區(qū)別和聯(lián)系,也是送分題。
首先得了解下字符串輸出的基本原理:
#includeint main() {char c[3] = {'a', 'b', 'c'};
printf("%s\n", c);
return 0;
}
輸出為:
abc*/
為什么會(huì)有這么奇怪的輸出呢?這是因?yàn)?code>printf("%s\n", c)輸出字符串時(shí),只要輸入一個(gè)指針(任何類(lèi)型的指針都可以),就會(huì)一直打印,直到遇到'\0'
為止。比如下面這個(gè)程序:
#includeint main() {int a[3] = {100, 101, 102};
printf("%s\n", a);
return 0;
}
輸出結(jié)果為:
d
這是因?yàn)閿?shù)據(jù)存放為大端方式,100
存在到第一個(gè)高地址字節(jié)空間后,后面三個(gè)存放的都是'\0'
,所以就停止打印了。我們可以印證一下:
假設(shè)我們想要打印出"abc"
,這就要保證內(nèi)存單元里面放的是97\98\99\00
。即0x61626300
。大端方式存入內(nèi)存單元就為:0x00636261
,這個(gè)數(shù)字 10 進(jìn)制大小為6513249
,因此:
#includeint main() {int a[3] = {6513249,100,200};
printf("%s\n", a);
return 0;
}
程序輸出為:
abc
果然輸出了"abc"
,這無(wú)疑是一件令人激動(dòng)的事情!當(dāng)然,這里的重點(diǎn)是字符串輸出,就不再贅述其它了。
經(jīng)過(guò)上面的例子,可以充分地說(shuō)明輸出函數(shù)的特性,即只要是一個(gè)指針,丟給printf("%s\n", a)
后,就會(huì)打印出結(jié)果。
所以字符串是使用空字符'\0'
結(jié)尾的一組數(shù)據(jù),就這么簡(jiǎn)單。我們可以很容易地構(gòu)造出一些字符串,比如我們上面通過(guò)一些手段構(gòu)造的"abc"
,以及下面用字符數(shù)組構(gòu)造的字符串(常用手段):
#includeint main() {char string[12] = {'H','e','l','l','o','w','o','r','l','d','!','\0'};
printf("%s\n",string);
char *string1 = "Helloworld!";
printf("%s\n",string1);
return 0;
}
在用字符數(shù)組構(gòu)造字符串時(shí),一定要注意在最后一個(gè)內(nèi)存單元加上’\0’,因?yàn)槲覀儾荒鼙WC字符串結(jié)束后的下一個(gè)內(nèi)存單元放的是不是’\0’,因此字符串可能不會(huì)正常終止。
還可以使用字面值常量來(lái)創(chuàng)建字符串,比如char *string1 = "Helloworld!"
。這種不需要在在最后一個(gè)內(nèi)存單元加上'\0'
,編譯系統(tǒng)會(huì)自動(dòng)加。
為了明白字符串賦值的本質(zhì),這里不會(huì)使用庫(kù)函數(shù)提供的各種函數(shù),比如strcpy()
,strcat()
等。
#includeint main() {char string[12] = {'H', 'e', 'l', 'l', 'o', 'w',
'o', 'r', 'l', 'd', '!', '\0'};
char string1[12] = {0}; // 初始化與否都可以,因?yàn)榫o接著我們就要對(duì)其賦值
for (int i = 0; i< 12; i++) {string1[i] = string[i];
}
printf("%s\n", string1);
return 0;
}
本例采用了逐個(gè)字符賦值的方法來(lái)完成對(duì)字符串的賦值。我們的主題是指針,那么可不可以像下面那樣賦值:
#includeint main() {char string[12] = {'H', 'e', 'l', 'l', 'o', 'w',
'o', 'r', 'l', 'd', '!', '\0'};
char string1[12] = {0}; // 初始化與否都可以,因?yàn)榫o接著我們就要對(duì)其賦值
string1 = string;
printf("%s\n", string1);
return 0;
}
當(dāng)然不可以,前文已經(jīng)提到,數(shù)組名只一個(gè)地址常量,既然是常量當(dāng)然就不可以被修改,自然就不能當(dāng)做左值。事實(shí)上,上文還提到了通過(guò)字面值常量來(lái)創(chuàng)造字符串,既然是常量,那么自然也就不能修改,比如:
#includeint main() {char* p = "Helloworld!";
*(p + 1) = 'E';
printf("%s\n", p);
return 0;
}
這個(gè)程序的目的是把"Helloworld"改為"HElloworld",那么目的能達(dá)到嗎?自然不能,編譯器會(huì)報(bào)錯(cuò)bus error
。我們不去管這個(gè)錯(cuò)誤的具體含義,只需要知道常量字符串是無(wú)法修改的。也就是說(shuō),我們的指針并不是指在哪里就改哪里,那計(jì)算機(jī)系統(tǒng)就亂套了!
如果我們迫切的想要修改,可以這樣做:
#include#include#includeint main() {char* p = "Helloworld!";
int len = strlen(p);
char* newString = (char*)malloc(sizeof(char) * (len + 1));
for (int i = 0; i< len; i++) {newString[i] = p[i];
}
newString[len] = '\0';
newString[1] = 'E';
printf("%s\n", newString);
return 0;
}
輸出結(jié)果為:
HElloworld!
過(guò)程也很簡(jiǎn)單,就是申請(qǐng)一塊內(nèi)存空間,先復(fù)制過(guò)來(lái),然后再修改成想要的樣子。
(五)★★★★★開(kāi)始之前,我們先來(lái)談?wù)勈裁词悄涿麛?shù)組。顧名思義,匿名就是藏起來(lái)名字的意思。比如:
#includeint main() {int* p = (int[2]){19, 20};
return 0;
}
我們定義了一個(gè)匿名的數(shù)組,并賦給一個(gè)int*
類(lèi)型的變量 p。匿名類(lèi)在 Java 中 的作用之一是起到很好的封裝性,同樣在 C語(yǔ)言中也有這樣的作用。這個(gè)數(shù)組,只能通過(guò) p 訪(fǎng)問(wèn)。你可能注意到了,我并沒(méi)有用int
類(lèi)型的指針去描述 p,而是用int*
類(lèi)型來(lái)描述 p。
C語(yǔ)言中的數(shù)據(jù)類(lèi)型有哪些?一般來(lái)說(shuō),有四大類(lèi)型,分別是基本類(lèi)型,構(gòu)造類(lèi)型,空類(lèi)型以及我們講的指針類(lèi)型。
指針類(lèi)型是花樣最多的一種類(lèi)型,它通常包括 5 類(lèi):int*
,char*
,int**
,int(*)[]
,int*[]
。
換句話(huà)說(shuō),從語(yǔ)法的角度講,把指針聲明語(yǔ)句里的指針名字去掉,剩下的部分就是這個(gè)指針的類(lèi)型。比如,給出一個(gè)例子:
#includeint main() {int a[3] = {1, 2, 3};
int* p;
char* s;
int** q;
int(*pt)[3];
int*[3];
return 0;
}
在這個(gè)例子中,指針的類(lèi)型分別為:int*
,char*
,int**
,int(*)[3]
,int*[3]
。我們經(jīng)常用到數(shù)組指針以及指針數(shù)組,因此重點(diǎn)理解它們就是關(guān)鍵。
對(duì)于指針數(shù)組,描述它的語(yǔ)法為int*p[3]
。首先,它是一個(gè)數(shù)組,所以得是個(gè)p[3]
;其次要是一個(gè)指針類(lèi)型的也就是int*
。所以就產(chǎn)生了int*p[3]
。數(shù)組 p 中每一個(gè)元素都是一個(gè)指針,指向一個(gè)一維數(shù)組。比如:
#includeint main() {int a[3] = {1,2,3};
int b[3] = {4,5,6};
int c[5] = {7,8,9,10,11};
int *p[3] = {a,b,c};
return 0;
}
為了更明顯,不如把它寫(xiě)成int*(p[3])
,但是由于[3]
的結(jié)合度非常高,因此這個(gè)括號(hào)可以去掉。
顧名思義,數(shù)組指針就是一個(gè)指向數(shù)組的指針。我們這樣定義:·int(*p)[3]·。首先得是一個(gè)指針,因此得是(int*p)
;然后得是一個(gè)數(shù)組類(lèi)型的,因此就是(int*p)[3]
。但這樣明顯歧義了,因此就成了int(*p)[3]
。它指向一個(gè)類(lèi)型為int[3]
的變量。比如:
#includeint main() {int a[3] = {1,2,3};
int (*p)[3] = &a;
}
a 的數(shù)據(jù)類(lèi)型為int[3]
,p 的類(lèi)型為int(*)[3]
。要想讓 p指向 a,我們只需要把變量 a 的地址賦值給 p,即int (*p)[3] = &a
。為了更明顯,我們可以這樣修改一下程序:
#includeint main() {int a[3] = {1,2,3};
int b[4] = {1,2,3,4};
int (*p)[3];
p = &a;
p = &b;
}
可以看到這樣的報(bào)錯(cuò):incompatible pointer types assigning to 'int (*)[3]' from 'int (*)[4]' [-Wincompatible-pointer-types]
。意思是從“int (*)[4]”[-Wincompatible-pointer-types] 分配給“int (*)[3]”的不兼容指針類(lèi)型
。這也從側(cè)面反映了int(*)[3]
是一種確定的指針類(lèi)類(lèi)型。
能看到這里,想必你已經(jīng)功力深厚,返璞歸真了,哈哈~
三、總結(jié)這里只總結(jié)難點(diǎn):
int*p[3]
,這里的重點(diǎn)是 p 的數(shù)據(jù)類(lèi)型為int*[3]
,數(shù)組每一個(gè)元素都是一個(gè)指針,指向int 類(lèi)型的數(shù)組
。int(*p)[3]
,這里的重點(diǎn)是 p 的數(shù)據(jù)類(lèi)型為int(*)[3]
,p 是一個(gè)指針,指向類(lèi)型為 int[3]的變量
。全文完,感謝你的閱讀。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧
網(wǎng)站欄目:C語(yǔ)言指針——從入門(mén)到精通-創(chuàng)新互聯(lián)
當(dāng)前鏈接:http://jinyejixie.com/article38/dieopp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站、小程序開(kāi)發(fā)、動(dòng)態(tài)網(wǎng)站、全網(wǎng)營(yíng)銷(xiāo)推廣、微信公眾號(hào)、網(wǎng)站維護(hù)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容