C++ 初始化列表(一)

何谓初始化列表

与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表。初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。在C++中,struct和class唯一的区别就是struct的所有成员默认都是public的,所以如果不考虑成员的可访问性,这两者是没有区别的,而这里我们不考虑访问性的问题,所以下面的代码都以struct来演示。

struct foo
{
    string name ;
    int id ;
    foo(string s, int i):name(s), id(i){} ; // 初始化列表
};

构造函数的两个执行阶段

构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。

初始化阶段

所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中。

计算阶段

一般用于执行构造函数体内的赋值操作,下面的代码定义两个结构体,其中Test1有构造函数,拷贝构造函数及赋值运算符,为的是方便查看结果。Test2是个测试类,它以Test1的对象为成员,我们看一下Test2的构造函数是怎么样执行的。

struct Test1
{
    Test1() // 无参构造函数
    {
        cout << "Construct Test1" << endl ;
    }

    Test1(const Test1& t1) // 拷贝构造函数
    {
        cout << "Copy constructor for Test1" << endl ;
        this->a = t1.a ;
    }

    Test1& operator = (const Test1& t1) // 赋值运算符
    {
        cout << "assignment for Test1" << endl ;
        this->a = t1.a ;
        return *this;
    }

    int a ;
};

struct Test2
{
    Test1 test1 ;
    Test2(Test1 &t1)
    {
        test1 = t1 ;
    }
};

调用代码

Test1 t1 ;
Test2 t2(t1) ;

输出

解释一下,第一行输出结果对应调用代码中第一行,构造一个Test1对象。第二行输出对应Test2构造函数中的代码,用默认的构造函数初始化对象test1,这就是所谓的初始化阶段。第三行输出对应Test1的赋值运算符,对test1执行赋值操作,这就是所谓的计算阶段。

为什么使用初始化列表

初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。使用初始化列表主要是基于性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?由上面的测试可知,使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。同样看上面的例子,我们使用初始化列表来实现Test2的构造函数

struct Test2
{
    Test1 test1 ;
    Test2(Test1 &t1):test1(t1){}
}

使用同样的调用代码,输出结果如下。

第一行输出对应调用代码的第一行。第二行输出对应Test2的初始化列表,直接调用拷贝构造函数初始化test1,省去了调用默认构造函数的过程。所以一个好的原则是,能使用初始化列表的时候尽量使用初始化列表。

哪些东西必须放在初始化列表中

除了性能问题之外,有些时场合初始化列表是不可或缺的,以下几种情况时必须使用初始化列表

  • 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
  • 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
  • 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。

对于没有默认构造函数的类,我们看一个例子。

struct Test1
{
    Test1(int a):i(a){}
    int i ;
};

struct Test2
{
    Test1 test1 ;
    Test2(Test1 &t1)
    {
        test1 = t1 ;
    }
};

以上代码无法通过编译,因为Test2的构造函数中test1 = t1这一行实际上分成两步执行。

1. 调用Test1的默认构造函数来初始化test1

2. 调用Test1的赋值运算符给test1赋值

但是由于Test1没有默认的构造函数,所谓第一步无法执行,故而编译错误。正确的代码如下,使用初始化列表代替赋值操作。

struct Test2
{
    Test1 test1 ;
    Test2(Test1 &t1):test1(t1){}
}

成员变量的初始化顺序

成员是按照他们在类中出现的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的,看代码。

struct foo
{
    int i ;
    int j ;
    foo(int x):i(x), j(i){}; // ok, 先初始化i,后初始化j
};

再看下面的代码

struct foo
{
    int i ;
    int j ;
    foo(int x):j(x), i(j){} // i值未定义
};

这里i的值是未定义的,虽然j在初始化列表里面出现在i前面,但是i先于j定义,所以先初始化i,但i由j初始化,此时j尚未初始化,所以导致i的值未定义。所以,一个好的习惯是,按照成员定义的顺序进行初始化。

同样再看看下面的代码

class Data{

public:

int i;

int j;

int k;

Data(int a):j(a),k(i),i(i)

{

}

};

这种写法有问题,按照此写法,只有j的值等于a的值,k和i的值都是未定义的。

时间: 2024-10-26 20:23:54

C++ 初始化列表(一)的相关文章

c++11之初始化列表

