CherieLi Student

统一初始化

2019-10-14
CherieLi

初始化

C++的初始化有很多方式:默认初始化,值初始化,直接初始化,拷贝初始化,列表初始化

1.默认初始化(default initialization)

默认初始化是指定义变量时没有指定初值时进行的初始化操作。

(1)内置类型变量(如int,double,bool等),如果定义在语句块外(即{}外),则变量被默认初始化为0;如果定义在语句块内(即{}内),变量将拥有不确定的值。

#include <iostream>
using namespace std;
int a;
int main()
{
    int b;
    cout << a << endl;
    cout << b << endl;
    return 0;
}

输出结果:

image

(2)对于类类型的变量(如string或其他自定义类型),不管定义于何处,都会执行默认构造函数。如果该类没有默认构造函数,则会引发错误。因此,建议为每个类都定义一个默认构造函数(=default)。

示例一:默认构造函数由编译器自动生成

#include <iostream>
using namespace std;
class testA
{
    public:
        void printf() const
        {
            cout << data << endl;
        }
    private:
        int data;
};

testA a;
int main()
{
    testA b;

    a.printf();
    b.printf();

    return 0;
}

输出结果:

image

示例二:定义一个构造函数,阻止编译器自动生成默认构造函数

#include <iostream>
using namespace std;

class testA
{
    public:
        testA(int a)
        {
            data = a;
        }

        void printf() const
        {
            cout << data << endl;
        }
    private:
        int data;
};

testA a;

int main()
{
    testA b;

    a.printf();
    b.printf();

    return 0;
}

编译出错:error:no matching function for call to ‘testA::testA()’

a与b都无法初始化

2.值初始化(value initialization)

值初始化是值使用了初始化器(即使用了圆括号或花括号)但却没有提供初始值的情况。

#include <string>
#include <vector>
#include <iostream>
 
struct T1
{
    int mem1;
    std::string mem2;
}; // 隐式默认构造函数
 
struct T2
{
    int mem1;
    std::string mem2;
    T2(const T2&) { } // 用户提供的复制构造函数
};                    // 无默认构造函数
 
struct T3
{
    int mem1;
    std::string mem2;
    T3() { } // 用户提供的默认构造函数
};
 
std::string s{}; // 类 => 默认初始化,值为 ""
 
int main()
{
    int n{};                // 标量 => 零初始化,值为 0
    double f = double();    // 标量 => 零初始化,值为 0.0
    int* a = new int[10](); // 数组 => 每个元素的值初始化
                            //          每个元素的值为 0
    T1 t1{};                // 有隐式默认构造函数的类 =>
                            //     t1.mem1 被零初始化,值为 0
                            //     t1.mem2 被默认初始化,值为 ""
//  T2 t2{};                // 错误:类无默认构造函数
    T3 t3{};                // 有用户提供默认构造函数的类 =>
                            //     t3.mem1 被默认初始化为不确定值
                            //     t3.mem2 被默认初始化,值为 ""
    std::vector<int> v(3);  // 值初始化每个元素
                            // 每个元素的值为 0
    std::cout << s.size() << ' ' << n << ' ' << f << ' ' << a[9] << ' ' << v[2] << '\n';
    std::cout << t1.mem1 << ' ' << t3.mem1 << '\n';
    delete[] a;
}

输出结果: image

3.直接初始化(direct initialization)

直接初始化是指采用小括号的方式进行变量初始化(小括号里一定要有初始值,如果没提供初始值,那就是值初始化了!)

#include <string>
#include <iostream>
#include <memory>

struct Foo {
    int mem;
    explicit Foo(int n) : mem(n) {}
};

int main()
{
    std::string s1("test"); // 自 const char* 的构造函数
    std::string s2(10, 'a');

    std::unique_ptr<int> p(new int(1)); // OK:允许 explicit 构造函数
//  std::unique_ptr<int> p = new int(1); // 错误:构造函数为 explicit

    Foo f(2); // f 被直接初始化:
              // 构造函数形参 n 从右值 2 复制初始化
              // f.mem 从形参 n 直接初始化
//  Foo f2 = 2; // 错误:构造函数为 explicit

    std::cout << s1 << ' ' << s2 << ' ' << *p << ' ' << f.mem  << '\n';
}

输出结果:

image

4.拷贝初始化(copy initialization)

拷贝初始化是指采用等号(=)进行初始化的方式。

#include <string>
#include <utility>
#include <memory>
 
struct A 
{
  operator int() { return 12; }
};
 
