c语言——指针篇

目录

 

运算符*和&

指针与数组

指针的定义及引用

定义:

初始化:

引用:

指针运算

指针与整数相加

指针递增

指针与整数相减

指针递减

两指针比较

两指针求差

指针应用场景

使用指针注意事项

指针与const

const的数组    


 

运算符*和&

        在开始学习指针之前,我们需要先了解一下 * 和 & 这两个运算符,以及关于内存中变量地址的概念:

        通常的机器都有一系列连续编号或编址的存储单元,这些编号我们把它叫作存储单元的地址,这些存储单元可以单个进行操纵,也可以以连续成组的方式操纵。机器的一个字节可以存放一个 char 类型的数据,两个相邻的字节可以存储一个 short 类型的数据,根据数据类型占据的大小以此类推.....

 

        地址运算符 & ,用来取得内存中对象的地址,该对象可以是变量或数组元素,但不能是表达式、常量以及register类型的变量。  //register的变量即寄存器变量,该变量存放在CPU寄存器中,不会占用内存单元,故无法通过 & 运算符访问 ,该变量的用处不在此章展开,先了解即可。      

        解引用(间接寻址)运算符 * ,用来取得某个地址上的值,当它作用于指针时,将访问指针所指向的对象。 //指针存储的即是地址,下面我们会讲到

        格式说明符 %p ,用来打印变量的地址,在 printf 中使用该格式说明符用来输出变量的地址。

指针与数组

        在之前数组篇中有提到过,c 语言中,在我们创建一个数组之后,该数组名表示数组所分配连续空间中第一个单元的地址,即数组名==首地址,由于数组空间一经分配之后在运行过程中不会改变,因此数组名是一个地址常量,不允许修改。指针中存放也是地址量,只是指针是地址变量,在程序运行过程中,指针可以根据所需存放另一个新的地址,道理和 c 语言中普通的变量一样,可以随意更改,只是一个存放着值一个存放的是地址。数组与指针关系非常密切,数组篇就有说过,在函数参数声明和定义中,它俩就可以混用。

指针的定义及引用

        指针就是保存地址的变量,变量的值是内存的地址,普通变量的值是实际的值,指针变量的值是具有实际值的变量的地址

定义:

        定义一个指针,要明确其指针类型,虽然指针存放的都是地址,但不同类型的指针不可以相互赋值,如一个定义是指向 int 的指针,可以将另一个也指向 int 的指针赋值给它,使它们两个都指向同一个值,但不能用一个指向 double 的指针将其地址赋值给指向 int 的指针,因为不同类型的数据占据的内存字节大小不一样。

 

        指针类型名  *指针名 ; 

         //指针类型名和普通变量类型名一样,表示该指针指向何种类型的数据,*代表这是一个指针,如 :

        int *p;   

        星号代表p是一个指针,所以每定义一个指针变量需要带上一个*。如果需要同时定义两个或多个指针,则每个变量名前需要带上*号,诸如 int *p,q;这样的定义,此时表示定义了一个指针 p 以及一个 int 型的普通变量 q。

初始化:

        和普通变量一样,指针可以在定义时刻初始化。

        指针类型名  *指针名 = 地址;  //地址可以是已定义的变量的地址,如&a,也可以是数组名,因为数组名本身就是地址,前面已经讲过多次了。

        说到初始化,很重要的一点注意事项就是,在使用指针时,千万不要用解引用未初始化的指针,例如:

int main(void)
{
	int *p;  //定义一个指针
	*p=8;    //未初始化就使用它
	
	return 0;
}

        严重的错误!就像定义一个普通的变量,系统分配给该变量一串地址,如果你未对其初始化,该地址上有什么值就会在你的变量中,如学校分配宿舍,学校系统只负责给你分配房间号,当你进去之后,如果你不清理,里面有什么都是你的,包括里面的垃圾也都是你的。而解引用未初始化的指针问题则更严重,第四行的意思是把 8 存储在 p 所指向的位置,但 p 未被初始化,其值是一个随机值,所以不知道 8 将存往何处,这可能如上面一样不会出什么错,也可能会擦写数据或代码,更或导致程序崩溃。切记在使用一个指针前必须用已分配的地址初始化它,例如用一个现有变量的地址初始化该指针 int *p,a; *p=&a;。

 

