zoukankan      html  css  js  c++  java
  • C++成员变量初始化-就地初始化&初始化列表

    就地初始化&初始化列表

    就地初始化:member initializer list

    初始化列表:member initializer list,或:member initialization list

    参考:https://www.cnblogs.com/lidabo/p/3628987.html

    C++构造函数初始化按下列顺序被调用:

    • 首先,任何虚拟基类的构造函数按照它们被继承的顺序构造;
    • 其次,任何非虚拟基类的构造函数按照它们被继承的顺序构造;
    • 最后,任何成员对象的构造函数按照它们声明的顺序调用;

    由于类成员初始化总在构造函数执行之前,编译器总是确保所有成员对象在构造函数体执行之前初始化,

    参考:https://www.cnblogs.com/simplepaul/p/7635648.html

    在C++11中,直接对成员变量赋值(就地初始化),和初始化列表,哪个先执行,哪个后执行?

    例如:

    class MyClass
    {
    private:
        int a = 10;
    public:
        MyClass()
            :a(20)
        {}
        ~MyClass(){}
    
        int getA()
        {
            return a;
        }
    };

      构造一个MyClass后,a的值是多少?

    条款4:C++“成员初始化次序”:base class更早于其derived class被初始化,而class的成员变量总是以其声明次序被初始化,即使成员变量在成员初始化列表中的次序不同。

    这里只是说了成员初始化的顺序

      但是:在构造MyClass中,已经初始化了a,然后再在初始化列表中调用拷贝构造函数吗?待查

     写了如下测试代码:

    class B
    {
    public:
        explicit B(int a)
        {
            this->b = a;
            cout << "construct:" << a << endl;
        }

        B(const B &val)
        {
          cout << "copy:" << val.b << endl;
          this->b = val.b;
        }

    ~B()
        {
            cout << "Destruct:" << this->b << endl;
        }
    private:
        int b;
    };
    class MyClass
    {
    private:
        int a = 10;
        B b = B(3);
    public:
        MyClass()
            :a(20),
            b(B(4))
        {}
        ~MyClass(){}
    
        int getA()
        {
            return a;
        }
    };
    int main()
    {
        MyClass aa;
        cout << "val a:" << aa.getA() << endl;
        int val = aa.getA();
    }

    输出的打印,类B只构造了一次。。。即成员初始化列表中的4那次。

    为啥嘞???

    相关文章:https://cloud.tencent.com/developer/article/1394301

    这篇文章说的是,就地初始化在先,然后成员列表初始化: 

      就地初始化与初始化列表的先后顺序

      C++11标准支持了就地初始化非静态数据成员的同时,初始化列表的方式也被保留下来,也就是说既可以使用就地初始化,也可以使用初始化列表来完成数据成员的初始化工作。当二者同时使用时,并不冲突,初始化列表发生在就地初始化之后,即最终的初始化结果以初始化列表为准。

    Note:

      上面链接的文章说的不准确,参考:https://en.cppreference.com/w/cpp/language/constructor,这个文章中说,如果成员变量就地初始化,成员变量又出现在初始化列表中,则就地初始化被忽略,以初始化列表中的为准

    If a non-static data member has a default member initializer and also appears in a member initializer list, then the member initializer is used and the default member initializer is ignored:

    struct S {
        int n = 42;   // default member initializer
        S() : n(7) {} // will set n to 7, not 42
    };

       该文章中对构造函数解释如下:

      构造函数没有函数名且不能直接被调用。构造函数在初始化发生时被调用,且根据初始化的规则选择特定的构造函数。没有explicit说明符的是可以隐式转换的构造函数。带有constexpr说明符的构造函数成为一个字面类型(LiteralType)。没够任何参数的构造函数为默认构造函数 default constructor。以另一个同类型对象作为的参数的构造函数有拷贝构造函数 copy constructor,和移动构造函数 move constructor。

      在构造函数的函数体执行之前(即{}内的语句),所有的父类、虚类、non-static成员变量都已初始化完成。成员初始化列表(member initializer list)可用于父类、虚类、非静态成员变量的初始化(非默认的初始化,non-default initialization)。对于不能调用默认构造函数构造的基类、non-static成员变量,必须在初始化列表中进行初始化,例如:引用类型、const类型的成员变量。

     什么情况必须使用初始化列表

      什么时候必须使用初始化列表?《深入理解C++对象模型》中描述如下:

     成员初始化顺序

      如果在构造函数中,通过赋值的形式对成员变量进行初始化,如下所示:

      这种情况,构造函数可能的内部扩张结果为:

       也就是说,在构造函数中,编译器先调用默认构造函数初始化了 _name ,_cnt,然后再通过赋值操作符(operator =)对其进行赋值操作,效率较差。

      而采用初始化列表形式,如:

       它会被编译器扩张为如下代码:

       即直接调用拷贝构造函数(copy constructor)对成员变量进行构造,效率较高。(注意:不论在初始化列表中写的初始化顺序是什么,成员变量的初始化顺序按照在类的声明中的顺序进行,条款4)

      在本例中,_name 先于 _cnt声明,则 _name先于_cnt初始化,“初始化次序” 和 “initialization list中的项目排列次序”是不同的

     构造函数调用构造函数

      在C++11之后,可以在构造函数中调用构造函数:如果一个类中有多个构造函数,为避免代码的重复,可以在一个构造函数中的初始化列表中调用另一个构造函数,但是注意构造函数调用不能出现循环情况。例如:

    class Foo {
    public: 
      Foo(char x, int y) {}
      Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char,int)
    };

    下面是cppreference给出的示例代码,包含大部分情况:

    #include <fstream>
    #include <string>
    #include <mutex>
     
    struct Base {
        int n;
    };   
     
    struct Class : public Base
    {
        unsigned char x;
        unsigned char y;
        std::mutex m;
        std::lock_guard<std::mutex> lg;
        std::fstream f;
        std::string s;
     
        Class ( int x )
          : Base { 123 }, // initialize base class
            x ( x ),      // x (member) is initialized with x (parameter)
            y { 0 },      // y initialized to 0
            f{"test.cc", std::ios::app}, // this takes place after m and lg are initialized
            s(__func__),  //__func__ is available because init-list is a part of constructor
            lg ( m ),     // lg uses m, which is already initialized
            m{}           // m is initialized before lg even though it appears last here
        {}                // empty compound statement
     
        Class ( double a )
          : y ( a+1 ),
            x ( y ), // x will be initialized before y, its value here is indeterminate
            lg ( m )
        {} // base class initializer does not appear in the list, it is
           // default-initialized (not the same as if Base() were used, which is value-init)
     
        Class()
        try // function-try block begins before the function body, which includes init list
          : Class( 0.0 ) //delegate constructor
        {
            // ...
        }
        catch (...)
        {
            // exception occurred on initialization
        }
    };
     
    int main() {
        Class c;
        Class c1(1);
        Class c2(0.1);
    }
  • 相关阅读:
    团队博客-十日冲刺6
    04构建之法阅读笔记之一
    Java基础-面向对象三大特性
    剑指 Offer 38. 字符串的排列
    Java基础:包装类 装箱/拆箱 Integer
    剑指 Offer 34. 二叉树中和为某一值的路径
    LeetCode 树:105. 从前序与中序遍历序列构造二叉树
    Java基础:类型
    Java基础:值传递和引用传递
    数据结构:图的基本知识
  • 原文地址:https://www.cnblogs.com/zyk1113/p/14248621.html
Copyright © 2011-2022 走看看