在c语言系列中,指针使得使用这种语言编写程序灵活。然而,灵活的另一面常常会带来多变,多变则容易出错。这篇将就指针进行深入理解和学习。
指针的定义是指向某一数据类型或者类的变量。也可以理解为地址。但是地址在一般意义上是一种常量,严格意义上这样理解指针是不对的。指针根据指向的类型来区分。比如说int型指针p,就是int *p。随着指针指向的类型复杂而变得复杂。比如说,指向类A派生类B的指针p,那么p在调用函数的时候,调用的是哪个类中函数将会变得复杂,基类的指针又如何过渡到子类,这涉及的是类的多态型,这里不展开说明。那么指针为什么常常会出错呢?这里主要总结两点。
第一、指针在没有初始化的情况下就调用指向对象的成员变量。在程序比较小的时候,我们很容易发现。但是一旦程序比较大,初始化和声明往往分开。如果在初始化之前调用,那么就很容易引起程序编译出错。在C++中,声明和定义可以同时进行。在指针未进行初始化时,往往都需要将指针初始化为NULL。那么指针在未初始化之前直接调用成员函数是否一定会出错呢?我自己测试了一下。
struct MyTest{
void funa(){
cout << "you are welcom!";
return;
}
int funb()
{
return m_a;
}
int m_a;
MyTest(int a)
{
m_a = a;
}
};
void TestPointer()
{
MyTest *p;
p->funa();
return;
}
这里定义了一个MyTest的类,在TestPointer中声明了一个MyTest型指针p,然后用该指针进行调用指向类的成员函数。结果在编译时出现如下错误:
cpp(27): error C4700: 使用了未初始化的局部变量“p”
这是因为p为进行初始化。如果我将p初始化为NULL呢?
void TestPointer()
{
MyTest *p = NULL;
p->funa();
return;
}
那么程序将编译通过,并且正确执行funa()函数。接下来我调用funb()函数。
void TestPointer()
{
MyTest *p = NULL;
p->funa();
p->funb();
return;
}
这时编译能够通过,但是在执行时会出现错误。我们发现funa()与funb()的区别在于前者没有调用类的成员变量,后者调用了成员变量。而成员变量要依附于类的对象才能存在,在这里对象没有。成员变量就不存在,这会导致错误出现。
第二、指针的指向对象没有释放,直接将指针删除。这是最容易出现的,并且常常难以发现。比如在函数中变量的声明周期随着函数的结束而自动销毁。
测试实例如下:
void TestPointer(int *p)
{
int a[10] = { 0 };
p = a;
return;
}
int _tmain(int argc, _TCHAR* argv[])
{
int *p_test = NULL;
TestPoint(p_test);
cout << *p_test<< endl;
return 0;
}
这里编译将不会出错,但是在执行时却会出现错误。具体原因就是在TestPointer()中是值传递,传递的是指针本身的值NULL给p,指针p随着函数的结束而生命周期结束,而p_test仍为NULL,这时输出*p_test将会出错。
指针和数组往往联系密切,这里又会增加指针的难度。
我们称int *(p)[10]这样类似的指针p代表的是数组指针。假如用new来为这样的p初始化,在对p进行析构时,利用delete [] p。
int *(p[10])就称为指针数组。假如用new来初始化p,那么对这些申请的内存释放用的是delete [] *p。
如果我们想申请一个二维数组的指针p,那么p如何初始化呢?