引用:

        指针的引用就是使用解引用运算符*,指针存储的是一个地址,*运算符就是用来取得该地址上的值,所以指针的引用也很简单,在指针名前带上*运算符即可,在上面例子里已经用到过了。

        *指针名;  //唯一需要注意的就是中间不要有空格

 

指针运算

        与普通变量相比,指针的运算相对较少一些,指针一共有加、减、、自增、自减、比较以及两指针求差六种运算,下面一一介绍:

指针与整数相加

        可以使用+运算符把指针与整数相加,如下:

int main(void)
{	 
	int a[3]={0,1,2};  
	int *p=&a[0];  //初始化一个指向a[0]的指针p

	
	printf("*p=%d\n",*p);
	p=p+2;        //指针+2,输出结果
	printf("*p+2=%d\n",*p);
	
	return 0;
}

296bbacbace647ac8a46c4378630ee47.png

        

        在计算机内部存储数据的最小单位是字节,我们在初始化数组时,数组名即代表该数组的第首单元的地址,其实这样说还是不够精准,除非这是一个char类型的数组,因为 c 语言中一个 char  占一个字节,说的更细节一点,其实初始化数组时,该数组名的地址代表的是该数组的首单元的首个字节的地址,指针同理,因为有不同类型的指针,初始化一个指针时,它所得到的地址是该类型变量地址的首字节的地址。而在定义需要给出变量的类型,故计算机有能力去判断出该首地址往后需要多少个字节为一块单元,从而进行读写操作。

       上述指针类型为 int ,故指针+2就使其指向2个int位置(4*2个字节)之后的首地址,其他类型同理,如果是double类型,则使指针指向2个double位置也就是 8*2个字节之后的首地址,指针的加法就是使其移动多少个位置。

指针递增

        递增指向指针元素(数组)的指针可以让该指针移动至下一个元素,递增普通指针变量可以使其指向该变量地址紧挨着的下一个地址,不过递增指向普通变量的指针一般我们不会去用,它不像数组,数组是一整串连续的地址,其中的元素都是我们自行给予,我们递增指向数组的指针目标很明确,但是普通变量我们去递增它使其指向下一个位置,我们是不确定该位置会有什么。数据存储在电脑的磁盘上,我们在使用电脑过程中会不停的增删读写数据,因此会在磁盘上留下一些断断续续的内存块,如果不对磁盘进行整理,那么很有可能此时你的变量就存放在某个空内存块,而该内存大小正好被你的变量填满(或该变量正好在此内存块的末尾),那么此时递增该指针使其指向的下一个地址,上面存放着一些和你的程序无关的数据,会很莫名其妙。

int main(void)
{	 
	int a[3]={0,1,2};
	int *p=&a[0];
	
	
	printf("before ++: *p=%d\n",*p++);  
	printf("after ++: *p=%d\n",*p);
	
	return 0;
}

f9bdc7e4c529424388e4ed29a68fe92f.png

//关于以上代码中的*p++, * 解引用运算符和自增运算符 ++ 都是单目运算符,不过*比++优先级来的高,*p++先取

指针与整数相减

        可以使用 - 运算符从一个指针中减去一个整数,与指针加一个整数同理,需要注意的是指针减法运算时,指针必须是第一个运算对象,整数是第二个运算对象,该整数将乘以指针指向类型的大小(以字节为单位),然后被初始地址减去,往前移动该整数个位置

main(void)
{	 
	int a[3]={0,1,2};  
	int *p=&a[2];  //初始化一个指向a[2]的指针p

	
	printf("*p=%d\n",*p);
	p=p-2;        //指针-2,输出结果
	printf("*p-2=%d\n",*p);
	
	return 0;
}

a4cda6ce31b645069ec734d38ff863f3.png

指针递减

        既然有指针递增自然就可以递减指针,与递增同理,指针递减使其指向当前地址的前一个位置。

int main(void)
{	 
	int a[3]={0,1,2};
	int *p=&a[2];
	
	
	printf("before ++: *p=%d\n",*p--);  
	printf("after ++: *p=%d\n",*p);
	
	return 0;
}

//++、--作为后缀时,该表达式本身的值为该变量在++、--之前的值

a26670515eff45afb9701c425b622c1b.png

