类和对象——构造和析构函数

一、类的6个默认成员函数

我们知道,当定义的类中任何成员都不存在的时候,就属于空类。

class Stack{};

那么空类中真的是什么东西都没有吗?当然不是,当空类在运行时,编译器会自动生成6个默认成员函数。
默认成员函数就是,用户没有显示实现,编译器会自动给用户生成的函数。默认成员函数分为以下6种。
在这里插入图片描述

二、构造函数

1.概念

class date
{
public:
void init(int year,int month,int day)
{
	_year=year;
	_month=month;
	_day=day;
}
private:
int _year;
int _month;
int _day;
}
int main()
{
	date d1;
	d1.init(2024,2,3);
	return 0;
}

在这段函数里,我们定义d1的时候,在使用之前还需要调用init函数来进行初始化,而有没有什么办法可以让我们在多次定义date类的对象时,不用多次调用init函数呢?
这里就用到我们的构造函数,构造函数是一种特殊的成员函数函数名和类名相同,编译器在定义类对象时直接调用,目的是保证每一个成员对象都有一个初始值,并且在每一个对象的生命周期中只调用一次

2.特性

构造函数需要注意的是,虽然名字是叫构造函数,但是其功能并不是开辟空间创建对象,而是对对象进行初始化。
其特征有以下几点:

  1. 函数名与类名相同
  2. 函数没有返回值
  3. 对象实例化时自动调用函数
  4. 构造函数可以重载
class date
{
date()//无参构造函数
{
	_year=1;
	_month=1;
	_day=1;
}
date(int year,int month,int day)//有参构造函数
{
	_year=year;
	_month=month;
	_day=day;
}
private:
int _year;
int _month;
int _day;
}
int main()
{
	date d1;
	date d2(2024,2,3);
	return 0;
}

在这里要注意,如果是想d1一样,调用无参数的构造函数,千万不要在d1后面加括号。因为会和函数声明发生混淆。

date d1();//这种的传参不要出现
  1. 如果类中没有定义显示构造函数,那么cpp编译器会自动生成一个默认的隐式构造函数,但是如果有了显示定义的构造函数,那么编译器就不会再生成默认的构造函数
class date
{
date(int year,int month,int day)//有参构造函数
{
	_year=year;
	_month=month;
	_day=day;
}
private:
int _year;
int _month;
int _day;
}
int main()
{
	date d1;
	return 0;
}

这个代码就会报错,因为编译器找不到合适的构造函数。为什么呢,让我们来看看。首先我们在最开始就对名为date的构造函数进行了显示定义,所以编译器在编译的过程中就不会再生成默认的构造函数,但是要注意,我们定义的构造函数是一个有参构造函数,所以在实例化对象时,要加上括号并且传参。
显而易见,d1并没有及时传参,编译器就找不到不用传参的构造函数,就会报错。

date d1(2024,2,3);//正确的实例化方式
  1. 默认构造函数只有自定义类型成员可以调用,内置类型成员直接是随机值,不过在cpp11中,内置成员类型也可以在声明时直接进行初始化了。
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的类中没有找到显示构造函数,于是编译器直接就去调用了他的默认构造类型,编译器找到了生成了调用time类变量的构造函数,于是对time内的成员进行了初始化。
但是剩下的三个int类型的变量因为其是内置类型,所有就还是随机值,但是在最新的cpp中,打了补丁,可以在内置类型声明的时候,给予一个默认值,如果没有显示构造函数的话,就直接赋值默认值。

  1. **无参数的构造函数和全缺省值的构造函数都是默认构造函数,并且默认构造函数只能有一个存在,防止其在实例化对象时产生歧义。**加上编译器自动生成的构造函数,这三种都可以被称为默认构造函数。

二、析构函数

1.概念

在前面的学习中,我们知道了一个对象的初始化是怎么来的,那么在销毁时也有一个函数在起着作用,这就是析构函数。
析构函数与构造函数的功能相反,对象在销毁时会自动调用析构函数,析构函数是完成对象资源的清理,但是析构函数不是要完成对对象空间的销毁,空间销毁是编译器要完成的。

2、特性

  1. 函数名是类名前面加上~
  2. 函数没有参数和返回值
  3. 一个类只有一个析构函数,如果没有显示定义的析构函数,那么会自动调用默认的析构函数。析构函数没有函数重载!
  4. 析构函数是编译器在对象生命周期结束时自动调用的
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;
 }
 void Push(DataType data)
 {
 // CheckCapacity();
 _array[_size] = data;
 _size++;
 }
 // 其他方法...
 ~Stack()
 {
 if (_array)
 {
 free(_array);
 _array = NULL;
 _capacity = 0;
 _size = 0;
 }
 }
private:
 DataType* _array;
 int _capacity;
 int _size;
};
void TestStack()
{
 Stack s;
 s.Push(1);
 s.Push(2);
}
  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;
}

三、拷贝构造函数

1.概念

当我们在实例化新对象时,我们可能需要创建两个一模一样的对象,这时就可以用拷贝构造函数来对另外一个需要初始化的对象,将其初始化为和另外一个有相同内容的类。
**拷贝构造函数:**是一种特殊的构造函数,其只有一个形参,这个形参是本类类型对象的引用,一般用const修饰,在用已存在的类类型对象创建新对象时由编译器自动调用。

2.特征

  1. 拷贝构造函数是构造函数的重载形式,因为其参数形式不一样。

  2. 函数的参数只有一个,而且必须是对应类类型对象的引用,否则会引发编译直接报错,因为在传参的时候会引发无穷递归。
    在这里插入图片描述

  3. 跟构造函数一样,在没有显示定义的时候,编译器会自己调用一个默认的构造函数,拷贝构造也一样。默认拷贝构造函数对象按内存存储的顺序去进行拷贝,这种拷贝叫值拷贝或者叫浅拷贝。

class date
{
private:
int _year=1990;
int _month=11;
int _day=22;
}
int main()
{
	date d1;
	date d2(d1);
	return 0;
}

当d2在初始化时,就是将d1中的数据拷贝初始化给d2,编译器就会自己调用拷贝构造函数,但是在类中我们没有显示定义拷贝构造函数,所以系统就会用默认的拷贝构造函数。
而系统的默认构造函数对于内置类型是按照字节进行拷贝,对于自定义类型是调用其拷贝构造函数进行拷贝。所以这里的date恰好可以按照字节完成拷贝构造,所以对于日期类的,我们就没有必要再去自己显示构造一个拷贝构造函数了。

  1. 当遇到有空间开辟,资源申请的类时,我们一定要显式定义拷贝构造函数
    在这里插入图片描述

如果在这里不进行空间上的再开辟和复制的话,我们就会将同一块空间进行两次释放,这时的编译器就会报错。我们要重新创建一个指针,指向和原本类同样大的空间,有着同样的数据,这就是深拷贝

  1. 为了提高效率,一般传参时,都会选择去传引用,既可以防止无线递归,又可以减少拷贝浪费的时间和空间。

拷贝构造函数典型的调用场景

  1. 使用已存在对象创建新对象
  2. 函数参数类型是类类型对象

创作不易,感谢观看。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值