记得以前看csdn上的文章,看到过一篇文章的作者写过这样一句话:
你以为你理解和你真正理解是有差别的,这中间的差别就是几个小时的痛苦.
所以现在遇到很多我自以为简单的知识点,就算自己以为理解了,也会尝试着去进行实践,并去想下这背后的深层次的原因。
是用来解决什么问题,问题是怎么来的?
第一部分:问题是怎么来的?
c++中有2种赋值方式,一种是直接赋值,另一种是拷贝构造赋值 .
int a = 5 ;//拷贝赋值
int a (5) ;//直接赋值
如果我们没有自己重写拷贝构造函数,那么系统会默认的帮我们创建一个拷贝构造函数,拷贝构造函数的过程就是把内存完整的拷贝给新的对象,这样如果我们的类中含有指针的话就很容易出问题.
class HashPtrMem {
public:
HashPtrMem():d(new int(0)){}
HashPtrMem(HashPtrMem& other){
std::cout << "HashPtrMem copy function" << std::endl;
d = new int (*other.d);
}
~HashPtrMem() {
std::cout << "~HashPtrMem dealloc" << std::endl;
delete d;
}
int *d ;
};
我们在main函数中,这样调用:
int main(int argc, const char * argv[]) {
HashPtrM a ;
HashPtrM b = a;
}
HashPtrM b = a;会调用默认的拷贝构造函数(叫做浅拷贝,如果自己重写了拷贝构造函数,那么就叫做深层拷贝)
指针在我们内存中保存的是什么?? 是地址,这个时候拷贝的时候,其实也会把地址完整的拷贝到新的对象中。
在main函数即将结束的时候,a和b都会析构,但是因为指针d的地址其实是同一个,所以最后会报错误 :
malloc: *** error for object 0x1005010d0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
解决的方案之一就是:
我们采用深度拷贝的方式,自己重写拷贝构造函数:
HashPtrMem(HashPtrMem& other){
std::cout << "HashPtrMem copy function" << std::endl;
d = new int (*other.d);
}
什么时候调用拷贝构造函数呢?
1.一个对象需要通过另一个对象进行初始化,比如我们的方式:
HashPtrM b = a;
2.函数参数以传值的方式进行传入
void GetTempPtr(HashPtrM b){
}
想想这里,其实对基础的掌握还是要求挺高的.
老铁们,在我们拷贝构造函数的时候,实际上是把堆内存拷贝一份给新对象,然后在函数结束后,又释放掉新创建的内存块。
所以这是问题点,在拷贝构造函数的过程中,内存复制2份,析构也是double。
OK,现在我们这里有这样一个需求:
在一个函数内部,有一个临时变量,这给函数即将结束,我们需要把这个临时变量的内容赋值给类的同类型的变量.
这给时候我们如果使用拷贝构造函数的话,那么就是内存拷贝一份,然后临时变量析够.
第二部分,那么我们应该如何优化呢????
一个类可能有int类型,指针类型,类类型各种类型,除了完整的内存拷贝,我们还可以这样做:
int等基本类型,直接赋值,也只能这么做.
对于指针,我们直接采用下面代码的操作:
类似这样的操作 :
int *p = new int (5);
int *q = p;
p = NULL;
小知识:
The C++ language guarantees that delete p will do nothing if p is equal to NULL。
这样新对象的指针也指向了实体的数据,然后原指针变为了NULL,变为NULL后,析构的时候 delete 是不会触发任何事件的.
类中的类类型,其实可以递归这样的方式
这给就是我们传右值的核心思想,数据只有一份,从原有的值就这样”移动了”新值中.
注意:怎么对右值进行操作的行为,是由编程者自己决定的,也就是我们自己决定的.
说起来抽象,举个例子:
//
// main.cpp
// 0304
//
// Created by zhangkai on 2018/9/6.
// Copyright © 2018年 zhangkai. All rights reserved.
//
#include <iostream>
class person {
public:
person()
{
age = 0;
name = new char [10]{'a','b','c'};
}
//拷贝构造函数
person(const person& other)
{
printf("the address of other is:%p \n" , &other);
std::cout << "person copy" << std::endl;
this->age = other.age;
size_t len = strlen(other.name);
name = new char [len + 1];
memcpy(name, other.name, len);
name[len] = '\0';
}
//返回 person 主要是为了实现链式表达式
person operator=(person& other){
std::cout << "person operator == " << std::endl;
this->age = other.age;
size_t len = strlen(other.name);
name = new char [len + 1];
memcpy(name, other.name, len);
name[len] = '\0';
return *this;
}
//注意行为都是由我们自己来决定的.s
//移动构造函数
person(person&& other){
std::cout << "person move" << std::endl;
this->age = other.age;
this->name = other.name;
other.name = NULL;
}
void copy(person other){
printf("other address:%p \n" , &other);
}
~person()
{
std::cout << "person deconstructor" << std::endl;
if(name == NULL)
{
std::cout << "name is null" << std::endl;
}
delete[] name;
}
int age;
char *name;
};
//class Family {
//public:
// Family(person son): son_(son){
//
// }
//private:
// person son_;
//};
int main(int argc, const char * argv[]) {
// insert code here...
person son;//默认构造函数
son.age = 10;
//拷贝构造函数
printf("the address of son is:%p \n" , &son);
//中间会产生临时变量
//类似:person temp = son;然后会把son的内存完整的拷贝到临时变量temp中
//这个时候地址是不一样的
person son1 (son); // 和 person son1 = son 等价!
//中间也会产生临时变量,但是person(person&& other)程序会执行这里
//临时变量的内容都是son的,中间没有涉及到内存拷贝
person son2 (std::move(son));
return 0;
}
std::move只是把类型变为右值,当然类自己必须提供对右值对支持,编译器是自己不会处理右值的.
默认的复制构造函数由于必须添加 const,所以其实其行为已经定死,就只能是拷贝构造函数.
注意:
person(person&& other){
std::cout << "person move" << std::endl;
this->age = other.age;
this->name = other.name;
other.name = NULL;
}
这里other.name必须置为null,不然析够的时候会问题.