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

遂宁公司做网站/电子商务网站有哪些?

遂宁公司做网站,电子商务网站有哪些?,响应式网页设计技巧,做外贸用什么搜索网站文章目录 1. 类的6个默认成员函数2. 构造函数构造函数概念构造函数特性特性1,2,3,4特性5特性6特性7 3. 析构函数析构函数概念析构函数特性特性1,2,3,4特性5特性6 4. 拷贝构造函数拷贝构造函数概念拷贝构造函数特性特性1,2特性3特性4特性5 5. 运算符重载一般运算符重载赋值运算符…

文章目录

  • 1. 类的6个默认成员函数
  • 2. 构造函数
    • 构造函数概念
    • 构造函数特性
      • 特性1,2,3,4
      • 特性5
      • 特性6
      • 特性7
  • 3. 析构函数
    • 析构函数概念
    • 析构函数特性
      • 特性1,2,3,4
      • 特性5
      • 特性6
  • 4. 拷贝构造函数
    • 拷贝构造函数概念
    • 拷贝构造函数特性
      • 特性1,2
      • 特性3
      • 特性4
      • 特性5
  • 5. 运算符重载
    • 一般运算符重载
    • 赋值运算符重载
      • 赋值运算符重载格式
      • 赋值运算符只能重载成类的成员函数不能重载成全局函数
      • 用户没有显式实现时, 编译器会生成一个默认赋值运算符重载, 以值的方式逐字节拷贝
    • 前置 `++` 和 后置 `++` 重载
    • 流插入 流提取 的重载
  • 6. `const` 成员
  • 7.取地址及 `const` 取地址操作符重载

1. 类的6个默认成员函数

如果一个类中什么成员, 简称为空类.

空类中并不是什么都没有, 任何类在什么都不写时, 编译器会自动生成以下 6 个默认成员函数.

默认成员函数: 用户没有显示实现, 编译器会生成的成员函数称为默认成员函数.

class Date{};

在这里插入图片描述

在我们学习这些默认生成的成员函数时, 我们需要把两点疑问放在心上

1. 我们不写这些成员函数, 编译器自己生成默认的成员函数, 这些函数做了些什么?

2. 我们自己要写这些成员函数, 应该如何实现?

2. 构造函数

构造函数概念

在类和对象(上)中, 初始化类生成的对象, 使用了成员函数 Init(), 但有时候, 会经常忘记初始化, 造成不可预料的结果.

例如日期类:

class Date
{
private:int _year;int _month;int _day;public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
};

如果不初始化, 或者忘记初始化, 该对象的年月日是随机值:

int main()
{Date d;d.Print();return 0;
}

在这里插入图片描述


为了防止忘记初始化, 或者简化每次创建对象都需要初始化的行为.C++引入了构造函数

构造函数是一个特殊的成员函数, 名字与类型相同, 创建类类型对象时由编译器自动调用, 以保证每个数据成员都有一个合适的初始值, 并且在对象整个生命周期内只调用一次.

构造函数特性

构造函数是特殊的成员函数, 需要注意的是, 构造函数虽然名称叫构造, 但是构造函数的主要任务并不是开空间创造对象, 而是初始化对象.

特性1,2,3,4

其特征如下

  1. 函数名与类名相同.
  2. 无返回值
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载

首先我们要注意的是, 全缺省函数和无参函数虽然构成函数重载, 但是不能同时存在, 会让编译器不知道具体调用哪一个:

Date()
{}Date(int year = 1, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;
}

下面的代码展示了构造函数是怎么起作用的:

class Date
{
private:int _year;int _month;int _day;
public://1. 无参构造函数Date(){}//2. 带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
};int main()
{Date d1;                //调用无参构造函数Date d2(2000, 1, 1);    //调用带参构造函数return 0;
}

在这里插入图片描述

调用无参构造函数只能类名 对象名, 不能在后面跟上圆括号, Date d1()这是错的, 这会被编译器当成函数声明.

在这里插入图片描述


特性5

  1. 如果类中没有显示定义构造函数, 则 C++ 编译器会自动生成一个无参的默认构造函数, 一旦用户显示定义编译器将不再生成.

一开始没有写构造函数, 直接创建对象是不会出问题的, 但是生成的是随机值, 可以大概得到一个模糊的结果: 编译器会自动生成一个默认构造函数.

一开始没有写构造函数, Date d1 不会报错
在这里插入图片描述

如果自己写了带参数的构造函数, Date d1 就会报错了
在这里插入图片描述


不实现构造函数的情况下, 编译器会生成默认的构造函数. 但是生成的仍然是随机值, 看起来构造函数却没有什么用?

特性6

  1. C++ 默认生成的构造函数, 内置类型不处理, 自定义类型会调用其构造函数.

C++ 把类型分为内置类型和自定义类型. 内置类型就是语言提供的数据类型, 如:int, char, double, 指针...(自定义类型的指针也是内置类型如Date*);自定义类型就是使用class, struct, union...定义的类型.

