首页 > 其他分享 >【深入理解指针2】——顶级理解!

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

时间:2024-06-04 18:58:50浏览次数:22  
标签:arr 顶级 int 地址 理解 数组 printf 指针

大家好,今天小编给大家带来的是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;
}


总结

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

标签:arr,顶级,int,地址,理解,数组,printf,指针
From: https://blog.csdn.net/2302_81301620/article/details/139221320

相关文章

  • 头歌实践教学平台—C语言程序设计(指针)
    5-1学习-指针基本概念(一)通过指针变量间接访问变量#include<stdio.h>intmain(){intx,y;int*p;//定义指针变量Pp=&x;//使指针变量p指向一个变量x/*****请在以下填写通过p输入输出x值代码*****//**********Begin*********/scanf("%d",p......
  • 深入理解指针(一)
    目录1.内存和地址1.1内存1.2编址2.指针变量和地址 2.1取地址操作符(&)2.2指针变量和解引⽤操作符(*)2.2.1指针变量2.2.2如何拆解指针类型2.2.3解引用操作符2.3指针变量的大小3.指针变量类型的意义3.1指针的解引用3.2指针加减整数3.3void*指针4.const修饰......
  • 如何从浅入深理解transformer?
    前言在人工智能的浩瀚海洋中,大模型目前无疑是其中一颗璀璨的明星。从简单的图像识别到复杂的自然语言处理,大模型在各个领域都取得了令人瞩目的成就。而在这其中,Transformer模型更是成为大模型技术的核心。一、大模型的行业发展现状如何?大模型,即大型语言模型,是指具有数十......
  • 深入理解Python的包管理器:pip
    深入理解Python的包管理器:pip引言Python作为一门流行的编程语言,拥有强大的生态系统,其中pip扮演着至关重要的角色。pip是Python的包管理工具,它允许用户安装、升级和管理Python包。本专栏旨在帮助读者深入了解pip的各个方面,从基础使用到高级技巧,再到安全特性和未来展望。第......
  • 为什么会收到此警告,如何解决 "此版本只能理解 SDK XML 2 以下版本,但 SDK XML...&quot;
    我刚刚更新了我当前的Android应用程序,使其使用java11、构建工具32.0.0和下面是我使用的AndroidStudio的详细信息AndroidStudioBumblebee|2021.1.1Beta5构建号:AI-211.7628.21.2111.7956428,构建于2021年11月30日运行时版本:11.0.11+0-b60-7590822x86_......
  • 如何理解mysql小表驱动大表
    就像自行车爬坡一样么,小齿轮驱动大齿轮MySQL采用“小表驱动大表”的策略,确实在某种程度上类似于自行车爬坡时小齿轮驱动大齿轮的原理,目的都是为了更高效地利用有限的资源达到目标。在数据库查询的上下文中,这个策略背后的逻辑可以这样理解:1.**效率最大化**:小表数据量少,遍历小......
  • C语言之指针进阶(5),sizeof和strlen的数组计算以及指针运算笔试难题详解
    目录前言一、sizeof和strlen的区分比较二、sizeof,strlen与数组的计算三、指针运算,笔试难题解析总结前言    本文作为指针进阶的最后一篇文章,给大家带来了丰富的例题,这其中包括区分比较sizeof和strlen计算各种花样的数组指针表达式,如果你能答对所有的关......
  • [22] 虚幻引擎知识拓展 智能指针、JSON解析、插件
    Day1大纲虚幻智能指针  共享指针  共享引用JSON解析对象型、数组型、解析Json文件、书写Json、读取场景Actor保存到Json任务:封装高德地图天气系统插件给蓝图使用内容虚幻智能指针创建共享指针////创建共享指针//TSharedPtr<FMyClass>pMyClass=MakeShareab......
  • 什么是二级指针,为什么要有二级指针.
    什么是二级指针?一个二级指针是一个指向指针的指针。简单来说,如果int*p是一个指向整数的指针,那么int**pp就是一个指向p的指针。inta=10;int*p=&a;//p是一个指向整数a的指针int**pp=&p;//pp是一个指向p的指针二级指针的作用众所周知,要......
  • 设计模式理解
    1.简单工厂模式:就是在一个单例里面通过传入的值创建不同的对象classModuleFactory{publicstaticModuleCreateModuel(ModuleTypetype){switch(type){caseModuleType.A:retur......