日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

C语言指针详解(超级详细)

發(fā)布時(shí)間:2023/12/10 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C语言指针详解(超级详细) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

C語(yǔ)言指針精解

前言

這不是我第一次寫(xiě)關(guān)于C指針的文章了,只是因?yàn)橹羔槍?duì)于C來(lái)說(shuō)太重要,而且隨著自己編程經(jīng)歷越多,對(duì)指針的理解越多,因此有了本文。然而,想要全面理解指針,除了要對(duì)C語(yǔ)言有熟練的掌握外,還要有計(jì)算機(jī)硬件以及操作系統(tǒng)等方方面面的基本知識(shí)。所以我想通過(guò)一篇文章來(lái)盡可能的講解指針,以對(duì)得起這個(gè)文章的標(biāo)題吧。

為什么需要指針?

指針解決了一些編程中基本的問(wèn)題。

第一,指針的使用使得不同區(qū)域的代碼可以輕易的共享內(nèi)存數(shù)據(jù)。當(dāng)然你也可以通過(guò)數(shù)據(jù)的復(fù)制達(dá)到相同的效果,但是這樣往往效率不太好,因?yàn)橹T如結(jié)構(gòu)體等大型數(shù)據(jù),占用的字節(jié)數(shù)多,復(fù)制很消耗性能。但使用指針就可以很好的避免這個(gè)問(wèn)題,因?yàn)槿魏晤愋偷闹羔樥加玫淖止?jié)數(shù)都是一樣的(根據(jù)平臺(tái)不同,有4字節(jié)或者8字節(jié)或者其他可能)。

第二,指針使得一些復(fù)雜的鏈接性的數(shù)據(jù)結(jié)構(gòu)的構(gòu)建成為可能,比如鏈表,鏈?zhǔn)蕉鏄?shù)等等。

第三,有些操作必須使用指針。如操作申請(qǐng)的堆內(nèi)存。還有:C語(yǔ)言中的一切函數(shù)調(diào)用中,值傳遞都是“按值傳遞”的,如果我們要在函數(shù)中修改被傳遞過(guò)來(lái)的對(duì)象,就必須通過(guò)這個(gè)對(duì)象的指針來(lái)完成。

指針是什么?

我們指知道:C語(yǔ)言中的數(shù)組是指 一類 類型,數(shù)組具體區(qū)分為 int 類型數(shù)組,double類型數(shù)組,char數(shù)組 等等。同樣指針 這個(gè)概念也泛指 一類 數(shù)據(jù)類型,int指針類型,double指針類型,char指針類型等等。

通常,我們用int類型保存一些整型的數(shù)據(jù),如 int num = 97 , 我們也會(huì)用char來(lái)存儲(chǔ)字符: char ch = ‘a(chǎn)’。

我們也必須知道:任何程序數(shù)據(jù)載入內(nèi)存后,在內(nèi)存都有他們的地址,這就是指針。而為了保存一個(gè)數(shù)據(jù)在內(nèi)存中的地址,我們就需要指針變量。

因此:指針是程序數(shù)據(jù)在內(nèi)存中的地址,而指針變量是用來(lái)保存這些地址的變量。

為什么程序中的數(shù)據(jù)會(huì)有自己的地址?

弄清這個(gè)問(wèn)題我們需要從操作系統(tǒng)的角度去認(rèn)知內(nèi)存。

電腦維修師傅眼中的內(nèi)存是這樣的:內(nèi)存在物理上是由一組DRAM芯片組成的。

?

而作為一個(gè)程序員,我們不需要了解內(nèi)存的物理結(jié)構(gòu),操作系統(tǒng)給程序員提供的一種對(duì)內(nèi)存使用的抽象。 在程序員眼中的內(nèi)存應(yīng)該是下面這樣的。

