指针---你真的会使用指针吗?

       

        指针作为C语言中的一个部分,可以说指针是C语言的核心,那么它的难度肯定是不言而喻的,总是能把人给绕得找不到方向。

        今天我就好好的说一说指针这个东西。

        1、何为指针?

        指针是C语言中用来存放地址的一个变量类型。我们可以将指针看作独特的一种变量,只用来存放地址。根据指针存放不同类型变量的地址,也可以将指针分为:int型指针,char型指针,等等。由于指针都是存放地址,而所有类型的地址又没有本质上的差异,所以无论任何类型的指针大小都是同样的。同为4个字节或者8个字节

        指针是 C 语言中的一种数据类型,可以用来存储内存地址。每个变量或对象都占据着计算机内存中的某个地址,指针变量可以保存这些地址,并允许对这些变量或对象进行间接访问

        

        2、一级指针

        想要了解指针,咱就得从最基础的一级指针开始。

        1、指针的声明

        在 C 语言中,指针变量声明时需要指定所指向的数据类型,以便编译器能够在使用指针时自动对其进行类型转换和内存管理等操作。

        例如,以下为一个整型指针变量的定义:

int a = 10;
int* pa = &a;

        *即代表这个pa是一个int型指针,指向的是a的地址。

        注意:声明指针之后,必须对其进行初始化!!!(否则它就是一个空指针)不能直接使用它!!!对空指针进行解引用操作系统可能会崩溃!!!后果非常严重!

        2、通过指针对变量或对象进行访问

int a = 10;
int* pa = &a;
*pa = 20;

        这里我们解引用pa,然后将pa的值赋值为20。(对pa解引用后,就相当于a。因为pa是a的地址,解引用a的地址,那就是找到了a)

        这里引出一个问题:

int a = 10, b = 20;
int* pa = &a, * pb = &b;
pa = pb;

        此时a的值是20吗???相信大部分对指针不了解的人都会认为此时a已经变成了20,因为将b的地址pb赋值给了pa。但是结果是:a仍然是10。首先:pa存的确实是a的地址,然后我们确实是对pa存放的地址进行了改变。但是!a是a,pa是pa,在这里改变pa,对a有影响吗?没有!!!!

