C---爲什麼不能向函數傳遞一個數組

如下內容來源書籍《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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章