frankfan的胡思乱想

学海无涯,回头是岸

拷贝构造的应用

拷贝构造 自定义字符串类

拷贝构造

C++中创建对象的方式处理直接定义在栈中ClassType obj也可以定义在堆中ClassType *obj = new ClassType 而无论是哪种方式,都是通过默认构造函数(或者自定义构造函数)创建的。

此外,C++还支持通过另一个C++对象创建一个新的C++对象,通过的就是拷贝构造函数

#include <iostream>

class Node{
public:
  Node(const char *str = nullptr){
    value = strlen(str);
    buf = new char[value+1];
    memcpy(buf,str);
  }
  //拷贝构造函数,当通过另一个对象构造新对象时,便会调用该构造函数
  //这是拷贝构造的模板,其参数特征是1、被const修饰2、传递的是引用而非对象实体
  //被const修饰的形参既能接受const类型参数,又能接受非const类型参数,兼容性更广,传递进来的参数是引用而非对象,这样就避免了传递对象时产生了拷贝,从而又再一次调用拷贝构造函数,造成死递归调用
  Node(const Node &node){
	value = node.value;
    //直接将另一个对象的指针值赋值给了新对象的指针。这就是所谓浅拷贝
    //浅拷贝的问题在于共享了对象指针成员指向的堆,这对堆内存管理造成了麻烦
    //buf = node.buf;
    //推荐使用深拷贝
    buf = new char[strlen(node.buf)+1];
    memcpy(buf,node.buf);
  }
  ~Node(){
    if(buf != nullptr){
      delete[] buf;
      buf = nullptr;
    }
  }
private:
  int value;
  char *buf;
};

int main(){
  
  Node node("hello");
  //通过另一个对象创建一个新对象,如果自己实现了拷贝构造函数则调用自定义的拷贝构造函数,否则走默认的实现(浅拷贝构造)
  Node node2(node);
}

但凡需要使用另一个对象来创建新对象的行为都会使用拷贝构造,而除了“明显”的在语法中使用另一个对象创建新对象外,还有很多“隐式创建”,比如函数的参数与返回值,以及等号赋值等。

#include <iostream>
using namespace std;
class Node{
public:
    int value;
    //默认构造函数
    Node(){
        cout<<"Node()"<<endl;
    }
    //拷贝构造函数
    Node(const Node &node){
        cout<<"Node copy constructer"<<endl;
    }
    ~Node(){}
};
int main(){
    Node node;//调用Node()默认构造函数
    Node node2(node);//调用拷贝构造
    Node node3 = node2;//调用拷贝构造
    node3 = node;//这样并不会调用任何构造器,单纯的赋值而已,没有新的对象产生
    return 0;
}

除了普通对象的定义外,拷贝构造还发生在函数参数的传递以及返回值中

#include <iostream>
using namespace std;
class Node{
public:
    int value;
    //默认构造函数
    Node(){
        cout<<"Node()"<<endl;
    }
    //拷贝构造函数
    Node(const Node &node){
        cout<<"Node copy constructer"<<endl;
    }
    ~Node(){}
};

Node func(Node node){
    return node;
}

int main(){
    Node node;//调用Node()默认构造函数
	//调用这个函数,会产生2次拷贝构造函数的调用
    func(node);//第1次发生在函数参数传递时,产生了拷贝,第2次发生在函数返回时,发生了拷贝构造
    //因此在函数参数以及返回值的处理中,一定要对对象特别注意,通常会考虑传入对象的引用以及返回对象的引用
    return 0;
}

新的对象创建了,要么构造函数会调用要么拷贝构造函数会调用,反之,当这两个函数被调用了,则证明有新的对象产生。

创建新对象是很消耗系统资源的,因此C++编译器会对不必要的对象创建进行优化,其中就设计到一个概念“临时对象

