分类: C/C++
2010-05-15 15:36:29
指针数组是一个各元素保存的均是内存地址值的数组,因此各元素都可以当做一个指针来使用。
int i = 3, j = 4, k = 5; // 定义3个int类型变量并初始化
int *pa[3] = { &i, &j, &k }; // 定义一个指针数组,并初始化各个
// 元素为相应变量的地址
*pa[0] = 6, *pa[1] = 8, *pa[2] = 10; // 以指针方式更改变量的值
执行以上代码片断,变量i,j,k的值分别为6,8,10。
数组指针是一个指向数组的指针,因此在对数组指针进行取值运算时(在指针变量名前面加*),所得到的结果是一个数组,即含有若干元素的数组的首地址。
假设有如下代码片断:
int a[2][3] = { {1, 2, 3}, {4, 5, 6} }; // 定义一个具有2行,3列的二维数组
int (*p)[3] = a; // 定义一个数组指针,并使其指向数组a
对于数组指针p,指向了一个2行,3列的二维数组a,因此在对p进行取值运算时,*p所得到的结果指向了一个一维数组,即数组a中的第1行中的3列元素所构成的一个具有3个元素的数组,值为数组a中第1行第1列元素的地址;同理,*(p+1)所得到的结果指向了数组a中第2行中的3列元素所构成的一个具有3个元素的一维数组,值为数组a中第2行第1列元素的地址。但是,*(p+2)则会访问越界,以后对p的操作可能会发生意想不到的情况。
在此基础上,如果我们再进行深一层次的取值操作,则可以取得数组a中相应元素的值了,表2是对数组指针p操作的部分结果。
表2. 对数组指针操作的部分结果
对p操作方式 | 结果 | 等效的a操作的结果 |
*p | 数组a第1行元素首地址 | a[0] |
*(p+1) | 数组a第2行元素首地址 | a[1] |
**p | 数组a第1行第1列元素的值 | a[0][0] |
**(p+1) | 数组a第2行第1列元素的值 | a[1][0] |
*(*(p+1)+1) | 数组a第2行第2列元素的值 | a[1][1] |
p[0][0] | 数组a第1行第1列的值 | a[0][0] |
p[1][2] | 数组a第2行第3列的值 | a[1][2] |
可见,指针数组与数组指针在声明或者定义语法上只差一个小括号(),但是其意义以及得到的结果却相差甚远。
多级指针是指指向指针的指针,最简单的多级指针是二级指针,而我们之前所涉及到的指针都是一级指针。如果在定义指针变量时,在变量名前冠以一个星号代表一级的话,二级指针变量在定义时就应该在变量名前加上连续的两个星号,三级指针变量则在变量名前加上三个连续的星号,以此类推。在此,我们只介绍二级指针的原理,三级或更高级别的指针原理相同。
有如下代码片断:
int i = 3; // 定义一个int型变量i,并赋初始值
int *p = &i; // 定义一个一级指针变量,并赋值为i在内存中的地址
int **pp = &p; // 定义一个二级指针变量,并赋值为指针p在内存中的地址
上述代码片断执行完毕后,各变量在内存中的布局如下:
当对一级指针变量p进行取值运算时,*p所得到的值即为p所指向内存地址中的值,即3。而当对二级指针pp进行一次取值运算时,*pp所得到的值为pp所指向内存地址中的值,即是指针变量p的值;当再次对pp进行取值运算时,**pp的值为指针变量p所指向内存地址中的值,为3。
内存的动态分配与释放离不开指针的使用,可以借助指针实现一维数组的动态分配与释放。
int *pa = 0; // 定义一个指针,并置空
// 动态分配可以保存3个int数据的内存空间,并将地址赋予pa
pa = new int[3];
pa[0] = 1, pa[1] = 2, pa[2] = 3; // 通过指针为元素赋值
// 对元素做一些其它操作
delete[] pa; // 释放内存
二维及二维以上数组的动态分配比一维数组要显得复杂,主要在于要从低到高依次为每一维分配内存空间保存维的相应信息。
// 以下代码片断分配2行3列的二维数组
int **ppa = 0; // 定义一个二级指针保存成功分配二维数组的地址
// 分配第一维的大小:行
ppa = new int*[2];
// 为每一行分配列元素
for (int i = 0; i < 2; i++)
ppa[i] = new int[3];
// 可以使用p[行][列]方式访问二维数组中的元素
// 回收内存
for (int i = 0; i < 2; i++)
delete[] ppa[i]; // 释放每一行列元素所占内存
delete[] ppa; // 释放行所占内存
当一个指针变量所指向的内存地址中保存的是一个类对象,则可以通过指针运算符->访问该对象的成员。
class data { // 定义一个简单类
public:
data(int d) : _d (d) {} // 构造函数,初始化私有变量
void display() const { // 公有成员访法,输出对象信息
std::cout << “value of data is: ” << _d << std::endl;
}
private:
int _d; // 类的私有成员
};
data *pd = new data(3); // 动态创建一个simple类对象
pd->display(); // 用指针运算符访问对象的方法
delete pd; // 回收动态创建的对象所占用的内存
this指针是一种只能在类的非静态成员函数中使用的特殊指针。this指针指向了调用成员函数的对象本身。
3.3.3小节中data类的display成员函数可以重新定义如下:
void display() const {
std::cout << “value of data is: “ << this->_d << std::endl;
}
data d(3); // 定义一个data类对象
d.display();
上述代码片断中,d.display()将会调用display成员函数显示类对象的信息。此时display成员函数体中的this指针实际上指向了对象d在内存中的地址,因为display成员函数是通过对象d调用的。
this指针的另外一个重要用途就是使类的非静态成员函数返回类对象本身的引用,使得通过一个类对象连续调用成员函数成为可能。
为3.3.3小节中data类添加一个新的公有成员函数add,代码片断如下:
data& add() {
_d += 1;
return *this; // 返回调用该成员函数的类对象本身的引用
}
data d(3);
d.add().add().add(); // 对对象d连续多次调用add()成员函数
在上述代码片断中,d.add()的返回结果是d对象本身,因而可以接下来直接再次调用add成员函数,同时使该对象的_d成员的值增加1;在接下来的两次对add成员函数的调用都会使_d成员值增加1。
多态(polymorphism)是面向对象程序设计语言的一个重要特性,C++中的虚拟函数(virtual function)是实现这一特性的重要支撑手段。但是利用虚拟函数实现多态只能通过指向对象的指针或者对象的引用两种方法。
// 定义一个描述形状的抽象基类
class shape {
public:
virtual void draw() = 0;
};
// 定义一个派生的三角形类
class triangle {
public:
triangle(const int (&a)[2], const int (&b)[2], const int (&c)[2]) {
p1[0] = a[0], p1[1] = a[1];
p2[0] = b[0], p2[1] = b[1];
p3[0] = c[0], p3[1] = c[1];
}
void draw() {
std::cout << “to draw triangle” << std::endl;
// 根据三个顶点信息画三角形
}
private:
int p1[2], p2[2], p3[2]; // 三角形的三个顶点
};
// 定义另外一个派生的圆类
class circle {
public:
circle(const int (&a)[2], int r) {
c[0] = a[0], c[1] = a[1], radius = r;
}
void draw() {
std::cout << “to draw circle” << std::endl;
// 根据圆心和半径画圆
}
private:
int c[2]; // 圆心坐标
int radius; // 圆半径
};
shape *ps[2];
int a[2] = { 10, 10 }, b[2] = { 20, 20 }, c[2] = { 30, 30 };
int r = 50;
ps[0] = new triangle(a, b, c); // 动态创建一个三角形对象
ps[1] = new circle(a, r); // 动态创建一个圆对象
for (int i = 0; i < 2; i++)
ps[i]->draw(); // 利用指针实现多态,调用正确的绘制方法
delete ps[0]; // 释放动态创建的三角形对象
delete ps[1]; // 释放动态创建的圆对象
指针可以指向函数地址,可以将函数指针作为函数参数,则可以实现回调功能。以下代码片断演示了如何将一个函数指针作为比较的规则。
int compare(int a, int b)
{
if (a == b) return 0;
return (a > b ? 1 : -1);
}
int result(int a, int b, int (*pfun)(int, int))
{
return (*pfun)(a, b);
}
int a = 3, b = 4;
int (*pf)(int, int) = &compare;
std::cout << result(a, b, pf) << std::endl;
上述代码中,函数指针pf指向了函数compare的地址,并且作为函数result的参数来充当比较参数a和b的规则,如果将pf指向另外的函数作为判断规则,就可以得到不同的结果。
指针除了可以指向全局函数地址之外,也可以指向类的成员函数。应用这一特性可以方便的实现另外一种风格的回调功能。
假设已经定义好了3.3.3小节的data类,则以下代码片断:
// 定义一个指向data类成员函数display
void (data::*pf)() const = &data::display;
data *pd = new data(9);
(pd->*pf)(); // 通过指针调用成员函数
delete pd;
指针在C++语言中是非常重要,非常灵活的东西,同时在使用指针的问题上,它又是一把双刃剑。在对指针充分理解的基础上,指针使用得当,不但使程序设计优雅,灵活而且能够保证程序的执行效率;另一方面,如果对指针的概念一知半解,则在使用指针问题上应当非常谨慎,否则将会导致程序的执行带来意想不到的后果。
本文是作者在对指针认识的基础上写的一篇介绍指针的文章,其中的措辞可能不甚严谨,属虚也难免有误,欢迎各位指正,谢谢!
email: blldw@163.com