目录
类的6个默认成员函数
初始化和清理
1.构造函数
2.析构函数
3.共同点
拷贝复制
1.拷贝构造
使用细节
2.赋值重载
运算符重载
== <= < >= > !=
连续赋值
C++入门 第一篇(C++关键字, 命名空间,C++输入&输出)-CSDN博客
C++入门 第二篇( 引用、内联函数、auto关键字、指针空值nullptr)-CSDN博客
【C++】类与对象 第一篇(class,this)-CSDN博客
类的6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
初始化和清理
1.构造函数
特征: 构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。 其特征如下:
-
函数名与类名相同。
-
无返回值。
-
对象实例化时编译器自动调用对应的构造函数。
-
构造函数可以重载。
class Stack{public:Stack(){_a=nullptr;_size=_capacity=0;}Stack(int n){_a=(int*)malloc(sizeof(int)*n);_size=_capacity=0;}void Init(int n=4){_a=(int*)malloc(sizeof(int)*n);if (nullptr==_a){perror("malloc is fail");return;}_capacity=n;_size=0;}void Push(int x){//...a[_size++]=x;}//...void Dstory(){//...}private:int _a;int _size;int _capacity;};int main(){Stack st;//无参//Stack st();//有参st.Push(1);st.Push(2);st.Push(3);st.Push(4);st.Dstory();return 0;}
以上述代码为例:
自动调用初始化
注意:调用无参时如:Data d;此处在后面不能➕(),否则编译器会调用有参的。
使用缺省值:
class Date{public:Date(int year=1,int month=1,int day=1){_year=1;_month=1;_day=1;}void print(){cout<<_year<<"/"<<_month<<"/"<<_day<<endl;}private://成员变量int _year;int _month;int _day;};int main(){//Date d1;Date d2(2077,2,3);d2.print();return 0;}
2.析构函数
概念: 通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的? 析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特性 析构函数是特殊的成员函数,其特征如下:
-
析构函数名是在类名前加上字符 ~。
-
无参数无返回值类型。
-
一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载
-
对象生命周期结束时,C++编译系统系统自动调用析构函数。
例子:
typedef int DataType;class Stack{public:Stack(size_t capacity = 3){_array = (DataType *)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:DataType *_array;int _capacity;int _size;};void TestStack(){Stack s;s.Push(1);s.Push(2);}
3.共同点
如果编译过程不写,那编译器会自动生成一个默认的,但是如果我们实现了任意一个,编译器就不会生成了。
若是自动初始化,那为什么这个地方会生成随机值呢?
C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。
内置类型(基本类型):int/char/double... /任意类型指针 自定义类型:class/structd定义的
默认生成构造函数:
1.内置类型成员不做处理。
2.自定义类型的成员,会去调用它的默认构造(不用传参数的构造)
private:// 基本类型/内置类型 - 不进初始化int _year;int _month;int _day;
实用场景:
class Date{public://内置类型成员不做处理void print(){cout<<_year<<"/"<<_month<<"/"<<_day<<endl;}private:// 基本类型/内置类型 - 不进初始化int _year;int _month;int _day;};class MyQueue{// 默认生成构造函数,对自定义类型,会调用它的默认构造函数void push(int x);{}//...Stack _pushST();Stack _popST();};
析构:
默认生成构造函数:
1.内置类型成员不做处理。
2.自定义类型的成员,会去调用它的析构函数
class MyQueue{// 默认生成析构函数,对自定义类型,会调用它的析构函数void push(int x);{}Stack _pushST();Stack _popST();};int main(){//Date d1;Date d1;d1.print();MyQueue q;return 0;}
内置类型并不会主动初始化,但可以通过给缺省值进行初始化:
private://声明位置给缺省值int _year=1;int _month=1;int _day=1;
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为 是默认构造函数。
拷贝复制
1.拷贝构造
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
拷贝构造:内置类型,编译器直接拷贝,自定义类型拷贝需要调用拷贝构造
特征
拷贝构造函数也是特殊的成员函数,其特征如下:
-
拷贝构造函数是构造函数的一个重载形式。
(
为什么自定义类型要用拷贝构造,而内置类型编译器却可以直接拷贝(按字节拷贝)? 因为自定义类型进行拷贝容易出问题:若要拷贝栈,栈的成员变量都是指向相同的空间,若进行拷贝,则会导致两个Stack指向同一个空间,导致原本Stack执行析构函数时被销毁,而复制的那个则指向空,正确的应该是各有各的空间。
)
-
拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
class Date{public:Date(int year=2077, int month=10, int day=12){_year = year;_month = month;_day = day;}Date(Date d)//这样拷贝会造成无限递归{_year = d.year;_month = d.month;_day = d.day;}private:int _year;int _month;int _day;};
在拷贝构造函数 Date(Date d)
中,参数 d
是按值传递的,这意味着每次调用拷贝构造函数时都会创建一个新的 Date
对象,并将原始对象 d
复制到新的对象中。然而,在拷贝构造函数内部,对于拷贝构造函数的调用又会传递同样的参数 d
,导致不断地递归调用拷贝构造函数,从而产生无限递归。
则可以使用 & ,使其不在重新创建空间并使用原Date对象
Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}
这个拷贝构造函数将创建一个新的 Date
对象,并将原始对象的 _year
、_month
和 _day
成员变量的值分别拷贝到新对象的相应成员变量中。通过使用对象引用作为参数,我们可以避免无限递归调用拷贝构造函数的问题,并且确保在构造新对象时不会复制整个对象
class Date{public:Date(int year = 2077, int month = 10, int day = 12){_year = year;_month = month;_day = day;}Date(const Date& d)//const是为了防止写反->d._year = _year;加const缩小权限{cout<<"Date(Date& d);"<<endl;_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;};int main(){Date d1(2023, 2, 3);//两种拷贝方式Date d2(d1); // 使用拷贝构造函数创建对象 d2,并将 d1 的值拷贝到 d2 中Date d3=d1;return 0;}
RunCode:
若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
深拷贝是指在对象拷贝时,复制所有的数据和资源,使得新对象和原对象完全独立,互不影响。与之相对的是浅拷贝,浅拷贝只复制指针或引用,导致新旧对象共享同一份数据,修改一个可能会影响另一个。深拷贝能够保证对象之间的独立性和数据完整性。
分清楚是不是拷贝构造:
class Date{public:Date(int year = 2077, int month = 10, int day = 12){_year = year;_month = month;_day = day;}//拷贝构造函数 Date(const Date& d){cout<<"Date(Date& d);"<<endl;_year = d._year;_month = d._month;_day = d._day;}//构造函数 不是拷贝构造Date(const Date* d){cout<<"Date(Date& d);"<<endl;_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;};int main(){Date d1(2023, 2, 3);//Date(const Date* d)Date d2(&d1); Date d3=&d1;return 0;}
拷贝构造函数典型调用场景:
-
使用已存在对象创建新对象
-
函数参数类型为类类型对象
-
函数返回值类型为类类型对象
使用细节
1使用拷贝构造时最好加上const,以防权限的放大
// 拷贝构造Date(const Date &d){_year = d.year;_month = d.month;_year = d.year;}
2编译器可自动生成但存在问题
本文所用的日期类可以使用自动生成拷贝,但进行拷贝栈时就会出现问题
当进行拷贝时,用值拷贝,导致两个栈指向同一个空间,造成空间互相覆盖 指向同一块空间的问题:1.插入删除数据会互相影响 2.析构两次,程序崩溃
析构是也跟栈一样是后进先出,后定义的先析构,故st2先析构,对st2滞空是,并不影响st1,导致野指针。此时浅拷贝行不通,需要进行深拷贝:
代码实现:
class Stack{public:void Push(const int &data){_array[_size] = data;_size++;}Stack(const Stack &st){_array = (int *)malloc(sizeof(int) * st._capacity);if (nullptr == _array){perror("malloc is fail");exit(-1);}memcpy(_array, st._array, sizeof(int) * st._size);_size = st._size;_capacity = st._capacity;}~Stack(){if (_array){delete[] _array;_array = nullptr;_capacity = 0;_size = 0;}}private:int *_array;int _size;int _capacity;};int main(){Stack st1;st1.Push(1);st1.Push(2);st1.Push(3);Stack st2(st1);return 0;}
什么情况下需要事项拷贝构造呢? 自己实现了析构释放空间,就需要实现拷贝构造
class Stack{public://构造函数 Stack(size_t _capacity = 10){cout << "Stack(size_t _capacity)" << endl;_array = (int *)malloc(sizeof(int) * _capacity);if (nullptr == _array){perror("malloc is fail");exit(-1);}_size = 0;_capacity = _capacity;}void Push(const int &data){_array[_size] = data;_size++;}//拷贝构造Stack(const Stack &st){_array = (int *)malloc(sizeof(int) * st._capacity);if (nullptr == _array){perror("malloc is fail");exit(-1);}memcpy(_array, st._array, sizeof(int) * st._size);_size = st._size;_capacity = st._capacity;}~Stack(){if (_array){delete[] _array;_array = nullptr;_capacity = 0;_size = 0;}}private:int *_array;int _size;int _capacity;};class MyQueue{//默认生成构造 ->生成定义在Stack中的默认构造函数//默认生成析构//默认生成的拷贝构造private:Stack _pushST;Stack _popST;int _size = 0;};int main(){Stack st1;st1.Push(1);st1.Push(2);st1.Push(3);Stack st2(st1);cout<<"====="<<endl;MyQueue q;return 0;}
默认生成拷贝构造和赋值重载:
a.内置类型完成 浅/值 拷贝--按byte一个一个拷贝
b.自定义类型,去调用这个成员 拷贝构造/赋值重载
2.赋值重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。 函数原型:返回值类型 operator操作符(参数列表)
自定义类型不能直接使用运算操作符 内置类型是语法定义,而自定义类型为人为定义,编译器并不知道该如何进行比较,这便出现了operator用函数完成
运算符重载:自定义类型对象可以使用运算符
函数重载:支持函数名相同,参数不同的函数
#include <iostream>using namespace std;class Date{public:// 构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 拷贝构造Date(const Date &d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}// d1==d2 -> d1.operator==(d2)bool operator==(const Date &d){return _year == d._year && _month == d._month && _day == d._day;// this->_year==d.year}private:// 成员变量int _year;int _month;int _day;};int main(){Date d1(2077, 2, 4);Date d2(2077, 2, 4);// d1==d2;cout << d1.operator==(d2) << endl;cout << (d1 == d2) << endl;return 0;}
注意:
-
不能通过连接其他符号来创建新的操作符:比如operator
-
重载操作符必须有一个类类型参数
-
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
-
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this现。
-
注意以下5个运算符不能重载
.* :: sizeof ?: .
== <= < >= > !=
// d1==d2 -> d1.operator==(d2)bool operator==(const Date &d){return _year == d._year && _month == d._month && _day == d._day;// this->_year==d.year}// bool operator<(const Date &d)// {// if (_year < d._year)// {// return true;// }// else if (_year == d._year && _month < d._month)// {// return true;// }// else if (_year == d._year && _month == d._month && _day < d._day)// {// return true;// }// else// return false;// }// d1<d2bool operator<(const Date &d){return _year < d._year && (_year == d._year && _month < d._month) && (_year == d._year && _month == d._month && _day < d._day);}
任何一个类都适用于,当写了一个>=或<=那其他的就可以进行复用
// d1<=d2bool operator<=(const Date &d){return *this < d || *this == d;//*this就是d1}
这个地方对operator<(const Date &d)
进行了复用。operator<=(const Date &d)
中的表达式*this < d
调用了operator<(const Date &d)
来判断两个日期对象的大小关系。同时,operator<=(const Date &d)
还利用operator==(const Date &d)
来判断两个日期对象是否相等。通过这样的复用方式,可以简化代码并提高代码的可读性
// d1>d2bool operator>(const Date &d){return !(*this<= d);}//d1>=d2bool operator>=(const Date& d){return !(*this<d);}//d1!=d2bool operator!=(const Date& d){return !(*this ==d);}
当然赋值运算符不使用&并不会无限循环
// d1<=d2bool operator<=(const Date d){return *this < d || *this == d;}
当然可以不用,但是最好还是加上
连续赋值
d3 = d1 = d2
// 因为Date operator=(const Date &d) 出了该函数的作用域*this/d1 还在此时返回的*this是临时拷贝,需要进行拷贝重新开空间造成浪费,不如直接进行&Date operator=(const Date &d)//&d 引用{if (this != &d)//防止d1=d1进行赋值 此处的&为去地址 ,若this与d的地址一样则无需赋值{_year = d._year;_month = d._month;_day = d._day;}return *this; //*this就是d1} // 返回值为了支持连续赋值,保持运算符特性 d3=d1=d2;
当然要注意,赋值重载是针对已经创造的对象
Date d5=d1;//拷贝构造
像这样就是拷贝构造
同样作为默认成员函数,可以不用手动写
源码:
#include <iostream>using namespace std;class Date{public:// 构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 拷贝构造Date(const Date &d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}// d1==d2 -> d1.operator==(d2)bool operator==(const Date &d){return _year == d._year && _month == d._month && _day == d._day;// this->_year==d.year}// d1<d2bool operator<(const Date &d){return _year < d._year && (_year == d._year && _month < d._month) && (_year == d._year && _month == d._month && _day < d._day);}// d1<=d2bool operator<=(const Date &d){return *this < d || *this == d; //*this就是d1}// d1>d2bool operator>(const Date &d){return !(*this <= d);}// d1>=d2bool operator>=(const Date &d){return !(*this < d);}// d1!=d2bool operator!=(const Date &d){return !(*this == d);}// // 因为Date operator=(const Date &d) 出了该函数的作用域*this/d1 还在此时返回的*this是临时拷贝,需要进行拷贝重新开空间造成浪费,不如直接进行&// Date operator=(const Date &d)//&d 引用// {// if (this != &d)//防止d1=d1进行赋值 此处的&为去地址 ,若this与d的地址一样则无需赋值// {// _year = d._year;// _month = d._month;// _day = d._day;// }// return *this; //*this就是d1// } // 返回值为了支持连续赋值,保持运算符特性 d3=d1=d2;private:// 成员变量int _year;int _month;int _day;};int main(){Date d1(2077, 2, 4);Date d2(2222, 3, 2);Date d3(2078, 2, 4);cout << (d1 < d2) << endl;// d3 = d1 = d2;d1 = d2;d1.Print();return 0;}