C++类:特殊的数据成员
目录
特殊的数据成员
常量数据成员
引用数据成员
对象成员
编辑
构造顺序的强制性
静态数据成员
特殊的数据成员
在 C++ 的类中,有4种比较特殊的数据成员,分别是常量成员、引用成员、类对象成员和静态成员,它们的初始化与普通数据成员有所不同。
常量数据成员
当数据成员用 const 关键字进行修饰以后,就成为常量成员。一经初始化,该数据成员便具有“只读属性”,在程序中无法对其值修改。事实上,在构造函数体内对const 数据成员赋值是非法的,const数据成员需在初始化列表中进行初始化(C++11之后也允许在声明时就初始化)。
普通的const常量必须在声明时就初始化,初始化之后就不再允许修改值;
const成员初始化后也不再允许修改值。
class Point {
public:
Point(int ix, int iy)
: _ix(ix) //初始化列表本质就是进行初始化的
, _iy(iy)
{}
private:
const int _ix;
const int _iy;
};
引用数据成员
引用数据成员在初始化列表中进行初始化,C++11之后允许在声明时初始化(绑定)。
之前的学习中,我们知道了引用要绑定到已经存在的变量,引用成员同样如此。
class Point {
public:
Point(int ix, int iy)
: _ix(ix)
, _iy(iy)
, _iz(_ix)
{}
private:
const int _ix;
const int _iy;
int & _iz;
};
思考:构造函数再接收一个参数,用这个参数初始化引用成员可以吗?
_iz绑定传入的z,看起来虽然是确定的值,但是由于值传递会进行复制,所以实际上是去绑定一个临时变量,临时变量的生命周期只有函数,等到构造函数结束引用的指向就没了,就成为了一个悬空指针。
_iz绑定 _ix,因为数据成员的初始化顺序与声明顺序一致,此时 _ix已经完成了初始化,是一个确定的值,就没有问题。
我现在好奇Point 的大小是多少,我们来看一下,发现为16字节,证明&引用还是占用空间的,并且是一个指针的内存大小。
对象成员
有时候,一个类对象会作为另一个类对象的数据成员被使用。比如一个直线类Line对象中包含两个Point对象。
对象成员必须在初始化列表中进行初始化。
注意:
-
不能在声明对象成员时就去创建。
-
初始化列表中写的是需要被初始化的对象成员的名称,而不是对象成员的类名。
class Line {
public:
Line(int x1, int y1, int x2, int y2)
: _pt1(x1, y1)
, _pt2(x2, y2)
{
cout << "Line(int,int,int,int)" << endl;
}
private:
Point _pt1;
Point _pt2;
};
注意:
如果在Line类的构造函数的初始化列表中没有显式地初始化Point类对象成员,编译器会自动去调用Point类型的默认无参构造;
如果不想用Point的无参构造,那么必须在Line类的初始化列表中对Point类的对象成员进行初始化
构造顺序的强制性
-
当类的对象包含其他类的对象作为成员时,这些成员对象的构造函数会在主类的构造函数体执行前自动调用。
-
如果未在初始化列表中显式指定成员对象的构造函数,编译器会尝试调用它们的默认构造函数(无参构造函数)。若成员对象的类没有默认构造函数,会导致编译错误
-
class Point { public: Point(int x, int y); // 没有默认构造函数 }; class Line { Point p1; // 必须通过初始化列表构造 Point p2; public: Line(int x1, int y1, int x2, int y2) : p1(x1, y1), p2(x2, y2) {} // 正确:显式调用有参构造函数 };
-
如果省略初始化列表,编译器会报错,因为Point没有默认构造函数。
此例子中,创建一个Line类的对象,会首先调用Line的构造函数,在此过程中调用Point的构造函数完成Point类对象成员的初始化;
Line对象销毁时会先调用Line的析构函数,析构函数执行完后,再调用Point的析构函数。(对象销毁系统才会自动调用析构函数)
—— 与看起来的顺序有所不同。
静态数据成员
C++ 允许使用 static (静态存储)修饰数据成员,这样的成员在编译时就被创建并初始化的(与之相比,对象是在运行时被创建的),且其实例只有一个,被所有该类的对象共享,就像住在同一宿舍里的同学共享一个房间号一样。静态数据成员和之前介绍的静态变量一样,当程序执行时,该成员已经存在,一直到程序结束,任何该类对象都可对其进行访问,静态数据成员存储在全局/静态区,并不占据对象的存储空间。
静态数据成员被整个类的所有对象共享。
class Computer {
public:
//...
private:
char * _brand;
double _price;
//数据成员的类型前面加上static关键字
//表示这是一个static数据成员(共享)
static double _totalPrice;
};
double Computer::_totalPrice = 0;
静态成员规则:
-
private的静态数据成员无法在类之外直接访问(显然)
-
对于静态数据成员的初始化,必须放在类外(一般紧接着类的定义,这是规则1的特殊情况),因为静态成员初始化一定要放在类外初始化,类外的空间有很多,所以编译器一般要求创建完类之后,紧接着对静态数据成员初始化。不能在类内进行初始化是因为他被所有该类对象所共享,如果在类内初始化就会每创建一个对象就会变化。
-
静态数据成员初始化时不能在数据类型前面加static,在数据成员名前面要加上类名+作用域限定符
-
如果有多条静态数据成员,那么它们的初始化顺序需要与声明顺序一致(规范)