C++ Primer Plus书之--C++ 类的构造函数和析构函数

类的构造函数和析构函数

构造函数就是为了在类初始化的时候, 初始化类需要的变量, 例如:

// 提供了一个三个参数的构造函数原型
// 构造函数原型位于类声明的共有部分
Stock(const string & co, long n, double pr = 0.0);

构造函数名和类名相同, 但是没有返回值

来看一个构造函数的定义:

// 构造函数定义
Stock::Stock(const 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" << std::endl;
        shares = 0;
    } else {
        shares = n;
    }
    share_val = pr;
    set_tot();
}

使用构造函数

C++提供了两种使用构造函数来初始化对象的方式, 第一种方式是显示的调用构造函数:

Stock food = Stock("World", 250, 1.25);

这将food对象的company设置为"World Cabbage", 将shares成员设置为250, 以此类推.

另外一种方式是隐式调用构造函数例如:

Stock gar("Tom", 50, 2.5);

// 和这种格式效果是一样的
Stock gar = Stock("Tom", 50, 2.5);

每次创建类对象(甚至使用new动态分配内存时), C++都户调用类构造函数, 通过new调用方式如下:

Stock * pstock = new Stock("Test", 10, 66.6);

这条语句创建了一个Stock对象, 将其初始化为参数提供的值, 并将该对象的地址赋给pstock指针.

默认构造函数

默认构造函数是在未提供显示初始值时, 用来创建对象的构造函数. 例如:

Stock flu; // 使用的就是默认构造函数

并且当且仅当没有提供任何构造函数时, 编译器才会提供默认构造函数.

如果要创建对象, 而不显示的初始化, 则必须定义一个不接受任何参数的默认构造函数, 定义默认构造函数的方式有两种:

1.是给已有构造函数的所有参数提供默认值:

Stock(const string & co = "Default", int n = 0, double pr = 0.0);

2.是通过函数重载来定义另一个没有参数的构造函数:

Sotck();

但是, 默认构造函数只能有一个, 因此不要同时采用这两种方式.

提供默认构造函数的时候, 最好将所有类成员都设置默认的合理值

例如:

Stock::Stock()
{
    company = "Default";
    shares = 0;
    share_val = 0.0;
    total_val = 0.0;
}

使用上述任何一种方式创建了默认构造函数后, 便可以声明对象变量, 而不对他们进行显示初始化:

// 调用的是默认的构造函数
Stock first;
// 调用的是有默认参数的构造函数
Stock first2 = Stock();
// 调用的是默认构造函数
Stock *prelief = new Stock;

然而, 不要被非默认构造函数的隐式形式所误导:

// 调用的是默认参数的构造函数
Stock first("Tom test");

// 声明了一个方法, 因为隐式调用默认构造函数时, 不要使用()
Stock second();

// 调用默认构造函数
Stock third;

析构函数

当我们用构造函数创建对象后, 程序负责跟踪该对象, 知道其过期为止, 对象过期时, 程序将自动调用一个特殊的成员函数--析构函数. 析构函数是完成清理工作的.

如果构造函数使用new来分配内存, 则析构函数将使用delete来释放这些内存.

析构函数名称也有特殊的地方: 在类名前加上~, 因此前面的Stock类的析构函数为~Stock(). 和构造函数一样, 析构函数也可以没有返回值和声明类型. 与构造函数不同的是, 析构函数没有参数, 因此Stock类的析构函数原型必须是:

~Stock();

由于Stock的析构函数不承担任何重要工作, 因此可以将它编写为不执行任何操作的函数:

~Stock()
{

}

什么时候调用析构函数是由编译器决定的, 通常不应再代码中显示的调用析构函数, 如果创建的是静态存储类对象, 其析构函数将在程序结束时自动被调用, 如果创建的是自动存储类对象, 则其析构函数将在程序执行完代码块时(该对象是在其中定义的)自动被调用. 如果对象是通过new创建的, 则它将驻留在栈内存或自由存储区中, 当使用delete来释放内存时, 其析构函数将自动被调用. 最后, 程序可以创建临时对象来完成特定的操作, 在这种情况下, 程序将在结束对该对象的时候时自动调用其析构函数.

类对象必须要有一个析构函数, 如果程序员没有提供析构函数, 编译器将隐式的声明一个默认析构函数, 并在发现导致对象被删除的代码后, 提供默认析构函数的定义.

看一下加上构造函数的重构代码:

// stock10.h
// 加入了构造函数的声明
#ifndef STOCK10_H_
#define STOCK10_H_
#include <string>

class Stock
{
private:
    std::string company;
    long shares;
    double share_val;
    double total_val;
    void set_tot(){total_val = shares * share_val;}
    
public:
    // 这里有两个构造函数, 一个是默认构造函数, 一个是带参数的构造函数
    Stock();
    // 带有部分默认参数的构造函数
    Stock(const std::string & co, long n = 0, double pr = 0.0);
    // 析构函数
    ~Stock();
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    void show();
};