也就是說(shuō),內(nèi)存是一個(gè)很大的,線性的字節(jié)數(shù)組(平坦尋址)。每一個(gè)字節(jié)都是固定的大小,由8個(gè)二進(jìn)制位組成。最關(guān)鍵的是,每一個(gè)字節(jié)都有一個(gè)唯一的編號(hào),編號(hào)從0開(kāi)始,一直到最后一個(gè)字節(jié)。如上圖中,這是一個(gè)256M的內(nèi)存,他一共有256x1024x1024 = 268435456個(gè)字節(jié),那么它的地址范圍就是 0 ~268435455 。

操作系統(tǒng)將RAM等硬件和軟件結(jié)合起來(lái),提供的抽象機(jī)制使得程序使用的是虛擬存儲(chǔ)器,而不是直接使用真實(shí)存在的物理內(nèi)存。所有的虛擬地址形成的集合就是虛擬地址空間。

由于內(nèi)存中的每一個(gè)字節(jié)都有一個(gè)唯一的編號(hào),因此,在程序中使用的變量,常量,甚至數(shù)函數(shù)等數(shù)據(jù),當(dāng)他們被載入到內(nèi)存中后,都有自己唯一的一個(gè)編號(hào),這個(gè)編號(hào)就是這個(gè)數(shù)據(jù)的地址。指針就是這樣形成的。

下面用代碼說(shuō)明

#include <stdio.h>

?

int main(void)

{

??? char ch = 'a';

??? int? num = 97;

??? printf("ch 的地址:%p\n",&ch);?? //ch 的地址:0028FF47

??? printf("num的地址:%p\n",&num);? //num的地址:0028FF40

??? return 0;

}

指針的值實(shí)質(zhì)是內(nèi)存單元(即字節(jié))的編號(hào),所以指針 單獨(dú)從數(shù)值上看,也是整數(shù),他們一般用16進(jìn)制表示。指針的值(虛擬地址值)使用一個(gè)機(jī)器字的大小來(lái)存儲(chǔ),也就是說(shuō),對(duì)于一個(gè)機(jī)器字為w位的電腦而言,它的虛擬地址空間是0~2∧w - 1 ,程序最多能能訪問(wèn)2∧w字節(jié)。這就是為什么xp這種32位系統(tǒng)最大支持4GB內(nèi)存的原因了。

我們可以大致畫(huà)出變量ch和num在內(nèi)存模型中的存儲(chǔ)。(假設(shè) char占1個(gè)字節(jié),int占4字節(jié))

變量和內(nèi)存

為了簡(jiǎn)單起見(jiàn),這里就用上面例子中的 int num = 97 這個(gè)局部變量來(lái)分析變量在內(nèi)存中的存儲(chǔ)模型。

已知:num的類型是int,占用了4個(gè)字節(jié)的內(nèi)存空間,其值是97,地址是0028FF40。我們從以下幾個(gè)方面去分析。

1、內(nèi)存的數(shù)據(jù)

? 內(nèi)存的數(shù)據(jù)就是變量的值對(duì)應(yīng)的二進(jìn)制,一切都是二進(jìn)制。97的二進(jìn)制是 : 00000000 00000000 00000000 0110000 , 但使用的小端模式存儲(chǔ)時(shí),低位數(shù)據(jù)存放在低地址,所以圖中畫(huà)的時(shí)候是倒過(guò)來(lái)的。

2、內(nèi)存數(shù)據(jù)的類型

? 內(nèi)存的數(shù)據(jù)類型決定了這個(gè)數(shù)據(jù)占用的字節(jié)數(shù),以及計(jì)算機(jī)將如何解釋這些字節(jié)。num的類型是int,因此將被解釋為 一個(gè)整數(shù)。

3、內(nèi)存的名稱

