1.<<和>>操作符重载
我们该如何重载操作符<<和>>呢?
如果在类里面,
void operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
好像这样就可以了,(>>用istream就可以了)
但是当你输入
cout << d1;//编译器报错
编译器就会报错
因为Date对象默认占用第一个参数(this指针),就是做了左操作数
正确做法是
d1 << cout;
//本质是d1.operator<<(cout)
你也许觉得这样写会很反人人类,那有没有更好的方法呢?
答案当然有,那就是写成全局函数,
2.const成员
我们来思考一个问题
我们在写函数的时候如果不想this指针指向的内容被修改怎么办?
可能我们会说加一个const不久行了!
但是this指针是不能显示写的,C++给我们提供了一种方式
void Date::print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Date d1;
const Date d2;
d1.print();
d2.print();//不加print函数上的const编译会报错,因为这样d2的权限被放大了
//但是权限只能缩小或者平移,所以编译器会报错
我们再来看看下面这个代码
bool Date:: operator>(const Date& x)
{
if (_year == x._year)
{
return true;
}
else if ((_year == x._year) && (_month > x._month))
{
return true;
}
else if ((_year == x._year) && (_month == x._month) && (_day > x._day))
{
return true;
}
return false;
}
Date d1;
const Date d2;
d1<d2//不会出错 原因是d1<d2本质上是d1.operator<(d2),d1传给*this,权限平移,
//而d2传给const Date& x权限的平移
d2>d1//编译器会出出错 原因是d2<d1本质上是d2.operator<(d1),d1传给const Date& x权限缩小
//而d2传给了*this,权限缩小
所以当成员函数后面加上const之后,普通变量和cosnt变量都可以调用
那么能不能所有成员函数都加这个const?
答案肯定不是,有些函数是要改变this指针指向的值,加了cosnt我怎么改值
同时,只要成员函数内部不修改成员变量,都应该加const,这样const对象和普通对象就都可以调用了!
当然这个权限对于指针同样使用
const int a=3;
int *b=a; //权限放大编译无法通过
3.取地址重载函数
我们讲了四个默认成员函数,还剩两个默认成员函数
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
}
public:
int _year;
int _month;
int _day;
};
这两个成员函数无非是对普通对象和const对象的取地址符进行重载,一般不需要我们自己去写,编译器自动生成就可以了,只有特殊情况才需要重载,比如想让别人获得指定内容!
class Date
{
public:
Date* operator&()
{
return nullptr;
}
const Date* operator&()const
{
}
public:
int _year;
int _month;
int _day;
};
比如说,像下面这样写,你就无法获得普通变量地址,但是可以获得const对象的地址!
4.再谈构造函数
我们如果想给对象的成员一个初始值,有两种方法,第一种就是我们前面学的构造函数体赋值,很熟悉
class Date
{
public:
Date (int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
今天我们学另外一种,就是初始化列表
1.初始化列表
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
public:
int _year;
int _month;
int _day;
};
2.cosnt成员和引用成员
首先我们来看引用成员和const成员,这两种都必须在定义的时候初始化
但是类里面的成员函数都是声明
我们在调用类的构造函数体赋值,它的作用是对定义的成员初始化
所以,初始化要在构造函数前,也就是构造函数的{}前(即前面的初始化列表)
初始化列表相当于成员定义的地方
当然对于内置的const类型也可以用缺省参数+构造函数体赋值
3.自定义类型(没有默认构造函数)
自定义成员为啥在该类没有默认构造函数的时候只能用初始化列表?
首先,我们要明确,自定义初始化要调用构造函数,这个时候该类没有默认构造函数,怎么调用?
这个地方的默认构造函数包括,没有显示写编译器自动生成的,无参数的构造函数,全缺省参数的构造函数
什么时候我们没有默认构造函数,我们自己写一个带参不全确省的构造函数的时候
向上面这个代码类a不存在默认构造函数,编译器就不知道怎么处理了
所以这个地方我们只能在初始化列表可以
像这样
4.总结
当然我们不写初始化列表,每个成员也会走初始化列表
只不过里面没内容罢了
class Date
{
public:
Date(int year, int month, int day)
:_month(month)
, _day(4)//这个地方显示写了,就不会去调用初始值了!
,_year(year)
{}
public:
int _year=3;//这个地方的缺省值是给初始化列表的
int _month=1;//这个地方的缺省值是给初始化列表的
int _day=3;//这个地方的缺省值是给初始化列表的
};
我们更推荐写初始化列表,但是初始化列表也无法解决全部的问题!
比如我malloc开辟一块空间,列表初始化可以帮我们解决
但是它无法帮我们判断malloc是否成功,是否成功的判定只能写在构造函数体内
此外,初始化列表执行的顺序和语句顺序无关,和成员顺序有关
class Date
{
public:
Date(int year, int month, int day)
:_month(month)//(1)
, _day(day) //(2)
,_year(year) //(3)
{}
public:
int _year;
int _month;
int _day;
};
这个地方列表初始化并不是(1)->(2)->(3)
而是(3)->(1)->(2)
因为这个地方成员声明的顺序是
_year ->_month->_day
6.explicit关键字
class aa
{
public:
aa(int s)
:a(s)
{}
private:
int a;
};
int main(void) {
aa A(1);
aa B = 2;//整形转换成自定义类型
return 0;
}
我们看这串代码,编译器没报错,那么这个aa B=2;是啥意思?
答案是隐患性类型转换
比如我们前面提过,
int a = 3;
double b = a;//这个地方是隐式类型转换
//会创建一个临时变量 临时变量有const属性
aa B=2;
//这个地方会用2去调用构造函数,生成临时变量是aa类型
//这个临时变量再会拷贝构造给B
//但是许多新的编译器会优化,会用2直接构造
像vs2022这种比较新的编译器就会用2直接构造
当然,如果你不想这个转换发生也很简单
就直接在构造函数前面加一个explicit就可以了
explicit 关键字的主要作用如下:
防止隐式类型转换:当构造函数被声明为 explicit 时,编译器将不会自动执行隐式类型转换。这意味着,只有在显式地指定类型转换时,才能使用该构造函数进行对象的创建。
class qaz
{
public:
explicit qaz(int a)
{
qwe = a;
}
private:
int qwe;
};
int main(void) {
int ww = 4;
qaz q1(4);//正确
q1 = ww;//错误
q1 = (qaz)ww;//正确
return 0;
}
explici只能作用于只有一个参数的构造函数,因为我把普通变量转换成类对象,普通变量只有一个数啊 !
5.static
1.初始化列表只能初始化对象自己的成员,不能初始化全局的,比如static修饰的,
且static修饰变量只能初始化一次!
2.static修饰的变量也不能写在构造函数体内
3.static修饰的值,只能在类外面定义(只能定义在全局,也就是静态区),定义的时候不用加static,类里面的只是声明,所有该类的对象都可以共享
4.static修饰的变量不可以有缺省值,因为缺省值要走初始化列表!但是static修饰的变量不走初始化列表
5.全局变量的劣势,所有地方都可以修改
6.static修饰的函数(静态成员函数)不可以访问普通的成员,因为没有this指针,你怎么知道是这个类哪个对象成员的成员呢?
7.static修饰的函数(静态成员函数), 没有this指针,访问需要指定类域且受访问限定符(public,private,protect)影响!
8.static修饰的函数(静态成员函数)可以通过具体的类的对象去调用,或者也可以直接用类域去访问
9.public,private,protetc访问限定符对static修饰的变量和函数同样有用
class Date
{
public:
Date(int year, int month, int day)
:_month(month)
, _day(day)
, _year(year)
{
}
public:
static void cha()
{
cout << "cha" << endl;
}
public:
static int a;
int _year = 3;
int _month = 1;
int _day = 3;
};
int Date::a = 10;//对应第3点
int main(void) {
class Date b(2005,4,14);
cout << Date::a << endl;
b.cha();//对应第8点
Date::cha();//对应第8点
return 0;
}
6.友元函数
前面讲过了,我们在类里面写一个operator<<很反人类,那有没有好的解决办法呢?
答案肯定是有的,答案就是友元函数了;
1.友元函数可以直接访问类的私有成员,它是定义在类外面的普通函数,不属于任何类,
但需要在类的内部声明,声明时需要加上friend关键字。(sizeof不算友缘函数大小,同时类的对象也不能调用该函数)
2.友元函数可以访问类的私有和保护成员,但不是类的成员函数
3.友元函数不能用const修斯,不是成员函数,哪来的this指针?
4.友元函数可以在类里面的任意一个地方定义声明,不受类访问符的限制
5.一个函数可以是多个类的友元函数
6.友元函数的调用和普通函数调用原理相同
其实在C++里面友元函数用的不多,因为友元函数会破会封装性
class Date
{
public:
Date(int year, int month, int day)
:_month(month)
, _day(day)
, _year(year)
{}
private:
friend void print(Date& x);//不受访问限定符影响
int _year = 3;
int _month = 1;
int _day = 3;
};
void print(Date &x)
//友元函数可以访问类里面的私有成员,由于不是类里面的成员,因此不用Date::了
{
cout <<x._day<<x. _month<<x._year<<endl;
}
int main(void) {
Date d1(2005,4,14);
print(d1);
return 0;
}
7.友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
1.友元是关系是单向的,不具有交换性
2.友元关系不能传递,
3.友元关系不能继承
class Time
{
friend class Date;
public:
Time(int hour, int minute, int second)
{
_hour = hour;
_minute = minute;
_second = second;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
friend class Time;
Date(int year, int month, int day)
:_month(month)
, _day(day)
, _year(year)
{}
public:
void print(Time& x)
{
cout <<x._hour<<"-" << x._minute << "-" << x._second << endl;
}
private:
int _year = 3;
int _month = 1;
int _day = 3;
};
int main(void) {
Date d1(2005,4,14);
Time t1(11, 34, 56);
d1.print(t1);
return 0;
}
8.内部类
概念:如果一个类定义在另一个类的内部,,这个类叫做内部类,内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员,外部类对内部类没有任何优越的访问权限
注意:内部类就是外部类的友元类,但是外部类不是内部类的友元
特性:1.内部类定义在外部类的protect,private,public都是可以的(且会受限定符限制,因为是定义不是声明)
2.内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
3.sizeof(外部类)=外部类,和内部类没有任何关系
4.类里面定义出来的东西才会被访问限定符限定,但是声明不会
5.内部类不能直接定义,要A::B b(B是A的内部域)(前提是public的)
class Date
{
public:
class Time
{
public:
Time(int hour, int minute, int second)
{
_hour = hour;
_minute = minute;
_second = second;
}
private:
int _hour;
int _minute;
int _second;
};
public:
friend class Time;
Date(int year, int month, int day)
:_month(month)
, _day(day)
, _year(year)
{}
private:
int _year = 3;
int _month = 1;
int _day = 3;
};
int main(void) {
Date d1(2005,4,14);
Date::Time t1(11, 34, 56);
return 0;
}
8.匿名对象
class Date
{
public:
friend class Time;
Date(int year=2000, int month=3, int day=4)
:_month(month)
, _day(day)
, _year(year)
{
cout << "hi" << endl;
}
public:
void print()
{
cout << "hello" << endl;
}
private:
int _year = 3;
int _month = 1;
int _day = 3;
};
int main(void) {
Date d1;//不能加括号的原因是和函数的声明无法分清
Date(2003, 3, 2);//匿名对象 会调用构造函数
Date().print();//匿名对象函数调用 会调用构造函数 加括号的原因是类型不能调用函数
//匿名对象即用即销毁,语句执行完销毁
//匿名对象和普通对象一样传参,只是没有名字
Date& ra = Date(2004, 1, 09);//匿名对象具有常性
const Date&ra=Date(2004,2,3);//const引用延长了生命周期,生命周期在当前函数局部域
return 0;
}