C语言基础---19.为什么不能向函数传递一个数组

如下内容来源书籍《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]代表的是数组的第11个元素,也不能代表数组的含义。
  • 如果数据的定义,是b[] = "abcde"方式, 虽然想定义数组是5个元素,实际暂用的空间是6个字符空间,还有一个\0当做数组结束符。所以传递b或者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的指向是可以修改的
  • 有关字符数组与字符串指针的问题,详见另一个博文:
    C—数组相关常见的坑(字符数组、字符指针、strcpy与=区别)
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
  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello-alien

您的鼓励,是我最大的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值