? 內(nèi)存的名稱就是變量名。實(shí)質(zhì)上,內(nèi)存數(shù)據(jù)都是以地址來(lái)標(biāo)識(shí)的,根本沒(méi)有內(nèi)存的名稱這個(gè)說(shuō)法,這只是高級(jí)語(yǔ)言提供的抽象機(jī)制 ,方便我們操作內(nèi)存數(shù)據(jù)。而且在C語(yǔ)言中,并不是所有的內(nèi)存數(shù)據(jù)都有名稱,例如使用malloc申請(qǐng)的堆內(nèi)存就沒(méi)有。

4、內(nèi)存的地址

? 如果一個(gè)類型占用的字節(jié)數(shù)大于1,則其變量的地址就是地址值最小的那個(gè)字節(jié)的地址。因此num的地址是 0028FF40。 內(nèi)存的地址用于標(biāo)識(shí)這個(gè)內(nèi)存塊。

5、內(nèi)存數(shù)據(jù)的生命周期

? num是main函數(shù)中的局部變量,因此當(dāng)main函數(shù)被啟動(dòng)時(shí),它被分配于棧內(nèi)存上,當(dāng)main執(zhí)行結(jié)束時(shí),消亡。

如果一個(gè)數(shù)據(jù)一直占用著他的內(nèi)存,那么我們就說(shuō)他是“活著的”,如果他占用的內(nèi)存被回收了,則這個(gè)數(shù)據(jù)就“消亡了”。C語(yǔ)言中的程序數(shù)據(jù)會(huì)按照他們定義的位置,數(shù)據(jù)的種類,修飾的關(guān)鍵字等因素,決定他們的生命周期特性。實(shí)質(zhì)上我們程序使用的內(nèi)存會(huì)被邏輯上劃分為: 棧區(qū),堆區(qū),靜態(tài)數(shù)據(jù)區(qū),方法區(qū)。不同的區(qū)域的數(shù)據(jù)有不同的生命周期。

? 無(wú)論以后計(jì)算機(jī)硬件如何發(fā)展,內(nèi)存容量都是有限的,因此清楚理解程序中每一個(gè)程序數(shù)據(jù)的生命周期是非常重要的。

? 我會(huì)在以后的文章中再對(duì)C語(yǔ)言的內(nèi)存管理做出介紹,敬請(qǐng)期待。

?

指針變量 指向關(guān)系

用來(lái)保存 指針 的變量,就是指針變量。如果指針變量p1保存了變量 num的地址,則就說(shuō):p1指向了變量num,也可以說(shuō)p1指向了num所在的內(nèi)存塊 ,這種指向關(guān)系,在圖中一般用 箭頭表示。

上圖中,指針變量p1指向了num所在的內(nèi)存塊 ,即從地址0028FF40開(kāi)始的4個(gè)byte 的內(nèi)存塊。

定義指針變量

C語(yǔ)言中,定義變量時(shí),在變量名 前 寫(xiě)一個(gè) * 星號(hào),這個(gè)變量就變成了對(duì)應(yīng)變量類型的指針變量。必要時(shí)要加( ) 來(lái)避免優(yōu)先級(jí)的問(wèn)題。

引申:C語(yǔ)言中,定義變量時(shí),在定義的最前面寫(xiě)上typedef ,那么這個(gè)變量名就成了一種類型,即這個(gè)類型的同義詞。

int a ; //int類型變量 a

int *a ; //int* 變量a

int arr[3]; //arr是包含3個(gè)int元素的數(shù)組

int (* arr )[3]; //arr是一個(gè)指向包含3個(gè)int元素的數(shù)組的指針變量

?

?

//-----------------各種類型的指針------------------------------

?

int* p_int; //指向int類型變量的指針

?

double* p_double; //指向idouble類型變量的指針

?

struct Student *p_struct; //結(jié)構(gòu)體類型的指針

?

int(*p_func)(int,int); //指向返回類型為int,有2個(gè)int形參的函數(shù)的指針

?

int(*p_arr)[3]; //指向含有3個(gè)int元素的數(shù)組的指針

?

int** p_pointer; //指向 一個(gè)整形變量指針的指針

取地址

