默认实参需要注意以下几点:
(1)函数默认实参的赋值应从右往左,否则编译报错,因为参数入栈应该从右往左。
void f(int, int, int = 1);
void f(int, int = 2, int);
void f(int = 3, int, int);
(2)类外的默认实参会使类的非默认构造函数变成默认构造函数。
class A
{
public:
A(int a);
void Print()
{
std::cout << i << std::endl;
}
int i;
};
// 类外初始化默认实参
A::A(int a = 100) : i(a) {}
/** 在类外初始化非默认构造函数,将其变为默认构造函数 */
A a = A();
a.Print(); // 输出:100
(3)如果在类中添加了该函数的该参数的默认实参,那么在类外再次定义该参数的默认实参,会发生重定义错误。
(4)虚函数的默认实参将根据对象的静态类型(编译时直接指定不会更改的类型)确定。
struct F
{
virtual ~F()
{
// 父类
}
};
struct C : F
{
// 子类
};
/**
* 对于p来说静态类型就是F
* 对于p来说动态类型就是C
* 所以如果父类和子类都有默认实参的话,会使用F中的默认实参函数
*/
F* p = new C();
默认初始化没什么难的,需要注意的是默认初始化是C++11新添加的,主要看一下位域初始化。
struct B
{
// int的低8位被初始化为12
int x : 8 = 12;
// int的低8位被初始化为17
int y : 4 { 17 };
};
在使用位域初始化的时候,一定要注意后面使用的运算符与:
的优先级问题。
#include
std::initializer_list
template <class _Elem>
class initializer_list {
public:
using value_type = _Elem;
using reference = const _Elem&;
using const_reference = const _Elem&;
using size_type = size_t;
using iterator = const _Elem*;
using const_iterator = const _Elem*;
constexpr initializer_list() noexcept : _First(nullptr), _Last(nullptr) {}
constexpr initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) noexcept
: _First(_First_arg), _Last(_Last_arg) {}
_NODISCARD constexpr const _Elem* begin() const noexcept {
return _First;
}
_NODISCARD constexpr const _Elem* end() const noexcept {
return _Last;
}
_NODISCARD constexpr size_t size() const noexcept {
return static_cast<size_t>(_Last - _First);
}
private:
const _Elem* _First;
const _Elem* _Last;
};
可以看出initializer_list就是一个有begin和end的一片内存空间。
int x[] = { 1, 2, 3, 4, 5 };
std::vector<int> v{1, 2, 3, 4, 5};
相当于使用initializer_list{1, 2, 3, 4, 5},就是先构造了一个array{ 1, 2, 3, 4, 5 },再把首地址和尾地址赋给begin和end。
class A
{
public:
/** 使用初始化列表构造并遍历 */
A(std::initializer_list<int> list)
{
for (const int* item = list.begin(); item != list.end(); ++item)
{
std::cout << *item << std::endl;
}
}
};
初始化优先级:
/** 调用构造5个元素,每个元素都是5 */
std::vector<int> x1(5, 5);
/** 调用构造2个元素,5和5 */
std::vector<int> x2{5, 5};
隐式缩窄转换规则:
(1)高位向低位转换,如double向float,float向int。
(2)从整数类型向超过其最大值的类型转换,如:int a = 999,向char转换。
为了增加灵活性,C++20增加了指定初始化。
struct Point3D
{
int x;
int y;
int z;
};
// 初始化列表构造,x=0,y=0,z=3
Point3D{.z = 3};
虽然增加了指定初始化,但有很多的限定:
(1)Point3D如果有了构造函数,则初始化列表会按照构造函数进行,指定的成员变量很有可能失败。
(2)指定初始化的顺序要按照定义顺序进行。
(3)联合体一次只能指定一个;指定初始化不能嵌套;指定初始化不能和普通的混用。