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