C语言中&运算符和*运算符详解
前言
在学习C Primer Plus 数组与指针一章时,对取址运算符& 与间接运算符* 的使用,始终有些迷惑,直到又结合左值与右值进行了一次深入的学习,感觉有些清楚了,写篇文章总结一下。
提示:以下是本篇文章正文内容
一、取址运算符&与间接运算符*
1.取址运算符&
取值运算符& 是用来取得其操作对象的地址。如果操作对象x的类型为T,则表达式&x的类型是T类型指针(指向T类型对象 x的指针)。
取值运算符的操作对象必须是在内存中可寻址到的地址。换句话说,该运算符只能用于函数或对象(左值),而不可用于位字段,以及哪些还未被存储类修饰符register声明的内容。
当需要初始化指针,以指向某些对象或函数时,需要获得这些对象或函数的地址:
float x, *ptr;
ptr = &x; // 合法,使得指针ptr指向x
ptr = &(x+1); // 错误,(x+1)不是一个左值
2.间接运算符*
与取址运算符& 相反,间接运算符* 用于当已经具有一个指针,并希望获取它所引用的对象时,因此也被称为解引用运算符。
它的操作对象必须是指针类型。如果ptr是指针,那么 *ptr就是
ptr所指向的对象或函数。如果ptr是一个对象指针,那么 *ptr就是一个左值,可以把它即( *ptr)当做赋值运算符左边的操作数:
float x, *ptr = &x;
*ptr = 1.7; // 将1.7赋值给变量x
++(*ptr); // 并将变量x的值加1
在最后的语句中,ptr不变,但是变量x的值变为了2.7。
注意:千万不要解引用未初始化的指针!!!
int * ptr; // 未初始化的指针
*pt = 5; // 严重错误!!!
因为,ptr还未被初始化,其值是一个随机值,所以不知道5将储存在何处,可能不会出什么错,但是也可能擦写数据或代码,或者将导致程序崩溃,切记,创建一个指针时,系统只分配了储存指针本身的内存,并未分配储存数据的内存。 在使用指针前,务必先用已分配的地址初始化它,例如使用现有变量的地址初始化,或者使用malloc()函数分配内存。
3.优先级与结合律
像其他一元运算符一样,&运算符和*运算符有很高的优先级,结合律是从右向左。
例如:
int urn[5] = {100, 200, 300, 400, 500};
int * start;
start = urn;
total += *start++; // 使用后缀递增
total += *++start; // 使用前缀递增
total += (*start)++ // 解引用后递增
设想一下这三种形式的结果分别是什么?
一元运算符*和++的优先级相同,但是结合律是从右向左,所以:
第一种情况:先把指针指向位置上的值加到total上,然后再递增指针。
第二种情况:先递增指针,指向数组的下一个元素,在把指针指向的当前位置的值加到total上。
第三种情况:先解引用,获取指针指向位置上的值,在递增该值,在把递增后的值加到total上,指针将一直指向同一个位置,只是该位置上的值发生了变化。
运算符&和*是互补的:如果x是一个表达式,用于指定一个对象或一个函数,那么表达式 *&x就等于x。相反地,在形如 &*ptr的表达式中,这些运算符会互相抵消,表达式的类型与值等效于ptr。然而,不管ptr是不是左值,&*ptr都一定不会是左值。
二、左值与右值
左值(lvalue)是用来指明一个对象的表达式。最简单的左值就是变量名称。左值之所以称为“左”,是因为一个左值表示一个对象,它可以出现在赋值运算符的左边,例如“=”。
其他的表达式(那些能表示一个值但不指明一个对象的),就类似地称为右值(rvalue)。右值可以出现在赋值运算符的右边而不是左边的表达式。例如常量和算术表达式。
从一个左值中必定可以解析出对应对象的地址,除非该对象是位字段或者被声明为寄存器存储类。生成左值的运算符包括下标运算符[]和间接运算符*。
因此,上面说到的&*ptr必定不是左值。
由此可以总结一下,指针和数组表达式哪些可能是左值:
表达式 | 是左值吗 |
---|---|
array[1] | 是;一个数组元素是一个具有位置的对象 |
&array[1] | 否;数组元素对象的地址,不是一个具有位置的对象 |
ptr | 是;指针变量是一个具有位置的对象 |
*ptr | 是;指针元素所指的地址是一个具有位置的对象 |
ptr+1 | 否;指针的加法,产生一个新的地址,但是不能保证是一个对象 |
*ptr+1 | 否;此加法产生的是一个新的算术值,但不是一个对象 |
对象可以被声明为const常量,但是在这种情况下,该对象就不能位于赋值运算符的左边,尽管它是左值:
int a = 1;
const int b = 2, *ptr = &a;
b = 20; // 错误,b是const int
*ptr = 10; // 错误,ptr是const int指针
赋值运算左边的操作数,以及任何自增或自减运算符(++和–)的操作数,不仅应该是左值,还应该是可修改的左值。可修改的左值不能声明 限定符const,并且可修改的左值不能是数组类型。 如果可修改的左值所表示的对象时结构或联合类型,那么它的元素都不可以被声明(不管是直接或者间接地)为具有限定符const的类型。
三、总结
经过了两个运算符、左值及右值的学习,我认识到,数组和指针的学习中,除了要对指针、变量、地址、值有深刻的认识外,更要重视左值与右值的区别,在实际的使用中,不要非左值写在运算符的左侧,防止代码错误的发生。
See you next time!