指针基本用法


指针,作为c语言最复杂也最独特的部分,一篇文章自然是讲不完的。所以今天小萌新只想稍微梳理一下指针的基本用法。

基本概念

众所周知,电脑里有一个真实存在的东西叫内存。计算机中所有程序的运行都在内存中进行。我们编写的程序在运行时,每一个数据都存储在内存中的某一个特定的地方,以电信号的0和1记录。当程序定义一个变量时,系统会给它分配一小块内存用于存储这个变量的值,那么每一个数据都有一个独一无二的“地址”,表示它在内存的什么地方。所谓的指针,就是表示这个“地址”的一个数据。
定义一个指针的标准语句为:类型 * 标识符;
先看程序:

	int a;//定义变量a
	a=9;//将a赋值为9
	int *p;//定义一个int型指针p
	p=&a;//使p指向a
	printf("a的值为%d\n",a);
	printf("a的地址为%p\n",p);

在这里插入图片描述
第三行 “int *p;”
意思是:定义一个指向int型变量的指针,名字叫做p
‘int’表示p指向的变量为int型,‘*’表示这个叫做p的东西是一个指针。
第四行 ”p=&a;“
意思是给p赋值,赋多少呢?赋“&a”这么多。‘&’是“取地址运算符”(即取址符),意思是取a的地址,正如前文所述,指针储存的是某一个变量的地址。在这个程序里,p储存的就是a的地址。当然,本着万物皆数的原则,p这个指针变量必然有一个数值,p的值,在此程序中,是62FE14(16进制)。
输出语句中‘%p’指的是以指针的形式输出数据。当然也可以用%d的方式输出p的值,能得到62FE14的十进制表示(大家可以自行验证)。但不管怎么表示,这串数都是表示变量a的地址。
当然,需要注意的是,这个程序的输出每次运行可能会有不同的结果。其原因是每次运行时,在‘int a;’步骤时,程序不一定每次都给a分配在内存的同一个地方。故a的地址也不一定相同。

如果我们定义一个指针
int *p;
再给它赋值:
p=NULL;
那么这个指针将会变成空指针,即不指向任何东西。其值为0。

需要注意的是,每个指针在定义时都与要说明其指向的变量的类型,并在之后严格遵守(除非有强制类型转换)。故以下定义是不正确的:
int a;
float *p;
p=&a;//此处系统报错

指针的运算方式

指针本身也是一个变量,也可以进行计算。只不过,对指针本身的数值进行乘法除法可能不仅没有任何意义,还会引发大量的事故,编译时编译器也会发出警告。所以这里我们介绍一些常用于指针的计算方式

取地址运算符 &

顾名思义,该运算符的运算结果为其后方变量的地址。比如
假设有定义
int a=6,b;
char d;
float c[5];
int *p;
那么
&a,&b,&c[1],&d都是正确的。
但是 &6 是不被允许的,因为&后面需要加变量,不可以加常量。
当然我们知道,指针本身也是一个数据,并且是变量。这也就意味着&p是合法的,当然,这样的用法我们后续会讲到。

间接引用运算符 *

* 是&的逆运算,指的是取这个地址上存储的数据值。如果有定义:

	int a=3;
	int *p=&a;
	printf("%d",*p);

那么运行后程序会输出 3
*p指的是取p这个地址所存储的值,由于我们在第二行让p指向a,故*p的运算结果为3.
由于&和*的运算级别很高,故可以认为,如果指针p指向变量a,那么计算中出现*p的地方,大部分都可以用a直接代替。比如:
int b=*p; //等价于int b=a;
(*p)++; //等价于a++;
*p=6; //等价于a=6;
特别注意第二行不能写作*p++,因为自增运算++与间接引用运算符*优先级相当,且他们的结合顺序为从右至左,故
*p++
等价于
*(p++)
它与(*p)++的区别后续会讲。

数组与指针

