目录
我在上一一篇文章详细写了日期类的计算器,链接如下:日期计算器https://blog.csdn.net/m0_68617301/article/details/136856490
主要讲两点:
- // 前置 ++ ,返回 ++ 之后的内容
- Date& Date::operator++()
- {
- *this += 1;
- return *this;
- }
-
- // 后置 ++, 返回 ++ 之前的内容
- Date Date::operator++(int)
- {
- // 注意这里是拷贝构造,不是赋值重载
- Date tmp = *this;
- *this += 1;
- return tmp;
- }
- 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递。
- 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1而tmp是临时对象,因此只能以值的方式返回,不能返回引用
- 这里的 << 和 >> 的重载不能放在Date类里面,如果放在类里面,第一个参数默认就是this 指针,则 Date 类型的 参数是左操作数,与平时的用法不同。
- 所以只能定义在类的外面,这个时候如果要访问类里面的 private 参数,可以通过友元的方法。
class Date { public: friend ostream& operator<<(ostream& _out, const Date& d);//友元 friend istream& operator>>(istream& _in, Date& d); private: int _year; int _month; int _day; }; ostream& operator<<(ostream& _out, const Date& d) { _out << d._year << "/" << d._month << "/" << d._day; return _out; } istream& operator>>(istream& _in, Date& d) { _in >> d._year >> d._month >> d._day; return _in; }
- 如果定义了 const Date 的对象,再去调用 Print() 函数,会报错;
// 会报错 const Date d2(2022,1,13); d2.Print();
- 因为 默认会把 d2 的地址传过去,而 d2 的地址是 const Date* 的,this指针的类型的 Date* 的,会出现权限的放大;
- 这时候可以把Print()函数后加上const就可以了,例如:
void Print() const { //... }
- 这里的加const实际上是再this指针前加了const。
- const对象可以调用非const成员函数吗? 不可以,权限放大
- 非const对象可以调用const成员函数吗? 可以
- const成员函数内可以调用其它的非const成员函数吗? 不可以,权限放大
- 非const成员函数内可以调用其它的const成员函数吗? 可以
总的来说,如果是读功能的函数,可以加上const,而写功能的函数不加const。
- class A
- {
- public:
- A* operator&()
- {
- return this;
- }
- const A* operator&()const
- {
- return this;
- }
- private:
- };
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容,可以返回假地址,例如
class A { public: A* operator&() { return nullptr; } const A* operator&()const { int a = 10; return (const A*)&a; } };
- class Date
- {
- public:
- Date(int year, int month, int day)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- private:
- int _year; // 年
- int _month; // 月
- int _day; // 日
- };
- 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变 量"后面跟一个放在括号中的初始值或表达式。
class Date { public: Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) {} private: int _year; // 年 int _month; // 月 int _day; // 日 };
每个成员变量在初始化列表中只能出现一次;
类中包含以下成员,必须放在初始化列表位置进行初始化:
- const 类型成员;
- 引用类型;
- 自定义类型(没有默认的构造函数)。
- 所有的成员变量都会通过初始化列表进行定义,所以尽可能的在初始化列表中定义;
- 对于自定义类型,如果没有默认构造函数(3种),且没有在初始化列表进行初始化,就会报错,因为它必须要进行初始化,而没有对应的构造函数调用。
- class B
- {
- public:
- /*B(int b = 1)
- {
- this->b = b;
- }*/
- // 如果没有默认的构造函数会报错
- B(int b)
- {
- this->b = b;
- }
- private:
- int b;
- };
-
-
- class A
- {
- public:
- A(int b,int raa)
- :a(1)
- ,ra(raa)
- //,bb(b)
- {}
-
- private:
- const int a;
- int& ra;
- B bb;
- };
看一下这段代码:
class A { public: A(int a) :_a1(a) , _a2(_a1) {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a2; int _a1; }; int main() { A aa(1); aa.Print(); }
- 输出结果是随机值,因为在初始化列表中,与你定义的顺序无关,和你在成员变量声明的顺序有关,这里是_a2先声明,_a1再声明,所以在初始化列表中也是先初始化_a2。
- 先看如下代码
int main() { int i = 10; double d = i; cout << d << endl; return 0; }
- 但是下面的一段代码就会报错:
int main() { int i = 10; double& d = i; cout << d << endl; return 0; }
- 加个const就可以了
int main() { int i = 10; const double& d = i; cout << d << endl; return 0; }
- 在类中也有类似的操作,如下的代码就是相当于先把 int 类型的 2 调用构造函数生成A类型的对象,之后再拷贝构造给 a。
class A { public : A(int a) :a(a) {} private: int a; }; int main() { A a = 2; }
- 但是通常情况下,现在的编译器会做出相应的优化,例如我想测试编译器是否调用了拷贝构造,但是并没有任何的输出:
class A { public : A(int a) :a(a) {} A(const A& a) { cout << "A(const A & a)" << endl; } private: int a; }; int main() { A a = 2; }
- 这种情况就是相当于把 构造函数 + 拷贝构造 直接优化成了 构造函数。
- 如下的这种情况也是相同的道理,注意加上const,临时变量具有常性:
class A { public : A(int a) :a(a) {} A(const A& a) { cout << "A(const A & a)" << endl; } private: int a; }; int main() { // const 注意 const A& b = 3; }
- 如果构造函数的时候有两个参数,在 C++ 11 后支持这样使用:
class A { public: A(int a, int b) :a(a) ,b(b) {} A(const A& a) { cout << "A(const A & a)" << endl; } private: int a; int b; }; int main() { A a = { 1,2 }; }
- 有的时候我们可能不想让构造函数进行隐式类型转换,这个时候可以在构造函数的函数名前加上 explicit 关键字就可以了 。
class A { public : explicit A(int a) :a(a) {} A(const A& a) { cout << "A(const A & a)" << endl; } private: int a; }; int main() { //这里就会报错 const A& b = 3; A a = 2; }
- 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
- class A
- {
- public:
- A(int a = 1, int b = 1)
- :a(a)
- ,b(b)
- {
- ++c;
- }
- A(const A& a)
- {
- cout << "A(const A & a)" << endl;
- ++c;
- }
- static int Get()
- {
- return c;
- }
-
- private:
- int a;
- int b;
- static int c;
- };
-
- int A::c = 0;
-
- int main()
- {
- cout << A::Get() << endl;
- A a1, a2;
- A a3(a1);
- cout << A::Get() << endl;
- }
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
友元 是一种突破封装的方式
就比如我刚才写的 >> 和 << 的 重载 就利用了友元 :
- 这里的 << 和 >> 的重载不能放在Date类里面,如果放在类里面,第一个参数默认就是this 指针,则 Date 类型的 参数是左操作数,与平时的用法不同。
- 所以只能定义在类的外面,这个时候如果要访问类里面的 private 参数,可以通过友元的方法。
class Date { public: friend ostream& operator<<(ostream& _out, const Date& d);//友元 friend istream& operator>>(istream& _in, Date& d); private: int _year; int _month; int _day; }; ostream& operator<<(ostream& _out, const Date& d) { _out << d._year << "/" << d._month << "/" << d._day; return _out; } istream& operator>>(istream& _in, Date& d) { _in >> d._year >> d._month >> d._day; return _in; }
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递,如果C是B的友元, B是A的友元,则不能说明C时A的友元。
class Time { friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类 public: Time(int hour = 0, int minute = 0, int second = 0) : _hour(hour) , _minute(minute) , _second(second) {} 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; } private: int _year; int _month; int _day; Time _t; };
注意:内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
- class A
- {
- private:
- static int k;
- int h;
- public:
- class B // B天生就是A的友元
- {
- public:
- void foo(const A& a)
- {
- cout << k << endl;//OK
- cout << a.h << endl;//OK
- }
- };
- };
- int A::k = 1;
- int main()
- {
- A::B b;
- b.foo(A());
- return 0;
- }
- class A
- {
- public:
- A(int a = 0)
- :_a(a)
- {
- cout << "A(int a)" << endl;
- }
- ~A()
- {
- cout << "~A()" << endl;
- }
- private:
- int _a;
- };
- class Solution
- {
- public:
- int Sum_Solution(int n) {
- //...
- return n;
- }
- };
-
- int main()
- {
- A aa1;
- // 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
- //A aa1();
- // 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
- // 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
- A();
- A aa2(2);
- Solution().Sum_Solution(10);
- return 0;
- }