初始化列表
问题:队列不需要写默认构造函数,它会调用栈的默认构造,自定义成员函数会调用它的默认构造,但万一栈不提供默认构造。会报错。
我们就必须显示调用栈的默认构造:
发现怎么写都会报错。我们就需要初始化列表。
定义
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。
class Date
{
public:
//初始化列表是每个成员定义的地方
Date(int year , int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
}
private: //每个成员的声明,声明不开空间,对象实例化的时候开空间
int _year; //这三个成员什么时候定义,对象实例化的时候整体定义
int _month;
int _day;
};
混着用
Date(int year , int month, int day)
:_year(year)
,_month(month)
{
_day = day;
}
应用
问题1:为什么要用初始化列表?有三个必须使用的场景
class A //第三个使用场景
{
public:
A(int a)
:_a(a)
{}
private:
int _a
};
Date(int year , int month, int day,int& i)
:_year(year)
,_month(month)
{
_day = day;
_x = 1;//错误,必须在定义的地方初始化,这里是赋值
_ref=i;//错误,这是赋值
}
private:
int _year;
int _month;
int _day;
const int _x;//必须在定义的地方初始化,第一个使用场景
int& _ref; //必须在定义的地方初始化,第二个使用场景
A _a; //第三个使用场景,自定义类型没有默认构造
};
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class Date
{
public:
Date(int year, int month, int day, int& i)
:_year(year)
, _month(month)
, _x(1) //必须在定义的地方初始化
,_refi(i)
,_a(1)
{
_day = day;
}
void func()
{
++_refi;
}
private:
int _year;
int _month;
int _day;
const int _x;
int& _refi;
A _a;
};
int main()
{
int n = 0;
Date d1(2023, 1, 23, n);
d1.func();
cout<<n<<endl; //n为1,i是n的别名,refi又是i的别名
return 0;
}
队列的问题也就得到了解决
class Stack
{
public:
Stack(size_t n) //没有提供默认构造 因该是Stack(size_t n=4)
{
_array = (int*)malloc(sizeof(int) * capacity);
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
top = 0;
capacity = n;
}
}
class MyQueue
{
public:
MyQueue()
:_st1(4)
,_st2(3)
{}
private:
Stack _st1;
Stack _st2;
};
int main()
{
MyQueue mq;
return 0;
}
执行时,先走初始化列表,不管你写不写,每个成员都要走初始化列表(初始化列表初始化的顺序和声明有关),再走函数体
但是_day什么时候初始化,内置类型编译器不处理,所以给了随机值,要是给了缺省值,C++11支持缺省值,这个缺省值是给初始化列表的。如果初始化列表没有显示给值,就用这个缺省值。如果显示给值了,就不用这个缺省值。
如果是自定义类型会处理,调用默认构造。
总结:
1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2.类中包含一下成员,必须放在初始化列表位置进行初始化:
引用成员变量、const成员变量、自定义类型成员(且该类没有默认构造函数的)
3.尽量使用初始化列表初始化,不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。 但是有时初始化列表和构造函数需要混着用,比如栈
Stack(size_t n)
:_array((int*)malloc(sizeof(int) * capacity))
,top(0)
,capacity(n)
{
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
//有时你需要调用一些函数,初始化,初始化列表就不能搞定
memset(_array, 0, sizeof(int) * capacity);
}
来个例题:
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.print();
}
输出:1 随机值
原因: 初始化列表初始化的顺序和声明有关,先用_a1初始化_a2,所以是随机值,在初始化_a1,为1。
2.explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余都有默认值的构造函数,还具有类型转换的作用。
class A
{
public:
A(int i)
:_a(i)
{}
private:
int _a;
};
int main()
{
A aa1(1);//构造函数初始化
A aa2 = 2;//本质是隐式类型转换,隐式类型转换中间会生成一个临时对象,用2调用A构造函数生成一个临时对象,在用这个对象去拷贝构造aa2
//A(2)编译器会在优化,直接构造
A& ref = 2;//报错,隐式类型转换中间会生成一个临时对象,临时对象具有常性,ref不能引用临时对象,权限放大,加const就可以
int i=0;
double d=1.1;
i=d; //可以,类型转换中间都会生成一个临时变量,跟传值返回生成临时对象是一样的
return 0;
}
class SeqList
{
public:
SeqList(size_t n=4)
{
_array = (int*)malloc(sizeof(int) * capacity);
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
top = 0;
capacity = n;
}
void push(const A& x) //假设顺序表中存的是A,A比较大,就要用const,引用
{
if (top == capacity)
{
int newcapacity = capacity == 0 ? 4 : capacity * 2;
int* temp = (int*)realloc(_array, sizeof(int) * newcapacity);
if (temp == nullptr)
{
std::cerr << "Memory reallocation failed" << std::endl;
return;
}
_array = temp;
capacity = newcapacity;
}
_array[top++] = x;
}
private:
//成员变量
int* _array;
int top;
int capacity;
};
int main()
{
SeqList s; //要是想在顺序表中插入数据
A aa3(3);
s.push(aa3);
A aa4(4);
s.push(aa4);//很不舒服
s.push(3);//有了隐式类型转换就可以这样写,
return 0;
}
但是我不想让隐式类型转化发生,加一个explicit关键字在构造函数位置
class A
{
public:
explicit A(int i)
:_a(i)
{}
private:
int _a;
};
以上是针对于单参数,如果是多参数呢?C++11支持多参数。
class B
{
public:
B(int b1,int b2)
:_b1(b1)
,_b2(b2)
{}
private:
int _b1;
int _b2;
};
int main()
{
B bb1(1,1);
B bb2={2,2}; //C++11支持多参数
const B& ref2={3,3};
return 0;
}
C++11还支持生成匿名对象,上面都是生成临时对象
A aa6(7); 有名对象,特点;生命周期在当前局部域
A(7); 匿名对象,特点:生命周期只在这一行