如下内容来源书籍《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