目录
取地址及const取地址操作符重载
这是类中六个默认成员函数的另外两个
这两个默认成员函数一般不用重新定义 ,编译器默认会生成
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ;
int _month ;
int _day ;
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容
这里的两个成员函数是构成函数重载的。
上面的函数的参数是
Date* this
;下面的函数的参数是const Date* this
<<和>>运算符重载
<<运算符重载
<<
被称为流插入。cout<<d1
的意思就是:d1流到cout中。
cout<<d1
中的<<
是一个运算符重载,cout
是一个类对象
cout是ostream类的全局对象,ostream在头文件iostream中
在C++中除了赋值运算符和取地址运算符可以直接用于自定义类型,其他运算都不可以
ostream对于常见的内置类型变量都进行了<<运算符的重载
int i;
double d;
cout<<i; //cout.operator<<(i)
cout<<double; //cout.operator<<(d)
<<运算符重载的实现位置
对于<<运算符重载,通常实现在类外
因为如果实现在类内的话,以日期类为例,假如我要打印d1那么代码为:d1<<cout
,尽管效果能实现,但这看着很变扭。
因为<<运算符重载写入日期类,那么日期类对象d1就默认为第一个参数,也就是左操作数。
但也可以将<<运算符重载放在类外,这样在传参时,就可以将第一个参数传为:cout。那么<<的左操作数也将为cout
即:void operator<<(ostream& out,const Date& d)
随之而来的问题是,d中的私有成员无法访问,即_year、_month、_day
访问不到。这里有两种做法:一是在类中提供公有函数:GetYear()
等;二位是用友元函数。在C++中,一般喜欢用友元函数
即在类的public中写上:friend void operator<<(ostream& out,const Date& d)
只是在类中加上这句代码。operator<<的声明和定义还是在类外
<<的连续输出
cout<<d1<<d2<<endl
想实现上面的连续输出,就得在<<重载函数的返回值上下功夫
我们想实现的效果是:<<从左往右结合,先cout<<d1
,然后返回cout
,再cout<<d2
那么我们只需要将返回值改成ostream&
,并在函数内部返回out
即可
ostream& operator<<(ostream& out,const Date& d)
{
out<< d._year<<"年"<<d._month<<"月"<<d._day<<"日"<<endl;
return out;
}
>>运算符重载
>>
和<<
一样
cin是istream类的全局对象,istream也在头文件iostream中
istream& operator>>(istream& in,const Date& d)
{
in>> d._year>>d._month>>d._day;
return in;
}
一些细碎知识点
explicit关键字
构造函数的隐式类型转换
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用
class A
{
public:
//构造函数
A(int a):_a1(a)
{
cout << "A(int a)" << endl;
}
//拷贝构造函数
A(const A& a):_a1(a._a1)
{
cout << "A(const A& a" << endl;
}
private:
int _a1;
};
int main()
{
A aa1(1);
A aa2 = 1;
return 0;
}
运行结果:
按上面所说:这里的A aa2 = 1
是一个隐式类型转换,其过程为:先用1
构造生成一个隐式临时对象,再将这个临时对象拷贝构造给aa2
那为什么最终没有显示调用了拷贝构造函数呢?这是因为编译器进行了优化,让A aa2 = 1
的构造过程和A aa1(1)
一样了
一般较新的编译器会进行优化
注意:假如有这样一个对象const A& ref = 10
,此时是不会发生优化的。这里的过程就是用10先构造生成一个临时对象,再将这个临时对象引用给ref。因为临时对象具有常性,所以需要加const
上面所讲是在C++98中就支持的,但C++98中不支持多参数构造函数的类型转换
到了C++11中,支持了多参数构造函数的类型转换
写法:
A aa3 = {2,2}
explicit关键字
而上述将的隐式类型转换,只要在构造函数前面加上explicit
关键字即可禁止
class A
{
public:
//构造函数
A(int a):_a1(a)
{
cout << "A(int a)" << endl;
}
//拷贝构造函数
A(const A& a):_a1(a._a1)
{
cout << "A(const A& a" << endl;
}
private:
int _a1;
};
int main()
{
A aa1(1);
A aa2 = 1;
return 0;
}
静态成员
用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数
静态成员变量
特性:
- 静态成员为所有类对象所共享,不属于某个具体的对象,属于整个类,存放在静态区
- 静态成员变量必须在类外定义和初始化,定义时不添加static关键字,类中只是声明
- 静态成员变量即可用
类名::静态成员变量
或者对象.静态成员变量
来访问 - 静态成员变量也是类的成员,受public、protected、private 访问限定符的限制
class A
{
private:
static int count;
};
//静态成员变量的定义和初始化
int A::count=0;
int main()
{
A aa1;
//静态成员变量的调用
cout<<A::count<<endl;
cout<<a1.count<<endl;
return 0;
}
静态成员函数
特性:
-
静态成员函数没有this指针,所以不能访问非静态成员变量/函数。因为没有this指针,非静态的成员都是属于某个对象的,而属于对象的成员有this指针的
-
因为静态成员函数没有this指针,所以调用其,只能通过类域直接调用,不能用对象调用
但非静态成员函数可以调用静态成员函数
-
静态成员函数也是类的成员,受public、protected、private 访问限定符的限制
-
一般要访问静态成员变量时,就可以使用静态成员函数
//续接上面的类A
class A
{
//……
static int GetCount()
{
return count;
}
};
友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用
友元函数
友元函数是定义在类外的普通函数,不属于任何类,但由于其需要调用某个类中的私有成员,所以会将该函数声明为该类的友元函数。声明为友元函数后,该函数可以直接访问该类的一切成员
如:
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
//……
};
说明:
-
友元函数可访问类的私有和保护成员,但不是类的成员函数
-
友元函数不能用const修饰。因为const修饰的是
*this
,而友元函数不是成员函数,所以没有this指针const只能修饰非静态成员函数
-
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
-
一个函数可以是多个类的友元函数
-
友元函数的调用与普通函数的调用原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
class Time
{
friend class Date;
//……
};
-
友元是单向好友
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行
-
友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元
-
友元关系不能继承
内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类
内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。
但反过来,内部类是外部类的友元类。内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元
特性:
-
内部类可以定义在外部类的public、protected、private都是可以的。但在定义内部类对象时受外部类的类域限制
当定义B的对象时,需要这样:
A::B bb
,而且需要类B是public下的 -
内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
-
sizeof(外部类)=外部类,也就是说内外部类是空间独立的
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();--err 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
A();
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
//匿名对象调用函数
Solution().Sum_Solution(10);
// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
return 0;
}
此外,用匿名对象作为返回值,也很方便
拷贝对象时的一些编译器优化
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的
优化不是说所有编译器都会这样做,但只要是新一点的就都会这样做
优化只能在一个表达式中优化,不能跨表达式优化
class A
{
public:
//构造函数
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
//拷贝构造
A(const A& aa) :_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
//赋值运算符重载
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
//析构函数
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
A f3()
{
return A();
}
int main()
{
A aa1 = 1; //构造+拷贝构造->优化为直接构造
cout << endl;
f1(1); //构造+拷贝构造->优化为直接构造
f1(A(2)); //构造+拷贝构造->优化为直接构造
//这里两个表达式都是传值传参,所以会优化,假如是传址传参则不会优化
//上面是传参优化
//-----------------------------------------------------
//下面是传返回值优化
A aa2 = f2(); //连续拷贝构造+拷贝构造->优化一个拷贝构造
A aa3 = f3(); //构造+拷贝构造+拷贝构造->优化为一个直接构造
// 一个表达式中,连续拷贝构造+赋值重载->无法优化
A aa4;
aa4 = f2(); //连续拷贝构造+赋值运算符重载->无法优化
return 0;
}
总结:
-
一个表达式中,如果构造+连续拷贝构造->优化为一个直接构造
-
一个表达式中,连续拷贝构造+拷贝构造->优化为一个拷贝构造
-
一个表达式中,连续拷贝构造+赋值重载->无法优化
总结:
- 函数传参时:尽量使用const &传参
- 对象返回时:
- 接收返回值对象,尽量使用拷贝构造函数接收,不要赋值接收
- 函数中返回对象时,尽量返回匿名对象