既然有了指針變量,那就得讓他保存其它變量的地址,使用& 運(yùn)算符取得一個(gè)變量的地址。

int add(int a , int b)

{

??? return a + b;

}

?

int main(void)

{

??? int num = 97;

??? float score = 10.00F;

??? int arr[3] = {1,2,3};

?

??? //-----------------------

?

??? int* p_num = &num;

??? float* p_score = &score;

??? int (*p_arr)[3] = &arr;??????????

??? int (*fp_add)(int ,int )? = add;? //p_add是指向函數(shù)add的函數(shù)指針

??? return 0;

}

特殊的情況,他們并不一定需要使用&取地址:

數(shù)組名的值就是這個(gè)數(shù)組的第一個(gè)元素的地址。

函數(shù)名的值就是這個(gè)函數(shù)的地址。

字符串字面值常量作為右值時(shí),就是這個(gè)字符串對(duì)應(yīng)的字符數(shù)組的名稱,也就是這個(gè)字符串在內(nèi)存中的地址。

int add(int a , int b){

??? return a + b;

}

int main(void)

{

??? int arr[3] = {1,2,3};

??? //-----------------------

??? int* p_first = arr;

??? int (*fp_add)(int ,int )? =? add;

??? const char* msg = "Hello world";

??? return 0;

}

解地址

我們需要一個(gè)數(shù)據(jù)的指針變量干什么?當(dāng)然使用通過(guò)它來(lái)操作(讀/寫(xiě))它指向的數(shù)據(jù)啦。對(duì)一個(gè)指針解地址,就可以取到這個(gè)內(nèi)存數(shù)據(jù),解地址 的寫(xiě)法,就是在指針的前面加一個(gè)*號(hào)。

解指針的實(shí)質(zhì)是:從指針指向的內(nèi)存塊中取出這個(gè)內(nèi)存數(shù)據(jù)。

int main(void)

{

??? int age = 19;

??? int*p_age = &age;

??? *p_age? = 20;? //通過(guò)指針修改指向的內(nèi)存數(shù)據(jù)

?

??? printf("age = %d\n",*p_age);?? //通過(guò)指針讀取指向的內(nèi)存數(shù)據(jù)

??? printf("age = %d\n",age);

?

??? return 0;

}

指針之間的賦值

指針賦值和int變量賦值一樣,就是將地址的值拷貝給另外一個(gè)。指針之間的賦值是一種淺拷貝,是在多個(gè)編程單元之間共享內(nèi)存數(shù)據(jù)的高效的方法。

int* p1? = & num;

int* p3 = p1;

?

//通過(guò)指針 p1 、 p3 都可以對(duì)內(nèi)存數(shù)據(jù) num 進(jìn)行讀寫(xiě),如果2個(gè)函數(shù)分別使用了p1 和p3,那么這2個(gè)函數(shù)就共享了數(shù)據(jù)num。

空指針

指向空,或者說(shuō)不指向任何東西。在C語(yǔ)言中,我們讓指針變量賦值為NULL表示一個(gè)空指針,而C語(yǔ)言中,NULL實(shí)質(zhì)是 ((void*)0) , 在C++中,NULL實(shí)質(zhì)是0。

換種說(shuō)法:任何程序數(shù)據(jù)都不會(huì)存儲(chǔ)在地址為0的內(nèi)存塊中,它是被操作系統(tǒng)預(yù)留的內(nèi)存塊。

下面代碼摘自 stdlib.h

#ifdef __cplusplus

???? #define NULL??? 0

#else???

???? #define NULL??? ((void *)0)

#endif

5

壞指針

指針變量的值是NULL,或者未知的地址值,或者是當(dāng)前應(yīng)用程序不可訪問(wèn)的地址值,這樣的指針就是壞指針,不能對(duì)他們做解指針操作,否則程序會(huì)出現(xiàn)運(yùn)行時(shí)錯(cuò)誤,導(dǎo)致程序意外終止。

