【c++基础(三)】类和对象中--构造和析构函数

1.前言

本章重点

本篇文章着重讲解类中的两个默认函数,分别为:
构造函数,析构函数
这是c++六个默认成员函数中的两个
(其他四个在后面章节讲解)

 我们平时在写基础的数据机构时,例如栈和队列

如果自己没有注意没有进行初始化,就有可能导致出错,同理,在写完代码后,忘记销毁开辟好的空间,这样容易导致内存泄漏,长期内存泄漏,就会造成不可能避免的损失。对于一些小白来说用起来是非常困难的。

这些都是c语言里面残留的一些问题,c++为了解决这两类问题,就提出了构造函数和析构函数的概念。

2.构造函数

构造函数:顾名思义就是当你自己不初始化时,这个函数会帮你进行初始化。

特性:

  • 数名与类名相同
  • 无返回值
  • 对象实例化时自动调用对应的构造函数
  • 构造函数可以重载

需要注意的点是:

1.构造函数并不是普通的成员函数,他是一个特殊的成员函数

2.他的任务只是对已经实例化出来的对象进行初始化,不是开辟空间实例化出对象。

举例说明: 

 class Date
 {
 public:
	  Date(int year, int month, int day)//构造函数
	  {
	       _year = year;
	       _month = month;
	       _day = day;
	  }
	  Date()//无参的构造函数
	  {
	  	_year = 1900;
	  	_month = 1;
	  	_day = 1;
	  }
 private:
      int _year;
      int _month;
      int _day;
 };

int main()
{
	 Date d1; // 调用无参构造函数
	 Date d2(2023, 7, 24);//调用含参的构造
    //这里要注意:如果调用的是无参的构造函数,后面不能加(),否则就成了函数声明
    // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
    // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
     Date d3();
}

3.构造函数的特性

如果使用者没有写构造函数,那么编译器会自动生成一个。

例:

class Date
 {
  public:
 /*
 // 如果用户显式定义了构造函数,编译器将不再生成
 Date(int year, int month, int day)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 */
 
 void Print()
 {
    cout << _year << "-" << _month << "-" << _day << endl;
 }
  
 private:
   int _year;
   int _month;
   int _day;
 };
  
  int main()
 {
 // 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
 
 // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
 
 // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
 
 Date d1;
 return 0;
 }

对上述代码的解释:

屏蔽掉自己写的构造函数时编译器会自动生成一个,d1在实例化时就会去调用编译器生成的

然而当放开自己写的构造函数后会报错,因为自己实现的构造函数没有缺省值,并且d1实例化时没有传参。

4. 对默认构造函数的理解

可能童鞋们会疑惑:既然编译器会自己生成构造函数,那我是不是写不写构造函数都可以了?

带着此疑问引出一个新概念:内置类型和自定义类型

内置类型是C++语言提供的类型。比如: int/char类型

自定义类型是用户使用class/struct/union类定义出来的类型,如:Date类(日期类)

这个新概念有什么用?

编译器自动生成的构造函数,不会处理内置类型,它们是随机值

然而自动生成的构造会处理自定义类型,它会去调用自定义类型的默认构造

举例说明一下:

class Time
{
public:
 Time()
 {
 cout << "Time()" << endl;
 _hour = 0;
 _minute = 0;
 _second = 0;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
private:
 // 基本类型(内置类型)
 int _year;
 int _month;
 int _day;
 // 自定义类型
 Time _t;
};
int main()
{
 Date d;
 return 0;
}

对上述代码进行解释:

发现编译器生成默认的构造函数会对自定类型成员 _t 调用的它的默认成员
函数。此时_year,_month,_day任然是随机值,而_t中的会调用它的默认构造函数进行初始化,所以_t中的成员都进行了初始化

5.对默认构造函数的补充

1.注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在
类中声明时可以给默认值
举例说明:
class Time
{
public:
 Time()
 {
 cout << "Time()" << endl;
 _hour = 0;
 _minute = 0;
 _second = 0;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};
int main()
{
 Date d;
 return 0;
}

对上述代码进行解释:

当你实例化一个Date对象时,你没有写默认构造函数,他会自己生成一个默认构造函数,按照之前的逻辑,Date中的int类型都是随机值,但是在c++11中内置类型成员变量在 ,类中声明时可以给默认值。因此此时int类型都不是随机值,而被默认初始化为给的默认值

2.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且 默认构造函数只能有一个
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。

举例说明:

class Date
{
public:
 Date()
 {
 _year = 1900;
 _month = 1;
 _day = 1;
 }
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
private:
 int _year;
 int _month;
 int _day;
};
// 以下测试函数能通过编译吗?
int main()
{
    Date d1();
    return 0;
}

对上述代码进行解释

这样是无法通过编译的,因为上面两种写法都是默认构造函数!但是它们不能同时存在
具体来说就是当实例化对象时没有传参,系统不知道是调用全缺省函数还是无参的函数

