类的默认成员函数
- 类的默认成员函数包括构造函数、析构函数和拷贝构造函数。如果在类定义中没有显式地定义这些函数,编译器会自动生成默认的版本。
- 默认构造函数没有参数,用于创建一个新的对象。默认析构函数没有参数,用于销毁一个对象。默认拷贝构造函数用于创建一个对象的副本。
- 默认构造函数会自动为类中的非静态成员变量执行默认的初始化操作,例如将数字类型初始化为0,将布尔类型初始化为false,将指针类型初始化为null等等。
- 默认析构函数会自动释放对象所占用的内存,同时如果对象中包含有指针类型成员变量,则需要在析构函数中手动释放这些成员变量所指向的内存空间,以避免内存泄漏。
- 默认拷贝构造函数用于创建一个对象的副本,将当前对象的状态复制到新创建的对象中。如果类中包含有指针类型的成员变量,则默认拷贝构造函数只会复制指针,而不会复制指针所指向的内存空间,这可能会导致两个对象共享同一块内存空间,对其中一个对象的修改可能会影响到另一个对象。因此,在包含有指针类型成员变量的类中,需要自定义拷贝构造函数来实现深拷贝,即将指针所指向的内存空间也复制到新的对象中。
构造函数
构造函数虽然叫做构造函数,但其实不是用来创建的,是用来初始化的。
主要特征:
1.函数名和类名相同
2.无返回值
3.对象实例化时编译器自动调用对应的构造函数
4.构造函数可以重载
5.如果类中没有显式定义构造函数,C++编译器会自动生成一个构造函数,一旦用户定义则不生成
6.无参、全缺省参数、系统默认构造函数这些都是默认构造函数
//构造函数
struct show
{
//构造函数
show(char author,char reader)
{
_author = author;
_reader = reader;
}
//类的成员函数
void print()
{
cout << "这是c写的类" << endl;
}
//类的成员
char _author;
char _reader;
};
int main()
{//调用构造函数
show s1('I', 'y');
}
析构函数
- 析构函数是一种特殊的成员函数,用于释放对象占用的资源。它的名称与类名相同,但前面有一个波浪线(~)符号。
- 当对象的生存期结束时,系统会自动调用其析构函数。通常,析构函数用于释放对象在构造函数中分配的内存空间或其它资源(例如打开的文件、网络连接等)。
- C++中的析构函数是自动调用的,程序员不需要显式地调用它。如果程序员显式地调用析构函数,可能会导致内存泄漏和其它错误。
简单来说就是当对象生命周期结束的时候,系统会自动调用一个销毁函数,释放内存。
拷贝构造
拷贝构造函数只有一个形参,该形参是对本类类型对象的引用(一般用const修饰),在用以存在的类类型对象创建新对象时由编译器自动调用。
//构造函数
struct show
{
//拷贝构造
show(const show& s1)
{
_author=s1._author;
_reader=s1._reader;
}
//构造函数
show(char author,char reader)
{
_author = author;
_reader = reader;
}
//类的成员函数
void print()
{
cout << "这是c写的类" << endl;
}
//类的成员
char _author;
char _reader;
};
int main()
{//调用构造函数
show s1('I', 'y');
}
相当于调用拷贝构造的时候要传一个自己的类过来
拷贝构造也是特殊的成员函数,特征如下:
1.拷贝构造是构造函数的一个重载形式
2.拷贝构造的参数只能有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归。
第二点不太好理解,我解释一下,形参是实参的临时拷贝对吧,也就是说如果我是传值,那么是不是先要拷贝一份实参,那么我怎么拷贝呢?调用拷贝函数- -也就是拷贝构造,好嘞,现在我正在定义拷贝构造,结果你还要我调用拷贝构造,那行呗,继续调用拷贝构造,那我拷贝啥啊,噢,你传过来的形参,那我要对形参进行一份拷贝......然后就这样无限套娃了。
赋值运算符重载
赋值重载
赋值运算符重载函数是一个特殊的成员函数,用于将一个对象的值赋给另一个对象。在 C++ 中,赋值运算符默认的行为是浅拷贝,即仅将指针成员变量的地址赋给新对象。如果对象中存在动态分配的内存,这个默认行为可能会导致指针悬空或内存泄漏等问题。因此,我们需要通过重载赋值运算符函数来实现深拷贝(即复制整个对象,包括指针成员变量指向的内存)。
示例代码如下:
#include <iostream>
using namespace std;
class MyClass {
private:
int* data;
public:
MyClass(int value) {
data = new int(value);
}
// 拷贝构造函数
MyClass(const MyClass& other) {
data = new int(*other.data);
}
// 赋值运算符重载函数
MyClass& operator=(const MyClass& other) {
if (this != &other) {
delete data;
data = new int(*other.data);
}
return *this;
}
~MyClass() {
delete data;
}
int getValue() const {
return *data;
}
};
int main() {
MyClass a(10);
MyClass b(20);
// 使用赋值运算符将 b 的值赋给 a
a = b;
// 输出 a 和 b 的值,应该相同
cout << "a = " << a.getValue() << endl;
cout << "b = " << b.getValue() << endl;
return 0;
}
在上面的示例代码中,我们定义了一个 MyClass 类,其中包含了一个指向 int 类型数据的指针成员变量。为了防止浅拷贝带来的问题,我们还实现了拷贝构造函数和赋值运算符重载函数。在赋值运算符重载函数中,我们首先判断是否自我赋值,然后删除原来的指针,再为新对象分配内存并赋值。
最后在 main 函数中,我们创建了两个 MyClass 类型的对象 a 和 b,然后使用赋值运算符将 b 的值赋给了 a,验证了赋值运算符重载函数的正确性。
运算符重载
在 C++ 中,运算符重载是允许用户自定义运算符操作的一种机制。通过重载运算符,可以使得用户定义的类的对象可以使用与内置类型相似的运算操作,从而使得代码更加简洁易懂。
C++ 中支持的运算符重载包括一元运算符、二元运算符和复合赋值运算符。一元运算符只需要重载一个参数,二元运算符需要重载两个参数,而复合赋值运算符既需要重载运算符本身,又需要重载赋值运算符。
下面以重载加号运算符为例,简要介绍如何重载运算符:
#include <iostream>
using namespace std;
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
Complex operator+(const Complex& other) {
return Complex(real + other.real, imag + other.imag);
}
double getReal() const {
return real;
}
double getImag() const {
return imag;
}
};
int main() {
Complex a(1, 2);
Complex b(3, 4);
Complex c = a + b; // 调用运算符重载函数
cout << c.getReal() << " + " << c.getImag() << "i" << endl;
return 0;
}
在上面的示例代码中,我们定义了一个 Complex 类,重载了加号运算符。在重载函数中,我们将两个复数的实部和虚部分别相加,返回一个新的 Complex 对象。
在 main 函数中,我们创建了两个 Complex 类型的对象 a 和 b,然后将它们相加,实现了复数的加法运算。当我们使用加号运算符时,编译器会自动调用我们所定义的重载函数。最后将相加后的结果输出。
需要注意的是,在重载运算符时,我们需要遵循一些约定俗成的规则,例如重载函数通常要声明为类的成员函数或友元函数,它们的参数和返回值类型也有一定的限制。