当前位置: 首页 > news >正文

建瓯网站制作网站开发架构

建瓯网站制作,网站开发架构,盘锦做网站选哪家,阿里云服务器可以做下载类网站吗1.类的6个默认成员函数 如果一个类中什么成员都没有#xff0c;简称为空类。 空类中真的什么都没有吗#xff1f;并不是#xff0c;任何类在什么都不写时#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数#xff1a;用户没有显式实现#xff0c;编译器会…1.类的6个默认成员函数 如果一个类中什么成员都没有简称为空类。 空类中真的什么都没有吗并不是任何类在什么都不写时编译器会自动生成以下6个默认成员函数。 默认成员函数用户没有显式实现编译器会生成的成员函数称为默认成员函数。 默认成员函数是一种特殊成员函数 ​ 1**.我们不写编译器会自己生成一个我们写了编译器就不会生成。例如构造函数析构函数** ​ 2.隐含意思对于有些类需要我们自己写对于另一些类编译器默认生成就可以用。 class Date {};2. 构造函数 2.1 概念 对于下面的Date类 class Date { public:/*void Init(int year, int month, int day) // 普通的初始化函数{_year year;_month month;_day day;}*/// 初始化构造函数// 1.带参构造函数// 可以使用缺省使用缺省参数就可以不用传参Date(int year 1, int month 1, int day 1) {_year year;_month month;_day day;}/*// 构造函数可以重载但是对于这两个构造函数如果不传参数会造成二义性(因为两个构造函数都可以不用进行传参但是语法上这么写是正确的但是函数调用时会有二义性)// 2.无参构造函数Date() {_year 1;_month 1;_day 1;}*/void Print(){cout _year - _month - _day endl;}private:int _year; // 年int _month; // 月int _day; // 日 };int main() {Date d1(2022, 9, 21); // 给构造函数传参在创建的对象后写需要传递的参数Date d2(2022, 9, 21);Date d3; // 不传参的形式// 无参的不要像下面这样写// Date d4();// Date func(); // 编译器无法判断func()是函数还是对象d1.Print();d2.Print();d3.Print();return 0; }对于Date类可以通过 Init 公有方法给对象设置日期但如果每次创建对象时都调用该方法设置信息未免有点麻烦那能否在对象创建时就将信息设置进去呢 构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次。 2.2 特性 构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是初始化对象。 其特征如下 函数名与类名相同。 无返回值。 对象实例化时编译器自动调用对应的构造函数。 构造函数可以重载。 class Date { public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year year;_month month;_day day;} private:int _year;int _month;int _day; };void TestDate() {// 调用无参构造函数Date d1; // 调用带参的构造函数Date d2(2015, 1, 1); // 注意如果通过无参构造函数创建对象时对象后面不用跟括号否则就成了函数声明// 以下代码的函数声明了d3函数并不是d3对象该函数无参返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Date d3(); // 错误演示 }int main() {TestDate();return 0; }// case1:class Stack { public:// 构造函数Stack(int capacity 4) {_a (int*)malloc(sizeof(int)*capacity);if (_a nullptr){perror(malloc fail);exit(-1);}_top 0;_capacity capacity;}~Stack() // 析构函数{free(_a);_a nullptr;_top _capacity 0;}void Push(int x){// ....// 扩容_a[_top] x;}private:int* _a;int _top;int _capacity; };int main() {// 有了构造函数和析构函数我们不在需要主动对创建的对象进行初始化 而对象在销毁时会自动调用析构函数完成对象中资源的清理工作和Destory的功能类似。Stack st; st.Push(1);st.Push(2);st.Push(3);return 0; }如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。 class Date { public:/*// 如果用户显式定义了构造函数编译器将不再生成Date(int year, int month, int day){_year year;_month month;_day day;}*/void Print(){cout _year - _month - _day endl;}private:int _year;int _month;int _day; };int main() {// 将Date类中我们自己定义的构造函数屏蔽后代码可以通过编译因为编译器生成了一个无参的默认构造函数// 将Date类中我们自己定义构造函数放开则不会生成默认构造函数代码编译失败(由于自己写的构造函数需要传参但Date d1并没有传参因此匹配不到合适的构造函数)因为一旦显式定义任何构造函数编译器将不再生成// 无参构造函数放开后报错error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0; }关于编译器生成的默认成员函数很多人会有疑惑不实现构造函数的情况下编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用d对象调用了编译器生成的默认构造函数但是d对象year/_month/_day 依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用 解答C把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型如int/char...自定义类型就是我们使用class/struct/union等自己定义的类型看看下面的程序就会发现编译器生成默认的构造函数会对自定类型成员 _t 调用的它的默认构造函数。 // case1:编译器生成默认的构造函数会对自定类型成员 _t 调用的它的默认构造函数class Time { public:// 内置类型需要自己定义构造函数Time(){cout Time() endl;_hour 0;_minute 0;_second 0;}// 内置类型 默认构造函数对其不进行处理因此需要我们自己来写构造函数 private:int _hour;int _minute;int _second; };class Date { private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型类名为Time的自定义类型不需要我们来写构造函数编译器会直接调用默认构造函数// 但是内置类型又无法处理// 因此需要用到初始化列表 类和对象下篇再解释初始化列表Time _t; };int main() {Date d;return 0; }// case2:编译器生成默认的构造函数会对自定类型成员_pushST,_popST调用的它的默认构造函数class Stack { public:// 自己创建的默认构造函数Stack(int capacity 4) {cout Stack(int capacity 4) endl;_a (int*)malloc(sizeof(int)*capacity);if (_a nullptr){perror(malloc fail);exit(-1);}_top 0;_capacity capacity;}~Stack() // 自己创建的析构函数{cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}void Push(int x){// ....// 扩容_a[_top] x;}private:int* _a;int _top;int _capacity; };class MyQueue { public:void push(int x){_pushST.Push(x);}Stack _pushST;Stack _popST;//size_t _size; 如果myQueue还存在一个内置类型又该怎么办呢 };// 面向需求编译器默认生成就可以满足就不用自己写不满足就需要自己写// Stack的构造函数需要自己写// MyQueue构造函数就不需要自己写默认生成就可以用// Stack的析构函数需要我们自己写// MyQueue 就不需要自己写析构函数默认生成就可以用int main() {MyQueue mq;mq.push(1);mq.push(2);return 0; }注意C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在类中声明时可以给默认值。 class Time { public:Time(){cout Time() endl;_hour 0;_minute 0;_second 0;}private:int _hour;int _minute;int _second; };class Date { private:// 基本类型(内置类型) //此处为内置类型内置类型成员变量在类中声明时可以给默认值则默认构造函数可以不用考虑内置类型int _year 1970;int _month 1;int _day 1;// 自定义类型 内置类型已经给了缺省值因此默认构造函数不用考虑内置类型Time _t; };int main() {Date d;return 0; } 再例如 class Stack { public:/*// 如果构造函数的参数不是全缺省值则不是默认构造函数编译器会报错// 在class MyQueue中会调用Stack初始化Stack _pushST;Stack _popST 但是Stack的构造函数不是全缺省值所以会报错Stack(int capacity) {cout Stack(int capacity 4) endl;_a (int*)malloc(sizeof(int)*capacity);if (_a nullptr){perror(malloc fail);exit(-1);}_top 0;_capacity capacity;}*/// 析构函数~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}// 再声明内置类型时给到缺省值则相当于默认构造 private:int* _a nullptr;int _top 0;int _capacity 0;/*// 只有创建对象之后才会调用默认构造函数才会malloc空间// 下面这种方法无法检测malloc是否成功开辟空间int* _a (int*)malloc(sizeof(int)*4); int _top 0;int _capacity 4;*/ };// 用两个栈来实现一个队列 class MyQueue { public:/*// 初始化列表 -- 类和对象下篇再讲MyQueue(size_t capacity 8):_popST(8), _pushST(8), _size(0){_size 0;}*/void push(int x){//_pushST.Push(x);}private:// 调用Stack的默认构造函数来初始化Stack _pushST;Stack _popST;// 下面这种在定义时给缺省值的方式不建议建议使用初始化列表后续讲解// 这里不是初始化给的缺省值但并不会开辟空间则默认构造函数不用考虑这个内置类型默认构造函数就会使用其进行初始化size_t _size 0; }; int main() {MyQueue q;return 0; }7.无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。 注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数。也就是不需要传参数就可以调用的构造函数就叫默认构造函数 class Date { public:// 无参构造函数可以将其看做是默认构造函数Date() {_year 1900;_month 1;_day 1;}// 全缺省构造函数可以将其看做是默认构造函数Date(int year 1900, int month 1, int day 1) {_year year;_month month;_day day;} private:int _year;int _month;int _day; };// 以下测试函数能通过编译吗 void Test() {Date d1; }// 答不能够通过测试因为无参构造函数和全缺省构造函数都可以接收参数因此由于d1对象没有传参导致二义性也就是没有默认构造函数3.析构函数 3.1 概念 通过前面构造函数的学习我们知道一个对象是怎么来的那一个对象又是怎么没呢的 析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。 3.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);}int main() {TestStack();return 0; }5.关于编译器自动生成的析构函数是否会完成一些事情呢下面的程序我们会看到编译器生成的默认析构函数对自定类型成员调用它的析构函数。 #includeiostream #include assert.h using namespace std;// 栈的类 class Stack { public:// 默认构造函数Stack(int capacity 4){cout Stack(int capacity 4) endl;_a (int*)malloc(sizeof(int)*capacity);if (_a nullptr){perror(malloc fail);exit(-1);}_top 0;_capacity capacity;}~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}// 内置类型 private:int* _a;int _top;int _capacity; };// 队列的类 class MyQueue { public:void push(int x){//_pushST.Push(x);}// 自定义类型 private:Stack _pushST;Stack _popST; };// 面向需求编译器默认生成就可以满足就不用自己写不满足就需要自己写// 用两个栈来实现一个队列 // Stack的构造函数需要自己写 都是内置类型 // MyQueue构造函数就不需要自己写默认生成就可以用都是自定义类型// Stack的析构函数需要我们自己写 都是内置类型 // MyQueue 就不需要自己写析构函数默认生成就可以用 都是自定义类型int main() {Date d1;MyQueue q;return 0; }两个栈实现一个队列 实现上述oj题深刻体会编译器生成析构函数的作用。 如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如Date类有资源申请时一定要写否则会造成资源泄漏比如Stack类。 4. 拷贝构造函数 4.1 概念 在现实生活中可能存在一个与你一样的自己我们称其为双胞胎 那在创建对象时可否创建一个与已存在对象一某一样的新对象呢 拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存在的类类型对象创建新对象时由编译器自动调用。 4.2 特性 拷贝构造函数也是特殊的成员函数其特征如下 拷贝构造函数是构造函数的一个重载形式。 拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为这会引发无穷递归调用。 #includeiostreamusing 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) {cout Date 拷贝构造 endl;_year d._year;_month d._month;_day d._day;// 形参加const防止写反了下面问题就可以检查出来因为d被const修饰所以d是无法被修改的//d._day _day;}private:int _year;int _month;int _day; };void Func1(Date d) {cout Func1() endl; }void Func2(Date d) {cout Func2() endl; }int main() {// 构造 - 初始化Date d1(2022, 9, 22); // 为传值传参因此传参需要先调用拷贝函数拷贝d1再将拷贝的值传参给void Func1(Date d)Func1(d1); // 为引用因此不需要调用拷贝函数Func2(d1); // 拷贝一份d1// 拷贝构造 -- 拷贝初始化// Date d2(d1); return 0; }// 打印结果 // Date 拷贝构造 // Func1() // Func2()思考为什么调用func1时会先调用拷贝函数 答由下图可知将d1传参需要先调用拷贝函数 拷贝d1再将其传参给Func1(Date d)而Date d,不需要拷贝传参d为d1的别名不需要拷贝d1也就不需要调用拷贝函数 若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。 class Date { public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}// 我们不写拷贝函数则编译器会默认生成拷贝函数 private:int _year;int _month;int _day; };int main() {// 构造 - 初始化Date d1(2022, 9, 22); // 拷贝一份d1// 拷贝构造 -- 拷贝初始化Date d2(d1); return 0; }注意在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定义类型是调用其拷贝构造函数完成拷贝的。 4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了还需要自己显式实现吗当然像日期类这样的类是没必要的。那么下面的类呢验证一下试试 class Stack { public:// 默认构造函数Stack(int capacity 4) {cout Stack(int capacity 4) endl;_a (int*)malloc(sizeof(int)*capacity);if (_a nullptr){perror(malloc fail);exit(-1);}_top 0;_capacity capacity;}/*// 我们不写拷贝函数则默认生成拷贝函数//默认生成的函数就是类似于这样的实现Stack(const Stack st) {_a st._a;_top st._top;_capacity st._capacity;}*/// 析构函数~Stack() {cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}void Push(int x){// ....// 扩容_a[_top] x;}private:int* _a;int _top;int _capacity; };int main() { Stack st1;st1.Push(1);st1.Push(2);//浅拷贝st1和st2在堆上会共用一块空间两个对象因此会调用两次析构函数所以会报错Stack st2(st1); return 0; }运行之后我们会发现程序崩溃了 思考 由下图可以得出st1和st2共用一块空间被调用两次析构函数同一块空间被释放两次因此会发生报错。 注意类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请时则拷贝构造函数是一定要写的否则就是浅拷贝。 我们应该如何解决浅拷贝的问题呢 为了解决浅拷贝一旦涉及到资源申请时则拷贝构造函数我们自己需要写这也就是深拷贝 class Stack { public:Stack(int capacity 4){cout Stack(int capacity 4) endl;_a (int*)malloc(sizeof(int)*capacity);if (_a nullptr){perror(malloc fail);exit(-1);}_top 0;_capacity capacity;}// 我们自己写的拷贝构造函数这样拷贝时才是深拷贝不会出现浅拷贝的问题Stack(const Stack st){cout Stack(const Stack st) endl;// 申请一块空间这块空间是给拷贝的新的栈对象用的// 这样就不会出现两个站对象用一块空间的问题了_a (int*)malloc(sizeof(int)*st._capacity);if (_a nullptr){perror(malloc fail);exit(-1);}memcpy(_a, st._a, sizeof(int)*st._top);_top st._top;_capacity st._capacity;}~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}void Push(int x){// ....// 扩容_a[_top] x;}private:int* _a;int _top;int _capacity; };// 需要写析构函数的类都需要写深拷贝的拷贝构造 Stack // 不需要写析构函数的类默认生成的浅拷贝的拷贝构造就可以用 Date/MyQueueclass MyQueue { public:void push(int x){_pushST.Push(x);} private:Stack _pushST;Stack _popST;size_t _size 0; };int main() {Stack st1; // 调用默认构造函数st1.Push(1);st1.Push(2);Stack st2(st1); // 调用拷贝函数st2.Push(10);// MyQueue为自定义类型调用默认的拷贝构造函数如果默认拷贝构造函数满足需求则我们不用自己写// 调用两次默认构造函数因为队列是由两个栈来实现的所以会调用两次栈的默认构造函数MyQueue q1; // 调用两次拷贝函数因为队列是由两个栈来实现的所以会调用两次栈的默认拷贝函数MyQueue q2(q1); return 0; // 调用6次析构函数 }// 打印结果如下 /* Stack(int capacity 4) Stack(const Stack st) Stack(int capacity 4) Stack(int capacity 4) Stack(const Stack st) Stack(const Stack st) ~Stack() ~Stack() ~Stack() ~Stack() ~Stack() ~Stack() */ **5.**赋值运算符重载 5.1 运算符重载 C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字operator后面接需要重载的运算符符号。 函数原型返回值类型 operator操作符(参数列表)如bool operator(const Date d1, const Date d2) 注意 不能通过连接其他符号来创建新的操作符比如operator 重载操作符必须有一个类类型参数 用于内置类型的运算符其含义不能改变例如内置的整型不能改变其含义 作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this指针 .* :: sizeof ?: . 注意这5个运算符不能重载。// 全局的operator 判断两个日期类是否相等class Date { public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}//private:int _year;int _month;int _day; };// 这里会发现运算符重载成全局的就需要成员变量是公有的那么问题来了封装性如何保证 bool operator(const Date d1, const Date d2) {return d1._year d2._year d1._month d2._month d1._day d2._day; }int main() {Date d1(2022, 9, 22);Date d2(2023, 1, 1);//d1 d2;cout (d1 d2) endl; // 程序运行时会转换成operator (d1, d2);// 也可以显示调用一般不这样operator(d1, d2);return 0; }// 将operator 定义在类里面#includeiostream using namespace std;class Date { public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}// bool operator(Date* this, const Date d2)// 这里需要注意的是左操作数是this指向调用函数的对象(操作符应该刚好是两个操作数)// 成员函数都是放在公共代码区bool operator(const Date d) {return _year d._year _month d._month _day d._day;}private:int _year;int _month;int _day; };int main() {Date d1(2022, 9, 22);Date d2(2023, 1, 1);//d1 d2;// (d1 d2) 一定要加括号因为 的优先级比高// 运行时会转换成 d1.operator(d2)cout (d1 d2) endl; // 也可以显示调用一般不这样cout (d1.operator (d2)) endl;return 0; }5.2 赋值运算符重载 1.赋值运算符重载格式 参数类型const T传递引用可以提高传参效率 返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值 检测是否自己给自己赋值 返回*this 要复合连续赋值得含义 我们自己写的运算符重载日期类 #includeiostream using namespace std;class Date { public:int GetMonthDay(int year, int month) // 得到对应月份的天数{// 将数组定义在静态区这样每次创建对象时都不需要重新创建一个数组static int monthDayArray[13] { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if (month 2 ((year % 4 0 year % 100 ! 0) || (year % 400 0))){return 29;}else{return monthDayArray[month];}}Date(int year 1, int month 1, int day 1) // 默认构造函数{_year year;_month month;_day day;// 检查日期是否合法if (!(year 1 (month 1 month 12) (day 1 day GetMonthDay(year, month)))){cout 非法日期 endl;}}// cout (d1 d2) endl; // 转换成d1.operator(d2)// 判断两个日期类是否相等bool operator(const Date d) {return _year d._year _month d._month _day d._day;}// d1 d2bool 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;}return false;}// d1 d2bool operator (const Date d){return *this d || *this d; // *this 也就是d1}// .... ! 都可以按照如上的方式来实现// d1 100// 使用传引用返回则不会拷贝*this来返回栈帧销毁后d1还存在Date operator (int day) {_day day;while (_day GetMonthDay(_year, _month)) {_day - GetMonthDay(_year, _month); _month;if (_month 13){_year;_month 1;}}return *this;}// d1 100 不会改变d1的值// 由于栈帧销毁后对ret没有访问权限所以不能够使用传引用返回// ret是一个临时创建的日期类对象只在当前运算符重载函数中存活Date operator(int day) {// 调用拷贝构造函数来创建ret这个日期类对象Date ret(*this); ret day;return ret;}void print(){cout _year endl;cout _month endl;cout _day endl;}// d1 d2;赋值重载Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}private:int _year;int _month;int _day; };void TestDate1() {Date d1;Date d2(2022, 10, 8);// 初始化时可以检测出来为非法日期Date d3(2022, 2, 40); // 初始化时可以检测出来为非法日期Date d4(2022, 2, 29); d3.Print(); d4.Print(); }void TestDate2() {Date d0;Date d1;Date d2(2022, 10, 8);// 拷贝构造(初始化) 将d2拷贝来初始化d3Date d3(d2); // 拷贝构造将d2拷贝给d4Date d4 d2; d1.Print();// 赋值重载复制拷贝 已经存在两个对象之间拷贝d0 d1 d2; d1.Print();int i, j;(i j 10);cout i j endl; // 打印结果为 11 10 }int main() {TestDate1()Date d1(2022, 9, 22);Date d2(2022, 9, 22);d1 8;d2 100;Date d3 d1 9;d1.print();d3.print();/*d1 - d2;*/return 0; }系统默认生成的运算符重载栈 class Stack { public:// 默认构造函数Stack(int capacity 4) {cout Stack(int capacity ) capacityendl;_a (int*)malloc(sizeof(int)*capacity);if (_a nullptr){perror(malloc fail);exit(-1);}_top 0;_capacity capacity;}// 拷贝构造函数// st2(st1)Stack(const Stack st) {cout Stack(const Stack st) endl;_a (int*)malloc(sizeof(int)*st._capacity);if (_a nullptr){perror(malloc fail);exit(-1);}memcpy(_a, st._a, sizeof(int)*st._top);_top st._top;_capacity st._capacity;}// st1 st2// 此处我们不自己写运算符重载的函数使用系统默认生成的运算符重载函数~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}void Push(int x){// ....// 扩容_a[_top] x;}private:int* _a;int _top;int _capacity; };void TestSatck() {Stack st1;st1.Push(1);st1.Push(2);Stack st2(10);st2.Push(10);st2.Push(20);st2.Push(30);st2.Push(40);st1 st2; }int main() {TestSatck();return 0; } 运行之后我们会发现使用默认生成的运算符重载系统崩溃了 系统为什么会崩溃呢出现了那些问题 1.通过下图可知赋值重载之后st1和st2中_a的地址是相同的 2.通过详细的分析得出下面的结论 我们自己写的运算符重载栈 /* 1.怎样才可以保证不发生如上所说的内存泄漏且对同一块空间多次析构 第一步首先释放st1的空间 第二步为st1申请一块和st2同样大小的空间 第三步将st2的数据拷贝到st1中 */// st1 st2; Stack operator(const Stack st) {cout Stack operator(const Stack st) endl;// 考虑到会有本身给本身赋值的情况,所以在赋值前要确保this ! st为真if (this ! st) {// 1.首先释放st1的空间free(_a);// 2.为st1申请一块和st2同样大小的空间_a (int*)malloc(sizeof(int)*st._capacity);if (_a nullptr){perror(malloc fail);exit(-1);}// 3.将st2的数据拷贝到st1中memcpy(_a, st._a, sizeof(int)*st._top);_top st._top;_capacity st._capacity;}// 将st1对象返回return *this; }使用我们自己写的运算符重载再来运行一下 class Stack { public:Stack(int capacity 4){cout Stack(int capacity ) capacityendl;_a (int*)malloc(sizeof(int)*capacity);if (_a nullptr){perror(malloc fail);exit(-1);}_top 0;_capacity capacity;}// st2(st1)Stack(const Stack st){cout Stack(const Stack st) endl;_a (int*)malloc(sizeof(int)*st._capacity);if (_a nullptr){perror(malloc fail);exit(-1);}memcpy(_a, st._a, sizeof(int)*st._top);_top st._top;_capacity st._capacity;}// st1 st2;// st1 st1;Stack operator(const Stack st) // 我们自己写的运算符赋值重载{cout Stack operator(const Stack st) endl;if (this ! st){free(_a);_a (int*)malloc(sizeof(int)*st._capacity);if (_a nullptr){perror(malloc fail);exit(-1);}memcpy(_a, st._a, sizeof(int)*st._top);_top st._top;_capacity st._capacity;}return *this;}~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}void Push(int x){// ....// 扩容_a[_top] x;}private:int* _a;int _top;int _capacity; };class MyQueue {public:void push(int x){_pushST.Push(x);}private:Stack _pushST;Stack _popST;size_t _size 0; };void TestSatck() {//Stack st1(10000);// 运行结果证明自己写的运算符重载不会再导致程序崩溃// 默认生成operator 不行 需要自己实现Stack st1;st1.Push(1);st1.Push(2);Stack st2(10);st2.Push(10);st2.Push(20);st2.Push(30);st2.Push(40);st1 st2;st1 st1;// MyQueue 默认生成operator 是可以的我们不需要自己去写MyQueue q1;q1.push(1);q1.push(2);q1.push(3);MyQueue q2;q2.push(10);q2.push(20);q2.push(30);q1 q2; }int main() {TestSatck();return 0; }6.日期类的实现完善日期类并将它的声明与定义分离 Date.h // Date.h class Date { // 友元声明这个声明可以写在类的任意位置// 有了这个友元声明那么这两个运算符重载函数就可以访问类内的成员变量和成员函数friend ostream operator(ostream out, const Date d); // 流输出friend istream operator(istream in, Date d); // 流输入public:// 这个函数可以得到年份月份对应的天数如2024-4就有30天int GetMonthDay(int year, int month){static int monthDayArray[13] { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if (month 2 ((year % 4 0 year % 100 ! 0) || (year % 400 0))){return 29;}else{return monthDayArray[month];}}// 默认构造函数Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;// 检查日期是否合法if (!(year 1 (month 1 month 12) (day 1 day GetMonthDay(year, month)))){cout 非法日期 endl;}}// d1 d2bool operator(const Date d) const;// d1 d2bool operator(const Date d) const;// d1 d2bool operator(const Date d) const;// d1 d2bool operator(const Date d) const;// d1 d2bool operator(const Date d) const;// d1 ! d2bool operator!(const Date d) const;// d1 100Date operator(int day);// d1 100Date operator(int day) const; // d1是不改变的// d1 - 100Date operator-(int day);// d1 - 100Date operator-(int day) const;// 前置Date operator();// 后置Date operator(int);// 前置--Date operator--();// 后置--Date operator--(int);// d1 - d2int operator-(const Date d) const;/*// 错误演示// d1.operator(cout); // d1 cout// 和 重载一般不写为成员函数因为this默认抢了第一个参数的位置Date对象就是左操作数不符合使用习惯和可读性; 因此我们可以考虑将其定义为全局变量// 如果operator不是成员函数的话那么第一个参数就不是this指针就不需要写为d1 cout// 错误演示// ostream 为流插入的类类型 // 不可以作为成员函数void operator(ostream out) {out _year 年 _month 月 _day 日 endl;}*/// 取类对象地址的重载// 要求这个类的对象不让取地址需要自己写取地址及const取地址操作符重载的一种情况Date* operator(){// 自己写的重载使用取类对象的地址拿到的就是空指针return nullptr;//return this; // 只是取类的对象的地址则不需要自己写系统默认生成的就可以}// 取类对象地址的重载(这个是被const修饰的)const Date* operator() const{return nullptr;//return this;}private:int _year;int _month;int _day; };// 流插入的声明 ostream operator(ostream out, const Date d); // 此处operator运算符重载必须声明与定义分离因为其是全局的不是类的成员函数// 错误演示如果都定义到Date.h中由于Date.cpp和test.cpp都包含了Date.h那么他们生成的.o文件中都会包含这个函数则在编译链接时就会报错/* // 不可以直接定义到Date.h ostream operator(ostream out, const Date d) {out d._year 年 d._month 月 d._day 日 endl;return out; } */// 解决方法 // 1.声明与定义分离 // 2.用static来修饰 operator // 3.定义为内联函数 这样可以放到Date.h // 注意将函数定义为全局变量那么想要访问类里面的私有成员变量则需要在类里面添加友元声明/* // static修饰则链接时函数不进入符号表 static ostream operator(ostream out, const Date d) {out d._year 年 d._month 月 d._day 日 endl;return out; } */// 内联函数则链接时函数不进入符号表 // 流插入 inline ostream operator(ostream out, const Date d) {out d._year 年 d._month 月 d._day 日 endl;return out; }// 流提取 // cin d1 operator(cin, d1) inline istream operator(istream in, Date d) {in d._year d._month d._day;return in; }Date.cpp // Date.cpp #include Date.h// 运算符重载 bool Date::operator(const Date d) {return _year d._year _month d._month _day d._day; }// d1 d2被const修饰的运算符重载 // 这种被const修饰的函数是专门给被const修饰的对象调用的普通对象也可以调用只是不可以进行修改 // 注最右侧的const是修饰隐藏的this指针的 bool Date::operator(const Date d) const {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;}return false; }// d1 d2 bool Date::operator(const Date d) const {// 大于 或上 等于也就是大于等于直接复用不用自己重新写// 编译器会自行调用 bool Date::operator(const Date d) const// bool Date::operator(const Date d)进行重载return *this d || *this d; }// d1 d2 bool Date::operator(const Date d) const {return !(*this d); // 大于取反也就是小于等于 }// d1 d2 bool Date::operator(const Date d) const {return !(*this d); // 大于等于取反也就是小于 }// d1 ! d2 bool Date::operator!(const Date d) const {return !(*this d); }Date Date::operator(int day) {if (day 0){//return *this - -day;return *this - abs(day);}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this; }// d1 100 Date Date::operator(int day) const {Date ret(*this);ret day;return ret; }// d1 - 100 Date Date::operator-(int day) {if (day 0){//return *this - -day;return *this abs(day);}_day - day;while (_day 0){--_month;if (_month 0){--_year;_month 12;}_day GetMonthDay(_year, _month);}return *this; }// d1 - 100 Date Date::operator-(int day) const {Date ret(*this);ret - day;return ret; }// 前置 Date Date::operator() {*this 1;return *this; // 前置返回加加之后的值 }// 后置 括号中多一个int参数主要是为了和前置 进行区分 // 构成函数重载 Date Date::operator(int) {Date tmp(*this);*this 1;return tmp; // 后置返回之前的值 }// 前置-- Date Date::operator--() {*this - 1;return *this; }// 后置-- Date Date::operator--(int) {Date tmp *this;*this - 1;return tmp; }// d1 - d2 int Date::operator-(const Date d) const {Date max *this;Date min d;int flag 1;// 针对于if (d *this) // 如果 bool operator(const Date d) const; 没有加const修饰*this,则程序会报错// 因为d被const修饰但是传参过去之后的*this并没有并const修饰是一种权限放大因此会报错// 加上const修饰*this之后if (d *this) 和 if (*this d) 都是可行的if (*this d) //if (d *this) {max d;min *this;flag -1;}int n 0;while (min ! max){n;min;}return n*flag; }// 重载流插入 ostream operator(ostream out, const Date d) {out d._year 年 d._month 月 d._day 日 endl;return out; } test.cpp // test.cpp#include Date.hvoid TestDate1() {Date d1(2022, 10, 8);Date d3(d1);Date d4(d1);d1 - 10000;d1.Print();Date d2(d1);/*// 打印 d2 - 10000 的两种方法Date d3 d2 - 10000; d3.Print();*/(d2 - 10000).Print();d2.Print();d3 - -10000;d3.Print();d4 -10000;d4.Print(); }void TestDate2() {Date d1(2022, 10, 8);Date d2(d1);Date d3(d1);Date d4(d1);(d1).Print(); // d1.operator()d1.Print();// 后置会存在一个占位符(d2).Print(); // d2.operator(1)d2.Print();(--d1).Print(); // d1.operator--()d1.Print();(d2--).Print(); // d2.operator--(1)d2.Print(); }void TestDate3() {Date d1(2022, 10, 10);Date d2(2023, 7, 1);cout d2 - d1 endl;cout d1 - d2 endl; }void TestDate4() {Date d1, d2;//cin d1; // 流提取//cout d1; // 流插入//d1 cout; // d1.operator(cout);cin d1 d2;cout d1 d2 endl; // operator(cout, d1); // 根据优先级会先运行cout d1; 因此operator的返回值类型应为ostream这样才可以支持d2进行插入cout d1 - d2 endl; }void TestDate5() {Date d1(2022, 10, 10);d1.Print();// 此处详解看下图被const修饰的类类型对象const Date d2(2022, 10, 10); d2.Print(); //d2 10;cout d2 1000 endl;cout d1 endl; // 取地址及const取地址操作符重载cout d2 endl; }int main() {TestDate2();return 0; }下图是对d2.Print() 的详解 7.const 将const修饰的“成员函数称之为const成员函数, const修饰类成员函数实际修饰该成员函数隐含的this指针也就是const Date* this,表明在该成员函数中不能对类的任何成员变量进行修改。 // const 在*的左侧是修饰 *this; (*this就是this指针指向的对象) // const Date* this; Date const* this// const 在*的右侧修饰 this; // Date* const this我们来看看下面的代码 class Date { public:Date(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout Print() endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;}void Print() const{cout Print()const endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;} private:int _year; // 年int _month; // 月int _day; // 日 };void Test() {//调用 void Print()Date d1(2022, 1, 13);d1.Print(); //调用 void Print() constconst Date d2(2022, 1, 13);d2.Print(); }请思考下面的几个问题 const对象可以调用非const成员函数吗 不可以。const 对象被视为只读对象它们不允许修改对象的状态。因此对 const 对象调用非 const 成员函数将违反 const-correctness 规则因此编译器会报错。 非const对象可以调用const成员函数吗 是的。非 const 对象可以调用 const 成员函数。const 成员函数被设计用于不修改对象状态的情况因此它们可以安全地被非 const 对象调用。 const成员函数内可以调用其它的非const成员函数吗 不可以。const 成员函数被视为只读函数它们保证不会修改对象的状态。因此在 const 成员函数内部调用非 const 成员函数将破坏这一保证编译器会报错。 非const成员函数内可以调用其它的const成员函数吗 是的。非 const 成员函数可以调用 const 成员函数。const 成员函数不会修改对象的状态因此它们可以安全地被非 const 成员函数调用。 总结 const 成员函数可以访问对象的成员变量但不能修改它们。非 const 成员函数可以访问并修改对象的成员变量。const 成员函数之间可以互相调用但不能调用非 const 成员函数。非 const 成员函数之间可以互相调用也可以调用 const 成员函数。 8.取地址及const取地址操作符重载 class Date { public:Date* operator(){return this;}const Date* operator() const{return this;}private:int _year; // 年int _month; // 月int _day; // 日 };//这两个运算符一般不需要重载使用编译器生成的默认取地址的重载即可只有特殊情况才需要重载比如想让别人获取到指定的内容
http://www.hkea.cn/news/14418383/

