C++类细节梳理之拷贝构造函数

在学习了c++类之后,终于可以把我的bug写到博客里面了

这篇博客将会重点记录c++类中的一些细节而不会完整的梳理知识点框架,相信自己写了一遍以后可以加深对这些知识点的理解

拷贝构造函数

拷贝构造函数就是构造函数,然后形参就是该构造函数相同类型的对象

形参用引用,加const

拷贝构造函数的形参应当是引用形式(避免形成形参副本,详见后面),并且前面加上const(也有说法是“其形参必须是引用,但并不限制为const,一般普遍的会加上const限制”)如果没有const的话会出现报错。

同时,const和引用还可以避免引用复制,从而提高程序效率

A(A & c) {}

cannot bind non-const lvalue reference of type 'A&' to an rvalue of type 'A'

主要原因是编译器,由于引用传进来的临时变量最后都会消失,对这个临时变量的修改就没有意义,所以有些编译器要求临时变量不能作为非const引用。这时在前面加上const就没问题了。

A(const A & c) {}

什么时候调用拷贝构造函数

下面这几种情况都会调用

A b(a);
A b = a;
new A(a)
//小a是一个已经存在的A的对象

除此之外,将一个对象作为实参,传递给被调用函数的形参对象时也会调用拷贝构造函数

用例子来说明:

#include <iostream>
using namespace std;
class A {
public:
	int v;
    A()=default;
    A(int v):v(v){}
    A(const A& that):v(that.v+2){}
};
void PrintAndDouble(A o)
{
	cout << o.v;
	cout << endl;
}
int main()
{
	A a(5);
	A b = a;
	PrintAndDouble(b);
	A c = 20;
	PrintAndDouble(c);
	A d;
	d = a;
	cout << d.v;
	return 0;
}
输出为:
9
22
5

上面的代码每调用一次拷贝构造函数都会使v的值增加2,b.v的值为5+2=7,随后调用PrintAndDouble(b)之后b作为实参传递给形参o后又调用一次拷贝构造函数,o.v=7+2=9,所以输出为9

扩展:看注释~

class A {
public:
	int v;
    A()=default;//无参数构造函数
    A(int v):v(v){}//有参数构造函数
    A(const A& that):v(that.v){}//拷贝构造函数
};

int main()
{
	A a(5);//调用有参数的构造函数
	A c = 20;//先调用有参数的构造函数构造一个隐式的对象,并使这个类的v的值为20,
             //然后再调用拷贝构造函数把这个隐式的对象拷贝构造给C
	A d;//调用无参数的构造函数
	d = a;
    A e(a)//调用拷贝构造函数
    A b = a;//调用拷贝构造函数,注意这里不是赋值运算
	return 0;
}

注意上面d已经定义后再执行d = a语句则不会调用拷贝构造函数

浅拷贝和深拷贝

当类中含有指针成员时,我们需要对指针成员进行深拷贝,不然可能会出现同一块内存被释放两次的情况

当一类中含有指针成员时通常应该重写:

1.构造函数为指针分配内存并复制指针指向的对象(深复制)

2.=操作重写,完成深复制策略

3.析构函数中释放内存

如果类中没有指针成员使用系统提供的默认构造函数就可以了

    buffer = new char[length + 1];
    for(int i = 0; i < length; i++){
        buffer[i] = str[i];
    }

值得一提的是new后面[ ]中的数字代表长度,所以new char[0]不会创建空间。代码中length+1是为了给'\0'留出空间

一个例题:

class Cat
{
	string name;
	Cat ** family;
	int size = 0;
public:
	Cat(string name="") {
		this->name = name;
		family = new Cat*[0];
		size = 0;
	}
//其余省略
};
Cat& Cat::operator=(const Cat &cat) {
    size = cat.size;
    name = cat.name;
    delete [] family;
    family = new Cat*[1];
    /*for(int i = 0; i <= size; i++){
        family[i] = cat.family[i];
    }*/
    return *this;
}
int main(){
	int num_of_cats;
	cin >> num_of_cats;
	Cat * cats = new Cat[num_of_cats];
	string name;
	for (int i = 0; i < num_of_cats; ++ i) {
		cin >> name;
		cats[i] = Cat(name); // operator =
	}
}