#endif

第二个文件

// 第二个文件
// stock10.cpp
// Stock类函数的实现, 及构造函数的实现
#include <iostream>
#include "stock10.h"

// 默认构造函数
Stock::Stock()
{
    std::cout << "Default constructor called \n";
    company = "default name";
    shares = 0;
    share_val = 0;
    total_val = 0.0;
}

Stock::Stock(const std::string & co, long n, double pr)
{
    std::cout << "Constructor using " << co << " called\n";
    company = co;
    if(n < 0)
	{
		std::cout << "Number of shares can't be negative;" << company << " shares set to 0" << std::endl;
		shares = 0;
	} else {
		shares = n;
	}
	share_val = pr;
	set_tot();
}

Stock::~Stock()
{
    // 这里仅仅是为了显示Stock的析构函数被调用了, 没有做实际的操作
    std::cout << "Bye, " << company << "\n";
}

void Stock::buy(long num, double price)
{
	if(num < 0)
	{
		std::cout << "Number of shares purchased can't be negative Transaction is aborted " << std::endl;
	} else {
		shares += num;
		share_val = price;
		set_tot();
	}
}

void Stock::sell(long num, double price)
{
	using std::cout;
	if(num < 0)
	{
		cout << "Number of shares sold can't be negative Transaction is aborted " << std::endl;
	} else if(num > shares){
		cout << "You can't sell more than you have, Transaction is aborted" << std::endl;
	} else {
		shares -= num;
		share_val = price;
		set_tot();
	}
}

void Stock::update(double price)
{
	share_val = price;
	set_tot();
}

void Stock::show()
{
    using std::cout;
    using std::ios_base;
    // 将cout的输出格式化为#.###
    ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield);
    std::streamsize prec = cout.precision(3);
	std::cout << "Company: " << company << " Shares: " << shares << std::endl;
	    
	// 格式化为#.##
	cout.precision(2);
	std::cout << " Share Price: $" << share_val << "Total Worth: $" << total_val << std::endl;
	    
	// 设置会原来的格式
	cout.setf(orig, ios_base::floatfield);
	cout.precision(prec);
	std::cout << " 2 Share Price: $" << share_val << "Total Worth: $" << total_val << std::endl;
}

第三个文件:

// usestock1.cpp
// compile with stock10.cpp
#include <iostream>
#include "stock10.h"

int main()
{
    // 使用代码块, 就是为了看析构函数的执行
    {
        using std::cout;
        cout << "Using constructors to creawte new obj\n";
        // 使用带参数的构造函数
        Stock stock1("NanoSmart", 12, 20.0);
        stock1.show();
        Stock stock2 = Stock("Boffo obj", 2, 2.0);
        stock2.show();
        
        cout << "Assigning stock1 to stock2:\n";
        stock2 = stock1;
        cout << "Listing stock1 and stock2 : \n";
        stock1.show();
        stock2.show();
        
        cout << "Using a constructor to reset an Obj \n";
        // 构造函数不仅仅能用于初始化
        // 还可以赋给变量新值, 
        // 这个是通过构造函数让程序创建一个新的, 临时的对象, 然后将其内容复制给
        // stock1, 随后程序调用了析构函数, 以删除该临时对象, 因此会看到输出结果里会先有个Bye Nifty的输出.
        stock1 = Stock("Nifty", 10, 50.0);
        cout << "Revised stock1:\n";
        stock1.show();
        cout << "Done\n";
    }
    return 0;
}

程序运行结果:

主要看一下Bye 执行的时机,  主要地方都有注释

C++11 列表初始化

C++11中, 可将列表初始化用于类, 前提是提供与某个构造函数的参数列表匹配的内容, 并用大括号将他们括起来:
 

Stock s1 = {"dev", 100, 45.0};
Stock s2 {"Sport"};
Stock s3 {};

前两个声明中, 用大括号阔气的列表与下面的构造函数匹配:

Stock::Stock(const std::string & co, long n = 0, double pr = 0.0);

而第三条语句与默认构造函数匹配, 因此使用默认构造函数创建s3.

const成员函数:

先看下面的代码片段:

// 调用了带参数的构造函数
const Stock land = Stock("Klu");
land.show();

编译器将拒绝执行第二行, 因为show()的代码无法保证调用对象不被修改--调用对象和const一样, 不应被修改. 我们以前是通过将函数参数声明为const引用, 或者指向const的指针来解决的. 但是这里存在个问题就是show()方法没有任何参数(它使用的对象是由方法调用隐式的提供的). 这里提供新的语法: 将const关键字放在函数括号后面:
 

// 保证不去改变引用的对象
void show() const;

同样函数定义的开头也要这么写:

void Stock::show() const

猜你喜欢

转载自blog.csdn.net/c1392851600/article/details/84932138