下面的代码就可以证明这个特性:
之前做过用两个栈模拟队列的题目, 这边先创建队列的对象.

typedef int STDataType;
class Stack
{
private:STDataType* _array;int _capacity;int _top;
public:Stack(int capacity = 4){_array = (STDataType*)malloc(sizeof(STDataType) * capacity);if (nullptr == _array){perror("malloc fail");exit(-1);}_capacity = 4;_top = 0;}
};class MyQueue
{
private://自定义类型Stack _pushst;Stack _popst;//内置类型int _size;
};int main()
{Stack st;MyQueue mq;return 0;
}

我只创建了 Stack 的构造函数, MyQueue 并没有创建.
但是调试发现 MyQueue 创建的对象 mq 的自定义类型成员被默认初始化了, 内置类型成员却仍然是随机值.
在这里插入图片描述

如果我把 Stack 的构造函数删去, 则会出现下面的结果:
所有的类型全是随机值, 即使创建 mq 的时候会调用 Stack 类型的构造函数, 但是是默认构造函数, 而 Stack 类中的所有成员变量都是内置类型, 所以最后所有的成员都是随机值.
在这里插入图片描述

由此可以看出, C++ 默认生成的构造函数, 不处理内置类型成员变量, 自定义类型成员变量会调用其构造函数


对于 C++ 默认构造函数只处理自定义类型的缺陷

C++11 中打了补丁, 即:内置类型成员变量在声明中可以给默认值

