要想实现流插入<<运算符的重载,首先简单看一下下图。

原来cout其实是一个对象,它的类型为ostream。一个<<有两个操作数,那么我们也可以把它作为运算符重载的一个参数。
如果重载<<写在类里面:
void operator<<(std::ostream& out)
{
out << _year << "-" << _month << "-" << _day << endl;
}
调用:
因为this指针默认是第一个参数,所以d要写在cout前面。
void test4()
{
Date d(1919, 8, 10);
d << cout;
}
//结果:1919-8-10
👆:程序跑起来是没什么问题,但是这个d << cout;的调用方式也太奇怪了吧,我们平时都是cout写在前面的啊。
看来只能写成全局的:
注意:
std::ostream& operator<<(std::ostream& out, const Date& d)
{
out << d._year << "-" << d._month << "-" << d._day << endl;
return out;
}
但是全局的函数在类外,访问不了类里面的私有成员。
我们可以把它设置为类的==友元函数==:
友元函数特性:
friend关键字。class Date
{
friend std::ostream& operator<<(std::ostream& out, const Date& d); //友元函数声明
public:
//略...
private:
int _year;
int _month;
int _day;
};
这样我们的调用就可以写成cout << d;了。
流提取>>运算符的重载与上述同理,cin也是对象,类型是istream。
注意检查输入的日期是否合法。
class Date
{
friend std::ostream& operator<<(std::ostream& out, const Date& d); //友元函数声明
friend std::istream& operator>>(std::istream& in, Date& d);
public:
//略...
private:
int _year;
int _month;
int _day;
};
std::istream& operator>>(std::istream& in, Date& d)
{
int year, month, day;
in >> year >> month >> day;
if (year >= 1
&& month <= 12 && month >= 1
&& day >= 1 && day <= d.GetMonthDay(year, month))
{
d._year = year;
d._month = month;
d._day = day;
}
else
cout << "日期非法" << endl;
return in;
}
一个类可以是另一个类的友元,友元类可以访问另一个类的私有成员。
class Date; // 前置声明
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 1, int minute = 1, int second = 1)
{
_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
{
public:
class B //B是A的友元
{
public:
void foo(const A& a)
{
cout << k << endl;
cout << a.h << endl; //B可以访问A的私有成员
}
private:
int _b;
};
private:
static int k;
int h;
};
sizeof(A)是多少?
答:A类型的对象里面没有B类型的对象,static修饰的k不算大小,所以只有一个int h占大小,答案为4
我们之前写的构造函数,成员变量在函数体内初始化:
//函数体内初始化
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
严格来说,函数体内初始化并不能叫做初始化,应该叫赋初值,因为初始化只有一次,而函数体内可以多次赋值。对于必须在定义时就初始化的类型,比如引用,函数体内就写不了了。
使用方法:
初始化列表:以一个冒号:开始,接着是由逗号,分隔的数据成员列表,每个成员变量后跟一个括号,括号内为初始值或表达式。
//初始化列表初始化
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
初始化列表才是对象里的成员定义的地方,编译器自己生成的默认构造函数也有初始化列表,用来定义成员变量,这也是为什么它对内置类型不处理(随机值),而自定义类型会调用它的默认构造函数。
C++11支持在成员变量声明处给缺省值,给的缺省值其实就是给初始化列表用的。
注意:
const成员变量,自定义类型成员(其类里面没有默认构造函数)三种类型成员变量在初始化列表初始化:
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
class A
{
public:
A(int a, int ci, const Date& d)
:_a(a)
, _ci(ci)
, _d(d)
{}
private:
int& _a; //引用
const int _ci; //const修饰
Date _d; //无默认构造的自定义类型
};
👆:A类中有Date类型的成员变量,而Date类没有提供默认构造函数,所以Date类在定义的时候就必须手动传参初始化。
建议:成员变量尽量都在初始化列表初始化。
成员变量在初始化列表中的初始化顺序是它在类中的声明次序,与它在初始化列表中的顺序无关
如下代码的结果是多少?
class A
{
public:
A(int n)
:_a(n)
, _b(_a)
{}
void Print()
{
cout << _a << " " << _b << endl;
}
private:
int _b;
int _a;
};
int main()
{
A aa(1);
aa.Print();
return 0;
}
答案:1 随机值
解析:_b先声明,先初始化_b,因为_b(_a)中的_a还未初始化,所以_b最后被初始化为随机值。接着_a(n),n为1,最后_a被初始化为1,所以最终结果为:1 随机值
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
class Date
{
public:
Date(int year)
:_year(year)
{}
void Print()
{
cout << _year << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022); //构造
Date d2 = 2023; //隐式类型转换
d2.Print(); //结果:2023
return 0;
}
对于Date d2 = 2023; ,一般编译器会使用2023构造一个临时对象,然后用它拷贝构造d2,不过编译器通常会对这种过程进行优化,直接使用2023去构造d2。
另外因为临时对象具有常性,它的引用必须加const,比如const Date& d3 = 2022;
explicit关键字可以用来修饰构造函数,被修饰的构造函数会被禁止隐式类型转换:
class Date
{
public:
explicit Date(int year) //被explicit修饰的构造函数
:_year(year)
{}
void Print()
{
cout << _year << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022); //构造
Date d2 = 2023; //此处报错:不存在从 "int" 转换到 "Date" 的适当构造函数
d2.Print();
return 0;
}
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化
静态成员变量存储在静态区(不占用对象的空间),其属于整个类,也属于该类的所有对象。
面试题:实现一个类,计算程序中创建出多少个该类的对象。
class A
{
public:
A()
{
++_count;
}
A(const A& aa)
{
++_count;
}
static int _count; //静态成员变量
};
int A::_count = 0; //在类外初始化
A func(A a)
{
A copy(a);
return copy;
}
int main()
{
A a1;
A a2 = func(a1);
cout << A::_count << endl;//也可以a1._count访问
return 0;
}
//结果:4
static也可以修饰成员函数
注意:static成员函数没有this指针,正因如此,它也无法直接访问其他非静态成员函数
class A
{
public:
A()
{
++_count;
}
A(const A& aa)
{
++_count;
}
static int GetCount() //静态成员函数
{
return _count;
}
private:
static int _count; //设为私有,不能在类外直接访问的情况
};
int A::_count = 0; //在类外定义
A func(A a)
{
A copy(a);
return copy;
}
int main()
{
A a1;
A a2 = func(a1);
cout << A::GetCount() << endl; //调用GetCount函数//也可以a1.GetCount()调用
return 0;
}
👆:因为没有this指针,所以这里调用静态成员函数不需要指定对象,只要指定类域即可。
下面这道题,可以用静态成员变量解决:
原题链接:求1+2+3+…+n__牛客网 (nowcoder.com)
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
数据范围: 0 < n ≤ 200 0<n≤200 0<n≤200
进阶: 空间复杂度 O ( 1 ) O(1) O(1),时间复杂度 O ( n ) O(n) O(n)
示例1
输入 5
输出 15
示例2
输入 1
输出 1
参考代码:
class Sum
{
public:
Sum()
{
_ret += _i;
++_i;
}
static int GetRet()
{
return _ret;
}
private:
static int _i;
static int _ret;
};
int Sum::_i = 1;
int Sum::_ret = 0;
class Solution {
public:
int Sum_Solution(int n) {
Sum a[n];
return Sum::GetRet();
}
};