C++类中成员变量的初始化总结二
C++类中成员变量的初始化总结一
以前写过一篇C++类中成员变量的初始化总结一,
现在发现还得在补充一些
三种成员变量初始化位置
对于C++11来说,类的成员变量有三个位置进行初始化:
-
声明时初始化
-
初始化列表初始化( 成员变量初始化的顺序是按照在那种定义的顺序)
以下三种情况下必须使用初始化成员列表
一、需要初始化的数据成员是对象,且该对象不能默认构造。(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化);
二、需要初始化const修饰的类成员;(C++11后,声明时初始化也可以)
三、需要初始化引用成员数据;
- 构造函数内赋值初始化成员变量
关于初始化列表和构造函数内赋值的区别:
https://blog.csdn.net/lws123253/article/details/80368047?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.compare&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.compare
对于内置类型(char,int……指针等):基本上是没有区别的,效率上也不存在多大差异
对于自定义类对象的成员初始化
- 初始化列表是直接调用拷贝函数构造;
- 构造函数内赋值是先调用默认构造函数构造(当然,你得保证你定义的类有无参构造函数),在调用重载赋值函数赋值
所以使用初始化列表效率高很多。能使用初始化列表的时候尽量使用初始化列表
成员变量初始化的三种位置:
初始化的顺序:先进行声明时初始化,然后进行初始化列表初始化,最后进行构造函数初始化
#include<iostream>
using namespace std;
class example1{
public:
int a=1;
example1(){
cout<<this->a<<endl;
}
example1(int n):a(n){
cout<<a<<endl;
}
example1(int n,int f):a(4){
a=n;
cout<<a<<endl;
}
};
int main(){
example1 t1,t2(2),t3(3,0);
system("pause");
return 0;
}
结果:
初始化列表初始化的变量值会覆盖掉声明时初始化的值,而构造函数中初始化的值又会覆盖掉初始化列表的。
可以看出初始化的顺序:先进行声明时初始化,然后进行初始化列表初始化,最后进行构造函数初始化
对于类的成员对象的初始化
class test2{
public:
int n;
test2(){
n=5;
cout<<n<<endl;
}
};
class example2{
public:
test2 t;
example2(){
//若test2对象成员带有无参的构造函数,则在example2构造函数中先调用了test2无参构造函数
//所以这里可以直接赋值
t.n=6;
cout<<t.n<<endl;
}
};
class test3{
public:
int n;
test3(int x){
n=x;
}
};
class example3{
public:
test3 t;//若该对象没有无参的构造函数,则此处的t还无法初始化,
example3():t(7){ //必须在在初始化列表处进行初始化
cout<<t.n<<endl;
}
};
int main(){
example2 t2;
example3 t3;
system("pause");
return 0;
}
成员数组的初始化
首先明确:成员数组不能通过初始化列表来初始化
所以只能考虑声明时初始化或构造函数内赋值来初始化了
基本类型的成员数组
举例:
- 声明时通过{}来初始化
class example4{
public:
int n[3]{0,1,2};//声明时通过{}来初始化
example4(){
cout<<"[";
for(int i=0;i<3;i++){
cout<<n[i];
if(i!=2)//最后一个元素后面没空格
cout<<" ";
}
cout<<"]"<<endl;
}
};
- 构造函数内循环赋值来初始化
class example5{
public:
int n[3];
example5(){
cout<<"[";
for(int i=0;i<3;i++){
n[i]=i; //构造函数内循环赋值来初始化
cout<<n[i];
if(i!=2)//最后一个元素后面没空格
cout<<" ";
}
cout<<"]"<<endl;
}
};
int main(){
example4 t4,t5;
system("pause");
return 0;
}
对象成员数组
- 若该对象有无参的构造函数,则会在声明时调用每一个元素的无参构造函数初始化数组的每一个元素
class test4{
public:
int n;
test4(){
cout<<"i am first"<<endl;
n=5;
}
};
class example4{
public:
test4 t[4];//该对象数组中的每一个元素都有无参构造函数
example4(){
cout<<"i am second"<<endl;
t[0].n=6;//数组元素已经被构造了,所以这里可以直接赋值
cout<<"[";
for(int i=0;i<4;i++){
cout<<t[i].n;
if(i!=3)
cout<<" ";
}
cout<<"]"<<endl;
}
};
int main(){
example4 t4;
system("pause");
return 0;
}
- 若该对象没有无参的构造函数,则可以在声明时利用 { }来初始化对象数组中的每一个元素
class test5{
public:
int n;
test5(int x){
cout<<"i am first"<<endl;
n=x;
}
/*当然,如果你这里给一个默认参数,那就和上面的无参构造函数一样了,每个元素都可以默认构造
test5(int x=5){
cout<<"i am first"<<endl;
n=x;
}
*/
};
class example5{
public:
test5 t[4]{test5(1),test5(2),test5(3),test5(4)};//该对象数组中的每一个元素都需要有参构造函数来构造
example5(){
cout<<"i am second"<<endl;
t[0].n=6;//所以这里可以直接赋值
cout<<"[";
for(int i=0;i<4;i++){
cout<<t[i].n;
if(i!=3)
cout<<" ";
}
cout<<"]"<<endl;
}
};
int main(){
example5 t5;
system("pause");
return 0;
}
但是用 { }的方式初始化数组,对于数组很大时,我们不可能把所有的值都手动放在 { }中呀
所以定义对象数组时,最好保证对象所属的类本身有默认无参的构造函数
或者
有参构造函数的参数都有默认值,那就和上面的无参构造函数一样了,每个元素都可以默认构造,
要不然,初始化数组是个麻烦事情,
用对象指针数组代替对象数组
既然对象数组有点麻烦,那就用对象指针数组。
class test6{
public:
int n;
test6(int x){
cout<<"i am first"<<endl;
n=x;
}
};
class example6{
public:
test6* ptr[4];//该对象指针数组
example6(){
cout<<"i am second"<<endl;
for(int i=0;i<4;i++){
ptr[i]=new test6(i); //注意在释放内存
}
}
~example6(){
for(int j=0;j<4;j++)
delete ptr[j];
}
};
int main(){
example6 t6;
for(int j=0;j<4;j++){
cout<<t6.ptr[j]->n<<" ";
}
system("pause");
return 0;
}
该方法与对象数组相比需要额外内存用于存放指针。
关于对象数组初始化的问题,这里还看到了一种方法 使用placement new
参考:https://zhidao.baidu.com/question/2122632718727528707.html
假设test6是需要存储的对象,
先为此数组分配raw memory,然后使用"placement new"在这块内存上构造test6对象。
具体操作:
1. //分配足够的raw memory,给一个预备容纳10个test6 对象的数组使用
void *rawMemory = operator new(10*sizeof(test6));
2. //让bestPieces指向此内存,使这块内存被视为一个test6数组
test6 *bestPieces = reinterpret_cast<test6*>(rawMemory);
3. //利用"placement new"构造这块内存中的test6 对象
int x = 0;
for(int i = 0; i < 10; i++){
new (&bestPieces[i]) test6( x );
}
注意:该方法维护比较困难。在数组内对象结束生命时,要以手动方式调用destructors,最后还得调用operator delete释放raw memory。
4. //将bestPieces中对象以构造次序的反序析构掉
for(i = 0; i < 10; i++){
bestPieces[i].~test6();
}
5. //释放raw memory
operator delete (rawMemory);
补充一下数组和动态数组的初始化
- 普通数组的初始化
//啥也不做,str[5]中的每个元素都是随机值
char str[5];
//数组中第一个为'\0',其他的全为0,'\0'其实就是0 同时可以表示字符串结束。也就是说数组内全是0
char str[5]={'\0'};
等价于
char str[5]={0};
char str[5]={0,0,0,0,0};
//数组中第一个为1,其他的全为0,
char str[5]={1};
等价于
char str[5]={1,0,0,0,0};
- 对于内置数据类型元素的动态数组,必须使用()来显示指定程序执行初始化操作,否则程序不执行初始化操作:
//动态数组
int *pia = new int[10]; // 每个元素都没有初始化,
int *pia2 = new int[10] (); // 每个元素初始化为0
//注意不能使用int *pia3=new int[5](1)来使数组元素全部初始化为1
int* pia3 = new int[10]{ 0,1,2,3,4,5,6,7,8,9 }; //VS2019支持
//new 一个元素
int *p1 = new int(1); // 元素初始化为1
int *p2 = new int(); // 元素初始化为0
int *p3 = new int; // 元素没有初始化
//字符串初始化
char* number=new char[n+1];
memset(number,'0',n);//每个字符都初始化为'0'
number[n]='\0';
- 类类型元素的数组,则无论是否使用(),都会自动调用其默认构造函数来初始化:
当然前提是你这个类本身有无参的默认构造函数,要不然也不行。
string *psa = new string[10]; // 每个元素调用默认构造函数初始化
string *psa = new string[10](); // 每个元素调用默认构造函数初始化
vector作为成员变量的初始化
- 直接让成员vector< >默认构造初始化,然后再让类Foo的构造函数通过push_back()去给vector< >赋值
class Foo{
public:
vector<string> name;//含有默认的无参构造函数
vector<int> val;//含有默认的无参构造函数
Foo(vector<string> &s,vector<int> &t){
for(auto i:s)
name.push_back(i);
for(auto j:t)
val.push_back(j);
}
void print(){
for(auto i:name)
cout<<i<<"\t";
cout<<endl;
for(auto j:val)
cout<<j<<"\t";
cout<<endl;
}
};
int main(){
vector<string> s={"one","two","three","four"};
vector<int> t={1,2,3,4};
Foo f(s,t);
f.print();
system("pause");
return 0;
}
-
不让成员vector默认构造,而采用显式构造函数
class Foo{
public:
// 这两种方法都会报错,提示“应输入类型说明符”
vector<string> name(5);
vector<int> val(5,0);
}
>C++11之前不能这么做是因为:类的定义实际上相当于是类型的声明,在实例化成对象前并没有分配存储空间,
>所以它内部的成员在类定义时并没有分配空间,空间都没有,所以不能直接初始化。必须用初始化列表
- 正确的方法:
- C++ 11以前:成员列表初始化
class Foo {
public:
vector<string> name;
vector<int> val;
Foo() : name(5), val(5,0) {}
};
- C++11以后,声明时直接 {}初始化
C++11后,可以在定义时初始化,但是格式必须是 {} 或 赋值操作
class Foo(){
public:
// 法一,赋值运算函数来初始化
vector<string> name = vector<string>(5);
// 法二,拷贝构造函数来初始化
vector<int> val{vector<int>(5,0)};
}
通过vector作为类的成员这个例子可以看出来,虽然可以在声明时初始化,但是必须用 { }的形式或赋值运算函数 (均是拷贝式的构造方式),不能直接在类中利用成员类对象的有参构造函数来初始化。
所以尽量还是使用列表初始化或者构造函数内赋值初始化成员变量