const 成员函数
const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
为什么要用const进行修饰
使用const修饰主要就是权限放大缩小的问题,这里我们能看见这里显示报错,那么报错的报错的原因就是权限的放大,这里传入的时候限制是不能改变指向的内容,而接收的权限是可以改变指向的内容,因此涉及权限放大,为了解决这一问题只需要在函数的后面加上const即可把Data *const this ——>const Data *const this。
const修饰的函数优缺点
通过const进行修改后,普通类型的也可以进行访问被const修饰的函数。这并不意味着所有的都可以这样设置,因为经过修饰后权限被缩小,只能读所指向的内容并不能所指向的内容进行修改。
案例如下:
取地址运算符重载
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
再探构造函数
初始化列表
初始化列表的使用方式式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式。注意:每个成员变量在初始化列表中只能出现⼀次。
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
必须使用初始化列表的三种情况
引用成员变量,const成员变量,没有默认构造的类类型成员变量。为什么以上三种要用初始化列表,因为引用必须在定义的时候进行初始化,const也是在初始化的那一刻才能进行初值的改变,没有默认构造,调用的时候需要传参,传参就要初始化,初始化只能在初始化列表中,因为在函数体成员可以出现多次那么到底那个是初始化,这就产出了问题,所以通俗说初始化列表可以认为是每个成员变量定义初始化的地方。
面试题分析
初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。此题先按照声明的顺序,先执行_a2(),此时a1还未进行传入,这时_a2就会显示随机值,_a1为1。
初始化列表总结
类型转换
C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。如我们学过的指针(虽然存放的是地址,也是有编号)可以和数据类型进行相互转换。
什么是隐式类型转换
A
类只有一个单参数的构造函数,因此该构造函数是支持隐式类型转换的,A aa1 = 1;
本质上就是隐式类型转换,首先在隐式转换过程中会产生一个临时的中间变量,用1去调用构造函数,得到一个A
类型的临时中间变量,然后再用这个A类型的中间变量去调用拷贝构造,最终完成aa1
的创建。
#include<iostream>
using namespace std;
class A
{
public:
// 构造函数explicit就不再⽀持隐式类型转换
// explicit A(int a1)
A(int a1)
:_a1(a1)
{}
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1 = 1;
int _a2 = 2;
};
int main()
{
A aa1 = 1;
aa1.Print();
//用于检测是否创建了中间变量
//临时对象具有常性,不能放大权限,如果创建了不加const限定就会报错
//const A& aa2 = 1;
return 0;
}
explicit的用法
构造函数前加explicit就不再支持隐式类型转换
用法如下:explicit A(int a1)
static关键字
static成员变量的用法
- 用static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进行初始化。在类外初始化要记得加上类域这要就知道是静态成员变量。
- 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
- 静态成员也是类的成员,受public、protected、private访问限定符的限制。
- 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是构造函数初始化列表的,静态成员 变量不属于某个对象,不⾛构造函数初始化列表。
static成员函数的特点
- 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
- 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。
- 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
- 突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量 和静态成员函数。
面试小案列
构造函数的调用顺序是 :先全局然后按照声明的顺序进行构造函数的调用,因此构造函数的调用 顺序为: C A B D。
析构函数调用的顺序:按照后构造的先析构,最后析构全局的构造函数。所以顺序为:B A D C。
友员补充
友元函数
- 在函数声明或者类 声明的前面加friend,并且把友元声明放到⼀个类的里面。
- 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
- ⼀个函数可以是多个类的友元函数。
友元类
- 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
- 友元类的关系是单向的,不具有交换性,如A类是B类的友元,但是B类不是A类的友元。
- 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。
- 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
内部类
内部类就是把一个类写在另一个类的里面,这种就叫做内部类。他只是受外部类类域限制和访问限定符限制。内部类并不占用所写入类的字节大小。
注意:内部类默认是外部类的友元类。因此在访问静态成员变量的时候可以直接使用,普通的成员需要引用外部类名。内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用。在内部类上加上private/protected专属内部类,其他地方就无法使用了。
#include<iostream>
using namespace std;
class A
{
private:
static int _k;
int _h = 1;
public:
class B // B默认就是A的友元
{
public:
void foo(const A& a)
{
cout << _k << endl;
cout << a._h << endl;
}
private:
int b=1;
};
};
int A::_k = 1;
int main()
{
cout << sizeof(A) << endl;
A::B b;
A aa;
b.foo(aa);
return 0;
}
匿名对象
没有名字的对象通常写成类型(实参)。匿名对象生命周期只在当前一行,而有名对象的生命周期是这个main()函数调用结束。匿名对象可以引用,引用后就具有了常性,需要加const加完const之后生命周期变长。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout <<"A(int a)"<<endl;
}
~A()
{
cout <<"~A()"<<endl;
private:
int _a;
};
int main()
{
//匿名对象的使用方式
A();
A(1);
}