假如我们有定义 int a[4]={4,3,2,1};
我们定义了一个名字叫做a,含四个int型变量的数组。系统在给数组分配内存时,会有意地将其中各元素有序地排在一起。即在内存中存储时是这样的:
在这里插入图片描述
即:
在这里插入图片描述
可以看到相邻两项的差值是4,不是1。(十六进制中,C表示12)
这是因为数组a的元素为int型,int型变量占四个字节。如果改错char a[4],则结果会不同:(原因在后文解释)
在这里插入图片描述
实际上,数组名本身可看作一个指针,它指向数组的第一个元素。

char a[4]={'w','x','y','z'};
char *p=&a[0];
printf("%c,%c,%c",*a,*p,a[0]);

输出: w,w,w
所以char *p=&a[0]; 也可以写成:char *p=a; 意思完全一样。
同样的,由于一个数组的各元素在内存中是顺序紧密排列的,我们也就可以通过对指针的加减来访问数组的各个元素。

int a[4]={6,5,4,3};
int *p=a;
for(int i=0;i<4;i++)//循环四次
printf("%d ",*p++);

输出: 6 5 4 3
但如果把第四行写作
printf("%d ",(*p)++);
则会输出:6 7 8 9
那为什么呢?

*p++的意思是,先执行p++,在执行*p,但p++与++p含义不同,p++指计算之后,p再自增。
所以*p++的执行顺序是:
系统获取p的值,访问这个地址上存储的数据(a[0]),然后提供给printf函数输出在屏幕上,再让p自增。(++与p结合)

而(*p)++的执行顺序是:
系统获取p的值,访问这个地址上存储的数据(a[0]),然后提供给printf函数输出在屏幕上,再让a[0]自增。(++与(*p)结合)

但是明明a[1]的地址比a[0]大4,为什么p++的运算结果会等于a[1]的地址呢?
这是因为指针类型的变量在进行自增自减运算时,加减的量不一定是1,而是其指向的元素类型所占的字节数。如果有定义
char *q;
int *p;
那么q++会加1,但p++会加4.
所以,如果p指向了int型数组中的某一个元素,++p/--p就代表了后/前一个元素的地址。

比较运算

由于指针本身也是变量,也是一个个的数,指针之间相互比较大小也毫不奇怪。但是我们一般认为只有指向同一个数组的指针才能比较大小。比如有定义:
int a[3];
int b[4];
int *p1=a,*p2=&a[2];
int *q=&b[1];
那么我们可以比较p1与p2的大小(p1<p2)
但是p1和q能不能比呢?能比,但是几乎没有意义。
p1<p2 代表着p1指向的元素 (a[0]) 在p2指向的元素 (a[2]) 前面。
但是p1>q代表了什么呢?只能说在内存中a[0]比b[1]更靠前,但这大抵是对程序运行没有任何帮助的。
同样,我们也可以对指针进行>=,<=,==运算,在此不一一赘述。

更加复杂的指针

多级指针

正如前文所述,指针本身也是一个变量,也在内存中有一块地盘,也就有一个地址,也就意味着如下定义是合法的:
int a;
int *p1=&a;
int **p2=&p1;
先定义了一个变量a,又定义了一个指向a的指针p1,再定义一个指向p1的指针p2。我们称p2这样的指针为多级指针。
int **p2指:定义一个叫做p2的指针,它指向int *型变量。
即指向了一个“指向整型变量的指针”。
在此定义下,以下表达式均为真:
*p1==a;
**p2==a;
*p2==&a;
*p2==p1;

函数指针

指针不仅可以指向变量,也可以指向函数。
先看代码:

#include <stdio.h>
int max(int a,int b){
	int c;
	if(a>b) c=a;
	else c=b;
	return c;
}
int min(int a,int b){
	int c;
	if(a<b) c=a;
	else c=b;
	return c;
}
int main(){
	int (*ph)(int a,int b);
	int a=2,b=3;
	ph=max;
	printf("%d",(*ph)(a,b));
 return 0;
} 

