1. 构造函数和析构函数总结
假设有这么一个类
#include <string>
class Stock // class declaration
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot() { total_val = shares * share_val; }
public:
void acquire(const std::string & co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
}; // note semicolon at the end
C++的目标之一是让使用类对象就像使用标准类型一样,然而,到现在为止,本章提供的代码还
不能让您像初始化int或结构那样来初始化 Stock对象。
也就是说,常规的初始化语法不适用于类型Stock:
int year = 2001; // valid initialization
struct thing {
char *pn;
int m:
};
thing amabob = {"wodge", -23}; // valid initialization
Stock hot = { "sukie's Autos, Inc ", 200, 50.25 }; //NO! compile error
不能像上面这样初始化 Stock对象的原因在于,数据部分的访问状态是私有的,这意味着程序不能直接访问数据成员。您已经看到,程序只能通过成员函数来访问数据成员,因此需要设计合适的成员函数,才能成功地将对象初始化(如果使数据成员成为公有,而不是私有,就可以按刚才介绍的方法初始化类对象,但使数据成为公有的违背了类的一个主要初衷:数据隐藏)。
一般来说,最好是在创建对象时对它进行初始化。例如,请看下面的代码:
Stock gift;
gift.buy(10, 24.75);
就 Stock类当前的实现而言,gift对象的 company成员是没有值的。类设计假设用户在调用任何其他成员函数之前调用 acquire(),但无法强加这种假设。
避开这种问题的方法之一是在创建对象时,自动对它进行初始化。
为此,C++提供了一个特殊的成员函数一类构造函数,专门用于构造新对象、将值赋给它们的数据成员。更准确地说,C++为这些成员函数提供了名称和使用语法,而程序员需要提供方
法定义。
名称与类名相同。例如, Stock类一个可能的构造函数是名为 Stock()的成员函数。构造函数的原型和函数头有一个有趣的特征一一虽然没有返回值,但没有被声明为void类型。实际上,构造函数没
有声明类型。
1. 声明和定义构造函数
现在需要创建 Stock的构造函数。
由于需要为 Stock对象提供3个值,因此应为构造函数提供3个参数(第4个值, total_val成员,是根据 shares和 share_val计算得到的,因此不必为构造函数提供这个值。)
程序员可能只想设置 company成员,而将其他值设置为0:这可以使用默认参数来完成(参见第8章)。
因此原型如下所示:
// constructor prototype with some default arguments
Stock(const string &co, long n=0, double pr =0.0);
第一个参数是指向字符串的指针,该字符串用于初始化成员 company. n和p参数为 shares和share_val成员提供值。注意,没有返回类型。原型位于类声明的公有部分。
下面是构造函数的一种可能定义
Stock::Stock(const string & co, long n, double pr)
{
company = co;
if (n < 0) {
std::cerr <<"Number of shares can't be negative;"
<< company << "shares set to 0. \n";
shares =0;
} else {
shares = n;
}
share_val = pr;
set_tot();
}
上述代码和本章前面的函数 acquire()相同。
区别在于,程序声明对象时,将自动调用构造函数
2. 使用构造函数
C++提供了两种使用构造函数来初始化对象的方式。
第一种方式是显式地调用构造函数:
Stock food = Stock("World Cabbage", 250, 1.25);
这将food对象的 company成员设置为字符串“ World Cabbage’”,将 shares成员设置为250,依此类推
另一种方式是隐式地调用构造函数:
Stock garment("Furry Mason", 50, 2.5);
这种格式更紧凑,它与下面的显式调用等价
Stock garment = Stock("Furry Mason", 50, 2.5);
每次创建类对象(甚至使用new动态分配内存)时,C++都使用类构造函数。下面是将构造函数与new起使用的方法:
Stock *pstock= new Stock("Electroshock Games", 18, 19.0);
这条语句创建一个 Stock对象,将其初始化为参数提供的值,并将该对象的地址赋给 stock指针。在这种情况下,对象没有名称,但可以使用指针来管理该对象。我们将在第11章进一步讨论对象指针。
构造函数的使用方式不同于其他类方法。一般来说,使用对象来调用方法:
stock1.show(); //stock1 object invokes show() method
但无法使用对象来调用构造函数,因为在构造函数构造出对象之前,对象是不存在的。因此构造函数被用来创建对象,而不能通过对象来调用。
构造函数是一种特殊的类成员函数,在创建类对象时被调用。
构造函数的名称和类名相同,但通过函数重载,可以创建多个同名的构造函数,条件是每个函数的特征标(参数列表)都不同。另外,构造函数没有声明类型。
通常,构造函数用于初始化类对象的成员,初始化应与构造函数的参数列表匹配。例如,假设Bozo类的构造函数的原型如下:
Bozo(const char * fname,const char * lname); // constructor prototype
则可以使用它来初始化新对象:
Bozo bozetta = bozo("Bozetta","Biggens"); // primary form
Bozo fufu("Fufu","O'Dweeb"); // short form
Bozo *pc = new Bozo("Popo","Le Peu"); // dynamic object
如果编译器支持C++11,则可使用列表初始化:
Bozo bozetta = {"Bozetta","Biggens"}; //C++11
Bozo fufu("Fufu","O'Dweeb") // C++11;
Bozo *pc = new Bozo{"Popo","Le Peu"); //C++11
如果构造函数只有一个参数,则将对象初始化为一个与参数的类型相同的值时,该构造函数将被调用。例如,假设有这样一个构造函数原型:
bozo(int i)
则可以使用下面的任何一种形式来初始化对象:
Bozo dribble =bozo(44) // primary form
Bozo roon(66); // secondary form
Bozo tubby = 32; // special form for one-argument constructors
实际上,第三个示例是新内容,不属于复习内容,但现在正是介绍它的好时机。第11章将介绍一种关闭这项特性的方式,因为它可能带来令人不愉快的意外。
警告:接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值:
Classname object = value;
这种特性可能导致问题,但正如第11章将介绍的,可关闭这项特性。
3. 接受一个参数的构造函数的例子
constructor.cpp
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using std::cout;
using std::endl;
class Bozo {
public:
int iVar;
Bozo(int i) {
cout << "i = " << i << endl;
this->iVar = i;
}
void show();
};
void Bozo::show()
{
cout << "iVar = " << this->iVar <<endl;
}
int main()
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
Bozo dribble = Bozo(44); // primary form
Bozo roon(66); // secondary form
Bozo tubby = 32; // special form for one-argument constructors
dribble.show();
roon.show();
tubby.show();
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
}
运行结果:
meng-yue@ubuntu:~/MengYue/c++/object_class/02$ ./constructor
---------------开始--->公众号:梦悦foundation---------------
i = 44
i = 66
i = 32
iVar = 44
iVar = 66
iVar = 32
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/object_class/02$
可以看到,直接给类实例赋值一个整数也是可以的。
默认构造函数没有参数,因此如果创建对象时没有进行显式地初始化,则将调用默认构造函数。
如果程序中没有提供任何构造函数,则编译器会为程序定义一个默认构造函数;否则,必须自己提供默认构造函数。
对于未被初始化的对象,程序将使用默认构造函数来创建:
Bozo bubi;// use default
Bozo *pb = new Bozo; // use default
就像对象被创建时程序将调用构造函数一样,当对象被删除时,程序将调用析构函数。
每个类都只能有一个析构函数。析构函数没有返回类型(连void都没有),也没有参数,其名称为类名称前加上~
。例如,Bozo类的析构函数的原型如下:
// class destructor
~Bozo();
如果构造函数使用了new,则必须提供使用delete的析构函数。