指针的进阶——(1)

news2024/11/18 11:39:59

本次讲解重点:

1、字符指针

2、数组指针

3、指针数组

4、数组传参和指针传参

5、函数指针

关于指针这个知识点的主题,我们在前面已经初级阶段已经对指针有了大致的理解和应用了。我们知道了指针的概念:

1、指针就是地址,但口语中说的指针通常指的是指针变量,指针变量用来存放地址,地址唯一标识一块内存空间。

2、指针的大小是固定的4/8字节(32位平台/64位平台)。

3、指针是有类型的,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。

4、指针的运算。

5、二级指针:存放一级指针变量的地址的变量。

        理解:指针变量前的第一个*与变量结合表示它是指针,再往前面所有东西表示这个指针所指向对象的类型。

关于初阶指针有什么不太了解,大家可以点击下方链接学习一遍之后,再来学习进阶。

S6---初阶指针_wangjiushun的博客-CSDN博客

接下来,我们继续探讨指针的高级主题。


1.  字符指针

在指针的类型中我们知道有一种指针类型为字符指针:char*

一般我们使用:让一个字符指针指向一个字符变量

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

	return 0;
}

还有一种使用方式:让一个字符指针指向一个常量字符串的首字符地址

#include<stdio.h>

int main()
{
	const char* pstr = "abcdef";
	printf("%s\n", pstr);

	return 0;
}

代码const char *pstr="abcdef";

很容易让我们误以为是把常量字符串存储到字符指针pstr里了,但是想一想一个字符指针怎么可能放得下7个字节的字符串呢?所以其本质是把字符串abcdef首字符的地址放到了pstr中。

图解:

知识点总结:

1.  常量字符串:

        ①常量字符串不能被修改:用const修饰(如果修改,程序崩溃);

        ②常量字符串出现在表达式中时,这个常量字符串的值是首字符的地址。

2.  const修饰的常变量:变量pstr不能被修改,但是pstr本质上还是一个变量,所以叫常变量

3.  %s-打印字符串,直到遇到'\0'才停止。 

有这么一道面试题:

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

大家猜猜运行的结果。

运行结果:

为什么会这样了?

知识点:

总结:

(常量字符串,不能被修改)C/C++会把常量字符串存储到单独的一个内存区域,当有几个指针,指向同一个常量字符串的时候,它们实际会指向同一块内存。
但是用相同的常量字符串去初始化不同的数组的时候,就会开辟不同的内存块。

一个变量对应着一个唯一的空间。

图解:

所以str1和str2不同,str3和str4相同。

2.  指针数组

在初阶指针里我们已经学习一次指针数组,指针数组是一个存放指针的数组。

这里,我们在回顾一下:

整形数组-存放整形的数组;

字符数组-存放字符的数组

指针数组-存放指针(地址)的数组

指针数组的使用:使用一维数组模拟二维数组

代码1:存放字符指针的数组

#include<stdio.h>

int main()
{
	//存放字符指针的数组
	const char* arr[4] = { "abcd","hello","hehe","wang" };
	//打印
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%s\n", arr[i]);
	}

	return 0;
}

 图解:

代码2:存放整形指针的数组

#include<stdio.h>

int main()
{
	int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 2,3,4,5 };
	int arr3[4] = { 3,4,5,6 };
	//存放整形指针的数组
	int* arr[3] = { arr1,arr2,arr3 };
	//打印
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			printf("%d ", *(arr[i] + j));//等价于arr[i][j]
		}

		printf("\n");
	}

	return 0;
}

图解:

总结:这就是和二维数组一样的,只不过我们是使用指针数组来模拟的。

 3.  数组指针

3.1  数组指针的定义

数组指针是指针?还是数组?

答案是:指针。

我们已经熟悉:

整形指针-存放整形地址的指针-指向整形的指针 int*

字符指针-存放字符(字符串)地址的指针-指向字符(字符串)的指针 char*

那数组指针是:存放数组地址的指针-指向数组的指针

代码实例:

int main()
{
	//整形指针
	int a = 10;
	int* pi = &a;

	//字符指针
	char ch = 'w';
	char* pc = &ch;

	//数组指针
	int arr[10] = { 0 };
	//int* pa[10]是这样的吗,但是这不是指针数组,
	//pa先与[]结合了就是数组,
	int(*pa)[10] = &arr;

	return 0;
}

