我们可以编写接受初始化器列表参数的构造函数:
class vector{
int sz; //大小
double* elem; //指向元素的指针
public:
vector(int s) //构造函数
:sz{s}, elem{new double[sz]} //为元素分配未初始化的内存
{
for(int i=0; i<sz; ++i) elem[i] = 0.0; //初始化
}
vector(initializer_list<double> lst) //初始化器列表构造函数
:sz{lst.size()}, elem{new double[sz]} //为元素分配未初始化的内存
{
copy(lst.begin(), lst.end(), elem); //初始化
}
}
编译器会将{}列表中的一个值解释为一个元素值,并将它作为一个initializer_list的元素传递给初始化器列表格构造函数。
在大多数情况下,{}初始化器列表前的=是可选的,因此我们可以编写如下代码:
vector v11 = {1,2,3}; //三个元素1.0、2.0、3.0
vector v12{1,2,3}; //三个元素1.0、2.0、3.0
拷贝构造函数:接受待拷贝对象的引用作为参数,例如当我们需要用一个vector初始化另一个vector时,拷贝构造函数会被调用:
class vector{
int sz;
double* elem;
public:
vector(const vector&); //拷贝构造函数
//...
}
因此定义的拷贝构造函数类似如下:
vector::vector(const vector& arg)
//分配元素,然后通过拷贝初始化它们:
:sz{arg.sz}, elem{new double[arg.sz]}
{
copy(arg.elem, arg.elem.sz, elem);
}
有了这个拷贝构造函数,我们就可以进行拷贝构造:
vector v2 = v;
//除此之外,我们还可以使用下面这种等价形式
//vector v2{v};
除了拷贝初始化外,我们也应该定义拷贝赋值操作:
class vector{
int sz;
double* elem;
public:
vector& operator=(const vector&); //拷贝赋值
//...
}
vector& operator=(const vector& a)
//将本vector变为a的副本
{
double* p = new double[a.sz]; //分配新空间
copy(a.elem, a.elem.sz, p); //拷贝元素
delete[] elem; //释放旧空间
elem = p; //现在我们重置elem了
sz = a.sz;
return *this; //返回一个自引用
}
这样就可以进行拷贝赋值操作:
vector v(3);
v.set(2, 2.2);
vector v2(4);
v2 = v;
对于拷贝一个对象,我们应该区别应该拷贝指针还是拷贝指针所指向的对象:
- 浅拷贝(shallow copy)只拷贝指针。
- 深拷贝(deep copy)将拷贝指针指向的数据。
当类对象需要深拷贝时,我们需要为其定义拷贝构造函数和拷贝赋值函数。
除了拷贝之外,还存在移动操作,作为拷贝操作的补充。当一个对象元素很多时,拷贝的代价可能会很高,这是,直接将原有对象指向的元素移动到新对象会更加高效。
class vector{
int sz;
double* elem;
public:
vector(vector&& a); //移动构造函数
vector& operator=(vector&&); //移动赋值
//...
};
vector::vector(vector&& a)
:sz{a.sz}, elem{a.elem} //拷贝a的elem和sz
{
a.sz = 0; //令a变为空vector
a.elem = nullptr;
}
vector& vector::operator=(vector&& a) //将a移动到本vector
{
delete[] elem; //释放旧空间
elem = a.elem; //拷贝a的elem和sz
sz = a.sz;
a.elem = nullptr; //令a变为空vector
a.sz = 0;
return *this; //返回一个自引用
}
个人总结:拷贝时,创建一份要拷贝对象的副本;移动时,不创建要拷贝对象的副本,而是把要拷贝对象的内容直接指向新对象中。
由关键字explicit定义的构造函数(即显示构造函数)只能用于对象的构造,而不能用于隐式转换。
class vector{
//...
explicit vector(int);
//...
};
vector v = 10; //错误:不存在int到vector的转换
v = 20; //错误:不存在int到vector的转换
vector v0(10); //正确
void f(const vector&);
f(10); //错误:不存在int到vector<double>的转换
f(vector(10)); //正确
为了避免意外的类型转换,我们和标准库都将单参数构造函数定义为explicit的。当拿不定主意时,应将所有的单参数的构造函数定义为explicit的。
如果希望使用下标方式访问元素时,应该实现下标运算符返回元素的引用:
class vector{
//...
double& operator[](int n){return elem[n];} //返回引用,用于非const的vector
double operator[](int n) const; //用于const vector
}
作为数组名向指针隐式转换的一个结果,你不能通过赋值操作来拷贝数组,因此必须编写一些更复杂的代码来实现:
for(int i=0;i<100;++i) x[i] = y[i]; //拷贝100个int
memcpy(x, y, 100*sizeof(int)); //拷贝100*sizeof(int)个字节
copy(y, y+100, x); //拷贝100个int
总结一下指针问题:
- 不要使用空指针进行数据访问;
- 对你的指针进行初始化;
- 不要访问不存在的数组元素;
- 不要通过一个已清除的指针访问数据;
- 不要返回指向局部变量的指针;