一.前言 C++的学习中,我想每个人都被变量定义和申明折磨过,比如我在大学笔试过的几家公司,都考察了const和变量,类型的不同排列组合,让你区别有啥不同.反正在学习C++过程中已经被折磨惯了,今天再来看看重温下那段"辉煌的历史".先来看一段代码: Player pa; // (a) Player pb(); // (b) Player pc = Player(); // (c) Player pd(Player()); // (d) pd = Player() // (e) a,b,

c++初始化列表

何谓初始化列表 与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段.在C++中,struct和class的唯一区别是默认的访问性不同,而这里我们不考虑访问性的问题,所以下面的代码都以struct来演示. struct foo { string name ; int id ; foo(string s, int i):name(s), id(i){} ; // 初始化列表 }; 构造函数的两个执行阶段 构造函数的执行

【C/C++】构造函数、默认构造函数、成员初始化列表

常见问题 Q1. 下列关于构造函数的描述中,错误的是( ) A. 构造函数可以设置默认的参数 B. 构造函数在定义类对象时自动执行 C. 构造函数可以是内联函数 D. 构造函数不可以重载 Q2. 下列代码中a.b的各个成员变量值是多少? 1 class Student 2 { 3 public: 4 Student() {} 5 void show(); 6 private: 7 string name; 8 int number; 9 int score; 10 }; 11 Student a

C++对象模型——成员初始化列表(第二章)

2.4    成员初始化列表 (Member Initialization List) 当编写一个类的构造函数时,有可能设定类成员的初始值,或者通过成员初始化列表初始化,或者在构造函数内初始化,除了四种情况,其实任何选择都差不多. 本节中,首先澄清何时使用初始化列表才有意义,然后解释初始化列表内部的真正操作是什么,然后再看一些微妙的陷阱.  下列情况中,为了让程序能够被顺利编译,必须使用成员初始化列表(不能在构造函数内初始化): 1.    当初始化一个引用的成员时 2.    当初始化一个常量

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员).拷贝构造函数  从概念上将,可以认为构造函数分为两个阶段执行: 1)初始化阶段: 2)普通的计算阶段.计算阶段由构造函数函数体中的所有语句组成. 一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 1.对象成员及其初始化 <span style="font-size:14px;">#include <iostream> using namespace std;

[C++11笔记001]修改通用库中的XDynamicArray,使它可以支持C++11的初始化列表和for循环

今天,有空翻了一下<C++Primer plus(第六版)>,看到里面有介绍新的for循环和初始化列表,但是我实现的动态数组XDynamicArray不支持这些新特性,没办法,只好进行改造了. 首先是for循环,如下面的样式 for(auto e:stList) { cout<<e<<endl; } 是于就各种google,和查找C++11的array的源代码,总结:就是提供一个标准的iterator和begin,end这两个方法,就可以了. 是于定义了一个iterat

C++构造函数初始化列表与赋值

C++中类的初始化操作一般有四个部分组成: 1.构造函数初始化列表 2.构造函数体内赋值 3.类外部初始化 4.类声明时直接赋值 对于内部数据类型(char,int,float...),构造函数初始化列表和构造函数体内赋值,基本上没多大区别,效率差异也不大,但两者不能共存: class Student { public: Student(char *name, int age): //A初始化列表 m_name(name), m_age(age) { } Student(char *name,

只能在初始化列表中初始化的变量

1.const变量 有几个容易混淆的地方: (1)const 的变量只能通过构造函数的初始化列表进行初始化:(貌似在c++11中可以正常编译) (2)static 的变量只能通过在类外重新定义进行初始化: (3)static const 变量 只能通过在类中直接用”=”进行赋值. 2.引用 引用只能初始化,不能赋值 3.不含默认构造函数的类的对象 因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化. 构造函数的函数体内只能做赋值而不是初始化,因此初始化const对

const成员或者引用成员必须使用构造函数初始化列表的方式

#include<iostream.h> class A { const int a; int b; }; void main() { A obja; }编译出现如下错误:error C2512: 'A' : no appropriate default constructor available;如果将const去掉就没错了! #include<iostream.h> class A { public: const int a; int b; A(int x):a(x){} };

构造函数初始化列表

构造函数初始化列表有时是必要的.虽然构造函数分为初始化和计算阶段,使用初始化构造函数列表效率要高一些,这是其一,而且有些情况下必须使用,下面是一例, 1 class Foo 2 { 3 public: 4 Foo(int x, int y): a(x), b(y) {} 5 6 private: 7 int a; 8 int b; 9 }; 10 11 class Bar 12 { 13 public: 14 Bar(): foo(10, 10) {} 15 private: 16 Foo fo