C语言非常道
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

4.3 指针类型的变量

既然指针是一种数据类型,那么,我们应该可以声明这种类型的变量,这样就可以保存指针类型的值以方便传递和使用。没错,这当然是可以的。不过,在进入这一话题前,还是让我们先来回忆一下整数类型的变量声明。对于声明

                int m;

我们是这样来解读的:先从标识符m开始,读作“标识符m的类型是int”,或者更经常地,读作“m是int类型的变量”,这个变量只用来保存和读出int类型的值。

相应地,要把一个指针类型的值保存在一个变量里,这个变量的类型也应该是指针类型才可以。而且,变量的类型必须和指针的类型一致,要保存一个指向char类型的指针,变量的类型也必须是指向char的指针。再比如,如果变量的类型是指向int的指针,则它应该这样声明:

                int * p;

如图4-3所示,这照例要从标识符开始认起。因为标识符p的右边是分号“;”,所以要向左读。左边是一个星号“*”,所以读作“p的类型是指针”或者“p是一个指针”。

图4-3 声明一个变量,其类型为指向int的指针

对于指针类型来说,指向的类型最为关键,如果指向的类型不同,则它们是不同的指针类型。因此,如图中所示,既然p的类型是指针,那么下一步就是将指针所指向的类型读出来。因为标识符p右边只是分号“;”,那我们继续向左读,左边是类型指定符“int”,所以读作“指向int”。

现在,我们把这个过程连起来,就可以说“p是一个指针,该指针指向int”,或者说“p是一个指向int的指针”,又或者说“变量p的类型是指向int的指针”;或者干脆简单地说“p是一个指针类型的变量”。

然而为了方便,人们更经常地把指针类型的变量叫作指针,相对于“p是一个指针类型的变量”,人们更喜欢说“p是一个指针”。在具体的上下文中,这通常不会引起混淆,多数教材和图书都是这么用的,但本书尽量避免这种叫法。

在程序中,我们会声明很多变量和函数,但如果要问起它们是什么类型,类型的名字是什么,该怎么描述呢?为了方便描述复杂的类型,C语言引入了类型名。所谓类型名,顾名思义,就是类型的名字。要想得到一个类型的类型名,需要先在纸上或者脑海里构造一个变量或者函数的声明,然后去掉标识符和末尾的分号,剩下的部分就是类型名。给定声明:

              unsigned int var;
              signed char * pc;
              _Bool do_sth(signed char, signed char);

要想指出标识符var、pc和do_sth的具体类型是什么(或者说它们是什么类型),可以用文字描述,但这样做很啰唆,用类型名比较简洁直观。

为了得到类型名,只需要把这三个标识符从它们的声明中去掉,末尾的分号也去掉,剩下的部分就是类型名。因此我们说变量var的类型是unsigned int;变量pc的类型是signed char *;函数do_sth的类型是_Bool(signed char, signed char)。

为了说明如何使用指针类型的变量,下面给出一个示例程序。在程序中,我们声明了三个变量,一个是变量x,其类型为int;另外两个分别是变量p和q,它们的类型都是指向int的指针。

注意星号“*”的位置,它是独立的,意思是“指针”,不和类型指定符int结合,也不和标识符p、q结合,但它是声明符的一部分。我们曾经提到过声明符,声明符用于描述被声明的实体,在这里,x是声明符,* p和* q也是声明符。带有星号的p和q意味着实体是指针,不带星号则意味着实体不是指针。

              /**********c0402.c********/
              int main(void)
              {
                  int x, * p = & x, * q;

                  q = & x;
                  * p = 1;
                  x = * p + * q;
              }

在这里,变量p的声明中带有一个初始化器,即表达式& x。因为x是一个int类型的左值,所以表达式& x的值是指向int的指针,这个指针由变量x的地址转化而来。而p的类型呢,也是指向int的指针,类型匹配。

现在,变量p的值是一个指针,实际上是指向变量x的,但变量q没有初始化,它的内容是随机的,不被认为是一个有效的指针。不过没关系,语句

              q = & x;

就是用来给变量q赋值的。变量q的类型是指向int的指针,而表达式& x的类型也是指向int的指针,两边类型一致,可以赋值。赋值之后,变量q的值指向变量x。换句话说,如图4-4所示,变量p和q的值都是指向变量x的指针。

图4-4 指针类型的变量及其值所指向的变量

再来看语句

              * p = 1;

表达式* p中的p是一个左值,要执行左值转换,转换为变量p的存储值,这是一个指针(类型的值)。因为一元*运算符的操作数是指针,故表达式* p的结果是一个左值为方便起见,可直接称之为“左值* p”。本书后面的行文中,类似的情况也照此办理。,代表一个变量,但因为它是赋值运算符=的左操作数,故不再进行左值转换。所以表达式*p = 1是把1赋给左值* p,也就是把1保存到左值* p所代表的那个变量里。

C语言的一个特点是变量和函数的声明与它们在程序中的用法在形式上一致。在变量p的声明中,星号“*”是一个符号,意思是“指针”,“* p”的意思是p是一个指针;而在表达式中,星号“*”是一个运算符,“* p”是一个表达式,用于间接地通过变量p的值来得到它所指向的变量。

再往下看另一条语句

              x = * p + * q;

在表达式* p和* q中,p和q都是左值,需要先进行左值转换,转换为变量p和q的存储值。左值转换后的值都是指针,所以表达式* p和* q又都是左值,都要继续进行左值转换,转换为它们所代表的变量的存储值。然后,将这两个值相加,并赋给左值x。上述语句执行后,变量x的值为2。

为了加深理解,我们再换一种说法:左值p和q经左值转换后的值都是指向变量x的指针,所以表达式* p和* q的结果都是左值,都代表变量x。这两个左值经左值转换,转换为变量x的存储值,相加后赋给左值x。因为这个原因,上述语句实际上等价于:

              x = x + x;

练习4.2

1.给定声明:

              int m, * p, f(void);

请指出其中的声明符。

2.拓展训练:在调试本节的程序时,可使用“p p”命令打印变量p的值(也就是变量x的地址),也可以使用“p &p”打印变量p自身的地址;还可以使用“p *p”命令打印p的值所指向的那个变量的值(也就是变量x的值)。

3.给定声明:

              int * x, p = & x, q;

请解释该声明合法或者不合法的原因。