深入浅出C++构造函数、析构函数、深浅拷贝的相关内容

时间:2021-10-29 22:00:41

构造函数:

C++的构造的主要作用:

在 类对象创建的时候通过构造函数对类对象进行初始化。即:为对象成员变量 进行赋值操作,一个类可以有多个构造函数,来满足初始化时的需求:即构造函数 的重载。重载的函数之间通过 函数参数和参数类型进行区分。


C++构造函数的特点:

1.构造函数的名字统一是类名。所以我们要注意的是:CStack() 指的是CStack类的构造函数,而CStack 指的是类。

2.构造函数为类对象在创建时提供初始化。即:为成员变量指定赋值。它没有返回值也没有函数类型的修饰。

3.构造函数不能够被直接调用,通常是在类对象创建时自行调用。即:程序执行到类对象创建时进行。

4.默认的构造函数是公有的,但是如果放在私有里面,那么我们就不可以通过new 运算符来创建对象。当一个类没有创建构造函数的时候,编译器会自行创建    一个什么都不敢的构造函数。


声明格式:

<类名 > (参数)

类的构造函数和常规初始化语法的区别:

int year = 2001;
struct thing
{
char *pn;
int m;
};
thing amabob = {"wrong", 20}; //可行的,因为struct结构体内声明的成员变量 是默认为公有的,外部可以直接访问
CStack hot = {“Sukic”,200,50.25}; //这里CStack为我们已经定义好的类,在程序中想要创建一个hot对象,但是这样是不可以的,因为CStack 的成员变量 //是私有的,不能 直接在外部访问,这也是为什么存在构造函数的原因之一。


现在我们假设: 如果没有构造函数的话:

假设:

CStack 有一个m_sum的int 类型成员变量,当我们实例化一个对象:CStack  tack; 我们并不知道m_sum 的值,如果要对m_num进行运算等操作的话,首先要让m_sum有一个初始值,那么我们可以写一个函数来给m_sum赋值:Adele(num)m_num = num;那么我们在实例化对象之后需要 执行Adele函数:

CStack tack;
tack.Adele(1001);//让m_num 为1001;

那么这样看来,每次我要实例化一个对象的时候,都要在后面调用一下赋值函数,这样显然是非常麻烦的。所以构造函数产生了。这个函数的功能跟Adele 的功能是类似的,只是与Adele比起来在名字和返回值等方面有区别。而且程序声明对象时,将自动执行构造函数。

因此C++提供 了自己的机制:类中通过默认构造函数提供赋值的功能。这样在实例化对象之时,自行给对象的成员变量赋值。当然,如果想要自己写构造函数也是可以的。只需要在实例化的时候将参数传到重载的构造函数中去就可以了。


声明和定义构造函数:

例子:

现在我们有一个CStack类,有成员变量:string m_name,int m_age.

那么我们要对m_name 和m_age进行初始化时的构造函数需要两个参数:

CStack::CStack(const string &name,const int & age)
{
m_name = name;
m_age = age;
}

注意事项:

在上面的代码中m_name 和m_age 是成员变量,但是如果参数名和成员变量名相同的时候,就会报错,因为构造函数的参数表示的不是类成员,而是赋给创元变量的值。如果这样做了的话,会造成混乱。所以参数名必须与成员变量名不相同。


构造函数的使用:

C++提供了两种构造函数 的使用方式:

1.显示调用:

