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