对象定位值

可修改的左值(对象定位值),用于标识可修改的对象。右值可以是常量、变量或其他可求值的表达式。


赋值运算符=

在C语言中,类似这样的语句没有意义(实际上是无效的)。

2002 = bmw;

因为在这种情况下,2002被称为右值(rvale),只能是字面常量。不能给常量赋值,常量本身就是他的值。因此,在编写代码时要记住,=号左侧的项必须是一个变量名。实际上,赋值运算符左侧必须引用一个存储位置。最简单的方法就是使用变量名。概括地说,C使用可修改的左值(modifiable lvalue)标记那些可赋值的实体。


赋值表达语句的目的是把值存储到内存位置上。用于存储值的数据存储区域统称为数据对象(data object)。C 标准只有在提到这个概念时才会用到对象这个术语。使用变量名是标识对象的一种方式。
左值(lvalue)是C 语言的术语,用于标识特定数据对象的名称或表达式。因此,对象指的是实际的数据存储,而左值是用于标识或定位存储位置的标签。
对于早期的 C 语言,提到左值意味着:

  • 它指定一个对象,所以引用内存中的地址;
  • 它可用在赋值运算符的左侧。

但是后来,标准中新增了const限定符。用const创建的变量不可修改。因此,const标识符满足第一项,但是不满足第二项。一方面 C 继续把标识对象的表达式定义为左值,一方面某些左值却不能放在赋值运算符的左侧。此时,标准对左值的定义已经不能满足当前的状况。
为此,C 标准新增了一个术语:可修改的左值(modifiable lvalue),用于标识可修改的对象。所以赋值运算符的左侧应该是可修改的左值。当前标准建议,使用术语 对象定位值(object locator value) 更好。
右值(rvalue)指的是能赋值给可修改左值的量,且本身不是左值。例如,考虑下面的语句:

bmw = 2002;

这里,bmw是可修改的左值,2002是右值。右值可以是常量、变量或其他可求值的表达式(如,函数调用)。实际上,当前标准在描述这一概念是使用的是表达式的值(value of expression),而不是右值。

int ex,why,zee;
const int TWO = 2;
why = 42;
zee = why;
ex = TWO * ( why + zee);

这里ex,why,zee都是可修改的左值(或对象定位值),它们可用于赋值运算符的左侧和右侧。TWO是不可改变的左值,它只能用于赋值运算符的右侧(TWO被初始化为2,这里的=运算符标识初始化而不是赋值,因此并未违反规则)。同时,42是右值,它不能引用某指定内存位置。另外,why和zee是可修改的左值,表达式(why + zee)是右值,该表达式不能标识特定内存位置,而且也不能给它赋值。它只是程序计算的一个临时值,在计算完毕后便会被丢弃。
在学习名称时,被称为“项”(如,赋值运算符左侧的项)的就是运算对象(operand)。运算对象时运算符操作的对象。例如,可以把吃汉堡描述为:“吃”运算符操作“汉堡”运算对象。类似地可以说,=运算符的左侧运算对象应该是可修改的左值(对象定位值)。
C 的基本赋值运算符有些与众不同。

int jane, tarzan, cheeta;
cheeta = tarzan = jane = 68;
printf("First round score %4d %8d %8d\n",cheeta,tarzan,jane);

许多其他语言都会回避该程序的三重赋值,但是C完全没问题。赋值的顺序是从右往左:首先把86赋给jane,然后再赋给tarzan,最后赋给cheeta。

加法运算符+

相加的值(运算对象)可以是变量,也可以是常量。因此,执行下面的语句:

income = salary + bribes;

计算机会查看加法运算符右侧的两个变量,把它们相加,然后把和赋给变量income。income,salary和bribes都是可修改的左值,因为每个变量都标识了一个可被赋值的数据对象。但是表达式salary + bribes是一个右值。

递增递减优先级

递增运算符和递减运算符都有很高的结合优先级,只有圆括号和优先级比他们高。因此,x*y++表示的是(x)*(y++),而不是(x*y)++。不过后者无效,因为递增和递减运算符只能影响一个变量(或者,更普遍地说,只能影响一个可修改的左值),而组合x*y本身不是可修改的左值。

指针

从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)。指针变量的值是地址。

ptr = &pooh;	// 把pooh的地址赋给ptr

对于这条语句,我们说ptr“指向”pooh。ptr和&pooh的区别是ptr是变量,而&pooh是常量。或则,ptr是可修改的左值,而&pooh是右值。还可以把ptr指向别处:

ptr = &bah;		// 把ptr指向bah,而不是pooh

现在ptr的值是bah的地址。

数组和指针

数组形式和指针形式有何不同?

const char ar1[] = "Something is pointing at me.";
const char * pt1 = "Something is pointing at me.";

