这是第二讲的最终章了,这一讲结束,我们类和对象的讲解就暂时告一段落了。
首先,我们要再次探讨一下构造函数,先前我们讲默认成员函数的时候,已经介绍过构造函数,但当时并没有说完,今天我们就介绍完。
这次再谈构造函数,主要是讲解一下初始化列表。之前我们写构造函数的时候,初始化成员变量都是在函数体内赋值,初始化列表则是另一种赋值方式,与其说是赋值,倒不如说是定义加初始化。
我来简单演示一下初始化列表的使用方法。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year=1,int month=1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{
}
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
d.Print();
return 0;
}

初始化列表语法是以:开始,分割,括号内跟着自己传的参数,也可以是表达式。
C++中成员变量初始化是一定会走初始化列表的,即使你没有写初始化列表。
这里我们分几种情况,对于有初始化列表的成员变量,就不过多赘述了,对于没有的,编译器首先会去查看声明成员变量的位置有没有缺省值,如果有就把缺省值赋值给成员变量,如果没有,对于内置类型,取决于编译器,对于自定义类型,调用它默认的构造函数,如果没有则报错。
对于引用变量或者const变量或是没有默认构造函数的类类型变量,必须在初始化列表完成初始化。
但是这里又有一点绕,大家看这段代码
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Date
{
public:
Date(int& a,int year=1,int month=1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{
}
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
cout << _a << ' ' << _b;
}
private:
int _year;
int _month;
int _day;
int& _a=_day;
const int _b=10;
};
int main()
{
int a = 3;
Date d(a);
d.Print();
return 0;
}

你看我引用对象和const也没走初始化列表啊,这也没报错啊?但其实,C++11引入了一个新的规则,支持我们在成员变量的声明位置给缺省值,假如我们没有写对应参数的初始化列表,就会通过这个缺省值初始化,其实也还是走了初始化列表,只不过省略了这一步,并且传了之前就给好的缺省值。
所以说我们建议所有成员变量都通过初始化列表初始化,因为你不写他也会走初始化列表。
初始化列表的初始化顺序依据成员变量在类中的声明顺序,和你初始化列表的出现顺序无关。
总结一下:不论你写不写初始化列表,每个构造函数都有初始化列表,不论是否在初始化列表显式初始化成员变量,每个成员变量都要走初始化列表初始化。
接下来讲一下类型转换
C++支持内置类型隐式转换为类类型对象,需要有相关内置类型为参数的构造函数。
举个例子
cpp
class A
{
public:
A(int a = 10)
:_a(a)
{
}
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A b = 15;
b.Print();
return 0;
}
这里构造函数写不写缺省值都是无所谓的。
当我们给构造函数前加上explicit就不再支持隐式类型转化。
类类型的对象之间也可以隐式类型转换,但需要相应的构造函数支持。
C++11之后也支持多参数转化,需要用{}括起
cpp
class A
{
public:
A(int a,int b)
:_a(a)
,_b(b)
{
}
void Print()
{
cout << _a << endl;
cout << _b << endl;
}
private:
int _a;
int _b;
};
int main()
{
A b = { 12,19 };
b.Print();
return 0;
}
接下来介绍一下static成员
用static修饰的成员变量,称为静态成员变量,静态成员变量一定要在类外进行初始化。
静态成员变量不依赖于具体对象,他被类所共享,存放在静态区,不在对象之中。
cpp
class B
{
public:
static void Print();
private:
static int _a;
};
void B::Print()
{
cout << _a << endl;
}
int B::_a = 10;
int main()
{
B::Print();
return 0;
}
同理,用static修饰的成员函数称为静态成员函数,静态成员函数因为不依赖于某个对象所以没有this指针。
静态成员函数可以访问其他的静态成员变量,但是不能访问非静态成员变量,因为没有this指针,但是非静态的成员函数就可以访问静态成员变量,同理对象也可以调用静态成员函数。
突破类域就可以访问静态成员函数,假如没有访问修饰限定符的限制,同理也可以访问成员变量,突破类域用类名::即可。
虽然,类中不存放静态成员变量,但静态成员变量仍是类的成员,受访问修饰限定符的约束。
静态成员变量不走初始化列表,也不能通过缺省值的方式初始化,因为缺省值本质也是走了初始化列表。
接下来介绍友元的概念。
之前其实提到过,友元就是加friend嘛。
所谓友元就是提供了一种突破访问修饰限定符的方法,友元分为友元函数和友元类,把这个函数的声明或是类的声明放到类的某个位置,前边加上friend即可达到效果。
友元函数或类虽然在类中写有声明,但并不是该类的成员!
一个函数可以是多个类的友元。
友元类(加friend的那个类)中所有的成员函数都被视为另一个类的友元函数,可以调用他们的所有私有变量或方法。
友元类不具有交换性,比如a是b的友元,反之不成立
友元类也不具有传递性,a是b的友元,b是c的友元,但a不是c的友元。
友元虽然提供了便利,但也增加了耦合度,所以不宜多用。
接着讲一下内部类。
顾名思义内部类就是定义在类内部的类,但是内部类是一个独立的类,与你在全局定义相比,就是被类域和访问修饰限定符约束。所以外部类的成员并不包括内部类。
内部类默认是外部类的友元类。
最后说一下匿名对象
用类型定义出的没有名字的对象就是匿名对象,匿名对象的生命周期和普通不同,只在本行。
至此,我们已经讲完了90%的类和对象内容,下一讲开头我会补充一下对象拷贝时的编译器优化,这一讲结束。