在 2.2 节中我们简要地介绍了指针和动态内存分配指针持有另一个对象的地址
使我们能够间接地操作这个对象指针的典型用法是构建一个链接的数据结构例如树tree
72 第三章 C++数据类型
和链表list 并管理在程序执行过程中动态分配的对象以及作为函数参数类型主要用
来传递数组或大型的类对象
每个指针都有一个相关的类型不同数据类型的指针之间的区别不是在指针的表示上
也不在指针所持有的值地址上——对所有类型的指针这两方面都是相同的8不同之处在
于指针所指的对象的类型上指针的类型可以指示编译器怎样解释特定地址上内存的内容
以及该内存区域应该跨越多少内存单元
如果一个int 型的指针寻址到1000 内存处那么在32 位机器上跨越的地址空间
是1000~1003
如果一个double 型的指针寻址到1000 内存处那么在32 位机器上跨越的地址
空间是1000~1007
下面是指针定义的例子
int *ip1, *ip2;
complex<double> *cp;
string *pstring;
vector<int> *pvec;
double *dp;
我们通过在标识符前加一个解引用操作符* 来定义指针在逗号分隔的标识符列表中
每个将被用作指针的标识符前都必须加上解引用操作符在下面的例子中lp 是一个指向long
类型对象的指针而lp2 则是一个long 型的数据对象不是指针
long *lp, lp2;
在下面的例子中fp 是一个float 型的数据对象而fp2 是一个指向float 型对象的指针
float fp, *fp2;
为清楚起见最好写成
string *ps;
而不是
string* ps;
有可能发生的情况是当程序员后来想定义第二个字符串指针时他会错误地修改定义
如下
// 喔: ps2 不是一个字符串指针
string* ps, ps2;
当指针持有0 值时表明它没有指向任何对象或持有一个同类型的数据对象的地址
已知ival 的定义
int ival = 1024;
下面的定义以及对两个指针pi 和pi2 的赋值都是合法的
// pi 被初始化为 "没有指向任何对象"
8 这对函数指针并不成立函数指针指向程序的代码段函数指针和数据指针是不同的函数指针将在7.9
节说明
73 第三章 C++数据类型
int *pi = 0;
// pi2 被初始化为ival 的地址
int *pi2 = &ival;
// ok: pi 和pi2 现在都指向ival
pi = pi2;
// 现在pi2 没有指向任何对象
pi2 = 0;
指针不能持有非地址值例如下面的赋值将导致编译错误
// 错误pi 被赋以int 值ival
pi = ival;
指针不能被初始化或赋值为其他类型对象的地址值例如已知如下定义
double dval;
double *pd = &dval;
那么下列两条语句都会引起编译时刻错误
// 都是编译时刻错误
// 无效的类型赋值: int* <== double*
pi = pd;
pi = &dval;
不是说pi 在物理上不能持有与dval 相关联内存的地址它能够但是不允许因为
虽然pi 和pd 能够持有同样的地址值但对那块内存的存储布局和内容的解释却完全不同
当然如果我们要做的仅仅是持有地址值可能是把一个地址同另一个地址作比较
那么指针的实际类型就不重要了C++提供了一种特殊的指针类型来支持这种需求空
void* 类型指针它可以被任何数据指针类型的地址值赋值函数指针不能赋值给它
// ok: void* 可以持有任何指针类型的地址值
void *pv = pi;
pv = pd;
void*表明相关的值是个地址但该地址的对象类型不知道我们不能够操作空类型指针
所指向的对象只能传送该地址值或将它与其他地址值作比较在4.14 节我们将会看到更
多关于void*类型的细节
已知一个int 型指针对象pi 当我们写下pi 时
// 计算包含在pi 内部的地址值
// 类型int*
pi;
这将计算pi 当前持有的地址值当我们写下&pi 时
// 计算pi 的实际地址
// 类型: int**
π
这将计算指针对象pi 被存储的位置的地址那么怎样访问pi 指向的对象呢
在缺省情况下我们没有办法访问pi 指向的对象以对这个对象进行读或者写的操作
74 第三章 C++数据类型
为了访问指针所指向的对象我们必须解除指针的引用C++提供了解引用操作符*
dereference operator 来间接地读和写指针所指向的对象例如已知下列定义
int ival = 1024, ival2 = 2048;
int *pi = &ival;
下面给出了怎样解引用pi 以便间接访问ival
// 解除pi 的引用, 为它所指向的对象ival
// 赋以ival2 的值
*pi = ival2;
// 对于右边的实例, 读取pi 所指对象的值
// 对于左边的实例则把右边的表达式赋给对象
*pi = abs( *pi ); // ival = abs(ival);
*pi = *pi + 1; // ival = ival + 1;
我们知道当取一个int 型对象的地址时
int *pi = &ival;
结果是int*——即指向int 的指针当我们取指向int 型的指针的地址时
int **ppi = π
结果是int**——即指向int 指针的指针当我们解引用ppi 时
int *pi2 = *ppi;
我们获得指针ppi 持有的地址值——在本例中即pi 持有的值而pi 又是ival 的地址
为了实际地访问到ival 我们需要两次解引用ppi 例如
cout << "The value of ival/n"
<< "direct value: " << ival << "/n"
<< "indirect value: " << *pi << "/n"
<< "doubly indirect value: " << **ppi
<< endl;
下面两条赋值语句的行为截然不同但它们都是合法的第一条语句增加了pi 指向的数
据对象的值而第二条语句增加了pi 包含的地址的值
int i, j, k;
int *pi = &i;
// i 加2 (i = i + 2)
*pi = *pi + 2;
// 加到pi 包含的地址上
pi = pi + 2;
指针可以让它的地址值增加或减少一个整数值这类指针操作被称为指针的算术运算
pointer arithmetic 这种操作初看上去并不直观我们总认为是数据对象的加法而不是
离散的十进制数值的加法指针加2 意味着给指针持有的地址值增加了该类型两个对象的长
度例如假设一个char 是一个字节一个int 是4 个字节double 是8 个字节那么指针
加2 是给其持有的地址值增加2 8 还是16 完全取决于指针的类型是char int 还是double
75 第三章 C++数据类型
实际上只有指针指向数组元素时我们才能保证较好地运用指针的算术运算在前面
的例子中我们不能保证三个整数变量连续存储在内存中因此lp+2 可能也可能不产生
一个有效的地址这取决于在该位置上实际存储的是什么指针算术运算的典型用法是遍历
一个数组例如
int ia[ 10 ];
int *iter = &ia[0];
int *iter_end = &ia[10];
while ( iter != iter_end ) {
do_something_with_value( *iter );
++iter; // 现在iter 指向下一个元素
}
使我们能够间接地操作这个对象指针的典型用法是构建一个链接的数据结构例如树tree
72 第三章 C++数据类型
和链表list 并管理在程序执行过程中动态分配的对象以及作为函数参数类型主要用
来传递数组或大型的类对象
每个指针都有一个相关的类型不同数据类型的指针之间的区别不是在指针的表示上
也不在指针所持有的值地址上——对所有类型的指针这两方面都是相同的8不同之处在
于指针所指的对象的类型上指针的类型可以指示编译器怎样解释特定地址上内存的内容
以及该内存区域应该跨越多少内存单元
如果一个int 型的指针寻址到1000 内存处那么在32 位机器上跨越的地址空间
是1000~1003
如果一个double 型的指针寻址到1000 内存处那么在32 位机器上跨越的地址
空间是1000~1007
下面是指针定义的例子
int *ip1, *ip2;
complex<double> *cp;
string *pstring;
vector<int> *pvec;
double *dp;
我们通过在标识符前加一个解引用操作符* 来定义指针在逗号分隔的标识符列表中
每个将被用作指针的标识符前都必须加上解引用操作符在下面的例子中lp 是一个指向long
类型对象的指针而lp2 则是一个long 型的数据对象不是指针
long *lp, lp2;
在下面的例子中fp 是一个float 型的数据对象而fp2 是一个指向float 型对象的指针
float fp, *fp2;
为清楚起见最好写成
string *ps;
而不是
string* ps;
有可能发生的情况是当程序员后来想定义第二个字符串指针时他会错误地修改定义
如下
// 喔: ps2 不是一个字符串指针
string* ps, ps2;
当指针持有0 值时表明它没有指向任何对象或持有一个同类型的数据对象的地址
已知ival 的定义
int ival = 1024;
下面的定义以及对两个指针pi 和pi2 的赋值都是合法的
// pi 被初始化为 "没有指向任何对象"
8 这对函数指针并不成立函数指针指向程序的代码段函数指针和数据指针是不同的函数指针将在7.9
节说明
73 第三章 C++数据类型
int *pi = 0;
// pi2 被初始化为ival 的地址
int *pi2 = &ival;
// ok: pi 和pi2 现在都指向ival
pi = pi2;
// 现在pi2 没有指向任何对象
pi2 = 0;
指针不能持有非地址值例如下面的赋值将导致编译错误
// 错误pi 被赋以int 值ival
pi = ival;
指针不能被初始化或赋值为其他类型对象的地址值例如已知如下定义
double dval;
double *pd = &dval;
那么下列两条语句都会引起编译时刻错误
// 都是编译时刻错误
// 无效的类型赋值: int* <== double*
pi = pd;
pi = &dval;
不是说pi 在物理上不能持有与dval 相关联内存的地址它能够但是不允许因为
虽然pi 和pd 能够持有同样的地址值但对那块内存的存储布局和内容的解释却完全不同
当然如果我们要做的仅仅是持有地址值可能是把一个地址同另一个地址作比较
那么指针的实际类型就不重要了C++提供了一种特殊的指针类型来支持这种需求空
void* 类型指针它可以被任何数据指针类型的地址值赋值函数指针不能赋值给它
// ok: void* 可以持有任何指针类型的地址值
void *pv = pi;
pv = pd;
void*表明相关的值是个地址但该地址的对象类型不知道我们不能够操作空类型指针
所指向的对象只能传送该地址值或将它与其他地址值作比较在4.14 节我们将会看到更
多关于void*类型的细节
已知一个int 型指针对象pi 当我们写下pi 时
// 计算包含在pi 内部的地址值
// 类型int*
pi;
这将计算pi 当前持有的地址值当我们写下&pi 时
// 计算pi 的实际地址
// 类型: int**
π
这将计算指针对象pi 被存储的位置的地址那么怎样访问pi 指向的对象呢
在缺省情况下我们没有办法访问pi 指向的对象以对这个对象进行读或者写的操作
74 第三章 C++数据类型
为了访问指针所指向的对象我们必须解除指针的引用C++提供了解引用操作符*
dereference operator 来间接地读和写指针所指向的对象例如已知下列定义
int ival = 1024, ival2 = 2048;
int *pi = &ival;
下面给出了怎样解引用pi 以便间接访问ival
// 解除pi 的引用, 为它所指向的对象ival
// 赋以ival2 的值
*pi = ival2;
// 对于右边的实例, 读取pi 所指对象的值
// 对于左边的实例则把右边的表达式赋给对象
*pi = abs( *pi ); // ival = abs(ival);
*pi = *pi + 1; // ival = ival + 1;
我们知道当取一个int 型对象的地址时
int *pi = &ival;
结果是int*——即指向int 的指针当我们取指向int 型的指针的地址时
int **ppi = π
结果是int**——即指向int 指针的指针当我们解引用ppi 时
int *pi2 = *ppi;
我们获得指针ppi 持有的地址值——在本例中即pi 持有的值而pi 又是ival 的地址
为了实际地访问到ival 我们需要两次解引用ppi 例如
cout << "The value of ival/n"
<< "direct value: " << ival << "/n"
<< "indirect value: " << *pi << "/n"
<< "doubly indirect value: " << **ppi
<< endl;
下面两条赋值语句的行为截然不同但它们都是合法的第一条语句增加了pi 指向的数
据对象的值而第二条语句增加了pi 包含的地址的值
int i, j, k;
int *pi = &i;
// i 加2 (i = i + 2)
*pi = *pi + 2;
// 加到pi 包含的地址上
pi = pi + 2;
指针可以让它的地址值增加或减少一个整数值这类指针操作被称为指针的算术运算
pointer arithmetic 这种操作初看上去并不直观我们总认为是数据对象的加法而不是
离散的十进制数值的加法指针加2 意味着给指针持有的地址值增加了该类型两个对象的长
度例如假设一个char 是一个字节一个int 是4 个字节double 是8 个字节那么指针
加2 是给其持有的地址值增加2 8 还是16 完全取决于指针的类型是char int 还是double
75 第三章 C++数据类型
实际上只有指针指向数组元素时我们才能保证较好地运用指针的算术运算在前面
的例子中我们不能保证三个整数变量连续存储在内存中因此lp+2 可能也可能不产生
一个有效的地址这取决于在该位置上实际存储的是什么指针算术运算的典型用法是遍历
一个数组例如
int ia[ 10 ];
int *iter = &ia[0];
int *iter_end = &ia[10];
while ( iter != iter_end ) {
do_something_with_value( *iter );
++iter; // 现在iter 指向下一个元素
}