数组形式(ar1[] )在计算机的内存中分配为一个内含29个元素的数组(每个元素对应一个字符,还加上一个末尾的空字符’\0’),每个元素被初始化为字符串字面量对应的字符。通常,字符串都作为可执行文件的一部分储存在数据段中。当把程序载入内存时,也载入了程序中的字符串。字符串储存在静态存储区(static memory)中。但是,程序在开始运行时才会为该数组分配内存。此时,才将字符串拷贝到数组中。主语,此时字符串有两个副本。一个是在静态内存中的字符串字面量,另一个时存储在ar1数组中的字符串。
此后,编译器便把数组名ar1识别为该数组首元素地址(&ar1[0])的别名。在数组形式中,ar1时地址常量。不能改变ar1,如果改变了ar1,则意味着改变了数组的存储位置(即地址)。可以进行类似ar1+1这样的操作,标识数组的下一个元素。但是不允许进行++ar1这样的操作。递增运算符只能作用于变量名前(或概括地说,只能用于可修改的左值),不能用于常量。
指针形式(*ptr)也使得编译器为字符串在静态存储区预留29个元素的空间。另外,一旦开始执行程序,它会为该指针变量pt1留出一个存储位置,并把字符串的地址储存在指针变量中。该变量最初指向该字符串的首字符,但是他的值可以改变。因此,可以使用递增运算符。例如,++pt1将指向第2个字符。
字符串字面量被视为const数据。由于pt1指向这个const数据,所以应该把pt1声明为指向const数据的指针。这意味着不能用pt1改变它所指的数据,但是仍然可以改变pt1的值(即,pt1指向的位置)。如果把一个字符串字面量拷贝给一个数组,就可以随意改变数据,除非把数组声明为const。
总之,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。

#include <stdio.h>

#define MSG "I'm special"

int main()
{
    char ar[] = MSG;
    const char *pt = MSG;

    printf("address of \"I'm special\": %p \n","I'm special");
    printf("address ar: %p\n",ar);
    printf("address pt: %p\n",pt);
    printf("address of MSG: %p\n",MSG);
    printf("address of \"I'm special\": %p \n","I'm special");

    return 0;
}
address of "I'm special": 00404044 address ar: 0061FEC0 address pt: 00404044 address of MSG: 00404044 address of "I'm special": 00404044

该程序的输出说明了

  • pt和MSG的地址相同,而ar的地址不同。
  • 虽然字符串字面量"I’m special"在程序中的两个printf()函数中出现了,但是编译器只使用了一个存储位置,而且与MSG的地址相同。
  • 静态数据使用的内存与ar使用的动态内存不同。不仅值不同,特定编译器甚至使用不同的位数表示两种内存。

数组和指针的区别

char heart[] = "I love Tillie!";
const char *head = "I love Millie!";

两者主要的区别是:数组名heart是常量,而指针名head是变量。那么,实际使用有什么区别?
首先,两者都可以使用数组表示法:

for(i = 0; i < 6; i++)
putchar(heart[i]);
putchar('\n');
for(i = 0; i < 6; i++)
putchar(head[i]);
putchar('\n');

I love
I love
其次,两者都能进行指针加法操作:

for(i = 0; i < 6; i++)
putchar(*(heart+i));
putchar('\n');
for(i = 0; i < 6; i++)
putchar(*(head+i));
putchar('\n');

I love
I love
但是,只有指针表示法可以进行递增操作:

while(*(head)!='\0')
putchar(*(head++));

I love Millie!
假设想让head和heart统一,可以这样做:

head = heart;		// head现在指向数组heart

这使得head指针指向heart数组的首元素。
但是,不能这样做:

heart = head;

这类似于x = 3; 和3 = x; 的情况。赋值运算符的左侧必须是变量(或概括地说是可修改地左值),如*pt_int。顺带一提,head = heart; 不会导致head指向的字符消失,这样做只是改变了存储在head中的地址。除非已经保存了"I love Millie!"的地址,否则当head指向别处时,就无法再访问该字符串。
另外,还可以改变heart数组中元素的信息。

heart[7] = 'M'; 或者\*(heart + 7) = 'M';

数组的元素是变量(除非数组被声明为const),但是数组名是不变量。

char * word = "frame";

是否能使用该指针修改这个字符串?

word[1] = 'l';

编译器可能允许这样做,但是对当前的C标准,这样的行为是未定义的。原因前面提到过,编译器可以使用内存中的一个副本表示所有完全相同的字符串字面量。例如,下面的语句中都引用字符串"Klingon"的一个内存位置:

char * p1 = "Klingon";
p1[0] = 'F';
printf("Klingon");
printf(":Beware the %ss!\n","Klingon");

也就是说,编译器可以用相同的地址替换每个"Klingon"实例。如果编译器使用这种单次副本表示法,并允许p1[0]修改’F’,那将影响所有使用该字符串的代码。所以以上语句打印字符串字面量"Klingon"时显示的是"Flingon"。
实际上在过去,一些编译器由于这方面的原因,其行为难以捉摸,而另一些编译器则导致程序异常中断。因此,建议在把指针初始化为字符串字面量时使用const限定符。
然而,把非const数组初始化为字符串字面量却不会导致类似的问题。因为数组获得的是原始字符串的副本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值