前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C语言·深入理解指针(进阶)

C语言·深入理解指针(进阶)

作者头像
AUGENSTERN_
发布2024-04-09 20:29:56
950
发布2024-04-09 20:29:56
举报
文章被收录于专栏:畅游IT畅游IT

1 字符指针

在指针类型中,我们知道字符类型的指针为char*

一般使用:

代码语言:javascript
复制
int main() 
{
    char ch = 'w';
    char* p = &ch; 
    *p = 'a'; // ch == 'a'
    return 0;
}

还有一种使用方法如下:

代码语言:javascript
复制
int main()
{
    char *p = "Hello world";
    printf("%s", p);
    return 0;
}

像这种情况,很多人会认为是吧"Hello world"存放到了p当中,

其实并不是,这行代码的本质只是将"Hello world"的首字符地址存放到了p当中。

2 指针数组

2.1 解释

我们知道,数组是用来存放相应元素的,例如整形数组用来存放整形,字符型数组用来存放字符,那么指针数组就是用来存放指针的一个数组。

2.2 创建指针数组

指针数组的创建和普通数组的创建是大同小异的,只不过他们的类型有所区别;

int arr[]是用来存放整形的数组,那么int* arr[]就是用来存放整形指针的数组

接下来我们创建一个简单的整形指针数组:

代码语言:javascript
复制
int main()
{
	int a = 1;
	int b = 2;
	int c = 10;
	int* pa = &a;
	int* pb = &b;
	int* pc = &c;
	int* arr[] = { pa, pb, pc };
}

3 数组指针

3.1 数组指针的定义

什么是数组指针,数组指针到底是指针还是数组?

答案是指针!!! (这里我们要区别于指针数组)

我们已经认识了

整形指针: int* pint; 指向整形的指针

字符指针: char* pchar; 指向字符的指针

那么顾名思义,数组指针就是指向数组的指针。

那么以下两个哪个是数组指针呢?

代码语言:javascript
复制
int* p1[10];
int (*p2)[10];

就多一个括号而已,又有什么不一样吗?

我们知道定义一个整形变量,我们需要他的类型和变量名来完成定义;

例如:

代码语言:javascript
复制
int a;
int arr[10];

那么,在这个地方,他的类型为int,也就是整形,a就是他的变量名。

那么一个数组的类型又是什么呢?

答案: int [10]就是他的类型!!!

再来看我们的问题,p1和p2的区别就在于,p2和*号被括号括起来了,就说明p2是一个指针,而他的类型又是int [10],所以说,p2就是数组指针!!!!

3.2 数组名 VS &数组名

在学习数组的内容的时候,我们了解到了数组名就是数组首元素的地址,那么&数组名又和数组名有什么区别呢?

我们不妨尝试运行一下下面这行代码:

代码语言:javascript
复制
int main()
{
	int arr[10];
	printf("%p\n", arr);
	printf("%p\n", &arr);
	return 0;
}

其运行结果如下:

可见arr和&arr的地址是一样的,那么他们的区别又在哪里呢?

我们继续看下一组代码:

代码语言:javascript
复制
int main()
{
	int arr[10];
	printf("%p\n", arr);
	printf("%p\n", arr + 1);
    printf("\n");
	printf("%p\n", &arr);
	printf("%p\n", &arr + 1);
	return 0;
}

其运行结果如下:

由运行结果可知,arr + 1与arr相比,其地址多了4,这是因为数组存放的是整形,而整形的大小为四个字节;

而&arr + 1与&arr的区别却是40,也就是10个整形的大小,

由此可见,&arr代表的是整个数组的地址,而arr仅仅代表首元素的地址,但在编译器上表现的都是首元素的地址!!!

接下来我们来进行练习,判断以下代码的意思是什么:

代码语言:javascript
复制
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

第一个: 创建一个大小为5的整形数组;

第二个: 创建一个数组名为parr1,大小为10的整形指针数组;

第三个: 创建一个指针名为parr2,所指向的数组大小为10的整形数组指针;

第四个: 创建一个指针名为parr3, 大小为10的存放大小为5的整形指针数组的数组;

4 数组参数,指针参数

4.1 一维数组传参

思考:一下五种函数的形参分别代表的是什么?

代码语言:javascript
复制
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int* arr)//ok?
{}
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

第一种:用arr[ ]数组接受arr,该形参代表一个新的arr数组,指向的地址和实参一样;

第二种:和第一种并无区别;

第三种: 形参为整形指针,接收的是arr数组的首元素地址

第四种: 用大小为20的指针数组接收arr2;

第五种: 是二级整形指针,接收的是arr2首元素的地址;

4.2 二维数组传参

代码语言:javascript
复制
void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
void test(int* arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok?
{}
void test(int** arr)//ok?
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

同一维数组传参类似,只有第二种是不符合语法规定的形参,因为二维数组可以省略行数,但是不能省略其列数为多少,故不能使用第二种;

4.3 一级指针传参

代码语言:javascript
复制
void print(int *p, int sz)
{
     int i = 0;
     for(i=0; i<sz; i++)
     {
     printf("%d\n", *(p+i));
     }
}
int main()
{
     int arr[10] = {1,2,3,4,5,6,7,8,9};
     int *p = arr;
     int sz = sizeof(arr)/sizeof(arr[0]);
     //一级指针p,传给函数
     print(p, sz);
     return 0;
}

以上代码就是将arr数组给遍历一次,将arr数组的首元素地址和数组长度传给自定义函数print,由于数组中的元素在内存中是连续存放的,故可以用for循环进行遍历;

4.4 二级指针传参

代码语言:javascript
复制
void func(int** pp)
{
	
}
int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;
	func(pp);
	func(&p);
	return 0;
}