对数组指针的理解:pa先和*结合,说明pa是一个指针变量,然后指向的是一个大小为10个整形的数组。(即pa是一个指针,指向一个数组的指针,叫做数组指针)

数组指针后面的[]表示所指向的数组有几个元素

知识点:

注意:[]的优先级要高于*的,所以必须加上()来保证pa先和*结合。

指针变量前的第一个*与变量结合表示它是指针,再往前面所有的东西表示这个指针所指向对象的类型。

接下来,我们深入了解数组名和&数组名

3.2  &数组名VS数组名

代码1:打印&数组名和数组名的地址观察

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//打印数组名地址
	printf(" arr-->%p\n", arr);
	//打印&数组名地址
	printf("&arr-->%p\n", &arr);

	return 0;
}

 运行结果:

运行之后我们发现:&数组名和数组名的地址是一样的。

难道两个是一样的吗?

代码2:&数组名和数组名+1

//代码2:
//数组名-数组首元素的地址
// &数组名-是数组的地址
// 数组首元素的地址和数组的地址从值的角度来看是一样的,但是意义不一样
// 指针的类型决定了指针+-整数的步长,指针解引用操作的时候的权限
//

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	
	printf(" arr---->%p\n", arr);//指针的类型:int*
	printf(" arr+1-->%p\n", arr + 1);//+1,向后跳4个字节
	
	printf("&arr---->%p\n", &arr);//指针的类型:int(*)[10]
	printf("&arr+1-->%p\n", &arr + 1);//+1,向后跳40个字节

	return 0;
}

 运行结果:

图解:

根据上面的代码,我们发现:

数组名-数组首元素的地址

&数组名-是数组的地址 

 数组首元素的地址和数组的地址从值的角度来看是一样的,但是意义不一样。

知识点:

1.  指针的类型决定了指针+-整数的步长,指针解引用操作的时候的权限。

2.  存储一个数组所需的内存字节数:

        ①一维数组:总字节数=sizeof(类型)*元素个数

        ②二维数组:总字节数=sizeof(类型)*行数*列数

3、对二维数组数组名的加深理解:数组名就是二维数组首元素的地址

        对于二维数组来说,它的第一行就是它的第一个元素,第二行就是它的第二个元素……即一行是一个元素,二维数组是一个一维数组的数组。

//代码:二维数组的数组名
//二维数组的数组名是首元素地址-->第一行的地址
// 对于二维数组一行是一个元素

#include<stdio.h>

int main()
{
	int arr[3][4] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);//跳过了16个字节

	return 0;
}

运行结果:

上机运行实践,也正是如此:arr+1跳40个字节,刚好是一行。

注意区分:二维数组的第一行第一个元素地址是&arr[0][0],

3.3  数组指针的使用

代码1:一维数组数组指针的使用,但是我们很少这样写代码

//代码1:一维数组数组指针的使用,但是我们很少这样写代码

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//数组指针
	int(*p)[10] = &arr;
	//打印数组
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", (*p)[i]);//*p[i]这种写法的话,p先与[i]结合再解引用
	}
	return 0;
}

 代码2:二维数组数组指针的使用

#include<stdio.h>

void print1(int arr[3][4], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

void print2(int(*p)[4], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));//等价于(*(p+i))[j],也等价p[i][j]
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
	//调用函数打印数组
	print1(arr, 3, 4);
	print2(arr, 3, 4);

	return 0;
}

图解:

 学了指针数组和数组指针我们一起回顾并看看下面代码的意思:

int arr[5];

int *parr1[10];

int (*parr2)[10];

int (*parr3[10])[5];

 4.  数组参数、指针参数

我们写代码的时候难免要把数组或者指针传给函数,那么函数的参数该如何设计呢?

4.1  一维数组传参

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?

{}
void test2(int** arr)//ok?
{}
int main()
{
    int arr[10] = { 0 };
    int* arr2[20] = { 0 };
    test(arr);
    test2(arr2);
}

 4.2  二维数组传参

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  一级指针传参

思考:当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

答案是:①一级指针变量;②一维数组数组名;③变量地址。

比如:

void test1(int *p)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?

void test1(int* p)
{}

void test2(char* p)
{}

