目录
今天我们继续学习c++类和对象部分。 我在上一节类和对象1中讲了类的定义、实例化对象、this指针,今天我来讲一下类和对象中比较难的部分:类的默认成员函数。分别为构造函数、析构函数、拷贝构造、 拷贝赋值运算符重载、取地址运算符重载、const取地址运算符重载。tips:这节干货满满。
构造函数
构造函数的作用可以被说成是用来替代初始化的,也就是说他可以初始化对象。
构造函数的特点
1.函数名和类名相同,无返回值,系统自动调用,可以重载。
2.如果类中没有显示定义构造函数,编译器自动生成一个无参的默认构造函数。
3.无参构造函数,全缺省构造函数,编译器自动生成的构造函数这三者都可以被称为默认构造函数,三者只能存在一个。
4.编译器生成的构造函数是否对内置类型进行处理是不确定的,看编译器如何选择。
5.如果这个成员变量没有默认构造函数就要使用初始化列表。
我们在下面的代码中体现上述特点。
//构造函数
class Date
{
public:
//无参构造函数(默认)
//函数名和类名相同,无返回值
Date()
{
_year = 2025;
_month = 3;
_day = 12;
};
//全缺省构造函数(默认)只能有一个默认构造函数,否则会报错
/*Date(int year = 2025, int month = 3, int day = 12)
{
_year = year;
_month = month;
_day = day;
}*/
//带参构造函数
/*Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}*/
void Printf()
{
cout << _year << " " << _month << " " << _year << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//实例化对象
//调用无参构造函数无需传参
//Date d1();会与函数的声明混淆
Date d1;
d1.Printf();//打印构造函数的结果
return 0;
}
析构函数
- 对象创建(租房):当你租下公寓时,相当于创建了一个对象。你可以使用公寓里的各种资源(相当于对象的数据成员)。
- 析构函数(退房):当你决定不再租住时,你需要做一些清理工作,比如归还钥匙、打扫卫生等。这相当于析构函数的工作,它会在对象生命周期结束时自动调用,释放对象占用的资源或执行必要的清理工作。
析构函数的特点
1.函数名是在类名前加~,无参数无返回值,一个类只能有一个析构函数,若未显示定义系统会默认生成析构函数,析构函数在对象生命周期结束时由系统自己调用。
2.析构函数对内置成员不做处理,内置成员的内存由系统自己回收。自定义类型无论什么情况都会自动调用析构函数。
3.如果类中自定义类型存在资源申请,则必须显示定义析构函数,否则会资源泄露。
4.C++规定一个局部域的多个对象中,后定义的先析构。
我们在下面代码中体现上述特点
//析构函数
//拷贝构造函数
//自定义类型
//栈类
class Stack
{
public:
//全缺省构造函数(默认)
Stack(int n = 4)
{
//做个标记证明系统自动调用了构造函数
cout << "Stack(int n = 4)" << endl;
//涉及动态内存分配
_arr = (int*)malloc(sizeof(int) * n);
if (_arr == nullptr)
{
return;
}
_capacity = 0;
_top = 0;
}
//涉及动态内存分配就必须显示定义构造函数
~Stack()
{
//做个标记证明系统自动调用了析构函数
cout << "~Stack()" << endl;
free(_arr);
_arr = nullptr;
_capacity = _top = 0;
}
private:
int* _arr;
int _capacity;
int _top;
};
//两个栈实现队列
class MyQueue
{
//MyQueue类中没有显示定义析构函数/构造函数
//编译器自动生成的析构函数/构造函数会自动调用Stack的析构函数/构造函数。
private:
Stack pushst;
Stack popst;
};
int main()
{
Stack st1;
MyQueue my1;
return 0;
}
拷贝构造函数
拷贝构造函数是一种特殊的构造函数。
拷贝构造函数的特点
1.拷贝构造是构造函数的一个重载,第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。(类传值传参的本质是将实参拷贝一份给形参,而在拷贝过程中就调用类的拷贝构造,此时如果类的拷贝构造是传值传参就会再次触发拷贝构造,无限递归。)
2.C++规定类类型对象进行拷贝必须调用拷贝构造。
3.如果类类型中的成员都是内置类型,且未显示定义拷贝构造,此时系统会自己生成一个拷贝构造来对值进行浅拷贝。像MyQueue这种类,系统自动生成的拷贝构造会自动调用Stack中的拷贝构造,我们无需显示定义。
4.如果自定义类型存在资源申请,就必须自己显示定义拷贝构造,否则在进行拷贝构造时系统会进行浅拷贝将两个实例化对象中的成员指针指向同一块内存,析构的时候就会析构两次,引发程序崩溃。
5.后定义的先析构。
//拷贝构造函数
//同样以栈为例
class Stack
{
public:
//构造函数
Stack(int n = 4)
{
_arr = (int*)malloc(sizeof(int) * n);
if (_arr == nullptr)
{
return;
}
_capacity = n;
_top = 0;
}
//拷贝构造
//第一个参数要是类类型的引用
Stack(const Stack& st)
{
//深拷贝
//创建同样大小的空间
_arr = (int*)malloc(sizeof(int) * st._capacity);
if (_arr == nullptr)
{
perror("malloc");
return;
}
//拷贝
memcpy(_arr, st._arr, sizeof(int) * st._top);
_capacity = st._capacity;
_top = st._top;
}
//实现一个入栈操作
void StackPush(int x)
{
//栈满了扩容
if (_top == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
int* _a = (int*)realloc(_arr, newcapacity);
if (_a == nullptr)
{
perror("realloc");
return;
}
_arr = _a;
_capacity = newcapacity;
}
_arr[_top++] = x;
}
//打印出来看看
void Print()
{
int i = 0;
int j = _top;
while (j--)
{
cout << _arr[i++] << endl;
}
}
//析构函数
~Stack()
{
cout << "~Stack()" << endl;
free(_arr);
_arr = nullptr;
_capacity = _top = 0;
}
private:
int* _arr;
int _capacity;
int _top;
};
class MyQueue
{
private:
Stack pushst;
Stack popst;
};
//测试
int main()
{
//实例化对象
Stack st1;
st1.StackPush(1);
st1.StackPush(2);
st1.StackPush(3);
st1.Print();
int a = 0;
//在st1析构前调用拷贝构造
//使用st1拷贝st2
Stack st2(st1);
//也可以写成
Stack st3 = st2;
st2.Print();
st3.Print();
/*自动生成的拷贝构造调用Stak里面的拷贝构造*/
MyQueue my1;
MyQueue my2 = my1;
return 0;
}
我将赋值运算符重载和取地址运算符重载放在下一节,同时有一个日期类的实现作为练习。看没看懂都点个赞呗。