笔者最近在找工作时,无意间读到了一本名为《剑指offer》的书,粗略翻阅了一下,感觉这将会是一本能让我不再苦恼于笔试和面试“手搓代码”的书。故笔者写下该系列博客记录自己的学习历程,希望能和这本书的读者朋友们一起交流学习心得。
介绍:《剑指Offer:名企面试官精讲典型编程题(第2版)》剖析了80个典型的编程面试题,系统整理基础知识、代码质量、解题思路、优化效率和综合能力这5个面试要点。
编程题链接:牛客网在线编程_算法面试_面试必刷TOP101 (nowcoder.com)
本博客关键词:拷贝构造函数、深拷贝和浅拷贝
拷贝构造函数
构造函数的概念大家应该都不陌生,但是拷贝构造函数是什么呢?这里直接放一段代码:
class MyClass
{
private:
int age;
char *name;
public:
// 构造函数
MyClass(const char *n, int s_age)
{
name = new char[strlen(n) + 1];
strcpy(name, n);
age = s_age;
cout << "调用构造函数" << endl;
}
// 拷贝构造函数,也叫复制构造函数
MyClass(const MyClass &other)
{
// 深拷贝
// name = new char[strlen(other.name) + 1];
// strcpy(name, other.name);
age = other.age;
// 浅拷贝
name = other.name;
cout << "调用复制构造函数" << endl;
}
~MyClass()
{
cout << "析构函数被调用" << endl;
}
void getValue()
{
cout << "age: " << age << endl;
}
void setValue(int s_age)
{
age = s_age;
}
void getName()
{
cout << "name: " << name << endl;
}
void setName(const char *new_name)
{
strcpy(name, new_name);
}
};
其中MyClass(const MyClass &other)
就是拷贝构造函数,大家之后写拷贝构造函数的输入可以默认用const 类名 &other
来写
const
的使用,保证了拷贝构造函数不会修改传入的对象。这是符合逻辑的,因为拷贝构造的目的是创建一个新对象,而不是修改现有对象。&
,通过引用传参,这一步是必须的,如果使用按值传参,会导致永无休止的递归调用。如果不使用引用传参,代码在编译阶段就会出错的。
输入参数使用const
修饰应该比较容易理解,可是为什么一定要通过引用传参?为什么按值传参会有无限的递归调用呢?下面简单做个解释:
- 拷贝构造函数一般会在以下几个场景被调用:
- 用一个已存在的对象初始化另一个对象
- 对象作为值传递给函数时
- 在容器中存储对象时
- 函数按值返回对象时(可能不会调用)
- 由以上可知,在对象作为值传递给函数时,也会调用拷贝构造函数,如果拷贝构造函数本身的输入参数也是按值传递的(输入参数为按值传递会进行值拷贝),则在形参拷贝实参的过程中又会产生拷贝构造函数,如此就会一直递归调用拷贝构造函数,最终导致栈溢出。
- 而通过引用传参,就避免了值拷贝的过程,传入拷贝构造函数的是另一个实例的引用。通过引用传递,可以减少类中构造函数和析构函数调用的次数,提高程序的执行效率
深拷贝和浅拷贝
- 深拷贝和浅拷贝是两种对象复制的方式,它们在复制对象时处理内存和资源的方式不同,尤其是在涉及动态分配内存或指针的情况下。
- 深拷贝和浅拷贝在基本类型变量(如
int
,double
等)上的拷贝没有太多的差别,都是按值拷贝,会开辟新的空间。比如对象A的val值拷贝给对象B之后,B的val值会占用新的空间,修改B的val值不会影响A的val值。 - 对于指针类型变量,深浅拷贝区别就很大:
- 浅拷贝:在复制对象时,只复制对象的内存地址和基本属性(指针类型),而不复制它所指向的数据,意味着原对象和新对象将共享相同的内存区域或资源。
- 深拷贝:在复制对象时,不仅复制对象的内存地址和基本属性(指针类型),还复制所有动态分配的内存或资源。也就是说,它会为新对象分配一个新的内存区域,并将原对象的数据复制到这个新区域中。
- 以拷贝构造函数为例:
// 拷贝构造函数,也叫复制构造函数
MyClass(const MyClass &other)
{
// 深拷贝
name = new char[strlen(other.name) + 1];
strcpy(name, other.name);
// 浅拷贝
// name = other.name;
age = other.age;
cout << "调用复制构造函数" << endl;
}
测试用例
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
class MyClass
{
private:
int age;
char *name;
public:
// 构造函数
MyClass(const char *n, int s_age)
{
name = new char[strlen(n) + 1];
strcpy(name, n);
age = s_age;
cout << "调用构造函数" << endl;
}
// 拷贝构造函数,也叫复制构造函数
MyClass(const MyClass &other)
{
// 深拷贝
// name = new char[strlen(other.name) + 1];
// strcpy(name, other.name);
age = other.age;
// 浅拷贝
name = other.name;
cout << "调用复制构造函数" << endl;
}
~MyClass()
{
cout << "析构函数被调用" << endl;
}
void getValue()
{
cout << "age: " << age << endl;
}
void setValue(int s_age)
{
age = s_age;
}
void getName()
{
cout << "name: " << name << endl;
}
void setName(const char *new_name)
{
strcpy(name, new_name);
}
};
void show(MyClass &obj1, MyClass &obj2)
{
obj1.getName();
obj1.getValue();
obj2.getName();
obj2.getValue();
}
MyClass createClass()
{
cout << "createClass:" << endl;
MyClass obj("王五", 22);
return obj; // 这里在GCC中并不会调用拷贝构造函数
}
int main()
{
vector<MyClass> vec;
MyClass obj1("张三", 23);
MyClass obj2 = obj1; // 这里会调用拷贝构造函数——用一个已存在的对象初始化另一个对象
cout << "vector:" << endl;
vec.push_back(obj1); // 这里会调用拷贝构造函数——在容器中存储对象时
MyClass obj = createClass();
obj.getName();
show(obj1, obj2);
obj1.setName("李四");
obj1.setValue(18);
show(obj1, obj2);
return 0;
}