任何一個(gè)指針變量在做 解地址操作前,都必須保證它指向的是有效的,可用的內(nèi)存塊,否則就會(huì)出錯(cuò)。壞指針是造成C語(yǔ)言Bug的最頻繁的原因之一。

下面的代碼就是錯(cuò)誤的示例。

void opp()

{

???? int*p = NULL;

???? *p = 10;????? //Oops! 不能對(duì)NULL解地址

}

?

void foo()

{

? ???int*p;

???? *p = 10;????? //Oops! 不能對(duì)一個(gè)未知的地址解地址

}

?

void bar()

{

???? int*p = (int*)1000;

???? *p =10;????? //Oops!?? 不能對(duì)一個(gè)可能不屬于本程序的內(nèi)存的地址的指針解地址

}

指針的2個(gè)重要屬性

指針也是一種數(shù)據(jù),指針變量也是一種變量,因此指針 這種數(shù)據(jù)也符合前面 變量和內(nèi)存 主題中的特性。 這里我只想強(qiáng)調(diào)2個(gè)屬性: 指針的類型,指針的值。

int main(void)

{

??? int num = 97;

??? int *p1? = &num;

??? char* p2 = (char*)(&num);

?

??? printf("%d\n",*p1);??? //輸出? 97

??? putchar(*p2);????????? //輸出? a

??? return 0;

}

指針的值:很好理解,如上面的num 變量 ,其地址的值就是0028FF40 ,因此 p1的值就是0028FF40。數(shù)據(jù)的地址用于在內(nèi)存中定位和標(biāo)識(shí)這個(gè)數(shù)據(jù),因?yàn)槿魏?個(gè)內(nèi)存不重疊的不同數(shù)據(jù)的地址都是不同的。

指針的類型:指針的類型決定了這個(gè)指針指向的內(nèi)存的字節(jié)數(shù)并如何解釋這些字節(jié)信息。一般指針變量的類型要和它指向的數(shù)據(jù)的類型匹配。

由于num的地址是0028FF40,因此p1 和 p2的值都是0028FF40

*p1 : 將從地址0028FF40 開(kāi)始解析,因?yàn)閜1是int類型指針,int占4字節(jié),因此向后連續(xù)取4個(gè)字節(jié),并將這4個(gè)字節(jié)的二進(jìn)制數(shù)據(jù)解析為一個(gè)整數(shù) 97。

*p2 : 將從地址0028FF40 開(kāi)始解析,因?yàn)閜2是char類型指針,char占1字節(jié),因此向后連續(xù)取1個(gè)字節(jié),并將這1個(gè)字節(jié)的二進(jìn)制數(shù)據(jù)解析為一個(gè)字符,即’a’。

同樣的地址,因?yàn)橹羔樀念愋筒煌?#xff0c;對(duì)它指向的內(nèi)存的解釋就不同,得到的就是不同的數(shù)據(jù)。

void*類型指針

由于void是空類型,因此void*類型的指針只保存了指針的值,而丟失了類型信息,我們不知道他指向的數(shù)據(jù)是什么類型的,只指定這個(gè)數(shù)據(jù)在內(nèi)存中的起始地址,如果想要完整的提取指向的數(shù)據(jù),程序員就必須對(duì)這個(gè)指針做出正確的類型轉(zhuǎn)換,然后再解指針。因?yàn)?#xff0c;編譯器不允許直接對(duì)void*類型的指針做解指針操作。

結(jié)構(gòu)體和指針

結(jié)構(gòu)體指針有特殊的語(yǔ)法: -> 符號(hào)

如果p是一個(gè)結(jié)構(gòu)體指針,則可以使用 p ->【成員】 的方法訪問(wèn)結(jié)構(gòu)體的成員

typedef struct

{

??? char name[31];

??? int age;

??? float score;

}Student;

?

int main(void)

