条款8 指向指针的指针
声明指向指针的指针是合法的。这就是C++(www.cppentry.com)标准所说的“多级”指针。
- int *pi; // 一个指针
- int **ppi; // 一个多级(2级)指针
- int ***pppi; // 一个多级(3级)指针
尽管超过两级的多级指针很罕见,但在两种常见的情形下,确实会看到指向指针的指针。第一种情形是当我们声明一个指针数组时:
- Shape *picture[MAX]; // 一个数组,其元素为指向Shape的指针
由于数组的名字会退化为指向其首元素的指针(参见“数组形参”[条款6]),所以指针数组的名字也是一个指向指针的指针:
- Shape **pic1 = picture;
我们在管理指针缓冲区的类的实现中最常看到这种用法:
- template <typename T>
- class PtrVector {
- public:
- explicit PtrVector( size_t capacity )
- : buf_(new T *[capacity]), cap_(capacity), size_(0) {}
- //...
- private:
- T **buf_; // 一个指针,指向一个数组,该数组元素为指向T的指针
- size_t cap_; // 容量
- size_t size_; // 大小
- };
- //...
- PtrVector<Shape> pic2( MAX );
从PtrVector的实现可以看到,指向指针的指针可能会很复杂,最好将其隐藏起来。
多级指针的第二个常见应用情形,是当一个函数需要改变传递给它的指针的值时。考虑如下函数,它将一个指针移动到指向字符串中的下一个字符:
- void scanTo( const char **p, char c ) {
- while( **p && **p != c )
- ++*p;
- }
传递给scanTo的第一个参数是一个指向指针的指针,该指针值是我们希望改变的。这意味着我们必须传递指针的地址:
- char s[] = "Hello, World!";
- const char *cp = s;
- scanTo( &cp, ',' ); // 将cp移动到第一个“,”出现的位置
这种用法在C中是合理的,但在C++(www.cppentry.com)中,更习惯、更简单、更安全的做法是使用指向指针的引用作为函数参数,而不是使用指向指针的指针作为参数。
- void scanTo( const char *&p, char c ) {
- while( *p && *p != c )
- ++p;
- }
- //...
- char s[] = "Hello, World!";
- const char *cp = s;
- scanTo( cp, ',' );
在C++(www.cppentry.com)中,几乎总是首选使用指向指针的引用作为函数参数,而不是指向指针的指针。
一个常见的误解是:适用于指针的转换同样适用于指向指针的指针。事实并非如此。例如,我们知道一个指向派生类的指针可被转换为一个指向其公共基类的指针:
- Circle *c = new Circle;
- Shape *s = c; // 挺好的……
因为Circle是一个(is-a)Shape,因而一个指向Circle的指针也是一个Shape指针。然而,一个指向Circle指针的指针并不是一个指向Shape指针的指针:
- Circle **cc = &c;
- Shape **ss = cc; // 错误!
当涉及const时也会发生同样的混淆。我们知道,将一个指向非常量的指针转换为一个指向常量的指针是合法的(参见“常量指针与指向常量的指针”[条款7]),但不可以将一个指向“指向非常量的指针”的指针转换为一个指向“指向常量的指针”的指针:
- char *s1 = 0;
- const char *s2 = s1; // 没问题
- char *a[MAX]; // 也就是char **
- const char **ps = a; // 错误!