C++ 如何初始化静态类成员(静态成员必须在.cpp文件中初始化)_c++ 静态成员变量初始化-程序员宅基地

技术标签: QT  后端  QT-笔记  

一般来说,关于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中)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u011555996/article/details/124415637

智能推荐

Kafka 实战 - Windows10安装Kafka_win10安装部署kafka-程序员宅基地

文章浏览阅读426次,点赞10次,收藏19次。完成以上步骤后,您已在 Windows 10 上成功安装并验证了 Apache Kafka。在生产环境中,通常会将 Kafka 与外部 ZooKeeper 集群配合使用,并考虑配置安全、监控、持久化存储等高级特性。在生产者窗口中输入一些文本消息,然后按 Enter 发送。ZooKeeper 会在新窗口中运行。在另一个命令提示符窗口中,同样切换到 Kafka 的。Kafka 服务器将在新窗口中运行。在新的命令提示符窗口中,切换到 Kafka 的。,应显示已安装的 Java 版本信息。_win10安装部署kafka

【愚公系列】2023年12月 WEBGL专题-缓冲区对象_js 缓冲数据 new float32array-程序员宅基地

文章浏览阅读1.4w次。缓冲区对象(Buffer Object)是在OpenGL中用于存储和管理数据的一种机制。缓冲区对象可以存储各种类型的数据,例如顶点、纹理坐标、颜色等。在渲染过程中,缓冲区对象中存储的数据可以被复制到渲染管线的不同阶段中,例如顶点着色器、几何着色器和片段着色器等,以完成渲染操作。相比传统的CPU访问内存,缓冲区对象的数据存储和管理更加高效,能够提高OpenGL应用的性能表现。_js 缓冲数据 new float32array

四、数学建模之图与网络模型_图论与网络优化数学建模-程序员宅基地

文章浏览阅读912次。(1)图(Graph):图是数学和计算机科学中的一个抽象概念,它由一组节点(顶点)和连接这些节点的边组成。图可以是有向的(有方向的,边有箭头表示方向)或无向的(没有方向的,边没有箭头表示方向)。图用于表示各种关系,如社交网络、电路、地图、组织结构等。(2)网络(Network):网络是一个更广泛的概念,可以包括各种不同类型的连接元素,不仅仅是图中的节点和边。网络可以包括节点、边、连接线、路由器、服务器、通信协议等多种组成部分。网络的概念在各个领域都有应用,包括计算机网络、社交网络、电力网络、交通网络等。_图论与网络优化数学建模

swagger使用map传参和实体传参编写注释_swagger页面get请求传map参数怎么写-程序员宅基地

文章浏览阅读1.7w次,点赞2次,收藏20次。@AutoLog(value = "web首页-地图显示按行政区划")@ApiOperation(value="web首页-地图显示按行政区划")@ApiImplicitParams({@ApiImplicitParam(paramType = "query",name = "orgType",value ="行政等级",dataType ="String"), ..._swagger页面get请求传map参数怎么写

【进阶版】机器学习之神经网络与深度学习基本知识和理论原理(07)_深度随机配置网络-程序员宅基地

文章浏览阅读931次。机器学习算法知识、数据预处理、特征工程、模型评估——原理+案例+代码实战机器学习之Python开源教程——专栏介绍及理论知识概述机器学习框架及评估指标详解Python监督学习之分类算法的概述数据预处理之数据清理,数据集成,数据规约,数据变化和离散化特征工程之One-Hot编码、label-encoding、自定义编码卡方分箱、KS分箱、最优IV分箱、树结构分箱、自定义分箱特征选取之单变量统计、基于模型选择、迭代选择机器学习八大经典分类万能算法——代码+案例项目开源、可直接应用于毕设+科研项目。_深度随机配置网络

python logging模块“另一个程序正在使用此文件,进程无法访问。”问题解决办法...-程序员宅基地

文章浏览阅读2.7k次。在多进程下使用python的logging模块,经常会遇到“另一个程序正在使用此文件,进程无法访问。”的错误。解决办法: https://github.com/Preston-Landers/concurrent-log-handlerpip install concurrent-log-handler To use this module from a logging config..._logging roatingfilehander 另一个程序正在使用此文件,进程无法访问

随便推点

组装电脑超详细步骤(超多图+用了2个小时写的)_电脑组装教程-程序员宅基地

文章浏览阅读5.4w次,点赞101次,收藏712次。准备工作主板机箱机箱布局机箱前置接口CPU有点cpu自带了cpu风扇内存固态硬盘机械硬盘电源显卡螺丝刀(可能需要自备)螺丝买机箱一般会提供所有的螺丝。开始组装装cpu和cpu风扇到主板上(2min)装内存到主板上(3min)装固态到主板上(3min)插入主板的M.2插槽,然后螺丝固定即可装IO..._电脑组装教程

爬取唐诗-程序员宅基地

文章浏览阅读843次。首先我们打开唐诗三百首网页1 http://www.gushiwen.org/gushi/tangshi.aspx目标分析:1、爬取网页七大板块:五言绝句,七言绝句,五言律诗,七言律诗,五言古诗,七言古诗,乐府。2、爬取每个板块的所有古诗。3、爬取每个古诗词内容。网页详情如下:我们很容易就能发现,每一个分类都是包裹在:1 <div i..._获取唐诗三百首接口

Laravel学习:请求到响应的生命周期-程序员宅基地

文章浏览阅读93次。Laravel请求到响应的整个执行过程,主要可以归纳为四个阶段,即程序启动准备阶段、请求实例化阶段、请求处理阶段、响应发送和程序终止阶段。程序启动准备阶段服务容器实例化服务容器的实例化和基本注册,包括了服务容器本身注册、基础服务提供者注册、核心类别名注册和应用的基本路径注册。注册的服务只是具体的类名,是通过反射机制来实例化对象,并且..._laravel new client() 响应时长

思科 下一跳_二层交换机下一跳命令-程序员宅基地

文章浏览阅读3.1k次,点赞2次,收藏6次。命令如下 仅供参考A :Router>enable Router#conf terminal Enter configuration commands, one per line. End with CNTL/Z.Router(config)#interface gigabitEthernet 0/0Router(config-if)#ip address 192.168.1..._二层交换机下一跳命令

IFeatureClass接口_list<ifeatureclass>-程序员宅基地

文章浏览阅读3.7k次。IFeatureClass用于访问控制要素类行为和属性的成员IFeatureClass接口是获取和设置要素类属性的主要接口。例如,使用IFeatureClass接口获取要素类类型、获取满足查询条件的要素数目或在要素类中创建新要素。IFeatureClass接口继承了IObjectClass接口。成员AddField 向这个类中添加一个字段。AddIndex _list

利用Mysql into outfile给网站留后门-程序员宅基地

文章浏览阅读9.6k次。Mysql into outfile使用Mysql into outfile语句,可以方便导出表格的数据。同样也可以生成某些文件。因此有些人会利用sql注入生成特定代码的文件,然后执行这些文件。将会造成严重的后果。Mysql into outfile 生成PHP文件SELECT 0x3C3F7068702073797374656D28245F524551554553545B636D645D293B3

推荐文章

热门文章

相关标签