指针?就那么回事儿

作者简介:大家好,我是Ceylan_,可以叫我CC ❣️    
个人主页:Ceylan_的博客
博主信息:平凡的大一学生,有着不平凡的梦

        专栏

  • 备战蓝桥杯   
  • 力扣每日一题
  • PTA天梯赛

⚡希望大家多多支持一起进步~❤️
若有帮助,还请关注点赞收藏,不行的话我再努努力

前言

说到指针,想必大家都不陌生,指针的最大特点就是难以理解,它是编程中很基础也是很重要的概念,在这里我们将一步一步,逐渐揭开指针神秘的面纱。

 指针?就那么回事儿_第1张图片

目录

前言

一、指针是什么?

内存与地址

 存取内存单元数据的方法

       直接访问——按变量名存取变量的值

        间接访问——通过变量的地址来存取变量的值。

  指针变量使用注意事项

二、指向指针的指针

三、指针变量作为函数参数

按值传递

按地址传递

四、指针与数组

指针与一维数组

指针与多维数组

五、指针与字符数组

一维字符数组

二维字符数组

六、练习

实现printf函数

实现strlen函数

实现strcpy函数

每日金句


一、指针是什么?

内存与地址

在了解指针是什么之前,我们需要先了解什么是计算机的内存,什么是地址。

计算机内存大部分时候指的是随机存储器也就是RAM,用于存放当前正在执行的数据或程序对象

内存中每一个字节都有对应的一个地址,就像一个小区中每间房子都有门牌号一样, 只有找到正确的门牌号才能找到对应的房子,通过地址可以找到内存中的字节。

当我们声明一个整型类型的变量  i  时,计算机会为这个特定的变量分配一定的空间,具体分配多少 空间取决于数据类型

数据类型与空间关系
c语言 所占字节数
char 1
int 4
float 4
double 8

 存取内存单元数据的方法

       直接访问——按变量名存取变量的值

                   例如:   int  x;
                                 x = 10;
                                 printf( “%d ”, x );

        间接访问——通过变量的地址来存取变量的值。

这一方法也称为「 指针访问 」,实现这一方法有两个问题需要我们解决。

 QS:    如何得到变量的地址?
              如何取得该地址下的数据呢?

为了解决这一问题,我们需要认识两种运算符。

“ & ” —— 取地址运算符。地址为一个16进制的整数。%d(10进制)或 %x(16进制)


  “ * ”—— 间接访问运算符。作用:返回该地址所指向存储单元的数据。

通过这两种运算符我们就能实现间接访问。

#include
int main()
{
	int a;            //我们声明一个变量a,假设它的地址为200
	int *p;           //声明一个指向整形的变量p,假设它的地址为400
	p=&a;             //p现在指向a,拥有了a的地址                
	a=5; 			  //如果我们对a进行赋值,再输出p会输出什么呢?
	printf("%d",p);   //我们会得到 200,是a的地址。&a同样表示a的地址
					  //那么如果我们输出&a会得到什么呢?                                       
	printf("%d",&a);  //我们也会得到 200,如果输出&p会得到什么呢?
	printf("%d",&p);  //p是一个变量,它同样存在在内存中,对它进行取地址,将会得到那个变量的地址              
				 	  //不难想出,我们会得到 400
	printf("%d",*p);  //得到5
	*p=8;             //我们将*p赋值为8,*p表示p指向地址的值这时我们输出a
	printf("%d",a);  //得到8   
	printf("&a=%d", &a );//200
	printf(" *(&a) = %d",  *(&a)  );//得到8
}

  指针变量使用注意事项

(1)指针变量中只能存放地址

int *p;   p=2001;       //非法赋值


(2)不能对未赋值的指针变量进行 “ * ” 运算

   int *p;   *p=10;        //非法赋值

二、指向指针的指针

我们是否可以创建一个指针指向指针变量呢?答案是肯定的。

当我们创建一个变量  q,它是用来存放  p  的地址 ,那么这个  q  是什么类型的呢?我们需要一个特定类型的指针来存放特定类型的地址,也就是需要一个指向指针的指针。我们在变量前面加两个  **,那么现在  q  就可以存放  p  的地址了。一个特定类型的指针,为了存储p的地址,需要一个指向int*类型的指针,为此再放一个*,表示这个指针指向的是int*。

我们可以一直这样套娃下去。比如我们想要声明一个指向指针的指针的指针 ,int**是指向指针的指针,我们再放一个*,放三个***在关键词int后面  int***r   。详细如下方代码

#include
int main()
{
	int x=5;                 //假设x的地址为200
	int *p=&x;               //假设p的地址为220
	*p=6;
	int **q=&p; 		     //q是存放指针p地址的指针,假设q的地址为240
	int ***r=&q;
	printf("%d",*p);         //通过指针来对地址进行解引用和写数据,此时x=6,输出6
	printf("%d",*q);         //输出220
	printf("%d",*(*q)); 	 //输出6 
	printf("%d",*r);  		 //输出240    
	printf("%d",*(*r));      //输出220   
	printf("%d",*(*(*r)));   //输出6 
}

三、指针变量作为函数参数

在前面,我们学会了定义指针变量,也学会了如何操作指针,在程序中如何使用指针,但是我们还没有讨论实际的指针用例,在什么情况下我们会想要使用指针变量。在这里我们将讨论指针的一个用例,这个用例就是使用指针,把指针作为函数参数,进行使用。

按值传递

