C++ 构造函数和析构函数

引言

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 <string>

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 <iostream>

//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 <iostream>

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";
	}
}

运行结果如下:
在这里插入图片描述

总结:构造函数不仅可以初始化新对象,还可以给已有对象赋值。

C++ 11 列表初始化

只要提供与某个构造函数的参数列表匹配的内容,并用大括号将它们括起来:

//匹配该构造函数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成员函数

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的析构函数。

  • 31
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值