CStack food = CStack(""Apple",1);

2.隐式调用:

CStack food("Apple",1);

即:这两种方式都是将food对象的m_name 赋值为Apple m_age 赋值为1.这两种方式 是等价的。

还有一种形式,我在这里提一下:

构造函数与new一起使用时:

CStack *food = new CSack("Apple",1);

这条语句将创建一个CStack对象,并将此对象的地址 赋值给food指针。那么我们就可以通过food指针来管理此对象。


默认构造函数:

默认构造函数用处:在未提供显式初始值时,用来创建对象的构造函数。

例如:

CStack food;

那么创建food对象时,将自行调用默认构造函数。

默认构造 函数的两种形式  :

1.无参数的默认构造函数:

例如:

CStack:: CStack()
{
m_name = '"Apple';
m_age = 1;
}

2.有初始值参数的默认构造函数 :

CStack::CStack(const string &name = "Apple",int age = 1)
{
m_name = name;
m_age = age;
}

要注意两点:

1.这两种方式是不能同时存在的,因为如果:

CStack food ;

那么声明food对象,自动调用默认构造函数时,会产生二义性(因为以上两种方式都是可以的),程序会报错 。


2.如果我们自己声明定义了一个构造函数不是默认的形式 ,而且没有声明定义默认构造函数(即使这个默认构造函数什么都没做),在以下情况下会出现错误:

CStack food;

因为在我们声明定义了一个非默认构造函数之后,编译器将不会再自动创建默认构造函数。这是因为C++规定:

当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。为类定义了构造函数之后,就必须为它提供默认构造函数。否则如果某些时候需要用到默认构造函数的情况下将会出错。而此时如果food要调用默认构造函数。这会造成冲突。

解决办法:

此时我们必须声明定义一个默认构造函数,来防止这样 的情况产生。


析构函数:

构造函数创建对象后,程序将跟踪对象 ,知道对象过期为止。对象过期后将调用一个 特殊的类成员函数来释放构造函数 用new申请的内存(如果没有申请,析构函数将不执行任何任务,,这种 情况下,只要让编译器生成一个默认的析构函数即可)。

析构函数特点:

1.形式:类名前 加上一个~符号。

2.没有参数。

3.没有返回值和声明类型。

4.用来释放在构造函数中用new申请的内存空间。


什么时候调用析构函数:

通常情况下析构函数不在代码  中 显示的的 调用。

1.创建 的是静态存储类对象时,在程序结束后自动调用。

2.创建 的是自动存储类对象 时,则程序在执行完 代码块后调用析构函数

3.如果是通过new来创建的对象,对象会位于  栈内存 或者*存储区。当使用delette释放   内存的时候 ,析构函数被调用  。

4.如果  是临时的对象,则在代码结束对临时对象使用的时候,自动调用析构函数。


即:类对象 在过期时自动  调用析构函数。因此必须有一个析构函数,如果用户没有定义析构函数,则编译器会隐式 的自动声明一个默认析构函数,

并在发现对象被删除后,自动的提供默认析构函数的定义。


例子:

(只需注意构造函数和析构函数即可)

#pragma once
#ifndef CSTOCK_H_
#define CSTOCK_H_
#include <string>
class CStock
{
public:
CStock(); //默认构造函数
CStock(const std::string &co, long n = 0, double pr = 0.0); //默认构造函数
void Buy(long num, double price);
void Sell(long num, double price);
void Update(double price);
void Show() const;
const CStock &TopVal(const CStock &s) const;
~CStock(); //析构函数
private:
std::string m_company;
int m_shares;
double m_shareVal;
double m_totalVal;
void SetTot(){ m_totalVal = m_shares *m_shareVal; }

};
#endif



#include "stdafx.h"
#include "Stock.h"
#include <iostream>


CStock::CStock() //默认的构造函数 将成员变量初始化
{
m_company = "no name";
m_shares = 0;
m_shareVal = 0;
m_totalVal = 0;
}
CStock::CStock(const std::string &co, long n, double pr) //如果需要外部参数,就传进来,这相当于函数重载
{
m_company = co;
if (n < 0)
{
std::cout << "Number of shares can't be negative;"
<< m_company << "shares set to 0.\n";
m_shares = 0;
}
else
{
m_shares = n;
}
m_shareVal = pr;
SetTot();
}


CStock::~CStock() //析构函数,由于我们并没有在构造函数中用new申请内存,所以析构函数无需执行任何操作、
{
}
void CStock::Buy(long num, double price)
{
if (num < 0)
{
std::cout << "Number of shares purrchsed can't be negative."
<< "Transaction is aborted\n";
}
else
{
m_shares += num;
m_shareVal = price;
SetTot();
}
}
void CStock::Sell(long num, double price)
{
using std::cout;
if (num < 0)
{
cout << "Number of shares sold can't be negative."
<< "Transaction is aborted.\n";
}
else if (num > m_shares)
{
cout << "You can't sell more than you have!"
<< "Transaction is aborted.\n";
}
else
{
m_shares -= num;
m_totalVal = price;
SetTot();
}
}
void CStock::Update(double price)
{
m_shareVal = price;
SetTot();
}
void CStock::Show() const
{
using std::cout;
using std::ios_base;
ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield);
std::streamsize prec = cout.precision(3);
cout << "Company :" << m_company
<< "shares:" << m_shares << "\n";
cout << "Share Price :" << m_shareVal;
cout.precision(2);
cout << "Total Worth: " << m_totalVal << std::endl;
cout.setf(orig, ios_base::floatfield);
cout.precision(prec);
}
const CStock &CStock::TopVal(const CStock &s) const
{
if (s.m_totalVal > m_totalVal)
return s;
else
return *this;
}