我们来看这个场景

#include
void increment(int a)
{
	a=a+1;
	}
int main()
{
	int a=10;
	increment(a);
	printf("a=%d",a);
}

 我们声明了一个变量a,在main函数中初始化,通过调用函数对a这个变量的值+1,

但是它运行了之后发现输出a=10。为什么呢?

5159132c0ca745cf8cfd53308f54fd66.png

在函数里面声明一个变量,我们把它叫做局部变量,这样一来,我们只能在这个函数里面使用这个变量,上面例子中在函数里面increment的a,和函数main中的a,他们实际上不是同一个a。

特点:函数中对形参的改变不影响实参的值。

按地址传递

#include
void increment(int *p)
{
	*p=*p+1;
	}
	int main()
{
	int a=10;increment(&a);
	printf("a=%d",a);
}

        当我们调用increment函数时,我们传过去的是a的地址,通过修改地址内存放的值来达成加一效果。

特点:函数中利用形参指针变量可直接操作实参。

四、指针与数组

指针与一维数组

在c或c++程序中指针和数组的概念经常一起出现,两者之间有很强的关系。

当我们声明数组的时候int a[5],我们就会创建5个整形的变量,分别为a[0] a[1] a[2] a[3] a[4]这5个整型数会作为一个整体,类似于这样的连续的5个整型数存储在内存中,就像这样。

地  址 数组
200 a[0] 2
204 a[1] 3
208 a[2] 4
212 a[3] 5
216 a[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
int main()
{
	int a[5]={2,3,4,5,6};
	for(int i=0;i<5;i++)
	{
		printf("%d",&a[i]); 	//得到地址 
		printf("%d",a+i);	 	//得到地址 
		printf("%d",a[i]);		//得到值 
		printf("%d",*(a+i));    //得到值
	}
}

指针与多维数组

指针与二维数组或者三维数组,甚至更多维的数组之间如何产生联系,为了理解这个,首先我们需要理解多维数组在计算机内存中的组织形式。我们先回到一维数组在内存中的组织形式,当我们声明一个一维数组的时候,比如声明一个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
int main()
{
	int B[2][3]={2,3,4,5,6,7};
	int (*p)[3]=B;
	printf("%d",B);			//400
	printf("%d",*B);		//400
	printf("%d",B[0]);		//400
	printf("%d",&B[0]);		//400
	printf("%d",&B[0][0]);	//400
	printf("%d",B+1);		//412
	printf("%d",&B[1]);		//412
	printf("%d",*(B+1));	//412
	printf("%d",B[1]);		//412
	printf("%d",&B[1][0]);	//412
	printf("%d",*(B+1)+2);	//420
	printf("%d",B[1]+2);   	//420
	printf("%d",&B[1][2]);	//420
	printf("%d",*(*B+1));   //3
	//*B==B[0]//B[i][j]=*(B[i]+j)=*(*(B+i)+j)
}

五、指针与字符数组

一维字符数组

当我们声明一个大小为6的字符数组C1char C1[6]="Hello"  用这个字符串字面值对它进行初始化假设它在内存中是这样存储的。

 H e l l 0
200 201 202 203 204 205

数组在内存中是连续存储的,假设第一个字符的地址是200,一个字符占据一个字符,所以下一个字符的地址是201,再下一个是202,以此类推。

现在我们声明一个指向字符的指针C2char *C2=strC2是一个指向字符的指针,仅仅使用数组的名字,实际上返回数组首元素的地址。这个表达式所做的就是,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"}」他在内存中是怎么样存放的呢?

指针?就那么回事儿_第2张图片

 我们用不同的方法来声明这个二维数组看看有什么不同。

「char  * num_2[5] ={"One","Two","Three", "Four","Five"}」

指针?就那么回事儿_第3张图片

六、练习

实现printf函数

#include
void print(char *C)//接受数组的地址作为函数参数 
{
	//函数并不知道这个特定的数组大小为20,只知道数组的基地址 
	while(*C !='\0')//当检测到NULL字符时退出,不在输出 
	{
		printf("%c",*C);C++;
	} 
}
int main()
{
	char C[20]="Hello";//我在里面存放了长度为5的一个字符串,使用字符串字面值的时候隐式包含了一个NULL字符 
					   //我们不使用printf函数,来写一个自己的printf函数 ,然后把这个字符数组传过去
	print(C);
}

实现strlen函数

#include
int StringLen (char *s)
{
	int n=0;
	char *p=s;
	while( *p != '\0' )   
	{  
		n++; 
		p++; 
	}
	return n;
}
int main()
{            
	char str[20]="how do you do?";
	printf("str=%s",str);    
	printf("len=%d",StringLen(str) );
}

实现strcpy函数

#include
void CopyString(char *s1,char *s2)
{
	char *p1,*p2;
	for(p1=s1,p2=s2; *p2!='\0'; p1++,p2++)
	{
		*p1=*p2;  
	}
	*p1='\0';
}
int main()
{
	char *a="I am a boy.";
	char b[30];
	CopyString(b,a);
	printf("b=%s",b);
}

每日金句

留下恒心,恒做到底,定能成功

指针?就那么回事儿_第4张图片

        本人不才,如有错误,欢迎各位大佬在评论区讨论。有帮助的话还请【关注点赞收藏】,不行的话我再努努力

3783c370bbfc4de9988e2631eaf0ff14.png

你可能感兴趣的:(数据结构,c语言,c++,后端,数据结构,算法)