a的地址并没有发生改变!!!所以a仍然是10。但是如果我们解引用pa后,会发现此时*pa的值是20,因为我们改变的是pa存放的地址。

        既然讲到这,那我们就得提出以下指针的运算了。指针没有复杂的运算,不能指针间进行运算。

        3、指针的运算

        赋值运算:相同类型的指针才能赋值。  算术运算:+,-,++,--  (这里的+和-,指的是+-多少,即对地址的运算,指向前面的地址或者后面的地址。并不是说简单的pa+pb,其实指针间是可以进行运算的,我们后面再谈论关系运算 :==,!=, <=,>=,<,>。

int a[10];
int* p1 = &a[2];
int* p2 = &a[5];
printf("%d", p2 - p1);//answer == 3

        answer == 3,why?        

        这里先不说,后面再说。

         1、算术运算

        a = *p++ ---->结果为:a = *p ,p++。

        a = (*p)++ --->结果为 : a = *p,*p++。

        2、关系运算

    若p1和p2指向同一数组,则  p1<p2    表示p1指的元素在前 。 p1>p2    表示p1指的元素在后。  p1==p2   表示p1与p2指向同一元素 。 若p1与p2不指向同一数组,则比较无意义。

        4、指针与函数

        先来个最简单的交换函数Swap。

        1、交换函数
void Swap(int x, int y)
{
	int c = x;
	x = y;
	y = c;
}
int main()
{
	int a = 10, b = 20;
	printf(" a = %d,b = %d", a, b);
	Swap(a, b);
	printf(" a = %d,b = %d", a, b);
	return 0;
}

        这个函数会起到交换a,b的值的作用吗???答案是不会!这个函数我们在传参的时候,只是将a,b的值传过去了,但没有影响地址。在Swap函数中创建临时变量来交换x,y的值,但是原来a,b的地址并没有改变。而地址才是决定一个变量的值。故a,b没有改变。

        所以应该用地址即指针来修改二者的值,才能改变a,b的地址,进而改变a,b的值。

void Swap(int* x, int* y)
{
	int c = *x;
	*x = *y;
	*y = c;
}
int main()
{
	int a = 10, b = 20;
	printf(" a = %d,b = %d", a, b);
	Swap(&a, &b);
	printf(" a = %d,b = %d", a, b);
	return 0;
}

        因此:函数参数为指针的时候,可以改变实参的值

        3、指针与数组名的关系

        1、数组名

        对于数组名,在大部分情况下数组名都代表首元素的地址。一维数组的数组名代表第一个元素的地址,二维数组的数组名代表第一行元素的地址(即二维数组的数组名代表的是一个一维数组的地址)。那么数组名就可以看作是一个指针。

        2、指针与数组访问元素的等价关系

        既然说了数组名就是指针,那么用数组名可以访问元素,用指针也可以访问元素了。

int arr[5] = { 1,2,3,4,5 };
int* parr = arr;
printf("%d ", *(parr + 1)); // 2
printf("%d ", *(arr + 1)); // 2

        由此可以看出来,对于一维数组,*(parr+i) 等价于arr[i]。同理对于二维数组,不同的是,对于二维数组需要用二级指针*(*(parr+i)+j)就等价于arr[i][j]。为什么呢?对于二维数组:*(parr+i)得到的是第i-1行的数组,而数组名又是一个首元素的地址,那么*(parr+i)得到的就是第i-1行的首元素地址,至此+j再解引用就和一维数组一样了。但是如果传参二维数组名,并不是以二级指针接收。二维数组名代表首行数组的地址,接收数组的地址,需要用数组指针接收。下面再细讲。

        3、字符数组和字符指针的区别

        1、字符数组
char str1[10] = "abcdef";
char str2[10];
str2 = "abcd";//错误!

        虽然数组名代表首元素地址,“abcd”也代表首元素a的地址,但是这样赋值给数组是错误的!我们只能通过拷贝来赋值给数组

char str2[10];
strcpy(str2, "abcd");

        对于此类赋值,我们是可以通过覆盖等操作来修改数组值的。下面将介绍另一种赋值法,这种赋值法是不能修改数组的值的。

            2、字符指针           
	char* str;
	str = "abcde";

        通过指针来给字符数组赋值,这样赋值的时候,“abcde”叫做字符串常量,字符串常量是不能修改的。则此时str是不能修改它的内容的。 

         4、数组指针

        1、数组指针的定义     

        数组指针:就是一个指向数组的指针,这里的指针代表的是整个数组的地址,并非数组首元素的地址。注意区分。

        2、数组指针的表示

int arr[3] = { 1,2,3 };
int(*p)[3] = &arr;

        我们来解析一下数组指针:p代表指针名称,*p代表p是一个指针,[ 10 ]代表这个指针指向一个拥有十个元素的数组,int代表这个数组是int类型的。

        注意: 这里需要用括号将*p结合起来,因为[ ]的优先级比*的优先级高。如果不用括号结合的话,这其实是一个指针数组。

        接下来我们将用代码来分析数组指针和数组名的差别:

	int arr[3] = { 1,2,3 };
	int(*p)[3] = &arr;
	printf("p = %p\n", p);
	printf("arr = %p\n", arr);
	printf("p + 1 = %p\n", p + 1);
	printf("arr + 1 = %p\n", arr + 1);

        可知:p和arr的地址都是一样的,都是指向数组的起始位置的地址。

        但是p+1和arr+1的地址就不一样了:发现:p+1比p的地址增加了12个字节,即三个地址的大小。而arr+1比arr只增加了4个字节,即一个地址的大小。

        由此可以看出:数组指针代表整个数组的地址,数组名只代表数组首元素的地址

        3、数组指针与二维数组传参

        通过上面我们知道,二维数组名代表首元素地址,这里的首元素地址,代表二维数组的第一行元素的地址,即看作一个一维数组的地址。既然如此,一维数组名传参我们需要用一级指针来接收地址。那么二维数组名传参,我们是否需要用数组指针来接收一维数组的地址呢?答案是:是的,要用数组指针

void fun(int(*arr)[3], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);//或者*(*(arr+i)+j)
		}
		printf("\n");
	}
}
int main()
{
	int arr[2][3] = { {1,2,3},{4,5,6} };
	fun(arr, 2, 3);
	return 0;
}

        注意:这里新参的[ ]里边,必须是那个一维数组的元素个数,否则打印输出的时候就不会正常打印。(如果小于元素个数,它会打印你第二行的元素,因为二维数组在内存中其实是线性连续的。如果大于数组个数,它会乱输出空间中数组末尾后边的数据) 但是:对于二维的字符数组,可能每一个一维数组的字符数是不同的,那么这里使用数组指针接收,[ ]里面就不知道写多少。那么对于这种情况,我们需要用到二级指针,但是这里的二维数组我们需要动态开辟,不能直接自己简单定义二维数组。

        5、二级指针 

         二级指针,显然就是接收二维数组名的指针,但是这里并不是简单的直接传入二维数组名,这里二维数组名需要动态开辟。

        1、二级指针与动态开辟字符二维数组

        一般使用二级指针是为了来应对二维字符数组的每个一维数组元素个数不同的情况。普通情况下,二维数组还是用数组指针来接收。

