C++变量初始化形式及其默认初始值问题
倒地不起的土豆 人气:0什么是初始化
当对象在创建时获得了一个特定的值,我们就说这个对象被初始化了。
注意:在C++语言中,初始化和赋值是两个完全不同的操作。
初始化:创建变量时赋予其一个初始值。
赋值:把对象的当前值删除,并赋予一个新的值。
而在很多类中,初始化和赋值的区别事关底层效率问题:前者直接初始化数据成员,后者则先初始化再赋值。
初始化方式
默认初始化
在下面情况发生:
在块作用域中定义非静态变量或者数组时没有赋初值
{ int var; int arr[10]; }
当一个类本身含有类类型的成员且使用合成的默认构造函数时
class B { int a = 1; int b = 2; }; class A { B m_b; };
当类类型的成员没有在构造函数初始化列表中显示地初始化时
简单来说,如果在变量初始化时没有指定初始值,则变量进行默认初始化,此时变量被赋予了默认值,默认值到底是什么由变量类型和变量的位置决定的,我们后面会具体讲解
值初始化
值初始化是只使用了初始化器(即使用了圆括号或花括号)但却没有提供初始值的情况。
int main() { int *p = new int();//值初始化 vector<int> vec(10);//值初始化 //int a();错误的初始化方式 int a = int();//值初始化 return 0; }
注意:当不采用动态分配内存的方式(即不采用new运算符)时,写成int a();是错误的值初始化方式,因为这种方式声明了一个函数而不是进行值初始化。如果一定要进行值初始化,必须结合拷贝初始化使用,即写成int a=int();
- 对于内置类型初始值为0
- 对于类类型则调用其默认构造函数,如果没有默认构造函数,则不能进行值初始化。
class A {//由于显示声明了构造函数,所以没有默认构造函数 public: A(int x) { a = x; } int a; }; int main() { A object{};//由于没有默认构造函数,初始化出错 cout << object.a << endl; return 0; }
直接初始化/拷贝初始化
直接初始化与拷贝初始化对应,其内部实现机理不同。
- 直接初始化在下面情况下发生
- 采用圆括号的方式进行变量初始化
- 与值初始化不同,括号里一定要有初始值
- 用emplace成员创建的元素都进行直接初始化
拷贝初始化在下面情况下发生
- 采用等号(=)进行初始化
- 从一个返回类型为非引用类型的函数返回一个对象
- 用列表初始化一个数组中的元素
int main() { int a(5);//直接初始化 vector<int>vec1(10);//值初始化 vector<int>vec2(vec1);//直接初始化 vector<int>vec3(10,1);//直接初始化 int b = 10;//拷贝初始化; vector<int>vec4 = vec3;//拷贝初始化 }
当使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。
注意:虽然拷贝初始化看起来像是给变量赋值,实际上是执行了初始化操作,与先定义再赋值本质不同。
可以看下面例子:
在这里插入代码片class Foo { public: Foo() { cout << "Foo()" << endl; }; Foo(int n) { cout << "Foo(int n)" << endl; } Foo(const Foo&x) { cout << "Foo(const Foo&x)" << endl; } Foo& operator=(const Foo&x) { cout << "Foo& operator=(const Foo&x) " << endl; return *this; } Foo& operator+(const Foo&x) { a += x.a; cout << "Foo& operator+(const Foo&x) " << endl; return *this; } int a=1; }; int main() { Foo f1;//默认初始化 Foo f2 = f1;//拷贝初始化 Foo f3=f1+f2;//拷贝初始化 f3 = f2;//赋值操作 }
输出结构:
可以看到在Foo f3=f1+f2;这行代码中并没有执行赋值操作
(1)对于内置类型变量(如int,double,bool等),直接初始化与拷贝初始化差别可以忽略不计。
(2)对于类类型的变量(如string或其他自定义类型),直接初始化调用类的构造函数(调用参数类型最佳匹配的那个),拷贝初始化调用类的拷贝构造函数。
特别的,当对类类型变量进行初始化时,如果类的构造函数采用了explicit
修饰而且需要隐式类型转换时,则只能通过直接初始化而不能通过拷贝初始化进行操作。
列表初始化
列表初始化是C++ 11 新引进的初始化方式,它采用一对花括号({}
)进行初始化操作。而在此之前,比如C++98/03 只有数组和POD类型才可以使用列表初始化。
到了C++ 11,能用直接初始化和拷贝初始化的地方都能用列表初始化,而且列表初始化能对容器进行方便的初始化,所以在新的C++标准中,推荐使用列表初始化的方式进行初始化。
而在某些情况下,初始化的真实含义依赖于初始值时用的是花括号还是圆括号。
代码如下:
int main(void) { vector<int>vec1(10);//vec1有10个元素,每个元素值为0 vector<int>vec2{10};//vec2有1个元素,值为10 vector<int>vec3(10, 1); // vec3有10个元素,每个元素值为1 vector<int>vec4{ 10, 1 };// vec4有2个元素,值分别为10和1 }
- 如果使用圆括号,提供的值是用来构造对象的
- 如果使用花括号,表明我们相要列表初始化该对象,即尽可能把花括号中的值当成元素初始值来处理。
- 但是如果提供的值不能用来列表初始化,则考虑通过构造来进行初始化
vector<string>vec5{10};
由于花括号里的值与元素类型不同,不能进行列表初始化,所以将vec5有10个元素,每个元素进行默认初始化。
当用于内置类型的变量时,这种初始化形式还有一个重要的特点:如果使用列表初始化且初始值存在丢失信息的风险,则编译器将报错
int main(void) { double d = 1.2345; int a(d), b = d; //正确;虽然编译器不报错,但是会提示存在丢失信息的风险 int x{ d }, y = { d };//错误;编译器会报错,因为从double转换到int存在丢失信息的风险。 }
默认初始值
内置类型的初始值由定义的位置决定
- 定义在任何函数体之外的变量被初始化为0
- 定义在函数体内部的局部变量则未定义,如果试图拷贝或者以其他形式访问该变量,则会引发错误
- 但是函数体内部的局部静态变量例外,如果局部静态变量没有显示的初始化,它将执行值初始化
int global_a;//值为0 short c;//值为0 int main(void) { static int a;//值为0 int b;//错误;未进行初始化 }
对于类类型,其默认初始值由类自己决定
- 如果类中的数据成员在默认构造函数中进行了赋值,则默认初始化值优先使用默认构造函数的值
- 如果在默认构造函数中没有赋值,但是该数据成员提供了类内初始值,则创建对象时,其默认初始值就是类内初始值,
- 如果在默认构造函数中没有赋值且没有类内初始值,对于内置类型,则其值未定义,对于类类型,则对其进行默认初始化
class B { public: string str="123"; }; class A { public: A() { x = 1; } int x = 5; int y = 10; B b; }; A global_a; int main(void) { A a; cout << global_a.x << " " << global_a.y << " " << global_a.b.str << endl; cout << a.x << " " << a.y << " " << a.b.str << endl; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
加载全部内容