 6. 析构函数

现在我们知道一个对象是怎么被初始化的
那么一个对象又是怎么被销毁的呢?

概念:

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

特性:

1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载(重载概念后面再谈)
4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数。

PS:析构函数和构造函数一样是特殊的函数,不能将它与普通函数相比

7.对析构函数的理解

 有了前面构造函数的铺垫
析构函数就容易理解了,和我们想的一样
编译器自动生成的默认析构函数
只处理自定义类型,而内置类型不会管

那你可能会产生一个问题:

既然默认析构函数不会处理内置类型
那么内置类型是不是不会销毁?

答案是: 不!

内置类型会在对象生命周期结束时,将它在栈区的空间还给操作系统
所以析构函数不处理在栈区的变量,也没有问题

但是有些变量的指针指向堆区,有由动态开辟出来的空间,这份空间不会主动还给操作系统
需要我们手动写析构函数来释放!

举个例子:

typedef int DataType;
class Stack
{
public:
	 Stack(size_t capacity = 3)//构造函数
	 {
		 _array = (DataType*)malloc(sizeof(DataType) * capacity);
		 if (NULL == _array)
		 {
			 perror("malloc申请空间失败!!!");
			 return;
		 }
		 _capacity = capacity;
		 _size = 0;
	 }
 
	 ~Stack()//析构函数
	 {
		 if (_array)
		 {
			 free(_array);
			 _array = NULL;
			 _capacity = 0;
			 _size = 0;
		 }
 }
private:
	 DataType* _array;
	 int _capacity;
	 int _size;
};
void TestStack()
{
 Stack s;
}

对上述代码进行解释:这段代码中,存在在堆区申请的空间,所以不能使用编译器默认生成的析构
而是要用自己写的析构函数去free掉,这块堆区的空间

7.1 对默认析构函数的理解

先上代码:

class Time
{
public:
 ~Time()
 {
 cout << "~Time()" << endl;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};
int main()
{
 Date d;
 return 0;
}

对上述代码的解释:

// 程序运行结束后输出: ~Time()
// main 方法中根本没有直接创建 Time 类的对象,为什么最后会调用 Time 类的析构函数?
// 因为: main 方法中创建了 Date 对象 d ,而 d 中包含 4 个成员变量,其中 _year, _month,
_day 三个是 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而 _t Time类对 象,所以在 d 销毁时,要将其内部包含的 Time 类的 _t 对象销毁,所以要调用 Time 类的析构函数。但是:
main 函数 中不能直接调用Time 类的析构函数,实际要释放的是 Date 类对象,所以编译器会调用 Date 类的析构函 数,而 Date 没有显式提供,则编译器会给 Date 类生成一个默认的析构函数,目的是在其内部 调用Time 类的析构函数,即当Date 对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main 函数中并没有直接调用 Time 类析构函数,而是显式调用编译器为 Date 类生成的默认析
构函数

注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

8.总结与拓展

总结:构造函数是析构函数是对立的,一个用于初始化,一个用于销毁对象调用
掌握它们对后面类和对象的学习很重要。

拓展:

类的六个默认函数:

现在已经学了前两个了,后续在学其他几个。

                                               下期预告:拷贝构造和运算符重载

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值