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

       

        指针作为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、总结 

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

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

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

相关文章

Uniapp + Vue3 + Pinia + Vant3 框架搭建

现在越来越多项目都偏向于Vue3开发&#xff0c;想着uniapp搭配Vue3试试效果怎么样&#xff0c;接下来就是详细操作步骤。 初始化Uniapp Vue3项目 App.vue setup语法 <script setup>import {onLaunch,onShow,onHide} from dcloudio/uni-apponLaunch(() > {console.l…

同源策略:保护你的网页免受恶意攻击的第一道防线(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

使用Flask逐步搭建Web应用程序

大家好&#xff0c;Flask是一个使用Python编写的轻量级Web应用框架。它被设计成简单、易于学习和使用的&#xff0c;同时具备足够的灵活性和扩展性&#xff0c;以满足各种规模的Web应用开发需求。本文我们将介绍一个使用Flask逐步搭建Web应用程序的简单入门示例。 1.安装Flask…

计算机存储术语: 扇区,磁盘块,页

扇区(sector) 硬盘的读写以扇区为基本单位。磁盘上的每个磁道被等分为若干个弧段&#xff0c;这些弧段称之为扇区。硬盘的物理读写以扇区为基本单位。通常情况下每个扇区的大小是 512 字节。linux 下可以使用 fdisk -l 了解扇区大小&#xff1a; $ sudo /sbin/fdisk -l Disk …

Vue3-24-组件-异步组件的介绍

什么是异步组件 个人理解 &#xff1a;异步组件 就是在用到这个组件的时候再进行加载&#xff0c;而不是 一上来就全部加载完成。即用即取的一个思想。异步组件中使用到的方法 &#xff1a; defineAsyncComponent () 方法 &#xff1a; 返回一个Promise 对象; 我们在开发过程中…

Unity中Shader缩放矩阵

文章目录 前言一、直接相乘缩放1、在属性面板定义一个四维变量&#xff0c;用xyz分别控制在xyz轴上的缩放2、在常量缓存区申明该变量3、在顶点着色器对其进行相乘&#xff0c;来缩放变换4、我们来看看效果 二、使用矩阵乘法代替直接相乘缩放的原理1、我们按如下格式得到缩放矩阵…

java实现回文数算法

判断一个数是否为回文数可以使用以下算法&#xff1a; 将数字转化为字符串&#xff1b;初始化左右两个指针&#xff0c;分别指向字符串的首尾&#xff1b;循环比较左右指针指向的字符&#xff0c;如果相等则继续比较&#xff0c;直到左右指针相遇或者发现不相等的字符为止&…

ES集群G1回收器,堆空间无法被回收问题

ES堆空间不足的问题&#xff0c;困扰了我有两年的时间。dump堆去分析&#xff0c;也未能分析出来&#xff0c;堆到底是被什么占用了。 我把堆空间给了31.9G&#xff0c;这是指针压缩生效的临界值&#xff0c;如果再大就指针压缩失效了。 痛苦的是&#xff0c;随着时间的增长。堆…

mysql复习笔记05(小滴课堂)

mysql的慢查询日志开启与问题定位 一张数据库数据很大的表。 查询一条数据&#xff0c;很快就查询出来了。 根据不同的条件&#xff0c;查到的数据相同&#xff0c;但是查询所花费的时间却是不同的。 使用命令查询慢查询日志是否开启&#xff0c;目前它是关闭着的。 开启日志。…

利用prometheus+grafana进行Linux主机监控

文章目录 一.架构说明与资源准备二.部署prometheus1.上传软件包2.解压软件包并移动到指定位置3.修改配置文件4.编写启动脚本5.启动prometheus服务 三.部署node-exporter1.上传和解压软件包2.设置systemctl启动3.启动服务 四.部署grafana1.安装和启动grafana2.设置prometheus数据…

Java研学-HTTP 协议

一 概述 1 概念和作用 概念&#xff1a;HTTP 是 HyperText Transfer Protocol (超文本传输协议)的简写&#xff0c;它是 TCP/IP 协议之上的一个应用层协议。简单理解就是 HTTP 协议底层是对 TCP/IP 协议的封装。   作用&#xff1a;用于规定浏览器和服务器之间数据传输的格式…

【源码解析】聊聊ReentrantReadWriteLock是如何实现的读写锁

为什么需要读写锁 在并发编程领域&#xff0c;有多线程进行提升整体性能&#xff0c;但是却引入了共享数据安全性问题。基本就是无锁编程下的单线程操作&#xff0c;有互斥同步锁操作&#xff0c;但是性能不高&#xff0c;并且同一时刻只有一个线程可以操作资源类。但是对于大…

[SWPUCTF 2021 新生赛]gift_F12

打开环境 题目有提示&#xff08;F12&#xff09;&#xff0c;那就查看一下源代码 直接滑到最后 看提示猜测&#xff0c;flag就在源代码里了 ctrlf查找flag 最后得到flag&#xff0c;改一下形式就可以了

网络技术基础与计算思维实验教程_2.4_跨交换机VLAN配置实验

实验内容 实验目的 实验原理 实验步骤 构建 在工作区放置交换机然后单击 选择config , 把交换机的默认名改为switch1 再放置两个交换机 再放置终端 放置三台与交换机1相连的终端 再放置三台与交换机3相连的终端 再放置两台与交换机2相连的终端 用直通线连接 然后用交叉线互联交…

java8流库之Stream.iterate

简介 java.util.stream.Stream 下共有两个 iterate iterate(T seed, final UnaryOperator<T> f)iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> f) 该方法产生一个无限流&#xff0c;它的元素包含seed&#xff0c;在seed上调用f产生的…