int main()
{
	//test1函数能接收什么参数?
	int a = 10;
	int* pi = &a;
	int arr[5] = { 0 };
	test1(pi);//一级指针变量
	test1(arr);//一位数组数组名
	test1(&a);//变量地址

	//test2函数能接收什么参数?
	char ch = 'w';
	char* pc = &ch;
	char arr1[6] = { '\0' };
	test2(pc);
	test2(arr1);
	test2(&ch);

	return 0;
}

4.4  二级指针传参

思考:当函数的参数为二级指针的时候,函数能接收什么参数?

答案是:①二级指针变量;②指针数组数组名;③一级指针变量地址。

代码实例:

void test(char** p)
{ }
int main()
{
	char c = 'b';
	char* pc = &c;
	char** ppc = &pc;
	char* arr[10];
	test(&pc);//一级指针变量地址
	test(ppc);//二级指针变量
	test(arr);//指针数组数组名

	return 0;
}

5、函数指针

我们先打印一下函数的地址

#include<stdio.h>

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

int main()
{
	//打印函数地址
	printf("%p\n", &Add);
	printf("%p\n", Add);

	return 0;
}

运行结果:

从结果我们发现:取函数名和函数名,打印的地址一样。

结论:①取函数名和函数名,都是函数的地址。

那函数的地址是怎么保存的呢?

看下面代码:

#include<stdio.h>

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

int main()
{
	//数组指针-指向数组的指针
	int arr[10] = { 0 };
	int(*pa)[10] = &arr;
	//函数指针-指向函数的指针
	int (*pf)(int, int) = &Add;//pf 是一个存放函数地址的指针变量-函数指针
	int ret1 = pf(2, 3);//pf是函数地址,Add也是函数地址,所以不用解引用也可以
	int ret2 = (*pf)(2, 3);//写上*也可以,符合语法,但是需注意带()
	printf("%d %d\n", ret1, ret2);

	return 0;
}

类比数组指针,函数指针也是如此要注意带()保证*和变量pf先结合。

使用函数指针找到所指向的函数:

①直接写函数指针:因为函数指针变量存储的就是函数的地址,所以在使用的时候可以不解引用。

②函数指针解引用:解引用其实没必要,这样写就是方便理解。因为pf是指针,指针要找到他所指向的对象(对象函数名就是函数的地址),解引用更容易理解,符合语法的理解。(注意:优先级缘故,如果要解引用,一定要使用()把*和函数指针变量括起来。)

现在我们来看两段有趣的代码:

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

代码1的解读:

int main()
{
	( *( void(*)() ) 0 )();
	//分析:
	// ①void(*)()-->函数指针类型,这个函数是无参的
	// ②()里面放类型-->强制类型转换
	// ③( void(*)() ) 0 -->把0强转成这种函数指针类型,
	// 就是0被当成了函数的地址
	// ④(*指针变量)-->解引用操作,找到0地址的函数
	// ⑤最后的()-->调用函数不用传参(因为强转的0函数类型是无参的)
	//
	//总结:该代码是一次函数调用,调用0地址处的一个函数
	//		首先代码中将0强转为类型为( void(*)() )的函数指针
	//      然后去调用0地址处的函数
	return 0;
}

破题点:从0出发,再开始分析。

代码2的解读:

//函数返回类型是函数指针

int main()
{
	void (*signal(int, void(*)(int))) (int);
	//分析:①signal没有与*结合,这不是指针
	//		②signal后有括号()-->signal是函数名
	//		③函数名括号()里-->函数参数
	//		第一个参数是int类型,第二个参数是函数指针类型
	//		④语句最后有;没有函数体-->函数是声明
	//		⑤函数的声明-->形式:函数返回类型 函数名 (形参列表);
	//		那剩下的void(*)(int)就是函数返回类型了
	//		
	//总结:该代码是一次函数的声明
	//		声明的函数名字叫signal
	//		signal函数的参数有2个,第一个是int类型,第二个是函数指针类型,
	//		该函数指针能够指向的那个函数的参数是int,返回类型是void。
	//		signal函数的返回类型是一个函数指针,
	//		该函数指针能够指向的那个函数的参数是int,返回类型是void。
	//我们能不能这么写?
	//void(*)(int) signal(int,void(*)(int));
	//答案是不能这么写,帮助理解可以这么写,但是语法上是错的
	//函数的返回类型是函数指针是,只能把函数名 (形参列表)移到*的后面。

	//优化:类型简化--->typedef
	//函数指针的重命名
	typedef void(*pf_t)(int);
	pf_t signal(int, pf_t);

	return 0;
}

