拷贝构造的应用
拷贝构造
自定义字符串类
拷贝构造
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) 编辑 收藏 举报