您的位置:首页 > 编程语言 > C语言/C++

C++入门 (五) 析构函数和this指针

2012-08-01 08:13 357 查看
一、析构函数

前面的一些例子都没有说明析构函数,这是因为所用到的类在结束时不需要做特别的清理工作。下面的程序给出了一新的Date类,其中包括一个字符串指针,用来表示月份。

#include iostream.h

#include string.h

class Date

{

int mo,da,yr;

char *month;

public:

Date(int m=0, int d=0, int y=0);

~Date();

void display() const;

};

Date::Date(int m,int d,int y)

{

static char *mos[] =

{

January,February,March,April,May,June,

July,August,September,October,November,December

};

mo=m; da=d; yr=y;

if(m!=0)

{

month=new char[strlen(mos[m-1])+1];

strcpy(month, mos[m-1]);

}

else month = 0;

}

Date::~Date()

{

delete [] month;

}

void Date::display() const

{

if(month!=0) cout<<month<<' '<<da<<','<<yr;

}

int main()

{

Date birthday(8,11,1979);

birthday.display();

return 0;

}

在Date对象的构造函数中,首先用new运算符为字符串month动态分配了内存,然后从内部数组中把月份的名字拷贝给字符串指针month。

析构函数在删除month指针时,可能会出现一些问题。当然从这个程序本身来看,没什么麻烦;但是从设计一个类的角度来看,当Date类用于赋值时,就会出现问题。假设上面的main()修改为“

int main()

{

Date birthday(8,11,1979);

Date today;

today=birthday;

birthday.display();

return 0;

}

这会生成一个名为today的空的Date型变量,并且把birthday值赋给它。如果不特别通知编译器,它会简单的认为类的赋值就是成员对成员的拷贝。在上面的程序中,变量birthday有一个字符型指针month,并且在构造函数里用new运算符初始化过了。当birthday离开其作用域时,析构函数会调用delete运算符来释放内存。但同时,当today离开它的作用域时,析构函数同样会对它进行释放操作,而today里的month指针是birthday里的month指针的一个拷贝。析构函数对同一指针进行了两次删除操作,这会带来不可预知的后果。

如果假设today是一个外部变量,而birthday是一个自变量。当birthday离开其作用域时,就已经把对象today里的month指针删除了。显然这也是不正确的。

再假设有两个初始化的Date变量,把其中一个的值赋值给另一个:

Date birthday(8,11,1979);

Date today(12,29,2003);

today=birthday;

问题就更复杂了,当这两个变量离开作用域时,birthday中的month的值已经通过赋值传递给了today。而today中构造函数用new运算符给month的值却因为赋值被覆盖了。这样,birthday中的month被删除了两次,而today中month却没有被删除掉。

二、重载赋值运算符

为了解决上面的问题,我们应该写一个特殊的赋值运算符函数来处理这类问题。当需要为同一个类的两个对象相互赋值时,就可以重载运算符函数。这个方法可以解决类的赋值和指针的释放。

下面的程序中,类中的赋值函数用new运算符从堆中分配了一个不同的指针,该指针获取赋值对象中相应的值,然后拷贝给接受赋值的对象。

在类中重载赋值运算符的格式如下:

void operator = (const Date&)

后面我们回加以改进。目前,重载的运算符函数的返回类型为void。它是类总的成员函数,在本程序红,是Date类的成员函数。它的函数名始终是operator =,参数也始终是同一个类的对象的引用。参数表示的是源对象,即赋值数据的提供者。重载函数的运算符作为目标对象的成员函数来使用。

#include iostream.h

#include string.h

class Date

{

int mo,da,yr;

char *month;

public:

Date(int m=0, int d=0, int y=0);

~Date();

void operator=(const Date&);

void display() const;

};

Date::Date(int m, int d, int y)

{

static char *mos[] =

{

January,February,March,April,May,June,

July,August,September,October,November,December

};

mo = m; da = d; yr = y;

if (m != 0)

{

month = new char[strlen(mos[m-1])+1];

strcpy(month, mos[m-1]);

}

else month = 0;

}

Date::~Date()

{

delete [] month;

}

void Date::display() const

{

if (month!=0) cout<<month<<' '<<da<<,<<yr<<endl;

}

void Date::operator=(const Date& dt)

{

if (this != &dt)

{

mo = dt.mo;

da = dt.da;

yr = dt.yr;

delete [] month;

if (dt.month != 0)

{

month = new char [std::strlen(dt.month)+1];

std::strcpy(month, dt.month);

}

else month = 0;

}

}

int main()

{

Date birthday(8,11,1979);

birthday.display();

Date newday(12,29,2003);

newday.display();

newday = birthday;

newday.display();

return 0;

}

除了为Date类加入了一个重载运算符函数,这个程序和上面的一个程序是相同的。赋值运算符函数首先取得所需的数据,然后用delete把原来的month指针所占用的内存返还给堆。接着,如果源对象的month指针已经初始化过,就用new运算符为对象重新分配内存,并把源对象的month字符串拷贝给接受方。