Main程序:

#include "stdafx.h"
#include <iostream>
#include "Stock.h"
const int STKS = 4;

int _tmain(int argc, _TCHAR* argv[])
{
CStock stocks[STKS] = { //通过构造函数在对象创建时对类对象的成员变量赋值(参数传进去)
CStock("NanoSmart", 12, 12.0),
CStock("Boffo objects", 200, 1.0),
CStock("Monolithic Obelisks", 130, 3.25),
CStock("Fleep EnterPrises", 60, 6.5)
};
std::cout << "Stock holdings:\n";
int st;
for (st = 0; st < STKS; st++)
{
stocks[st].Show();
}
const CStock* top = &stocks[0];
for (st = 1; st < STKS; st++)
{
top = &top->TopVal(stocks[st]);
}
std::cout << "\nMost valuble holding:\n";
top->Show();
return 0;
}

当然构造函数还有很多细节性的东西以及其他类型。想要深究的可以去学一下。

复制构造函数:

在程序 中,我们很多时候会让 一个对象等于另一个对象,这就要求自己定义函数,来将对象的每个值进行一次 赋值操作 ,在让  对象1  = 对象2 的操作时显式的调用复制函数。显然这是非常麻烦的,所以C++提供了复制构造函数,在让一个对象1 = 对象2时,自动调用复制构造函数,简化程序。

1.系统默认的复制构造函数:

如果用户没有自己写的复制构造函数,那么编译器会 自行创建一个默认的复制构造函数,它将 每一个成员变量都进行了复制。

 

2.自己声明定义复制构造函数:

例子:

如果在 让对象1 = 对象2时,你 想要 一部分成员变量不进行赋值,或者进行特殊的操作,则需要自己写想要 的复制构造函数。

由此引出深浅拷贝:

浅拷贝:

如果我们在程序将每个成员变量 依次拷贝复制时,将进行的是 浅拷贝。

深拷贝:

在程序中,如果兑现的构造函数中使用了new来为一些成员变量申请了内存,那么我们在执行复制构造函数 时,对象1 = 对象2 》对象1的这些成员变量 的内存相当于也是对象 2的成员变量的内存,这是一件很恐怖的事情,因为如果  我们在删除对象2的时候,相当于对象1的这些成员变量的内存被释放了,而当对象1 被删除时,释放对象1的这些成员变量的内存将是不存在,运行会报错。即:释放已经释放过的内存空间是一件很危险的时候。

那么这个时候我们需要自己写复制构造函数来实现深拷贝:

即:

另用new 申请内存空间来存放 从对象2中传过来的数据。


例子:

浅拷贝例子:

在上面代码的CStock类的.h文件中声明复制构造函数:

CStock(const CStock &s);

在.cpp 文件中给出实现:

CStock::CStock(const CStock &s)
{
m_shares = s.m_shares;
//.......
}

深拷贝例子:

如果在CStock类中存在一个 char *m_str成员变量,那么我们在复制时需要为其申请内存空间后才能执行赋值操作,如果只想赋值,会造成两个对象的m_Str只想同一块内存单元。

即:

CStock::CStock(const CStock &s)
{
m_shares = s.m_shares;
//.......
m_Str = new char[1024]; //然后将s中的m_str指向的单元中的字符复制一份给本对象的m_Str内存中即可。


}
构造函数和析构函数并没有想象中的那么简单或者那么难,简单的是理解,难的是细节。


(介绍中很少提到new运算符和构造函数的联系运用等知识点,我打算重新写一份,将new单独出来。)

写完后会贴出链接。