如下內容來源書籍《C語言深度剖析–第2版》,強烈建議初學者買來讀一讀,良心推薦!
1.常規理解(錯誤)
#include <stdio.h>
void fun(char a[10]) {
char c = a[3];
printf("%c \n", c);
};
int main() {
char b[10] = "abcdefg";
fun(b[10]); // 此處出錯,實參b[10]
return 0;
}
分析:
- 函數:char * 與 char的間接級別不同,說明函數中,一個需要的是char * ,一個傳遞的是char。所以傳遞錯誤了。
- 本身b[10]也越界取值了,且b[10]代表的是數組的一個元素,也不能代表數組的含義。
2.改良之後(感覺正確了)
經過改良之後,打印出來預期的值d:
#include <stdio.h>
void fun(char a[10]) {
char c = a[3];
printf("%c \n", c);
printf("%d \n", sizeof(a)); // 打印數組所佔字節大小
};
int main() {
char b[10] = "abcdefg";
fun(b);
return 0;
}
d
4
但是,如果真的把數組傳遞到函數中,那在函數中打印數組的大小,應該是10纔對的,不應該是4呀?難道傳遞過去的不是數組?
重點!重點!重點!:
1).在C語言中,當一維數組作爲函數參數的時候,編譯器總是把它解析成一個指向其首元素地址的指針。
2).在C語言中,所有非數組形式的數據實參,均以傳值形式調用。(對實參做一份備份並傳遞給被調用的函數,函數不能修改作爲實參的實際變量的值,只能修改傳遞給它的那份備份)
3).如果在複製整個數組,無論在空間上還是時間上,其開銷都非常大。更重要的是,絕大部分情況下,你其實並不需要整個數組的備份,你只是想告訴函數,在哪一刻對哪個特定的數組操作。這樣的話,爲了節省時間和空間,直接告訴程序這個數組的指針即可,通過指針可以訪問、可以修改,效率還高。這個就是爲什麼,一維數組傳遞給函數的是指針,而不是數組本身。
4).函數本身沒有類型的,只有函數的返回值纔有類型。
3.最終方案:
方案一:用指針代表數組
#include <stdio.h>
void fun(char *p) {
char c = p[3];
printf("%c \n", c); // char c = *(p+ 3) 效果一樣
};
int main() {
char b[10] = "abcdefg";
fun(b);
return 0;
}
- main函數中,我們把數組名傳遞到fun函數中,但fun函數的形參是個指針變量,即fun的形參,char * p中char * 是數據類型,傳遞給函數的是p
- main函數中,數組名b也是首元素的地址,傳遞給fun函數,其實隱藏的步驟是char *p = &b;
- 只是傳遞過去的是數組b的值,給到了指針(後面會說明爲什麼傳遞的是值,而不是數組本身!)
方案二:用空數組代表數組
#include <stdio.h>
void fun(char a[]) { // 空數組,可以傳遞過去任意長度字符數據,不受限制
char c = a[3];
printf("%c \n", c);
};
int main() {
char b[10] = "abcdefg";
fun(b);
return 0;
}
這種方式有個限制:
在fun函數中,如果用到了數組的長度,則無法計算
4.把指針變量傳遞給函數
這部分想驗證的是:把指針變量本身傳遞給了函數,還是指針變量的備份給了函數
1.錯誤的過程
#include <stdio.h>
void fun(char* p) {
printf("%c \n", *(p + 3)); // p[3]效果一樣
};
int main() {
char* p2 = "abcdefg"; // 此處P2是字符數組指針,且"abcdefg"保存在常量區
fun(p2);
return 0;
};
- char *p2 = "abcdefg"這個相當於const char *p2 =”abcdefg”,這個保存在常量區域,所以通過指針是無法修改這個字符數組的
- ”abcdefg”字符數組相當於先保存在常量區,然後指針p2再指針這個常量區域內存。
- p2無法改變這個常量區的值,但是p2的指向是可以修改的
- 有關字符數組與字符串指針的問題,詳見另一個博文:
d
既然傳遞的是指針,那可以根據指針對數據進行修改了?
#include <stdio.h>
char* GetMemory(char* p, int num) {
p = (char*)malloc(num * sizeof(char)); // 手動創建一個內存空間,程序對此有讀寫權限了
};
int main(void) {
char* p2 = NULL;
GetMemory(p2, 10);
strcpy(p2, "hello");
printf("%c \n", *p2);
free(p2);
return 0;
}
運行之後的報錯信息如下:
最終發現,利用strcpy改變指針所指向的內存空間的時候,居然顯示訪問權限衝突了!這個是爲啥呢?
重點:
- 我們在main函數中,將指針p2傳遞給了fun函數,其實只是傳遞了p2指針的一個備份_p2。原始的指針地址並沒有改變。
- 如果p2有值,則系統會臨時開闢一塊空間存儲這個臨時的值,並把臨時的指針_p2指向這個臨時的內存空間。所以_p2以及這個臨時的內存,都是編譯器自動分配和回收的,只能讀取,沒有修改的權限!
- 綜上所述:指針變量傳遞給函數,只是傳遞值的一個拷貝,而不是指針本身
2.return方法
#include <stdio.h>
char* GetMemory(char* p, int num) {
p = (char*)malloc(num * sizeof(char)); // 手動創建一個內存空間,程序對此有讀寫權限了
return p; // 使用return接收這塊內存空間
};
int main() {
char* p2 = NULL; // 創建一個空指針,其實主要爲了GetMemory函數解析指針類型
p2 = GetMemory(p2, 10); // p2是main函數的局域變量,GetMemory創建的空間,被p2接收,相當於p2又重新指向了一塊內存空間
strcpy(p2, "hello"); // 或通過 *(p2 + 1) = "m"驗證也可以
printf("%c %c \n", p2[0], p2[1]);
free(p2);
return 0;
};
h e
3.二維指針:指針的指針
#include <stdio.h>
char* GetMemory(char** p, int num) {
*p = (char*)malloc(num * sizeof(char)); // 自動創建一個內存空間,用指針指向這個空間
printf("p的地址爲:%p \n", p);
printf("&p的地址爲:%p \n", &p);
printf("*p的地址爲:%p \n", *p);
};
int main() {
char* p2 = NULL;
printf("%p \n", p2);
printf("%p \n", &p2);
printf("===============\n");
GetMemory(&p2, 10);
strcpy(p2, "hello");
printf("===============\n");
printf("%p \n", p2);
printf("%p \n", &p2);
printf("%c %c \n", p2[0], p2[1]);
free(p2);
return 0;
};
00000000
012FF730
===============
p的地址爲:012FF730
&p的地址爲:012FF658
*p的地址爲:017A2FF0
===============
017A2FF0
012FF730
h e
char * p2 = NULL的含義如下:
GetMemory(&p2, 10)的含義如下:
GetMemory(&str, 10):含義
- 此處傳遞的是指針的地址&str,給p,相當於p = "0xF0012B"類似這樣的,那p就變成了指針的指針了!有關這個,可參考此篇博文:C—看圖學指針和多級指針—入門篇
- GetMemory本身創建的的內存地址,被&p2的一個替身,_&p2接收了。而臨時變量p = _&p2,相當於有個指針的指針**p指向了p,而*p指向新創建的內存空間
strcpy(p2, “hello”) 的含義如下:
- p2是main函數創建的局域變量,這個變量的&p2肯定一致沒變
- strcpy(p2, “hello”)相當於,把p2的指針,指向一塊可以修改的內存空間,並賦值爲“hello”
- 有關strcpy的應用,可參考如下博文:
https://www.cnblogs.com/ngnetboy/archive/2012/11/19/2777384.html