一、类的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.特性
构造函数需要注意的是,虽然名字是叫构造函数,但是其功能并不是开辟空间创建对象,而是对对象进行初始化。
其特征有以下几点:
- 函数名与类名相同
- 函数没有返回值
- 对象实例化时自动调用函数
- 构造函数可以重载
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();//这种的传参不要出现
- 如果类中没有定义显示构造函数,那么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);//正确的实例化方式
- 默认构造函数只有自定义类型成员可以调用,内置类型成员直接是随机值,不过在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.概念
在前面的学习中,我们知道了一个对象的初始化是怎么来的,那么在销毁时也有一个函数在起着作用,这就是析构函数。
析构函数与构造函数的功能相反,对象在销毁时会自动调用析构函数,析构函数是完成对象资源的清理,但是析构函数不是要完成对对象空间的销毁,空间销毁是编译器要完成的。
2、特性
- 函数名是类名前面加上~
- 函数没有参数和返回值
- 一个类只有一个析构函数,如果没有显示定义的析构函数,那么会自动调用默认的析构函数。析构函数没有函数重载!
- 析构函数是编译器在对象生命周期结束时自动调用的
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);
}
- 和构造函数类似,当我们没有显示定义析构函数的时候,编译器也会自动调用默认析构函数,同时默认析构函数也对成员类型有要求,只会对自定义类型成员进行资源清理,而不对内置类型进行清理。因为内置类型不需要析构函数对其进行清理,只需要等函数结束之后,编译器直接将其空间收回即可。但自定义类型往往会有自己开辟的空间,此时就需要析构函数来进行清理。
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.特征
-
拷贝构造函数是构造函数的重载形式,因为其参数形式不一样。
-
函数的参数只有一个,而且必须是对应类类型对象的引用,否则会引发编译直接报错,因为在传参的时候会引发无穷递归。
-
跟构造函数一样,在没有显示定义的时候,编译器会自己调用一个默认的构造函数,拷贝构造也一样。默认拷贝构造函数对象按内存存储的顺序去进行拷贝,这种拷贝叫值拷贝或者叫浅拷贝。
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恰好可以按照字节完成拷贝构造,所以对于日期类的,我们就没有必要再去自己显示构造一个拷贝构造函数了。
- 当遇到有空间开辟,资源申请的类时,我们一定要显式定义拷贝构造函数
如果在这里不进行空间上的再开辟和复制的话,我们就会将同一块空间进行两次释放,这时的编译器就会报错。我们要重新创建一个指针,指向和原本类同样大的空间,有着同样的数据,这就是深拷贝。
- 为了提高效率,一般传参时,都会选择去传引用,既可以防止无线递归,又可以减少拷贝浪费的时间和空间。
拷贝构造函数典型的调用场景
- 使用已存在对象创建新对象
- 函数参数类型是类类型对象
创作不易,感谢观看。