class Time
{
private:int _hour;int _minute;int _second;
public:Time(){cout << "Time()" << endl; _hour = 0;_minute = 0;_second = 0;}
};class Date
{
private:// 内置类型int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};int main()
{Date d1;return 0;
}

打印 d1 的所有成员, 均初始化完毕
在这里插入图片描述


如果同时存在缺省值和自己创建的构造函数, 优先构造函数.

在上面的 Date 类中添加了自己写的 Date 构造函数, 结果编译器优先使用构造函数进行构造.

Date()
{_year = 2000;_month = 4;_day = 1;
}

程序运行如下:
在这里插入图片描述


特性7

  1. 无参构造函数, 全缺省构造函数, 我们没写编译器默认生成的构造函数, 都可以认为是默认构造函数.
    但是只能有一个默认构造函数.

在这里插入图片描述

//无参构造函数
Date()
{_year = 2000;_month = 4;_day = 1;
}
//全缺省构造函数
Date(int year = 2000, int month = 4, int day = 1)
{_year = year;_month = month;_day = day;
}

推荐使用全缺省构造函数作为默认构造函数, 这样可以做到程序自己初始化, 自己自定义初始化.

int main()
{Date d1;Date d2(1999, 1, 1);return 0;
}

在这里插入图片描述


关于如何写构造函数的策略:

  1. 一般情况下, 自己要写构造函数
  2. 如果成员都是自定义类型, 或者声明类时已经给了缺省值, 可以考虑让编译器自己生成

3. 析构函数

析构函数概念

构造函数可以对对象进行初始化, 那么这些数据是如何归还给操作系统的呢?

析构函数: 与构造函数功能相反, 析构函数不是完成对象本身的销毁, 局部对象销毁工作是由编译器完成的. 而对象在销毁时会自动调用析构函数, 完成对象中的资源清理工作.

析构函数特性

特性1,2,3,4

析构函数是特殊的成员函数, 其特征如下:

  1. 析构函数名是在类名前面加上字符~
  2. 无参数无返回类型
  3. 一个类只能有一个析构函数. 若未显示定义, 系统会自动生成默认的析构函数. 注意: 析构函数不能重载
  4. 对象生命周期结束时, C++ 编译系统自动调用析构函数
class Date
{
private:int _year = 1970;int _month = 1;int _day = 1;
public: ~Date(){//严格来说, Date类不需要析构函数cout << "~Date()" << endl;}
};int main()
{Date d1;return 0;
}

在对象被自动销毁前, 程序会自行调用析构函数, 先把类中的成员得到的资源归还给操作系统.
在这里插入图片描述


特性5

  1. 编译器生成的默认析构函数, 对内置类型不做处理, 自定义类型调用它的析构函数

还是双栈构造队列的代码:

typedef int STDataType;
class Stack
{
private:STDataType* _array;int _capacity;int _top;
public:Stack(int capacity = 4){//Stack的构造函数_array = (STDataType*)malloc(sizeof(STDataType) * capacity);if (nullptr == _array){perror("malloc fail");exit(-1);}_capacity = 4;_top = 0;}~Stack(){//Stack的析构函数cout << "~Stack()" << endl; free(_array);_array = nullptr;_capacity = _top = 0;}
};class MyQueue
{
private://自定义类型Stack _pushst;Stack _popst;//内置类型int _size = 0;
};int main()
{Stack st;MyQueue mq;return 0;
}

可以看到, 虽然 MyQueue 没有写析构函数, 但是系统生成的默认析构函数调用了其自定义类型成员的析构函数, 所以一共会有三次调用 ~Stack() 的记录.

在这里插入图片描述

main 方法中只创建了 1 次 Stack 类的对象, 但是最终会调用 3 次 Stack 类的析构函数.

原因是, 系统要销毁 mq 对象前, 会先调用 MyQueue 类的析构函数, 但是我们没有写, 会调用系统默认的析构函数.

系统默认的析构函数, 会自动调用自定义类型成员的析构函数, 正好 MyQueue 类有两个 Stack 类的成员, 调用了 2 次.

剩下的内置类型空间交由系统释放, 入栈出栈用栈帧指针控制.


特性6

  1. 如果类中没有申请资源时, 析构函数可以不写, 直接使用编译器生成的默认析构函数, 比如 Date 类; 有资源申请时, 一定要写, 否则会造成资源泄露, 比如 Stack 类.

4. 拷贝构造函数

拷贝构造函数概念

在创建对象的时候, 可否创建一个与已存在对象一模一样的新对象呢?

拷贝构造函数: 只有单个形参, 该形参是对本类类型对象的引用(一般用 const 修饰), 再用已存在的类类型对象创建新对象时由编译器自动调用.


那么拷贝构造函数有什么用呢? 系统明明会自己拷贝, 例如在函数调用的时候.

我们都知道当调用函数时, 函数形参是实参的拷贝.

若实参所占的内存空间很大, 传值效率很低. 如果该实参类型的对象是需要申请空间的, 如果还是传值操作, 编译器的默认操作就会造成错误:

typedef int STDataType;
class Stack
{
private:STDataType* _array;int _capacity;int _top;
public:void Print(){cout << "_array: " << _array << endl;cout << "_capacity: " << _capacity << endl;cout << "_top: " << _top << endl << endl;}// 构造函数Stack(int capacity = 4){cout << "Stack()" << endl; _array = (STDataType*)malloc(sizeof(STDataType) * capacity);if (nullptr == _array){perror("malloc fail");exit(-1);}_capacity = 4;_top = 0;}// 析构函数~Stack(){cout << "~Stack()" << endl;free(_array);_array = nullptr;_capacity = _top = 0;}
};// 写一个函数, 将 Stack 类类型的变量传值操作当作参数
void func(Stack st)
{cout << "func方法中的st:" << endl; st.Print();
}int main()
{Stack stack;cout << "main方法中的stack:" << endl; stack.Print();func(stack);   //传值操作return 0;
}

程序调用了两次 Stack 类的析构函数后, 崩溃了

在这里插入图片描述

分析一下程序的操作

  1. 首先创建了一个 Stack 类的对象调用了一次构造函数

  2. 接着调用 func, 函数是传值操作, 编译器直接创建了一个 Stack 类类型的对象,(其实这个时候也调用了系统默认生成的构造函数) 并同时将 mainstack 的成员数据复制给给 func 中的 st. 这个时候发现, stackst 两个对象的 _array 的地址是一样的, 即指向一片空间
    在这里插入图片描述

  3. func 函数结束前, 需要销毁创建的临时变量 st, 调用析构函数, 直接释放了 _array 的空间, 而这一片空间同时也被 main 方法中的 stack 使用着.

  4. 最后 main 方法结束, 销毁 stack 就出现错误了, 调用析构函数释放已经释放过的内存空间, 造成了内存泄漏.

由此可以看到, 编译器默认能做的只是浅拷贝, 如果拷贝需要动态开辟空间的类型对象, 会出现问题.

而自己写的拷贝构造函数就要做到"深拷贝", 如果被拷贝对象有动态开辟的空间, 需要把这一块空间的数据也拷贝一份到新的对象中去.

拷贝构造函数特性

特性1,2

拷贝构造函数也是特殊的成员函数

  1. 拷贝构造函数是构造函数的一种重载形式
  2. 拷贝构造函数的参数只有一个必须是类类型对象的引用, 使用传值方式编译器直接报错, 因为会引发无穷递归调用.

如果拷贝构造函数使用传值操作, 会不停的创建临时对象调用拷贝构造, 造成无穷递归
在这里插入图片描述

同时编译器也会直接报错, 提示只能传引用
在这里插入图片描述

下面是正确的拷贝构造函数写法, 当然传指针也是可以的, 但是明显引用更好.

typedef int STDataType;
class Stack
{
private:STDataType* _array;int _capacity;int _top;
public:void Print(){cout << "_array: " << _array << endl;cout << "_capacity: " << _capacity << endl;cout << "_top: " << _top << endl << endl;}// 构造函数Stack(int capacity = 4){cout << "Stack()" << endl; _array = (STDataType*)malloc(sizeof(STDataType) * capacity);if (nullptr == _array){perror("malloc fail");exit(-1);}_capacity = 4;_top = 0;}// 拷贝构造函数Stack(const Stack& st){cout << "Stack(const Stack& st)" << endl;_array = (STDataType*)malloc(sizeof(STDataType) * st._capacity);if (_array == nullptr){perror("malloc fail");exit(-1);}memcpy(_array, st._array, sizeof(STDataType) * st._top);_top = st._top;_capacity = st._capacity;}// 析构函数~Stack(){cout << "~Stack()" << endl;free(_array);_array = nullptr;_capacity = _top = 0;}
};// 写一个函数, 将 Stack 类类型的变量传值操作当作参数
void func(Stack st)
{cout << "func方法中的st:" << endl; st.Print();
}int main()
{Stack stack;cout << "main方法中的stack:" << endl; stack.Print();func(stack);   //传值操作return 0;
}

程序正常运行.
在这里插入图片描述


特性3

  1. 若未显示定义, 编译器会生成默认的拷贝构造函数. 默认的拷贝构造函数对象按内存存储按字节序完成拷贝, 这种拷贝叫做浅拷贝, 或者值拷贝.

默认生成的拷贝构造函数完成值拷贝, 也是 C++ 对 C语言 的传承.

class Time
{
private:int _hour;int _minute;int _second;
public:// 构造函数Time(){_hour = 1;_minute = 1;_second = 1;}// 拷贝构造函数Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;}void Print(){cout << _hour << ": " << _minute << ": " << _second << endl;}
};class Date
{
private:// 内置类型int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
public:void Print(){cout << _year << '-' << _month << '-' << _day << ' ';_t.Print();}};int main()
{Date d1;d1.Print();// 用已经存在的 d1 拷贝构造 d2, 此处会调用 Date 类的拷贝构造函数// 但 Date 类没有显式定义拷贝构造函数, 则编译器会给 Date 类生成一个默认的拷贝构造函数Date d2(d1);d2.Print();return 0;
}

两个对象完全一样.
在这里插入图片描述

在编译器生成的默认拷贝构造函数中, 内置类型是按照字节方式直接拷贝的, 而自定义类型是调用其拷贝构造函数完成拷贝的.


特性4

  1. 如果类中涉及资源申请, 必须要自己写拷贝构造函数进行深拷贝; 如果不涉及, 不写使用编译器默认的拷贝构造函数也是可以的.

特性5

  1. 拷贝构造函数典型调用场景:
  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
class Date
{
public:// 构造函数Date(int year, int minute, int day){cout << "Date(int, int, int)" << this << endl;}// 拷贝构造函数Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;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(2000, 1, 1);Test(d1);return 0;
}

在这里插入图片描述

如果我们返回的是一个树怎么办, 需要一个一个结点进行拷贝, 这所消耗的时间和空间都是巨大的.

为了提高程序效率, 一般对象传参时, 尽量使用引用类型, 返回时根据实际场景, 能用引用尽量用引用.

5. 运算符重载

一般运算符重载

C++ 为了增强代码的可读性引入了运算符重载, 运算符重载是具有特殊函数名的函数, 也具有其返回值类型, 函数名字以及参数列表, 其返回值类型与参数列表与普通的函数类似.

内置对象可以直接使用各种运算符, 但是自定义类型是不可以的.

运算符重载主要是用在类的比较上面的, 类比较是不能直接用语言自带的运算符的.例如下面的 Date 类:

class Date
{
private:int _year = 1970;int _month = 1;int _day = 1;public:// 构造函数Date(){}Date(int year, int month, int day){_year = year;_month = month;_day = day;}
};int main()
{Date d1;Date d2(2023, 1, 1);cout << (d1 > d2) << endl;return 0;
}

编译器会直接报错: 内置 > 运算符不支持类对象
在这里插入图片描述


涉及到类对象相关运算符操作, 就需要自己写相关比较函数了.

当然, 不可以使用外部函数 Compare()...

  • 函数名不够见名知意, 谁也不知道这个函数究竟是用来干什么的.
  • 类的成员变量一般都是 private 的, 不可以直接进行访问, 还需要写 get 成员函数, 太麻烦

C++ 提供了运算符重载的语法
函数名字为: 关键字 operator 后面接需要重载的运算符符号
函数原型: 返回值类型 operator 操作符(参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符: 比如 operator@
  • 重载操作符必须有一个类类型参数(操作符至少有一个对象)
  • 用于内置类型的运算符, 其含义不能改变. 例如: 内置的整型+, 不能改变其含义.
  • 作为类成员函数重载时, 其形参看起来比操作数少1, 因为成员函数的第一个参数为隐藏的 this 指针
  • .* :: sizeof ?: .注意以上5个运算符不能重载

首先写一下比较两个日期是否相等的运算符重载
先在类外面尝试一下, 这里先将 Date 的成员变量设置成 public

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;Date d2(2023, 1, 1);Date d3(1970, 1, 1);// 可以写成函数调用的形式cout << operator==(d1, d2) << endl;// 最好写成正常运算符的形式, 编译器会自己转化成运算符重载函数cout << (d1 == d3) << endl;return 0;
}

程序运行结果如下:

在这里插入图片描述

使用运算符重载可以规避乱取名, 可以直接使用运算符进行比较, 提高了程序的可读性.


但是成员公有的话, 就不能保证封装性了, 可以直接将运算符重载包装入类中, 需要简单修改一下:

class Date
{//...// 包装入类中后 只有一个参数了bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}
};int main()
{Date d1;Date d2(2023, 1, 1);Date d3(1970, 1, 1);// 两种调用也都是可以的cout << d1.operator==(d2) << endl;cout << (d1 == d3) << endl;return 0;
}

程序没有问题:
在这里插入图片描述

  • 不同于之前写在类外的运算符重载, 包装在类中的运算符重载只有一个参数, 另一个参数就是指向调用该成员函数的对象的 this 指针. 这和构造函数等都是一样的.等同于operator=(Date* const this, const Date& d)

  • 同时, 调用可以使用 对象.成员函数 的格式, 但是为了维护抽象性, 推荐直接使用运算符来进行调用, 编译器会自己来处理.


接下来是 > 运算符重载的实现

class Date
{//...int getMonthDay(int year, int month){int day[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;}return day[month];}bool operator>(const Date& d){if (_year > d._year)return true;if (_month > d._month)return true;if (_day > d._month)return true;return false;}
};int main()
{Date d1;Date d2(2023, 1, 1);Date d3(1970, 1, 1);cout << (d1 > d2) << endl; cout << (d2 > d3) << endl;return 0;
}

程序运行正确:
在这里插入图片描述


运算符重载和函数重载没有关系

  • 函数重载: 可以允许参数不同的同名函数
  • 运算符重载: 自定义类型可以直接使用运算符

++= 的运算符重载

首先先实现 += 的运算符重载, 再用 += 复用写 + 的运算符重载

Date& Date::operator+=(int day)
{// 如果 day 是负数, 直接进行-=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;
}Date Date::operator+(int day)
{Date temp(*this);temp += day;return temp;
}

+= 的实现需要注意的就是函数返回类型是 Date& 的, 这样可以省去一次的对象拷贝构造, 函数内部返回 *this 即可, 这里的 this 指针出函数作用域不会被销毁, 所以返回 *this 的引用是不会造成错误的.

重点需要研究的是 使用 += 复用 + 和 使用 + 复用 += 哪个效率更高.
在这里插入图片描述

目前只是日期类, 拷贝不会消耗太多性能, 但是如果是拷贝一个有着一万个结点的树呢? 那肯定拷贝会消耗很多性能.

+= 复用创建 + 明显是效率更高的. 同时, 如果要将一个对象进行加操作, += 的效率肯定是要比 + 效率高的.

赋值运算符重载

赋值运算符重载格式

  • 参数类型: const T&, 传递引用可以提高传参效率
  • 返回值类型: T&, 返回引用可以提高返回的效率, 有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this: 要符合连续赋值的含义
Date& Date::operator=(const Date& d)
{// 如果赋值自己本身, 直接返回自身if (&d == this){return *this;}_year = d._year;_month = d._month;_day = d._day;// *this就是类对象return *this;
}

赋值运算符重载和拷贝构造还是有点区别的, 赋值运算符重载使用的情况是: 已经有两个对象. 而拷贝构造则是: 只有一个已经存在的对象, 用该对象去拷贝初始化一个新对象.

赋值运算符只能重载成类的成员函数不能重载成全局函数

在这里插入图片描述

如果我直接在类外定义赋值运算符重载, 编译器会直接报错: operator= 必须是非静态成员.

原因是: 如果赋值运算符不显示实现, 编译器会生成一个默认的. 此时用户再在类外自己实现一个全局的赋值运算符重载, 就和编译器在类中生成的默认赋值运算符重载冲突了, 故赋值运算符重载只能是类的成员的函数.

用户没有显式实现时, 编译器会生成一个默认赋值运算符重载, 以值的方式逐字节拷贝

对于 Date 类, 显然是没有必要进行显式的进行赋值运算符重载的. 但是对于需要开辟空间的类来说, 就需要自己显示实现赋值运算符重载, 具体内容涉及深拷贝, 之后会详细讲解.

前置 ++ 和 后置 ++ 重载

Date& Date::operator++()
{*this += 1;return *this;
}Date Date::operator++(int)
{Date temp(*this);*this += 1;return temp;
}

C++ 规定 operator++(int) 是前置 ++; operator++(int) 是后置 ++. 这是语法涉及, 涉及到无法逻辑闭环的操作, 只能进行特殊处理了.

让它们构成函数重载, 这样编译器就能进行区分了.

可以看出, 前置 ++ 的效率更高, 少了两次拷贝构造.

流插入 流提取 的重载

如果需要打印对象, 我们不可以直接 cout << 对象;, 需要自己实现

首先要知道, coutcin 其实分别是 ostream 类和 istream 类的对象
在这里插入图片描述

<<>> 分别接受两个参数, 左操作数必须是 ostream 或者 istream 的对象, 右操作数是要打印的对象.

学过了函数重载, 就可以很好理解为什么流插入和流读取可以自动识别类型了.
std 命名空间里, C++已经提供了常用的内置类型:
在这里插入图片描述

但是自定义类型的流插入和流提取需要我们自己实现
首先先像之前一样, 直接写在类域里面:

ostream& Date::operator<<(ostream& out)
{out << _year << '-' << _month << '-' << _day << endl; 
}istream& Date::operator>>(istream& in)
{in >> _year >> _month >> _day;
}

为了让 cout << 对象 后还能继续流插入, 需要返回 ostream 类型的引用

但是在main函数中, 却出现了问题:
在这里插入图片描述

正常方向写出现了错误, 但是反方向写却是正确的.

这里复习一下, 类中的成员函数, 第一个参数是一个隐含的 this 指针指向调用该函数的对象, 但是运算符重载第一个参数是左操作数, 第二个参数是右操作数.

这明显就反了, 为了将左右操作数顺序正确, 必须要将流插入和流提取的运算符重载放到全局中.

但这又会出现一个新的问题, private 的成员变量是不允许类外访问的

在这里插入图片描述

为了解决类外访问的问题, 这里简单引入一下友元的概念

在类外定义的函数, 如果在类域中声明时在最前面添加 friend, 表示该函数可以访问类成员私有变量

在这里插入图片描述

最终, 流插入流提取操作符重载实现完毕

在这里插入图片描述

可以看出, C++引入流的概念, 相比于 C语言使用 printf 打印对象, 要方便的多.


总结:

    1. 其他运算符一般是实现成成员函数
    1. 流运算符必须实现全局, 这样才能用流对象作为第一个参数

6. const 成员

之前写过 Date 类的成员函数 Print(), 用来打印对象的数据
但是这个函数是不安全的, 对象的内容可能会被修改, 正常的操作应该是将传入的参数前加上 const 修饰, 但是成员函数的 this 指针参数被隐藏了, 应该怎么办呢?

C++ 引入了可以对类成员函数修饰的 const

const 修饰的"成员函数"称之为 const 成员函数, const 修饰类成员函数, 实际修饰该成员函数隐含的 this 指针, 表明在该成员函数中不能对类的任何成员进行修改.

在这里插入图片描述


思考以下几个问题:

  1. const 对象可以调用非 const 成员函数吗?

    不可以. const 修饰的对象为只读类型, 若调用非 const 成员函数, 属于权限放大, 只读权限变为可读可写.

  2. const 对象可以调用 const 成员函数吗?

    可以. 权限缩小, 可读可写变为只读.

  3. const 成员函数内可以调用其它的非 const 成员函数吗?

    不可以. const 修饰的对象为只读类型, 若调用非 const 成员函数, 属于权限放大, 只读权限变为可读可写.

  4. const 成员函数内可以调用 其它的 const 成员函数吗?

    可以. 权限缩小, 可读可写变为只读.

成员函数的定义规则:

  • 能定义成 const 的成员函数都应该定义成 const, 这样 const 和非 const 对象都能调用
  • 要修改成员变量的成员函数不能定义成 const, const 对象不能调用, 非 const 对象可以调用

7.取地址及 const 取地址操作符重载

这两个默认成员函数一般不用定义, 编译器会自己生成

Date* Date::operator&()
{return this;
}const Date* Date::operator&() const
{return this;
}

这两个运算符一般不需要重载, 使用编译器生成的默认取地址的重载即可, 只有特殊情况, 才需要重载, 比如想要别人获取到指定的内容

本章完.

相关文章:

C++: 类和对象(中)

文章目录 1. 类的6个默认成员函数2. 构造函数构造函数概念构造函数特性特性1,2,3,4特性5特性6特性7 3. 析构函数析构函数概念析构函数特性特性1,2,3,4特性5特性6 4. 拷贝构造函数拷贝构造函数概念拷贝构造函数特性特性1,2特性3特性4特性5 5. 运算符重载一般运算符重载赋值运算符…...

图片批量归类:告别混乱,实现高效文件管理

在日常生活中&#xff0c;我们经常需要处理大量的图片文件。这些图片可能来自于不同的设备、不同的目录&#xff0c;甚至不同的存储介质。随着时间的推移&#xff0c;这些图片文件会越来越多&#xff0c;管理起来也会越来越困难。如何高效地整理这些图片文件&#xff0c;告别混…...

187. 重复的DNA序列 --力扣 --JAVA

题目 DNA序列 由一系列核苷酸组成&#xff0c;缩写为 A, C, G 和 T.。 例如&#xff0c;"ACGAATTCCG" 是一个 DNA序列 。 在研究 DNA 时&#xff0c;识别 DNA 中的重复序列非常有用。 给定一个表示 DNA序列 的字符串 s &#xff0c;返回所有在 DNA 分子中出现不止一次…...

Mysql高级——Mysql8一主一从,多主多从搭建

修改 /etc/hosts文件 ip地址 master1 ip地址 master2 ip地址 slave1 ip地址 slave2一主一从 create database master1db;create table master1db.master1tab(name char(50));insert into master1db.master1tab VALUES(1111);insert into master1db.master1tab VALUES(2222);m…...

【Qt5】QNetworkAccessManager

2023年11月5&#xff0c;周日晚上 QNetworkAccessManager是Qt Network模块中的一个类&#xff0c;用于发送网络请求和接收网络响应。它提供了一种方便的方式来进行网络通信&#xff0c;支持常见的网络协议&#xff0c;如HTTP、HTTPS、FTP等。 QNetworkAccessManager和QNetwork…...

zookeeper节点类型

节点类型 持久节点&#xff08;Persistent Nodes&#xff09; 这些是Zookeeper中最常见的一种节点类型&#xff0c;当创建一个持久类型节点时&#xff0c;该值会一直存在zookeeper中&#xff0c;直到被显式删除或被新值覆盖。 临时节点&#xff08;Ephemeral Nodes&#xff…...

【C++】一篇文章搞懂auto关键字及其相关用法!

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …...

微信小程序overflow-x超出部分样式不渲染

把display:flex改成display:inline-flex&#xff0c; 将对象作为内联块级弹性伸缩盒显示&#xff0c; 类似与是子元素将父元素撑开&#xff0c;样式就显示出来了...

Oracle常用运维SQL-SQL执行性能及锁表等查询分析

oracle相关系列文章: docker–在Anaconda jupyter 容器中使用oracle数据源时,Oracle客户端安装配置及使用示例 Oracle常用运维SQL–用户管理、数据导入、导出的实用脚本 Oracle TEMPORARY TABLE 临时表的使用及删除报ORA-14452错误的解决办法 Oracle常用运维SQL-SQL执行性能及…...

安装MySQL时出现 由于找不到 MSVCR120.dll,无法继续执行代码。重新安装程序可能会解决此问题。

--------------------------- mysqld.exe - 系统错误 --------------------------- 由于找不到 MSVCR120.dll&#xff0c;无法继续执行代码。重新安装程序可能会解决此问题。 --------------------------- 确定 --------------------------- 安装MySQL时出现 “This appl…...

【基础IO⑧】:文件描述符fd(进程与文件的联系)

【基础IO⑧】&#xff1a;进程与文件之间的联系(文件描述符fd&#xff09; 一.前言探讨[进程与文件关系]二.C语言文件操作三.系统文件调用1.open/write 四.文件描述符fd 一.前言探讨[进程与文件关系] 我们首先了解一些基本的认识&#xff1a; 1.文件包括文件内容和文件属性 2.…...

搭建WAMP网站教程(windows+apache+mysql+php)

之前为了学习网络安全&#xff0c;从搭建网站学起&#xff0c;对网站运行有个初步的了解。 今天翻到了之前的笔记&#xff0c;顺手发到csdn上了。 搭建网站步骤 一、Apache 安装Apache&#xff0c;下载Apache之后把Apache解压&#xff0c;此处解压到C:\目录下 2.然后要记得安…...

瓦斯抽采VR应急救援模拟仿真系统筑牢企业安全生产防线

矿工素质对安全生产的影响很大。传统的煤矿安全事故培训出于条件差、经验少加上侥幸心理&#xff0c;导致其在教学内容时过于简单且不切合实际&#xff0c;无法真正发挥培训作用。瓦斯检查作业VR模拟实操培训通过真实还原煤矿作业环境&#xff0c;让受训者身临其境地进入三维仿…...

nodelist 与 HTMLCollection 的区别

原地址 https://cloud.tencent.com/developer/article/2013289 节点与元素 根据 W3C 的 HTML DOM 标准&#xff0c;HTML 文档中的所有内容都是节点&#xff1a; 整个文档是一个文档节点每个 HTML 元素是元素节点HTML 元素内的文本是文本节点每个 HTML 属性是属性节点注释是注…...

系列十二、过滤器 vs 拦截器

一、过滤器 vs 拦截器 1.1、区别 &#xff08;1&#xff09;触发时机不一样&#xff0c;过滤器是在请求进入容器后Servlet之前进行预处理的&#xff0c;请求结束返回也是&#xff0c;是在Servlet处理完后&#xff0c;返回给前端之前&#xff1b; &#xff08;2&#xff09;过滤…...

dockerfile运行apk命令卡住的问题解决——更换镜像

在练习docker官方文档关于docker compose初体验&#xff08;https://docs.docker.com/compose/gettingstarted/&#xff09;过程中&#xff0c;执行dockerfile命令RUN apk add --no-cache gcc musl-dev linux-headers时&#xff0c;出现卡住的情况&#xff0c;等了几千秒都不行…...

Android - 编译 openssl 踩坑之路

一、简述 如果你想快速在项目中使用上 openssl,可以使用网上其他开发者提供好的预编译库: OpenSSL(All):https://builds.viaduck.org/prebuilts/openssl/OpenSSL(3.1.*) :https://github.com/217heidai/openssl_for_android以上的预编译库可能最低只支持 API 21(即 Andro…...

verdi技巧分享--合并多个fsdb文件、统计信号边沿

文章目录 0 前言1 如何显示信号高位的02 统计信号的上升沿、下降沿3 合并信号4 将多个fsdb文件合并成一个 0 前言 分享几个这段时间学到的verdi操作 1 如何显示信号高位的0 这个可能对一些有强迫症的有帮助吧 nand相关的操作&#xff0c;有一些特定的cmd&#xff0c;比如 r…...

czmq的4版本中CURVE怎么实现的两个程序之间使用的一个证书?

在CZMQ的4版本中&#xff0c;CURVE是一种加密机制&#xff0c;用于在两个程序之间建立安全的连接。要使用CURVE&#xff0c;你需要创建和分发公钥和私钥。以下是一个简单的步骤说明&#xff1a; 首先&#xff0c;你需要为每个程序生成一对公钥和私钥。你可以使用CZMQ的zcert类…...

Spring Boot整合Swagger

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…...

SpringBoot-WebSocket浏览器-服务器双向通信

文章目录 WebSocket 介绍入门案例 WebSocket 介绍 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c;并进行双向数据传输。 应用场景&#xff1a; 视…...

Docker网络模式_Docker常用命令_以及Docker如何给运行的镜像内容连接互联网_Docker网络模式原理---Docker工作笔记004

然后我们来看一下docker的网络模式: 这个docker我们先看一下电脑上的网络,有两个,1个是lo是测试用的一个是enp0s3这个是我们以太网地址,然后我们去: 安装docker 安装后我们再去ip address可以看到多出来一个网络是docker0 这里ip地址是172.17.0.1这个是私有地址外部无法访问 这…...

爬虫项目-爬取股吧(东方财富)评论

1.最近帮别人爬取了东方财富股吧的帖子和评论&#xff0c;网址如下&#xff1a;http://mguba.eastmoney.com/mguba/list/zssh000300 2.爬取字段如下所示&#xff1a; 3.爬虫的大致思路如下&#xff1a;客户要求爬取评论数大于5的帖子&#xff0c;首先获取帖子链接&#xff0c…...

【Midjourney入门教程2】Midjourney的基础操作和设置

文章目录 Midjourney的常用命令和基础设置1、 /imagine2、 /blend3、 /info4、 /subscribe5、 /settings&#xff08;Midjourney的基础设置&#xff09;6、 /shorten 有部分同学说我不想要英文界面的&#xff0c;不要慌&#xff1a; 点击左下角个人信息的设置按钮&#xff0c;找…...

后端使用DES加密,前端解密方法

前言&#xff1a; 现在为了防止用户直接篡改数据会采用加密的方式进行传输&#xff0c;加密的方法有很多种&#xff0c;这篇文章主要讲解下后端使用DES加密的数据传输给前端&#xff0c;前端接收到之后如何去解密。 操作步骤如下&#xff1a; 1.安装crypto-js npm install c…...

chrome 扩展 popup 弹窗的使用

popup的基本使用方法 popup介绍 popup 是点击 browser_action 或者 page_action图标时打开的一个小窗口网页&#xff0c;焦点离开网页就立即关闭&#xff0c;一般用来做一些临时性的交互。 popup配置 V3版本中&#xff08;V2版本是在 browser_action 中 &#xff09;&#x…...

Spring Security入门教程,springboot整合Spring Security

Spring Security是Spring官方推荐的认证、授权框架&#xff0c;功能相比Apache Shiro功能更丰富也更强大&#xff0c;但是使用起来更麻烦。 如果使用过Apache Shiro&#xff0c;学习Spring Security会比较简单一点&#xff0c;两种框架有很多相似的地方。 目录 一、准备工作 …...

如何在 Unbuntu 下安装配置 Apache Zookeeper

简介 Zookeeper 是 apache 基金组织下的项目&#xff0c;项目用于简单的监控和管理一组服务&#xff0c;通过简单的接口就可以集中协调一组服务&#xff0c;如配置管理&#xff0c;信息同步&#xff0c;命名&#xff0c;分布式协调。 准备工作 Ubuntu 23.04 或者 20.04访问…...

AI视觉领域流媒体知识入门介绍(二):深入理解GOP

GOP&#xff08;group of pictures&#xff09; 在流行的视频编码算法中&#xff0c;都包含GOP这个概念&#xff0c;例如MPEG-2, H.264, and H.265。 背景 关于视频存储和传输的“size”&#xff1a; Resolution 分辨率 Uncompressed Bitrate 未压缩时的比特率 1280720 (720p…...

C++ 代码实例:并查集简单创建工具

文章目录 前言代码仓库代码说明main.cppMakefile 结果总结参考资料作者的话 前言 C 代码实例&#xff1a;并查集简单创建工具。 代码仓库 yezhening/Programming-examples: 编程实例 (github.com)Programming-examples: 编程实例 (gitee.com) 代码 说明 简单地创建并查集注…...