C++ 如何初始化静态类成员(静态成员必须在.cpp文件中初始化)
一般来说,关于C++类静态成员的初始化,并不会让人感到难以理解,但是提到C++ 静态成员的"类内初始化"那就容易迷糊了。
我们来看如下代码:
1 //example.h 2 #include<iostream> 3 #include<vector> 4 using namespace std; 5 6 class Example{ 7 public: 8 static double rate = 6.5; 9 static const int vecSize = 20; 10 static vector<double> vec(vecSize); 11 }; 12 13 //example.cpp 14 #include "example.h" 1516 double Example::rate; 17 vector<double> Example::vec; 18
我们需要判断上面的静态数据成员的声明和定义有没有错误,并解释原因。
首先,要谨记:通常情况下,不应该在类内部初始化成员,无论是否为静态成员。
其次,若一定要在类内初始化静态成员,那么就必须满足如下条件才行:
1) 静态成员必须为字面值常量类型的constexpr。
所谓的字面值类型就是通常遇到的:算术类型,引用,指针等。字面值常量类型就是const型的算术类型,引用,指针等。
所谓的constexpr,就是常量表达式,指值不会改变且在编译过程中就能得到计算结果的表达式。比如字面值,或者用常量表达式初始化的const对象也是常量表达式。为了帮助用户检查自己声明/定义的变量的值是否为一个常量表达式,C++11新规定,允许将变量声明为constexpr类型,以便由编译器来进行验证变量是否为常量表达式。
2)给静态成员提供的初始值,必须为常量表达式
注意:在C++ primer 第五版中说:只能给静态成员提供const 整数类型的类内初始值,且该const整数类型的初始值必须是常量表达式。我觉得是有误的!详情见后面分析。
有了这两条原则,我们就可以对上面的代码进行验证了。
1)static double rate = 6.5;
显然不满足第一条:因为rate不是常量类型。改成constexprt static const double rate = 6.5即可
从这里也可以看出初始值不一定必须为const 整数类型。
ps: 如果我们不再这里加入constexprt修饰符的话,编译器会提示错误:error: ‘constexpr’ needed for in-class initialization of static data member ‘const double Example::rate’ of non-integral type [-fpermissive]
大体意思就是,对于非const整数类型的初始值,如果它是常量表达式的话,我们需要手工在前面添加修饰符constexprt。
至于Example.cpp文件中的定义部分,由于我们已经在类内部进行了初始化,就不需要再在类外部进行定义了。如果非要定义的话,必须采用如下格式:
//example.cpp
constexpr const double Example::rate; //其中的const是可以删除的,因为constexprt本身就包含了const
2)static const int vecSize = 20;
vecSize是const int类型的,且为常量表达式——满足第一条;提供的初始值为20,是一个常量表达式——满足第二条!且由于是const int型的,前面可以不用修饰符constexpr。
3)static vector<double> vec(vecSize);
错误!vector是模板不是字面值常量类型,所以不满足第一条。应该改为 static vector<double> vec; //仅仅且只能进行声明,不能定义
然后在Example.cpp中进行定义:
static vector<double> vec(Example::vecSize);
现在我们可以在Example.cpp中添加测试代码进行测试了:
1 #include "example.h" 2 vector<double> Example::vec(Example::vecSize); 3 constexpr const double Example::rate; 4 5 int main(){ 6 7 Example::vec.push_back(10.5); 8 cout << Example::vec.back() << endl; 9 cout << Example::rate << endl; 10 cout << Example::vecSize << endl; 11 }
执行结果:
wanchouchou@wanchouchou-virtual-machine:~/c++/7.5$ ./Example 10.5 6.5 20
/************************************************************
我们定义如下类:
//A.h
class A
{
private:
static const int m = 5;
static int n;
static vector<int> buf;
};
其中包含三个私有的静态类成员,C++规定const静态类成员可以直接初始化,其他非const的静态类成员需要在类声明以外初始化,我们一般选择在类的实现文件中初始化,初始化的方式是书写一遍类型的定义:
//A.cpp
int A::n; //不指定任何初始值,系统自动初始化为0
vector<int> A::buf; //调用vector的默认构造函数来初始化
//注意:调用默认构造函数时,不要使用括号,否则编译器将把A::buf()当做静态成员函数,
//但是A::buf()实际没有被声明,所以编译器将报错
或者:
//A.cpp
int A::n(9); //使用字面量9来初始化n
vector<int> A::buf(100); //调用vector的带参构造函数来初始化
对于更复杂的情形 ,如没有构造函数可以调用(比如单例模式实现的类),或者需要多步骤才能完成的初始化怎么办?
假设有一个类S实现了单例模式,S的实例是通过调用S的静态方法S::GetInstance()来获得的,现在定义一个包含S作为静态成员的类:
//B.h
class B
{
private:
static S s;
};
按照前面的介绍,我们或许应该以下面这种方式初始化s:
//B.cpp
#include <B.h>
S B::s; //编译器会报错,因为S没有可以调用的构造函数
解决方法是定义一个静态方法,负责初始化静态成员s:
//B.h
class B
{
public:
static S Init();
private:
static S s;
};
//B.cpp
#include <B.h>
S B::Init()
{
....
return S::Instance();
}
S B::s = B::Init(); //调用静态函数初始化静态成员
上例中,为了初始化类B的静态 成员s,我们定义了一个公有的静态方法Init(),它可以很好的工作。但是,在现实的工程中,我们很可能碰到更进一步的要求,就是希望Init()仅仅作为静态变量s的初始化器使用,而不能使用在程序中别的地方,但是我们又不能把Init()声明为private,这样Init()就不能被调用来初始化s了。解决的方法是使用内部类:
//B.h
class B
{
private:
class C
{
public:
static S InitB();
};
static S s;
};
//B.cpp
S B::C::InitB()
{
....
return S::Instance();
}
S B::s = B::C::InitB(); //调用内部类的静态成员函数来初始化静态数据成员
因为C是B的内部类,C仅在B的作用域范围内可见,如果程序的其他地方调用了B::C::InitB(),编译器将报错,因为C不可访问。
最后说一下,从初始化的方式可以看出来,类的静态数据成员其实就是“带类名”的全局变量。
静态数据成员必须显式初始化,否则在类方法中操作该成员时,将报链接错误 undefined reference to `A::buf' (gcc中)
相关文章
- [C++]在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include StdAfx.h”
- 在C++类中使用dllimport和dllexport导出,
- c++操作文件初体验,读写数据小例子
- C/C++的开发环境安装
- android中调用c++文件并转为so
- Win10系列:VC++ XML文件解析
- C++函数模板
- c++读取文件到vector
- c++------------提取文件中的信息
- C++ vector容器的多种遍历方式
- C/C++基础讲解(三十六)之数值计算与趣味数学篇(数字移动与多项式乘法)
- 【转】C++调用Matlab的.m文件
- Atitit 文件上传 架构设计 实现机制 解决方案 实践java php c#.net js javascript c++ python
- Atitit 文件上传 架构设计 实现机制 解决方案 实践java php c#.net js javascript c++ python
- C++Qt开发——阻止系统休眠方法
- [h5棋牌项目]-17-C++读取json文件
- 【华为OD机试 2023】投篮大赛(C++ Java JavaScript Python)
- 【华为OD机试 2023】 计算数组中心位置(C++ Java JavaScript Python)
- C++——重载运算符和重载函数
- C++ static静态成员函数详解
- C++中类的(static)静态成员变量与(static)静态成员函数
- C++之读写文件操作(fread/fwrite)(七十七)
- C++学习笔记10-面向对象
- VC++判断目标文件是否被独占(附源码)
- C++托盘图标异常消失问题的分析与解决
- C++如何将Unicode文本写到日志文件中
- C++Primer笔记——5.语句
- C++搭建集群聊天室(七):MySQL数据库配置 及项目工程目录配置
- C++使用技巧(二十七):回顾函数指针参数、数组参数、结构体函数参数
- C++使用技巧(八):输入输出读写文件
- 【C++要笑着学】迭代器适配器 | 内嵌类型实现反向迭代器 | 迭代器萃取