对于Stock类,我们还有其他一些工作要做。因为类提供被称为构造函数和析构函数的标准函数。
C++的目标之一就是让使用对象类像使用标准类型一样,然而在上一节的Stock类代码中还不能像初始化一个int那样来初始化Stock类对象。比如int a=1
; 对于一个结构初始化等,那样的操作是不能用于Stock类的。
为什么要有构造函数?原因在于:数据部分的访问状态是私有的。程序不能直接访问私有成员。因此需要设计合适的成员函数,才能成功地初始化对象。
在前面的代码中,我们是用acquire
成员函数来初始化一个Stock类对象:1
2
3Stock A;
A.acquire(abc, 10 ,10);
A.buy(10, 24,75);
但是我们要做到的是一个名为Stock的构造函数,使得每次创建一个新对象,都能把他初始化。虽然用别的函数可以达到这个目的,但我们不能强行假设用户会每次都会去调用函数初始化。构造函数的目的就在于确保声明新的类对象时立刻初始化它,这是自动的。
构造函数
声明和定义
由于新建一个Stock类对象,需要提供3个值,因此构造函数默认提供3个参数。(total_val是计算得出的)
tips:如果只想设置company,其他的都是0,则可以用到默认参数。1
Stock(const string & co, long n = 0, double pr = 0.0);
注意:函数名称和类型名相同。没有返回类型。原型位于类声明的共有部分。
添加构造函数后的Stock类在右边。其实这个可能的构造函数与函数acquire()的代码是相同的。区别在于,程序声明对象时,会自动调用构造函数。
这个可能的构造函数与函数acquire()
的代码是相同的。区别在于,程序声明对象时,会自动调用构造函数。而acquire()
,需要你手动显式地调用。
1 | Stock::Stock(const std::string & co, long n, double pr) |
注意,可能由于不熟悉构造函数的写法,有可能会将成员名作为构造函数的参数名:
1 | Stock::Stock(const std::string & company, long shares, double share_val) |
这是错误的,参数名不能与类成员名相同,否则会出现这样的代码:1
shares = shares;
从而引起混乱。
调用方法
调用方法及注意点
• 法1:显式地调用构造函数。1
Stock A = Stock("Future Technology", 250, 1.25);
• 法2:隐式地调用构造函数。1
Stock B("Sun Prison", 50, 2.5);
• 构造函数与new结合在一起使用的方法。这条语句会创造一个Stock对象,但是是没有名称的,只能通过用p代它的地址来管理该对象。这就是对象指针。1
Stock *p = new Stock("Sun Empire", 18, 19.0);
• 不能通过类对象来调用构造函数
默认构造函数
默认构造函数是用于未提供显示初始值时候用来初创建对象(也就是说,并没有初始化)的构造函数。就好像int a;
并没有立即给 a 赋值,只是声明一个int变量一样。
如果没有提供任何构造函数,C++会自动提供默认构造函数,提供隐式声明类对象的方法,不对对象做任何初始化。也就大概是这样的:1
Stock::Stock() {}
如果提供了非默认构造函数,如
1 | Stock(const string &co, long n = 0, double pr = 0.0); |
却没有提供默认构造函数,则隐式且不带初始值的声明会出错!原因就是禁止创建未初始化的对象。
设置默认构造函数的方式有两种:
为已有构造函数的所有参数提供默认值。
1
Stock(const string &co = "ERROR", long n = 0, double pr = 0.0);
通过函数重载定义另一种构造函数——没有参数的构造函数。
1
Stock();
由于只能存在一个默认构造函数,所以,不要同时使用这两种方法。实际上,通常应该初始化所有的对象,以确保一开始所有的成员就有已知的合理值。例如下面这个默认构造函数:1
2
3
4
5
6
7Stock::Stock()
{
company = "No name";
shares = 0;
share_val = 0.0;
total_val = 0.0;
};
析构函数
用构造函数创建对象时,程序负责跟踪此对象,至到其过期为止。对象过期时,程序将调用一个特殊的成员函数,来完成清理工作。这个成员函数的名字就叫做析构函数。
析构函数完成的就是清理工作,实际上是很有用的。例如如果构造函数用了new
,那么在析构函数里使用delete
来释放这些内存。Stock类的构造函数没有用new
,因此析构函数是没啥需要完成的实际任务的。然而,了解如何声明和定义析构函数十分重要,下面我们试着来写一个Stock类的析构函数。
声明和定义析构函数
和构造函数一样,析构函数的名称也是很特殊的:在类名前加上~
。因此,Stock类的析构函数一定是~Stock()
。析构函数一样没有返回值和类型声明,但与构造函数不同的是,析构函数没有参数,因此析构函数的原型声明也是固定的:~Stock();
Stock类的析构函数不承担任何工作,因此可以其定义这样编写。其他不承担工作的析构函数同理:1
2
3Stock::~Stock()
{
}
然而如果想看到函数何时被调用,可以这样写:1
2
3
4Stock::~Stock()
{
cout << "Bye, " << company << "!\n";
}
tips:什么时候会调用析构函数呢?
这由编译器决定。通常,不应该在代码中显示地调用析构函数,
对象 | 析构函数调用时机 |
---|---|
静态存储类 | 程序结束时自动调用 |
自动存储类 | 程序执行完相对应代码块(对象在其中定义)时自动调用 |
通过new创建的对象 | 驻留在栈内存或者自由存储区,当使用delete来释放内存是,调用析构函数 |
程序创建的临时对象 | 程序在结束对该对象的使用时调用析构函数 |