输出 3

这段程序先定义了两个函数,分别是求最大值与求最小值,然后定义了一个指针ph
int (*ph)(int a,int b);
这句代码的意思是:
“定义一个叫做ph的指针,它指向一类参数为两个整型变量,返回值为整型的函数”。
小萌新的表述可能比较拗口,但大体意思是这个😊。
ph=max;指让ph指针指向max函数。
单数第三行的 (*ph)(a,b) 等同于max(a,b) 请注意(* )是必需的,如果写成 ph(a,b) 部分编译器可能报错。

指针的更多用法

指针形参

正如前文所述,指针本身也是一种数据类型,它和整型,浮点型一样,也可以参与许多计算。比如我们可以用指针作函数的形参:

int *backward(int *p){
	p++;
	return p;
}

这个函数的功能是让p自增。正如前文所述,这样的操作只有在p指向某一个数组的元素(且不是最后一个)时才有意义。
其中int *backward(int *p)的意思是定义一个函数,叫做backward,它需要一个int *型变量,并返回一个int *型的值。

指针数组

我们也可以用指针作为数组的元素:

	int *p1,*p2,*p3;
	int* q[3]={p1,p2,p3};

定义一个数组,名字叫q,它有三个int*型元素。int*即代表“指向整型的指针”。
借此我们可以定义一个二维动态数组。
//限于篇幅,暂时不介绍动态数组。

常见问题

在这里必须要讲一个几乎所有初学者(包括小萌新自己)都会遇到的,关于函数与指针的问题。

#include<stdio.h>
void huan1(int x,int y){
	int t;
	t=x;
	x=y;
	y=t;
}
void huan2(int *p,int *q){
	int t;
	t=*p;
	*p=*q;
	*q=t;
}
int main (){
	int a=5,b=6;
	int *p1=&a,*p2=&b;
	printf("现在a=%d,b=%d\n",a,b);
	huan1(a,b);
	printf("现在a=%d,b=%d\n",a,b);
	huan2(p1,p2);
	printf("现在a=%d,b=%d\n",a,b);
 return 0;
} 

先定义两个函数,可以看到这两个函数是非常相像的。运行结果:
在这里插入图片描述
注意到两次调用函数,只有后一次改变了a,b的值。
为什么?

我们可以编一个故事来解释这件事情:
说有一个数学家叫main在做一道题,做到一半发现有一步自己不会算(或者不想算),于是他把这个问题抛给了另一个叫做huan1的学生。main把演算纸递了过去,叫huan1算一下,huan1就从main先生的演算纸上抄了两个值5和6,在自己的演算纸上写好“x=5,y=6”(即向函数传递实参)接着huan1通过计算得出x=6,y=5,但是由于huan1属于void型(无返回值)他算完就走了。所以main先生的问题没有得到解决,于是他去找了huan2。
这个huan2就比较厉害了,他没有抄5和6两个数,而是记录了“第一行第一列,第二行第三列”(传输的参数为地址),接着他计算t=*p(把第一行第一列的数赋给t)*p=*q(把第二行第三列的数的值赋给第一行第一列的数)所以这个时候他又一把抢过main先生的演算纸,粗暴的修改了上面第一行第一列记录的值(即修改了a的值)之后huan2计算*q=t,同理,又修改了main先生纸上第二行第三列的数值(修改了b的值)。
虽然也是void型,即huan2算完也不会留下任何东西,但他在计算的时候已经对main先生的演算纸上内容经行了修改。
//写的比较啰嗦,希望我解释清楚了这其中的原因

当然,c语言函数调用的原则“单向值传递”并没有被违背。huan2函数并没有修改实参的值(即a,b的地址不变)修改的是这个地址上储存的数值。

结尾

关于指针的用法,小萌新暂时只介绍这么多了,当然c语言指针的用法远不止这些。
或许可以在之后的文章里给大家继续介绍。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值