C语言指针

 

目录

指针

一、字符指针

二、指针数组

三、数组指针

1.数组指针的定义

2.&数组名和数组名

3.数组指针的使用

四、 数组传参、指针参数

1、一维数组传参

2、二维数组传参

3、一级指针传参与二级指针传参

五、函数指针

六、函数的指针数组

七、函数的指针数组的指针

八、回调函数


 

 

指针

1.指针就是一个变量(指的是指针变量),指针是用来存放地址的,地址唯一标识一块内存空间。(也就是说,内存会划分为小的内存单元,每个内存单元都有一个编号,这个编号就被称为地址,也把地址叫做指针)。

2.指针的大小是固定的,在32位系统下是4个字节,在64位系统下是8个字节。

3.指针是有类型的,指针的类型决定了指针的+-整数的步长(+1或者-1会跳过几个字节),指针解引用操作的时候的权限。

一、字符指针

指针类型有一种类型为char*(字符指针)

一般情况下,我们将一个字符的地址存放在一个字符指针变量中

int main()
{
    char ch = 'a';
    char* pc = &ch;
    *pc = 'a';
    return 0;
}

pc就是一个指针变量,里面存放的是ch的地址,当我们对指针变量pc进行解引用,就可以的到我们的字符'a';

这个我们很容易理解,但是我们还有一种常用的写法,

#include<stdio.h>
int main()
{
    const char* str = "hello";
    printf("%s\n",str);
    return 0;
}
 

 

在这里,我们就会想到,这是将一个字符串放在str的指针变量里了吗?

在这里,我们并不是将一整个字符串放在str的指针变量中了,而是将字符串的首字符的地址存放在了str中,当我们去向后访问时,访问到\n就访问结束。

#include<stdio.h>
int main()
{
	char str1[] = "hello bit";
	char str2[] = "hello bit";
	const char* str3 = "hello bit";
	const char* str4 = "hello bit";
	if (str1 == str2)
	{
		printf("str1 and str2 are same\n");
	}
	else
	{
		printf("str1 and str2 are not same\n");
	}
	if (str3 == str4)
	{
		printf("str3 and str4 are same\n");
	}
	else
	{
		printf("str3 and str4 are not same\n");
	}
	return 0;
}

这里的运行结果应该是什么呢?

 str1 str2是在内存中内存中开辟了两个空间,分别用来存储字符串str1 和 str2,因此str1!=str2。在str3 与 str4比较时,这里就需要注意了,这里是将hello bit这个字符串的地址分别存放在str3 和 str4中,因此这里str4 和 str3 是相同的。

二、指针数组

我们了解到数组,数组是一个存放相同类型元素的集合,我们之前学习到有

int* arr1[10];//整型指针数组
char* arr2[10];//字符指针数组
char* * arr3[10];//二级字符指针数组

指针数组,是一个用来存放指针的数组。

三、数组指针

1.数组指针的定义

当我们看到数组指针时,数组指针是数组还是指针?他应该是指针,也就是一个数组的地址,

int* p1[10];

int(*p2)[10];

在这里p1和p2分别是指的什么?

p1指的是一个整型指针数组,p1是一个数组,里面存放了10个int*类型的元素;

p2是一个指针,在这里表示p2是一个指针变量,P2指向的是一个大小为10的一个整型数组,p2因为指向一个数组,也是一个数组指针。

2.&数组名和数组名

在之前我们了解到,数组名是数组首元素地址,但是有两个情况例外,一个是将数组名在sizeof里面使用时,另一个就是&数组名,在这两种情况下数组名不代表数组首元素地址。

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr);
	return 0;
}

上面一串代码,我们输出的时arr的地址,还有&arr的地址,当我们运行时,我们会看到

输出的结果是一样的,但是它的本质上是一样的吗?

接下来将arr与&arr分别加1我们来看一下结果

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr);
 
	printf("arr+1 = %p", arr + 1);
	printf("&arr+1 = %p", &arr + 1);
	return 0;
}

 

 当我们对arr和&arr分别+1时,我们看到了不同,006FF8F0与006FF914之间相差了36个字节,因此我么们不由的想到arr和&arr的值是一样的,但是其本质的意义有所不同。本质上&arr表示的是数组的地址,当我们对&arr+1时,我们跳过了arr整个数组,arr数组里面存储了10个int类型的元素,因此&arr+1我们这个操作跳过了40个字节,&arr的类型是int(*)[10],是一种数组指针类型。

