zoukankan      html  css  js  c++  java
  • C++成员变量、构造函数的初始化顺序 [转]

    一、C++成员变量初始化

    1、普通的变量:一般不考虑啥效率的情况下 可以在构造函数中进行赋值。考虑一下效率的可以再构造函数的初始化列表中进行

    2、static 静态变量(本地化数据和代码范围):

    static变量属于类所有,而不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有一个。在这种性质上理解,有点类似于全局变量的唯一性。

    • 函数体内static变量的作用范围时该函数体,不同于auto变量,该变量内存只被分配一次,因此其值在下次调用时维持上次的值。
    • 在模块内的static全局变量可以被模块内所有函数访问,但不能被模块外的其它函数访问。
    • 在模块内的static函数只可被这一模块内的其他函数调用,这个函数的适用范围被限制在声明它的模块内。
    • 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝。
    • 在类中的static成员函数属于整个类所拥有,这个函数不接受this指针,因而只能访问类的static成员变量。

    3、const 常量变量:

    const常量需要在声明的时候即初始化。因此需要在变量创建的时候进行初始化。一般采用在构造函数的初始化列表中进行。

    4、Reference 引用型变量:

    引用型变量和const变量类似。需要在创建的时候即进行初始化。也是在初始化列表中进行。但需要注意用Reference类型。

    5、字符串初始化

    char str[10] = "HELLO";

    结尾会被编译器自动加上结尾符'/0',编译的时候可以看到它最后是'',ASC码值是0;

    "HELLO"只有5个字符,加上编译器自动添加的'/0',也就是会初始化数组的前6个元素,剩下有元素会被全部初始化为'/0',这个要注意哦

    char str[] = "HELLO";

    编译器自动为后面的字符串分配大小并加'/0'

    char str[] = {'H','E','L','L','O','/0'};

    编译器会根据字符串大小分配空间,可是不会自动分配'/0',所以结尾的时候要自己加上'/0'

    char *str = "HELLO";

    把指向字符串的指针给定义好的字符指针

    二、C++类初始化

    1)用构造函数确保初始化

    对于一个空类,编译器会自动声明4个默认函数:构造函数、拷贝构造函数、赋值函数、析构函数(如果不想使用自动生成,就应该明确拒绝),这些生成的函数都是public且inline的。

    2)为什么构造函数不能有返回值

    3)为什么构造函数不能为虚函数

    虚函数调用的机制,是知道接口而不知道其准确对象类型的函数,但是创建一个对象,必须知道对象的准确类型;当一个构造函数被调用时,它做的首要事情之一就是初始化它的VPTR来指向VTABLE。

    1. 面试题:构造函数

    #include <iostream>
    using namespace std;

    class Base 
    {
    private:
    int i;
    public:
    Base(int x)
    {
    i = x;
    }
    };

    class Derived : public Base
    {
    private:
    int i;
    public:
    Derived(int x, int y)
    {
    i = x;
    }
    void print()
    {
    cout << i + Base::i << endl;
    }
    };

    int main()
    {
    Derived A(2,3);
    A.print();
    return 0;
    }

    首先,是访问权限问题,子类中直接访问Base::i是不允许的,应该将父类的改为protected或者public(最好用protected)

    其次,统计父类和子类i的和,但是通过子类构造函数没有对父类变量进行初始化;此处编译会找不到构造函数,因为子类调用构造函数会先找父类构造函数,但是没有2个参数的,所以可以在初始化列表中调用父类构造函数

    最后个问题,是单参数的构造函数,可能存在隐式转换的问题,因为单参数构造函数,和拷贝构造函数形式类似,调用时很可能会发生隐式转换,应加上explicit关键字

    #include <iostream>
    using namespace std;

    class Base 
    {
    protected:
    int i;
    public:
    explicit Base(int x)
    {
    i = x;
    }
    };

    class Derived : public Base
    {
    private:
    int i;
    public:
    Derived(int x, int y):Base(x)
    {
    i = y;
    }
    void print()
    {
    cout << i + Base::i << endl;
    }
    };

    int main()
    {
    Derived A(2,3);
    A.print();
    return 0;
    }

    2、初始化列表

    1)使用初始化列表提高效率

    class Student 
    {
    public:
    Student(string in_name, int in_age)
    {
    name = in_name;
    age = in_age;
    }
    private :
    string name;
    int age;
    };

    因为在构造函数中,是对name进行赋值,不是初始化,而string对象会先调用它的默认构造函数,再调用string类(貌似是basic_string类)的赋值构造函数;对于上例的age,因为int是内置类型,应该是赋值的时候获得了初值。

    要对成员进行初始化,而不是赋值,可以采用初始化列表(member initialization list)

    class Student 
    {
    public:
    Student(string in_name, int in_age):name(in_name),age(in_age) {}
    private :
    string name;
    int age;
    };

    在初始化的时候调用的是string的拷贝构造函数,而上例会调用两次构造函数,从性能上会有不小提升

    有的情况下,是必须使用初始化列表进行初始化的:const对象、引用对象

    2)初始化列表初始顺序

    #include <iostream>
    using namespace std;

    class Base 
    {
    public:
    Base(int i) : m_j(i), m_i(m_j) {}
    Base() : m_j(0), m_i(m_j) {}
    int get_i() const
    {
    return m_i;
    }
    int get_j() const
    {
    return m_j;
    }

    private:
    int m_i;
    int m_j;

    };

    int main()
    {
    Base obj(98);
    cout << obj.get_i() << endl << obj.get_j() << endl;
    return 0;
    }

    输出为一个随机数和98,为什么呢?因为对于初始化列表而言,对成员变量的初始化,是严格按照声明次序,而不是初始化列表中的顺序进行初始化,如果改为赋值初始化则不会出现这个问题,当然,为了使用初始化列表,还是严格注意声明顺序吧,比如先声明数组大小,再声明数组这样。

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

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

    #include <iostream>
    using namespace std;
    class OBJ1{
    public:
    OBJ1(){ cout<<"OBJ1 "; }
    };
    class OBJ2{
    public:
    OBJ2(){ cout<<"OBJ2 ";}
    }
    class Base1{
    public:
    Base1(){ cout<<"Base1 ";}
    }
    class Base2{
    public:
    Base2(){ cout <<"Base2 "; }
    };
    class Base3{
    public:
    Base3(){ cout <<"Base3 "; }
    };
    class Base4{
    public:
    Base4(){ cout <<"Base4 "; }
    };
    class Derived :public Base1, virtual public Base2,public Base3, virtual public Base4//继承顺序{
    public:
    Derived() :Base4(), Base3(), Base2(),Base1(), obj2(), obj1(){//初始化列表
    cout <<"Derived ok. ";
    }
    protected:
    OBJ1 obj1;//声明顺序
    OBJ2 obj2;
    };

    int main()
    {
    Derived aa;//初始化
    cout <<"This is ok. ";
    return 0;
    }


    结果:
    Base2 //虚拟基类按照被继承顺序初始化
    Base4 //虚拟基类按照被继承的顺序 
    Base1 //非虚拟基类按照被继承的顺序初始化
    Base3 //非虚拟基类按照被继承的顺序 
    OBJ1 //成员函数按照声明的顺序初始化
    OBJ2 //成员函数按照声明的顺序 
    Derived ok. 
    This is ok.

    转自:http://www.cnblogs.com/lidabo/p/3628987.html

  • 相关阅读:
    Web API 强势入门指南
    毫秒必争,前端网页性能最佳实践
    Windbg Extension NetExt 使用指南 【3】 ---- 挖掘你想要的数据 Managed Heap
    Windbg Extension NetExt 使用指南 【2】 ---- NetExt 的基本命令介绍
    Windbg Extension NetExt 使用指南 【1】 ---- NetExt 介绍
    WCF : 修复 Security settings for this service require Windows Authentication but it is not enabled for the IIS application that hosts this service 问题
    透过WinDBG的视角看String
    Microsoft Azure Web Sites应用与实践【4】—— Microsoft Azure网站的“后门”
    企业IT管理员IE11升级指南【17】—— F12 开发者工具
    WCF : 如何将NetTcpBinding寄宿在IIS7上
  • 原文地址:https://www.cnblogs.com/vranger/p/3907474.html
Copyright © 2011-2022 走看看