C++ 使用 new 和 delete 运算符来动态内存分配。遗憾的是,在类中使用这些运算符将导致许多新的编程问题。这时,析构函数是必不可少的,有时候还必须重载运算符。
新的存储类型静态类成员.
下面是一个示例:
// strngbad.h
#include
#ifnedf STRNGBAD_H_
#define STRNGBAD_H_
class StringBad
{
private:
char * str;
int len;
static int num;
public:
StringBad(const char * s);
StringBad();
~StringBad();
// friend function
friend std::ostream & operator<<(str:ostream & os,
const StringBad & st);
};
#endif
静态类成员有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本。也就是说,类的所有对象共享同一个静态成员,这对于所有类对象都具有相同值的类私有数据是非常方便。
// strngbad.cpp
#include
#include "strngbad.h"
using std::cout;
int String::num = 0;
// class methods
StringBad::StringBad(const char * s)
{
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str,s);
num++;
cout << num <<": \"" <
注意:不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存 . 但如果静态成员是 const 或枚举型,则可以在类声明中初始化.
另外,在构造函数中使用new时,必须在相应的析构函数中使用delete。
如果没有提供任何构造函数,C++ 将创建默认构造函数。例如,假如定义了一个 Klunk 类,但没有提供任何构造函数,编译器将提供下述默认构造函数:
Klunk::Klunk()
{
}
// 一个不接受参数,不执行操作的构造函数
Klunk lunk;
默认构造函数使 lunk 类似于一个常规的自动变量,它的值在初始化时未知的。
如果定义了构造函数,C++将不会定义默认构造函数。如果希望在创建对象时不显式地对它进行初始化,则必须显式定义默认构造函数。它没有任何参数,但可以用来设定特定的值:
Klunk::Klunk()
{
Klunk_ct = 0;
...
}
带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值。例如,Klunk 类可以包含下述内联构造函数:
Klunk(int n = 0)
{
Klunk_ct = n;
}
但只能有一个默认构造函数。
(2)默认析构函数
复制构造函数用于将一个对象复制到新创建的对象中。它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。其原型如下:
Class_name(const Class_name &);
对于复制构造函数需要知道两点: 何时调用 , 有何功能 .
何时调用:
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。很多情况下都可能发生,最常见的情况是将新对象显式地初始化为现有的对象。以下四种声明都将调用复制构造函数:
StringBad ditto(motto);
StringBad metoo = motto;
StringBad also = StringBad(motto);
StringBad * pStringBad = new StringBad(motto);
有何功能:
默认的复制构造函数逐个复制非静态成员(成员复制也被称为浅复制),复制的是成员的值。
下述语句:
StringBad sailor = sports;
// 与下面的代码等效
StringBad sailor;
sailor.str = sports.str;
sailor.len = sports.len;
// 由于私有成员无法访问,因此这三行代码不能通过编译
如果成员本身就是类对象,则将使用这个类的复制构造函数来复制成员对象。静态成员不受影响,因为他们属于整个类,而不是各个对象。
注意:如果类中包含了使用 new 初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这被称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制仅浅浅地复制指针信息,而不会深入挖掘以复制指针引用的结构
赋值运算符只能由类成员函数来重载它.
对于由于默认赋值运算符不合适而导致的问题,解决方法是提供赋值运算符(进行深度复制)定义。其实现与复制构造函数相似,但也有一些差别。
1.由于目标对象可能引用了以前分配的数据,所以函数应使用delete[ ] 来释放这些数据.
2.函数应当避免将对象赋值给自身;否则,给对象重新赋值前,释放内存操作可能删除对象的内容.
3.函数返回一个指向调用对象的引用.
StringBad & StringBad::operator = (const StringBad & st)
{
if (this == &st)
{
return *this;
}
delete [] str;
len = st.len;
str = new char [len + 1];
std::strcpy(str, st.str);
return *this;
}
1. 如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete.
2. new 和 delete 必须相互兼容,new 对应于delete,new [ ] 对应于 delete [ ] .
3. 如果有多个构造函数,则必须以相同的方式使用new,要么都带 [ ] , 要么都不带。因为只有一个析构函数,所有的析构函数都必须与他兼容。然而,可以在一个构造函数中使用new 初始化指针,而在另一个构造函数中将指针初始化为空,这是因为delete 可以用于空指针.
如果函数返回(通过调用对象的方法或将对象作为参数)传递给它的对象,可以通过返回引用来提高效率。例如,要编写函数 Max(),它返回两个Vector对象中较大的一个。该函数将以下面的方式被使用:
Vector force1(50,60);
Vector force2(10,70);
Vector max;
max = Max(force1,force2);
以下两种实现都是可行的:
// version1
Vector Max(const Vector & v1, const Vector & v2)
{
if(v1.magval() > v2.magval)
{
return v1;
}
else{
return v2;
}
}
// version2
const Vector & Max(const Vector & v1, const Vector & v2)
{
if(v1.magval() > v2.magval)
{
return v1;
}
else{
return v2;
}
}
首先,返回对象将调用复制构造函数,而返回引用不会。所以第二个实现效率更高。其次,引用指向的对象应该在调用函数指向时存在。
两种常见的返回非 const 对象情形是,重载赋值运算符以及重载与 cout 一起使用的 << 运算符。
如果被返回的对象是被调用函数中的局部对象,则我们就不能够返回指针引用。当控制权毁掉调用函数时,引用已经不存在了。所以在这种情况下,我们应该放回对象而不是引用。
假如我们直接返回一个对象,那么我们可能会在程序中遇到一些容易令人误解的语句
net = v1 + v2;
v1 + v2 = net;
cout<<(v1 + v2 = net).getLength();
当我们返回了const类型的对象,我们能够阻止令人生畏的后两句的出现.