Bootstrap

【深入理解指针2】——顶级理解!

大家好,今天小编给大家带来的是C语言中关于指针的理解的第二部分,干货超多哦,一起来来看看吧!

1.数组名的理解

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", arr);//008FFB14
	printf("%p\n", &arr);//008FFB14
	return 0;
}

理解:数组名表示数组首元素的地址,除了两种特例外

特例

1.sizeof(数组名):求的是整个数组的大小,单位是字节
2. &数组名:取的是整个数组的地址

除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地址。

那么&arr和arr又有什么区别呢?

#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0]   = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0]+1);
printf("arr       = %p\n", arr);
printf("arr+1     = %p\n", arr+1);
printf("&arr      = %p\n", &arr);
printf("&arr+1    = %p\n", &arr+1);
return 0;
}

在这里插入图片描述
理解:

  1. 这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是⾸元素的地址,+1就是跳过⼀个元素。
  2. 但是&arr和&arr+1相差40个字节,这是因为&arr表示的是获整个数组的地址,+1于是跳过了整个数组即4*10==40个字节

2.使用指针访问数组

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p+i));
	}
	return 0;
}

说明:

  1. 指针变量p存放的是数组首元素的地址
  2. p+i等价于&arr[0+i]
  3. p[i]等价于*(p+i) ,同理arr[i] 应该等价于 *(arr+i),编译器处理数据时,⾸元素的地址+偏移量求出元素的地址,然后解引⽤来访问的。

3.数组传参的本质

3.1一维数组传参的本质

理解
传递的是数组⾸元素的地址。
形参部分可以写成指针的形式,也可以写成数组形式,实质还是指针 。

void test(int arr[])//参数写成数组形式,本质上还是指针
{
	printf("%d\n", sizeof(arr));
}
void test(int* arr)//参数写成指针形式
{
	printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	test(arr);
	return 0;
}

***总结:一维数组传参,参数的部分可以写成数组的形式,也可以写成指针的形式,但本质上还是指针

3.2二维数组传参的本质

3.2.1学习指针数组之前的二维数组传参

void test(int  a[3][5], int r, int c)//形参也写成了二维数组的形式
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)//遍历行
	{
		for (j = 0; j < c; j++)//遍历列
		{
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}

3.2.2使用数组指针

理解:⾸先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
说明:

  1. 数组名表示首元素的地址。
  2. 二维数组数组名表示第一行的地址,是一维数组的地址。
  3. 第一行的数组类型 int[5],第一行的地址的类型就是数组指针类型,int(*)[5]
  4. 那么,二维数组传参的实质上是传递了地址,传递的是第一行这个一维数组的地址。

既然传递的是地址,那么形参也可以写成指针的形式,如下:

void test(int(*p)[5], int r, int c)//形参写成了指针的形式
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);//数组名表示首元素的地址,二维数组的数组名则表示第一行一维数组的地址
	return 0;
}

数组传参总结:

  1. 形参可以是数组,也可以是指针(数组指针)
  2. 实参是函数的地址,形参只能是函数指针
  3. 创建一个数组指针来接受二维数组首元素即第一行的地址

4.冒泡排序

排序:

冒泡排序,选择,插入,快速,堆排序,希尔排序

冒泡排序的思想:

两两相邻的元素比较,如果不满足顺序就交换,满足就进行下一对。