struct B 
{
  B(int) {}
};
 
int main()
{
    std::string s = "test"; // OK:构造函数非 explicit
    std::string s2 = std::move(s); // 此复制初始化进行移动
 
//  std::unique_ptr<int> p = new int(1); // 错误:构造函数为 explicit
    std::unique_ptr<int> p(new int(1)); // OK:直接初始化
 
 
    int n = 3.14;    // 浮点整型转换
    const int b = n; // 不受 const 与否
    int c = b;       // ……影响
 
 
    A a;
    B b0 = 12;
//    B b1 = a; // < 错误:要求从 'A' 到非标量类型 'B' 的转换
    B b2{a};        // < 等同:调用 A::operator int() ,然后是::B(int)
    B b3 = {a};     // <
    auto b4 = B{a}; // <
 
//    b0 = a; // < 错误:需要重载赋值运算符
}

(1)对于内置类型变量(如int,double,bool等),直接初始化与拷贝初始化差别可以忽略不计。

(2)对于类类型的变量(如string或其他自定义类型),直接初始化调用类的构造函数(调用参数类型最佳匹配的那个),拷贝初始化调用类的拷贝构造函数。

特别的,当对类类型变量进行初始化时,如果类的构造函数采用了explicit修饰而且需要隐式类型转换时,则只能通过直接初始化而不能通过拷贝初始化进行操作。

统一初始化(Uniform initialization)

在C++ 11之前,所有对象的初始化方式是不同的。

直接初始化(小括号)

拷贝初始化(等号)

值初始化(花括号) C++ 11努力创造一个统一的初始化方式。 其语法是使用{}和std::initializer_list。(列表初始化)

它采用一对花括号(即{})进行初始化操作。能用直接初始化和拷贝初始化的地方都能用列表初始化,而且列表初始化能对容器进行方便的初始化。

int values[]{1,2,3};
vector<int> v{2,3,5,7,11,12,17};
vector<string> cities{"Berlin","New York","London"};
complex<double> c{4.0,3.0};

原理:

编译器看到{t1,t2,…tn}的参数列表,首先自动调用参数初始化,将其转换成initializer_list,它关联至一个array<T,n>。调用函数时该array内的元素可被编译器分解逐一传给函数,但若函数参数是个initializer_list,调用者却不能给予数个T参数然后以为它们会被自动转为一个initializer_list传入。

int i;   //i没有定义值
int j{};   //初始化为0
int *p;  // 没有定义值
int *q{};  //初始化为nullptr

构造函数

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class P {
public:
  P(int a,int b) { cout << "P(int, int), a=" << a << ", b=" << b << endl; }
  
};
int main()
{
    P p(77,5);
    P q{77,5};
    P r{77,5,42};
    P s = {77, 5};
    return 0;
}

C++会先使用{}中的参数生成一个std::initializer_list对象,然后调用赋值对象的构造函数。

在stl库中出现大量的这种使用方式。

forward_list.h

stl_map.h

stl_algo.h

stl_bvector.h

stl_set.h

stl_list.h

basic_string.h

#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
    vector<int> v1{2, 5, 7, 13, 69, 83, 50};
    vector<int> v2({2, 5, 7, 13, 69, 83, 50});
    vector<int> v3;
    v3 = {2, 5, 7, 13, 69, 83, 50};
    v3.insert(v3.begin() + 2, {0, 1, 2, 3, 4});

    for (auto i : v3)
        cout << i << ' ';
    cout << endl;

    cout << max({string("Ace"), string("Stacy"), string("Sabrina"),
               string("Barkley")})<<endl; // Stacy  字典排序
    cout << min({string("Ace"), string("Stacy"), string("Sabrina"),
               string("Barkley")})<<endl; // Ace
    cout << max({54, 16, 48, 5})<<endl;     // 54
    cout << min({54, 16, 48, 5})<<endl;     // 5
    return 0;
}

可能出现的问题(Narrowing Initializations)

  int x1(5.3);                     // x1的值为5
  int x2 = 5.3;                    // x2的值为5
  int x3{5.0};                     // error
  int x4{5.3};                     // error
  char c1{7};                      // OK,c1的值是ascii码对应的字符
  char c2{99999};                  // error ,99999不能与一个字符对应
  std::vector<int> v1{1, 2, 4, 5}; // OK
  std::vector<int> v2{1, 2.3, 4, 5.6}; // Error
  int a {}

上一篇 Big five

下一篇 流控

Comments

Content