我们之前在 C 语言中可以定义 const 成员,那么我们是否可以在类中定义 const 成员呢?我们来看看下面代码中的类定义是否合法呢?如果合法,ci 的值是什么,存储在哪里呢?
#include <stdio.h> class Test { private: const int ci; public: int getCI() { return ci; } }; int main() { Test t; printf("t.ci = %d\n", t.getCI()); return 0; }
照我们之前学习的知识可以猜测对象 t 是创建于栈上,那么它中的 ci 便会是随机值了。我们看看编译结果
编译报错,它说我们没有初始化 ci,下面我们在它定义的时候并初始化为10。也就是将第 6 行改为 const int ci = 10;我们再次看看它是否编译通过
我们看到编译产生警告了,虽然它的运行结果是正确的。我们之前说过,一个优秀的程序员会将任何一个警告都看作是错误,因为它的结果将是不确定的。这样写是由问题的,洽好 g++ 编译器支持这样写,我们不能写出依赖于某种编译器特性的代码。所以这时便会用到初始化列表了,在 C++ 中提供了初始化列表对成员变量进行初始化,它的语法规则如下
那么我们在程序中加上构造函数并用初始化列表进行初始化,如下
Test() : ci(5) { }
我们再次编译,如下
我们看到编译器没有报任何警告,这便证明了在 C++ 中提供了初始化列表对成员变量进行初始化。那么我们在这块有几个注意事项:a> 成员的初始化顺序与成员的声明顺序相同;b> 成员的初始化顺序与初始化列表中的位置无关;c> 初始化列表先于构造函数的函数体执行。为例更加形象的说明,我们再次以代码为例进行说明
#include <stdio.h> class Value { private: int mi; public: Value(int i) { printf("i = %d\n", i); mi = i; } int getMI() { return mi; } }; class Test { private: Value m2; Value m3; Value m1; public: Test() : m1(1), m2(2), m3(3) { printf("Test::Test()\n"); } }; int main() { Test t; return 0; }
我们在 Test 类中定义了 3 个 Value 对象,然后在构造函数中使用了初始化列表对他们进行初始化。在构造函数中我们加入了一条打印语句,按照我们之前讲的,应该在初始化完了之后再打印那条语句。我们编译看看结果
我们看到打印的是如我们分析的那样,但是前面初始化的顺序好像不太一样,我们是按照 m1、m2、m3 这样的顺序进行初始化的。想想我们之前说的:初始化的顺序和它声明的顺序相同,和它的位置并无关。所以看看我们声明顺序就知道打印的是正确的了。
类中的 const 成员会被分配空间的,它的本质是只读变量并且只能在初始化列表中指定初始值。编译器无法直接得到 const 成员的初始值,因此无法进入符号表成为真正意义上的常量。我们以代码为例进行说明
#include <stdio.h> class Test { private: const int ci; public: Test() : ci(10) { printf("Test::Test()\n"); } int getCI() { return ci; } int setCI(int v) { int* p = const_cast<int*>(&ci); *p = v; } }; int main() { Test t; printf("t.ci = %d\n", t.getCI()); t.setCI(100); printf("t.ci = %d\n", t.getCI()); return 0; }
我们看看编译结果是否改变了 ci 的值
我们看到已经成功的通过指针 + const_cast去掉了它的 const 属性,也就是说,它只是一个具有只读属性的变量。我们再次强调下,初始化与赋值不同。初始化是对正在创建的对象进行初值设置,而赋值则是对已经存在的对象进行值设置。通过对初始化列表的学习,总结如下:1、类中可以使用初始化列表对成员进行初始化;2、初始化列表先于构造函数体执行;3、类中可以定义 const 成员变量,const 成员变量必须在初始化列表中指定初值,const 成员变量为只读变量。
欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。