C++的优点体现在封装性(Encapsulation)、继承性(Inheritance)、多态性(Polymorphism),这些特性在类中都有所体现。类描述了一类事物,以及事物所应具有的属性,例如定义“多边形”这个类,应该具有的属性有顶点数,顶点坐标,顶点的连接顺序,面积,等等。对“多边形”这个类的属性进行实例化,例如顶点数是3,顶点坐标是A(0,0),B(0,1),C(1,1),等等,定义的这个“三角形”就是“多边形”这个类的一个对象(或类的实例)。对象是可以销毁的,相当于把定义好的三角形擦除,但类不能销毁,“多边形”这个类是一个抽象的概念,属性是客观存在的。
构造函数的作用是对对象本身做初始化工作,也就是给用户提供初始化类中的成员变量的一种方式。利用构造函数来对类的成员变量进行初始化,例如下面程序中,在 main 函数中执行Triangle TriTest;
这条语句时,会自动调用Triangle
这个类的构造函数,完成对TriTest
这个对象的内部成员int iNumSides
和vector
的初始化工作。
using namesapce std;
struct tagPOINT
{
int x;
int y;
};
class Triangle
{
private:
// 类中的成员变量
int iNumSides;
vector Coord_Vertex;
public:
Triangle() // Triangle类的构造函数,必须与类名字相同,无返回值
{
tagPOINT PtTemp;
iNumSides = 3;
PtTemp.x = 0; PtTemp.y = 0;
Coord_Vertex.push_back(PtTemp);
PtTemp.x = 0; PtTemp.y = 1;
Coord_Vertex.push_back(PtTemp);
PtTemp.x = 1; PtTemp.y = 1;
Coord_Vertex.push_back(PtTemp);
}
void output() // 成员函数
{
cout << "The number of sides is " << iNumSides << endl;
cout << "The points' coordinates are:" << endl;
for (int i=0; icout << "(" << Coord_Vertex[i].x << "," << Coord_Vertex[i].y << ")" << endl;
}
}
};
void main()
{
Triangle TriTest;
TriTest.output();
}
对于成员变量初始化,需要注意的是,在类中定义成员变量时,不能给成员变量直接赋初值(但对于常量必须赋初值),例如:
class Triangle
{
private:
// 类中的成员变量
int iNumSides = 3; // 此处不能给变量赋值
vector Coord_Vertex;
// ...
}
这种做法之所以错误,可以理解为,类的定义只是抽象出了一个事物的特征,这些特征只是概念而不是具体的值,只有把这个类实例化,定义了类的对象之后,那些特征对于定义的对象而言,具有实际的值。正是由于不能给成员函数直接赋值的原因,才有了利用构造函数的必要。
如果在一个类中没有定义构造函数,编译器会提供默认的构造函数,这个构造函数不带参数,默认构造函数会为这个类定义的对象的参数赋默认的值。一个类中构造函数可以有多个,但参数不能相同,也就是构造函数可以被重载(overload),例如Triangle
类中可以有多个构造函数:
class Triangle
{
private:
// 类中的成员变量
int iNumSides;
vector Coord_Vertex;
public:
// 一组重载的构造函数
Triangle(); // 默认的构造函数
Triangle(int Num);
Triangle(int Num, vector _Vertex);
// ...
};
Triangle
类的对象定义出来后,编译器会自动根据获得的参数,挑选出应该被调用的构造函数。例如:
Triangle T0;
会对T0
应用不带参的默认构造函数。而对于
Triangle T1(3);
会调用带有一个参数的构造函数。括号内的值被视作传给构造函数的参数。构造函数还有另一种初始化参数的方法——成员初始化列表:
// 成员函数初始化列表
Triangle::Triangle(const Triangle &ThatTri)
: iNumSides(ThatTri.iNumSides),
Coord_Vertex(ThatTri.Coord_Vertex)
{ } // 函数体为空
可以看出,成员函数列表紧接在参数列表最后的冒号后面,是个以逗号分隔的列表。需要给成员赋的值放在括号里。
析构函数的作用和构造函数相反,是对一个定义好的对象的“解构”。当一个对象的生命周期结束时,应该释放掉这个对象所占有的资源,这时可以利用析构函数来完成。析构函数名称有严格的规定:类名称在加上前缀 ‘~’,更重要的是析构函数没有返回值,也没有任何参数。例如:
class Triangle
{
private:
// 类中的成员变量
int iNumSides;
char *pName;
public:
Triangle() // 构造函数
{
iNumSides = 3;
pName = new char[32];
}
~Triangle() // 析构函数
{
delete[] pName;
}
// ...
};
对于类的对象Triangle Tri_Test;
编译器在Tri_Test
被定义出来时,自动应用构造函数来初始化指针*pName
,指向一块内存能存放32个char
类型的元素。在语句块结束之前再多用析构函数来释放pName
所指向的那块内存。用户不需要知道内存管理细节。构造函数并不是绝对需要的,如定定义的成员int iNumSides;
是以储值(by value)方式来存放,不需要构造函数来释放,在对象的生命周期结束时会自动释放。因此,了解何时需要定义析构函数而何时不需要是很重要的。
所谓函数重载(overload)是指同一个函数名可以对应着多个函数的实现。利用重载函数可以对一类功能相似的函数用相同的函数名,从而减少命名。函数重载构成的条件:函数的参数类型或参数个数不同,才能构成函数的重载。上面所列举的在类中定义多个参数列表不同的构造函数就属于函数的重载。在定义构造函数时需要注意带有常量的特殊情况:
void Tri_Overload(int Parmtr1, int Parmtr2 = 25);
void Tri_Overload(int Parmtr1);
当调用Tri_Overload(47);
时,由于第一个函数带有具有默认值的参数,这条语句能调用两个函数,会带来歧义,因此这种情况不能构成函数的重载。
this指针指向对象本身,代表了对象的地址。利用this指针可以对类的成员函数进行重新赋值,也可以实现一个对象作为另一个对象的初值。
class Triangle
{
public:
int iNumSides;
Triangle()
{
iNumSides = 3;
}
Triangle(int sides)
{
iNumSides = sides;
}
void output()
{
cout << iNumSides << endl;
}
void input(int iNumSides)
{
this->iNumSides = iNumSides;
}
};
void main()
{
Triangle Tri3(5);
Tri3.input(25);
Tri3.output();
}
对于上面定义的类,在对象调用Tri.input(25);
时,成员除了接收一个实参外,还接收到了Tri3
对象的地址,这个地址被隐含的形参this指针所获取。通过this指针可以访问对象的内部成员。
[1] 孙鑫, 余安萍. VC++深入详解[M]. 北京: 电子工业出版社, 2006: 34-40.
[2] Lippman S. B. Essential C++[M]. 侯捷, 译. 北京: 电子工业出版社, 2013