破题点:从signal出发,再开始分析。

知识点:

1、函数返回类型是指针函数时的写法:当函数的返回类型是函数指针时,只能把函数名 (参数列表)移到返回类型这个函数指针的*的后面。

2、指针类型简化(重命名)--->typedef

        指针类型重命名,别名写在*的后面并且还要注意操作符的优先级(即*先与别名结合)

指针进阶的主题并未结束,在下一次文章终结。

有什么不足希望大家指出,我会更加努力写出更好的文章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP基础(3)

PHP基础表单提交文件处理PHP连接数据库异常抛出表单提交 PHP通过全局变量 $_GET和 $_POST来收集表单数据。 接下来改用post方式进行提交&#xff0c;再次查看是否隐藏了提交的内容&#xff1a; 发现提交的信息已经不在链接之中进行显示了。 GET与POST区别在于一个会在连接…

番外9:使用ADS对射频功率放大器进行非线性测试1(以IMD3测试为例)

番外9&#xff1a;使用ADS对射频功率放大器进行非线性测试1&#xff08;以IMD3测试为例&#xff09; 一般可以有多种方式对射频功率放大器的非线性性能进行测试&#xff0c;包括IMD3、ACPR、ACLR等等&#xff0c;其中IMD3的实际测试较为简单方便不需要太多的仪器。那么在ADS中…

VUE的生命周期- VUE2.x

1.生命周期有哪些VUE2.x 自带八个&#xff1a;beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed2.一旦进入组件会执行哪些生命周期beforeCreate,created,beforeMount,mountedbeforeCreate,没有DOM($el),没有data&#xff0c;不能拿到方…

飞桨-鹏城云脑发行版亮相第四届启智开发者大会,软硬一体化助力科研

2月24日&#xff0c;主题为“算网筑基、开源启智、AI赋能”的第四届OpenI/O启智开发者大会在深圳开幕&#xff0c;大会由科技部指导、鹏城实验室与新⼀代人工智能产业技术创新战略联盟&#xff08;AITISA&#xff09;主办&#xff0c;科技部高新司副司长梅建平&#xff0c;中国…

Simple RNN、LSTM、GRU序列模型原理

一。循环神经网络RNN 用于处理序列数据的神经网络就叫循环神经网络。序列数据说直白点就是随时间变化的数据&#xff0c;循环神经网络它能够根据这种数据推出下文结果。RNN是通过嵌含前一时刻的状态信息实行训练的。 RNN神经网络有3个变种&#xff0c;分别为Simple RNN、LSTM、…

ESP-C3入门13. SoftAP模式

ESP-C3入门13. SoftAP模式一、 ESP32-C3 WIFI的工作模式二、SoftAP配置1. wifi_config_t 结构体2. wifi_event_handler 事件(1) esp_event_handler_instance_register 注册事件(2) system_event_sta_connected_t 结构体3. 关闭SoftAP三、示例1. main.c2. wifi_ap.h3. wifi_ap.…

自动化构建部署devops(CICD)--敏捷开发

一。gitlab结合jenkins自动化项目构建部署 代替早期的手动部署服务&#xff0c;写文档&#xff0c;java-jar启动啦。麻烦还容易出错。 二。DevOps 三。部署流水线 四&#xff0c;页面工具&#xff08;类似于ones&#xff09; 1&#xff0c;开发组长在页面添加项目成员&#…

integrationobjects点com all OPC Crack

Integration Objects 是世界领先的系统集成商和解决方案提供商&#xff0c;专门从事运营和制造智能、高级分析、异常事件的预防性检测、在线诊断和根本原因分析、OPC 连接、工厂自动化、知识管理解决方案、网络安全和企业电力和公用事业以及全球流程和制造行业的集成。 OPC UA …

storybook使用info插件报错

报错内容: RangeErrorMaximum call stack size exceededCall StackprettyPrintvendors-node_modules_pmmmwh_react-refresh-webpack-plugin_lib_runtime_RefreshUtils_js-node_mod-4ff2dd.iframe.bundle.js:160:27undefinedvendors-node_modules_pmmmwh_react-refresh-webpack-…

结构方程模型全流程

