c语言指针
前言
在c语言的中主要就是掌握指针和结构体、加链表吧。之前也只是学习过c语言的,但是多年来都做的面向对象的开发,对于面向过程的c语言,已经相忘于江湖了。最近由于在学习数据结构与算法,里面用到结构体链表,所以针对指针这块做了一下复习,现在把学习过程和体会记录下来。
为什么需要指针?
1.可指针的使用使得不同区域的代码可以轻易的共享内存数据,这样可以使程序更为快速高效;
2.使用指针传值,而有些操作传值调用是无法完成的, 如需要给函数传递一个结构体,结构体需要内存空间是20个字节,但是使用指针传递只需要4个字节,并可在函数内修改原结构体中元素的值。
3.一些复杂的数据结构往往需要使用指针来构建,如链表、二叉树等
4.所有指针所占用的内存空间都是4个字节(32机器)
什么是指针
C语言中,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。也就是说:指针是一种存放变量地址的变量
声明一个指针
指针其实就是一个变量,指针的声明方式与一般的变量声明方式没太大区别
int *p; // 声明一个 int 类型的指针 p
char *p // 声明一个 char 类型的指针 p
int *arr[10] // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针
int (*arr)[10] // 声明一个数组指针,该指针指向一个 int 类型的一维数组
int **p; // 声明一个指针 p ,该指针指向一个 int 类型的指针
指针的声明比普通变量的声明多了一个一元运算符 “*”。运算符 “*” 是间接寻址或者间接引用运算符。当它作用于指针时,将访问指针所指向的对象。在上述的声明中: p 是一个指针,保存着一个地址,该地址指向内存中的一个变量; *p 则会访问这个地址所指向的变量
指针变量的引用
指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机.指针变量的赋值只能赋予地址, 决不能赋予任何其它数据,否则将引起错误.在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。
两个有关的运算符:
1)&:取地址运算符.
2)*:指针运算符(或称“间接访问” 运算符).
C语言中提供了地址运算符&来表示变量的地址
其一般形式为: &变量名;
如&a 表示变量 a 的地址,&b 表示变量 b 的地址。变量本身必须预先说明.
设有指向整型变量的指针变量 p,如要把整型变量 a 的地址赋予 p 可以有以下两种方式:
(1) 指针变量初始化的方法
int a;
int *p=&a;
(2) 赋值语句的方法
int a;
int *p;
p=&a;
例1:
int i=200, x;
int *ip;
我们定义了两个整型变量 i,x,还定义了一个指向整型数的指针变量 ip.i,x 中可存放整数,而 ip 中只能存放整型变量的地址.我们可以把 i 的地址赋给 ip:
ip=&i;
此时指针变量 ip 指向整型变量 i,假设变量 i 的地址为 1800,这个赋值可形象理解为下图所示的联系.
以后我们便可以通过指针变量 ip 间接访问变量 i,例如:
x=*ip;
运算符*访问以 ip 为地址的存贮区域,而 ip 中存放的是变量 i 的地址,因此,*ip 访问的是地址为 1800 的存贮区域(因为是整数,实际上是从1800开始的两个字节),它就是i所占用的存贮区域, 所以上面的赋值表达式等价于
x=i;
例2:
int i,j,*p1,*p2;i='a';
j='b';
p1=&i;
p2=&j;
则建立如下图所示的联系:
这时赋值表达式:
p2=p1
就使 p2 与 p1 指向同一对象 i,此时*p2 就等价于 i,而不是 j,图所示:
如果执行如下表达式:
*p2=*p1;
则表示把 p1 指向的内容赋给 p2 所指的区域, 此时就变成图所示
通过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,而且不直观,因为通过指针要访问哪一个变量,取决于指针的值(即指向),例如"*p2=*p1;"实际上就是"j=i;",前者不仅速度慢而且目的不明.但由于指针是变量,我们可以通过改变它们的指向,以间接访问不同的变量,这给程序员带来灵活性,也使程序代码编写得更为简洁和有效.
指针变量可出现在表达式中, 设
int x,y,*px=&x;
指针变量 px 指向整数 x,则*px 可出现在 x 能出现的任何地方.例如:
y=*px+5; /*表示把x的内容加5并赋给y*/
y=++*px; /*px的内容加上1之后赋给y,++*px相当于++(*px)*/
y=*px++; /*相当于y=*px;px++*/
例3:
int a=5,*p=&a;
printf ("%d",*p);
表示指针变量 p 取得了整型变量 a 的地址。printf("%d",*p)语句表示输出变量 a 的值
指针变量的运算
1) 赋值运算:指针变量的赋值运算有以下几种形式
a.指针变量初始化赋值
int a=5;
int *p=&a;
b.把一个变量的地址赋予指向相同数据类型的指针变量
int a,*pa;
pa=&a; /*把整型变量 a 的地址赋予整型指针变量 pa*/
c.把一个指针变量的值赋予指向相同类型变量的另一个指针变量
int a,*pa=&a,*pb;
pb=pa; /*把 a 的地址赋予指针变量 pb*/
d.把数组的首地址赋予指向数组的指针变量
int a[5],*pa;
pa=a; (数组名表示数组的首地址,故可赋予指向数组的指针变量 pa)
也可写为:
pa=&a[0]; /*数组第一个元素的地址也是整个数组的首地址,
当然也可采取初始化赋值的方法:
int a[5],*pa=a;
e.把字符串的首地址赋予指向字符类型的指针变量
char *pc;
pc="C Language";
或用初始化赋值的方法写为:
char *pc="C Language";
也可赋予pa*/
这里应说明的是并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量
f.把函数的入口地址赋予指向函数的指针变量
int (*pf)();
pf=f; /*f 为函数名*/
2) 加减算术运算
对于指向数组的指针变量,可以加上或减去一个整数 n.设 pa 是指向数组 a 的指针变量,则pa+n,pa-n,pa++,++pa,pa--,--pa 运算都是合法的.指针变量加或减一个整数 n 的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动 n 个位置.应该注意,数组指针变量向前或向后移动一个位置和地址加 1 或减 1 在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的.如指针变量加 1,即向后移动 1 个位置表示指针变量指向下一个数据元素的首地址.而不是在原地址基础上加 1
int a[5],*pa;
pa=a; /*pa 指向数组 a,也是指向 a[0]*/
pa=pa+2; /*pa 指向a[2],即 pa 的值为&pa[2]*/
指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的.
#define NULL 0
int *p=NULL;
对指针变量赋 0 值和不赋值是不同的.指针变量未赋值时,可以是任意值,是不能使用的.否
则将造成意外错误.而指针变量赋 0 值后,则可以使用,只是它不指向具体的变量而已
数组指针和指向数组的指针变量
一个变量有一个地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址.所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址.
1.指向数组元素的指针
一个数组是由连续的一块内存单元组成的.数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的.每个数组元素按其类型不同占有几个连续的内存单元.一个数组元素的首地址也是指它所占有的几个内存单元的首地址
例:
int a[10]; /*定义 a 为包含 10 个整型数据的数组*/
int *p; /*定义 p 为指向整型变量的指针*/
应当注意,因为数组为int 型,所以指针变量也应为指向 int 型的指针变量.下面是对指针变量赋值:
p=&a[0];
把 a[0]元素的地址赋给指针变量 p.也就是说,p 指向 a 数组的第 0 号元素.
C 语言规定,数组名代表数组的首地址,也就是第 0 号元素的地址.因此,下面两个语句等价:
p=&a[0];
p=a;
2 通过指针引用数组元素
C 语言规定:如果指针变量 p 已指向数组中的一个元素,则 p+1 指向同一数组中的下一个元素。引入指针变量后,就可以用两种方法来访问数组元素了.
如果p 的初值为&a[0],则
a.p+i和a+i就是a[i]的地址,或者说它们指向a数组的第i个元素
b.*(p+i)或*(a+i)就是p+i或a+i所指向的数组元素,即a[i].例如,*(p+5)或*(a+5)就是a[5]
c.指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价
指向多维数组的指针和指针变量
1.多维数组的地址
int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}}
设数组 a 的首地址为 1000,各下标变量的首地址及其值如图所示:
C语言允许把一个二维数组分解为多个一维数组来处理.因此数组a可分解为三个一维数组,即 a[0],a[1],a[2].每一个一维数组又含有四个元素
例如 a[0]数组,含有 a[0][0],a[0][1],a[0][2],a[0][3]四个元素.数组及数组元素的地址表示如下:
从二维数组的角度来看,a 是二维数组名,a 代表整个二维数组的首地址,也是二维数组 0 行的首地址,等于 1000.a+1 代表第一行的首地址,等于 1008.如图:
a[0]是第一个一维数组的数组名和首地址,因此也为 1000.*(a+0)或*a 是与 a[0]等效的, 它表示一维数组 a[0]0 号元素的首地址,也为 1000.&a[0][0]是二维数组 a 的 0 行 0列元素首地址,同样是 1000.因此,a,a[0],*(a+0),*a,&a[0][0]是相等的.
同理,a+1 是二维数组 1 行的首地址,等于 1008.a[1]是第二个一维数组的数组名和首地址,因此也为 1008。&a[1][0]是二维数组 a 的 1 行 0 列元素地址,也是 1008。因此
a+1,a[1],*(a+1),&a[1][0]是等同的。
由此可得出:a+i,a[i],*(a+i),&a[i][0]是等同的.
2. 指向多维数组的指针变量
把二维数组a 分解为一维数组 a[0],a[1],a[2]之后,设 p 为指向二维数组的指针变量.
可定义为:
int (*p)[4]
它表示p 是一个指针变量,它指向包含 4 个元素的一维数组.若指向第一个一维数组
a[0],其值等于 a,a[0],或&a[0][0]等.而 p+i 则指向一维数组 a[i].从前面的分析可得
出*(p+i)+j 是二维数组 i 行 j 列的元素的地址,而*(*(p+i)+j)则是 i 行 j 列元素的值.
二维数组指针变量说明的一般形式为:
类型说明符 (*指针变量名)[长度]
其中“类型说明符”为所指数组的数据类型.“*”表示其后的变量是指针类型.“长度”表示
二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数.应注意“(*指
针变量名)”两边的括号不可少,如缺少括号则表示是指针数组,意义就完
全不同了.
字符串的指针指向字符串的针指变
1.用字符数组存放一个字符串,然后输出该字符串
char string[]=”I love China!”;
printf("%s\n",string);
字符数组和其他数组属性一样,string 是数组名,它代表字符数组的首地址.
2.用字符串指针指向一个字符串
char *string=”I love China!”;
printf("%s\n",string);
字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的.只能按对指针
变量的赋值不同来区别。对指向字符变量的指针变量应赋予该字符变量的地址.
如:
char c,*p=&c;
表示 p 是一个指向字符变量 c 的指针变量.
而:
char *s="C Language";
则表示s 是一个指向字符串的指针变量.把字符串的首地址赋予 s
例: 输出字符串中 n 个字符后的所有字符
char *ps = "this is a book";
int n =10;
ps = ps+n;
printf("%s\n",ps);
//结果:book
用字符数组和字符指针变量都可实现字符串的存储和运算.但是两者是有区别的.在使
用时应注意以下几个问题:
1. 字符串指针变量本身是一个变量,用于存放字符串的首地址.而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以‘\0’作为串的结束.字符数组是由于若干个数组元素组成的,它可用来存放整个字符串.
2. 对字符串指针方式
char *ps="C Language";
可以写为:
char *ps;
ps="C Language";
而对数组方式:
static char st[]={"C Language"};
不能写为:
char st[20];
st={"C Language"};
而只能对字符数组的各元素逐个赋值从以上几点可以看出字符串指针变量与字符数组在使用时的区别,同时也可看出使用指针变量更加方便.