void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
int i = 0;
for(i=0; i<sz-1; i++)
{
int flag = 1;//假设这⼀趟已经有序了
int j = 0;
for(j=0; j<sz-i-1; j++)
{
if(arr[j] > arr[j+1])
{
flag = 0;//发⽣交换就说明,⽆序
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
if(flag == 1)//这⼀趟没交换就说明已经有序,后续⽆序排序了
break;
}
}
int main()
{
int arr[] = {3,1,7,5,8,9,0,2,4,6};
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}

5.二级指针

一级指针
int a=10;
int *p=&a;//p就是一级指针变量
int **pp=&p;//pp是二级指针变量,是用来存放一级指针变量的地址
int ***ppp=&pp;//ppp是三级指针变量
解引用*  =  开锁

说明:

int**pp=&p;
前一个解引用表示:pp指向对象是int*类型,
后一个解引用表示:pp是指针变量

一级指针与二级指针的联系:

在这里插入图片描述


6.指针数组

6.1什么是指针数组?

理解:存放指针(地址)的数组 int*arr[10];
类比:

  • 整型数组:int arr[10]; 存放整型的数组

  • 字符数组:char s[10]; 存放字符的数组

    在这里插入图片描述
    在这里插入图片描述

6.2指针数组模拟二维数组

下面是模拟实现二维数组:

#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
	int* parr[3] = { arr1, arr2, arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述

理解:
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][ j]就是整型⼀维数组中的元素。


8.字符指针变量

8.1类比和联系

类比:整形指针: int*p 指向整型的指针,存放的是整型的地址。

char*p=“hello world”;//常量字符串,不可修改
字符数组,把字符串首字符的地址放进去,等价于:

char arr[]=“hello world”;
char*p=arr;

const char*p="hello world";//常量字符串,不可修改 
*p='q';//err
3=5;//err

8.2使用

使用:打印字符串,只需提供起始地址,就可以打印

char arr[]="abcdef";
char*p=arr;//数组名表示首元素的地址
printf("%S\n",arr);//
printf("%s\n",p);//
//注:打印字符串,只需提供起始地址,就可以打印

8.3辨析题

辨析题:

#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";//常量字符串不可修改
const char *str4 = "hello bit.";//内容相同的常量字符
//串没有必要保留两份,因为str3和str4指向同一个常量字符串,
//指针指向同一块内存
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相同。


9. 数组指针变量

9.1什么是数组指针变量?

类比和联系:

我们知道,指针数组,是一种数组,数组中存放的是指针(地址)。那么,指针变量是什么呢?
类比:
字符指针: charp 指向字符的指针,存放的是字符的地址
整形指针: int
p 指向整型的指针,存放的是整型的地址
数组指针:是一种指针变量,存放数组的地址
怎么取?:&arr 得到的是整个数组的地址

9.2 数组指针变量怎么初始化?

int arr[10]={0};
int (*p)[10]=&arr;//p是数组指针,p指向的是数组,10个元素,int类型

理解:p先和*结合,说明p是一个指针变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以p是⼀个指针,指向⼀个数组,叫 数组指针

说明:

  • int 是指向的数组的元素的类型
  • p是数组指针变量名
  • 10是p指向数组的元素个数

例:

char arr[5];//p是数组指针
char(*p)[5]=&arr;//char (*)[5]是数组指针类型
//去掉名字得到类型

10.函数指针变量

10.1什么是函数指针变量?

之前我们已经知道了,整型指针,字符指针,数组指针等;那么,函数指针变量又是什么呢?

函数指针是一种指针变量,存放函数的地址,从而通过地址来调用函数。

10.2函数指针变量的创建

tip: &函数名 和 函数名 都表示函数的地址。

int arr[8] = { 0 };
int(*pf)(int,int) = &Add;//pf是函数指针变量
int(*pa)[8] = &arr;//pa是数组指针变量

在这里插入图片描述

10.3函数指针变量的使用

通过函数指针调⽤指针指向的函数。

#include <stdio.h>
int Add(int x, int y)//加法函数
{
return x+y;
}
int main()
{
//创建和初始化
int(*pf3)(int, int) = Add;//pf3是函数指针变量
printf("%d\n", (*pf3)(2, 3));//使用
printf("%d\n", pf3(3, 5));//*可以去掉
return 0;
}

``

	int(*pf)(int, int) = &Add;
	去掉名字,就是类型 int (*)(int,int)
	int r1 = (*pf)(3, 7);
	int r2 = pf(4, 5);
	int r2 = Add(3, 7);

10.4两端代码分析

来源:《C陷阱和缺陷》

1.(*(void (*)())0)();

整体看,此代码是一次函数调用
从里向外看,void()()是函数指针类型
//1.将0强制转化成void(
)()型的函数指针
//2.调用0地址处放的这个函数

2.void (*signal(int , void(*)(int)))(int);

整体看,此代码是一次函数声明
//1.声明的函数名是signal
//2.signal有两个参数:int , void(*)(int)
//3.signal的返回类型void *(int)

10.5 typedef关键字

10.5.1 介绍:typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。

10.5.2 几种常见的重命名:

1.类型重命名
typedef unsigned int uint;
//将unsigned int 重命名为uint

2.整型指针类型重命名
typedef int* ptr_t;
//将 int* 重命名为 ptr_t

3.数组指针类型重命名(有点区别)
typedef int(*parr_t)[5]; 
//将int(*)[5]重命名为 parr_t
//新的类型名必须在*的右边(语法要求)

4.函数指针重命名(有点区别)
 typedef void(*pf_t)(int);
//将 void(*)(int) 类型重命名为 pf_t 
//新的类型名必须在*的右边(语法要求)

10.5.3场景模拟

1.整型重命名
typedef unsigned int unit;
int main()
{
	unsigned int num1;
	unit num2;
	return 0;
}

2.数组指针类型重命名
//typedef int(*)[5] parr_t;//err语法错误,要写在里面
typedef int(*parr_t)[5];
int main()
{
	int arr[5] = { 0 };
	int(*p)[5] = &arr;
	parr_t p2 = &arr;
}


10.5.4 typedef和define的区别

typedef int* ptr_t;
#define PTR_T int*;
int main()
{
	ptr_t p1;
	PTR_T p2;

	ptr_t p1, p2;//p1,p2是整型指针
	PTR_T p3, p4;//p3是int*型,p4是int型

	return 0;
}

11.函数指针数组

11.1什么是函数指针数组?

再次类比,我们不难想到:
什么是函数指针数组?——存放函数指针的数组

11.2函数指针数组的定义

int (*parr1[3])();//函数指针数组的定义
parr1先和[]结合,说明parr1是数组
数组存放的内容呢?是int (*)()类型的函数指针

去掉名字就是类型
int (*[3])();

12.转移表

函数指针数组的用途:转移表

12.1常规写法实现计算器

计算器的一般实现:


//计算器的一般实现
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()
{
	int x, y;
	int input = 1;
	int ret = 0;
	
	do
	{
		printf("*********************************\n");
		printf("  1:add                  2:sub   \n  ");
		printf("  3:mul                  4:div   \n  ");
		printf("  0:exit                         \n");
		printf("*********************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入操作数: ");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入操作数: ");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入操作数: ");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入操作数: ");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("推出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

12.2使用函数指针数组,转移表 实现计算器

是不是感觉很冗余呢?接下来看看使用了函数指针数组的写法吧!

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()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x,int y) = { 0,add,sub,mul,div };//转移表,将函数放在数组中
	do
	{
		printf("*********************************\n");
		printf("  1:add                  2:sub   \n  ");
		printf("  3:mul                  4:div   \n  ");
		printf("  0:exit                         \n");
		printf("*********************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入操作数:");
			scanf("%d%d", &x, &y);
			ret = (*p[input])(x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("选择错误\n");
		}
		

	} while (input);
	return 0;
}


总结

以上就是摆子今天给各位带来的有关指针的进阶的一些内容,还请一键三连加关注,感谢感谢!我们下期再见!!!

;