📄 c++10.dat
字号:
private:
int width;
int height;
//...
};
Image::Image (const int w, const int h)
{
width = w;
height = h;
//...
}
其实,类Image中的width和height的初始化也可以通过成员初始化表来初始化,例如:
程序段2
class Image {
public:
Image(const int w, const int h);
private:
int width;
int height;
//...
};
Image::Image (const int w, const int h) : width(w), height(h)
{
//...
}
这个定义的效果是width被w初始化,height被w初始化.用成员初始化表初始化类成员与用构造函数初始化类成员的区别在于:前者的初始化是在构造函数被执行以前进行的.
成员初始化表可用于初始化类的任意数据成员(后面要介绍的静态数据成员除外),它被放在构造函数的头和体之间,并用冒号将它与构造函数的头分隔开.它由逗号分隔的数据成员表组成,初值放在一对圆括号中.
2.3 析构函数
我们已经知道,当对象创建时,会自动调用构造函数进行初始化.当对象销毁时,也会自动调用析构函数进行一些清理工作.与构造函数类似的是:析构函数也与类同名,但在名字前有一个~符号,析构函数也没有返回类型和返回值.但析构函数不带参数,不能重载,所以析构函数只有一个.
若一个对象中有指针数据成员,该指针数据成员指向某一个内存块.在对象销毁前,往往通过析构函数释放该指针指向的内存块.例如,Set类中elems指针指向一个动态数组,我们应该给Set类再定义一个析构函数,使elems指向的内存块能够在析构函数中被释放:
class Set
{
public:
Set (const int size);
~Set(void) {delete elems;} // 析构函数
//...
private:
int *elems; // 集合元素
int maxCard; // 集合最大尺寸
int card; // 集合元素个数
};
下面我们通过一个例子,分析析构函数是如何工作的:
void Foo (void)
{
Set s(10);
//...
}
当函数Foo被调用时,创建对象s,并调用其构造函数,为s.elems分配内存及初始化其它对象成员.在Foo函数返回以前,s的析构函数被调用,释放s.elems指向的内存区.
注意:对象的析构函数在对象销毁前被调用,对象何时销毁也与其作用域有关.例如,全局对象是在程序运行结束时销毁,自动对象是在离开其作用域时销毁,而动态对象则是在使用delete运算符时销毁.析构函数的调用顺序与构造函数的调用顺序相反.
用析构函数确保对象的清除
我们常常不会忽略初始化的重要性,却很少想到清除的重要性.实际上,清除也很重要,例如,我们在堆中申请了一些内存,如果不在用完后就释放,就会造成内存泄露,它会导致应用程序运行效率降低,甚至崩溃,我们不可掉以轻心.在C++中,提供了析构函数,保证对象清除工作的自动执行.
析构函数与构造函数不同点是:析构函数的功能与构造函数相反,也不带任何参数,所以,它不能被重载.当对象超出其作用域被销毁时,析构函数会被自动调用.
例
#include <iostream.h>
class Tree
{
int height;
public:
Tree (int initialHeight); //Constructor
~Tree (); //Destructor
void grow(int years);
void printsize();
};
Tree:: Tree (int initialHeight)
{
height = initialHeight;
}
Tree::~ Tree ()
{
cout<<"inside tree destructor"<<endl;
printsize();
}
void Tree::grow(int years)
{
height += years;
}
void Tree::printsize()
{
cout<<"tree height is "<<height<<endl;
}
void main()
{
cout<<"before opening brace"<<endl;
{
Tree t(12);
cout<<"after tree creation"<<endl;
t.printsize();
t.grow(4);
cout<<"before closing brace"<<endl;
}
cout<<"after closing brace"<<endl;
}
下面是上面程序的输出结果:
before opening brace
after tree creation
tree height is 12
before closing brace
inside tree destructor
tree height is 16
after closing brace
我们可以看到,析构函数在包含它的右括号处调用.
关于返回值
构造函数和析构函数是两个非常特殊的函数:它们没有返回值.这与返回值为void的函数是不同的.后者虽然也不返回任何值,但有返回类型,而构造函数和析构函数根本就没有返回类型.
2.4 缺省的构造函数和析构函数
前面我们介绍了构造函数和析构函数的特点、功能及应用.当用户未显式定义构造函数和析构函数时,编译器会隐式定义一个内联的、公有的构造函数和析构函数.
缺省的构造函数执行创建一个对象所需要的一些初始化操作,但它并不涉及用户定义的数据成员或申请的内存的初始化.例
class C
{
private:
int n;
char *p;
public:
virtual ~C() {}
};
void f()
{
C obj; //隐式定义的构造函数被调用
}
类C没有显式定义构造函数,一个隐式缺省的构造函数被定义.在函数f中,定义对象obj时,调用该构造函数,但它并不初始化数据成员n和p,也不为后者分配内存.
同样,缺省的的析构函数也不涉及释放用户申请的内存的释放等清理工作.
第三节 复制构造函数
对一个简单变量的初始化方法是用一个常量或变量初始化另一个变量,例如:
int m = 80;
int n = m;
我们已经会用构造函数初始化对象,那么我们能不能象简单变量的初始化一样,直接用一个对象来初始化另一个对象呢?答案是肯定的.我们以前面定义的Point类为例:
Point pt1(15, 25);
Point pt2 = pt1;
后一个语句也可以写成:
Point pt2( pt1);
它是用pt1初始化pt2,此时,pt2各个成员的值与pt1各个成员的值相同,也就是说,pt1各个成员的值被复制到pt2相应的成员当中.在这个初始化过程当中,实际上调用了一个复制构造函数.当我们没有显式定义一个复制构造函数时,编译器会隐式定义一个缺省的复制构造函数,它是一个内联的、公有的成员,它具有下面的原型形式:
Point:: Point (const Point &);
可见,复制构造函数与构造函数的不同之处在于形参,前者的形参是Point对象的引用,其功能是将一个对象的每一个成员复制到另一个对象对应的成员当中.
虽然没有必要,我们也可以为Point类显式定义一个复制构造函数:
Point:: Point (const Point &pt)
{
xVal=pt. xVal;
yVal=pt. yVal;
}
如果一个类中有指针成员,使用缺省的复制构造函数初始化对象就会出现问题.为了说明存在的问题,我们假定对象A与对象B是相同的类,有一个指针成员,指向对象C.当用对象B初始化对象A时,缺省的复制构造函数将B中每一个成员的值复制到A的对应的成员当中,但并没有复制对象C.也就是说,对象A和对象B中的指针成员均指向对象C.
第四节 类作用域
我们已经学习了局部作用域和全局作用域,下面介绍类作用域,所有的类成员是属于类作用域的.
类本身可被定义在三种作用域内:
1. 全局作用域.这是所谓全局类,绝大多数的C++类是定义在该作用域中,我们在前面定义的所有类都是在全局作用域中.
2. 在另一个类的作用域中.这是所谓嵌套类,即一个类包含在另一个类中.
3. 在一个块的局部作用域中.这是所谓局部类,该类完全被块包含.
例如:
int fork (void); // 全局fork
class Process {
int fork (void);
//...
};
成员函数fork隐藏了全局函数fork,前者能通过单目域运算符调用后者:
int Process::fork (void)
{
int pid = ::fork(); // 使用全局fork
//...
}
下面举一个嵌套类的例子.
例
class Rectangle {
public:
Rectangle (int, int, int, int);
//..
private:
class Point {
public:
Point (int, int);
private:
int x, y;
};
Point topLeft, botRight;
};
类Point嵌套在Rectangle类中,Point成员函数可定义为内联的,也可在全局作用域中,但后者对成员函数名需要更多的限制,例如:
Rectangle::Point::Point (int x, int y)
{
//...
}
同样,在类域外访问嵌套类需要限制类名,例如:
Rectangle::Point pt(1,1);
下面我们再看一个局部类的例子:
例
void Render (Image &image)
{
class ColorTable {
public:
ColorTable(void) { /* ... */ }
AddEntry(int r, int g, int b) { /* ... */ }
//...
};
ColorTable colors;
//...
}
类ColorTable是在函数Render中的局部类.与嵌套类不同的是:局部类不能在其局部作用域外访问,例如:
ColorTable ct; // 非法,没有定义ColorTable类!
局部类必须完全定义在局部作用域内.所以,它的所有成员函数必须是内联的,这就决定了局部类的成员函数都是很简单的.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -