嵌入式Linux C语言(二)——指针

    指针是C语言中广泛使用的一种数据类型,是C语言的灵魂。指针提供了动态操控内存的机制,强化了对数据结构的支持,而且实现了访问硬件的功能。学习指针是学习C语言中最重要的一环,能否正确理解和使用指针是我们是否掌握C语言的一个标志。

一、指针的概念

在计算机中,所有的数据都是存放在内存中的,一般把内存中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不一样,如int占用4个字 节,char占用1个字节。为了正确地访问内存单元,必须为每个内存单元编上号。每个内存单元的编号是唯一的,根据编号可以准确地找到该内存单元。内存单元的编号叫做地址(Address),也称为指针(Pointer)。因此理解指针的关键在于理解C程序如何管理内存。

内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系我们每个人都有一个编号唯一的×××账户,×××存储了我们每个人的身份信息,×××号码就是账户的指针, 身份信息是账户的内容。对于一个内存单元来说,单元的地址(编号)即为指针,其中存放的数据才是该单元的内容。

在C语言中,存放指针的变量称为指针变量。一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。
    设有字符变量c,其内容为 'K'ASCII码为十进制数 75),c占用了0X11A号内存单元(地址通常用十六进数表示)。设有指针变量p,内容为 0X11A,这种情况我们称为p指向变量c,或说p是指向变量c的指针。

二、C语言指针类型分析

1C语言常见指针类型分析

int p;

    这是一个普通的整型变量

int *p;

    首先从P处开始,先与*结合,所以说明P是一个指针,然后再与int结合,说明指针所指向的内容的类型为int .所以 P是一个返回整型数据的指针

int p[3];

    首先从P处开始,先与[]结合,说明P 是一个数组,然后与int结合,说明数组里的元素是整型的,所以 P是一个由整型数据组成的数组 

int *p[3];

    首先从P处开始,先与[]结合,因为其优先级比*,所以P是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与 int结合,说明指针所指向的内容的类型是整型的,所以是一个由返回整型数据的指针所组成的数组 

