1与C语言的区别
常量引进是在早期的 C + +版本中,当时标准 C规范正在制订。那时,常量被看作是一个好
的思想而被包含在 C中。但是, C中的 c o n s t意思是“一个不能被改变的普通变量”,在C中,它
总是占用存储而且它的名字是全局符。 C编译器不能把 c o n s t看成一个编译期间的常量。在 C中,
如果写:
const bufsize=100;
char buf[bufsize];
尽管看起来好像做了一件合理的事,但这将得到一个错误结果。因为 b u f s i z e占用存储的某个地
方,所以 C编译器不知道它在编译时的值。在 C语言中可以选择这样书写:
const bufsize;
这样写在 C + +中是不对的,而 C编译器则把它作为一个声明,这个声明指明在别的地方有存储
分配。因为 C默认 c o n s t是外部连接的, C + +默认c o n s t是内部连接的,这样,如果在 C + +中想完
成与 C中同样的事情,必须用 e x t e r n把连接改成外部连接:
extern const bufsize;//declaration only
这种方法也可用在 C语言中。
在C语言中使用限定符c o n s t不是很有用,即使是在常数表达式里(必须在编译期间被求出)
想使用一个已命名的值,使用 c o n s t也不是很有用的。 C迫使程序员在预处理器里使用 # d e f i n e。
2指向const的指针
使用指针定义的技巧,正如任何复杂的定义一样,是在标识符的开始处读它并从里向外读。
c o n s t指定那个“最靠近”的。这样,如果要使正指向的元素不发生改变,我们得写一个像这
样的定义:
const int* x;
从标识符开始,是这样读的: “ x是一个指针,它指向一个 const int。 ”这里不需要初始化,因为
说x可以指向任何东西(那是说,它不是一个 c o n s t),但它所指的东西是不能被改变的。
这是一个容易混淆的部分。有人可能认为:要想指针本身不变,即包含在指针 x里的地址
不变,可简单地像这样把 c o n s t从一边移向另一边:
int const* x;
并非所有的人都很肯定地认为:应该读成“ x是一个指向 i n t的c o n s t指针”。然而,实际上
应读成“ x是一个指向恰好是 c o n s t的i n t普通指针” 。即 c o n s t又把它自己与 i n t结合在一起,结果
与前面定义一样。两个定义是一样的,这一点容易使人混淆。为使程序更具有可读性,我们应
该坚持用第一种形式。
3 const指针
使指针本身成为一个 c o n s t指针,必须把 c o n s t标明的部分放在 *的右边,如:
int d=1 ;
int* const x=&d ;
现在,指针和对象都不能改变。
一些人认为第二种形式更好。因为 c o n s t总是放在被修改者的右边。但对于特定的代码类型
来讲,程序员得自己决定哪一种形式更清楚。
格式
这本书主张,不管何时在一行里仅放一个指针定义,且在定义的地方初始化每个指针。正
因为这一点,才可以把‘ *’ “附于”数据类型上:
int* u=&w;
i n t *本身好像是离散型的。这使代码更容易懂,可惜的是,事情并非如此。事实上, ‘ *’与标
识符结合,而不是与类型结合。它可以被放在类型名和标识符之间的任何地方。所以,可以这样做:
int* u=&w, v = 0;
它建立一个 int* u和一个非指针 int v。由于读者时常混淆这一点,所以最好用本书里所用
的表示形式(即一行里只定义一个指针)。
7.2.3 赋值和类型检查
C + +关于类型检查有其特别之处,这一点也扩展到指针赋值。我们可以把一个非 c o n s t对象
的地址赋给一个 c o n s t指针,因为也许有时不想改变某些可以改变的东西。然而,不能把一个
c o n s t对象的地址赋给一个非 c o n s t指针,因为这样做可能通过被赋值指针改变这个 c o n s t指针。
当然,总能用类型转换强制进行这样的赋值,但是,这不是一个好的程序设计习惯,因为这样
就打破了对象的 c o n s t属性以及由 c o n s t提供的安全性。例如:
int d = 1;
const int e = 2;
int* u = &d; //OK -- d not const
int* v = &e; //ilegal -- e const
int* w = (int*)&e; // legal but bad practice
虽然C + +有助于防止错误发生,但如果程序员自己打破了这种安全机制,它也是无能为力
的。
串字面值
限定词 c o n s t是很严格的, c o n s t没被强调的地方是有关串字面值。也许有人写:
char* cp="howdy";
编译器将接受它而不报告错误。从技术上讲,这是一个错误,因为串字面值(这里是
“ h o w d y”)是被编译器作为一个常量串建立的,所引用串的结果是它在内存里的首地址。
所以串字面值实际上是常量串。然而,编译器把它们作为非常量看待,这是因为有许多现
有的C代码是这样做的。当然,改变串字面值的做法还未被定义,虽然可能在很多机器上是这
样做的。
7.3 函数参数和返回值
用c o n s t限定函数参数及返回值是常量概念另一个容易被混淆的地方。 如果以值传递对象时,
对用户来讲,用 c o n s t限定没有意义(它意味着传递的参数在函数里是不能被修改的)。如果以
常量返回用户定义类型的一个对象的值,这意味着返回值不能被修改。如果传递并返回地址,
c o n s t将保证该地址内容不会被改变。
7.3.1 传递const值
如果函数是以值传递的,可用 c o n s t限定函数参数,如:
void f1(const int i){
i++; // illegal -- compile-time error
}
这是什么意思呢?这是作了一个约定:变量初值不会被函数 x ( )改变。然而,由于参数是以
值传递的,因此要立即制作原变量的副本,这个约定对用户来说是隐藏的。
在函数里, c o n s t有这样的意义:参数不能被改变。所以它其实是函数创建者的工具,而不
是函数调用者的工具。
为了不使调用者混淆,在函数内部用 c o n s t限定参数优于在参数表里用 c o n s t限定参数。可
以用一个指针这样做,但更好的语法形式是“引用”,这是第 1 0章讨论的主题。简言之,引用
像一个被自动逆向引用的常指针,它的作用是成为对象的别名。为建立一个引用,在定义里使
用 &。所以,不引起混淆的函数定义看来像这样的:
void f2(int ic){
const int& i = ic;
i++; // illegal -- compile-time error
}
这又会得到一个错误信息,但这时对象的常量性( c o n s t)不是函数特征标志的部分;它仅
对函数实现有意义,所以它对用户来说是不可见的。
7.3.2 返回const值
对返回值来讲,存在一个类似的道理,即如果从一个函数中返回值,这个值作为一个常
量:
const int g();
约定了函数框架里的原变量不会被修改。正如前面讲的,返回这个变量的值,因为这个变量被
制成副本,所以初值不会被修改。
首先,这使 c o n s t看起来没有什么意义。可以从这个例子中看到:返回常量值明显失去意
义:
//:CONSTVAL.CPP -- Returning consts by value
// Has no meaning for built-in types
int f3(){ return 1; }
const int f4() { return 1; }
main() {
const int j = f3(); // Works fine
int k = f4(); // But this works fine too!
}
对于内部数据类型来说,返回值是否是常量并没有关系,所以返回一个内部数据类型的值
时,应该去掉 c o n s t从而使用户程序员不混淆。
处理用户定义的类型时,返回值为常量是很重要的。如果一个函数返回一个类对象的值,
其值是常量,那么这个函数的返回值不能是一个左值(即它不能被赋值,也不能被修改)。例
如:
// : CONSTRET.CPP -- Constant return by value
// Result cannot be used as an lvalue
class X {
int i;
public:
X(int I = 0) : i(I) {}
void modify(){ i++;}
}
X f5(){
return X();
}
const X f6() {
return X();
}
void f7(X& x) { // Pass by non-const reference
x.modify();
}
main() {
f5() = X(1); // OK -- non-const return value
f5().modify(); // OK
f7(f5()); // OK
// Causes compile-time errors:
//! f6() = X(1);
//! f6().modify();
//! f7(f6());
}
f 5 ( )返回一个非 const X对象,然而 f 6 ( )返回一个 const X对象。只有非 c o n s t返回值能作为一
个左值使用。换句话说,如果不让对象的返回值作为一个左值使用,当返回一个对象的值时,
应该使用 c o n s t。
返回一个内部数据类型的值时, c o n s t没有意义的原因是:编译器已经不让它成为一个左值
(因为它总是一个值而不是一个变量)。仅当返回用户定义的类型对象的值时,才会出现上述问
题。
函数f 7 ( )把它的参数作为一个非 c o n s t引用( C + +中另一种处理地址的办法,这是第 1 0章讨
论的主题) 。从效果上讲,这与取一个非 c o n s t指针一样,只是语法不同。
临时变量
有时候,在求表达式值期间,编译器必须建立临时对象。像其他任何对象一样,它们需要
存储空间而且必须被构造和删除。区别是我们从来看不到它们— 编译器负责决定它们的去
留以及它们存在的细节。这里有一个关于临时变量的情况:它们自动地成为常量。因为我们
通常接触不到临时对象,不能使用与之相关的信息,所以告诉临时对象做一些改变几乎肯定
会出错。当程序员犯那样的错误时,由于使所有的临时变量自动地成为常量,编译器会向他
发出错误警告。
类对象常量是怎样保存起来的,将在这一章的后面介绍。
7.3.3 传递和返回地址
如果传递或返回一个指针(或一个引用) ,用户取指针并修改初值是可能的。如果使这个
指针成为常( c o n s t)指针,就会阻止这类事的发生,这是非常重要的。事实上,无论什么时
候传递一个地址给一个函数,我们都应该尽可能用 c o n s t修饰它,如果不这样做,就使得带有
指向 c o n s t的指针函数不具备可用性。
是否选择返回一个指向 c o n s t的指针,取决于我们想让用户用它干什么。下面这个例子表明
了如何使用 c o n s t指针作为函数参数和返回值:
//: CONSTP.CPP -- Constant pointer arg/return
void t(int*) {}
void u(const int* cip) {
//! *cip = 2; // Illegal -- modifies value
int i = *cip; // OK -- copies value
//! int *ip2 = cip; //Illegal:non-const
}
const char* v() {
// Returns address of static string:
return "result of function v()";
}
const int* const w() {
static int i;
return &i;
}
main() {
int x = 0;
int* ip = &x;
const int* cip = &x;
t(ip); // OK
//! t(cip); // Not OK
u(ip); // OK
u(uip); // Also OK
//! char* cp = v(); // Not OK
const char* ccp = v(); // OK
//! int* ip2 = w(); // Not OK
const int* const ccip = w(); //OK
const int* cip2 = w(); // OK
//! *w() = 1; // Not OK
}
函数t ( )把一个普通的非 c o n s t指针作为一个参数,而函数 u ( )把一个c o n s t指针作为参数。在
函数u ( )里,我们会看到试图修改 c o n s t指针的内容是非法的。当然,我们可以把信息拷进一个
非c o n s t变量。编译器也不允许使用存储在 c o n s t指针里的地址来建立一个非 c o n s t指针。
函数v ( )和w ( )测试返回的语义值。函数 v ( )返回一个从串字面值中建立的 const char* 。在编
译器建立了它并把它存储在静态存储区之后,这个声明实际上产生串字面值的地址。像前面提
到的一样,从技术上讲,这个串是一个常量,这个常量由函数 v ( )的返回值正确地表示。
w ( )的返回值要求这个指针及这个指针所指向的对象均为常量。像函数 v ( )一样,仅仅因为
它是静态的,所以在函数返回后由 w ( )返回的值是有效的。函数不能返回指向局部栈变量的指
针,这是因为在函数返回后它们是无效的,而且栈也被清理了。可返回的另一个普通指针是在
堆中分配的存储地址,在函数返回后它仍然有效。
在函数m a i n ( )中,函数被各种参数测试。函数 t ( )将接受一个非 c o n s t指针参数。但是,如果
我们想传给它一个指向 c o n s t的指针,那么就将不能防止 t ( )丢下这个指针所指的内容不管,所以
编译器会给出一个错误信息。函数 u ( )带一个 c o n s t指针,所以它接受两种类型的参数。这样,
带c o n s t指针函数比不带 c o n s t指针函数更具一般性。
正如所期望的,函数 v ( )返回值只可以被赋给一个 c o n s t指针。编译器拒绝把函数 w ( )的返回
值赋给一个非 c o n s t指针,而接受一个const int* const,但令人吃惊的是它也接受一个 const int*,
这与返回类型不匹配。正如前面所讲的,因为这个值(包含在指针中的地址)正被拷贝,所以
自动保持这样的约定:原始变量不能被触动。因此,只有把 const int*const中的第二个 c o n s t当
作一个左值使用时(编译器会阻止这种情况),它才能显示其意义。
标准参数传递
在C语言中,值传递是很普通的,但是当我们想传递地址时,只能使用指针。然而,在
C + +中却不使用这两种方法。在 C + +中,传递一个参数时,先选择通过引用传递,而且是通过
常量( c o n s t)引用。对程序员来说,这样做的语法与值传递是一样的,所以在指针方面没有
混淆之处的—他们甚至不必考虑这个问题。对于类的创建者来说,传递地址总比传递整个类
对象更有效,如通过常量( c o n s t)引用来传递,这意味着函数将不改变该地址所指的内容,
从用户程序员的观点来看,效果恰好与值传递一样。
由于引用的语法(看起来像值传递)的原因,传递一个临时对象给带有一个引用的函数,
是可能的,但不能传递一个临时对象给带有一个指针的函数—因为它必须清楚地带有地址。
所以,通过引用传递会产生一个在 C中不会出现的新情形:一个总是常量的临时变量,它的地
址可以被传递给一个函数。这就是为什么临时变量通过引用被传递给一个函数时,这个函数的
参数一定是常量( c o n s t)引用。下面的例子说明了这一点
//: CONSTTMP.CPP -- Temporaries are const
class X {};
X f() { return X(); } // Return by value
void g1(X&) {} // Pass by non-const reference
void g2(const X&) {} // Pass by const reference
main() {
// Error: const temporary created by f();
// ! g1(f()); //invalid initialization of non-const reference of type 'X&' from an revalue of type 'X'
// OK: g2 takes a const reference:
g2(f());
}
函数 f ( )返回类 X的一个对象的值。这意味着立即取 f ( )的返回值并把它传递给其他函数时
(正如g 1 ( )和g 2 ( )函数的调用) ,建立了一个临时变量,那个临时变量是常量。这样,函数 g 1 ( )中
的调用是错误的,因为 g 1 ( )不带一个常量( c o n s t)引用,但是函数 g 2 ( )中的调用是正确的。
int d=1 ;
int* const x=&d ;
现在,指针和对象都不能改变。
一些人认为第二种形式更好。因为 c o n s t总是放在被修改者的右边。但对于特定的代码类型
来讲,程序员得自己决定哪一种形式更清楚。