C++学习之类的构造函数和析构函数

对于Stock类,我们还有其他一些工作要做。因为类提供被称为构造函数析构函数的标准函数。

C++的目标之一就是让使用对象类像使用标准类型一样,然而在上一节的Stock类代码中还不能像初始化一个int那样来初始化Stock类对象。比如int a=1; 对于一个结构初始化等,那样的操作是不能用于Stock类的。

为什么要有构造函数?原因在于:数据部分的访问状态是私有的。程序不能直接访问私有成员。因此需要设计合适的成员函数,才能成功地初始化对象。

在前面的代码中,我们是用acquire成员函数来初始化一个Stock类对象:

1
2
3
Stock 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
2
3
4
5
6
7
8
9
10
11
12
13
14
Stock::Stock(const std::string & co, long n, double pr)
{
company = co;
if (n < 0)
{
std::cout << "Number of shares can't be negative."
<< company << " shares set to 0.\n";
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
}

注意,可能由于不熟悉构造函数的写法,有可能会将成员名作为构造函数的参数名:

1
2
3
4
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
7
Stock::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
3
Stock::~Stock()
{
}

然而如果想看到函数何时被调用,可以这样写:

1
2
3
4
Stock::~Stock()
{
cout << "Bye, " << company << "!\n";
}

tips:什么时候会调用析构函数呢?

这由编译器决定。通常,不应该在代码中显示地调用析构函数,

对象 析构函数调用时机
静态存储类 程序结束时自动调用
自动存储类 程序执行完相对应代码块(对象在其中定义)时自动调用
通过new创建的对象 驻留在栈内存或者自由存储区,当使用delete来释放内存是,调用析构函数
程序创建的临时对象 程序在结束对该对象的使用时调用析构函数