void fun(char** str, int row)
{
	for (int i = 0; i < row; i++)
	{
		printf("%s\n", str[i]);
	}
}
int main()
{
	char** str = (char**)malloc(sizeof(char*) * 3);//开辟一个二维字符数组,一共有三个字符串
	for (int i = 0; i < 3; i++)
	{
		str[i] = (char*)malloc(sizeof(char) * 10);//给每个字符串开辟空间
		scanf("%s", str[i]);//输入
	}
	fun(str, 3);
	return 0;
}

        6、其他指针类型

        1、函数指针 

        函数指针:顾名思义就是存放函数地址的指针咯。那么问题来了:函数也有地址吗? 函数当然有地址,不然在调用函数的时候,在内存中哪里去寻找这个函数呢?

        1、函数名

        首先来说一下函数名(地址):

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);
	return 0;
}

        由此我们可以看出,对于函数名,无论是对它取地址,还是直接是函数名,都代表的这个函数的地址。

         2、函数指针的定义
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int* p1 = Add;
	printf("%p\n",p1);
	int (*p2)(int, int) = Add;
	printf("%p\n", p2);
	return 0;
}

        对于如上两种定义,虽然二者打印结果相同,但是第一种定义是错的。在好的编译器下,会发出警告:“初始化”:“int *”与“int (__cdecl *)(int,int)”的间接级别不同。这个警告就说明这种定义函数指针的方法其实是错的,二者并不相同。

        所以正确的函数指针定义方法应该是第二种

	int (*p2)(int, int) = Add;
        3、解析函数指针
	int (*p2)(int, int) = Add;

        p2:指针名称;*p2代表是一个指针类型;前面的int:代表函数的返回值是int;(int,int):代表这个函数的两个参数是int和int类型的。

        4、调用函数指针 
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*p2)(int, int) = Add;
	int ret = p2(1, 2);
	printf("%d", ret);
	return 0;
}

         我们可以直接运用函数指针来执行该函数的功能(虽然多此一举哈哈哈哈哈)。

        那有人会问了,为什么这里p2没有解引用就可以使用?前面讲了,函数名取地址和不取地址的结果都是一样的,那么解引用和不解引用结果也是一样的

        2、其他

        对于指针,我们其实可以引出很多不同种类的指针,越往后,指针越头大,我们只需要掌握这些常用的就行了。

        6、总结 

        写了这么久,都给我写累了,但是指针的奥妙远不止于此,还有很多细节需要自己去琢磨。哎,路漫漫其修远兮~ 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[电子榨菜] js中的闭包closure

