指针就相当于遥控器。指针变量就是一个遥控器,专门存放内存单元地址的变量。因此可以认为指针变量就是地址变量。32位的内存地址范围是:0x00000000~0xffffffff,指针变量要存储这些值,需要的内存空间是32位,即4个字节。所以,指针变量固定地占4个字节的内存空间。
目录
1、指针变量的指针
1.1 指针变量的定义
指针变量简称指针,其本质就是一个变量,它不同于普通变量的地方是只能存储地址,不能存放除地址以外的值。任何变量都需要先定义后使用。定义指针变量的格式为:
类型说明符 * 指针变量名
说明:
(1)类型说明符:说明的是指针变量存储的内存地址中所存数据的数据类型
(2)* :不是乘法运算符,它表示定义的是指针变量。
(3)指针变量名使用合法的用户标识符命名即可。
例如:int *p1,int *p2;定义了两个指针p1和p2,它们可以存储整型数据的地址。
注意:
(1)int *p1,p2;与int *p1,*p2;表示的意义不一样,它定义的是一个指针变量p1和一个整型变量p2
(2)注意指针变量名是p1和p2,而不是*p1和*p2
1.2指针变量的初始化和赋值
在C语言中,用指针来表示一个变量指向另一个变量这样的指向关系。假如有变量i和p,当p变量中存放了i变量的地址时,就说p指向变量i。例如:int i=3;int *p;p=&i;
在定义一个指针变量后,编译器不会自动为其赋值,此时指针变量的值是不确定的。有可能这个不确定的值正是某块有用的内存地址,那么直接使用它可能会带来内存错误。为了防止这种情况,可以使用NULL对其初始化,NULL的意思是“空”,表示指针哪里都不指向。NULL是指针类型中逻辑值为假的常量,除了NULL,其余指针常量的逻辑值都是真。
以p为例,来说明指针变量的初始化和赋值格式:
(1)为指针变量p初始化 int *p=NULL;
(2)先定义指针变量p,然后再为它赋值。int *p;p=NULL;
注意:变量名是p,不要写为*p=NULL。
1.3使用指针变量
正如用遥控器可以控制空调一样,可以通过指针访问它所指向的内存区域。这需要使用运算符“*”
1.3.1 *运算符
*运算符除了在定义指针变量时作为指针变量的标识外,还可以使用它达到“间接引用”的目的,即:通过指针访问其所指向的内存空间。
其使用格式: * 指针变量名; 这种格式表示取指针所指向的目标变量的值
例如:int i=3;int *p;p=&i;j=*p;
当p中存入了i的地址,*p与i等价,也为3,。将*p的值赋给j后,j的值为3。
验证运算符*和&的作用
使用指针间接访问它所指向的变量
2、指向数组的指针
2.1指针和数组名
数组名代表数组的首地址,可以将其看作是一个存放了数组首地址的指针。不同于普通指针的是,数组名是个常量,不能修改它的值。例如:int i;int a[10];a=&i;
说明:
(1)a是常量,不能修改它的值,因此a=&i是错误的
(2)a的类型是int[10],也可以看作int *。当一个指针存放了数组的首地址时,这个指针可以当作数组名使用,反之亦然。
假设有数组a和指针p的定义:
int *p,a[10];若需要指针指向数组的首地址,可以有两种方式:
1. p=&a[0];实现的功能是:将数组a起始元素a[0]的地址赋给指针p,p指向数组起始元素的地址,即p指向a数组的首地址。
2. p=a;实现的功能与p=&a[0];等价,也是让p指向a数组的首地址。
当指针p指向数组a的首地址后,指针p和数组名a等价,这样,既可以通过数组名a访问数组元素,也可以通过指p访问数组元素。例如:若有下标变量i,那么下面的操作均合法:
1. *p=0;实现的功能是将p所指向的a数组首位元素a[0]赋值为0,与a[0]=0;等价。
2. 也可以将数组名a当作指针使用。*a=0;实现的功能与*p=0;等价。
3. 指针p可以当作数组名使用p[i]=0;实现的功能是将p所指向的a数组的元素a[i]赋值为0,与a[i]=0;等价。
2.2指针的赋值运算
以下几种给指针变量赋值的方式都合法:
(1)p=&i; 完成的功能:将变量i的地址赋值给指针p,即:pi。
(2)p=a; 完成的功能:将数组a的首地址赋值给指针p,即:pa[0]。
(3)p=&a[i]; 完成功能:将数组元素a[i]的地址赋值给指针p,即:pa[i]
(4)p1=p2;完成功能:将指针变量p2的值赋值给指针p1,即:p1与p2指向同一个地址。
2.3指针的算术和关系运算
2.3.1指针加减整数
指针可以与整数进行加减操作,其结果也是一个指针,确切的说是个地址值。通常,“指针+整数”用于将指针向后移动“d*整数”个内存单元(其中d为指针指向的变量所占的字节数)。同理,“指针-整数”用于将指针向前移动“d*整数” 个内存单元(其中d为指针指向的变量所占的字节数)。
注意:编译器不会检查指针移动后,目的地址是否可用,如果移动失误,很可能会修改一些不能修改的内存单元,给程序带来致命的后果。因此限制这种“指针和整数的加减运算”智能在指针指向数组,不超出数组界限时使用。
2.3.2指针相减
当两个指针指向同一块内存区域时(例如:指向同一个数组),可以执行减法操作,结果为一个整数值,表示两个指针指向的位置之间相差几个元素。注意:如果两个指针不是指向同一块内存区域时,指针相减的结果是不可预测的,应该避免这种无意义的操作。就像同一条街道上的两个门牌号码相减,大致可以判断出中间相隔多少个房子。而不同街道上的门牌号码之间相减,是没有意义的。
2.3.3指针的关系运算
(1)表达式p<q为真,表示p指向的元素在前面,q指向的元素在后面
(2)表达式p>q为真,表示p指向的元素在后面,q指向的元素在前面
(3)表达式p==q为真,表示p和q指向同一元素
2.3.4指针的自增、自减
当指针指向数组时,可以指向自增、自减运算。另外,也可以通过指针间接地对其所指向的变量进行自增、自减运算。大致可以分为8种格式:
(1)指针变量++ 例如p++,即p++=p,p=p+1
(2)++指针变量 例如++p,即p=p+1,++p=p
(3)指针变量-- 例如p--,即p--=p,p=p-1
(4)--指针变量 例如--p,即p=p-1,--p=p
(5)指针变量*++ 例如*p++,即*p++=*p,p=p+1
或(*p)++,即(*p)++=(*p),(*p)=(*p)+1
(6)++*指针变量 例如 ++*p或++(*p),即(*p)=(*p)+1,++*p=*p
(7)*指针变量-- 例如 *p--即*p--=*p,p=p-1
或(*p)--即(*p)--=(*p),(*p)=(*p)-1
(8)--*指针变量 例如--*p或--(*p)即(*p)=(*p)-1,--*p=*p
注意:*和++、--的优先级相同,属于第二级,结合方向是自右向左。
2.3.5利用指针引用数组元素
当指针指向数组的首地址的时候,指针和数组名等价。此时,可以用两种来访问数组,分别是下标法和指针法。
2.3.6指针作函数的参数
函数的参数不仅可以是整型、实型、字符型,还可以是指针类型。当指针作函数的实际参数时,它可以所存地址传送到函数中,完成双向地址传递。当指针作函数的实际参数时,它可以将所存在地址传送到函数中,完成双向地址传递。当指针作函数的形式参数时,它可以接收实际参数传递的地址值,在函数内部可以存取或修改此地址下的数据。
一、指针作函数的形参,接收实参变量的地址,可以修改实参变量的值。
普通变量作函数的形参,从实参到形参传递的是变量的值,实现的是"单向值传递"。形参的改变无法传回给实参。在函数内部只能通过形参使用实参的值,无法修改它。
指针变量作函数的形参,从实参到形参传递的是变量的地址,实现的是“双向地址传递”。形参的改变可以传回给实参。在函数内部可以通过形参使用并修改实参的值。
二、指针和杉树分别做函数的参数,接收实参所传递的地址,修改实参地址中所存储的值。指针和数组分别作函数的参数,实参与形参的对应关系有四种形式。如下表所示:
实参 | 形参 |
数组名 | 数组 |
数组名 | 指针变量 |
指针变量 | 数组 |
指针变量 | 指针变量 |
将数组a中的n个整数按相反顺序存放
练习:将数组a中的元素按照从小到大的顺序排列
//将数组a中的元素按照从小到大的顺序排列
#include<stdio.h>
#define N 10
void fun(int *x){
int t,i,j;
for(i=0;i<=N-2;i++){
for(j=i+1;j<=N-1;j++){
if(*(x+i)>*(x+j)){
t=*(x+i);
*(x+i)=*(x+j);
*(x+j)=t;
}
}
}
}
int main(){
int i,a[N];
printf("请输入%d个数组元素:",N);
for(i=0;i<=N-1;i++){
scanf("%d",&a[i]);
}
printf("打印输出数组元素:");
for(i=0;i<=N-1;i++){
printf("%d,",a[i]);
}
fun(a);
printf("\n");
printf("数组升序排列后:");
for(i=0;i<=N-1;i++){
printf("%d,",a[i]);
}
}
3、指向字符串的指针
3.1字符指针和字符数组
C语言中,可以将字符串存放到字符数组中,数组名表示该字符串第一个字符存放的地址。如果将字符串的首地址或者字符数组名赋给一个字符型的指针变量,指针变量便指向了字符串。即:字符指针变量可以指向任一字符串的首地址。
分别定义字符数组和字符指针并为其初始化,以字符串的格式输出。
分别定义字符数组和字符指针,以字符串的格式输入、输出值。
修改字符数组名和字符指针的值
运行程序时出错,因为str数组名是一个常量,不能够修改它的值
3.2利用字符指针处理字符串
4、指针数组和指向指针的指针
4.1指针数组
数组的每一个元素都可以看作是一个变量,而指针是存储地址值的变量,因此,指针也可以作为数组元素存储在数组中,这时,数组称之为是指针数组。指针数组的定义格式是:
类型说明符 * 数组名 [整型常量或整型常量表达式]
例如:char *name[5];
name就是一个包含5个元素的指针数组,它的每个元素都是char *类型的,即:指向字符类型的指针。
4.1.1练习
用指针数组输出多个字符串
对图书目录的图书名称进行排序
4.2指向指针的指针
前面介绍的都是一级指针,一级指针可以指向普通的变量,本节要研究的是指向指针的指针,它实际上是多级指针。多级指针包含二级指针、三级指针以及更高级的指针。平时,使用最多的应该是二级指针。当某个指针变量所指向的不是普通变量,而是一个一级指针变量时,这个指针变量就称之为是二级指针。二级指针的基本格式为:
类型说明符 **指针名;
例如:char **p;
其中第一个*表示p指针指向的变量是char*类型的变量,即:指针变量。第二个*表示p本身的指针类型。
4.2.1练习
利用二级指针对图书目录中的图书名称进行排序
4.3数组指针
数组指针是指向一维或更多维数组的指针。数组指针的定义格式是:
类型说明符(*指针名)[数组最后一维的长度];
例如:int (*p)[4];
p是一个指针,指向一个最后一维长度是4的整型数组,4可以说是数组指针p的步长。若执行p+1,p在内存中实际移动的导航度是4*sizeof(int)。
二维数组的每一行都可以看作是一维数组组成的,因此,若需要一次操作二维数组的每一行,就可以先定义一个数组指针,然后让此指针依次指向二维数组的每一行。若利用p引用二维数组i行j列的一个元素a[i][j],有多种方式,包括:*p[i][j]、*(*p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]。
5、函数指针与指针函数
5.1函数指针
每个函数在编译时都会被分配一个入口地址,即:第一条指令的地址。如果将该地址赋给一个指针,那么指针变量就会存储了该函数的入口地址,该指针就指向了该函数,通常称这种指向函数的指针为函数指针。函数指针定义的一般格式为:
数据类型 (*指针名)(形参表列)
例如:int *p(int x,int y);
5.2指针函数
当一个函数的返回值是指数类型时,该函数称之为是指针函数。定义指针函数的一般格式如下:
函数类型 *函数名(形参表列)
{
说明部分
语句部分
}
注意:指针函数的声明和函数指针的格式非常相似,它们的区别在于函数指针比指针函数的声明多一对小括号。
int (*p)(int a[],int n);//定义函数指针
int *max(int a[],int n);//指针函数的声明
设计一个字符串查找函数,完成的功能是:在字符串中查找指定的字符串,并从第一次找到该字符串的位置开始输出字符串的内容。
#include<stdio.h>
#include<string.h>
char *seek(char *p1,char *p2)
{
int i,flag;
char *p;
for(p=p1;*p!='\0';p++)
{
flag=1;
for(i=0;*(p2+i)!='\0';i++){
if(*(p2+i)!=*(p+i))
{
flag=0;
break;
}
}
if(flag==1){
return p;
}
}
return NULL;
}
int main(){
char s1[30],s2[10],*s;
puts("enter string:");
gets(s1);
puts("enter word:");
gets(s2);
s=seek(s1,s2);
if(s!=NULL){
puts(s);
}
else{
puts("can't find this word.");
}
return 0;
}