C++中如果一个类中什么成员都没有,简称为为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数,他们分别是负责初始化和清理的构造函数和析构函数,用来拷贝复制的拷贝构造函数和赋值重载函数,还有普通对象和const对象取地址。下面我们对几个默认构造函数进行讲解。
默认成员函数
需要注意的是,默认成员函数如果我们不实现,编译器会自动生成对应的函数,有些情况下我们可以直接使用默认生成的成员函数,有的情况下我们需要自己实现,那么让我们看看默认生成的成员函数有哪些特性吧。
构造函数
构造函数:构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
构造函数由来
在C语言中我们使用的自定义类型有时会忘记对其进行初始化,因此C ++的类中有默认的构造函数,即使我们不实现他也会由编译器自动生成并进行初始化
构造函数特征
1.函数名与类名相同。 2. 无返回值。 3. 对象实例化时编译器自动调用对应的构造函数。 4. 构造函数可以重载。 5.不能显示的调用构造函数 自己实现的构造函数如下
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2022, 3, 22);
return 0;
}
这里需要注意的是如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
默认构造函数特征
C++中将类型分为两类:内置类型和自定义类型,其中内置类型有 int/double/指针等等,自定义类型有struct/class定义的类型。 编译器的默认构造函数,对内置类型不做初始化处理。 对于自定义类型去调用他的无参默认构造函数(不用参数就可以调用的)进行初始化,如果没有默认构造函数就会报错,默认构造函数就是不用参数也可以调用的。 默认构造函数可以认为有3种:无参的,全缺省的或者不写编译器自己生成的
代码范例以及运行结果如下:
class Test
{
public:
Test()
{
cout << "TEST" << endl;
_a = 10;
}
Test(int a)
{
cout << "TEST" << endl;
_a = 10;
}
private:
int _a;
};
class Date
{
public:
private:
int _year;
int _month;
int _day;
Test _a;
};
int main()
{
Date d1;
return 0;
}
从代码不难看出这里的Date类我们没有实现构造函数,因此使用的是编译器自己生成的默认构造函数,这里调试结果显示编译器自动生成的默认构造函数对内置类型没有进行处理,对内置类型调用他自己的构造函数,如果没有构造函数则会进行报错。
总结:
C++默认生成的构造函数,没有对自定义类型和内置类型进行统一处理,不处理内置类型成员变量,只处理自定义类型的成员变量
析构函数
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中的一些资源清理工作。
特征
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
析构函数自己实现如下:
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
cout << "A" << endl;
}
private:
int _year;
int _month;
int _day;
};
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * _capacity);
if (_a == nullptr)
{
cout << "malloc failed" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
free(_a);
_a = nullptr;
}
private:
int* _a;
size_t _capacity;
size_t _top;
};
这里可以看到析构函数和构造函数类似,对于内置类型可以不做处理,但是对于使用malloc或者new之类的在堆上开辟空间后,需要在析构函数中进行主动释放。 构造函数和析构函数执行的顺序相反,构造函数先定义的先构造,析构函数从后往前析构
拷贝构造
拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
- 无穷递归调用原因:传值传参是用一个同类型对象初始化,就是拷贝构造而调用拷贝构造需要先传参数,传值传参又是一个拷贝构造。
默认拷贝构造
对于内置类型成员,会完成按字节序的拷贝(浅拷贝) 浅拷贝对于stack类型进行拷贝构造,成员变量会指向同一空间,在调用析构函数的时候会析构两次引起崩溃。 测试结果如下:
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * _capacity);
if (_a == nullptr)
{
cout << "malloc failed" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
free(_a);
_a = nullptr;
}
private:
int* _a;
size_t _capacity;
size_t _top;
};
int main()
{
Stack s1;
Stack s2(s1);
return 0;
}
自定义类型成员会调用他的拷贝构造。 代码范例如下:
class Test
{
public:
Test()
{
}
Test(const Test&)
{
cout << "TEST1" << endl;
}
~Test()
{
cout << "TEST2" << endl;
}
private:
int _a;
};
class Date
{
public:
Date(int year=0,int month=1,int day=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
Test _a;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
运行结果:
从调试结果可以看到,对于内置类型默认生成的拷贝构造函数会完成字节序的拷贝,对于自定义类型_a会调用他自己的拷贝构造函数,因此打印"TEST1"。
总结
总结:拷贝构造不自己实现,由系统自动生成的默认拷贝构造函数对于内置类型和自定义类型都会拷贝处理(进行字节序的浅拷贝),但是处理的细节和构造和析构不一样。
|