这里运用了重载运算符(operator=),发现如果不注释掉for循环的family[i] = cat.family[i],则会发生内存泄露,我认为原因是期初创建对象时new的是Cat*[0]没有空间,导致cat.family为nullptr,而进行赋值语句的话编译器就会创造空间,最终导致内存泄露

值得注意的是,由于类中的成员有指针,这时构造函数可能会传入一个指针对象,若是char*类型我们可以用strlen()函数来获取长度,string类型用size()来获取长度,注意strlen()函数不包括'\0',所以最后new空间时要+1

Creature::Creature(const char* _sound, int _age){
    age = _age;
    sound = new char[strlen(_sound) + 1];//此处要+1
    for(int i = 0; i < strlen(_sound); i++){
        sound[i] = _sound[i];
    }
    sound[strlen(_sound)] = '\0';
}

另一种表现形式,arr[]中arr同样是char类型的指针

void setjob(char arr[]){
        delete[] job;
        job = new char[strlen(arr) + 1];
        for(int i = 0; i < strlen(arr) + 1; i++){
            job[i] = arr[i];
        }
    }

 

若指针是一个类的类型时,一般该类中会有一个记录包含的类的数量的成员,如上面cat例题中的size 

使用引用避免形成形参副本

c++相比起c多了引用这个东西,因为实参的引用是实参的别名,使用引用时就不会复制一个形参副本,这种性质在类中也可以起到很妙的作用。

一个例题:

class Point{
    private:
    int x;
    int y;
    public:
    Point() : x(0), y(0) {}
    Point(int x, int y) : x(x), y(y) {}
    Point(Point &p) : x(p.x), y(p.y) {}
    ~Point() {}
    void move(int m, int n){
        x = m;
        y = n;
    }
    int getX() const{
        return x;
    }
    int getY() const{
        return y;
    }
    void setX(int x){
        this->x = x;
    }
    void setY(int y){
        this->y = y;
    }
};
class AofPoint{
    private:
    Point *point;
    int size;
    public:
    AofPoint(int number){
        size = number;
        point = new Point[number];
    }
    ~AofPoint(){
        delete [] point;
    }
    AofPoint(AofPoint & a){
        size = a.size;
        point = new Point[size];
        for(int i = 0; i < size; i++){
            point[i].setX(a.visit_point(i + 1).getX());
            point[i].setY(a.visit_point(i + 1).getY());
        }
    }
    Point & visit_point(int num){
    //返回的是对象别名
        return point[num - 1];
    }
};
#include "Point.h"
#include "AofPoint.h"
#include <iostream>
using namespace std;

int main()
{
	AofPoint afp(3);//创建afp对象时,也设置了动态对象数组的大小
	afp.visit_point(1).move(4,6);
	afp.visit_point(2).move(8,16);
	cout << "point(1):x=" << afp.visit_point(1).getX() << ",y=" << afp.visit_point(1).getY() << endl;
	cout << "point(2):x=" << afp.visit_point(2).getX() << ",y=" << afp.visit_point(2).getY() << endl;
	AofPoint afp2(afp);
	cout << "point(1):x=" << afp2.visit_point(1).getX() << ",y=" << afp2.visit_point(1).getY() << endl;
	cout << "point(2):x=" << afp2.visit_point(2).getX() << ",y=" << afp2.visit_point(2).getY() << endl;
	return 0;
}

本题中的Point & visit_point(int num)函数就运用到了引用不会复制形参副本的思想,如果写为Point visit_point(int num)的话,前面的afp.visit_point(1).move(4,6)和afp.visit_point(2).move(8,16)这两条语句执行完,x、y的值确实会发生变化,但之后的cout中又使用afp.visit_point(1).getX(),这时编译器为了能返回一个Point对象,就会拷贝构造一个新的对象,这会导致拷贝构造的形参会调用默认构造函数进行初始化,x、y又初始化为0。

而运用引用的话返回的是对象别名,编译器则不会拷贝构造新对象,x、y自然也不会初始化为0了

由于学习不精,文章有不正确的地方望读者指正。

  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值