int (*p)[3];

    首先从P处开始,先与*结合,说明P是一个指针然后再与[]结合("()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P是一个指向由整型数据组成的数组的指针 

int **p;

    首先从 P开始,先与*结合,说明P是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与 int结合,说明该指针所指向的元素是整型数据. 所以P是一个返回指向整型数据的指针的指针 

int p(int); 

    从P处起,先与()结合,说明P是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数然后再与外面的int 结合,说明函数的返回值是一个整型数据.所以P是一个有整型参数且返回类型为整型的函数 

int (*p)(int);

    从P处开始,先与指针结合,说明P是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P是一个指向有一个整型参数且返回类型为整型的函数的指针 

int *(*p(int))[3];

    从 P开始,先与()结合,说明P是一个函数,然后进入()里面,int结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P是一个参数为一个整数且返回一个指向由整型指针变量组成的数组的指针变量的函数

2指针分析

    指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。 要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。

A指针的类型

把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型 

B指针所指向的类型

把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型(在指针的算术运算中,指针所指向的类型有很大的作用) 

C指针所指向的内存区

指针变量定义之后必须赋值才能使用,指针所指向的内存区从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。一个变量指针指向了某块内存区域,就相当于说该指针变量的值是这块内存区域的首地址。如果指针变量指向的内存区未初始化,则该内存区保存的是垃圾数据。如同变量定义之后不赋值就会是垃圾值一样,指针定义后也必须赋值指定合法内存地址,否则解引用将会出错(段错误)

 D指针本身所占据的内存区

用函数sizeof(指针的类型)可以测出指针本身所占据的内存区(在 32位平台里,指针本身占据了 4个字节的长度) 

3指针的强制类型转换

    当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式,这就要求两边的类型一致,所指向的类型也一致,如果不一致的话,需要进行强制类型转换。语法格式是:(TYPE *)p。

这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE *,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。要注意的是,原来的指针p的一切属性都没有被修改。 

    另外,一个函数如果使用了指针作为形参, 那么在函数调用语句的实参和形参的结合过程中,也必须保证类型一致 ,否则需要强制转换。

4、const与指针

const int *p;指针p为变量,指针指向的内存空间存储的是常量

int const *p;指针p为变量,指针指向的内存空间存储的是常量

int * cosnt p;指针是常量,指针指向的内存空间存储的是变量

const int * const p;指针是常量,指针指向的内存空间存储的是常量

 

5、野指针

野指针是指向的地址不可预知的指针。野指针容易触发运行时段错误。一般来说,指针定义后如果没有赋值就使用就是一个野指针,指向的内存地址空间是不确定的。避免野指针的一般做法:

    A、定义指针时初始化为NULL

    B、解引用指针前将指针赋值

    C、解引用指针前检查指针是否是NULL

    D、指针使用完毕后赋值NULL

int a;

int *p = NULL;

p = &a;

if(NULL != p)

{

*p = 3;

}

p = NULL;

 

三、指针变量的赋值

    指针变量同普通变量一样,使用之前不仅要定义说明, 而且必须赋予具体的值。未经赋值的指针变量不能使用, 否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。在C语言中, 变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。 C语言中提供了地址运算符&来表示变量的地址。其一般形式为: & 变量名; 如&a变示变量a的地址,&b表示变量b的地址。 变量本身必须预先说明。指针变量的赋值方式有两种:

1、指针变量初始化的方法

int a;

int *p = &a;

2赋值语句的方法

int a;

int *p;
p = &a;
    不允许把一个数赋予指针变量,故下面的赋值是错误的int *p;p=1000; 被赋值的指针变量前不能再加“*”说明符,如写为*p=&a 也是错误的

四、指针变量的运算

    指针变量可以进行某些运算,但其运算的种类是有限的。 它只能进行赋值运算和部分算术运算及关系运算。

1、指针运算符

A取地址运算符&

   取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。&a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。

B取内容运算符*

   取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。需要注意的是指针运算符*和指针变量说明中的指针说明符* 不是一回事。在指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量。

2、指针变量的运算

A、赋值运算

指针变量的赋值运算有以下几种形式:

指针变量初始化赋值

把一个变量的地址赋予指向相同数据类型的指针变量

int a,*pa;

pa=&a; /*把整型变量a的地址赋予整型指针变量pa*/

把一个指针变量的值赋予指向相同类型变量的另一个指针变量

int a,*pa=&a,*pb;

pb=pa; /*a的地址赋予指针变量pb*/

把数组的首地址赋予指向数组的指针变量

int a[5],*pa;

pa=a; (数组名表示数组的首地址,故可赋予指向数组的指针变量pa)

把字符串的首地址赋予指向字符类型的指针变量。

char *pc;pc="c language";

char *pc="C Language";

把函数的入口地址赋予指向函数的指针变量

int (*pf)();

pf=f; /*f为函数名*/

 

B、加减算术运算

    指针变量加或减一个整数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]*/ 指针变量的加减运算只能对数组指针变量进行, 对指向其它类型变量的指针变量作加减运算是毫无意义的

C指针变量之间的运算

    两个指针变量之间的运算只有指向同一数组的两个指针变量之间才能进行运算, 否则运算毫无意义。

两指针变量相减

    两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址) 相减之差再除以该数组元素的长度(字节数)。例如pf1和pf2 是指向同一浮点数组的两个指针变量,设pf1的值为2010H,pf2的值为2000H,而浮点数组每个元素占4个字节,所以pf1-pf2的结果为 (2000H-2010H)/4=4,表示pf1和 pf2之间相差4个元素。两个指针变量不能进行加法运算。 例如, pf1+pf2是什么意思呢?毫无实际意义。

两指针变量进行关系运算

    指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如:

pf1==pf2表示pf1pf2指向同一数组元素
pf1>pf2表示pf1处于高地址位置
pf1<pf2表示pf2处于低地址位置

    指针变量还可以与0比较。设p为指针变量,则p==0表明p是空指针,它不指向任何变量;p!=0表示p不是空指针。空指针是由对指针变量赋予0值而得到的。例如: #define NULL 0int *p=NULL; 对指针变量赋0值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向 具体的变量而已。

 

 

本文是学习网络博文并经自己思考总结整理而来,博文来源有:

C语言中文网、C语言指针总结(开源中国-宏哥)、深入理解C指针(Richard Reese)、C语言指针详解(CSDN ad_ad_ad)。

由于网络博文繁杂,无法一一查明原出处,所列来源为本人学习时所查阅资料。