### 指针类型

在 C++语言中指针数据类型是最重要也是最难掌握的数据类型。熟练、灵活的应用指针可以提供程序的运行效率，增强程序的灵活性。在应用程序中，指针是非常强大的，它能够在调用函数中修改函数参数，能够进行动态分配。同时，指针也是非常危险的。在程序中使用一个未经初始化的指针是非常危险的，它能够导致程序崩溃。因此，对于初学者来说，在程序中使用指针一定要小心和仔细。

在介绍了指针的作用，权衡了指针的利弊之后，下面详细阐述指针数据类型。所谓指针，实际是一个变量，它能够包含某一个内存地址。我们知道，在程序中定义的各种变量，都需要一个内存空间来存储变量的数据，这个内存空间的地址就是变量的地址。而指针能够存储变量的地址，那么通过指针自然能够访问到变量地址空间中的数据，即变量的值。图 1 描述了指针与变量之间的关系。

图 1 指针与变量之间的关系

为了防止用户产生歧义，我们将指针值描述为指针指向的地址，将指针指向地址中的数据（它指向的变量值）称之为指针指向的数据。

q 定义指针

在 C++中，定义指针的语法格式如下：

数据类型 *指针变量名;

例如，下面的语句定义了一个整型指针变量 pLen。

int* pLen;

当然我们也可以利用一行语句定义多个指针对象。例如：

double* pRate, * pLength;

注意：第 2 个指针变量 pLenght 前需要写“*”，如果省略了“*”，则 pRate 表示一个 double 指针变量，而 pLength 表示一个 double 变量。

在定义一个指针之后，如果指针没有进行初始化，还不能够使用指针，在使用指针之前，必须为指针赋值。因为使用一个未初始化或未赋值的指针是非常危险的。由于指针的值要求的是一个变量的地址，因此需要将一个变量的地址赋值给指针对象。可以使用取地址运算符“&”获取变量的地址。下面的语句将一个整型变量的地址赋值给指针变量。

int nLen = 8; //定义一个整型变量

int* pLen; //定义一个指针变量

pLen= &nLen; //为指针变量赋值

与定义普通的变量类似，在定义指针变量时也可以直接进行初始化。例如：

int nLen = 8; //定义一个整型变量

int* pLen = &nLen; //定义一个指针变量并进行初始化

在指针被赋值或初始化之后，就可以利用指针来访问或修改其他变量的值了。为了访问指针指向的数据，需要在指针前使用“*”符号。例如：

使用指针变量访问和修改对象

#include "stdafx.h"

#include "iostream.h"

int main(int argc, char* argv[])

{

int nLen = 10; //定义整型变量

int * pLen = &nLen; //定义指针，并进行初始化

cout << "pLen 指向的数据" << *pLen << endl; //输出指针指向的数据

*pLen = 100; //设置指针指向的数据

cout << "nLen 的值：" << nLen << endl; //输出变量 nLen

cout << "pLen 指向的数据：" << *pLen << endl; //输出指针指向的数据

return 0;

}

运行程序，效果如图 2 所示。

图 2 使用指针访问数据

在上述代码中，首先定义了一个整型变量 nLen，将其初始化为 10，然后定义一个整型指针变量 pLen，初始化为 nLen 的地址。接着输出指针 pLen 指向的数据，发现其结果为 10，即变量 nLen 的值。最后修改指针 pLen 指向的数据为 100，输出变量 nLen 的值和 pLen 指向的数据，发现他们的数据相同，均为 100。这样，通过指针就可以访问和修改它指向的对象了。

q 指针变量的赋值

两个整型变量之间可以进行赋值，两个指针变量之间也可以进行赋值。例如：

int nLen = 10; //定义一个整型变量，初始化为 10

int nCount = 100; //定义一个整型变量，初始化为 100

int* pLen = &nLen; //定义一个整型指针，初始化为 nLen 的地址

int* pCount = &nCount; //定义一个整型指针，初始化为 nCount 的地址

pLen = pCount; //将 pCount 赋值给 pLen

在上述代码中，语句“pLen = pCount;”将指针 pCount 赋值给 pLen，此时 pLen 的值（pLen 所指向的地址）与 pCount 的值是相同的，即变量 nCount 的地址，那么 pLen 所指向的数据（*pLen）为 100，即 nCount 的值。

