文章目录
- 1. 初始化列表
- 1.1 什么是初始化列表
- 1.2 为什么要引入初始化列表
- 1.3 初始化列表如何写
- 1.3.1 显式写初始化列表
- 1.3.2 隐式写初始化列表
- 1.3.2.1 不声明缺省值
- 1.3.2.2 声明缺省值
- 1.3.3 初始化列表总结
- 2.类型转换
- 2.1 内置类型与自定义类型转换
- 2.2 自定义类型间的转换
- 2.3 构造函数被explicit修饰
- 3. 友元
- 3.1 友元函数
- 3.1 友元类
- 4.static成员
- 4.1 static修饰成员变量
- 4.2 static修饰成员函数
- 5. 内部类
- 6. 匿名对象
- 6.1 匿名对象解析
- 6.2 匿名对象生命周期延长
1. 初始化列表
1.1 什么是初始化列表
初始化列表,是所有成员变量定义初始化的地方(此处的定义不再开空间,在对象创建时,空间便已开辟)。
初始化列表与构造函数结合,调用构造函数时,先走初始化列表,再走构造函数体。
初始化列表的格式如下:
1.2 为什么要引入初始化列表
引入初始化列表,主要是考虑到两类特殊情况:
- 自定义类的成员变量不含默认构造。C++规定,自定义类的对象在创建时就要初始化,即调用构造函数。而自定义类的成员变量会调用它的默认构造,但有些时候,不存在默认构造,此时该成员变量的初始化就会出现问题,这时便要使用初始化列表。
- 被const修饰的内置类型成员变量。这类成员变量同样要在定义的时候初始化,一旦初始化,便不可更改,否则会报错。而初始化列表,可以理解为变量定义并初始化的地方,所以这些被const修饰的内置类型成员变量,必须在初始化列表中初始化。
1.3 初始化列表如何写
初始化列表可以显式写,也可以隐式写。
1.3.1 显式写初始化列表
显式写初始化列表时,有以下三点需要注意:
- 每个成员变量在初始化列表中只能出现一次,不能重复出现
- 在初始化列表中,成员变量初始化的顺序并不是按照初始化列表中排列的顺序,而是按照成员变量声明的顺序,更准确地来说,是按照成员变量在内存中的存储顺序(先声明的,存储在前;后声明的,存储在后),其中先声明的,先初始化,后声明的,后初始化。所以,考虑到这点,成员变量在初始化列表的顺序应尽量与其声明顺序保持一致。
- 若成员变量中含有以下三类:const修饰的内置类型,引用,无默认构造的自定义类型——这三类,在未声明缺省值时,一定要显式在初始化列表中写出。
1.3.2 隐式写初始化列表
1.3.2.1 不声明缺省值
在不声明缺省值的情况下,能够不在初始化列表中写出的成员变量只有不被const修饰的内置类型成员变量以及含默认构造的自定义类型成员变量。 此时,这些成员变量虽然没有写出,但本质上依旧会走初始化列表——至于这时,初始化列表如何对这些成员变量进行初始化,C++是没有规定的,看编译器。
1.3.2.2 声明缺省值
在声明缺省值的情况下,所有的成员变量都可以不在初始化列表中写出。这个缺省值的作用是,若某个成员变量未在初始化列表中写出时,也就是程序员未主动对该成员变量初始化,此时,初始化列表便用该缺省值对该成员变量进行初始化。
需要注意的是,缺省值只用于未显式在初始化列表中写出的成员变量,显式在初始化列表中初始化的成员变量,用显式给出的值进行初始化。
1.3.3 初始化列表总结
我们在写初始化列表时,尽量保持形式的一致,即对于成员变量,要么全部显式初始化,要么全部给缺省值,隐式初始化,在允许的情况下,尽量不要混着写。
2.类型转换
内置类型与内置类型之间存在类型转换,同样,内置类型与自定义类型之间,自定义类型之间也存在类型转换,而这个类型转换,本质上是通过类的构造函数实现的。
2.1 内置类型与自定义类型转换
在创建一个类的对象时,往往可以看到下面的创建方式:
这种对象的初始化方式并不是直接调用构造函数初始化那么简单。
实质上,这中间存在一个临时对象的过渡。首先,编译器会生成一个AA类的临时对象,并将1传给该临时对象的构造函数进行初始化;然后,这个临时对象再通过拷贝构造,对我们创建的AA类对象sample进行初始化。所以,实际上有两个过程:调用普通构造,然后再调用拷贝构造。但实际上,编译器对于“构造+拷贝构造”会优化为直接构造,即直接将1传给构造函数,对sample进行初始化。
注:当构造函数需要多个参数时,使用花括号,如下所示:
2.2 自定义类型间的转换
自定义类型间的转换,同样借助类的构造函数实现。类型转换的过程同上,同样是构造+拷贝构造,同样会被编译器优化为直接构造。
同上,当构造函数需要多个参数时,使用花括号:
2.3 构造函数被explicit修饰
构造函数被explicit修饰时,此时就不再支持隐式类型转换,但显式类型转换仍然支持。
3. 友元
友元的关键字为friend,分为两种:友元函数和友元类。
3.1 友元函数
友元函数的声明,目的是为了在类外访问类设置为私有的成员变量,更准确地说,一个类的友元函数可以访问这个类中所有的成员。
但值得注意的是,友元函数仅是声明为友元,它不会使得这个函数变为该类的成员函数。同时,友元声明可以放在一个类的任何地方,放在类的何处对友元声明无影响,但我们一般习惯将友元声明放在类的开头。
3.1 友元类
一个类也可以声明为另一个类的友元,尤其当一个类中要频繁地用到另一个类中的成员,此时声明为友元类,会带来极大的方便。
需要注意的是,友元类的关系是单向的,且不具备传递性。举个例子,A是B的友元,但B不是A的友元;再举个例子,A是B的友元,B是C的友元,但A不是C的友元。所以,看友元类,关键就是看哪个类在哪个类中声明为友元,重点看声明。
但是,凡事有利必有弊。友元虽然很方便,但还是不宜多用的,因为声明为友元会增加耦合度,破坏了类的封装性。
4.static成员
4.1 static修饰成员变量
static修饰的成员变量为静态成员变量,它不为某个对象私有,而是为该类实例化出的所有对象共有,这就决定了静态成员变量的几个特性:
- 静态成员变量放在静态区,而不是放在某个对象中,因此在计算类的大小时,静态成员变量不包含在内。
- 静态成员变量定义初始化不通过初始化列表,所以其必须在类外定义初始化,类中所写,仅为静态成员变量的声明。
- 静态成员变量仍是类的成员变量,仍然受到public/private/protected的影响,允许类外访问时,仍要突破类域,才可访问——两种方式突破类域:使用域作用限定符或使用成员访问操作符。
4.2 static修饰成员函数
static修饰成员函数,即为静态成员函数,此函数无this指针,因而致使有以下几个特性:
- 静态成员函数无this指针,导致此函数只能访问类中的静态成员变量,而非静态的成员变量无法访问。
- 静态成员函数仍是类的成员函数,仍然受到public/private/protected的影响,允许类外访问时,仍要突破类域,才可访问——两种方式突破类域:使用域作用限定符或使用成员访问操作符。
5. 内部类
一个类定义在另外一个类中,此时,我们把这个类称为内部类。内部类有以下几个特性:
- 内部类默认为外部类的友元类,可以自由访问外部类的成员。
- 内部类受到外部类中public/private/protected的限制,可以通过private将内部类限制为私有内部类。如过不是私有内部类的话,即允许外部类外使用,内部类需要通过域作用限定符才可以使用。
- 内部类本质上仍是一个独立类,计算外部类大小时,并不将内部类计算在内。同样,使用外部类实例化对象时,并不会在该对象内部再实例化出一个内部类对象。
总的来说,内部类是一种封装,如果A类实现出来就是为给B类使用,我们就可以将A类定义为B类的内部类,而且最好是私有内部类,B类外无法使用A类。从这个意义上讲,内部类一定程度上可看作友元类声明的优化——内部类维护了封装,友元类破坏了封装。
6. 匿名对象
C++中对象有两种:有名对象和匿名对象。有名对象即有变量名,匿名对象即无变量名。
6.1 匿名对象解析
匿名对象的生命周期只在当前语句,一旦当前语句执行完,进入下条语句时,匿名对象便会析构。
6.2 匿名对象生命周期延长
我们可以使用const引用来延长匿名对象的生命周期,至于为什么要加const,这是因为匿名对象具有常性。
其实严格来说,匿名对象生命周期延长后,已是一个有名对象,只不过该有名对象对应的内存空间即为匿名对象对应的内存空间。