目录:
对象的创建 — 构造函数
初始化表达式
对象的创建
在之前的 Computer 类中,通过自定义的公共成员函数 setBrand 和 setPrice 实现了 对数据成员的初始化。实际上, C++ 为类提供了一种特殊的成员函数 — 构造函数 来完成相同的工作。构造函数有一些独特的地方:
- 函数的名字与类名相同
- 没有返回值
- 没有返回类型,即使是 void 也不能有
- 对象在创建的时候,调用构造函数
构造函数在对象创建时自动调用,用以完成对象成员变量等的初始化及其他操作(如为指针成员动态申请 内存等);如果程序员没有显式定义它,系统会提供一个默认构造函数(无参)。下面我们用一个点 Point 来举例:
class Point
{
public:
//即使不写,编译器也会自动提供一个
//构造函数
Point()
{
cout << "Point()" << endl;
_ix = 0;
_iy = 0;
}
void print()
{
cout << "(" << _ix
<< "," << _iy
<< ")" << endl;
}
private:
int _ix;
int _iy;
};
int main(void)
{
Point pt;
pt.print();
return 0;
}
编译器自动生成的缺省(默认)构造函数是无参的,实际上,构造函数可以接收参数,在对象创建时提供更大的自由度。我们在上面的 Point 类中可以加入一个新的构造函数
class Point
{
public:
//...
Point(int ix, int iy)
{
cout << "Point(int,int)" << endl;
ix = ix;
_iy = iy;
}
//...
};
int main(void)
{
Point pt(1, 2);
pt.print();
Point pt2(11, 12);
pt2.print();
return 0;
}
上面的例子同时出现了无参构造函数,和有参构造函数,这说明了 构造函数是可以重载的。
在上面,我们说编译器会自动给 Point 类生成一个默认构造函数,这是有前提条件的,就是类中没有定义任何构造函数。现在假设 Point 类中只 显式定义了一个有参构造函数,则编译器不会再自动提供默认构造函数,如果还希望通过默认构造函数创建对象,则需要显式定义一个默认构造函数。
初始化表达式
(初始化表达式是对数据成员进行真正的初始化)
在上面的例子中,构造函数对数据成员进行初始化 时,都是在函数体内进行的。除此以外,还可以通过 初始化列表 完成。
- 初始化列表位于构造函数形参列表之后,函数体之前,用冒号开始
- 多个数据成员,再用逗号分隔
- 初始值放在一对小括号中
例子如下:
class Point
{
public:
//...
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{
cout << "Point(int,int)" << endl;
}
//...
};
如果没有在构造函数的初始化列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。如在 “对象的创建” 部分的两个构造函数中的 _ix 和 _iy 都是先执行默认初始化后,再在函数体中执行赋值操作。有些时候成员必须在初始化列表中进行,否则会出现编译报错。
注意:
- 每个成员在初始化列表之中只能出现一次
- 初始化的顺序是由成员变量在类中被声明时的顺序决定的 ,不是由成员变量在初始化列表中的顺序决定的
class Foo
{
public:
Foo(int a)
: _iy(a) //在初始化列表中,_iy好像先被初始化
, _ix(_iy)
{
cout << "Foo(int)" << endl;
}
private:
int _ix; //在声明时,_ix在前
int _iy;
};
例:构造函数初始化
#include <iostream>
using std::cout;
using std::endl;
class Coord
{
public:
Coord(int val)
: _iy(val)
, _ix(_iy)
{
cout << "Coord(int)" << endl;
}
void print()
{
cout << "ix = " << _ix
<< ",iy = " << _iy << endl;
}
private:
int _iy;
int _ix;
};
int main(int argc, char **argv)
{
Test coord(1);
coord.print();
return 0;
}
对象的销毁
构造函数在创建对象时被系统自动调用,而析构函数(Destructor)在对象被撤销时被自动调用,相比构造函数,析构函数要简单的多。析构函数有如下特点:
- 与类同名,之前冠以波浪号,以区别于构造函数。
- 析构函数没有返回类型,也不能指定参数。因此,析构函数只能有一个,不能被重载。
- 对象超出其作用域被销毁时,析构函数会被自动调用。
析构函数在对象撤销时自动调用,用以执行一些清理任务,如释放成员函数中动态申请的内存等。如果程序员没有显式的定义它,系统也会提供一个默认的析构函数。例如:
class Point
{
public:
//...
~Point() {}
//...
};
由于 Point 类比较简单,数据成员中没有需要进行清理的资源,所以即使不显式定义析构函数,也没关系。我们再举一个例子:
class Computer
{
public:
Computer(const char * brand, double price)
: _brand(new char[strlen(brand) + 1]())
, _price(price)
{}
~Computer()
{
delete [] _brand;
cout << "~Computer()" << endl;
}
private:
char * _brand;
double _price;
};
以上的 Computer 中,有一个数据成员是指针,而该指针在构造函数中初始化时已经申请了堆空间的资源,则当对象被销毁时,必须回收其资源。此时,编译器提供的 默认析构函数是没有做回收操作的,因此就不再满足我们的需求,我们必须 显式定义一个析构函数,在函数体内回收资源。
析构函数除了在对象被销毁时自动调用外,还可以显式手动调用(p.~Computer()
),但一般不建议这样使用。
析构函数的调用时机:
-
对于全局定义的对象,每当程序开始运行,在主函数 main 接受程序控制权之前,就调用构造函数创建全局对象,整个程序结束时,自动调用全局对象的析构函数。
-
对于局部定义的对象,每当程序流程到达该对象的定义处就调用构造函数,在程序离开局部对象的作用域时调用对象的析构函数。
-
对于关键字 static 定义的静态局部对象,当程序流程第一次到达该对象定义处调用构造函数,在整个程序结束时调用析构函数。
-
对于用 new 运算符创建的对象,每当创建该对象时调用构造函数,当用 delete 删除该对象时,调用析构函数。
例:析构函数的调用
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
using std::cout;
using std::endl;
class Student
{
public:
Student(const char * name, int score)
: _name(new char[strlen(name) + 1]())
, _score(score)
{
strcpy(_name, name);
cout << "Student" << endl;
}
void print() const
{
cout << "name:" << _name << endl
<< "score:" << _score << endl;
}
~Student()
{
delete [] _name;
cout << "~Student" << endl;
}
private:
char * _name;
int _score;
};
void func()
{
cout << "func()" << endl;
Student stu("Smith", 10);
stu.print();
}
Student stu2("Janes", 20);
void func2()
{
cout << "func2()" << endl;
stu2.print();
}
void func3()
{
cout << "func3()" << endl;
static Student stu3("Fox", 30);
stu3.print();
}
void func4()
{
cout << "func4()" << endl;
Student * stu4 = new Student("Loess", 40);
stu4->print();
delete stu4;
}
int main()
{
func();
func2();
func3();
func4();
return 0;
}