3.数组指针的使用

我们了解到了数组指针,但是数组指针具体有什么用处呢?

#include<stdio.h>
void print(int(*arr)[5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print(arr, 3, 5);
	return 0;
}

我们创建一个二维数组,写一个打印函数print 将二维数组打印出来

 我们在传参时将二维数组arr传进去,当我们接收时可以用一个数组指针来接受,我们知道二维数组其实在存储是还是以一维数组的方式存储的(应该可以这样子理解);

 只是我们理解为一个二维数组,但是编译器在处理时还是当作一个一维数组处理的,编译器是认为这样子的

 

数组名arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,因此可以用数组指针来接收。

四、 数组传参、指针参数

1、一维数组传参

include<stdio.h>
int main()
{
	int arr1[10] = { 0 };
	int* arr2[20] = { 0 };
	test1(arr1);
	test2(arr2);
	return 0;
}

当我们创建两个数组arr1  arr2  我们要将这两个数组当作实参传入到函数test1与test2中,这个我们应该怎样来写test1,2的形参呢?

void teat1(int arr[])
{}
void test1(int arr[10])
{}
void test1(int* arr)
{}

当test1时,我们用一个整型数组来接受,是没有任何问题的,当然这里我们可以不写数组的大小,因为在数组传参时我们本质上传入的是首元素的地址,在传参的时候并不会创建一个新的数组,因此不写大小,也可以直接遍历。当然我们也可以用一个指针的形式来接收

void test2(int* arr[20])
{}
void test2(int** arr)
{}

当test2时,我们用一个int*类型的数组来接收,也可以用一个指针变量来接收

2、二维数组传参

void test(int (*arr)[5])
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

在二维数组传参时,我们可以用数组指针的形参来接收,但是这里需要注意的是,二维数组传参,函数形参的设计只能省略第一个[]中的数字,也就是只能省略行,但不能省略列,因为并不知道一行中有多少个元素,但是对于行,当我们对数组指针进行解引用时,我们对其加1,数组指针加1跳过的是一个数组,因此就可以找到第二行的首元素的地址,这样子我们就可以找到每一行的每一个元素,也就可以找到每一个元素,因此行可以省略,但是列不能省略。

3、一级指针传参与二级指针传参

这里的指针传参比较简单,当一级指针传参时,我们用一个一级指针变量来接收就好了,同理,二级指针用二级指针变量来接收就可以了

五、函数指针

函数指针,顾名思义,函数指针就是指向函数的指针。

#include<stdio.h>
void test()
{
	printf("hello bit");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

上面一串代码,我们分别打印test的地址和&test的地址

我么看到,打印出来的结果竟然是一样的,那是不是和上面说的arr和&arr类似,只是地址一样,但是本质上不一样呢?

其实test==&test,他们两个在本质上是一样的(这也太神奇了,hahaha)。

那么这里我们应该将函数的地址保存起来,应该怎样保存呢?这时我们应该用到函数指针,函数指针应该是什么样子的?

首先创建一个变量p我们再将指针变量与*结合起来,表示p是一个指针,那么然后指针指向的是一个函数,指向的函数无参数(*p)(),函数的返回值类型为void因此应该为void(*p)();
例如

int ADD(int x,int y)
{
    return x+y;
}

这个函数的指针应该为int (*ADD)(int x,int y)

六、函数的指针数组

将函数的地址存放在一个数组中,那么这个数组就叫做函数的指针数组。

如   int  (*parr[10]) ();

parr首先和[10]结合,表示parr是一个数组,里面存放的是函数指针,而函数指针的类型是int  (*) ();那么函数的指针数组有自己的用途:转移表

假设我们现在要做一个简易的加减乘除计算器,
 

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("*******************\n");
	printf("***1.Add  2.Sub****\n");
	printf("***3.Mul  4.Div****\n");
	printf("*******************\n");
}
int main()
{
	int x, y;
	int input = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			int ret = Add(x, y);
			printf("ret = %d", ret);
			break;
		case 2:
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			int ret = Sub(x, y);
			printf("ret = %d", ret);
			break;
		case 3:
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			int ret = Mul(x, y);
			printf("ret = %d", ret);
			break;
		case 4:
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			int ret = Div(x, y);
			printf("ret = %d", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
		}
	} while (input);
	return 0;
}

