const总结

来源:互联网 发布:武钢裁员5万人 知乎 编辑:程序博客网 时间:2024/05/21 19:37


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总是放在被修改者的右边。但对于特定的代码类型

来讲,程序员得自己决定哪一种形式更清楚。


0 0
原创粉丝点击