在上一篇博客中学习了一些类和对象的基础,下面让我们一起来看看这部分比较难以理解的重点部分吧.在中篇我主要学习了默认成员函数以及其中包含的运算符重载.在这篇中主要分享下默认成员函数的前三个.赋值函数以及其中包含的运算符重载的知识见下.
类和对象的默认成员函数
默认成员函数就是指在一个类中,就算用户没有显示实现,编译器也会自动生成的成员函数.在一个类中,编译器会默认生成6个成员函数.分别是构造函数,析构函数,拷贝构造函数,赋值函数,取地址重载.在这里我只重点学习了前四个.
我们主要去了解一下两方面
1.在我们自己不去是现实时编译器会默认生成怎样的函数,能否满足我们的需求
2.如果编译器默认的不满足我们需求那我们要如何自己生成
构造函数
首先我们来看一下构造函数,说是构造其实并不准确,他的作用主要是初始化类中的成员.和之前我们写的Init函数作用类似.下面我们来看下构造函数的特点.
构造函数特点
1.函数名与类名相同.
2.无返回值且不写void.
3.可进行重载.
4.在对象实例化时会自动调用对应的构造函数.
5.如果用户未显示实现则编译器会自动生成构造函数,如果用户显示实现了编译器则不在自动生成了.
6.当我们自己不写时编译器自动生成的构造函数.对内置类型不做处理,对于自定义类型则调用自定义类型对应的默认构造函数进行初始化,(也可能有的编译器会对内置类型作处理,但那是个别的编译器行为,不可以当作普遍性来处理).如果类里面的自定义类型无默认构造那么编译就会报错,(我们可以用初始化列表去解决,这个内容将在之后提到).(就算自己写了,类中的内置类型还是会先调用默认构造然后在进行赋值操作)
7.来补充一下上面提到的默认构造函数:由无参构造函数,编译器默认生成的那个构造函数和全缺省构造函数组成.可以简单概括为不传实参的构造函数就是默认构造函数.但是这三个函数只能有一个存在,不能同时存在.虽然无参构造与全缺省构造可以构成函数重载,但是在调用时会产生调用歧义.
所以构造函数一般都需要显示实现,只有在及特殊的条件下才可以直接用编译器自动生成的,例如经典习题用两个栈来实现队列中的队列这个类就可以用自动生成的构造函数.
下面是一些有关的代码练习
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
//显示实现---有参数全缺省
class Date
{
public:
Date(int year = 2025, int month = 3, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "." << _month << "." << _day << "." << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2024, 5, 15);
d1.Print();
d2.Print();
return 0;
}
//
//显示实现---无参数
//class Date
//{
//public:
// Date()
// {
// _year = 2025;
// _month = 3;
// _day = 9;
// }
// void Print()
// {
// cout << _year << "." << _month << "." << _day << "." << endl;
// }
//private:
// int _year;
// int _month;
// int _day;
//};
//
//int main()
//{
// Date d1;
// d1.Print();
// return 0;
//}
//不现实实现-------内置随机值
//class Date
//{
//public:
// /*Date(int year = 2025, int month = 3, int day = 9)
// {
// _year = year;
// _month = month;
// _day = day;
// }*/
// void Print()
// {
// cout << _year << "." << _month << "." << _day << "." << endl;
// }
//private:
// int _year;
// int _month;
// int _day;
//};
//
//int main()
//{
// Date d1;
// //Date d2(2024, 5, 15);
// d1.Print();
// //d2.Print();
// return 0;
//}
//
//不现实实现-------内置随机值自定义调用默认构造
//class A
//{
//public:
// A(int a = 1)
// {
// _a = a;
// }
// void Print()
// {
// cout << _a << endl;
// }
//private:
// int _a;
//};
//class Date
//{
//public:
// /*Date(int year = 2025, int month = 3, int day = 9)
// {
// _year = year;
// _month = month;
// _day = day;
// }*/
// void Print()
// {
// cout << _year << "." << _month << "." << _day << "." << endl;
// _a.Print();
// }
//private:
// int _year;
// int _month;
// int _day;
// A _a;
//};
//
//int main()
//{
// Date d1;
// Date d2(2024, 5, 15);
// d1.Print();
// d2.Print();
// return 0;
//}
//现实实现-------自定义调用相匹配的构造
//class A
//{
//public:
// A()
// {
// _a = 1;
// }
// A(int a)
// {
// _a = a;
// }
// void Print()
// {
// cout << _a << endl;
// }
//private:
// int _a;
//};
//class Date
//{
//public:
// Date(A a1, int year = 2025, int month = 3, int day = 9)
// {
// _year = year;
// _month = month;
// _day = day;
// _a = a1;
// }
// void Print()
// {
// cout << _year << "." << _month << "." << _day << "." << endl;
// _a.Print();
// }
//private:
// int _year;
// int _month;
// int _day;
// A _a;
//};
//
//int main()
//{
// A a1(3);
// Date d2(a1,2024, 5, 15);
// d2.Print();
// return 0;
//}
析构函数
与构造函数相反,析构函数做的是一些资源的清理工作(主要是释放开辟出来的空间),相当于我们之前写的Destroy.下面让我们看下析构函数的特点.
析构函数的特点:
1 .函数名是类名前面加~.
2.无返回值也无参数(不用写void).
3.在对象生命周期结束时自动调用.
4.我们不显示实现构造函数的话编译器会自动生成,但是我们显示实现了编译器就不再会生成.
5.编译器自动生成的析构函数,对内置变量不做处理,对自定义变量调用他们的析构函数.要注意我们写了析构函数,对于自定义类型也会调用他们的析构,也就是说自定义成员无论什么情况都会自动调用析构.
6.如果没有申请资源时可以不写析构函数,直接用默认生成的,如Date,要是类中没有内置变量或内置变量没有申请空间,也可以直接用默认生成的,如两个栈建队列中的队列.有资源申请时一定要自己写析构.
7.一个局部域的对象,c++规定后定义的先析构.
一点有关的代码练习:
typedef int stdatatype;
class stack
{
public:
stack(int n = 4)
{
_capcity = n;
_top = 0;
_st = (stdatatype*)malloc(sizeof(stdatatype) * n);
if (_st == nullptr)
{
cout << "初始化失败" << endl;
}
}
~stack()
{
free(_st);
_capcity = 0;
_top = 0;
cout << "~stack()" << endl;
}
private:
int _capcity;
int _top;
stdatatype* _st;
};
int main()
{
stack st;
return 0;
}
有了构造函数和析构函数后可以有效的缓解C语言中忘记调用Init导致的没有给初始空间就插入以至于程序崩溃和忘记调用Destroy导致内存泄漏的问题.
拷贝构造函数
拷贝构造是一种特殊的构造函数,他的第一个参数是自身类型的引用,且额外的参数都有默认的数值.下面看一下拷贝函数的特点.
拷贝函数的特点:
1.拷贝构造是构造函数的一个重载.
2.第一个参数必须是自身类型的引用,(因为使用传值的方式会造成无穷递归报错.因为在对象传值时会自动调用拷贝构造).拷贝构造也可以有多个参数,但后面的参数必须要有缺省值.
3.c++规定自定义类型对象进行拷贝一定要调用拷贝构造,所以在自定义对象进行传值传参和传值返回时都要用拷贝构造.
4.若用户没有显示实现拷贝构造,编译器会自动生成拷贝构造函数.但编译器自动生成的拷贝构造对内置类型是一个字节一个字节的拷贝,就是浅拷贝,对于自定义类型的拷贝是调用他的拷贝构造.
5.因为编译器默认生成的是浅拷贝,所以若是将编译器默认生成的拷贝函数用在如Stack这种申请了空间并由内置类型来指向这块空间的类的对象上,就会出现同一块空间被多个对象操控的现象.所以例如Stack这种不可以用编译器默认生成的拷贝构造.而未申请空间的类,如Date以及类里面除自定义类型之外没有其他内置类型申请空间的类,例如两个栈建队列中的队列则可以用编译器默认生成的拷贝构造.
6.传值返回会产生一个临时对象的拷贝构造,穿引用返回返回的则是对象的别名,不产生拷贝.但如果返回对象是当前函数局部域的局部对象,函数结束对象会被销毁,返回的引用会变成类似于野指针的东西.引用返回可以减少拷贝但是要保证返回对象在当前函数结束后还在,才可以用引用返回.
补:在这里突然想起了一点关于引用权限的小问题,详情可以看看这篇文章哇.c++引用中的权限问题-优快云博客
下面是一点相关练习:
//栈的深拷贝
typedef int StDataType;
class Stack
{
public:
Stack(int n = 4)
{
_capcity = n;
_top = 0;
_st = (StDataType*)malloc(sizeof(StDataType) * n);
if (_st == nullptr)
{
cout << "初始化失败" << endl;
}
}
Stack(const Stack& st)
{
_capcity = st._capcity;
_top = st._top;
_st = (StDataType*)malloc(sizeof(StDataType) * _capcity);
if (_st == nullptr)
{
cout << "初始化失败" << endl;
}
memcpy(_st, st._st, sizeof(StDataType) * _capcity);
}
void Push(StDataType a)
{
if (_top == _capcity)
{
int newcapcity = _capcity * 2;
StDataType * ptr= (StDataType*)malloc(sizeof(StDataType) * newcapcity);
if (ptr == nullptr)
{
cout << "扩容失败" << endl;
}
_st = ptr;
_capcity = newcapcity;
}
_st[_top] = a;
_top++;
}
bool Empty()
{
return _top == 0;
}
StDataType Top()
{
if (Empty())
{
cout << "栈空" << endl;
}
return _st[_top - 1];
}
void Pop()
{
if (Empty())
{
cout << "栈空" << endl;
}
_top--;
}
~Stack()
{
free(_st);
_capcity = 0;
_top = 0;
cout << "~Stack()" << endl;
}
private:
int _capcity;
int _top;
StDataType* _st;
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(3);
st1.Push(5);
//Stack st2 = st1;
Stack st2(st1);
cout << st1.Top() << " ";
st1.Pop();
cout << st1.Top() << " ";
st1.Pop();
cout << st1.Top() <<endl;
st1.Pop();
cout << st2.Top() << " ";
st2.Pop();
cout << st2.Top() << " ";
st2.Pop();
cout << st2.Top() << endl;
st2.Pop();
return 0;
}
//日期类---可以不自己写拷贝
class Date
{
public:
Date(int year = 2025, int month = 3, int day = 9)
{
_day = day;
_month = month;
_year = year;
}
Date(Date& d)
{
_day = d._day;
_month = d._month;
_year = d._year;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
int main()
{
Date d1;
d1.Print();
Date d2 = d1;
//Date d2(d1);
return 0;
}