C++引入类的目标之一是使用类对象能像使用标准类型一样,要实现这样对目的,就必须提到C++的构造函数。如下所示的案例是int 或者结构的初始化:
int year = 2023;
struct thing
{
char * pn;
int m;
}
thing amabob = {"wodget", -23};
以Stock类为例,Stock的对象却不能像int类型、结构那样初始化。如:
class Stock
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot(){total_val * share_val;}
public:
...
};
Stock hot= {"Sukie's Autos, Inc", 200, 50.25};
不能像上面这样初始化Stock对象的原因是Stock的数据成员都是私有的,所以程序不能直接访问类的私有部分。如果将类的数据成员改为公有的,则破坏了类的数据隐藏(将数据封装到私有部分可以保护数据的完整性,被称为数据隐藏)。
C++提供了一个特殊的成员函数-构造函数。专门用于构造新对象、将值赋给它们的数据成员。构造函数名称与类名相同。例如Stock类的构造函数名为Stock()。构造函数的一个主要特征:没有返回值,但没有被声明为void类型。也就是说构造函数没有声明类型。
通常情况下,类有多少个数据成员,构造函数就有多少个参数。参数类型与类的数据成员类型保持一致。以上述的Stock类为例,Stock类有3个数据成员,则Stock类的构造函数有3个参数。可以得到Stock构造函数如下:
Stock(const string &co, long n = 0, double pr = 0.0);
第一个参数是字符串的引用,用于company成员的初始化,n和pr 参数为shares和share_val成员赋值。构造函数的原型位于类声明的公有部分(构造函数在公有部分,才能供外部程序调用)。程序在声明类对象时,会自动嗲用构造函数。
注意事项:构造哈桑农户的参数名不可以和数据成员名相同。如下所示的声明是不可以的。
Stock(const string & company, long shares, double share_val);
构造函数的参数标识的不是类的数据成员,而是赋值给类成员的值。否在在构造函数定义时会出现shares = shares;
为了避免出现shares = shares的这种混乱;有两种常见方法:
1、在数据成员名中使用m_前缀:
class Stock
{
private:
string m_company;
long m_shares;
...
};
2、在成员名中使用后缀_
class Stcok
{
private:
string company_;
long shares_;
...
};
以上两种类数据成员名命名方式都可以,使用其中一种即可,这样公有接口的参数就可以使用company, shares。
C++有两种使用构造函数的方法。一种是显式地调用构造函数:
Stock food = Stock("World Cabbage", 250, 1.25);
另一种是隐式地调用构造函数:
Stock garment("Furry Mason", 50, 2.5);
这种隐式地调用格式更加紧凑,与下面显式地调用等价:
Stock garment = Stock("Furry Mason", 50, 2.5);
在创建对象时,C++都会使用构造函数。在日常的开发工作中也经常会遇到下面这种将构造函数与new一起使用的情况:
Stock *pstock = new Stock(" Electroshock Game", 18, 19.0);
注:这条语句创建了一个Stock临时对象,将其初始化为参数提供的值,并将该对象的地址赋值给pstock指针。该临时对象没有名称,但可以使用指针来管理该对象。
一般来说都是使用对象来调用方法。但无法使用对象来调用构造函数,因为在构造函数构造出对象之前,对象是不存在的。因此构造函数被用来创建对象,而不能通过对象来调用。
默认构造函数是在未提供显式初始值时,用来创建对象的构造函数。用于以下声明方式调用的构造函数:
Stock fluffy_the_cat;//use the defalt constructor
在没有提供任何构造函数时,C++将自动提供默认构造函数,属于构造函数的隐式版本,不做任何工作。默认构造函数定义为:Stock::Stock(){ }
需要注意的是**当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。为类定义了构造函数后,程序员就必须为它提供默认构造函数。**如果提供了非默认构造函数,但没有提供默认构造函数那么直接使用Stock stock;的声明会出错。(这样做的原因可能是为了禁止创建未初始化的对象)
如果要创建对象,而不显式地初始化,则必须定义一个不接受任何参数的默认构造函数。定义默认构造函数的方式有两种。一种是给已有构造函数的所有参数提供默认值:
Stock(const string &co = "Error", int n = 0, double pr = 0.0);
另一种方式是通过函数重载来定义另一个构造函数-一个没有参数的构造函数:
Stock();
由于只能有一个默认构造函数,因此不要同时采用这两种方式。实际上通常应初始化所有的对象,以确保所有成员就有已知的合理值。用户定义的默认构造函数通常给所有成员提供隐式初始化值。如下:
Stock::Stock()
{
company = "no name";
shares = 0;
share_val = 0;
total_val = 0;
}
(注:在设计类时,通常提供对所有类成员做隐式初始化的默认构造函数)
以下列举一些常见的创建对象方式:
Stock first;//隐式地调用默认构造函数
Stock first = Stock();//显式地调用默认构造函数
Stock *prelief = new Stock;//隐式地调用默认构造函数
Stock first = ("Concrete Conglomerate");//调用非默认构造函数
Stock second();//隐式地调用构造函数时,不要使用圆括号。此处是声明一个返回值类型为Stock名为second的函数
析构函数完成清理工作。如果构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存。构造函数没有使用new分配内存,则析构函数也不需要使用delete。析构函数是在类名前面加上~,和构造函数一样,析构函数也没有返回值和声明类型。且析构函数没有参数。以Stock为例析构函数的原型如下:
~Stock();
对象调用析构函数的时间:
如果常见的是静态存储类对象,则其析构函数将在程序结束时自动被调用。如果创建的是自动存储类对象,则其析构函数将在程序执行完代码块(该对象在该代码块内被定义的)是自动被调用。如果对象是同通过new创建的,则它将驻留在栈内存或自由存储区中,当使用delete来释放内存时,其析构函数将自动被调用。程序可以创建临时对象来完成特定操作,在这种情况下,程序将在结束对该对象的使用时自动调用其析构函数。
改进之前介绍类和对象中涉及的Stock类
1.头文件stock10.h
#pragma once
//stock10.h--Stock class declaration with constructors, destructor added
#include
class Stock
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot() { total_val = shares* share_val; };
public:
//two construtors
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();
};
2.实现文件stock10.cpp
//stock10.cpp--Stock class with constructors,destructor added
#include "stock10.h"
#include
//constructors(verbose vesions)
Stock::Stock()//default constructor
{
std::cout << "Default constructor valled\n";
company = "no name";
shares = 0;
share_val = 0.0;
total_val = 0.0;
}
Stock::Stock(const std::string& co, long n, double pr)
{
std::cout << "Construtor using " << co << " called\n";
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();
}
//class destructor
Stock::~Stock()
{
std::cout << "Bye, " << company << "!\n";
}
//other destrutor
void Stock::buy(long num, double price)
{
if (num < 0)
{
std::cout << "Number of shares purchased can't be negative. " << "Transaction is aborted.\n";
}
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 abored.\n";
}
else if (num > shares)
{
cout << "You can't sell more than you have! " << "Transaction is aborted.\n";
}
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;
//set form to #.###
ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield);
std::streamsize prec = cout.precision(3);
cout << "Company: " << company
<< " Shares: " << shares << '\n';
cout<< " Share price:$" << share_val;
//set format to #.##
cout.precision(2);
cout << " Total Worth:$" << total_val << '\n';
//restore original format
cout.setf(orig, ios_base::floatfield);
cout.precision(prec);
}
3.客户文件usestock1.cpp
//usestock1.cpp-- using the Stock class
//compile with stock10.cpp
#include "stock10.h"
#include
int main()
{
{
using std::cout;
cout << "Using constrctors to creat new objects\n";
Stock stock1("NanoSmart", 12, 20.0);//syntax 1
stock1.show();
Stock stock2 = Stock("Boffo Objects", 2, 2.0);//syntax 2
stock2.show();
cout << "Assigning stock1 to stock2:\n";
stock2 = stock1;
cout << "Listing stock1 to stock2:\n";
stock1.show();
stock2.show();
cout << "Using a construtor to reset an object\n";
stock1 = Stock("Nifty Foods", 10, 50.0);//temp object
cout << "Revised stock1:\n";
stock1.show();
cout << "Done\n";
}
}
总结:构造函数不仅可以初始化新对象,还可以给已有对象赋值。
只要提供与某个构造函数的参数列表匹配的内容,并用大括号将它们括起来:
//匹配该构造函数Stock::Stock(const std::string &co, long n = 0, double pr = 0.0);
Stock hot_tip = {"Derivativew Plus Plus", 100, 45.0};
Stock jock{"Sport Age Stroage, Inc"};
//匹配默认构造函数Stock::Stock()
Stock temp{};
const Stock land = Stock("Kludgehorn Properties");
land.show(); //会报错
show方法无法确保调用对象不会被修改。为确保函数不会修改调用对象,C++是将const关键字放在函数的括号后面,show方法的声明和定义应如下所示:
void show() const;//声明
void Stock::show()const//定义的开头
以这种方式声明和定义的类函数被称为const成员函数。就像应尽可能将const引用和指针用作函数形参一样,只要类方法不修改调用对象,就应该将其声明为const。
特别提醒:
1.如果编译器支持C++11,则可以使用列表初始化
2.接受一个参数的构造函数允许使用赋值语句将对象初始化为一个值,如下:
//构造函数原型为:Bozo(int age);
Bozo dribble = Bozo(44);
Bozo roon(66);
Bozo tubby = 32;
3.默认构造函数可以没有任何参数,如果有,则必须给所有参数都提供默认值
4.每个类都只有一个析构函数,析构函数没有返回类型(连void都没有),也没有参数,析构函数名称为类名前加上~。
5.构造函数使用了new, 则必须提供使用delete的析构函数。