一、初始化列表
1.1什么是初始化列表,他是用来干什么的
初始化列表可以理解为每个对象中成员定义的地方,它的用处可以顾名思义,也就是对类中的成员进行初始化
1.2初始化列表的使用举例
当我们希望用栈实现队列的时候,我们需要创建这样一个类
class MyQueue
{
Stack _pushst;
Stack _popst;
int _size;
}
我们已知的是如果我们不显式写构造函数,那么内置类型会给一个随机值,而自定义类型会自动调用它的默认构造函数,那么问题来了:
如果没有默认构造函数呢?也就是在Satck类中我们显式写了构造函数,但是它含有无缺省值的参数,那么我们要怎样让MyQueue类正常进行构造呢?
答:我们可以选择显式实现MyQueue的构造函数,在过程中使用初始化列表:
在构造函数参数列表之后写一个用:(冒号)开始并且用,(逗号)结束,格式为成员名加括号的几条语句,例如
MyQueue(int n=20)
:_pushst(n)
,_popst(n)
,_size(0)
{
//...
}
此时我们给了Stack类进行构造所需参数,自然可以完成MyQueue的构造了。
1.2补:
初始化列表与函数体内赋值可以混用,但习惯上主要用初始化列表
1.3三种必须要使用初始化列表的情况
1.3.1前两种
const变量和引用变量,如:
private:
const int _a;
int& _af;
1.3.1补:
①const变量必须在定义时赋值
②引用变量必须在定义时赋值
1.3.2后一种(对应1.2中的例子)
没有默认构造函数的自定义类型成员,如1.2中的Stack
1.4不写初始化列表的时候会发生的情况
初始化列表,不管你是否显式去写,所有的成员变量都会走一遍,内置类型给随机值,自定义类型自动调用其默认构造函数
1.4补:
C++11以后支持定义成员变量的时候给缺省值,其实本质就是把这个值提供给初始化列表,只是缺省值的优先度比较低,如果在初始化列表中显式写了成员变量的值,那么这时候缺省值会被忽略。
1.5为什么说初始化列表是"自由"的
在初始化列表的括号中,我们可以写一个表达式甚至进行函数调用,
例如:
{
//...
Stack(int cap)
:_top(2+3*5)
,_capacity((int*)malloc(sizeof(int)*cap))
,_size(10)
,_a({1,2})//第四个是2.3中介绍的多参数隐式类型转换
pravite:
int _top;
int* _capacity;
int _size;
A _a;
}
1.5补:
既然缺省值是提供给初始化列表的,而初始化列表“自由”,那么缺省值是否也“自由”呢?
是这样的,在给缺省值的时候,可以为函数或者表达式
如:
{
//...
Stack()
{}
//...
pravite:
int _top = 2+3*5;
int* _capacity = (int*)malloc(sizeof(int)*40);
int _size = 10;
A _a = {1,2};
}
1.6初始化列表对成员变量初始化的顺序
该顺序为类中变量的声明顺序,尤其是与在初始化列表中的顺序无关
1.6补:
类对象创建出来以后,成员变量在内存中的存储顺序与在类中声明顺序一致。
二、内置类型直接给类类型赋值的原理(隐式类型转换)
2.1单参数构造函数支持内置类型直接赋值
例如有一个int类型参数的构造函数,我们可以
class A
{
public:
//构造函数
A(int a = 0)
:_a(0)
{
cout<<"A(int a)"<<endl;
}
//拷贝构造函数
A(const A& aa)
{
cout<<"A(const A& aa)"<<endl;
}
private:
int _a;
}
int main()
{
//这是使用构造函数直接进行构造,打印的结果应为一个构造函数
A aa1(1);
//此处包含了int类型的3利用构造函数隐式类型转换为类类型中间变量,再利用拷贝构造函数
//把中间变量拷贝给aa3
//但是由于编译器的优化,打印结果依旧只有一个构造
A aa3=3;
//此处我们用raa接收了3利用构造函数隐式类型转换出来的类类型中间变量,因为中间变量具
//有常性,所以我们需要用const修饰一些类型才能顺利接收
const A& raa=3;
return 0;
}
代码进行了详细解释,但是这里涉及到了一个编译器的优化:
当编译器遇到了连续(所谓连续,就是像A aa3=3这种一个语句涵盖)的构造+拷贝构造,则直接优化为了一个单独的构造。
2.1补:
当前主流编译器大多都会这样优化,但是像VC6.0等等老式编译器可能不做优化,VS2022等很高级的编译器又会优化的更加彻底(不限于连续,跨行进行优化)
毕竟这一部分C++标准没有明确进行规定。
2.2可不可以拒绝进行隐式类型转换?
可以的,有一个关键字explicit,把它加到构造函数之前,那么这个构造函数将不再支持隐式类型转换,如:
explicit A
{
//...;
}
2.3单参数构造函数可以,那么多参数呢?
在C++11之后添加了支持,例如对
A(int a1,int a2)
{
//...
}
我们可以在main函数中
A aa1 = {1,2};
来实现同时满足两个参数,它起到的效果与单参数类似,先构造为临时变量再拷贝构造
三、关于静态static修饰成员变量和函数
3.1static修饰成员变量
3.1.1使用注意事项
①创建对象后,对象中只存非静态成员变量,不存静态成员变量(因为静态成员变量在静态区不在栈区)并且sizeof的结果也不包含静态成员变量
②静态成员变量不能给缺省值(因为静态成员变量不存在与对象中,所以不走初始化列表)
③必须要并且只能用声明定义的方式给初始值
3.1.2它的价值
静态成员变量属于整个类,属于所有成员,
当它为public属性的时候,还可以这样访问:
//任选对象直接访问
aa1._sta;
//用类类型加::来访问
A::_sta;
3.2static修饰成员函数
使用static修饰后的成员函数将不再有this指针,只能访问静态成员变量,关于它的价值,其一我们只能通过静态成员函数来访问静态成员变量,其二我们可以利用静态成员变量加静态成员函数来完成求1+2+...+n
四、关于类和对象的小知识点
4.1友元函数和友元类
关于友元函数:
作为友元的函数,不可以被const修饰
关于友元类:
(我是你的朋友,但你还不是我的朋友)
class Time
{
friend class Date;
//...
}
Date可以访问Time中的私有成员,但是Time不能访问Date中的,
这种访问可以像这样:
//(Date中)
Time _t;
_t._hour=hour;
直接创建类类型对象,修改对象中的私有成员变量值
4.1补:
①友元不能传递:A为B的友元,B为C的友元,不能说明A为C的友元
②友元是单向的
③友元不能继承
4.2内部类
假如在类A中写了类B,那么
①他们是独立的类,仅仅是B受到A的类域和访问限定符的限制
A aa1;
A::B b1;
②B天生是A的友元,可以访问A中的私有成员
4.3匿名对象
匿名就是不起名,如
//默认构造创建对象
A aa1;
//传参数2构造对象
A aa2(2);
//匿名对象
A (2);
匿名对象的生命周期只在当前这一行,过了这一行就销毁
4.3补:匿名对象和单参数构造函数创建对象的特殊传参
当写了一个函数f1时,我们要传入其中类类型参数
A aa1;
//正常传入,参数列表可以为A& aa
f1(aa1);
//认为匿名对象具有常性,需要参数列表为const A& aa
f1(A(2));
//单参数构造函数把2隐式类型转换为中间变量,需要参数列表为const A& aa
f1(2);
若要正常进行下去,那么f1需要参数列表为
void f1(const A& aa)
{
//...
}
4.4优化
对于连续一行的表达式,拷贝构造+拷贝构造会被优化为一个拷贝构造,请看代码
A f2()
{
A aa;
return aa;
}
int main()
{
A ret = f2();
return 0;
}
分析可知,创建了aa对象,要返回的时候需要拷贝构造为一个中间变量,中间变量再进行第二次拷贝构造赋值给ret,而编译器在遇到这样的代码时,会自动将两个拷贝构造合为一个,节省资源加快速度。
注:把A ret = f2()改为两行
A ret;
ret = f2();
的时候优化将不再进行,所以我们最好不要这么做