#include <iostream>
using namespace std;
class Node{
public:
    int value;
    //默认构造函数
    Node(){
        cout<<"Node()"<<endl;
    }
    //拷贝构造函数
    Node(const Node &node){
        cout<<"Node copy constructer"<<endl;
    }
    ~Node(){}
};
int main(){
    Node node;//产生默认构造函数调用
    Node node2 = node;//新对象node2的产生是node对象拷贝而来,因此产生了拷贝构造函数的调用
    
    //此处并没有产生拷贝构造。新对象node3的产生并不是拷贝而来,而是直接调用默认构造函数创建而来。右侧生成的是一个临时对象,C++对临时对象通常会优化处理。
    Node node3 = Node();//
    return 0;
}
#include <iostream>
using namespace std;
class Node{
public:
    int value;
    //默认构造函数
    Node(int v = 0):value(v){
        cout<<"Node()"<<endl;
    }
    //拷贝构造函数
    Node(const Node &node){
        cout<<"Node copy constructer"<<endl;
    }
    ~Node(){}
};

Node func(Node node){
    return node;
}

int main(){
    //此处生成了一个临时对象,当这句源码结束后,该临时对象就会被释放
    cout<<Node(11).value<<endl;
    
    Node node;//默认构造
    Node node2 = func(node);//传参时产生一次拷贝构造,理论上函数返回node时应该产生一次拷贝构造,然后赋值给node2变量时应该再产生一次拷贝构造。但是如果这样的话函数返回值产生的临时对象很快就会被销毁,而node2却是通过拷贝这个临时对象而产生的新对象,这显然是不合理的。
    //因此C++编译器对此进行了优化,此处并不会拷贝构造产生新对象,而是直接将func函数返回的对象交给node2,这样就只拷贝构造了一次,没有多余的对象产生
    return 0;
}
#include <iostream>
using namespace std;
class Node{
public:
    int value;
    //默认构造函数
    Node(int v = 0):value(v){
        cout<<"Node()"<<endl;
    }
    //拷贝构造函数
    Node(const Node &node){
        cout<<"Node copy constructer"<<endl;
    }
    ~Node(){}
};

Node func(Node node){
    return node;
}

int main(){
  
    //此处只产生了1次默认构造以及1次拷贝构造
    Node node = func(Node());//通过一次默认构造产生了一个临时对象,当临时对象当函数参数时此时并不会产生拷贝构造,被编译器优化。此外func函数返回时产生了一次拷贝构造,生成了新对象。
    return 0;
}

总结:C++中,既可以通过普通构造产生新对象,又能通过拷贝构造产生新对象,不同的场合下会调用不同的函数产生新对象。对于函数的参数与返回值,我们应该时刻注意其形式,当可以使用引用类型时,应该优先使用引用。

自定义字符串类

从这个项目中需要掌握几个知识点。

1、函数返回对象的引用的应用场景(链式调用)

2、函数的不定参

3、底层数据容器的封装

class Node{

public:
	Node(const char *bf = nullptr) {
	
		if (bf != nullptr) {
		
            capcity = str_len + 1 + 10;
			str_len = strlen(bf);
			buf = new char[capcity];
			memcpy(buf, bf, str_len + 1);
		}
		else
		{
			str_len = 0;
			capcity = 10;
            buf = new char[capcity];
		}
	}

	Node &append(const Node &node) {
	
		int total_str_len = str_len + node.str_len;
		if (capcity >= total_str_len + 1) {//直接拼接
		
			strcat(buf, node.buf);
			str_len = total_str_len;
		}
		else//扩容后再拼接
		{
            capcity = total_str_len + 1 + 10;
			char *tmp = new char[capcity];
			memcpy(tmp, buf,strlen(buf)+1);
			strcat(tmp, node.buf);
			delete[] buf;
			buf = tmp;
			str_len = total_str_len;
		}

		return *this;//返回对象的引用,既能避免对象拷贝产生多余的中间对象又能实现函数的链式调用
	}

	char *fetchValue() {
	
		return buf;
	}

	~Node() {

		if (buf != nullptr) {
			delete[] buf;
			buf = nullptr;
		}
	}

	void showValue() {
		cout << buf << endl;
	}

private:
	int str_len;
	int capcity;
	char *buf;
};


int main()
{
	Node node("hello");
	cout << node.append(" world").fetchValue() << endl;//通过返回对象引用实现链式调用
}

posted on 2021-12-28 00:28  shadow_fan  阅读(40)  评论(0编辑  收藏  举报

导航