C++初始化列表详解
本篇博客详细讲解初始化列表的用法、必须使用初始化列表场景、初始化列表的优缺点等。
写在前面:
初始化列表是一种用于初始化成员变量的语法结构,它可以在类的构造函数中使用,用于初始化类的成员变量。
而 列表初始化指的是 是一种初始化变量的简洁方式,可以用花括号{}来表示。列表初始化可以用于各种类型的变量,包括基本类型、数组、结构体、类等。
今天先来浅浅讲一下初始化列表,列表初始化在后面的博客会整理出来
一、常规初始化成员变量方式
话不多说我们先来看看一些初始化成员变量的方式
1.1 默认构造函数
作为类的六大默认成员函数,构造函数主要作用是什么呢?
构造函数是一种用于创建对象并初始化对象成员的特殊函数,它在对象创建时被调用,执行一些必要的操作以确保对象的正确性和一致性,每个类都可以定义一个或多个构造函数,用于初始化对象的成员变量。
这里浅浅说一下 初始化和赋值的区别
避免有的同学对其概念混淆,将类成员变量的初始化和 " = " 赋值 混为一谈
1.1.1 初始化
初始化是在创建一个变量时为其赋予一个初始值的过程,可以使用多种方式进行初始化,包括默认初始化、值初始化、直接初始化、列表初始化等。
例如:
int a; // 默认初始化,a的值是未定义的
int b = 0; // 值初始化,b的值为0
int c(1); // 直接初始化,c的值为1
int d{2}; // 列表初始化,d的值为2
1.1.2 赋值
赋值是在变量已经存在的情况下,将一个新的值赋给该变量的过程。赋值使用赋值操作符"="来完成,例如:
int a = 1;
a = 2; // 将2赋值给变量a
继续回到我们的成员初始化
例如我们使用默认的构造函数来对类的成员变量初始化
class Test
{
public:
Test()//默认构造函数,写不写都可以
{
}
void Print()
{
cout << m_ia << " " << m_ib << endl;
}
~Test()
{
}
private:
int m_ia;
int m_ib;
};
int main()
{
Test clsT1;
clsT1.Print();
return 0;
}
运行结果:
可以看到,使用默认的构造函数,只是创建了这个变量,没有赋初值,因此是一个默认值,当然我们也可以在构造函数中对其赋初值,示例:
这是不带参的构造函数,正如上面提到的,每个类可以定义一到多个构造函数,因此我们也可以定义一个带参的构造函数:
1.2 带参的构造函数
代码示例:
#include<iostream>
using namespace std;
class Test
{
public:
Test(int a, int b)
{
m_ia = a;
m_ib = b;
}
void Print()
{
cout << m_ia << " " << m_ib << endl;
}
~Test()
{
}
private:
int m_ia;
int m_ib;
};
int main()
{
Test clsT2(1, 2);
clsT2.Print();
return 0;
}
结果:
这种方式也是OK 的,接下来我们再看这样一种情况
//当我们定义下面这两种成员变量,一个是 const 类型的成员变量
private:
const int m_ia;
如果再使用我们原来的构造函数时:
这时候发现编译器开始给我们画红线了,有的小伙伴可能和我一样有疑问,没错啊 const 类型的成员变量本就不可以被修改,那么这样的变量定义再类里面有什么意义呢?
1.保护数据:常量成员变量可以在类中定义不可变的数据,保护数据的一致性和完整性,避免意外的数据修改。这对于提高代码的可靠性和安全性非常重要。
2.提高效率:常量成员变量可以在对象创建时进行初始化,避免了因为对象初始化后再进行赋值操作而浪费时间和资源。
3.优化代码:常量成员变量可以使代码更加清晰和易于理解,因为它们的值是固定的,不会被修改。这样可以使代码更加简洁和易于维护。
4.精细控制权限:常量成员变量可以被声明为private访问权限,这样只有类的成员函数可以访问它们,确保了其它代码无法修改该数据。
那么常成员变量如何进行初始化呢?接下来就可以把我们今天的主题,初始化列表 拿出来讲了
二、初始化列表
我们先来看看初始化列表的方式:
代码示例:
#include<iostream>
using namespace std;
class Test
{
public:
//初始化列表的方式
Test():m_ia(0),m_ib(1)
{
}
void Print()
{
cout << m_ia << " " << m_ib << endl;
}
~Test()
{
}
private:
int m_ia;
int m_ib;
};
int main()
{
Test clsT2;
clsT2.Print();
return 0;
}
结果:
可以看到初始化列表初始化成员变量的方式还是和常规的构造函数不一样的,初始化的代码是写在 构造函数的 () 后的
构造函数() 再接着一个冒号 " : " m_a(value)
再接着用逗号将成员变量一一隔开 " , " m_b(value2) , m_n(value)
最后一个成员变量初始化完后不用写逗号 直接跟一个 {} 即可
当然如果成员变量很多的话,一行写不下可以分行来写,例如:
#include<iostream>
#include<string>
using namespace std;
class Test
{
public:
Test():
m_ia(0),
m_ib(1),
m_str1("hello"),
m_d1(3.14),
m_c1('a')
{
}
void Print()
{
cout << m_ia << " " << m_ib << " "<< m_str1<<" "<< m_d1 << " "<< m_c1<<endl;
}
~Test(){}
private:
int m_ia;
int m_ib;
string m_str1;
double m_d1;
char m_c1;
};
int main()
{
Test clsT2;
clsT2.Print();
return 0;
}
看下结果:
2.1 使用初始化列表初始化成员变量的顺序可以改变吗?
答案:尽量按照变量声明的顺序来进行初始化
我们先来看一下代码示例:
#include<iostream>
#include<string>
using namespace std;
class Test
{
public:
Test():m_ic(1), m_ia(2),m_ib(3)
{
}
void Print()
{
cout << m_ia << " " <<m_ib<<" "<< m_ic<<endl;
}
~Test(){}
private:
int m_ia;
int m_ib;
int m_ic;
};
int main()
{
Test clsT2;
clsT2.Print();
return 0;
}
运行结果:
没啥问题,我们再试试:
使用 m_ia +1 来初始化 m_ic
也没啥问题,再试试用 m_ic + 1来初始化 m_ib
由于声明顺序如下:
1 int m_ia;
2 int m_ib;
3 int m_ic;
当我们成员变量初始化依赖其他的成员变量时,最好按照按照声明的顺序来一一对其进行初始化,且依赖关系也要按照声明的顺序来控制,否则容易造成未知的一些风险
2.2 那些情况下必须要使用初始化列表?
像上述的那些成员变量可以用常规的构造函数初始化,也可以用初始化列表进行初始化,比较简洁,而以下这些情况必须要用初始化列表来初始化
2.2.1 const 成员变量
代码示例:
class Test
{
public:
Test()
{
m_ia = 2;//报错表达式必须是可修改的左值
}
Test():m_ia(2) //初始化列表初始化
{
}
void Print()
{
cout << m_ia <<endl;
}
~Test(){}
private:
const int m_ia;
};
当为常成员变量时只能用初始化列表来进行初始化
2.2.2 引用成员变量
引用和指针的区别大家应该都知道
引用就是 别名 ,例如张三别名有 小张、小张三,小张和小张三都代表 张三本人
而 指针就是有个人他认知张三,可以通过这个人来找到张三
而引用有一个特点就是必须要在定义时就初始化
示例:
因此引用成员变量必须使用初始化列表进行初始化
代码示例:
#include<iostream>
#include<string>
using namespace std;
class Test
{
public:
Test(int&a):m_ia(a) //引用成员变量使用初始化列表初始化
{
}
void Print()
{
cout << m_ia <<endl;
}
~Test(){}
private:
int& m_ia;
};
int main()
{
int a = 9;
Test clsT2(a);
clsT2.Print();
return 0;
}
2.2.3 基类初始化
我们都知道,子类继承自父类时,实例化出子类对象时需要先调用父类的构造函数,再调用子类自己的构造函数 我们看一下这一段示例代码:
#include<iostream>
#include<string>
using namespace std;
class Base
{
Base(string str)
{}
private:
string m_strName;
};
class Test : public Base
{
public:
Test(int&a):m_ia(a)
{
}
void Print()
{
cout << m_ia <<endl;
}
~Test(){}
private:
int& m_ia;
};
int main()
{
int a = 9;
Test clsT2(a);
clsT2.Print();
return 0;
}
来看一下编译结果:
当类没有定义自己的默认构造函数,而是定义了带参的构造函数,那么这时候必须使用初始化列表来对基类进行初始化
这里说一下:只有类没有任何构造函数时,编译器才会自动生成默认的构造函数
2.2.4 初始化成员变量为类对象
当一个类的成员变量是一个类对象时,那么就必须使用初始化列表来对其进行初始化,例如:
class Apple
{
public:
Apple(string str)//没有默认构造函数
{
m_strName = str;
}
private:
string m_strName;
};
class Test
{
public:
Test()//报错 Apple 不存在默认的构造函数
{
}
~Test(){}
private:
int m_ia;
Apple a1; //类对象
};
这时候我们就需要使用初始化列表来对其进行初始化
三 、初始化列表的优缺点有哪些
3.1 优点:
优点:
1.效率高:使用初始化列表初始化成员变量可以避免在构造函数体中进行多余的赋值操作,从而提高代码的执行效率。
2.精细控制:使用初始化列表可以在构造函数中精细控制成员变量的初始化顺序,以及对于常量成员变量和引用成员变量的初始化。
3.可读性强:使用初始化列表可以使代码更加简洁和易于阅读,因为它们将类的成员变量的初始化放在了一个地方,使代码更加清晰。
4.语法简单:初始化列表的语法简单明了,可以很容易地理解和使用。
3.2 缺点:
1.初始化顺序:如果不按照成员变量在类中的声明顺序,按照不同的顺序对成员变量进行初始化,可能会导致一些难以预测的问题。
2.可读性差:当需要初始化的成员变量很多时,初始化列表可能会变得冗长和难以阅读,影响代码的可读性。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)