{

??? Student stu = {"Bob" , 19, 98.0};

??? Student*ps = &stu;

?

??? ps->age = 20;

??? ps->score = 99.0;

??? printf("name:%s age:%d\n",ps->name,ps->age);

??? return 0;

}

數(shù)組和指針

1、數(shù)組名作為右值的時(shí)候,就是第一個(gè)元素的地址。

int main(void)

{

??? int arr[3] = {1,2,3};

?

??? int*p_first = arr;

??? printf("%d\n",*p_first);? //1

??? return 0;

}

2、指向數(shù)組元素的指針 支持 遞增 遞減 運(yùn)算。(實(shí)質(zhì)上所有指針都支持遞增遞減 運(yùn)算 ,但只有在數(shù)組中使用才是有意義的)

int main(void)

{

??? int arr[3] = {1,2,3};

?

??? int*p = arr;

??? for(;p!=arr+3;p++){

??????? printf("%d\n",*p);

??? }

??? return 0;

}

3、p= p+1 意思是,讓p指向原來(lái)指向的內(nèi)存塊的下一個(gè)相鄰的相同類型的內(nèi)存塊。

? 同一個(gè)數(shù)組中,元素的指針之間可以做減法運(yùn)算,此時(shí),指針之差等于下標(biāo)之差。

4、p[n] == *(p+n)

? p[n][m] ==?(?(p+n)+ m )

5、當(dāng)對(duì)數(shù)組名使用sizeof時(shí),返回的是整個(gè)數(shù)組占用的內(nèi)存字節(jié)數(shù)。當(dāng)把數(shù)組名賦值給一個(gè)指針后,再對(duì)指針使用sizeof運(yùn)算符,返回的是指針的大小。

這就是為什么我么將一個(gè)數(shù)組傳遞給一個(gè)函數(shù)時(shí),需要另外用一個(gè)參數(shù)傳遞數(shù)組元素個(gè)數(shù)的原因了。

int main(void)

{

??? int arr[3] = {1,2,3};

?

??? int*p = arr;

??? printf("sizeof(arr)=%d\n",sizeof(arr));? //sizeof(arr)=12

? ??printf("sizeof(p)=%d\n",sizeof(p));?? //sizeof(p)=4

?

??? return 0;

}

函數(shù)和指針

函數(shù)的參數(shù)和指針

C語(yǔ)言中,實(shí)參傳遞給形參,是按值傳遞的,也就是說(shuō),函數(shù)中的形參是實(shí)參的拷貝份,形參和實(shí)參只是在值上面一樣,而不是同一個(gè)內(nèi)存數(shù)據(jù)對(duì)象。這就意味著:這種數(shù)據(jù)傳遞是單向的,即從調(diào)用者傳遞給被調(diào)函數(shù),而被調(diào)函數(shù)無(wú)法修改傳遞的參數(shù)達(dá)到回傳的效果。

void change(int a)

{

??? a++; ?????//在函數(shù)中改變的只是這個(gè)函數(shù)的局部變量a,而隨著函數(shù)執(zhí)行結(jié)束,a被銷毀。age還是原來(lái)的age,紋絲不動(dòng)。

}

int main(void)

{

??? int age = 19;

??? change(age);

??? printf("age = %d\n",age);?? // age = 19

??? return 0;

}

有時(shí)候我們可以使用函數(shù)的返回值來(lái)回傳數(shù)據(jù),在簡(jiǎn)單的情況下是可以的,但是如果返回值有其它用途(例如返回函數(shù)的執(zhí)行狀態(tài)量),或者要回傳的數(shù)據(jù)不止一個(gè),返回值就解決不了了。

傳遞變量的指針可以輕松解決上述問(wèn)題。

void change(int* pa)

{

??? (*pa)++;?? //因?yàn)閭鬟f的是age的地址,因此pa指向內(nèi)存數(shù)據(jù)age。當(dāng)在函數(shù)中對(duì)指針pa解地址時(shí),

?????????????? //會(huì)直接去內(nèi)存中找到age這個(gè)數(shù)據(jù),然后把它增1。

}

