空类真的是空的吗,并不是!
本期概览:
类的五个默认成员函数
空类中真的如我们看到的一样,真的是空的吗?
其实不然,里边还有五个默认生成的成员函数(如果自己写了对应功能的函数,编译器就不会自动生成;没写就会自动生成)
初始化、销毁等等,总是容易忘记,干脆搞个默认的!
对于编译器来说,内置类型是很熟悉,能轻松初始化、赋值、拷贝,但是复杂的自定义类型它可搞不定,需要一个函数来操作。
是用来初始化对象的成员函数。
最终也会走到内置类型这一步啊,那么…
C++打了个补丁:内置类型成员在声明的时候可以给缺省值。
注意,这不是初始化,只是赋初值——定义后才给的缺省值,初始化是定义时赋值。
class Date
{
public:
//...
private:
int _year = 1970;
int _month = 1;
int _day = 1;
};
需要特别强调,默认构造函数并不特指编译器自动生成的构造函数,而指不用传参的,以下几种都叫做默认构造函数:
注意:默认构造函数只能有一个(避免二义性)
class Date
{
public:
void ShowDate()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
private:
//在声明的时候给缺省值
int _year = 1970;
int _month = 1;
int _day = 1;
};
int main()
{
Date d1;
d1.ShowDate();
return 0;
}
:1970-1-1
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void ShowDate()
{
cout << _year << '-' << _month << '-' << _day << endl;
if (_month == 10 && _day == 1)
cout << "祖国万岁!" << endl;
}
private:
int _year = 1970;
int _month = 1;
int _day = 1;
};
int main()
{
//Date d1(2022, 1, 1);
Date d1;
d1.ShowDate();
return 0;
}
:2022-10-1
祖国万岁!
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void ShowDate()
{
cout << _year << '-' << _month << '-' << _day << endl;
if (_month == 10 && _day == 1)
cout << "祖国万岁!\n" << endl;
}
private:
int _year ;
int _month;
int _day;
};
int main()
{
Date d1(2022, 10, 1);
d1.ShowDate();
return 0;
}
:2022-10-1
祖国万岁!
直接对指针/数组赋值达不到效果,指向同一块空间不好玩了。
是用来清理对象资源的成员函数。(如释放空间)
编译器自动生成的默认析构函数(无法调试看见或打印信息)
自定义的析构函数
class DynamicArr
{
public:
DynamicArr(int capacity = 4)
{
cout << "DynamicArr(int capacity = 4)" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
assert(_a);
_capacity = capacity;
}
void Push()
{
//...
}
~DynamicArr()
{
cout << "~DynamicArr()" << endl;
free(_a);
_a = NULL;
_capacity = _size = 0;
}
private:
void CheckCapacity()
{
//...
}
int* _a = NULL;
int _size = 0;
int _capacity = 0;
};
int main()
{
DynamicArr a;
return 0;
}
:DynamicArr(int capacity = 4)
~DynamicArr()
拷贝别人用于初始化自己的函数——用一个已存在对象给同类型新对象初始化。
我们知道,新对象实例化的时候会调用构造,但是在此基础上,我们如果想用已存在对象的成员给新对象初始化,就可以用拷贝构造
:不需要修改用来拷贝的已存在对象,所以加const;引用减少拷贝,提高效率,且传值传参会无穷调用!
:调用拷贝构造,传参的时候又触发调用拷贝构造,无穷调用
当用已存在对象,给新对象初始化的时候调用
如
这里涉及到浅拷贝和深拷贝,像之前的复制带随机指针的链表,就是深拷贝,开辟了空间的一类都需要深拷贝。
如果需要深拷贝的用了浅拷贝:
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
原因是s2的 _array被按字节拷贝成了s1的_array,两指针指向同一块空间,于是析构s1、s2的时候对同一块空间释放两次,自然崩溃。
对于这样的日期类
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
:Date(const Date& d)
void test(Date d)
{}
int main()
{
Date d;
test(d);
return 0;
}
:Date(const Date& d)
Date test()
{
Date d;
return d;//返回了局部对象,不合理的代码,此处仅示范
}
int main()
{
Date d = test();
return 0;
}
:Date(const Date& d)
诶?奇怪,为什么这里只调用了一次拷贝构造?
对于紧贴着连续调用的
部分编译器会直接合并,用内置类型来理解:
int a = 10, b, c;
//不合并
b = a;//a拷贝给b
c = b;//b拷贝给c
//合并
c = a;//a直接拷贝给c
这样的场景有:
一句话总结就是
连续出现的拷贝构造会合并。
想要学习赋值运算符重载,我们得先了解什么是运算符重载。
拥有特殊函数名(operator + [运算符])的函数。
Date& operator+=(const Date& d2);//Date类的加号运算符重载
*operator是C++中的关键字,用于运算符重载。
直接对对象使用即可。(可读性高)
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
//全局的operator==
bool operator==(const Date& d1, const Date& d2)
{
//在类外无法访问私有
//1. 可以直接重载成成员函数(类内可以访问)
//2. 也可以将此函数声明成友元(后面会讲)
//此处为了简单演示,直接把私有的权限放开了
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
Date d1(2022, 1, 1);
Date d2(2022, 1, 2);
//这里要注意,==的优先级低于<<
//cout << d1 == d2 << endl;//wrong
cout << (d1 == d2) << endl;
return 0;
}
:0
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 1, 1);
Date d2(2022, 1, 1);
cout << (d1 == d2) << endl;
return 0;
}
:1
用同类的已存在对象,给另一已存在对象赋值的函数(运算符重载)
int a;
int b = a = 10;//没有返回值则无法正常使用
d1 == d2;
d1.operator==(d2);
二者相等,后者可以更好地理解:运算符重载的本质是函数
按字节拷贝
int main()
{
Date d1(2022, 1, 1);
Date d2(2022, 1, 2);
cout << (d1 == d2) << endl;
cout << (d1.operator==(d2)) << endl;
return 0;
}
:0
0
取地址运算符重载分为 取地址运算符重载 和 const对象取地址运算符重载
是对对象取地址的时候调用的函数。
基本不用自己写,除非有这样的需求:让类的用户取地址时取到你指定的东西
是对const对象取地址的时候调用的函数
基本不用自己写,除非有这样的需求:让类的用户取地址时取到你指定的东西
越学越感觉有很多nb的玩法等待挖掘,C++…
今天的分享就到这里啦,这里是培根的blog,期待与你共同进步!