0.写在前面: 下学期就打算去实习了,这段时间要密集接收考试和面试的捶打,计网和软工就没有办法为大家继续贡献开源内容了,明年九月份之前的更新内容将会以前端,人工智能,和工程设计为基础, 很抱歉啦,不过我还是希望我这一年来的努力可以帮到一些人.虽然自己这一年过的浑浑噩噩…

more的详细用法

概要&#xff1a; Linux中more的功能是分页显示文件内容 空格键显示下一屏(页)&#xff0c;回车键Enter显示下一行&#xff0c;q键退出 本篇所用系统是Ubuntu22.04 一、more filename more后面跟的是文件名&#xff0c;分页显示文件内容 二、more < filename more从…

C语言—每日选择题—Day59

指针相关博客 打响指针的第一枪&#xff1a;指针家族-CSDN博客 深入理解&#xff1a;指针变量的解引用 与 加法运算-CSDN博客 第一题 1. 以下关于 typedef 正确的描述是&#xff08;&#xff09;【多选】 A&#xff1a;用typedef可以定义各种类型别名&#xff0c;但不能定义变量…

收藏!可能是最完整的全球AI大模型名单

ChatGPT的出现在全球掀起了AI大模型的浪潮。 区块链是生产关系的重构&#xff0c;元宇宙是虚拟场景的重构&#xff0c;而互联网则解决了信息流通和生产效率的问题&#xff0c;但这些都是在生产关系范畴内&#xff0c;而ChatGPT的出现则大幅度提升了生产力&#xff0c;让人类可…

openGauss学习笔记-168 openGauss 数据库运维-备份与恢复-导入数据-使用gs_restore命令导入数据

文章目录 openGauss学习笔记-168 openGauss 数据库运维-备份与恢复-导入数据-使用gs_restore命令导入数据168.1 操作场景168.2 操作步骤168.3 示例 openGauss学习笔记-168 openGauss 数据库运维-备份与恢复-导入数据-使用gs_restore命令导入数据 168.1 操作场景 gs_restore是…

Windows中安装nvm进行Node版本控制

1.nvm介绍 nvm英文全程也叫node.js version management&#xff0c;是一个node.js的版本管理工具。nvm和npm都是node.js版本管理工具&#xff0c;但是为了解决node各种不同之间版本存在不兼容的问题&#xff0c;因此可以通过nvm安装和切换不同版本的node。 2.nvm下载 可在点…

Linux基础(第三部分)

目录 一、查看文件显示命令 1、cat&#xff1a;查看文件内容&#xff1b;上下合并文件 2、标准输入与标准输出&#xff1a; 3、例题&#xff1a;如何合并两个文件 4、tac 就是反方向查看 5、rev 把一行倒过来 6、hexdump 查看硬件设备 7、more和less 8、查看文件内容…

ICC2:Less than minimum edge length和Concave convex edge enclosure

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 首先,要介绍一下这两种drc Less than minimum edge length对应的tf rule如下: 而Concave convex edge enclosure对应图示和tf 规则如下,可

【UML】第7篇 用例图(2/3)

目录 一、什么是用例&#xff08;Use Case&#xff09; 二、用例的识别 2.1 识别用例的思考方法 2.2 识别用例的注意事项 三、用例的命名 四、用例规约 五、用例的粒度处理 错误1&#xff1a;粒度过细 错误2&#xff1a;把步骤当用例 错误3&#xff1a;把活动当用例 …

关于RAP和ZIP之间的区别、关系和转换,看这篇就差不多了

