目录
1. 日期类的实现
1.1 前置 ++ 和 后置 ++ 重载
1.2 >> 和 << 的重载
2. const 成员
3. 取地址及const取地址操作符重载
4. 再谈构造函数
4.1 构造函数体赋值
4.2 初始化列表
4.3 隐式类型转换
4.4 explict 关键字
5. static 成员
5.1 概念
5.2 特性
6. 友元
6.1 友元函数
6.2 友元类
7. 内部类
8. 匿名对象
1. 日期类的实现
我在上一一篇文章详细写了日期类的计算器,链接如下:日期计算器https://blog.csdn.net/m0_68617301/article/details/136856490
主要讲两点:
1.1 前置 ++ 和 后置 ++ 重载
// 前置 ++ ,返回 ++ 之后的内容
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是临时对象,因此只能以值的方式返回,不能返回引用
1.2 >> 和 << 的重载
- 这里的 << 和 >> 的重载不能放在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; }
2. const 成员
- 如果定义了 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。
3. 取地址及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; } };
4. 再谈构造函数
4.1 构造函数体赋值
- 在实例化对象的过程中,编译器会通过调用构造函数对成员变量进行赋值,例如:
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
- 构造函数体内容只能称作变量的赋值,不能说是变量的初始化,赋值可以有很多次但是初始化只能有一次,那是在什么时候初始化的呢?
4.2 初始化列表
- 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变 量"后面跟一个放在括号中的初始值或表达式。
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。
4.3 隐式类型转换
- 先看如下代码
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 }; }
4.4 explict 关键字
- 有的时候我们可能不想让构造函数进行隐式类型转换,这个时候可以在构造函数的函数名前加上 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; }
5. static 成员
5.1 概念
- 声明为static的类成员称为类的静态成员,用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;
}
5.2 特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
6. 友元
友元 是一种突破封装的方式
6.1 友元函数
就比如我刚才写的 >> 和 << 的 重载 就利用了友元 :
- 这里的 << 和 >> 的重载不能放在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修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
6.2 友元类
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。比如上述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; };
7. 内部类
- 概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
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;
}
8. 匿名对象
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;
}