q 指针与数组

使用指针不仅可以指向变量，还可以指向数组对象。对于数组来说，数组名表示的是数组的首地址，即数组中第一个元素的地址。因此将数组直接赋值给指针对象是完全合法的。例如：

int nArray[5] = {1, 2, 3, 4, 5}; //定义一个包含 5 个元素的整型数组，并进行初始化

int* pIndex = nArray; //定义一个整型指针，将其初始化为一个数组对象

这样，指针 pIndex 就指向了数组的首地址，即数组中第一个元素的地址。图 2 描述了指针 pIndex 与数组 nArray 的关系。

图 2 指针与数组的关系

由于数组中的元素是按顺序存储的，因此通过指针 pIndex 可以访问到数组中的每一个元素。当前指针 pIndex 指向数组 nArray 中的第一个元素，如何能够让 pIndex 指向其他的元素呢？这是通过在指针对象上使用“++”运算符实现的。下面简要介绍“++”运算符。“++”运算符是一个一元运算符，它能够对某一个对象进行自加 1 操作。例如，定义一个整型变量 nIndex，初始值为 10。经过“nIndex++”运算之后，变量 nIndex 的值为 11。对于指针变量来说，进行“++”运算并不是简单地对指针值自加 1 或者对指针指向的数据自加 1，而是对指针的值自加“sizeof(指针类型)”。以指针 pIndex 为例，它的初始值（指针包含的地址，不是指针指向的数据）为 0013FF6C，如果进行“pIndex++”运算，则此时指针 pIndex 的值（指针包含的地址）为 0013FF6C + sizeof(int)，即 0013FF70，也就是第 2 个元素 nArray[1]的地址。这样 pIndex 就指向了第 2 个数组元素。下面通过一个实例来说明如何使用指针遍历一个数组。

使用指针变量遍历数组

#include "stdafx.h"

#include "iostream.h"

int main(int argc, char* argv[])

{

int nArray[5] = {1, 2, 3, 4, 5}; //定义一个数组，包含 5 个元素

int* pIndex = nArray; //定义一个指针，指向数组

for(int i=0; i<5; i++) //使用指针遍历数组

{

cout << *pIndex << endl; //读取数组元素值

pIndex++; //使指针指向下一个数组元素

}

return 0;

}

运行程序，效果如图 3 所示。

图 3 使用指针遍历数组

上述代码中，语句“cout << *pIndex << endl;”和“pIndex++;”还可以合成一条语句，例如：

cout << *pIndex++ << endl;

其作用是相同的。输出当前指针 pIndex 指向的数据，并使指针 pIndex 指向下一个元素。这里的“++”是后置运算符，先计算表达式“*pIndex”的值，然后执行“pIndex++”操作。

q 指针数组

数组是相同数据类型元素的集合，那么数组中的元素是否可以是指针类型呢？答案是可以的，此时的数组称之为指针数组。例如，下面的语句定义了一个指针数组。

int* pArray[3]; //定义一个指针数组，数组中包含 3 个元素

数组中的每一个元素均是指针，即 pArray[0]、pArray[1]和 pArray[2]均是一个指针。下面的代码实现了为指针数组赋值。

int* pArray[3]; //定义一个指针数组

int nArray[3] = {1, 2, 3}; //定义一个数组，包含 3 个元素

for(int i=0; i<3; i++) //使用指针遍历数组

{

pArray[i] = &nArray[i]; //为指针数组元素赋值

cout << *pArray[i] << endl; //读取数组元素值

}

我们知道，数组名表示的是数组的首地址，即第一个元素的地址。用户可以将一个不同的数组名赋值给一个指针变量，那么能否将指针数组赋值给指针变量呢？答案是不可以的。分析下面的语句。

int* pArray[3]; //定义一个指针数组

int* pIndex = pArray; //错误的赋值语句

如果在程序中编译上述代码，会出现不能从 int *[3]到 int*的转换的错误。这是因为数组名 pArray 是一个是不同寻常的指针，它指向数组中的第一个元素 pArray[0]，而 pArray[0]本身又是一个指针，它能够指向其他的整型变量。使得 pArray 成为了一个“指针的指针”对象。将一个指针的指针对象赋值给指针对象当然是非法的。如果能够定义一个指针的指针对象，那么就可以将 pArray 赋值给该对象。在 C++中定义一个指针的指针，可以采用如下的形式：