int main(void)

{

??? int age = 19;

??? change(&age);

??? printf("age = %d\n",age);?? // age = 20

??? return 0;

}

再來(lái)一個(gè)老生常談的,用函數(shù)交換2個(gè)變量的值的例子:

#include<stdio.h>

void swap_bad(int a,int b);

void swap_ok(int*pa,int*pb);

?

int main()

{

??? int a = 5;

??? int b = 3;

??? swap_bad(a,b);?????? //Can`t swap;

??? swap_ok(&a,&b);????? //OK

??? return 0;

}

?

//錯(cuò)誤的寫(xiě)法

void swap_bad(int a,int b)

{

??? int t;

??? t=a;

??? a=b;

??? b=t;

}

?

//正確的寫(xiě)法:通過(guò)指針

void swap_ok(int*pa,int*pb)

{

??? int t;

??? t=*pa;

??? *pa=*pb;

??? *pb=t;

}

30

有的時(shí)候,我們通過(guò)指針傳遞數(shù)據(jù)給函數(shù)不是為了在函數(shù)中改變他指向的對(duì)象,相反,我們防止這個(gè)目標(biāo)數(shù)據(jù)被改變。傳遞指針只是為了避免拷貝大型數(shù)據(jù)。

考慮一個(gè)結(jié)構(gòu)體類型Student。我們通過(guò)show函數(shù)輸出Student變量的數(shù)據(jù)。

typedef struct

{

??? char name[31];

??? int age;

??? float score;

}Student;

?

?

//打印Student變量信息

void show(const Student * ps)

{

??? printf("name:%s , age:%d , score:%.2f\n",ps->name,ps->age,ps->score);??

}

我們只是在show函數(shù)中取讀Student變量的信息,而不會(huì)去修改它,為了防止意外修改,我們使用了常量指針去約束。另外我們?yōu)槭裁匆褂弥羔樁皇侵苯觽鬟fStudent變量呢?

從定義的結(jié)構(gòu)看出,Student變量的大小至少是39個(gè)字節(jié),那么通過(guò)函數(shù)直接傳遞變量,實(shí)參賦值數(shù)據(jù)給形參需要拷貝至少39個(gè)字節(jié)的數(shù)據(jù),極不高效。而傳遞變量的指針卻快很多,因?yàn)樵谕粋€(gè)平臺(tái)下,無(wú)論什么類型的指針大小都是固定的:X86指針4字節(jié),X64指針8字節(jié),遠(yuǎn)遠(yuǎn)比一個(gè)Student結(jié)構(gòu)體變量小。

函數(shù)的指針

每一個(gè)函數(shù)本身也是一種程序數(shù)據(jù),一個(gè)函數(shù)包含了多條執(zhí)行語(yǔ)句,它被編譯后,實(shí)質(zhì)上是多條機(jī)器指令的合集。在程序載入到內(nèi)存后,函數(shù)的機(jī)器指令存放在一個(gè)特定的邏輯區(qū)域:代碼區(qū)。既然是存放在內(nèi)存中,那么函數(shù)也是有自己的指針的。

C語(yǔ)言中,函數(shù)名作為右值時(shí),就是這個(gè)函數(shù)的指針。

void echo(const char *msg)

{

??? printf("%s",msg);

}

int main(void)

{

??? void(*p)(const char*) = echo;?? //函數(shù)指針變量指向echo這個(gè)函數(shù)

?

??? p("Hello ");????? //通過(guò)函數(shù)的指針p調(diào)用函數(shù),等價(jià)于echo("Hello ")

??? echo("World\n");

??? return 0;

}

const 指針

const到底修飾誰(shuí)?誰(shuí)才是不變的?

下面是我總結(jié)的經(jīng)驗(yàn),分享一下。

