前言作者简介:大家好,我是Ceylan_,可以叫我CC ❣️
个人主页:Ceylan_的博客
博主信息:平凡的大一学生,有着不平凡的梦专栏
备战蓝桥杯 力扣每日一题PTA天梯赛
⚡希望大家多多支持一起进步~❤️
若有帮助,还请关注➕点赞➕收藏,不行的话我再努努力
说到指针,想必大家都不陌生,指针的最大特点就是难以理解,它是编程中很基础也是很重要的概念,在这里我们将一步一步,逐渐揭开指针神秘的面纱。
目录
前言
一、指针是什么?
内存与地址
存取内存单元数据的方法
直接访问——按变量名存取变量的值
间接访问——通过变量的地址来存取变量的值。
指针变量使用注意事项
二、指向指针的指针
三、指针变量作为函数参数
按值传递
按地址传递
四、指针与数组
指针与一维数组
指针与多维数组
五、指针与字符数组
一维字符数组
二维字符数组
六、练习
实现printf函数
实现strlen函数
实现strcpy函数
每日金句
一、指针是什么? 内存与地址
在了解指针是什么之前,我们需要先了解什么是计算机的内存,什么是地址。
计算机内存大部分时候指的是随机存储器也就是RAM,用于存放当前正在执行的数据或程序对象。
内存中每一个字节都有对应的一个地址,就像一个小区中每间房子都有门牌号一样, 只有找到正确的门牌号才能找到对应的房子,通过地址可以找到内存中的字节。
当我们声明一个整型类型的变量 i 时,计算机会为这个特定的变量分配一定的空间,具体分配多少 空间取决于数据类型。
例如: int x;
x = 10;
printf( “%d ”, x );
这一方法也称为「 指针访问 」,实现这一方法有两个问题需要我们解决。
QS: 如何得到变量的地址?
如何取得该地址下的数据呢?
为了解决这一问题,我们需要认识两种运算符。
“ & ” —— 取地址运算符。地址为一个16进制的整数。%d(10进制)或 %x(16进制)
“ * ”—— 间接访问运算符。作用:返回该地址所指向存储单元的数据。
通过这两种运算符我们就能实现间接访问。
#include
(1)指针变量中只能存放地址
int *p; p=2001; //非法赋值
(2)不能对未赋值的指针变量进行 “ * ” 运算
二、指向指针的指针int *p; *p=10; //非法赋值
我们是否可以创建一个指针指向指针变量呢?答案是肯定的。
当我们创建一个变量 q,它是用来存放 p 的地址 ,那么这个 q 是什么类型的呢?我们需要一个特定类型的指针来存放特定类型的地址,也就是需要一个指向指针的指针。我们在变量前面加两个 **,那么现在 q 就可以存放 p 的地址了。一个特定类型的指针,为了存储p的地址,需要一个指向int*类型的指针,为此再放一个*,表示这个指针指向的是int*。
我们可以一直这样套娃下去。比如我们想要声明一个指向指针的指针的指针 ,int**是指向指针的指针,我们再放一个*,放三个***在关键词int后面 int***r 。详细如下方代码
#include
在前面,我们学会了定义指针变量,也学会了如何操作指针,在程序中如何使用指针,但是我们还没有讨论实际的指针用例,在什么情况下我们会想要使用指针变量。在这里我们将讨论指针的一个用例,这个用例就是使用指针,把指针作为函数参数,进行使用。
按值传递我们来看这个场景
#include
我们声明了一个变量a,在main函数中初始化,通过调用函数对a这个变量的值+1,
但是它运行了之后发现输出a=10。为什么呢?
在函数里面声明一个变量,我们把它叫做局部变量,这样一来,我们只能在这个函数里面使用这个变量,上面例子中在函数里面increment的a,和函数main中的a,他们实际上不是同一个a。
按地址传递特点:函数中对形参的改变不影响实参的值。
#include
当我们调用increment函数时,我们传过去的是a的地址,通过修改地址内存放的值来达成加一效果。
四、指针与数组 指针与一维数组特点:函数中利用形参指针变量可直接操作实参。
在c或c++程序中指针和数组的概念经常一起出现,两者之间有很强的关系。
当我们声明数组的时候int a[5],我们就会创建5个整形的变量,分别为a[0] a[1] a[2] a[3] a[4]这5个整型数会作为一个整体,类似于这样的连续的5个整型数存储在内存中,就像这样。
地 址数组值200a[0]2204a[1]3208a[2]4212a[3]5216a[4]6如果我声明一个整形指针p,然后把这数组的第一个元素a[0]的地址赋给p,当我们输出p时,就会得到200,输出*p就会得到2。当我们输出p+1时我们会得到204,如果我们解引用p+1,也就是输出*(p+1),会得到3。以此类推,解引用p+2会得到第三个元素的值。
数组还有一个特点,如果我们使用数组名a,会得到一个指向数组首元素的指针,因此我们可以这么表达语句p=a,我们甚至不需要写&,如果我们输出a,会得到数组首元素的地址200,如果对他进行解引用,输出*a,会得到它的值2。输出a+1会得到地址204,*(a+1)会得到3。以此类推,解引用a+2会得到第三个元素的值。
对于数组a中索引值(也就是下标)是i的元素,为了取得这个特定的元素的地址,可以使用&a[i],或者是简单地使用a+i,两者都可以得到a[i]的地址。
简单做个总结:
int a[10] ,*p;
若 p = &a[i]
则 *p 等价于 a[i]
a+i 等价于 &a[i]
p+i 等价于 a+i 等价于 &a[i]
我们来看一些代码示例,来巩固一下。
#include
指针与二维数组或者三维数组,甚至更多维的数组之间如何产生联系,为了理解这个,首先我们需要理解多维数组在计算机内存中的组织形式。我们先回到一维数组在内存中的组织形式,当我们声明一个一维数组的时候,比如声明一个5个元素的数组a[5],基本上相当于是在一块连续的内存上创建了「 a[0] a[1] a[2] a[3] a[4] 」这样五个整形变量。假设数组a存放在这块内存区间,a的起始地址是200,我们知道内存中的每个字节都有一个地址,整形变量是以4字节存储的,那么从200开始的4字节属于a[0],从204开始的4字节属于a[1] ,从208开始的4字节属于a[1],从212开始的4字节属于a[2],从216开始的4字节属于a[3],从220开始的4字节属于a[4]。
假设我们要创建一个二维数组b,「 int b[2][3] 」,实际上这是在创建数组的数组,我们创建了两个一维数组,每个一维数组中有三个整形数据。
我们说过,数组名返回数组首元素的指针,这一次,元素不是一个类型,而是具有3个整形的一维数组因此如果我这样写「 int*p=b 」,会有编译错误,因为b返回的是一个指向一维数组的指针,而不是一个整形的指针,因此指针的类型是很重要的。
我们可以定义一个指向一维数组的指针「 int(*p)[3]=b 」当我们使用b[0]时会返回b[0]中第一个整形数n[0][0]的指针。
a[i][j] 等价于 *(a[i]+j) 等价于 *(*(a+i)+j)
我们来看一些代码示例,来巩固一下。
#include
当我们声明一个大小为6的字符数组C1「char C1[6]="Hello" 」用这个字符串字面值对它进行初始化假设它在内存中是这样存储的。
Hell0200201202203204205数组在内存中是连续存储的,假设第一个字符的地址是200,一个字符占据一个字符,所以下一个字符的地址是201,再下一个是202,以此类推。
现在我们声明一个指向字符的指针C2「char *C2=str」C2是一个指向字符的指针,仅仅使用数组的名字,实际上返回数组首元素的地址。这个表达式所做的就是,c2的值变成了200,所以C2就指向了数组的首元素。我们可以使用这个字符指针C2,就像使用C1一样,来对数组进行读写 。
例如 当我们输出C2[1]时,就会得到'e'。我们还可以直接修改这个数组,比如我们想修改索引位置是0的字符值变为'A',就可以直接运行「c2[0]='A'」。
当我们写c2[i]的时候,它其实就是「*(c2+i)」,c2是基地址,(c2+i)是会把你带到第i个元素的地址,比如C2+2会是202,如果我们加一个*,那么就是对这个地址的元素进行解引用,所以这两种写法是等效的。
二维字符数组我们声明一个二维字符数组「char num_1[5][6] = {"One","Two", "Three", "Four","Five"}」他在内存中是怎么样存放的呢?
我们用不同的方法来声明这个二维数组看看有什么不同。
「char * num_2[5] ={"One","Two","Three", "Four","Five"}」
六、练习 实现printf函数#include
#include
#include
留下恒心,恒做到底,定能成功
本人不才,如有错误,欢迎各位大佬在评论区讨论。有帮助的话还请【关注➕点赞➕收藏】,不行的话我再努努力