C語(yǔ)言指針的傳遞
傳遞指針可以讓多個(gè)函數(shù)訪問指針?biāo)玫膶?duì)象,而不用把對(duì)象聲明為全局可訪問,要在某個(gè)函數(shù)中修改數(shù)據(jù),需要用指針傳遞數(shù)據(jù),當(dāng)數(shù)據(jù)是需要修改的指針的時(shí)候,就要傳遞指針的指針,傳遞參數(shù)(包括指針)的時(shí)候,傳遞的是它們的值,也就是說,傳遞給函數(shù)的是參數(shù)值的一個(gè)副本,本文將討論C語(yǔ)言中指針傳遞給函數(shù)與從函數(shù)返回指針的內(nèi)容。
用指針傳遞數(shù)據(jù)
用指針傳遞數(shù)據(jù)的一個(gè)主要原因是函數(shù)可以修改數(shù)據(jù)
下面的代碼實(shí)現(xiàn)一個(gè)常見的交換函數(shù):
#include
void swap(int* a, int* b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int m, n;
m = 5;
n = 10;
printf("m=%d, n=%d ",m, n);
swap(&m, &n);
printf("m=%d, n=%d ",m, n);
return 0;
}
如果不通過指針傳遞參數(shù),交換就不會(huì)發(fā)生,具體的原理參見任何一本C語(yǔ)言教材
傳遞指向常量的指針
傳遞指向常量的指針是C中常用的技術(shù),效率很高,因?yàn)楸苊饽撤N情況下復(fù)制大量?jī)?nèi)存,如果不希望數(shù)據(jù)被修改,就要傳遞指向常量的指針
我們不能修改通過指向常量的指針傳進(jìn)來的值:
#include
void passconstant(const int* num1, int*num2)
{
*num2 = *num1;
}
int main()
{
const int a = 100;
int b = 5;
printf("a=%d, b=%d ",a, b);
passconstant(&a, &b);
printf("a=%d, b=%d ",a, b);
return 0;
}
下面的代碼會(huì)產(chǎn)生錯(cuò)誤(第二個(gè)形參和實(shí)參的類型不匹配,試圖修改第一個(gè)參數(shù)所引用的常量):
#include
void passconstant(const int* num1, int* num2)
{
*num1 = 100;
*num2 = 200;
}
int main()
{
const int limit = 100;
passconstant(&limit, &limit);
return 0;}
C語(yǔ)言中堆和棧的區(qū)別
預(yù)備知識(shí)—程序的內(nèi)存分配
一個(gè)由C編譯的程序占用的內(nèi)存分為以下幾個(gè)部分:
1、棧區(qū)(stack)— 由編譯器自動(dòng)分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
2、堆區(qū)(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表。
3、全局區(qū)(靜態(tài)區(qū))(static),全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后由系統(tǒng)釋放。
4、文字常量區(qū) —常量字符串就是放在這里的, 程序結(jié)束后由系統(tǒng)釋放
5、程序代碼區(qū)—存放函數(shù)體的二進(jìn)制代碼。
下面就說說C語(yǔ)言程序內(nèi)存分配中的堆和棧,內(nèi)存分配一般情況下程序存放在Rom或Flash中,運(yùn)行時(shí)需要拷到內(nèi)存中執(zhí)行,內(nèi)存會(huì)分別存儲(chǔ)不同的信息,如下圖所示:
內(nèi)存中的棧區(qū)處于相對(duì)較高的地址以地址的增長(zhǎng)方向?yàn)樯系脑,棧地址是向下增長(zhǎng)的,棧中分配局部變量空間,堆區(qū)是向上增長(zhǎng)的用于分配程序員申請(qǐng)的內(nèi)存空間。另外還有靜態(tài)區(qū)是分配靜態(tài)變量,全局變量空間的;只讀區(qū)是分配常量和程序代碼空間的;以及其他一些分區(qū)。
堆棧的區(qū)別,來看一個(gè)經(jīng)典例子:
#include
#include
int a = 0; /pic/p>
char *p1; /pic/p>
int main()
{
int b; /pic/p>
char s[] = "abc"; /pic/p>
char *p2; /pic/p>
char *p3 = "123456"; /pic/p>
static int c =0; /pic/p>
p1 = (char*)malloc(10); /pic/p>
p2 = (char*)malloc(10);
return 0;
}
不知道你是否有點(diǎn)明白了,堆和棧的第一個(gè)區(qū)別就是申請(qǐng)方式不同:棧(英文名稱是stack)是系統(tǒng)自動(dòng)分配空間的,例如我們定義一個(gè) char a;系統(tǒng)會(huì)自動(dòng)在棧上為其開辟
空間。而堆(英文名稱是heap)則是程序員根據(jù)需要自己申請(qǐng)的空間,例如malloc(10);由于棧上的空間是自動(dòng)分配自動(dòng)回收的,所以棧上的數(shù)據(jù)的生存周期只是在函數(shù)的運(yùn)行過程中,運(yùn)行后就釋放掉,不可以再訪問。而堆上的數(shù)據(jù)只要程序員不釋放空間,就一直可以訪問到,不過缺點(diǎn)是一旦忘記釋放會(huì)造成內(nèi)存泄露。還有其他的一些區(qū)別網(wǎng)上的總結(jié)的不錯(cuò)這里轉(zhuǎn)述一下:
1.申請(qǐng)后系統(tǒng)的響應(yīng)
棧:只要棧的剩余空間大于所申請(qǐng)空間,系統(tǒng)將為程序提供內(nèi)存,否則將報(bào)異常提示棧溢出。
堆:首先應(yīng)該知道操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)收到程序的申請(qǐng)時(shí),會(huì)遍歷該鏈表,尋找第一個(gè)空間大于所申請(qǐng)空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑結(jié)點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序,另外,對(duì)于大多數(shù)系統(tǒng),會(huì)在這塊內(nèi)存空間中的首地址處記錄本次分配的大小,這樣,代碼中的 語(yǔ)句才能正確的釋放本內(nèi)存空間。另外,由于找到的堆結(jié)點(diǎn)的大小不一定正好等于申請(qǐng)的大小,系統(tǒng)會(huì)自動(dòng)的將多余的那部分重新放入空閑鏈表中,也就是說堆會(huì)在申請(qǐng)后還要做一些后續(xù)的工作這就會(huì)引出申請(qǐng)效率的問題。
2.申請(qǐng)效率的比較
棧:由系統(tǒng)自動(dòng)分配,速度較快。但程序員是無法控制的。
堆:是由new分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過用起來最方便。
3.申請(qǐng)大小的限制
棧:在Windows下,棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個(gè)編譯時(shí)就確定的常數(shù)),如果申請(qǐng)的空間超過棧的剩余空間時(shí),將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲(chǔ)的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見,堆獲得的空間比較靈活,也比較大。
4.堆和棧中的存儲(chǔ)內(nèi)容
棧: 在函數(shù)調(diào)用時(shí),第一個(gè)進(jìn)棧的是主函數(shù)中函數(shù)調(diào)用后的下一條指令(函數(shù)調(diào)用語(yǔ)句的下一條可執(zhí)行語(yǔ)句)的地址,然后是函數(shù)的各個(gè)參數(shù),在大多數(shù)的C編譯器中,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量。注意靜態(tài)變量是不入棧的。 當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開始存的地址,也就是主函數(shù)中的下一條指令,程序由該點(diǎn)繼續(xù)運(yùn)行。
堆:一般是在堆的頭部用一個(gè)字節(jié)存放堆的大小。堆中的具體內(nèi)容有程序員安排。
堆和棧的區(qū)別可以引用一位前輩的比喻來看出:
使用棧就象我們?nèi)ワ堭^里吃飯,只管點(diǎn)菜(發(fā)出申請(qǐng))、付錢、和吃(使用),吃飽了就走,不必理會(huì)切菜、洗菜等準(zhǔn)備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。使用堆就象是自己動(dòng)手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。
局部變量指針
如果不了解程序棧如何工作,就很容易犯返回指向局部數(shù)據(jù)指針的錯(cuò)誤,看下面的例子:
#include
#include
int* allocateArray(int size, int value)
{
int arr[size];
for(int i = 0; i < size; i++) {
arr[i] = value;
}
return arr;
}
int main()
{
int* vector = allocateArray(5, 45);
for(int i = 0; i < 5; i++) {
printf("%d ", vector[i]);
}
return 0;
}
一旦函數(shù)返回,返回的數(shù)組地址也就無效,因?yàn)楹瘮?shù)的棧幀從棧中彈出了
有一種方法是把a(bǔ)rr變量聲明為static,這樣會(huì)把變量的作用域現(xiàn)在在函數(shù)內(nèi)部,但是分配在棧幀的外面,避免其他函數(shù)覆寫變量值
#include
#include
int* allocateArray(int size, int value)
{
static int arr[10];
for(int i = 0; i < size; i++) {
arr[i] = value;
}
return arr;
}
int main()
{
int* vector = allocateArray(5, 45);
for(int i = 0; i < 5; i++) {
printf("%d ", vector[i]);
}
return 0;
}
返回指針
從函數(shù)返回對(duì)象經(jīng)常使用以下兩種技術(shù):
使用malloc在函數(shù)內(nèi)部分配內(nèi)存并返回其地址,調(diào)用者負(fù)責(zé)釋放返回的內(nèi)存
傳遞一個(gè)對(duì)象給函數(shù),讓函數(shù)修改它,這樣分配和釋放對(duì)象的內(nèi)存都是調(diào)用者的責(zé)任
#include
#include
int* allocateArray(int size, int value)
{
int* arr = (int*)malloc(size * sizeof(int));
for(int i = 0; i < size; i++) {
arr[i] = value;
}
return arr;
}
int main()
{
int* vector = allocateArray(5, 45);
for(int i = 0; i < 5; i++) {
printf("%d ", vector[i]);
}
free(vector);
return 0;
}
下面這個(gè)版本的allocateArray函數(shù)傳遞了一個(gè)數(shù)組指針、數(shù)組的長(zhǎng)度和用來初始化數(shù)組元素的值,返回指針只是為了方便
#include
#include
int* allocateArray(int *arr, int size, int value)
{
if(arr != NULL) {
for(int i = 0; i < size; i++) {
arr[i] = value;
}
}
return arr;
}
int main()
{
int* vector = (int*)malloc(5 * sizeof(int));
allocateArray(vector, 5, 45);
for(int i = 0; i < 5; i++) {
printf("%d ", vector[i]);
}
free(vector);
return 0;
}
傳遞指針的指針
將指針傳遞給函數(shù)的時(shí)候,傳遞的是值,如果希望修改原指針而不是指針的副本,就需要傳遞指針的指針
#include
#include
void allocateArray(int **arr, int size, int value)
{
*arr = (int*)malloc(size * sizeof(int));
if(arr != NULL) {
for(int i = 0; i < size; i++) {
*(*arr + i) = value;
}
}
}
int main()
{
int* vector = NULL;
allocateArray(&vector, 5, 45);
for(int i = 0; i < 5; i++) {
printf("%d ", vector[i]);
}
free(vector);
return 0;
}
二叉樹遞歸實(shí)現(xiàn)與二重指針
二叉樹的諸多操作往往是通過遞歸調(diào)用來實(shí)現(xiàn)的,這就決定,不能只通過main函數(shù)實(shí)現(xiàn)全部過程,其中還需要調(diào)用main外定義的函數(shù)。也因此,對(duì)main調(diào)用外定義的函數(shù)的參數(shù)傳遞,就有了嚴(yán)格的要求。在網(wǎng)上查找很多關(guān)于二叉樹建立的程序,但直接拷貝在自己計(jì)算機(jī)上運(yùn)行卻發(fā)現(xiàn)不少錯(cuò)誤,無法編譯通過。以下有關(guān)代碼編譯通過,不涉及二叉樹的全部操作,著重通過C語(yǔ)言實(shí)現(xiàn)二叉樹的創(chuàng)建過程說明遞歸實(shí)現(xiàn)與二重指針的相關(guān)問題。
1、二叉樹的定義
二叉樹的定義結(jié)構(gòu)通常為如下形式:
typedef struct Node
{
char ch;
struct Node *lchild,*rchild;
}Node,*BTree;
Node一般可用來定義二叉樹節(jié)點(diǎn),而*BTree可用來定義指向二叉樹(根節(jié)點(diǎn))的指針
2、內(nèi)存動(dòng)態(tài)分配
采用內(nèi)存動(dòng)態(tài)分配需要用到malloc函數(shù)。值得注意的是,該函數(shù)在成功開辟新的內(nèi)存時(shí),默認(rèn)返回void*指針,因此需要強(qiáng)制轉(zhuǎn)換成Node*形式,其調(diào)用形式如(Node*)malloc(sizeof(Node))
3、遞歸調(diào)用
因?yàn)檫f歸調(diào)用的需要,二叉樹的一些操作需要獨(dú)立作為一個(gè)函數(shù)。但是,這些函數(shù)是在main中調(diào)用,因此傳遞的參數(shù)和返回的值的處理是非常重要的。另外注意,對(duì)二叉樹的操作,首先就需要知道二叉樹的入口,即指向二叉樹的指針,也即指向二叉樹根節(jié)點(diǎn)的指針。因此,所傳遞的參數(shù),則為指向根節(jié)點(diǎn)的指針。又因?yàn)樯婕胺峙鋬?nèi)存的操作,必須傳遞二級(jí)指針,如下程序,CreateTree函數(shù)可以是由返回值,也可以不具有返回值(因?yàn)閭鬟f的是地址)。在main函數(shù)中作了測(cè)試,返回的值為二叉樹根節(jié)點(diǎn)的值。
void CreateTree(Node** pTree)
{
char ch;
scanf("%c",&ch);
if(chr == '#') {
(*pTree) = NULL;
} else {
if(!((*pTree) = (Node*)malloc(sizeof(Node)))) {
exit(OVERFLOW);
}
(*pTree)->ch = chr;
CreateTree(&((*pTree)->lchild));
CreateTree(&((*pTree)->rchild));
}
}
【C語(yǔ)言指針的傳遞】相關(guān)文章:
C語(yǔ)言的指針12-21
C語(yǔ)言指針的總結(jié)11-24
C語(yǔ)言指針的概念02-25
C語(yǔ)言指針的用法11-15
C語(yǔ)言指針教學(xué)02-10
什么是C語(yǔ)言中指針 C語(yǔ)言指針的基礎(chǔ)使用12-17
如何理解C語(yǔ)言指針12-18
C語(yǔ)言指針變量的運(yùn)算09-30
- 相關(guān)推薦