- 构造函数
- 析构函数
- 拷贝构造函数
上一节我们引用了一个栈的类但实际上栈的类初始化和销毁,依靠着构造函数和析构函数进行,没有必要额外写个init()和destory(),我们通过c++类与对象的语法用这构造、析构函数来表现
class stack {
public:
// 栈的初始化 ---> 构造函数 // 创造对象的时候自动调用
stack() {
_stackArray = nullptr;
_stackTop = _capacity = 0;
}
~stack() {
_stackArray = nullptr;
_stackTop = _capacity = 0;
}
void push(int value) {
// 扩容
if (_stackTop == _capacity) {
int newCapacity = _capacity == 0 ? 4: _capacity * 4 ;
int* tmp = (int*)realloc(_stackArray, sizeof(int) * newCapacity);
if (tmp == nullptr) {
perror("malloc fail");
return;
}
_stackArray = tmp;
_capacity = newCapacity;
}
// 栈顶插入
_stackArray[_stackTop] = value;
_stackTop++;
}
void pop() {
if (_stackTop == 0) {
cout << " 栈内没有元素可以出栈 " << endl;
return;
}
_stackTop--;
}
void printTop() {
assert(_stackTop!=0);
cout << _stackArray[_stackTop-1] << endl;
}
// 成员变量最好加前杠(利于成员方法区别参数),成员变量一般封装
private:
int* _stackArray;
int _capacity;
int _stackTop;
};
void test_1() {
stack myStack ; // 创造对象的同时进行析构函数
myStack.push(10);
myStack.printTop();
}
// test_1这个生命周期的结束 自动走析构函数
那么开始讲一下构造函数(重点)和析构函数
构造函数
构造函数的重载
这里上一段错误的代码(这个无参数的构造函数会跟这个全缺省在test_2()中无参数调用时无法区分
class Data{
public:
Data(){
_year = 1;
_month = 1;
_day = 1;
}
Data(int year = 1; int month = 1; int day = 1){
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
const int _num;
};
void test_2(){
Data today; // 因为在这个类里面 这两个构造函数会冲突,不知道选哪一个
}
那么对于这种类我们可以只用全缺省构造函数,这里也体现了全缺省的优势
class Data{
public:
Data(int year = 1; int month = 1; int day = 1){
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
const int _num;
};
void test_3(){
// 兼顾无参数传入
Data today;
// 也可以直接传入
Data tomorrow(2023, 7, 23);
}
构造函数的机制
但是在构造函数中有一个 “初始化列表”
初始化列表
初始化列表是每个成员定义的地方,每个成员都需要走,内置类型走默认构造,自定义类型走自定 ,然后在C++11版本开始封装区声明时的缺省值,是传给初始化列表,顺序仅与声明顺序有关
// 构造函数
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
, _num(10) // 这里的10是给const修饰的_num复制为10
{
// 如果这里 _num = 10; 会报错,因为const修饰一旦生成就无法修改,进入构造函数在就已修改
}
实际上,初始化列表一般用于在没有自定义类型的构造函数的时候,用于给自定义类型来进行构造的地方,下面用队列来演示
class myQueue {
public:
myQueue(int capacity)
: _pushStack(capacity)
, _popStack(capacity)
{
}
private:
Stack _pushStack;
Stack _popStack;
};
接着就是我们的析构函数了
析构函数
与构造函数不同的是析构函数就只有一个,完成对象在生命周期结束前的销毁、释放内存的操作,每一个类中都应该析构函数
关于自定义类型处理的问题
在构造函数和析构函数中,我们定义类型的时候,属于c++内置类型,例如:int / char / double /.....这些时,c++编译器不进行处理,如果是自定义的成员变量(如结构体成员),那编译器就会去生成处理,向下回去处理该成员的默认构造函数,那么到了这里构造函数和析构函数的学习就结束啦
拷贝构造函数
拷贝构造就是把对象B与对象A弄得一样的操作,需要注意的是传入引用,传入拷贝的话会死循环
下面是一个示例,演示如何定义和使用拷贝构造函数:
浅拷贝
#include <iostream>
using namespace std;
class MyClass {
private:
int data;
public:
// 默认构造函数
MyClass() {
cout << "Default constructor called" << endl;
data = 0;
}
// 带参数构造函数
MyClass(int value) {
cout << "Parameterized constructor called" << endl;
data = value;
}
// 拷贝构造函数
MyClass(const MyClass& obj) {
cout << "Copy constructor called" << endl;
data = obj.data;
}
// 成员函数
void display() {
cout << "Data: " << data << endl;
}
};
int main() {
MyClass obj1(10); // 调用带参数构造函数
MyClass obj2(obj1); // 调用拷贝构造函数
obj1.display(); // 输出: Data: 10
obj2.display(); // 输出: Data: 10
return 0;
}
在上面的示例中,我们定义了一个名为MyClass的类,包含了默认构造函数、带参数构造函数和拷贝构造函数。在main函数中,我们首先使用带参数构造函数创建了一个对象obj1,并将其值设置为10。然后,我们使用拷贝构造函数创建了另一个对象obj2,并将其内容初始化为obj1。最后,我们分别调用对象的display函数,输出了它们的数据值。
需要注意的是,如果没有显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。但在某些情况下,例如类中包含指针成员变量时,需要自定义拷贝构造函数来确保正确地复制指针所指向的内存。
上面的是拷贝构造函数的浅拷贝(直接指向同一块空间),接着补充深拷贝(另开一片空间
深拷贝
// 栈的深拷贝
stack(stack& myStack) {
_stackArray = (int*)malloc(sizeof(int) * myStack._capacity);
if (_stackArray == NULL) {
perror("malloc Fail");
return;
}
// 对象数据的拷贝
memcpy(_stackArray, myStack._stackArray, sizeof(int) * myStack._stackTop);
_stackTop = myStack._stackTop;
_capacity = myStack._capacity;
}
这里简略了data的拷贝函数(自己去写很简单的),所以总结以下
1.内置类型直接就浅拷贝就好,也就是默认生成
2.自定类型涉及了开辟空间和数据拷贝,进行深拷贝,但是当自定义类型中分出了自定类型,比如栈实现队列时,只要栈里面存在深拷贝,就没必要给队列写一个深拷贝,直接默认生成拷贝函数就可解决
3.尽量拷贝的时候写一个const在传入引用前保证传入的拷贝的对象不受影响