遂宁公司做网站/电子商务网站有哪些?
文章目录
- 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
其特征如下
- 函数名与类名相同.
- 无返回值
- 对象实例化时编译器自动调用对应的构造函数
- 构造函数可以重载
首先我们要注意的是, 全缺省函数和无参函数虽然构成函数重载, 但是不能同时存在, 会让编译器不知道具体调用哪一个:
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
- 如果类中没有显示定义构造函数, 则 C++ 编译器会自动生成一个无参的默认构造函数, 一旦用户显示定义编译器将不再生成.
一开始没有写构造函数, 直接创建对象是不会出问题的, 但是生成的是随机值, 可以大概得到一个模糊的结果: 编译器会自动生成一个默认构造函数.
一开始没有写构造函数, Date d1
不会报错
如果自己写了带参数的构造函数, Date d1
就会报错了
不实现构造函数的情况下, 编译器会生成默认的构造函数. 但是生成的仍然是随机值, 看起来构造函数却没有什么用?
特性6
- 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
- 无参构造函数, 全缺省构造函数, 我们没写编译器默认生成的构造函数, 都可以认为是默认构造函数.
但是只能有一个默认构造函数.
//无参构造函数
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;
}
关于如何写构造函数的策略:
- 一般情况下, 自己要写构造函数
- 如果成员都是自定义类型, 或者声明类时已经给了缺省值, 可以考虑让编译器自己生成
3. 析构函数
析构函数概念
构造函数可以对对象进行初始化, 那么这些数据是如何归还给操作系统的呢?
析构函数: 与构造函数功能相反, 析构函数不是完成对象本身的销毁, 局部对象销毁工作是由编译器完成的. 而对象在销毁时会自动调用析构函数, 完成对象中的资源清理工作.
析构函数特性
特性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
- 编译器生成的默认析构函数, 对内置类型不做处理, 自定义类型调用它的析构函数
还是双栈构造队列的代码:
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
- 如果类中没有申请资源时, 析构函数可以不写, 直接使用编译器生成的默认析构函数, 比如
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
类的析构函数后, 崩溃了
分析一下程序的操作
-
首先创建了一个
Stack
类的对象调用了一次构造函数 -
接着调用
func
, 函数是传值操作, 编译器直接创建了一个Stack
类类型的对象,(其实这个时候也调用了系统默认生成的构造函数) 并同时将main
中stack
的成员数据复制给给func
中的st
. 这个时候发现,stack
和st
两个对象的_array
的地址是一样的, 即指向一片空间
-
func
函数结束前, 需要销毁创建的临时变量st
, 调用析构函数, 直接释放了_array
的空间, 而这一片空间同时也被main
方法中的stack
使用着. -
最后
main
方法结束, 销毁stack
就出现错误了, 调用析构函数释放已经释放过的内存空间, 造成了内存泄漏.
由此可以看到, 编译器默认能做的只是浅拷贝, 如果拷贝需要动态开辟空间的类型对象, 会出现问题.
而自己写的拷贝构造函数就要做到"深拷贝", 如果被拷贝对象有动态开辟的空间, 需要把这一块空间的数据也拷贝一份到新的对象中去.
拷贝构造函数特性
特性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
- 若未显示定义, 编译器会生成默认的拷贝构造函数. 默认的拷贝构造函数对象按内存存储按字节序完成拷贝, 这种拷贝叫做浅拷贝, 或者值拷贝.
默认生成的拷贝构造函数完成值拷贝, 也是 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
- 如果类中涉及资源申请, 必须要自己写拷贝构造函数进行深拷贝; 如果不涉及, 不写使用编译器默认的拷贝构造函数也是可以的.
特性5
- 拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
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 << 对象;
, 需要自己实现
首先要知道, cout
和 cin
其实分别是 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
打印对象, 要方便的多.
总结:
-
- 其他运算符一般是实现成成员函数
-
- 流运算符必须实现全局, 这样才能用流对象作为第一个参数
6. const
成员
之前写过 Date
类的成员函数 Print()
, 用来打印对象的数据
但是这个函数是不安全的, 对象的内容可能会被修改, 正常的操作应该是将传入的参数前加上 const
修饰, 但是成员函数的 this
指针参数被隐藏了, 应该怎么办呢?
C++ 引入了可以对类成员函数修饰的 const
将
const
修饰的"成员函数"称之为const
成员函数,const
修饰类成员函数, 实际修饰该成员函数隐含的this
指针, 表明在该成员函数中不能对类的任何成员进行修改.
思考以下几个问题:
-
const
对象可以调用非const
成员函数吗?不可以.
const
修饰的对象为只读类型, 若调用非const
成员函数, 属于权限放大, 只读权限变为可读可写. -
非
const
对象可以调用const
成员函数吗?可以. 权限缩小, 可读可写变为只读.
-
const
成员函数内可以调用其它的非const
成员函数吗?不可以.
const
修饰的对象为只读类型, 若调用非const
成员函数, 属于权限放大, 只读权限变为可读可写. -
非
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. 运算符重载一般运算符重载赋值运算符…...

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

