嵌入式Linux C语言程序设计七
指针
7.1 指针基础
内存单元的地址称为指针.专门用来存放地址的变量,称为指针变量(pointer variable).
7.1.1 指针变量的定义
类型说明符 *变量名;
其中,"*"表示一个指针变量,变量名即为定义的指针变量名,类型说明符表求本指针变量所旨向的变量的数据类型,例如
int *p1
以上代码表示p1是一个指针变量,它的值是某个整型变量的地址,或者说p1指向一个整型变量,到于p1究竟指向哪一个整型变量,应由向p1赋予的址址来决定.
static int *p2;
float *p3;
char *p4;
对于指针变量的定义,需要注意以下两点:
- 指针变量的变量名是 "*"后面的内容,而不是 “*p2”,"p3",""只是说明定义的是一个指针变量.
- 虽然所有的指针变量都是待长的,但仍然需要定义指的类型说明符,因为对指针变量的其他操作(如加,减)都涉及指针所指向变量的数据宽量.要注意的是,一个指针变量只能指向同类型的变量.
7.1.2 指针变量的赋值
指针变是在使用前不仅要定义说明,而且要赋予具体的值,未经赋值的指针变是不能随便使用,否则造成程序运行错误.指针变量的值只是变量的地址,不能是其他数据,否则将引起错误.
在C语言中,变量的地址是由编译系统分配的,用户不知道变量的具体址.C语言中提供了地址运算符"&" 来取变量的地址.
&变量名;
int i ,*p;
p = &i;
int i ,*p=&i;
#include <stdio.h>
int main(int argc,char *argv[])
{int m = 100;int *p;p = &m;printf("%d %d\n",sizeof(m),sizeof(p));printf("%d %p %p %p\n",m,&m,p,&p);return 0;
}
7.1.3指针变是的引用
指针指向的内存区域中的数据称为指针的目标.如果它指向的区域是程序 中的一个变量的内存空间.则这个变量称为指针的目标变量.
指针的目标变量简称为指针的目标.对指针目标的控制,需要用到下面两个运算符;
- &—取地址运算符
-
- —指针运算符(间接存取运算符).
例:“&a"就是取变量a的地址,而"b"就是取指针变量b所指向的存储单元里的内容.通过一个指针访问它所指向的对象的值称为变量的间接访问(通过操作符"”)
对于指针的引用经常会出现以下错误:
int *a;
*a = 52;
虽然已经定义了指针变量a,但并没有对它时行初始化,也就是说没有让a指向一个对象.这里,变量a的值是不确定的.即随机指向一个内存单元,这样的指针被称为"野指针".这样的代码在执行时通常会出现"segmentation fault"错误,原因是访问了一个非法地址.因上在对指针变量时行间接引用之前一定要确保它们已经被指向一个合法的对象.
#include <stdio.h>int main()
{int *p1,*p2,a,b;a = 1;b =20;p1=&a;p2 = &b;printf("a=%d,b=%d\n",a,b);printf("*p1=%d,*p2=%d\n",*p1,*p2);printf("&a=0x%x,&b=0x%x\n",&a,&b);printf("p1=0x%x,p2=0x%x\n",p1,p2);*p1 = 20 ;printf("After changing *p.......\n");printf("a=%d,b=%d\n",a,b);printf("*p1 = %d,*p2 =%d\n",*p1,*p2);return 0;
}
#include <stdio.h>
int main()
{int *p1,*p2,a,b,c,d;a = 1;b = 20;p1 = &c;p2 = &d;printf("a = %d,b=%d\n",a,b);printf("*p1=%d,*p2=%d\n",*p1,*p2)
}
7.2指针的运算
指针运算是以指针变量所存放的值作为运算量而进行的运算.因此,指针运算的实质就是地址的计算.
指针运算的种类是有限的,它只能进行算术运算,关系运算 和赋值运算前面已经价绍过了,这里不再重复.
| 运算符 | 计算形式 | 意义 |
|---|---|---|
| + | p+n | 移动指针 |
| - | p-n | 移动指针 |
| ++ | p++或++p | 移动指针 |
| – | p–或–p | 移动指针 |
| - | p-q | 两个指针之间相间数据元素个数 |
不同数据类型的两个指针寮行加减整数运算是无意义的.
指针的加减运算
int main(int argc,char *argv[])
{int m = 100;double n = 200;int *p;p = &m;q = &n;printf("m=%d &m=%p\n",m,&m);printf("p=%p p+2=%p\n",p,p+2);printf("\nn=%lf &n=%p\n",n,&n);printf("q=%p p+2=%p\n",q,q+2);return 0;
}
sizeof(int) = 4 p+2地址增加两个整数.其他类型的依次类推
指针与指针的运算
#include <stdio.h>
int main(int argc,char *argv[])
{int m = 100;double n = 200;int *p1,*p2;double *q1,*q2;p1 = &m;p2 = p1+p2;q1 = &n;q2 = q1+2;printf("p1=%p p2=%p\n",p1,p2);printf("p2-p1=%p\n",p2-p1);printf("q1=%p,p2=%p\n",q1,q2);printf("q2-q1=%p\n",q2-q1);return 0;
}
地址相减不是地址量,而是两指针相隔数据的个数.
7.2.2 指针的关系运算
注意问题
- 具有不同数据数据类型的指针之间的关系运算没有意义,指向不同数据区域的数据两指针之间,关系运算也没有意义.
- 指针与一般整数变量之间的关系运算没有意义.但可以和零进行等于或不等于的关系运算,判断指是否为空.
指针关系运算
#include <stdio.h>
#include <string.h>/*在程序中,指针p指向字符数组的第一个字符w,指针q指向最后一个字符e,循环中指针p往地址大的方向移动,指针q往地址小的方向行移动,当指针相等时,停止字符交换.
*/
int main()
{char s[] = "welcome";char *p = NULL,*q = NULL ,t;printf("%s\n",s);p = s;q = s+strlen(s)-1;while(p<q){t = *p;*p = *q;*q = t;p++;q--;}printf("%s\n",s);return 0;
}
指针 * 与 ++ 的优先级是一样的,结合性自右向左.当cp++相当于(cp++),++cp相当于(++p).
指针的表达式
#include <stdio.h>
int main()
{char *s = "123";int num = 0;while(*s!="\0"){num = num * 10+*s - '0';s++; }printf("num = %d\n",num);
}
当程序中使用了指针,有比较复杂的指针表达式时,一定要清楚指针的当前值.
7.2.3 空指针
这里所说的空指针,指的是批针变量存了零号地址,在程序中呆以为指针赋零,很显然,可以用 int *p = 0;c语中指针定义了一个NULL指针,其就代表了0.在实际编程中, NULL指针的使用是非常普遍的,因为它可以用来表明一个指针目前并未消指向任保对象.
NULL指针用例:
int main()
{int *p = NULL;printf("%d\n",*p);*p = 10;return 0;
}
将指针设置为NULL,这是一个好的习惯,但不可以对空指针进行间接引用操作.
7.3 指针与数组
7.3.1 指针与一维数组
- 数组的指针
数组元素的地址是指数组元素在内存中的超始地址.可以由各个元素加上取地址符"&"构成.&a[0],就表示数组中第一个元素的址址,&a[1]就表于数组中第二个元素的地址.
int a[10]; //a 和 &a[10]
在这是引出一个概念数组指针.数组指针是指向数组起始地址的指针,基本质为指针.一维数组的数组名为一维数组的指针. - 数组元素的表示
读者已经知道,指针也可以进行运算
a+i-->a[i]
事实上,在C语言中指针的效率往往高于数组下标,因此,编译器对程序中数组下标的操作全部转换为对指针的偏移量的操作.
例:
int main()
{int a[] = {9,1,34,7,3,10},i;int *p,n;p = a;n = sizeof(a)/sizeof(int);for(i = 0;i<n;i++){printf("%d %d %d\n",*(a+i),a[i],*(p+i),p[i];)}return 0;}
需要特别说明的一点是,指针变量和数组在访问数组中元素时,一定条件下其使用方法具有相同的形式,因为指针变量和数组名都是地址量.但指针变量和数组的指针(数组名)在本质上不同,数组在内存中的位置在程序地运行过程中是无法动态改变的,因比,数组名是地址常 ,指针是地址变量.数组名可以在运算中作为指针参与,但不允许被值.
例 :
#include <stdio.h>
int main()
{int a[10],b[10];int i,*p;p = b;a = p;for(i = 0;i<10;i++){printf("a[%d]is %d\n",i,a[i]);}return 0;
}
7.3.2 指针与多维数组
- 列指针遍历二维数组
例:
#include <stdio.h>
int main()
{int a[][3] = {9,1,4,7,3,6},i,j;int *p,r,c,n;p = &a[0][0];r = sizeof(a)/sizeof(a[0]);c = sizeof(a[0])/sizeof(int);n = sizeof(a)/sizeof(int);for(i = 0;i<r;j++){for(j =0;j<c;j++){printf("%d %p\n",a[i][j],&a[i][j]);}printf{"\n"}}for(i = 0;i<n;i++){printf("%d %p\n",*(p+i),p+i);}return 0;
}
- 行指针遍历二维数组
所谓的行指针是个元素为指向一维数组的指针的指针,一组数组的大小就下式[表达式]
<存储类型> <数据类型> (*<指针变量名>)[表达式]
例:
#include <stdio.h>
int main()
{int a[2][3] = {{8,2,6},{1,4,7}};printf("a:%p\t a+1:%p\ta+2:%p\n\n",a,a+1,a+2);printf("a[0]:%p\t&a[0][0]=%p\n",a[0],&a[0][0]);printf("a[1]:%p\t&a[1][0]=%p\n",a[1],&a[0][0]);printf("a[2]:%p\t&a[2][0]=%p\n",a[2],&a[0][0]);return 0;
}
$ ./a.out
a:0x7ffe8fae30d0 a+1:0x7ffe8fae30dc a+2:0x7ffe8fae30e8a[0]:0x7ffe8fae30d0 &a[0][0]=0x7ffe8fae30d0
a[1]:0x7ffe8fae30dc &a[1][0]=0x7ffe8fae30d0
a[2]:0x7ffe8fae30e8 &a[2][0]=0x7ffe8fae30d0
可以看出二维数组名是一个很特殊的地址,参与运算时以行为单位移动,因此被称为行地址.在该程序中,a代表第一行的首地址,a[0],&a[0][0]代表一行第一列元素的地址;a+1代表第二行的首地址,a[1],&a[1][0]代表第二行第一列元素的地址,依次类推.
#include <stdio.h>
int main()
{int a[][3] = {9,1,4,7,3,6},i,j,r,c;int (*p)[3]p = a;r = sizeof(a)/sizeof(a[0]);c = sizeof(a[0])/sizeof(int);for(i = 0;i<r;j++){for(j = 0;j<c;j++){printf("%d %d %d",a[i][j],*(*(a+i)+j),*(a[i]+j));printf("%d %d %d",p[i][j],*(*(p+i)+j),*(p[i]+j));}}
}
7.4 多级指针
7.4.1 多级指针的定义及引用
把一个指向指针变量的指针变量,称为多级指针变量.对于指向处理数据指针变量称为一级指针变量,简一级指针.而把指向一级批针变量称为二级指针变量.简称二级指针.
<存储类型> <数据类型> **<指针名>
例:
#include <stdio.h>
int main(int argc,char *argv[])
{int m = 100,*p;int **q;p = &m;q = &p;printf("m=%d\t&m=%p\tp=%p\t&p=%p\n",m,&m,p,&p);printf("q=%p\t&q=%p\t*q=%p\t**q=%d\n",q,&q,*q,**q);return 0;
}
7.4.2 多级指针运算
例 int *p p+1移动 一个int 变量所占用的内存空间,int *p ,p+1移动一个int 所占用的内存空间.
#include<stdio.h>
int main(int argc,char *argv[])
{int n = 100,*p;int **q;p = &m;q = &p;printf("q =%p\t&q=%p\t*q=%p **q=%d\n",q,&q,*q,**q);printf("q+1=%p\nsizeof(q)=%d\n",q+1,sizeof(q));return 0;
}
7.5 指针数组
7.5.1 指针数组的定义及初始化.
所谓指针数组是指由若干个具有相同存储类型和数据类型的指针变量构成的集合.
<存储类型> <数据类型> *<指针变量数组名>[<大小>]
int *p[2]
char *p[9]
关于指针数组的初始化 用例:
#include <stdio.h>
int main(int argc,char *argv[])
{int m = 100,n = 200;int *p[2];p[0]=&m;p[1]=&n;printf("sizeof(p)=%d\n",sizeof(p));printf("&m=%p\t&n=%p\n",&m,&n);printf("p[0]=%p\np[1]=%p\n",p[0],p[1]);printf("p=%p\n&p[0]=%p\n&p[1]=%p\n",&p[0],&p[1]); printf("\nm=%d\nn=%d\n",m,n);printf("*p[0]=%d\n*p[1]=%d",*p[0],*p[1]);printf("**p=%d\n**(p+1)=%d\n",**p,**(p+1));return 0;
}
7.5.2 理解指针数组名
对于指针数组的数组名,也代表数组的起始地址.由于数组的元素已经是指针了,数组名就是数组首元素的地址,因此数组名是指针的地址,是多级指针了,数组名就是数组首元素的地址,因此数组名是指针的地址,是多级指针了.
比如指针数组 int* p[N];数组名p代表&p[0],p[0]是int *,则&p[0]就是int ** ,若用指针存储数组的超始地址p或&p[0],可以这样用: int **q=p
#include <stdio.h>
int main(int argc,char *argv[])
{int a[3][2]={9,6,1,7,8,3};int *p[3],i,j;int **q;p[0]=a[0];p[1]=a[1]p[2]=a[2];q = p;for(i = 0;i<3;i++){for(j=0;j<2;j++){printf("%d %d %d",*(p[i]+j),*(*(p+i)+j),p[i][j]);printf("%d %d %d",*(q[i]+j),*(*(q+i)+j),q[i][j]);}printf("\n");}return 0;
}
7.6 const与指针
在C语言中,关键字const修饰变量,可以使得变量常量化.const修饰基本简单类型(非指针)时,很容易理解,变是变量的值不允许修改
const int m = 10;
int const m = 10;
const 优先修饰左的,如果左边没有就修饰右边的**
- 常量化指针目标表达式
常量化指针目标是限制通过指针改变其目标的数值.
const <数据类型> *<指针变量名称>[=<指针运算表达式>]
#include <stdio.h>int main(int argc,char *argv[])
{int m = 100,n = 200;const int *p;p = &m;printf("1)%d %p %p\n",m,&m,*p,p);m++;printf("2)%d %p %d %p\n",m,&m,*p,p);p = &n;printf("3)%d %p %d %p\n",n,&n,*p,p);
#ifdef _DEBUG_*p = n;printf("4)%d %p %d %p\n",m,&m,*p,p);
#endifreturn 0;
}
gcc const.c -D _DEBUG_ -Wall
- 常量化指针变量
常量化指针变量,使得批针变量存储的地址值不能修改.
<数据类型> *const<指针变量名>=<指针运算表达式>;
#include <stdio.h>int main(int argc,char *argv[])
{int m = 100,n = 200;int *const p = &m;printf("1)%d %p %d %p\n",m,&m,*p,p);m++;printf("2)%d %p %d %p\n",m,&m,*p,p);*p=n;printf("3)%d %p %d %p\n",m,&m,*p,p);
#ifdef _DEBUG_p = &n;printf("4) %d,%p,%d,%p\n",n,&n,*p,p);
#endifreturn 0;
}
3.常量化指针变量及其目标表达式
const <数据类型> * const <指针变量名> = <指针运算表达式>;
7.7 void指针
对于void型的指针变量,实际使用的,一般需通过强制类型转换才能使void型指针变量得到具体变量或数组地址.在没有强制类型转换之前,void型指针变量不能进行任何指针算术运算.
#include <stdio.h>
int main(int argc,char *argv[])
{int m = 100;void *p;p = (void*)&m;printf("%d %p %d %p\n",m,&m,*p,p);return 0;
}
在该程序中,使用void指针时,应该首选进行类型转换.尤其是引用指针的目标值,若没有经过转换,void* 相当于类开型不确定,只知道指针目标的超始地址,不知道其占用的字节数,没办法处理,是编译错误.类似的道理.如果void指针没有转换成具体的指针类开型,也不可以时行指什的运算,因为没办法决定以什么为单位来偏移.
对于void型的指针变量,实际使用时,一般需通过强制转换才能使类型转换才能使void型指针变量进行指针的运算或得到其目标的值.
7.8 字符指针
字符指针就是存储字符变量的地址.
7.8.1字符串
例:
#include <stdio.h>
#include <string.h>
int main()
{char s[10];char *p,*q,t;printf("input a string:");scanf("%s",s);p = s;q = s+strlen(s)-1;while(p<q){t = *p;*p = *q;*q = t;p++;q--;}printf("%s\n",s);return 0;
}
初始化字符指针,可以把内存中字符串的首地址赋予指针.这时,并不是把该字符串复到指针中,而是让指针指向字符串的起始字符.
#include <stdio.h>
int main()
{char *s = "Welcome";printf("%s",s);s++;printf("%s\n",s);
#ifdef _DEBUG_(*s)++;printf("%s\n",s);
#endif
}
在使用字符指针指向字符数组时,有以下两点需要注意:
- 虽然数组名也表示数组的首地址,但由于数组名为指针常量,其值是不能改变的(不能自行加,减,赋值等操作).如果把字符数组的首地址赋给一个字符指针变量,就可以移动这个指针来访问或修改数组中的字符.
- 在使用scanf时,其参数前要加上取地址符&,表明是一个地址,如果是指针变量的值已经是一个字符数组的首地址,那么可以直接把指针变量作为参数而不需要再加上取地址符&
当一个字符指针初始化为指向一个字符串常量时,不能对字符指针变量的目标赋值.
7.8.2 字符指针数组
若数组中囓 储了若干个字符串的地址,则这样的数组就叫作字符指针数组.
#include <stdio.h>
#include <string.h>int main(int argc,char *argv[])
{char s1[]="Welcome";char s2[]="to";char s3[]="china";char *a1[3] = {s1,s2,s3};char *a2[3] ={"Welcome","to","china"};char **p;int i;p = a1;printf("array1:%s %s %s\n",a1[0],a1[1],a1[2]);for(i = 0;i<sizeof(a2)/sizeof(char *);i++){printf("%s ",*(p+i));}printf("\n");p = a2;printf("array2:%s %s %s\n",a2[0],a2[1],a2[2]);for(i = 0;i<sizeof(a2)/sizeof(char *);i++){printf("%s ",*(p+i));}printf("\n");return 0;
}