如果const 后面是一個(gè)類型,則跳過(guò)最近的原子類型,修飾后面的數(shù)據(jù)。(原子類型是不可再分割的類型,如int, short , char,以及typedef包裝后的類型)

如果const后面就是一個(gè)數(shù)據(jù),則直接修飾這個(gè)數(shù)據(jù)。

int main()

{

??? int a = 1;

?

??? int const *p1 = &a;??????? //const后面是*p1,實(shí)質(zhì)是數(shù)據(jù)a,則修飾*p1,通過(guò)p1不能修改a的值

??? const int*p2 =? &a;??????? //const后面是int類型,則跳過(guò)int ,修飾*p2, 效果同上

?

??? int* const p3 = NULL;????? //const后面是數(shù)據(jù)p3。也就是指針p3本身是const .

?

??? const int* const p4 = &a;? // 通過(guò)p4不能改變a 的值,同時(shí)p4本身也是 const

??? int const* const p5 = &a;? //效果同上

?

??? return 0;

?

}

typedef int* pint_t;? //將 int* 類型 包裝為 pint_t,則pint_t 現(xiàn)在是一個(gè)完整的原子類型

?

int main()

{

?

??? int a? = 1;

??? const pint_t p1 = &a;? //同樣,const跳過(guò)類型pint_t,修飾p1,指針p1本身是const

??? pint_t const p2 = &a;? //const 直接修飾p,同上

?

??? return 0;

?

}

深拷貝和淺拷貝

如果2個(gè)程序單元(例如2個(gè)函數(shù))是通過(guò)拷貝 他們所共享的數(shù)據(jù)的 指針來(lái)工作的,這就是淺拷貝,因?yàn)檎嬲L問(wèn)的數(shù)據(jù)并沒(méi)有被拷貝。如果被訪問(wèn)的數(shù)據(jù)被拷貝了,在每個(gè)單元中都有自己的一份,對(duì)目標(biāo)數(shù)據(jù)的操作相互 不受影響,則叫做深拷貝。

附加知識(shí)

指針和引用這個(gè)2個(gè)名詞的區(qū)別。他們本質(zhì)上來(lái)說(shuō)是同樣的東西。指針常用在C語(yǔ)言中,而引用,則用于諸如Java,C#等 在語(yǔ)言層面封裝了對(duì)指針的直接操作的編程語(yǔ)言中。

大端模式和小端模式

Little-Endian就是低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端。個(gè)人PC常用,Intel

Intel X86處理器是小端模式。

Big-Endian就是高位字節(jié)排放在內(nèi)存的低地址端,低位字節(jié)排放在內(nèi)存的高地址端。

采用大端方式 進(jìn)行數(shù)據(jù)存放符合人類的正常思維,而采用小端方式進(jìn)行數(shù)據(jù)存放利于計(jì)算機(jī)處理。有些機(jī)器同時(shí)支持大端和小端模式,通過(guò)配置來(lái)設(shè)定實(shí)際的端模式。

假如 short類型占用2個(gè)字節(jié),且存儲(chǔ)的地址為0x30。

short a = 1;

如下圖:

?//測(cè)試機(jī)器使用的是否為小端模式。是,則返回true,否則返回false

//這個(gè)方法判別的依據(jù)就是:C語(yǔ)言中一個(gè)對(duì)象的地址就是這個(gè)對(duì)象占用的字節(jié)中,地址值最小的那個(gè)字節(jié)的地址。

?

bool isSmallIndain()

{

????? unsigned int val = 'A';

????? unsigned char* p = (unsigned char*)&val;? //C/C++:對(duì)于多字節(jié)數(shù)據(jù),取地址是取的數(shù)據(jù)對(duì)象的第一個(gè)字節(jié)的地址,也就是數(shù)據(jù)的低地址

?

????? return *p == 'A';

}

本文轉(zhuǎn)載于:www.cnblogs.com/lulipro

總結(jié)

以上是生活随笔為你收集整理的C语言指针详解(超级详细)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。