指针进阶(深入理解)

发布于:2024-03-28 ⋅ 阅读:(18) ⋅ 点赞:(0)



指针初级(基础知识)-CSDN博客

目录

​​​​​​​​​​​​​​​​​​​​​指针初级(基础知识)-CSDN博客

1.数组名的理解

1.1.特殊情况一(&数组名)

1.2.特殊情况二(sizeof(数组名))

2.字符指针

3.二级指针

3.1二级指针模拟二维数组

4.指针数组

​4.1指针数组模拟二维数组

​5.数组指针

5.1数组指针模拟二维数组

6.一维数组传参的本质

7.二维数组的理解

8.函数指针

9.函数指针数组

10.回调函数 

11.转移表


1.数组名的理解

通常情况下数组名表示首元素地址,当然数组名在不同的地方有着不同的意思,不能总的理解为首元素的地址。

1.1.特殊情况一(&数组名)

这里&arr中arr表示整个数组地址,&数组名,这里数组名表示整个数组的地址。那么有人可能会想整个数组的地址又是什么呢?其实:

                            &arr=&arr[0];

&地址数组名的值=&地址数组首元素的值
区别在于:

                           (&arr+1)!=(&arr[0]+1);
&数组名加1跳过整个数组&数组首元素加1跳过一个元素。所以我们把&数组名中数组名理解为整个数组,而不是首元素地址。

1.2.特殊情况二(sizeof(数组名))

sizeof(数组名),这里的数组名表示整个数组,即sizeof(数组名)计算数组在内存中占的字节个数。我们常用sizeof(数组名)/sizeof(数组首元素)计算数组中元素个数。
sizeof(数组名+常数),这里不是单纯放一个数组名,那么这里的数组名就是通常我们说的首元素地址,是地址的话,这里的sizeof结果只能是4或8。

2.字符指针

在指针的类型中我们知道有⼀种指针类型为字符指针 char* ;
⼀般使⽤:

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

还有⼀种使⽤⽅式如下:

int main()
{
    const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
    printf("%s\n", pstr);
    return 0;
}

代码 const char* pstr = "hello bit."; 特别容易让同学以为是把字符串 hello bit 放
到字符指针 pstr ⾥了,但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中

上⾯代码的意思是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量 pstr 中

下面是一道笔试题我们来思考一下输出结果

#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;
}

        这⾥str3和str4指向的是同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域,当⼏个指针指向同⼀个常字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同

3.二级指针

二级指针很好理解。就是存放一级指针的地址指针。当我们要用函数对一级指针进行修改的时候,我们的函数形参变量就需要用二级指针。

3.1二级指针模拟二维数组

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
	int** p = NULL;
	int n = 1;
	p = malloc(sizeof(int*) * 3);
	assert(p);
	for (int i = 0; i < 3; i++)
	{
		p[i] = (int*)malloc(sizeof(int) * 5);
		assert(p[i]);
		for (int j = 0; j < 5; j++)
		{
			p[i][j] = n++;
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
	return 0;
}

4.指针数组

它的主语是数组意思是存放指针的数组。如以下定义:


4.1指针数组模拟二维数组

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* parr[3] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

5.数组指针

它的主语是指针,意思是存放数组的指针,如以下定义


注意:[ ]的优先级比 * 高,所以用括号让 * 先与p结合。

而p的类型为 int(*)[5] ,意思是元素为int类型长度为5的数组类型的指针变量

5.1数组指针模拟二维数组

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
	int	(*p)[5], n = 1;
	p = (int*)malloc(sizeof(int) * 3 * 5);
	assert(p);
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			p[i][j] = n++;
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
	free(p);
	return 0;
}

6.一维数组传参的本质

一维数组传参


在刚学数组的时候,我们可能用以上方式进行数组传参。发现传过去的实参的是数组名,数组名就是首元素地址也就是一个指针,所以说明形参可以用一个指针来接收如以下方式。

7.二维数组的理解

一个3行5列的二维数组如下

我们可以看作3个一维数组的组合二维数组名就是整个第1行数组的地址,即arr=&arr[0],而arr[0]=&arr[0][0]。即arr[0]就是第一行的数组名。

根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址是⼀维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀
⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下

8.函数指针

要知道写一个函数也是需要向内存开辟空间的,它也有地址。函数指针顾名思义就是指向函数的指针。既然是指针就是存地址的。那么怎么得到函数的地址呢?

答案很简单,就是&函数名,而与数组类似的是函数名 就是 函数的地址。即&函数名=函数名。这里比如我们定义一个函数指针变量。

这里pf3的类型就是int(*)(int,int),表示返回类型为int,两个参数类型为int的函数指针类型
函数指针的用法:


当然这里重点是让大家知道函数指针的用法,而这么用是有点多余,那么它常常是怎么用呢?接下来我们继续往下看。

9.函数指针数组

函数指针数组指的就是存放函数指针的数组。

如以下定义:

而以下两种写法是错误的:


当然为了方便理解我们还可以这么写:

padd是一个int(*)(int,int)类型长度为5的数组。即它存放的元素为函数指针。

10.回调函数 

回调函数就是通过函数指针传参来实现从一个函数里调用另一个函数,举个例子。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
void menu()
{
	printf("****1.+ *****2. - ******\n");
	printf("****3.* *****4. / ******\n");
	printf("******** 0.exit *********\n");
}
int con(int(*p)(int, int),int a,int b)
{
	return p(a, b);
}
int main()
{
	
	menu();
	int n = 0;
	printf("请选择>>\n");
	scanf("%d", &n);
	if (!n)
	{
		printf("已退出\n");
		return 0;
	}
	printf("input>>\n");
	int a, b,ret;
	scanf("%d%d", &a, &b);
	switch (n)
	{
		case 1:
			ret=con(add, a, b);
			break;
		case 2:
			ret = con(sub, a, b);
			break;
		case 3:
			ret = con(mul, a, b);
			break;
		case 4:
			ret = con(div, a, b);
			break;
		default:
			printf("输入错误");
			return 0;
	}
	printf("结果为%d", ret);
	return 0;
}

11.转移表

转移表实质就是一个函数指针数组,它是如何使用的呢?
举一个不成熟的例子(计算机模拟)。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
void menu()
{
	printf("****1.+ *****2. - ******\n");
	printf("****3.* *****4. / ******\n");
	printf("******** 0.exit *********\n");
}
int main()
{
	
	int(*p[5])(int, int) = { NULL,add,sub,mul,div };
	menu();
	int n = 0;
	scanf("%d", &n);
	if (n == 0)
	{
		return 0;
	}
	printf("input\n");
	int a, b;
	scanf("%d%d", &a, &b);
	printf("结果为%d",p[n](a, b));
	return 0;
}

本文含有隐藏内容,请 开通VIP 后查看