Matlab论文插图绘制模板第131期—函数等高线图

在之前的文章中&#xff0c;分享了Matlab函数折线图的绘制模板&#xff1a; 函数三维折线图&#xff1a; 函数网格曲面图&#xff1a; 函数曲面图&#xff1a; 进一步&#xff0c;再来分享一下函数等高线图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数…

【Week-P2】CNN彩色图片分类-CIFAR10数据集

文章目录 一、环境配置二、准备数据三、搭建网络结构四、开始训练五、查看训练结果六、总结3.1 ⭐ torch.nn.Conv2d()详解3.2 ⭐ torch.nn.Linear()详解3.3 ⭐torch.nn.MaxPool2d()详解3.4 ⭐ 关于卷积层、池化层的计算4.2.1 optimizer.zero_grad()说明4.2.2 loss.backward()说…

MyBatis Plus使用遇到的问题

如果想使用Mapper的xxxById()方法&#xff0c;实体类的主键上面必须加上TableId注解&#xff0c;如果不加&#xff0c;会报错 2023-12-21 22:48:33.526 WARN 11212 --- [ main] c.b.m.core.injector.DefaultSqlInjector : class com.example.mybatisplusdemo.dom…

ubuntu18.04 64 位安装笔记——备赛笔记——2024全国职业院校技能大赛“大数据应用开发”赛项——任务2:离线数据处理

进入VirtuakBox官网&#xff0c;网址链接&#xff1a;Oracle VM VirtualBoxhttps://www.virtualbox.org/ 网页连接&#xff1a;Ubuntu Virtual Machine Images for VirtualBox and VMwarehttps://www.osboxes.org/ubuntu/ 将下发的ds_db01.sql数据库文件放置mysql中 12、编写S…

无约束优化问题求解笔记(2):最速下降法

目录 3. 最速下降法3.1 最速下降法的基本思想3.2 基于精确搜索的最速下降法3.3 基于精确搜索的最速下降法的程序实现3.4 基于精确搜索的最速下降法的缺点 Reference 3. 最速下降法 3.1 最速下降法的基本思想 最速下降法是典型的线搜索方法. 设 f f f 是 R n \mathbb{R}^n R…