相关文章:

  • 如何查看网站模板东莞网站推广哪里找
  • 郑州春蕾网站建设网上商城的意义
  • 中国建筑行业网站聊城网站推广
  • 六灶网站建设WordPress 如何去域名授权
  • 网站建设淮安网页界面设计的功能性主要体现在信息的哪两个方面
  • 工信部查网站备案图书馆馆建设网站
  • 如何查网站pv营销策略有哪些4种
  • 网站开发技术职责优秀的html5网站
  • 宁波做网站优化的公司活动页面设计模板
  • 视频医疗平台网站开发手工艺品网站建设目的
  • 网站建设 黑龙江客户登记管理系统
  • 分销网站制作条件wordpress 滑动门效果
  • 分销网站有哪些网页链接的视频怎么下载
  • 电子商务网站建设的阶段化分析移动端网站开发框架
  • 广东旅游网站建设方案江西南昌网站建设公司哪家好
  • 成都网站建设制作价格婚恋网站做期货现货贵金属的人
  • 长春制作网站软件西安电商网站制作
  • 三亚网站建设价格网站内页制作
  • 广东省建设工程质量安全监督检测总站网站知乎网站内容建设的逻辑
  • flash网站模板源码盐城网站建设兼职
  • 网站是如何优化的网站logo位置
  • 深圳网站制作的公司深圳app开发昌黎网站建设
  • 昭通网站建设公司永远网站建设
  • 汕头市国外网站建设公司搜索引擎营销的特点有
  • 手机网站开发在pc端西安网络推广公司
  • 什么网站发布找做效果图的苏州的网站建设
  • 哈尔滨网站制作维护有网站模板怎么建站
  • 荆州做网站公司dw软件是做什么用的
  • 哈尔滨建站免费模板建设信用卡中心网站
  • 深圳宝安商城网站建设公司wordpress手机菜单导航