目录
一.流插入、流提取
流插入cout是ostream类型的对象,流提取cin是istream类型的对象。为了方便自定义类型的输入输出,需要我们对'<<'、'>>'进行重载。
要注意的是类的成员函数都有一个隐藏参数this指针作为第一个参数,如果按照这个逻辑写的话就是
ostream& operator<<(ostream& out) {
printf("%4d年%02d月%02d日", _year, _month, _day);
cout << endl;
return out;
}
int main() {
Date d1(2024,2,21);
d1.operator<< (cout);
return 0;
}
这样写必须要保证日期类的对象是第一个参数,所以必须写在左边,这和常用的<<不符合。那能在声明形参时把this指针顺序放在后面吗?不能!!!但是又不能把他做成全局的(一是会改变原来的含义,二是类外不允许访问私有成员)
只有一个办法:友元声明。把全局函数变成类的好友,让他可以使用类的所有成员,但是没有this指针,所以能实现正常的流插入顺序。
友元声明就是在函数之前加一个friend,并且函数写在类里面。
提个问题:为什么要返回ostream类型的引用?
想一想上期讲的赋值重载。
或许有人会有疑问:为什么要返回该对象的引用呢?如果我们写成这样
void operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; }
那么如何解释d1=d2=d3?显然,要实现完整的功能,连续赋值,就要返回被赋值的对象。
那再想想,能不能传值返回?显然对于d1=d2=d3是可以的,但是如果遇上(d1=d2)=d3呢?要知道传值返回是返回创建的临时对象,而临时变量是不能做左值的(不能被修改),所以必须返回引用,使其能做被被修改的左值。
所以,类似地,为了解决连续流插入的问题,需要返回控制台一直做第一个参数
cout << d1 << d2 << endl;
流提取也是同理,重载并友元声明 :
friend istream& operator>>(istream& in, Date& d) {
cin >> d._year >> d._month >> d._day;
return in;
}
Date d2();
cin >> d2;
二.友元函数
接着上面提出的话题讲,关于友元函数和友元类。
问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成员函数。因为cout的
输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作
数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成
全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数(声明在内部),不属于任何类,但是需要在类内部加友元声明,声明时需要加friend关键字。
- 友元函数可以访问类的私有成员,但不是类的成员函数
- 友元函数不能加const限制
- 友元函数可以在类定义的任何地方声明,不受访问限定符的限制
- 一个函数可以是多个类的友元函数(这个函数可以用很多资源)
- 友元函数的调用和普通函数的调用一样
三.内部类
如果一个类定义在另一个类的内部,这个类就叫做内部类。它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访
问外部类中的所有成员。但是外部类不是内部类的友元。(你拿别人当朋友,别人不拿你当朋友)
class A {
public:
class B {
private:
int _b;
};
private:
int _a;
};
内部类和全局类的定义没区别,sizeof外部类大小不计算内部类。所以sizeof(A)是4不是8。
四.初始化列表
1.初始化列表
调用构造函数时什么时候是定义成员变量?
Date(int year = 2024, int month = 2, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
是在函数体内部吗?那这样写算不算重定义呢?
Date(int year = 2024, int month = 2, int day = 21)
{
_year = year;
_month = month;
_day = day;
_year = 1;
}
如果是重定义的话就会报错。但是没有。原来,c++祖师爷为了解决这个问题发明了初始化列表的写法。就是在构造函数函数体前用括号内的值对成员变量初始化,这里也就是变量最开始定义的地方
Date(int year = 2024, int month = 2, int day = 21)
:_year(year)
,_month(month)
,_day(day)
{}
注意在构造函数时有几个地方可以缺省,但是有不同的含义和顺序。
class Date {
private:
//声明
int _year=2005;
int _month=12;
int _day = 18;//这里是作为初始化的缺省,如果初始化没有给值,就用缺省值
//如果没有缺省值就得到随机值
public:
Date(int year = 2024, int month = 2, int day = 21)//定义的形参是函数体赋值修改的缺省
:_year(2)
, _month(3)
//_day没有给值,就用缺省值18初始化
{
_year = year;
_month = month;
_day = day;//函数体内部时对已经初始化的变量赋值修改
}
};
//构造结果是2024——2——21;
列表初始化有以下几点特征:
- 初始化的顺序是变量声明的顺序
- 就算没写括号外面的也要先走括号外面的(得到随机值)
- 先初始化,再赋值修改
- 引用、const变量、自定义类型成员(且该类没有默认构造函数)必须初始化(必须给值)
无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。总结一下,不需要传参就可以调用构造函数,都可以叫默认构造函数。所以如果不传参时写了构造函数却不写缺省值编译器就会报错。
class A
{
public:
A(int a) {
_a = a;
}
private:
int _a;
};
class Date {
private:
int _year=2005;
int _month=12;
int _day = 18;
A _aa;
public:
Date(int year = 2024, int month = 2, int day = 21)
:_year(2)
, _month(3)
,_aa(23)
{}
};
2.隐式类型转换
c++支持单参数隐式类型转换
class A {
private:
int _a;
public:
A(int a = 10) :_a(a)
{}
};
int main() {
A aa1(2);
A aa2 = 4;
int i=8;
double d=i;
return 0;
}
隐式类型转换:4会先构造一个对象,然后再拷贝构造。
但是结果不是这样,编译器并不会调用拷贝构造,而是直接调用构造函数。这是编译器的一个优化。一般连续几个步骤的构造会被合二为一
五.静态对象
静态变量只会申请一次,声明写在类里面,不是某一个对象的成员,而是所有对象共有的
而定义要单独写,当作是初始化(static变量必须初始化)
这个可以用来计算创建了多少个对象。
class A {
private:
static int _n;
};
int A::_n=0;//初始化
比如这道题:求1+2+3+...+n__牛客网
class Sum{
public:
Sum(){
_ret+=_i;
_i++;
}
int getSum(){
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 a[n-1].getSum();
}
};
我们直接创建n个对象,每次调用析构函数都++i,再求总和。
六.匿名对象
有名字的对象叫有名对象,没名字的叫匿名对象。匿名对象生命周期只在当前一行。
意义:生来就是为了调用对象或对象的函数,不方便写名字
七.编译器优化
1.传参
构造(临时变量)+拷贝构造——》》直接构造
比如:隐式类型转换:这个是把2构造对象,然后拷贝构造给aa1,优化成了直接构造aa1
这个是直接引用常变量(临时变量具有常性)
本来构造匿名对象,然后拷贝构造给形参,优化成直接构造。因为匿名对象创建后会销毁,函数形参函数结束后也会销毁,就直接构造。
2.传返回值
-
A f1() { A aa; return aa;//一个构造,一个拷贝构造 } int main() { f1(); return 0; }
-
A f1() { A aa; return aa;//一个构造,两个拷贝构造————》》一个构造,一个拷贝构造 } int main() { A ret=f1(); return 0; }
两个拷贝构造合二为一
-
A f1() { return A();//一个构造,两个个拷贝构造————》》两个构造!!! } int main() { A ret=f1(); return 0; }
匿名对象直接三合一