这个写法比较冗余;当我们以后要添加新的算法的时候,那就要写更多的case语句,但是,当我们使用函数指针数组后,就可以简化很多了:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("*******************\n");
	printf("***1.Add  2.Sub****\n");
	printf("***3.Mul  4.Div****\n");
	printf("*******************\n");
}
int main()
{
	int x, y;
	int input = 0;
	int red = 0;
	int(*p[5])(int x, int y) = { 0,Add,Sub,Mul,Div };
	while (input)
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			red = (*p[input])(x, y);
		}
		else
		{
			printf("输入有误\n");
			printf("red = %d", red);
		}
	}
	return 0;
}

这样子看起来,当我们用转移表的方式写起来这个功能,这样子代码量就少了很多,但是这里也有自己的不足,因为在函数指针数组中,只能存放相同类型的函数指针,假设当我们的要运算的数有三个数的时候,那么这个方法便不可取了;

七、函数的指针数组的指针

函数的指针数组的指针,就是一个数组里面存放的是函数的指针,这个数组的指针就叫做函数指针数组的指针,那么这样的话,我们的这个就有点套娃了,但是我们先在这里套一下(哈哈哈)

看上面的代码我们将其拿下来

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int  Div(int x,int y)
{
    return x / y;
}
int main()
{
    void(*p[4])(int x,int y) = {Add,Sub,Mul,Div};
    return 0;
}

void(*p[4])(int x,int y) = {Add,Sub,Mul,Div};就是一个数组,里面放的四个函数指针,我们取到这个数组的地址,这个地址也被称为函数指针数组的指针,

首先我们创建一个指针变量pp因为是一个数组,我们首先要和[]结合,因此是(pp[ ]),表示是一个数组,因为我们需要的是一个指针,(*(pp[ ])),它里面是函数的指针,这个数组里面有四个元素,因此最终创建为

viod(*(pp[4]))(int x,int y);

这里确实有点套娃的感觉,这样子的话,还会有函数指针数组的指针的数组了,又会有函数指针数组的指针的数组的指针了,那就巴拉巴拉没完没了了。

八、回调函数

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

这里我们写一个test函数;
 

void test()
{
	printf("hehe\n");
}
void print(void(*p)())
{
	if (1)
	{
		p();
	}
}
int main()
{
	print(test);
	return 0;
}

我们在这里这个test函数是可以直接调用的,我们将test函数作为一个参数传递给print函数,这时候print函数就应该来接受这个函数,当我们给一个条件,我们的P就相当于这个test函数,我们的程序结果运行起来

我们就能打印出hehe,这个print函数就被称为回调函数,但是这个回调函数有什么用呢?

我们看函数的指针数组的第一串代码,那里还是比较冗余,我们可以这样子处理,我们写一个回调函数,当我们case1 的时候,我们将Add函数当作参数传进去,case2的时候,我们将Sub函数传进去同理我们就得到了这样子的
 

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("*******************\n");
	printf("***1.Add  2.Sub****\n");
	printf("***3.Mul  4.Div****\n");
	printf("*******************\n");
}
void call_back(void (*p)(int,int))
{
    int x, y;
    int ret = 0;
    printf("请输入两个操作数\n");
    scanf("%d %d",&x,&y);
    ret = p(x,y);
    printf("ret = %d",ret);
}
int main()
{
	
	int input = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			call_back(Add);
			break;
		case 2:
			call_back(Sub);
			break;
		case 3:
			call_back(Mul);
			break;
		case 4:
			call_back(Div);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
		}
	} while (input);
	return 0;
}

这样子的话我们的代码就会减少很多,更加的容易理解了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

雾里有果橙

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值