重载的Date类赋值运算符函数的第一个语句比较了源对象的地址和this指针。这个操作取保对象不会自己给自己赋值。

三、this指针

this指针是一个特殊的指针,当类的某个非静态的成员函数在执行时,就会存在this指针。它指向类的一个对象,且这个对象的某个成员函数正在被调用。

this指针的名字始终是this,而且总是作为隐含参数传递给每一个被声明的成员函数,例如:

void Date::myFunc(Date* this);

实际编程时函数的声明不需要包含这个参数。

当程序中调用某个对象的成员函数时,编译器会把该对象的地址加入到参数列表中,感觉上就好象函数采用了上面所示的声明,并且是用如下方式来调用的:

dt.myFunc(& dt);

静态成员函数不存在this指针。

当调用某个对象的成员函数时,编译器把对象的地址传递给this指针,然后再调用该函数。因此,成员函数你对任何成员的调用实际上都隐式地使用了this指针。

1.以this指针作为返回值

使用this指针可以允许成员函数返回调用对象给调用者。前面的程序中重载赋值运算符没有返回值,因此不能用如下的形式对字符串进行赋值:

a=b=c;

为了使重载的类赋值机制也能这样方便,必须让赋值函数返回赋值的结果,在这里就是目标对象。当赋值函数执行时,其返回值也恰好是this指针所指的内容。

下面的程序对前面那个程序进行了修改,让重载赋值运算符返回了一个Date对象的引用。

#include iostream.h

#include string.h

class Date

{

int mo,da,yr;

char *month;

public:

Date(int m=0, int d=0, int y=0);

~Date();

void operator=(const Date&);

void display() const;

};

Date::Date(int m, int d, int y)

{

static char *mos[] =

{

January,February,March,April,May,June,

July,August,September,October,November,December

};

mo = m; da = d; yr = y;

if (m != 0)

{

month = new char[strlen(mos[m-1])+1];

strcpy(month, mos[m-1]);

}

else month = 0;

}

Date::~Date()

{

delete [] month;

}

void Date::display() const

{

if (month!=0) cout<<month<<' '<<da<<,<<yr<<endl;

}

void Date::operator=(const Date& dt)

{

if (this != &dt)

{

mo = dt.mo;

da = dt.da;

yr = dt.yr;

delete [] month;

if (dt.month != 0)

{

month = new char [std::strlen(dt.month)+1];

std::strcpy(month, dt.month);

}

else month = 0;

}

return *this;

}

int main()

{

Date birthday(8,11,1979);

Date oldday,newday;

oldday=newday=birthday;

birthday.display();

oldday.display();

newday.display();

return 0;

}

2.在链表中使用this指针

在应用程序中,如果数据结构里有指向自身类型的成员,那么使用this指针会提供更多的方便。下面的程序中建立了一个类ListEntry的链表。

#include iostream.h

#include string.h

class ListEntry

{

char* listvalue;

ListEntry* preventry;

public:

ListEntry(char*);

~ListEntry() { delete [] listvalue; }

ListEntry* PrevEntry() const { return preventry; };

void display() const { cout<<endl<<listvalue; }

void AddEntry(ListEntry& le) { le.preventry = this; }

};

ListEntry::ListEntry(char* s)

{

listvalue = new char[strlen(s)+1];

strcpy(listvalue, s);

preventry = 0;

}

int main()

{

ListEntry* prev = 0;

while (1)

{

cout <<endl<<enter a="" name('end'="" when="" done):="" ;

char name[25];

cin >> name;

if (strncmp(name, end, 3) == 0) break;

ListEntry* list = new ListEntry(name);

if (prev != 0) prev->AddEntry(*list);

prev = list;

}

while (prev != 0)

{

prev->display();

ListEntry* hold = prev;

prev = prev->PrevEntry();

delete hold;

}

return 0;

}

程序运行时,会提示输入一串姓名,当输入完毕后,键入end,然后程序会逆序显示刚才输入的所有姓名。

程序中ListEntry类含有一个字符串和一个指向前一个表项的指针。构造函数从对中获取内存分配给字符串,并把字符串的内容拷贝到内存,然后置链接指针为NULL。析构函数将释放字符串所占用的内存。

成员函数PrevEntry()返回指向链表前一个表项的指针。另一个成员函数显示当前的表项内容。

成员函数AddEntry(),它把this指针拷贝给参数的preventry指针,即把当前表项的地址赋值给下一个表项的链接指针,从而构造了一个链表。它并没有改变调用它的listEntry对象的内容,只是把该对象的地址赋给函数的参数所引用的那个ListEntry对象的preventry指针,尽管该函数不会修改对象的数据,但它并不是常量型。这是因为,它拷贝对象的地址this指针的内容给一个非长常量对象,而编译器回认为这个非常量对象就有可能通过拷贝得到的地址去修改当前对象的数据,因此AddEntry()函数在声明时不需要用const。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: