8.3.3 传递和返回地址

如果传递或返回一个地址(一个指针或一个引用),客户程序员去取地址并修改其初值是可能的。如果使这个指针或者引用成为const,就会阻止这类事的发生,这是非常重要的事情。事实上,无论什么时候传递一个地址给一个函数,都应该尽可能用const修饰它。如果不这样做,就不能以const指针参数的方式使用这个函数。

是否选择返回一个指向const的指针或者引用,取决于想让客户程序员用它干什么。下面这个例子表明了如何使用const指针作为函数参数和返回值:

8.3.3 传递和返回地址 - 图1

8.3.3 传递和返回地址 - 图2

函数t()把一个普通的非const指针作为一个参数,而函数u()把一个const指针作为参数。在函数u()里,会看到试图修改const指针所指的内容是非法的。当然,可以把信息拷贝进一个非const变量中。编译器也不允许使用存储在const指针里的地址来建立一个非const指针。

函数v()和w()测试返回值的语义。函数v()返回一个从字符数组的字面值中建立的const char*。在编译器建立了它并把它存储在静态存储区之后,这个声明实际上产生这个字符数组的字面值的地址。像前面提到的一样,从技术上讲,这字符数组是一个常量,这个常量由函数v()的返回值正确地表示。

w()的返回值要求这个指针及这个指针所指向的对象均为常量。像函数v()一样,仅仅因为它是静态的,所以在函数返回后由w()返回的值是有效的。函数不能返回指向局部栈变量的指针,这是因为在函数返回后它们就无效了,而且栈也被清除了。可返回的另一个普通指针是在堆中分配的存储地址,在函数返回后它仍然有效。

在main()中,函数被各种参数测试。函数t()将接受一个非const指针参数。但是,如果想传给它一个指向const的指针,那么将不能防止t()会丢下这个指针所指的内容不管,所以编译器会给出一个错误信息。函数u()带一个const指针,所以它接受两种类型的参数。这样,带const指针参数的函数比不带const指针参数的函数更具一般性。

正如所期望的一样,函数v()的返回值只可以被赋给一个const指针。编译器拒绝把函数w()的返回值赋给一个非const指针,而接受一个const intconst,但令人奇怪的是它也接受一个const int,这不是与返回类型恰好匹配的。又正如前面所讲的,因为这个值(包含在指针中的地址)正被拷贝,所以自动保持这样的约定:原始变量不能被改变。因此,只有当把const int*const中的第二个const当做一个左值使用时(编译器会阻止这种情况),它才能显示其意义所在。

8.3.3.1 标准参数传递

在C语言中,按值传递是最常见的。当想传递地址时,惟一的选择就是使用指针[1]。然而,在C++中这两种方法都受重视。相反,当传递一个参数时,首先选择按引用传递,而且是const引用。对于客户程序员来说,这样做语法与按值传递是一样的,所以不会像使用指针那样的混淆—他们甚至不必考虑指针。对于函数的创建者来说,传递地址总比传递整个类对象更有效,如果按const引用来传递,意味着函数将不改变该地址所指的内容,从客户程序员的观点来看,效果就像按值传递一样(只是更有效)。

由于引用的语法(对于调用者它看起来像按值传递)的原因,把一个临时对象传递给接受const引用的函数是可能的,但不能把一个临时对象传递给接受指针的函数—对于指针,它必须明确地接受地址。所以,按引用传递会产生一个从来不会在C中出现的新的情形:一个总是const的临时变量,它的地址可以被传递给一个函数。这就是为什么当临时变量按引用传递给一个函数时,这个函数的参数必须是const引用的原因。下面的例子说明了这一点:

8.3.3 传递和返回地址 - 图3

函数f()按值返回类X的一个对象。这意味着当立即取f()的返回值并把它传递给另外一个函数时(正如g1()和g2()函数的调用),将建立一个临时量,该临时量是const。这样,函数g1()中的调用是错误的,因为g1()不接受const引用,但是函数g2()中的调用是正确的。

[1]有些人甚至会说C中的一切都是按值来传递,因为当传递一个指针时,也会得到了一份副本(所以是通过值传递指针的)。但我认为,无论这种看法有多么准确,它都会使这个问题变得更加混乱而不易理解。