以上代码就是将二级指针pp传给函数func,这就是二级指针的简单使用;

5 函数指针

我们先看以下代码:

代码语言:javascript
复制
void func()
{}

int main()
{
	printf("%p\n", func);
	printf("%p\n", &func);
	return 0;
}

其运行结果如下:

5.1 函数指针的创建

那我们又应该如何存储函数的地址呢?

类似与数组指针的创建:

数组指针的创建为:

代码语言:javascript
复制
int (*p)[10];

*p代表其为指针,他的类型为 int(*)[10];

同理如果一个函数的定义是这样的:

代码语言:javascript
复制
void func1()
{}
void func2(int a, int b)
{}

那么函数指针的创建就应该为:

代码语言:javascript
复制
void (*p1)()
void (*p2)(int int)

接下来我们看两段有趣的代码:

代码语言:javascript
复制
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

第一种: void (*)()是函数指针类型,将0强制类型转换成函数指针,并解引用,调用该函数

第二种: void(*)(int)是函数指针类型,而signal是函数名,(int, void(*)(int))是函数signal的参数;故这行代码意思就是声明一个函数名为signal返回值为void(*)(int)的参数为int和,void(*)(int)的函数;

如果你能够看懂以上两段代码了,那就说明你对函数指针有了比较深的理解了;

6 函数指针数组

6.1 定义

我们已经了解了指针数组,那么函数指针数组又应该如何定义呢?

和指针数组类似:

代码语言:javascript
复制
void (*parr[10])();

6.2 应用

那么函数指针数组又有说明用处呢?

答案:转移表

我们接下来举一个例子来说明一下转移表如何使用 (简单的计算机的两种实现方式):

代码语言:javascript
复制
#include<stdio.h>
int add(int a, int b)
{
	return a + b;
}

int sub(int a, int b)
{
	return a - b;
}

int mlt(int a, int b)
{
	return a * b;
}

int Div(int a, int b)
{
	return a / b;
}

int main()
{
	int a = 0, b = 0;
	char ch;
	printf("请输入两个整数:\n");
	scanf("%d %d", &a, &b);
	printf("请输入想要进行的操作:\n (+   -   *   /)\n");
	scanf(" %c", &ch);
	switch (ch)
	{
		case '+':
		{
			printf("%d", add(a, b));
			break;
		}
		case '-':
		{
			printf("%d", sub(a, b));
			break;
		}
		case '*':
		{
			printf("%d", mlt(a, b));
			break;
		}
		case '/':
		{
			printf("%d", Div(a, b));
			break;
		}
		default:
		{
			printf("输入错误!\n");
		}
	}
	return 0;
}

运行结果:

这是用我们以往的知识来写一个简单的计算器,但是如果我们用转移表来实现的话,就会方便很多:

代码语言:javascript
复制
#include<stdio.h>
int add(int a, int b)
{
	return a + b;
}

int sub(int a, int b)
{
	return a - b;
}

int mlt(int a, int b)
{
	return a * b;
}

int Div(int a, int b)
{
	return a / b;
}

int main()
{
	int a = 0, b = 0, n = 0;
	printf("请输入两个整数:\n");
	scanf("%d %d", &a, &b);
	int (*parr[5])(int, int) = { 0, add, sub, mlt, Div };
	printf("请输入要进行的操作\n");
	printf("1. 加法   2.减法\n3. 乘法   4.除法\n");
	scanf("%d", &n);
	printf("%d", (*parr[n])(a, b));
	return 0;
}

运行结果:

使用了转移表之后,我们选择调用哪个函数只需要使用函数指针数组的对应的下标元素就好了,不需要像上述示例一样使用switch语句去判断,使得代码简单明了!!

7. 指向函数指针数组的指针

7.1 定义

同上述指针的定义相同,只需要在指针名之前添加*,加上函数指针数组的类型即可:

代码语言:javascript
复制
void func(int x)
{

}

int main()
{
	void (*pfunc)(int) = func;  //函数指针
	void (*pfuncarr[5])(int);  //函数指针数组
	pfuncarr[0] = func;
	void (*(*ppfuncarr)[5])(int);//指向函数指针数组的指针
	ppfuncarr = &pfuncarr;
	return 0;
}

8 回调函数

8.1 定义

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

8.2 示例

qsort函数中的第四个参数就是一个很经典的回调函数,他的具体参数如下:

代码语言:javascript
复制
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));

可知他有四个参数,最后一个参数是一个函数指针,调用了compar函数,故compar函数是一个回调函数。

以上就是该文章的全部内容了!!!

希望大家多多包涵,如果有错误希望大佬及时指出(毕竟我也只是初学者)O.o

感谢大家的收看!!!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-03-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 字符指针
  • 2 指针数组
    • 2.1 解释
      • 2.2 创建指针数组
      • 3 数组指针
        • 3.1 数组指针的定义
          • 3.2 数组名 VS &数组名
          • 4 数组参数,指针参数
            • 4.1 一维数组传参
              • 4.2 二维数组传参
                • 4.3 一级指针传参
                  • 4.4 二级指针传参
                  • 5 函数指针
                    • 5.1 函数指针的创建
                    • 6 函数指针数组
                      • 6.1 定义
                        • 6.2 应用
                        • 7. 指向函数指针数组的指针
                          • 7.1 定义
                          • 8 回调函数
                            • 8.1 定义
                              • 8.2 示例
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档