RAR和ZIP文件都是流行的文件格式&#xff0c;允许你以压缩的方式存储文件。因此&#xff0c;文件占用的空间较小&#xff0c;并且可以在需要时快速传输。 当你有一些RAR文件已经被压缩时&#xff0c;由于各种原因&#xff0c;你可能仍然想将它们转换为ZIP文件。阅读本文了解如…

Axure RP 8 for Mac/win中文版:打造完美交互式原型设计体验

Axure RP 8&#xff0c;一款引领潮流的交互式原型设计工具&#xff0c;为设计师提供了无限的可能性&#xff0c;让他们能够创造出逼真的原型&#xff0c;从而更好地展示和测试他们的设计。 Axure RP 8拥有丰富的功能和工具&#xff0c;让设计师可以轻松地创建出复杂的交互式原…

hadoop集群的开启与关闭

背景 很久没完hadoopl,连怎么开启关闭都不会了qwq 1.进入安装hadoop的目录 我这里是已经进入了 2.开启集群 sbin/start-dfs.sh 3.关闭集群 sbin/stop-dfs.sh

尚硅谷 java 2023(基础语法)笔记

一、变量与运算符 1、HelloWorld的编写和执行 class HelloChina{public static void main(String[] args){System.out.println("hello,world!!你好&#xff0c;中国&#xff01;");} } 总结&#xff1a; 1. Java程序编写和执行的过程&#xff1a; 步骤1&#xff1…

【java】java学习笔记

1. 快速入门 // Hello类 public class Hello {// main方法public static void main(String[] args) {System.out.println("hello world!");} } 在控制台输入以下命令&#xff0c;对.java文件&#xff08;源文件&#xff09;进行编译操作&#xff0c;生成Hello.clas…

提前预判和确认再做 现货白银投资的两种思路

在现货白银投资中&#xff0c;对于交易的步骤长期有两种看法。一种是提前预判行情并提前布局。另外一种是等待行情启动再做布局。这种两种方法要怎么选呢&#xff1f;笔者将从自己的角度出发&#xff0c;对这个问题进行讨论。 我们来看一下前一种的投资者&#xff0c;他们喜欢提…

个人或企业为啥要搭建及自己的知识付费平台才有前景?

在知识经济的时代&#xff0c;知识的传播和变现显得愈发重要。许多个人或企业选择将自己的知识、技能和经验通过课程的形式进行传播&#xff0c;以实现知识的价值。然而&#xff0c;在使用其他平台进行课程销售的过程中&#xff0c;他们常常面临着一些问题和挑战。 首先&#…

基于FPGA的简易BPSK和QPSK

1、框图 2、顶层 3、m_generator M序列的生成&#xff0c;输出速率为500Kbps 4、S2P是串并转换模块 将1bit的m序列转换到50M时钟下的2bit M序列数据&#xff08;就有4个象限&#xff09;&#xff1b; 5、my_pll是生成256M的时钟作为载波&#xff0c;因为sin和cos信号的…

linux 性能优化-内存优化

CPU 管理一样&#xff0c;内存管理也是操作系统最核心的功能之一。内存主要用来存储系统和应 用程序的指令、数据、缓存等。 1.内存原理 1.1.内存映射 1.1.1.日常生活常说的内存是什么? 我的笔记本电脑内存就是 8GB 的这个内存其实是物理内存物理内存也称为主存&#xff0…

【分享】Word技巧之锁定部分区域

在编辑Word文档过程中&#xff0c;如果部分内容已经确定好&#xff0c;但又担心不小心做了改动&#xff0c;我们可以将这部分内容锁定起来&#xff0c;使之无法编辑更改&#xff0c;不清楚的小伙伴一起来看看如何设置吧。 首先&#xff0c;打开Word文档后&#xff0c;用鼠标选…

Opencv 入门三(视频滑动条窗口)

视频滑动条窗口源码如下&#xff1a; #include "opencv2\highgui\highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <fstream> using namespace std; int g_slider_position 0; // 滑动条的位置 …