初始化
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;
}
输出结果:
(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;
}
输出结果:
示例二:定义一个构造函数,阻止编译器自动生成默认构造函数
#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;
}
输出结果:
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';
}
输出结果:
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
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 {}