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

宝塔面板建设二级域名网站访问不了wordpress编辑留言板

宝塔面板建设二级域名网站访问不了,wordpress编辑留言板,重庆知名做网站的公司,一般使用的分辨率是多少dpi#x1f3d6;️作者#xff1a;malloc不出对象 ⛺专栏#xff1a;C的学习之路 #x1f466;个人简介#xff1a;一名双非本科院校大二在读的科班编程菜鸟#xff0c;努力编程只为赶上各位大佬的步伐#x1f648;#x1f648; 目录前言一、类的6个默认成员函数二、构造… ️作者malloc不出对象 ⛺专栏C的学习之路 个人简介一名双非本科院校大二在读的科班编程菜鸟努力编程只为赶上各位大佬的步伐 目录前言一、类的6个默认成员函数二、构造函数2.1 特性三、析构函数3.1 特性四、拷贝构造函数4.1 特性4.2 构造函数与析构函数的调用顺序五、运算符重载六、友元6.1 友元函数6.2 友元类七、赋值运算符重载7.1 赋值运算符与运算符重载的关系7.2 赋值运算符重载 VS 拷贝构造函数八、const成员函数九、取地址及const取地址操作符重载前言 本篇文章讲解的是类的六大默认成员函数它们的特性以及注意点非常多学起来是有一定难度的但只要我们认真一点相信什么困难都是可以克服的orz~ 一、类的6个默认成员函数 Q如果一个类中什么成员都没有简称为空类。空类中真的什么都没有吗 并不是任何类在什么都不写时编译器会自动生成6个默认成员函数。 默认成员函数用户没有显式实现编译器会生成的成员函数称为默认成员函数。 class Date {};下面就让我们一起来了解一下这6个函数的特性吧 二、构造函数 概念构造函数是一个特殊的成员函数名字与类名相同创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次。 下面我们来看看日期类的例子 #include iostream using namespace std;class Date { public:void Init(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 d1;d1.Init(2022, 11, 5);d1.Print();Date d2;d2.Init(2022, 11, 6);d2.Print();return 0; }对于上面的日期类Date可以通过Init共有函数对对象设置日期但如果每次创建对象时都要通过该函数设置信息未免有点麻烦。那能否在创建对象时就将信息设置进去呢为此C之父就设计出了构造函数来解决这一问题。 2.1 特性 构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是初始化对象。 其特征如下 1.函数名与类名相同。 2.无返回值。 3 对象实例化时编译器自动调用对应的构造函数。 4.构造函数可以重载。 注构造函数的无返回值就是类名后面直接跟形参列表void不算做无返回值构造函数可以重载的意思就是可以写多个构造函数提供多种初始化方式。 下面我们就来利用构造函数解决上面日期类出现的问题 #include iostream using namespace std; class Date { public:// 初始化对象// 无参构造函数Date(){_year 2023;_month 1;_day 1;}// 有参构造函数与无参构造函数构成函数重载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 d1; // 1.调用无参构造函数对象实例化时会自动调用对应的构造函数d1.Print();Date d2(2022, 11, 5); // 2.调用有参构造函数 d2.Print();return 0; }我们来看看结果 我们通过调试带大家感受一下这个过程 通过上述的分析想必大家都知道了构造函数的用法其实上述的两个构造参数可以合并成一个缺省构造函数它也是以后我们最推荐的写法下面我们一起来看看这段代码 #include iostream using namespace std; class Date { public:// 初始化对象// 无参构造函数Date(){_year 2023;_month 1;_day 1;}// 有参构造函数与无参构造函数构成函数重载Date(int year, int month, int day){_year year;_month month;_day day;}// 缺省构造函数Date(int year 2023, int month 1, int day 1){_year year;_month month;_day day;}void Print(){cout _year / _month / _day endl;}private:int _year;int _month;int _day; };int main() {Date d1(2022, 11, 5);d1.Print();Date d2(2022, 11);d2.Print();Date d3(2022);d3.Print();Date d4;d4.Print();Date d5();d5.Print();return 0; }首先我想问一下这段代码存在哪些错误 那么该如何解决这些问题呢 我们实际上只需要定义一个缺省构造函数就可以解决这些问题了因为无参和有参构造函数都是与缺省构造函数发生冲突了才产生的错误我们一起来看看修改后的结果 在以后的学习过程中我们也是推荐采用缺省构造函数的方式来初始化对象这样既可以初始化对象了又很好的利用了缺省参数的特性来方便我们初始化可谓是一举俩得嘿嘿 我们来看看下面一组例子为什么右图定义一个有参构造函数就发生了报错呢并且报错信息为没有合适的默认构造函数可用 首先我们在开头就已经讲到过任何一个类中都有默认的6大成员函数它们就是天选之子对于构造函数来说只要发生了对象实例化就一定会调用构造函数而如果我们没显示的定义构造函数的话就会自动的调用默认构造函数如左图所示它是没有任何问题的。而对于右图来说只要类中实现了任意一种构造函数此时编译器就不会自动生成一个默认构造函数了换而言之我在右图中显式的定义了这个构造函数那么在实例化对象时我就会去默认调用这个构造函数但右图明显的未调用这个有参构造函数而是无参构造函数所以编译器提示未找到合适的默认构造函数。 总结如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义了任意一种构造函数编译器将不再生成这个默认的无参构造函数那么在对象实例化时默认调用的构造函数就是用户显式定义的构造函数(其他构造函数都不行) 接下来我想问大家一个问题在类中没有显式定义构造函数此时自动生成的无参默认构造函数中的内容是什么我们来看一个例子 d对象调用了编译器生成的无参默认构造函数但是d对象中_year/_month/_day成员变量依旧是随机值也就说在这里编译器生成的默认构造函数其实并没有什么用 解答这其实是C之父设计的不合理的地方。在C中把类型分成内置类型(基本类型)和自定义类型内置类型就是语言提供的数据类型如int/char…自定义类型就是我们使用class/struct/union等自己定义的类型。默认生成的构造函数对内置类型成员不做处理而对自定义类型的成员进行处理。对于上述_year、_month以及_day都是内置类型成员默认构造函数不会对它们进行处理所以它们打印的是随机值。 注意事项默认生成的构造函数对内置类型成员不做处理而对于自定义类型的成员会去调用它的默认构造函数。默认构造函数无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数。 接下来我们就来看看默认构造函数对自定义类型的处理 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 _t; };int main() {Date d;return 0; }首先我们的对Date类进行了实例化对象它一定会调用构造函数在Date类中我们没有显式的定义一个构造函数所以它会生成一个默认的构造函数但是它只对自定义类型成员进行处理我们的_t是Time类的一个实例化对象此时它又会调用构造函数Time类中显式的定义了一个无参构造函数所以会一定会调用这个无参构造函数结果会打印出Time()。 这个设计在当初本来就是有一点不合理的于是C之父在C11中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在类中声明时可以给默认值。 其实通过这些例子我们知道其实到最后使用最多的情况还是自己写构造函数不用自己定义构造函数的情况还是比较少的。当然了即使这个地方设计的不那么尽如意大家根据环境的不同来判断要不要自定义写构造函数是最靠谱的。 另外关于构造函数其实还有一点内容没讲完我放在了下一篇文章给大家讲解。 三、析构函数 通过前面构造函数的学习我们知道一个对象是怎么来的那一个对象又是怎么没呢的 析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。 3.1 特性 学完了构造函数学习析构函数就非常的轻松了因为析构函数很多特性是与构造函数相反的 析构函数是特殊的成员函数其特征如下 1.析构函数名是在类名前加上字符 ~。 2.无参数无返回值类型。 3.一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数(与默认构造函数的特性相似都是只对自定义类型成员处理而不对内置类型成员处理)。 注意析构函数不能重载。 4.对象生命周期结束时C编译系统系统自动调用析构函数。 下面我们一起来看看析构函数的例子 #include iostream #include assert.h using namespace std;class Stack { public:// 初始化栈Stack(int capacity 4){cout Stack() endl;_a (int*)malloc(sizeof(int) * capacity);if (nullptr _a){perror(malloc申请空间失败);return;}_capacity capacity;_size 0;}void Push(int x){_a[_size] x;}bool Empty(){return _size 0;}int Top(){return _a[_size - 1];}// 销毁栈~Stack() // 析构函数{cout ~Stack() endl;free(_a);_a nullptr;_size _capacity 0;}private:// 成员变量int* _a;int _size;int _capacity; };int main() {Stack st(4);st.Push(1);st.Push(2);st.Push(3);st.Push(4);return 0; }我们知道动态申请的空间在使用完之后需要进行释放如果不释放会造成内存泄露有时候我们经常可能会把这项事情忽略掉那么在C中我们显式的在类中定义一个析构函数就能很好的解决这个问题它会在对象声明周期结束时自动调用这样就达到了空间释放的功能妈妈再也不怕出现内存泄露了 我们通过调试让大家清楚整个过程 关于析构函数的第三点特性析构函数未显式定义时生成默认的析构函数这里我不再进行测试了大家下来可以自行去尝试一下与构造函数是差不多的orz~ 四、拷贝构造函数 在现实生活中可能存在一个与你一样的自己我们称其为双胞胎。那在创建对象时可否创建一个与已存在对象一某一样的新对象呢 答案是可以的在C中借助拷贝构造函数可以完成这个任务。拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存在的类类型对象创建新对象时由编译器自动调用。 4.1 特性 拷贝构造函数也是特殊的成员函数其特征如下 1.拷贝构造函数是构造函数的一个重载形式。 2.拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。 3.在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定义类型是调用其拷贝构造函数完成拷贝的。 我们来看一个例子我们想创建一个新的对象d2它与d1对象内容一样 #include iostream using namespace std;class Date { public:Date(int year 2023, int month 2, int day 25){_year year;_month month;_day day;}Date(Date d) // 将d1对象中的成员变量赋值给d2对象的成员变量由此便完成了d1对象的拷贝{_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; }从上面注释的分析来说理论上这个拷贝构造函数是可行的但为什么编译器会报错而且会出现无限递归调用拷贝构造函数的情况呢 首先调用函数是不是要先进行传参操作这个形参为自定义类型那么我们知道自定义类型拷贝时需要去调用拷贝构造函数而拷贝构造函数也是一个函数它也是先要进行传参的此时的形参还是为自定义类型所以又会去调用拷贝构造函数所以这样就引发了无穷递归调用。 这个地方确实是有点难懂的但其实把思路理清了是非常好想的一件事我们可以看看下图的分析 我们来看一个例子让大家理清函数调用与拷贝构造函数之间的关联 #include iostream using namespace std;class Date { public:Date(int year 2023, int month 2, int day 25){_year year;_month month;_day day;}Date(const Date d) {cout 调用了拷贝构造函数 endl;_year d._year;_month d._month;_day d._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;Func1(d1);Func2(d1);return 0; }通过这个例子想必我已经讲清楚了这段代码中函数调用传参与拷贝构造函数之间的关联。 4.2 构造函数与析构函数的调用顺序 下面我们再来看一个比较难的例题如果这个例题你能很好的消化或者独立做出来那么对于引用、构造函数、析构函数、拷贝构造函数以及函数调用这部分的理解会更进一步 #include iostream using namespace std;class Date { public:Date(int year, int minute, int day){cout Date(int,int,int): this endl;}Date(const Date d){cout Date(const Date d): this endl;}~Date(){cout ~Date(): this endl;}private:int _year;int _month;int _day; };Date Test(Date d) {Date temp(d);return temp; } int main() {Date d1(2022, 1, 13);Test(d1);return 0; }构造函数与析构函数的调用顺序 调用构造函数的顺序基类(父类)构造函数、对象成员构造函数、派生类本身的构造函数。 调用析构函数的顺序派生类本身的析构函数、对象成员析构函数、基类(父类)构造函数。 我们来看一组关于全局对象与静态对象的例子大家想想看答案会是什么 #includeiostream using namespace std;class A { public:A(){cout A的构造函数 endl;}~A(){cout A的析构函数 endl;} };class B { public:B(){cout B的构造函数 endl;}~B(){cout B的析构函数 endl;} };class C { public:C(){cout C的构造函数 endl;}~C(){cout C的析构函数 endl;} };class D { public:D(){cout D的构造函数 endl;}~D(){cout D的析构函数 endl;} };C c; int main() {static D d;A a;B b;return 0; }通过上图我们发现全局对象比静态局部变量先构造再其次是构造静态局部对象还是说跟静态局部对象的位置有关系 我们发现静态局部对象跟局部成员对象的的构造顺序是一致的先实例化就先进行构造但是静态成员的析构不是按照局部成员对象的析构顺序来的它与全局对象构成栈的关系先构造的后析构。 下面我们再来看看全局静态对象与全局对象的构造与析构顺序 从上图我们可以发现静态全局对象与全局对象的构造析构顺序与定义的先后顺序有关 关于构造函数与析构函数的调用顺序有些知识点我们现阶段还未学到到了后面我们再来进行详谈。 我们只需要记住在一般情况下调用析构函数的次序正好与调用构造函数的次序相反最先被调用的构造函数其对应的(同一对象中的)析构函数最后被调用而最后被调用的构造函数其对应的析构函数最先被调用。简单的来说先构造的后析构后构造的先析构。 我们根据栈的性质(后进先出)来记住是最好不过的 拷贝构造函数的典型应用场景拷贝一份已存在的对象去构造一个新对象、函数参数类型为自定义类类型对象、函数返回值类型为自定义类类型对象。 这道题涉及到的知识点还是很多的相对来说还是较难的但是我们只要吸收了对我们的收获是很大的实际上这道题如果使用引用作为函数参数类型、做返回值类型的话能减少很多的拷贝工作所以根据实际场景能用引用就尽量使用引用。 关于拷贝构造函数它其实还有其他的实现方式下面我们一起来看看 #include iostream using namespace std;class Date { public:// 缺省构造函数Date(int year 2023, int month 2, int day 25){_year year;_month month;_day day;}// 拷贝构造函数Date(const Date d) // 这里我们一般使用const修饰参数这是为了防止写错赋值对象的情况例如写成d._year _year这样得出的答案就是随机值了{_year d._year;_month d._month;_day d._day;}// 构造函数Date(Date* d){_year d-_year;_month d-_month;_day d-_day;}private:int _year;int _month;int _day; };int main() {Date d1;Date d2(d1);Date d3 d1; // 写成的形式也是可以的‘’是一个赋值运算符后续我们会提到六大默认函数之一赋值重载函数但在这里用就表示拷贝构造在文章后面我会将两者进行对比Date d4(d1); // 写成传指针的构造函数之后我们需要传对象的地址这样其实是没有写成引用拷贝对象这么直观的并且使用起来也没引用这么方便Date d5 d1;return 0; } 注意拷贝构造函数的参数写成指针也能实现但是这个函数就不再是拷贝构造函数了而是构造函数。拷贝构造函数的引用一般要用const关键字修饰这样可以避免将两个参数赋值行为写反 接下来我们来探究一下拷贝构造函数的第三个特性为什么自定义类型一定要调用其拷贝构造函数来完成拷贝呢 class Stack { public:// 初始化栈Stack(int capacity 4){cout Stack() endl;_a (int*)malloc(sizeof(int) * capacity);if (nullptr _a){perror(malloc申请空间失败);return;}_capacity capacity;_size 0;}void Push(int x){_a[_size] x;}bool Empty(){return _size 0;}int Top(){return _a[_size - 1];}// 销毁栈~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_size _capacity 0;}private:// 成员变量int* _a;int _size;int _capacity; };int main() {Stack st;st.Push(1);st.Push(2);st.Push(3);st.Push(4);Stack st2(st); // 将st对象拷贝给st2对象未显式定义拷贝构造函数默认的拷贝构造函数对象按内存存储按字节序完成拷贝return 0; }我们来看看结果 我们发现程序已经崩溃了这是为什么 要想解决这个问题我们只能自己去实现这个拷贝构造函数进行拷贝之前的内置类型拷贝也被叫做浅/值拷贝它是按照一字节一字节来进行拷贝的而对于自定义类型拷贝是去调用它的成员函数拷贝构造/赋值重载的它也被叫做深拷贝 下面我们就来简单实现一下这个深拷贝通过上述例子我们知道值拷贝不适用于指向同一块空间的拷贝那么我们就将两者分别指向不同的独立的空间这样就不会发生上面的问题了 Q什么情况下需要我们实现拷贝构造函数 如果类中没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请时自己实现了析构函数释放空间则拷贝构造函数是一定要写的否则就是浅拷贝(按照字节序方式直接进行拷贝)。 我们再来谈一个题外话为什么内置类型不会出现这种异常的情况呢 因为内置类型是很简单的类型编译器是完全能够驾驭它的拷贝方式的而自定义类型可以认为是多样性的编译器不能驾驭它的拷贝方式由此就交给了拷贝构造函数去完成这项任务。 五、运算符重载 C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字operator后面接需要重载的运算符符号。 函数原型返回值类型 operator操作符(参数列表) 注意 不能通过连接其他符号来创建新的操作符比如operator重载操作符必须有一个自定义类类型参数用于内置类型的运算符其含义不能改变例如内置的整型不能改变其含义。作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this指针。.* :: sizeof ?: .注意以上5个运算符不能重载。.*是极少出现的一种运算符重载 为什么会出现运算符重载以及为什么增强了代码的可读性 运算符重载的出现就是为了解决自定义类型无法直接使用运算符来进行运算的问题。假设我们用函数来实现各项运算符的功能但使用函数我们就需要取各种各样的函数名这样在调用的时候存在名字的规范性问题而使用运算符重载我们不需要命名函数就可以直接使用运算符了。例如要实现俩个自定义对象的比较我们使用 d1 d2即可这样是不是代码的可读性大大提高了 下面我们就来针对日期类进行各项运算符重载的讲解 #include iostream using namespace std;class Date { public:Date(int year 2023, int month 2, int day 26){_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._month; }int main() {Date d1(2023, 2, 25);Date d2(2023, 2, 26);cout (d1 d2) endl; // 使用运算符会自动转换成去调用对应的运算符重载函数operator(d1,d2)operator(d1, d2); // 显式调用了运算符重载函数但是它的可读性不高这样写和我们自定义一个函数实现的功能然后去调用这个函数差不多这就体现不出运算符重载的优势所在了return 0; }从上图我们知道operator函数就是两个日期类对象的比较运算符重载函数但是我们可以看到如果我们的operator函数是定义在类外面的也就是全局的我们要想使用Date类中的成员变量就必须将其放开到公有域中但是这样写的话C的封装性就体现不出来了。 这里有几种解决方式下面我给出最简单的方式就是将operator函数写在Date类中但是我们不能直接复制粘贴到类中就完事了我们来看看现象 为什么这里编译器提示函数参数太多呢难道我们不是利用两个对象来做比较的吗 这是因为此时我们的operator函数已经在类中了它是一个成员函数既然是成员函数那么参数列表中一定隐含了一个this指针所以这里我们只需要传递一个对象就好了 下图为正确的表示形式 operator 比较两个日期类对象是否相等 bool operator(const Date d) {return _year d._year _month d._month _day d._day; }operator 判断第一个日期类对象是否小于第二个日期类对象 bool operator(const Date d) {return _year d._year|| (_year d._year _month d._month)|| (_year d._year _month d._month _day d._day); }有了上面两个运算符重载函数接下来其他的运算符重载函数都可以进行复用不用我们一个个的去实现了。 operator 判断第一个日期类对象是否小于或等于第二个日期类对象 bool operator(const Date d) {// 复用d1对象的operator以及operator运算符重载函数return (*this d) || (*this d); }operator 判断第一个日期类对象是否大于第二个日期类对象 bool operator(const Date d) { // 复用operator运算符重载函数return !(*this d); }operator 判断第一个日期类对象是否大于或等于第二个日期类对象 bool operator(const Date d) {return !(*this d); }operator! 判断第一个日期类对象是否不等于第二个日期类对象 bool operator!(const Date d) {return !(*this d); }operator求第一个日期加上第二个日期得到的日期返回的是两者加上之后得到的新的日期对象 // 由于每个月的天数是不确定的所以我们要获取每个月的天数 int GetMonthDay(int year, int month) {// static修饰数组避免频繁创建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 operator(int day) {// 处理 day 0的情况if (day 0){return *this - -day;}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this; }运算符会修改变量的值返回的是加上之后新的日期对象。出了函数的作用域*this对象还存在所以我们可以传引用返回日期类对象这样可以减少拷贝提高程序效率。 operator求第一个日期加上第二个日期表示的日期对象返回的是原来的日期对象 Date operator(int day) {Date ret(*this);ret day;return ret; }不能传引用返回因为ret对象是局部变量出了函数作用域会被销毁。 知道了日期对象day的写法对于日期对象-day就很简单了。 operator- 求第一个日期减去第二个日期得到的日期返回的是两者相减之后得到的新的日期对象 Date operator-(int day) {// 处理 day 0的情况if (day 0){return *this -day;}_day - day;while (_day 0){--_month;if (_month 0){--_year;_month 12;}_day GetMonthDay(_year, _month);}return *this; }operator- 求第一个日期减去第二个日期表示的日期对象返回的是原来的日期对象 Date operator-(int day) {Date ret(*this);ret - day;return ret; }下面我们来验证一下代码的正确性 operator 前置与后置 前置和后置都是一元运算符为了让前置 与 后置 能正确重载C规定后置重载时多增加一个int类型的参数但调用函数时该参数不用传递编译器自动传递。 前置 Date operator() {*this 1;return *this; }前置返回1之后的新结果。出了函数作用域*this指向的对象还存在此时我们可以传引用减少拷贝。 后置 Date operator(int) // int参数仅仅是为了占位跟前置重载区分传不传参都无所谓 {Date tmp(*this);*this 1;return tmp; }后置是先使用后1所以需要返回1之前的旧值。我们先将原值拷贝一份给tmp*this指向的对象1之后返回tmptmp为临时变量那么不能传引用返回。 注意在内置类型中前置与后置的区别不大但是在自定义类型中需要使用时我们建议使用前置因为后置需要多调用两次拷贝构造函数! operator- - 前置- -与后置- - 前置- -和后置- -跟前置与后置是一样的道理这里我就不再对它们进行解释了。 前置- - Date operator--() {*this 1;return *this; }后置– Date operator--(int) {Date tmp(*this);*this 1;return tmp; }日期-日期 我们的思路是让一直让小的日期直到它们两者相等。 int operator-(const Date d) {Date max *this;Date min d;int flag 1;if (*this d){max d;min *this;flag -1;}int n 0;while (min ! max){n;min;}return n * flag; }operator 与 operator : 输入与输出流重载 我们经常见到cout 用来输出内容cin 用来输入内容但你真的明白它实现的机理吗下面就让我们一起来探究一下吧 cin是头文件istream中的对象cout是头文件ostream的对象表示流提取运算符表示流插入运算符。注istream与ostream都是类。 Q为什么C要重载流插入、流提取运算符呢 在C语言中printf其实是只能打印内置类型内容的之所以能打印结构体成员是因为结构体成员是自由的不受任何限制对于C而言结构体/类中的成员变量是私有属性的所以就要按照C的语法规定来重载这个运算符。cin与cout为什么能够自动识别类型就是因为标准库已经将内置类型全部构造完成了所以它能够直接进行使用而对于自定义类型来说编译器并没有帮我们完成我们需要自己重载流插入、流提取运算符。 题外话为什么经常有人说printf的效率要比cout稍微高一些 因为使用cout会涉及大量的函数调用并且C为了保持与C语言的兼容性它们的流需要保持同步 自定义类型需要运算符重载才能进行使用 既然编译器没帮我们实现自定义类型的重载那么同样的我们就自己来构造运算符重载函数 operator: 流提取日期类对象 // 以下代码放在Date类中 void operator(ostream out) {out _year 年 _month 月 _day 日 endl; }我们将这段代码放在Date类中作为一个成员函数但是我们的this指针会默认占据参数的第一个位置这也就导致了与我们平时使用的习惯相斥使用起来特别的别扭那么我们该怎么使cout对象作为第一个参数呢 我们可以试着将它放在全局中进行函数重载第一个参数位置就为cout对象了为了保证它可以连续输出多项内容所以它的返回值要成ostream类我们一起来看看 虽然成功的完成了任务但是我们将类中私有成员变量放到了公有域中这样封装性又不好了。 这里我们有两种方法一种是定义一个在public中的函数来接收私有成员变量的值我们直接使用对象调用这个函数就能使用私有属性的成员变量了另外一种方式是借助友元函数来实现。下面我们讲解第二种方式同时也顺便把友元的知识一并全部讲完。 六、友元 友元提供了一种突破封装的方式有时提供了便利。但是友元会增加耦合度破坏了封装所以友元不宜多用。 友元分为友元函数和友元类。 6.1 友元函数 友元函数友元函数可以直接访问类的私有成员它是定义在类外部的普通函数不属于任何类但需要在类的内部声明声明时需要加friend关键字。 形象的来讲就是在类中声明这个函数是友好的把这个函数当成朋友充分信任它让它能访问类中的私有成员。 operator: 流提取日期对象 实现了operator重载定义这个重载函数就非常简单了下面我们一起来看看 istream operator(istream in, Date d) {in d._year d._month d._day;return in; }6.2 友元类 友元类的所有成员函数都可以是另一个类的友元函数都可以访问另一个类中的非公有成员。 友元关系是单向的不具有交换性。 比如上述Time类和Date类在Time类中声明Date类为其友元类那么可以在Date类中直接 访问Time类的私有成员变量但想在Time类中访问Date类中私有的成员变量则不行。友元关系不能传递。 如果C是B的友元 B是A的友元则不能说明C时A的友元友元关系不能继承。在继承位置再给大家详细介绍。 我们来看一个例子 #include iostreamclass Time {friend class Date; // 声明日期类为时间类的友元类则在日期类中就可以直接访问Time类中的私有成员变量 public:Time(int hour 0, int minute 0, int second 0): _hour(hour), _minute(minute), _second(second){}void Print(){std::cout _hour 时 _minute 分 _second 秒 std::endl;}private:int _hour;int _minute;int _second; };class Date { public:Date(int year 1900, int month 1, int day 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour hour;_t._minute minute;_t._second second;_t.Print();}void Print(){std::cout _year 年 _month 月 _day 日 std::endl;}private:int _year;int _month;int _day;Time _t; };int main() {Date d1(2023, 3, 1);d1.Print();Time t1(23, 34, 20);t1.Print();d1.SetTimeOfDate(10, 24, 0);return 0; }在Date类中使用_Time类中非公有成员完成了对_Time类非公有成员值的修改 关于友元暂时就讲到这里了后续我们在遇到一些特殊场景再来详谈。 七、赋值运算符重载 赋值运算符重载格式 参数类型const T传递引用可以提高传参效率返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回*this 要复合连续赋值的含义 赋值运算符重载既是默认成员函数又是运算符重载 函数重载支持函数名相同参数不同的函数可以同时使用。 运算符重载自定义类型对象可以使用运算符。 operator赋值运算 // 第一种形式对于单次赋值 void operator(const Date d) {if (*this ! d) // 避免出现同一个对象进行赋值操作如d1 d1这种无意义的赋值操作{_year d._year;_month d._month;_day d._day;} }我们知道赋值是可以连续复合进行赋值的例如在内置类型中可以这样int x1 x2 x3来进行连续赋值对于赋值运算符来说是从右往左结合的也就是x2 x3进行赋值操作之后这里其实会产生一个临时变量之后再将这个临时变量赋值给x1那么我们在实现复合连续赋值重载函数时就需要将对象进行返回由此就写成了下面的形式 Date operator(const Date d) {if (*this ! d){_year d._year;_month d._month;_day d._day;}return *this; }对于这个日期类对象赋值来说实际上不需要定义也可以达到目的因为编译器会默认生成一个赋值重载函数它与内置类型赋值采取的都是值拷贝的形式这里未涉及空间资源的申请所以不会有任何问题产生。 Q赋值运算符重载到底什么时候需要我们自己去定义呢 像拷贝构造函数一样如果该类需要写析构函数那么就需要写赋值运算符重载函数如果该类不需要写析构函数那么就不需要写赋值运算符重载。 我们把赋值运算符写在类外此时它为一个全局赋值运算符重载函数我们看看它能不能实现赋值功能 Date operator(Date d1, Date d2) {if (d1 ! d2){d1._year d2._year;d1._month d2._month;d1._day d2._day;} }为什么赋值运算符只能重载成类的成员函数而不能重载成全局函数 赋值运算符如果不显式实现编译器会生成一个默认的(因为它是六大默认成员函数之一)。此时用户再在类外自己实现一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值运算符重载只能是类的成员函数。 在《C prime》这本书中也特意提及到了这个语法项 7.1 赋值运算符与运算符重载的关系 关系 自定义类型使用运算符都需要重载除了赋值运算符和运算符重载(默认六大成员函数) 关于运算符重载这里只讲了一部分有些特殊一点的运算符(例如[]它也是运算符)重载还没讲到过以后碰见了我们再来谈吧 7.2 赋值运算符重载 VS 拷贝构造函数 我们来看看下面这个例子 #include iostreamclass Date { public:Date(int year 2003, int month 10, int day 24){std::cout this : Date(int, int, int) std::endl;_year year;_month month;_day day;}bool operator(const Date d){return _year d._year _month d._month _day d._day;}bool operator!(const Date d){return !(*this d);}Date operator(const Date d){std::cout this : Date operator(const Date ) std::endl;if (*this ! d){_year d._year;_month d._month;_day d._day;}return *this;}Date(Date d){std::cout this : Date(Date d) std:: endl;_year d._year;_month d._month;_day d._day;}void Print(){std::cout _year 年 _month 月 _day 日 std::endl;}private:int _year;int _month;int _day; };int main() {Date d1(2023, 2, 27);// 构造d1.Print(); Date d2 d1; //拷贝构造 d2.Print();Date d3(d2); //拷贝构造d3.Print();d3 d1; //赋值重载d3.Print();Date d4 d3 d2; // 赋值重载 构造拷贝d4.Print();return 0; }从上图我们知道拷贝构造函数会构造出一个新的对象这个新的对象与拷贝对象的内容一致赋值运算符重载函数没有新的对象生成两边使用的都是已经实例化过的对象将右边对象的内容赋给左边对象两者内容一致 我们还可以通过调试来检测一下我们的推测 结论赋值运算符和拷贝构造函数最大区别即是赋值运算符重载没有新的对象生成而拷贝构造函数会生成新的对象(拷贝构造函数是构造函数的一种特殊重载函数构造函数和拷贝构造函数一致都有新的对象生成)。拷贝构造函数是将老对象的数据成员一一赋值给新的对象数据成员的一种构造函数赋值运算符重载函数是将右边已经存在对象的数据成员一一赋值给左边另一个已经存在对象数据成员的一种操作符重载函数 其实说白了只有在两边对象都已经存在的情况下使用才是赋值运算符重载其他情况下都是拷贝构造函数 八、const成员函数 将const修饰的“成员函数”称之为const成员函数const修饰类成员函数实际是修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。 我们来看下面一个例子大家认为会打印出什么结果呢 class A { public:void Print() const{cout _a endl;}private:int _a 20; };int main() {const A aa;aa.Print(); return 0; }通过上图我们发现这段代码竟然报错了这是为何 这是因为我们的类A对象aa使用了const修饰那么在调用成员函数时编译器会隐式的将aa const A*传递给成员函数Print的形参但是成员函数Print默认是用A* this来接收实参所以此时就发生了权限放大的问题我们在之前讲过对于指针和引用来说权限放大是不行的。 那么该做如何修改呢在C中为了解决这个问题使用了const修饰成员函数在成员函数后加上const表示它是一个const成员函数 下面这种权限缩小的方式也是可行的 内部不改变成员变量的成员函数最好加上constconst对象和普通对象都可以调用只能修饰成员函数因为它有this指针 下面有几个问题我们一起来看一下 1.const对象可以调用非const成员函数吗 不可以 2.非const对象可以调用const成员函数吗 可以 3.const成员函数内可以调用其它的非const成员函数吗 4.非const成员函数内可以调用其它的const成员函数吗? 第一二个问题我们已经解决了接下来我们看看剩下的问题 Qconst成员函数内可以调用其它的非const成员函数吗 Q非const成员函数内可以调用其它的const成员函数吗? 九、取地址及const取地址操作符重载 这两个默认成员函数一般不用重新定义 编译器默认会自动生成。 operator 取地址操作符重载 Date* operator() {return this; } const operator const取地址操作符重载 const Date* operator() const {return this; }如果这两个函数不写也没有什么问题编译器生成也够用。如果你不想让别人拿到类对象的地址就可以像下面这样写。 Date* operator() {return nullptr; }const Date* operator() const {return nullptr; }本篇文章的内容就到这里了这篇文章有一定的难度特别是细节方面需要大家去认真琢磨最后如果文章有任何疑问或者错处欢迎大家评论区一起交流orz~
http://www.hkea.cn/news/14458882/