187. 重复的DNA序列 --力扣 --JAVA
题目 DNA序列 由一系列核苷酸组成,缩写为 A, C, G 和 T.。 例如,"ACGAATTCCG" 是一个 DNA序列 。 在研究 DNA 时,识别 DNA 中的重复序列非常有用。 给定一个表示 DNA序列 的字符串 s ,返回所有在 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,周日晚上 QNetworkAccessManager是Qt Network模块中的一个类,用于发送网络请求和接收网络响应。它提供了一种方便的方式来进行网络通信,支持常见的网络协议,如HTTP、HTTPS、FTP等。 QNetworkAccessManager和QNetwork…...

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

【C++】一篇文章搞懂auto关键字及其相关用法!
💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃个人主页 :阿然成长日记 …...

微信小程序overflow-x超出部分样式不渲染
把display:flex改成display:inline-flex, 将对象作为内联块级弹性伸缩盒显示, 类似与是子元素将父元素撑开,样式就显示出来了...

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,无法继续执行代码。重新安装程序可能会解决此问题。 --------------------------- 确定 --------------------------- 安装MySQL时出现 “This appl…...

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

搭建WAMP网站教程(windows+apache+mysql+php)
之前为了学习网络安全,从搭建网站学起,对网站运行有个初步的了解。 今天翻到了之前的笔记,顺手发到csdn上了。 搭建网站步骤 一、Apache 安装Apache,下载Apache之后把Apache解压,此处解压到C:\目录下 2.然后要记得安…...

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

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

系列十二、过滤器 vs 拦截器
一、过滤器 vs 拦截器 1.1、区别 (1)触发时机不一样,过滤器是在请求进入容器后Servlet之前进行预处理的,请求结束返回也是,是在Servlet处理完后,返回给前端之前; (2)过滤…...

dockerfile运行apk命令卡住的问题解决——更换镜像
在练习docker官方文档关于docker compose初体验(https://docs.docker.com/compose/gettingstarted/)过程中,执行dockerfile命令RUN apk add --no-cache gcc musl-dev linux-headers时,出现卡住的情况,等了几千秒都不行…...

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相关的操作,有一些特定的cmd,比如 r…...

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

Spring Boot整合Swagger
🙈作者简介:练习时长两年半的Java up主 🙉个人主页:程序员老茶 🙊 ps:点赞👍是免费的,却可以让写博客的作者开心好久好久😎 📚系列专栏:Java全栈,…...

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

Docker网络模式_Docker常用命令_以及Docker如何给运行的镜像内容连接互联网_Docker网络模式原理---Docker工作笔记004
然后我们来看一下docker的网络模式: 这个docker我们先看一下电脑上的网络,有两个,1个是lo是测试用的一个是enp0s3这个是我们以太网地址,然后我们去: 安装docker 安装后我们再去ip address可以看到多出来一个网络是docker0 这里ip地址是172.17.0.1这个是私有地址外部无法访问 这…...

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

【Midjourney入门教程2】Midjourney的基础操作和设置
文章目录 Midjourney的常用命令和基础设置1、 /imagine2、 /blend3、 /info4、 /subscribe5、 /settings(Midjourney的基础设置)6、 /shorten 有部分同学说我不想要英文界面的,不要慌: 点击左下角个人信息的设置按钮,找…...

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

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

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

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

AI视觉领域流媒体知识入门介绍(二):深入理解GOP
GOP(group of pictures) 在流行的视频编码算法中,都包含GOP这个概念,例如MPEG-2, H.264, and H.265。 背景 关于视频存储和传输的“size”: Resolution 分辨率 Uncompressed Bitrate 未压缩时的比特率 1280720 (720p…...

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