程序员成长之旅——C语言指针和数组的补充 - Go语言中文社区

程序员成长之旅——C语言指针和数组的补充


数组和指针的参数

参数相信大家都清楚,它是分为实参和形参,实参的话就是在主函数调用函数时,实际传过去的值,而形参是声明和定义函数时的参数。

一维数组参数

先看一个例子

void fun(char a[10])
{
	char c = a[3];
}
int main()
{
	char b[10] = “abcdefg”;
	fun(b[10]);
	return 0;
}

这样做对不对,传过去b[10]。
根据前面讲的我们很明显能看出来这个是错误的

首先第一它是不存在b[10]这个数组元素的,在调试时不报错,但是在运行时它就会实际访问这个数组元素的地址,可是访问不到,那不用说是错误的。先看一下编译器给出的错误。

在这里插入图片描述
根据上图我们很明显能找到两个错误,第一个我已经说了,相信大家只要看了我上篇博客就很好理解,还有一个错误就是实参和形参类型不同。
但是你会说为啥不一样呢,它不是形参是一个数组吗?怎么会是char* 呢,别急,我们再看一下,将实参传成b。
在这里插入图片描述
你看到了,当你传过去一个数组b的时候,编译器没有报错,好的,完美。
但是你有没有想过数组b是不是真正传到了函数内部了呢
无法向函数传递一个数组
我们来验证一下
在这里插入图片描述
我们通过调试后发现 i 的值是4。但是如果数组真的传过去的话,i的值一定不会是4,而是10。所以由此可见数组是没有传过去的。这是因为C语言中有这样一条规定。
C语言中,当一维数组作为函数参数的时候,编译器总是将它解析成一个指向首元素地址的一个指针。
经过上面的解释,相信你已经理解上述的规定以及它的来由。上面编译器给出的提示,
说函数的参数是一个 char*类型的指针,这点相信也可以理解。
既然如此,我们完全可以把 fun 函数改写成下面的样子:
在这里插入图片描述
实际传递的数组大小与函数形参指定的数组大小没有关系。

一级指针参数

能否将一个指针变量传递给一个函数
先看一个例子
在这里插入图片描述
发现没有出错,但是我们是真的将变量传递给了函数吗?
当然不是,因为我们清楚main是一个局部变量,只要出了main函数,p就不存在了,但是我们这边确实调用p了,那是怎么回事,其实很简单,就是做了一个临时拷贝给函数内部,改变的是_p,而并非p本身。
无法把指针变量本身传递给一个函数
这就好比孙悟空的猴毛可以变成自己,但并不是他本身一样。所以指针变量本身是不能传递给一个函数的,那要怎么做,再看一个例子:
在这里插入图片描述
我们发现程序运行到strcpy就出错了,这是为啥呢?

这是因为函数调用的时候我们传过去的并不是指针变量str本身,而是它的一个临时拷贝,而且出了局部变量它的生命周期就结束了,因此我们并没有给str开辟空间。

那我们应该怎么做才能成功呢?这里给两种办法:
第一种

char* GetMemory(char* p, int num)
{
	p = (char*)malloc(num * sizeof(char));
	return p;
}
int main()
{
	char* str = NULL;
	str = GetMemory(str,10);
	strcpy(str, "hello");
	free(str);
	return 0;
}

这个很好理解,再看第二种
用二级指针

void GetMemory(char ** p, int num)
 {
	*p = (char *)malloc(num*sizeof(char));
	return p;
 }
int main()
{
	char *str = NULL;
	GetMemory(&str,10);
	strcpy(str,"hello");
	free(str);
	return 0;
}

这里额外注意的是我们传过去的是str的地址,而用*开锁,其值就是str本身。

二维数组参数和二维指针参数

上篇我们已经详细讲了二维数组和二维指针,那么它们分别作为参数和不做参数时有啥区别呢?

void Fun(char a[3][4]);

我们已经清楚,在一维数组中,C语言规定我们传的参数实际是一个指向首元素地址的一个指针,在这里同样适用,我们可以把二维数组形象的看成一个一维数组a[3],它的每一个元素都是一个包含四个char类型的一个数组。
这样的话,我们就可以改写成:

void Fun(char (*p)[4]));

这里的括号绝对不能省略,这样才能保证编译器把 p 解析为一个指向包含 4 个 char 类
型数据元素数组地址的指针,即指向一维数组 a[3]的元素的地址的指针。
同样,作为参数时,一维数组“[ ]”号内的数字完全可以省略:

