类的构造函数和析构函数
构造函数就是为了在类初始化的时候, 初始化类需要的变量, 例如:
// 提供了一个三个参数的构造函数原型
// 构造函数原型位于类声明的共有部分
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