案例与数据 某研究者想要研究关于教师懈怠感的课题&#xff0c;教师懈怠感是指教师在教育情境的要求下&#xff0c;由于无法有效应对工作压力与挫折而产生的情绪低落、态度消极状态&#xff0c;这种状态甚至会引发心理、生理的困扰&#xff0c;终至对教育工作产生厌倦&#xf…

英语二-议论文写作词汇、话题、模板、范文参考

1. 词汇多样性 1. 表示因果关系 2. 表示转斩关系 3. 表示顺序关系 4. 表示递进关系 5. 表示对比关系 6. 表示总结关系 7. 连接论据的词 2. 高频考试话题 1. 有益身心的短语 2. 提高能力的短语 3. 写作模板 支持原创作文&#xff0c;如果不会&#xff0c;请牢记模板。 如果嫌…

Android源码分析 —— Activity栈管理(基于Android8)

0. 写在前面 本文基于 Android8.0源码&#xff0c;和Android9.0大同小异&#xff0c;但和Android10.0差别非常大&#xff01;新版改用ATM来管理Activity的启动&#xff0c;Activity的生命周期也通过XXXItem来管理。由于我分析的Activity启动流程就是基于Android8/9的&#xff…

FFmpeg/OpenCV 实现全屏斜体水印

实现思路 &#x1f914;​ 基于ffmpeg&#xff0c;画布的方式&#xff0c;创建画布 -> 水印 -> 旋转 -> 抠图 -> 叠加到图像上基于ffmpeg&#xff0c;旋转图片的方式&#xff0c;填充 -> 水印 -> 顺时针旋转 -> 逆时针旋转 -> 截图基于opencv&#xff…

Pag渲染过程 -- 背景知识

什么是渲染 渲染是图形程序的核心&#xff0c;无论是我们在电子设备上看到的任何图形或者文字都是利用计算机图形渲染技术给我们呈现出来的结果。在计算机里一开始是直接利用CPU往显示器的FrameBuffer内写入数据即可把图形展示到显示器上&#xff0c;但是随着用户的需求和技术…

别担心,ChatGPT还抢不动你的饭碗

前言&#xff1a; “你是谁&#xff1f;” “我是一个由OpenAI训练的大型语言模型。我旨在帮助人们解答问题和提供信息。由于我是一个计算机程序&#xff0c;所以不会感知或者思考&#xff0c;只能通过已有的数据来回答问题。如果您有任何问题&#xff0c;请随时告诉我。” ---…

rabbitmq部署安装(mac)

安装&#xff1a; // 默认已经下载了homebrew&#xff0c;更新brew资源 brew update // 执行安装 brew install rabbitmq 配置&#xff1a; // 切换到MQ目录,注意你的安装版本可能不是3.9.5&#xff08;我的版本&#xff0c;当前最新版 cd /usr/local/Cellar/rabbitmq/3.…

如果不是互联网人,谁会找到这些神器?

一、上线啦 你肯定该问了&#xff0c;这个是什么鬼东西。它本来是一个创建自己网站的网站。 现在使用它可以创建自己的小程序&#xff0c;又不是有点小厉害了。 而且功能强大&#xff0c;还支持微信支付&#xff0c;分销&#xff0c;优惠券&#xff0c;营销等多种功能。 还有多…

DS期末复习卷(九)

一、选择题(30分) 1&#xff0e;下列程序段的时间复杂度为&#xff08;A &#xff09;。 for(i0&#xff1b; i<m&#xff1b; i) for(j0&#xff1b; j<t&#xff1b; j) c[i][j]0&#xff1b; for(i0&#xff1b; i<m&#xff1b; i) for(j0&#xff1b; j<t&am…

LeetCode:二叉树的最大深度104;559. N 叉树的最大深度

104. 二叉树的最大深度 给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 示例&#xff1a; 给定二叉树 [3,9,20,null,null,15,7]&#xff0c; 3/ \9 20/ \15 7 返回它的…

看似平平无奇的00后,居然一跃上岸字节,表示真的卷不过......

又到了一年一度的求职旺季金&#xff01;三&#xff01;银&#xff01;四&#xff01;在找工作的时候都必须要经历面试这个环节。在这里我想分享一下自己上岸字节的面试经验&#xff0c;过程还挺曲折的&#xff0c;但是还好成功上岸了。大家可以参考一下&#xff01; 0821测评 …