两指针比较

        使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象,两指针比较,比较的对象是两地址的大小

#include <stdio.h>
int main(void)
{	 
	int a=1;
	int b=2;
	int *p=&a,*q=&b;
	
	
	{
	printf("address of a=%p\n",&a);
	printf("address of b=%p\n",&b);
	}
	
	
	if( p>q ){
		printf("max of address=a //%p\n",&a);
	}else{
		printf("max of address=b //%p\n",&b);
	}
	
	return 0;
}

c58fc04ede80403b94b5654bd645a1b4.png     

        上述代码有几点要讲一下,我们知道%p格式说明符是以十六进制输出地址的,变量a定义在变量b之前,故 if 中表达式的结果是true输出了那个max是a的地址,但并不是所有的电脑先定义的变量地址就一定大于紧挨着它之后定义的,这涉及到内存中数据的存放,在之后数据结构专栏中会对其详细解释,在指针篇我们不再展开,不过上述代码还有一点要说一下,不知道你有没有注意到,我们在定义变量a之后紧接着定义了变量b,它们直接的地址值仅仅相差4个字节。上述代码a和b都是int类型,我们要讲的是,在c语言中,连续定义的变量它们是紧挨着的

#include <stdio.h>
int main(void)
{	 
	int a=1;
	int b=2;
	int *p=&a;
	
	p++;
	printf("%d",*p);
	
	return 0;
} 

f75e915a77634014bf4ae4e2bfb8b299.gif

