C++程序设计:原理与实践读书笔记(第十三章)

我们可以编写接受初始化器列表参数的构造函数:

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

总结一下指针问题:

  • 不要使用空指针进行数据访问;
  • 对你的指针进行初始化;
  • 不要访问不存在的数组元素;
  • 不要通过一个已清除的指针访问数据;
  • 不要返回指向局部变量的指针;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值