相关文章:

  • 永久免费的建站系统有哪些北京东道设计
  • 建设网站有哪些目的是什么意思企业网站如何做自然搜索
  • 网站备案主体负责人做网站用花瓣上的图片会侵权吗
  • app软件免费模板下载网站wordpress 火车头 作者
  • 怎么健免费网站吗怎样做科普视频网站
  • dede 网站地图生成好的wordpress 教程
  • 地图网站制作抖音代运营怎么取消合作
  • 网页制作与网站建设期末考试廊坊视频优化推广
  • 绍兴网站建设技术外包新网站多久被收录
  • phpcms手机网站模板第一调查网
  • 用ps设计网站做多大的小米官网网站建设
  • 如何找到网站管理员建设销售型网站
  • 有哪些好的做兼职的网站wordpress 主题错误
  • 廉江市住房和城乡规划建设局网站企业策划公司
  • 企业自建网站的优势百度搜索优化平台
  • 空包网站建设属于哪类工程承包商赚钱吗
  • 建一个网站需要多长时间什么是网站app
  • 网站服务器可以自己做吗成都网站开发环球中心
  • 做网站需要那些技术全球军事网站
  • 企业网站管理系统最新4湖南岚鸿牛x1 0装修公司网站 源码
  • wordpress.org建站wordpress链接速度慢
  • python做网站前端广州 网站开发 公司
  • 网站开发 参考文献外贸网站建设 三方登录
  • 搜狗网站优化软件16种营销模式
  • 哪个网站能把图片拼凑起来做gif的制作企业网站是怎么收费的
  • 公司网站模板凡建站肥料网站建设
  • 哪个网站建设公司比较好手机自己做网站
  • 专业手机网站建设哪家好网站模板化
  • 官方网站首页网站运营心得
  • 口碑好的网站建设商家咨询公司网站源码