嗯?好像和我们想的有点不一样,哪出差错了呢?我们先后定义了两个变量a、b,然后让一个指针p指向a,p获得了a的地址,此时p++应该就是下一个地址b了对吧。如果你这样想那么你就犯了很严重的错误,在本篇指针递增那段有讲过:


        /*递增指向指针元素(数组)的指针可以让该指针移动至下一个元素,递增普通指针变量可以使其指向该变量地址紧挨着的下一个地址,不过递增指向普通变量的指针一般我们不会去用,它不像数组,数组是一整串连续的地址,其中的元素都是我们自行给予,我们递增指向数组的指针目标很明确,普通变量我们去递增它使其指向下一个位置,我们是不确定该位置会有什么。/*


       我们在这里知道a的下一个地址就是b,但是在你的电脑内部可不一定如你所想的一样,虽然a变量是定义在变量b之前的,但是由于电脑存储数据的方式不同,此时在我的电脑中,b的地址是比a变量的地址小的,也就说,此时变量b在内存中是存放在变量a前面的,除非是数组,数组是一个容器,它存储的是一系列相同类型的值,定义数组时会在内存中申请一串连续的地址,故数组中数据在内存中的排列方式就如我们定义时刻一样,从前往后。如我们这次使指针向指向b再加一,此时就会得到我们想要的结果了

#include <stdio.h>
int main(void)
{	 
	int a=1;
	int b=2;
	int *p=&b;
	printf("p= %p\n",p); 
	
	p+=1;
	printf("&a=%p\n",&a);
	printf("&b=%p\n",&b);
	printf("指针p加一之后的值及地址:\n");
	printf("*p=%d\n",*p);
	printf("p= %p\n",p);
	
	return 0;
} 

285eb336ea904d9ca4c111044306cbed.png

两指针求差

        指针求差可以计算两个指针的差值,我们通常在数组中使用指针求差,求差的两个指针分别指向同一个数组的不同元素,通过计算求差两元素之间的距离。差值的单位与数组类型的单位相同

#include <stdio.h>
int main(void)
{	 
	int a[3]={1,2,3};  
	int *p=&a[0];  //初始化一个指向a[0]的指针p,
	int *q=&a[2];  //指向a[2]的指针q
	
	printf("q - p = %d\n",q-p);  
	
	return 0;
}

//也就说指针q比指针p大两个单位,指针p+2就获得q相同的地址

17e749d3ee454f0da20d675c57c5ec05.png

指针应用场景

        至此指针的基本操作我们已经了解的差不多了,指针是c语言的灵魂,单单知道这些还是不够的。指针在c语言中的用处非常多,在深入学习了c语言之后我们会经常用到指针,但是指针也是比较难掌握的,就好像之前说过的goto语句一样,指向这又指向那,用不好就会很容易破坏了程序的结构性,减少了代码的可读性。

        在讲指针的应用场景之前,我们先了解c语言中变量的生存期及作用域的概念,c语言能让程序员恰到好处地控制程序,这是它的优势之一,程序员通过c的内存管理系统指定变量的作用域和生命期,实现对程序的控制。

        生存期:即变量创建到消亡的这段时间,这一阶段我们叫做变量的生存期

        作用域: 就是变量所能作用的范围

        指针典型的应用场景总结起来就是穿梭于函数之间,在c语言中,我们到目前为止,定义的都是本地变量,也叫局部变量,在之后我们会学习程序结构,那时我们会详细解释本地变量和全局变量,在本篇中了解即可,本地变量的生存期、作用域都只局限与定义它的块内(花括号{ }内),也就是函数内部,出了这个函数这些变量就消亡了,所以在学指针之前,我们连一个最简单的交换函数(用户输入两个值,通过调用函数进行交换)都做不出来,因为c语言函数在传递参数的时候,做的只是值传递,也就是copy一份副本给另一个函数,而有了指针,我们就可以做成这件事了,在函数参数列表中的形参指针,虽然c语言仍然做的是值传递,但我们得到了地址,copy过来的地址,在它所在的函数内部它是可以通过该地址值去访问到我们的变量的。c语言中的各个函数就像处在不同的时空,在其内部的值、变量等等都是这个时空所拥有的,而指针就是打开时空隧道的钥匙。

 

使用指针注意事项

        我们还并没有深入学习指针,现阶段我们需要注意的一两点,上面都已经的提到过了,现在再总结一下:

        1.定义一个指针之后,切记在使用一个指针前必须用已分配的地址初始化它使其获得某个地址值,例如用一个现有变量的地址初始化该指针。

        2.初学指针,尽量不要对一个指向普通变量的指针让其做加法或减法,你可能会得到一些没有意义的值。

 

指针与const

        关于指针与const,我们知道定义一个数组,数组空间一经分配之后在运行过程中不会改变,因此数组名是一个地址常量,不允许修改。可以说其实数组就是 const 的指针,在定义指针时前面加上 const,就表示该指针是指向的某个地址之后就不能在指向另外的地址了

定义:

        指针类型 *const 指针名=某地址  ; 

        //如int *const p=&a; 表示指针 p 获得a变量的地址,我们叫做指针p指向了a,且该指针p在之后程序运行过程中,无法再获得其他地址指向其他变量。

 

还有一点就是,在定义时,const还可以写在*号前面:

        const 指针类型 *指针名=某地址 ;  

        //如const int *p=&a,表示指针p获得了变量a的地址,但是在之后我们让指针指向其他变量,同时也可以修改变量a的值,这个const在这的作用是说,在之后程序运行过程中,你不可以通过该指针去修改变量a的值。判断哪个被const了标志就是const在*前or后。

const的数组    

        还要补充一点就是,数组我们知道它本身就是const的指针,那么在数组前再加上const,就表示该数组元素中的每一个单元都是const

        我们会用到const的指针和const的数组就是因为,在之前我们自己写的函数,是无法修改到别的函数中变量的值的,但是有了指针之后我们可以做到了,而const的指针和数组就是为了防止我们在将地址传给函数之后不小心错误的修改了值,所以我们可以在函数参数列表中,为指针加上const,以避免造成数据的破坏。

        const int a[3]={0,1,2};  

        //数组a中的三个值已经初始化赋予了三个确定的值,再之后程序运行中,如果你试图对该数组中的值进行写操作,c语言编译器会报错而不只是警告  error!!!

int main(void)
{	
	int X=520;
	int Y=1314;
	const int *p=&X; //表示不可通过指针p去修改number
	 
	X=X*10000+Y; //正确 
	*p=0; //error
	p=&Y; //正确

//
	
	const int a[3]={1,2,3}; //该数组每个单元都是const 
	int *const q=&a[0]; //表示指针q无法再指向其他变量 
	
	a[0]=X; //error
	q=&a[2]; //error
	*q=Y; //error 此时q指针无法指向其他变量,
	      //但可以通过q去修改它所指向的变量,
		  //但由于q初始化时所指向的是一个const的数组,
		  //故无法通过q去修改其值 
	return 0;
}

        

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值