数据类型** 变量名;

例如，下面的代码定义了一个整型指针的指针对象。

int** pIndex; //指针的指针对象

这样，将指针数组 pArray 赋值给 pIndex 就是合法的了。例如：

pIndex = pArray; //将指针数组赋值给指针的指针

为了访问 pArray 数组中的元素，可以使用“*”运算符，即*pIndex，但是 pArray 数组中的元素是指针类型，为了访问数组元素指向的数据，需要使用**pIndex 来获取。下面通过一个实例来演示使用指针的指针遍历指针数组，输出数组元素指向的数据。

使用指针的指针遍历指针数组

#include "stdafx.h"

#include <iostream.h>

int main(int argc, char* argv[])

{

int* pArray[3]; //定义一个指针数组

//为 pArray 中的元素赋值

int nArray[3] = {1, 2, 3}; //定义一个数组，包含 3 个元素

for(int i=0; i<3; i++) //使用指针遍历数组

{

pArray[i] = &nArray[i];

}

int** pIndex; //定义指针的指针

pIndex = pArray; //将指针数组赋值给 pIndex

for (int j=0; j<3; j++)

{

cout << **pIndex << endl; //输出数组元素指向的数据

*pIndex++; //指向下一个数组元素

}

return 0;

}

运行程序，效果如图 4 所示。

图 4 使用指针的指针遍历指针数组

对于一个普通的指针来说，它可以指向任意数组长度的数组。例如，定义一个整型指针 pIndex，它可以指向具有 3 个元素的整型数组，也可以指向具有 5 个元素的整型数组。那么在应用程序中能否限制指针只允许指向具体元素个数的数组呢？这需要使用一个特殊的指针的指针来实现。分析下面的语句。

int nArray[3] = {1, 2, 3}; //定义一个整型数组

int (*pArray)[3]; //定义一个指针的指针

pArray = &nArray; //为 pArray 赋值

上述代码中注意“int (*pArray)[3];”语句定义的是一个指针的指针，而不是一个指针数组。该对象能够指向具有 3 个元素的数组的地址。如果将数组 nArray 的长度设置为 4，则上述代码是无法通过编译的。

q 常量指针和指针常量

在定义指针时，也可以使用 const 关键字。例如：

int nVar = 10; //定义一个整型变量

const int *pVar = &nVar; //定义一个整型常量的指针，并进行初始化

对于指针 pVar 来说，用户不能够修改 pVar 指向的值，但是可以修改 pVar 指向的地址。例如：

int nVar = 10; //定义一个整型变量

const int *pVar = &nVar; //定义一个指针常量，对其进行初始化

//*pVar = 20; //错误的代码，不能修改指针常量的数据

int nNum = 5; //再次定义一个整型变量

pVar = &nNum; //修改指针常量指向的地址

在定义常量指针时，也可以将 const 关键字放置在数据类型之后，*符号之前的位置。例如：

int const *pVar = &nVar; //等同于 const int *pVar = &nVar;

在使用 const 关键字定义指针时，也可以将 const 关键字放置在指针变量的前面。但是指针变量的性质发生了改变。观察如下代码：

int nVar = 10; //定义一个整型常量

int * const pVar = &nVar; //定义一个指向整型的常量指针

*pVar = 20; //修改指针指向的数据

int nNum = 5; //定义一个整型变量

//pVar = &nNum; //错误的代码，不能够修改常量指针指向的地址

上面的代码，定义了一个常量指针 pvar，使得用户不能够修改 pvar 指向的地址，但是可以修改 pvar 指向的数据。

在定义指针时也可以同时使用两个 const 关键字。例如：

int nVar = 10; //定义一个整型变量

const int * const pVar = &nVar; //定义一个常量指针常量

在上面的代码中，用户即不能够修改 pVar 指向的地址，也不能够修改 pVar 指向的数据。因此，下面的代码均是非法的。

//*pVar = 20; //非法的代码，不能够修改 pVar 指向的数据

int nNum = 5; //定义一个整型变量

//pVar = &nNum; //非法的代码，不能够修改 pVar 指向的地址