void fun(char a[ ][4]);

不过第二维的维数却不可省略,想想为什么不可以省略?
因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
这样才方便运算。
注意:如果把上面提到的声明 void fun(char (*p)[4])中的括号去掉之后,声明“void fun
(char *p[4])”可以改写成:

void fun(char **p)

这是因为参数*p[4],对于 p 来说,它是一个包含 4 个指针的一维数组,同样把这个一维数组也改写为指针的形式,那就得到上面的写法。
来张等效关系图:
在这里插入图片描述

函数指针

定义
函数指针,顾名思义,就是一个指向函数的一个指针。
先看一个例子:

char * (*fun1)(char * p1,char * p2);//A
char * *fun2(char * p1,char * p2);//B
char * fun3(char * p1,char * p2);//C

B和C我相信大家都很好理解。
那么A是什么意思呢?fun1是函数名吗?
先回忆一下,我们之前说过的数组指针int (*p)[10]

int (*)[10] p;这样定义会更加清晰,现在明白了吧,fun1并不是函数名,它是一个指针变量,指向函数。

函数指针的使用

#include <string.h>
char* fun(char* p1, char* p2)
{
	int i = 0;
	i = strcmp(p1, p2);
	if (0 == i)
	{
		return p1;
	}
	else
	{
		return p2;
	}
}
int main()
{
	char* (*pf)(char* p1, char* p2);
	pf = &fun;
	(*pf) ("aa", "bb");
	return 0;
}

这个很容易就看懂,那么在看一个例子

void Function()
{
	printf("Call Function!n");
}
int main()
{
	void (*p)();
	*(int*)& p = (int)Function;
	(*p) ();
	return 0;
}

(int)&p ----这是什么?
在这里插入图片描述
画个图你就理解了。
这个是不是还没有很大难度呢?
那么我们再来看一个例子:
((void() ())0)()------这是什么?**

(*(void(*) ())0)();

第一步:void() (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
第二步:(void(
) ())0,这是将 0 强制转换为函数指针类型,0 是一个地址,也就是说一
个函数存在首地址为 0 的一段区域内。
第三步:((void() ())0),这是取 0 地址开始的一段内存里面的内容,其内容就是保存
在首地址为 0 的一段区域内的函数。
第四步:((void() ())0)(),这是函数调用。
好像还是很简单是吧,上面的例子再改写改写:
((char**() (char **,char **))0) ( char **,char **);
如果没有上面的分析,肯怕不容易把这个表达式看明白吧。不过现在应该是很简单的
一件事了。可以试着看一下。

函数指针数组

我们已经清楚函数指针了,那么是指针的话,就可以保存在一个数组中,举个例子

#include <stdio.h>
#include <string.h>
char* fun1(char* p)
{
	printf("%sn", p);
	return p;
}
char* fun2(char* p)
{
	printf("%sn", p);
	return p;
}
char* fun3(char* p)
{
	printf("%sn", p);
	return p;
}
int main()
{
	char* (*pf[3])(char* p);
	pf[0] = fun1; // 可以直接用函数名
	pf[1] = &fun2; // 可以用函数名加上取地址符
	pf[2] = &fun3;
	pf[0]("fun1");
	pf[0]("fun2");
	pf[0]("fun3");
	return 0;
}

在这里插入图片描述
看上图和例子分析一下你就懂了。
在这里插入图片描述
我们还可以用typedef 类型的重命名让比较复杂的函数容易看懂。

指向函数指针数组的指针

char * (*(*pf)[3])(char * p);
#include <stdio.h>
#include <string.h>
char* fun1(char* p)
{
	printf("%sn", p);
	return p;
}
char* fun2(char* p)
{
	printf("%sn", p);
	return p;
}
char* fun3(char* p)
{
	printf("%sn", p);
	return p;
}
int main()
{
	char* (*a[3])(char* p);
	char* (*(*pf)[3])(char* p);
	pf = &a;
	a[0] = fun1;
	a[1] = &fun2;
	a[2] = &fun3;
	pf[0][0]("fun1");
	pf[0][1]("fun2");
	pf[0][2]("fun3");
	return 0;
}

在这里插入图片描述
不用想那么多,只要知道它是一个指针就行了,在将上面例子看懂就没问题了。

回调函数

定义

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

回调函数模拟实现qsort函数
https://blog.csdn.net/wuweiwuju___/article/details/90708238

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/wuweiwuju___/article/details/96710029
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-04-19 13:11:55
  • 阅读 ( 1695 )
  • 分类:职场

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