C/C++中的对象指的是一块存储区。字符串字面量是不需要创建过程就可以使用的对象,所以它既没有变量那样的声明或者定义(字符串字面量是无名对象),也不需要向动态分配的对象那样进行动态分配。由于这个原因,用来限定变量的类型限定符(如const、volatile)以及存储类别指示符(如extern、static、auto、register)不能用在字符串字面量上。
数组类型
字符串字面量是数组类型的对象,因而具有数组的一切特点。关于这一点在下面还会进一步说明。
静态存储期
C/C++中对象的生存周期按照其存储性质可分为三类:静态存储期(static storage duration)、自动存储期(automatic storage duration)以及动态存储期(dynamic storage duration)。相应地,对象可根据存储期性质分为静态对象、自动对象和动态对象三种。
字符串字面量是静态对象,所以在程序运行期间会一直存在。字符串字面量是左值,而且是不可被更改的左值。
例如,char s[] = "hello"中的"hello"是数组类型的左值(lvalue),用于初始化s数组;
sizeof("hello")
以及
&"hello"
中的“hello”也是左值。在这些情况下,“hello”处于左值语义上下文环境中,所以不会产生下面将要提到的数组转换为指针的现象。
另外,有些运算不但要求其操作数是左值,还要求可变。例如,对对象进行赋值、自加、自减等运算。因为数组是不可被更改的左值,所以不能对数组进行这些操作,也就说不存在数组类型的赋值、自加、自减等运算。
字符串字面量可以转换为指向其第一个字符的指针。
处于右值语义环境中的字符串字面量将被默认转换为指向第一个字符的指针。例如,
char* p = "hello"
中的“hello”在转换为字符指针后用于初始化指针变量p;表达式
“hello”[0]
(相当于
*("hello"+0)
或者
*"hello"
)中的“hello”也是同样转换为指针后参与下标运算,等等。
这种性质也是数组类型的特点。在右值语义环境下,一般类型的对象表示的值是由其存储内容决定的;而数组类型的对象与此不同,它代表的值不是来源于数组对象首元素所在的地址。这是数组最为特殊的地方,也是人们容易产生误解的地方。
取址运算
字符串字面量是一个可取址的对象。例如:&"hello"是合法的表达式。
地址常量
静态对象的地址在编译期间即可被确定,所以其地址(如
&"hello"
)是常量;而字符串字面量又可以从数组类型自动转换为指针(如
“hello”
转换为指针后等同于
&"hello"[0]
),所以字符串字面量可以直接作为地址常量表达式来使用。
修改字符串字面量的行为是无意义的
下面的操作都试图修改字符串字面量中的第一个字符从而修改字符串字面量,所以其结果是无定义的(Undefined)的:
"hello"[0] = 'A' //报错 Undefined
char * p = "hello";
*p = ‘A’; //报错 Undefined
使用了无定义行为的程序是错误的;避免在程序中出现无定义行为是程序员的责任。
区别点:在类型限定上的不同
C中的字符串字面量"hello"是数组类型char[6](相应地,每个字符元素是无const限定的char型);作为右值使用的时候转换为指针类型char*。
在C++中“hello”是char const[6]型(相应地,每个元素的类型是char const);转换为指针使用的时候是char const*,在特殊情况下也可以是char*。
之所以在C中字符串字面量不是const数组(也就是说每个字符元素的类型不是char const),是因为C要照顾或者考虑到标准制定之前已经存在的大量代码--那时的c语言还没有const关键字,如果硬性规定为const数组,则char *p = "hello"这样的初始化或者char *q; q="hello"这样的赋值就是非法的了(因为右边的类型char const*不能默认转换为左边的类型char*)。
同样,为了使上述代码能顺利通过编译过程,C++采取了另外一种策略:它规定了字符串字面量的类型是const数组,同时有特别规定了字符串字面量也可以有限制地转换为指向非常量的指针(对于"hello"来说就是char*),从而解决了上述代码中存在的问题。不过,转换到char*主要是为了兼容以前的代码,这种转换被C++标准标记为“Deprecated”,所以在写程序时不应该依赖于这种转换。
C++中的字符串字面量是常量,而在C中不是常量。
正是由于标准在类型上的不同规定造成了在C和C++中字符串字面量常量性质上的差别。
在C中,除了string literals和compound literals(C99)之外,其他的字面量都是常量;而在C++中,包括string literals在内的所有literals都是常量(注意:C++中不存在compound literals)。
在现实中,经常可以看到用“字符串常量”来指代“字符串字面量”的情况,其实对于C来说这是不正确的,因为在C中字符串字面量不属于常量;而对于C++来说,“字符串常量”和“字符串字面量”实际上是一回事,只不过看问题的角度不同罢了。
顺便提一下:C++中的常量可以有对象的常量(如字符串字面量、const限定的对象)和非对象的常量之分,而C中的常量不包括对象,它们最明显的特征就是不能进行取值运算,因此常量只能作为右值来使用。
语法及语义上的区别
C中的字符串字面量不是常量,它的每个字符元素也不是常量,所以字符元素的不可变性仅仅表现在语义层面,但在语法和约束条件上没有要求。而C++中的字符串字面量是常量,每个字符元素也是常量,因此在语义和约束条件两方面都要求不能改变其中的每个字符元素;另外,处于兼容性考虑C++还存在着特殊情况下的向非const指针的转换。
下面用具体的嗲吗来对以上内容进行说明。
*"hello" = 'A';
表达式*"hello"代表字符串字面量的第一个字符元素对象。上述语句试图通过赋值操作改变第一个元素,当然这样的行为在C和C++中都是无定义的。除了这个相同点外,还有如下的一些细微的区别:
在C++中,*“hello”是一个const对象(其类型是const char。注意:这里的"hello"不会转换为char*指针,所以*"hello"不会是char类型),所以上述赋值违反了赋值号做操作数必须是一个可被改变
的左值的约束条件。在此情况下,标准要求会给出诊断信息。
在C中,*"hello"是一个非const对象(其类型是char),是一个可被改变的左值,所以不违反赋值的约束条件。在此情况下,尽管这个赋值操作是未定义的,标准对诊断信息没有要求。
char* p = "hello";
char* q;
q = "hello";
void foo(char*s);
foo ("hello");
上面的初始化和赋值语句中的"hello"都能转换为char*指针类型,所以都是合法的。在C++中,尽管"hello"作为指针使用时是char const*类型,在此情况下(如果不是char*类型则初始化或者赋值不能成立)基于对字符串字面量的特殊规定使它可以转换为char*使用。
要注意C++中字符串字面量转换为指向非常量的指针是有限制的,仅仅在有明确的名表类型要求的情况下才能进行这样的转换,否则是非法的。比如下面的情况:
char *p = “hello” + 1;
char *q;
q = "hello"+1;
void foor(char*s);
foo("hello" + 1);
上述是合法的C代码,但是作为C++代码是非法的。非法的原因在于:”hello“转换为char const*指针类型,而不能转换为char*,因为+运算符对于其操作数的类型没有转换为char*这样的直接的要求(因为无论是char const*还是char*都能进行指针加法运算),所以孩子真假挨罚表达式的结果仍然是char const*类型。这样,上面指针的初始化或赋值操作就违反了在类型上的约束条件,需要给出诊断信息。