C++vector常用接口函数及模拟实现

1.vector的介绍和使用 

vector的文档介绍

  • vector 是表示可变大小数组的序列容器。
  • 就像数组一样,vector 也采用连续存储空间来存储元素。这意味着可以采用下标对 vector 的元素进行访问,效率与数组一样高。但是 vector 的大小是可以动态改变的,这种变化由容器自动处理。
  • 本质上,vector 使用动态分配的数组来存储它的元素。当新元素插入时,如果现有的数组空间不足,vector 需要重新分配更大的数组并将所有元素移到新数组中。这是一个相对代价高的操作,因为重新分配和元素移动需要时间。不过,vector 并不会每次插入新元素时都重新分配大小。
  • vector 的空间分配策略是:会预先分配一些额外的空间以适应可能的增长。这意味着 vector 的实际存储空间往往比当前需要的空间要大。不同的库在空间使用和重新分配之间有不同的权衡策略,但无论如何,重新分配的增长通常是按指数(对数)间隔进行的,以保证在末尾插入元素时能以摊销常数时间完成。
  • 因此,vector 占用更多的存储空间是为了能够有效地管理存储空间并动态增长。
  • 与其它动态序列容器(如 dequelistforward_list)相比,vector 在访问元素时更加高效,在末尾添加和删除元素也相对高效。对于其它位置的删除和插入操作,效率较低。相比于 listforward_listvector 具有统一的迭代器和引用,这使得它在很多情况下更方便使用。

 成员类型

 成员函数

非成员函数重载 

 

 模版特化

vector的使用

vector的定义

(constructor)构造函数声明接口说明
vector()(重点)无参构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造

vector iterator 的使用 

vector空间增长问题

容量空间接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变vector的size
reserve (重点)改变vector的capacity

下面示例展示了 reserveresize 的区别,以及 vector 在不同环境下的容量增长行为。

#include <iostream>
#include <vector>

int main() {
	std::vector<int> vec;

	// 初始容量
	std::cout << "Initial capacity: " << vec.capacity() << std::endl;

	// 添加元素,观察容量变化
	for (int i = 1; i <= 15; ++i) {
		vec.push_back(i);
		std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
	}

	// 使用 reserve 分配空间
	vec.reserve(30);
	std::cout << "After reserve(30), Capacity: " << vec.capacity() << std::endl;
	for (auto e : vec)
	{
		std::cout << e << " ";
	}

	// 使用 resize 改变大小
	vec.resize(10);
	std::cout << "After resize(10), Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
	for (auto e : vec)
	{
		std::cout << e << " ";
	}
	//std::cout << vec[10];//报错,越界访问

	return 0;
}

 输出:

Initial capacity: 0
Size: 1, Capacity: 1
Size: 2, Capacity: 2
Size: 3, Capacity: 3
Size: 4, Capacity: 4
Size: 5, Capacity: 6
Size: 6, Capacity: 6
Size: 7, Capacity: 9
Size: 8, Capacity: 9
Size: 9, Capacity: 9
Size: 10, Capacity: 13
Size: 11, Capacity: 13
Size: 12, Capacity: 13
Size: 13, Capacity: 13
Size: 14, Capacity: 19
Size: 15, Capacity: 19
After reserve(30), Capacity: 30
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 After resize(10), Size: 10, Capacity: 30
1 2 3 4 5 6 7 8 9 10

vector增删查改

vector增删查改接口说明
push_back(重点)尾插
pop_back (重点)尾删
find查找。(注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[] (重点)像数组一样访问

迭代器失效问题
 迭代器失效(Iterator Invalidation)指的是迭代器在某些容器操作后变得不再指向有效的元素,继续使用这些失效的迭代器可能会导致未定义行为或程序崩溃。

 对于vector可能会导致其迭代器失效的操作有:

  • 底层空间改变的操作

    • resize:改变 vector 的大小。如果新大小大于当前大小,可能会重新分配内存,所有迭代器都会失效。如果新大小小于当前大小,删除部分的迭代器会失效。
    • reserve:增加 vector 的容量,如果导致重新分配内存,所有迭代器都会失效。
    • insert:插入元素时,如果 vector 的容量不足而导致重新分配内存,所有迭代器都会失效。如果没有重新分配内存,插入位置后的迭代器会失效。
    • assign:替换 vector 中的所有元素,所有迭代器都会失效。
    • push_back:在 vector 的末尾添加元素,如果导致重新分配内存,所有迭代器都会失效。
  • 指定位置元素的删除操作

    • erase:删除 vector 中的一个或多个元素,删除位置及其之后的所有迭代器会失效。
  • 删除单个元素
    iterator erase(iterator pos);
  • 删除范围内的元素
    iterator erase(iterator first, iterator last);

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	vector<int>::iterator it = v.begin() + 2;
	//v.erase(it);//这样写报错
	it = v.erase(it);//正确写法应该更新it
	cout << *it << endl;

	return 0;
}
int main() {
	std::vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8 };

	// 删除 [begin + 2, begin + 5) 范围的元素,即 3, 4, 5
	auto first = vec.begin() + 2;
	auto last = vec.begin() + 5;

	std::cout << "Before erase: ";
	for (const auto& val : vec) {
		std::cout << val << " ";
	}
	std::cout << std::endl;

	auto it = vec.erase(first, last); // 删除范围内的元素,返回指向第一个未删除元素的迭代器

	//删除后的区间中的迭代器不可再访问了
	
	// 输出删除后 vector 的当前状态
	std::cout << "After erase: ";
	for (const auto& val : vec) {
		std::cout << val << " ";
	}
	std::cout << std::endl;

	// it 现在指向原来的第六个元素,现为第三个元素
	if (it != vec.end()) {
		std::cout << "Iterator after erase points to: " << *it << std::endl; // 输出: 6
	}													
	else {
		std::cout << "Iterator after erase points to end of vector" << std::endl;
	}

	return 0;
}

由此可见,导致迭代器失效的大部分原因是因为扩容,原来的空间被销毁;也有可能是删除导致迭代器失效了。

 注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

这里不做演示。

2.vector的模拟实现

#pragma once
#include <assert.h>
#include <algorithm>
#include <list>

using namespace std;

namespace bit
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//迭代器封装
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		// const 迭代器封装
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}
		//n个参数初始化
		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0;i < n; ++i)
			{
				push_back(val);
			}
		}

		//n个参数初始化(为了支持vector<int>调用)
		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0;i < n; ++i)
			{
				push_back(val);
			}
		}
		//(通过初始化列表来方便地初始化)
		vector(initializer_list<T> il) // initializer_list支持迭代器
		{
			//初始化列表大小为0时调用默认构造
			reserve(il.size());
			for (auto e : il)
			{
				push_back(e);
			}
		}
		//类模板的成员函数
		// 函数模板 -- 目的是支持任意容器迭代器区间来初始化
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		//默认构造(没有强制生成或没有显式给出的话:拷贝构造中的传值调用会调用默认构造)
		vector() = default;
			
		//拷贝构造
		vector(const vector<T>& v)
		{
			//只需要一次扩容
			reserve(v.capacity());
			for (auto e : v)
			{
				push_back(e);
			}
		}
		//交换资源
		void swap(vector& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		//重载赋值运算符=
		vector& operator=(vector<T> v)
		{

			if (this != &v)
			{
				//只需要一次扩容
				reserve(v.capacity());
				for (auto e : v)
				{
					push_back(e);
				}
			}
			return *this;
		}
		//写成引用调用赋值也可以
		vector& operator= (const vector<T>& v)
		{
			if (this != &v)
			{
				//只需要一次扩容
				reserve(v.capacity());
				for (auto e : v)
				{
					push_back(e);
				}
			}
			return *this;
		}

		//析构函数
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}
		//扩容(不使用空间配置器(提高效率))
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				// size_t oldsize = size();
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}
				/*
				这里如果不强行依赖顺序来更新值,也可以提前保留执之前的size():
				_start = tmp;
				_finish = tmp + oldsize;
				_end_of_storage = _start + n;
				*/
				_finish = tmp + size();
				_start = tmp;
				_end_of_storage = _start + n;
			}

		}
		//计算容量
		size_t capacity()const
		{
			return _end_of_storage - _start;
		}
		//计算数据个数
		size_t size()const
		{
			return _finish - _start;
		}
		//重载[]运算符
		T& operator[](size_t i)
		{
			assert(i < size());

			return _start[i];
		}

		//尾插
		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			++_finish;
		}
		//尾删
		void pop_back()
		{
			assert(size() > 0);
			--_finish;
		}
		//指定位置插入
		iterator insert(iterator pos, const T& x)//pos的实参begin()具有常性(begin()返回的是临时对象_start),不能被引用,所以非要访问pos位置,会更新pos的值并以返回值方式返回
			//如果扩容时不对pos更新,会使得迭代器失效, 
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			//扩容
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;//防止迭代器失效

				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);

				pos = _start + len;
			}
			//往后挪动数据:不同于string的insert,不会出现死循环
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *(end);
				--end;
			}
			*pos = x;
			return pos;
		}
		//指定位置删除 
		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			//往前挪动数据:
			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *(it);
				++it;
			}
			--_finish;
		}

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

范围for 测试 

//使用范围for遍历(需要实现迭代器接口函数)
void test_vector1()
{
	bit::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	//常见的遍历方法
	for (size_t i = 0;i < v1.size();++i)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
	//范围for遍历
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	//iterator 是定义在类域中的,需要指定在类域中
	bit::vector<int>::const_iterator it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
		++it;
	}
}

输出:

1 2 3 4
1 2 3 4
1 2 3 4

 insert,erase应用 测试

//insert,erase应用
void test_vector2()
{
	bit::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	//v1.push_back(5);

	//find适用任何支持迭代器的容器模板,不定义在类中
	bit::vector<int>::iterator it = find(v1.begin(), v1.end(), 2);//找到值为2并返回对应位置的迭代器
	if (it != v1.end())
	{
		it = v1.insert(it, 100);//扩容后,it需要更新,没有返回值it还是扩容前的it
		//参数传值调用,不会影响到实参it,扩容会造成it仍是原来的位置
		cout << *it << endl;
	}

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	//删除第三个位置的数据
	v1.erase(v1.begin() + 2);

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

 输出:

100
1 100 2 3
1 100 3

VS上C++标准库的拷贝模式 测试

//测试VS2022环境下C++标准库中的拷贝模式
void test_vector3()
{
	//引用计数写时拷贝(Copy - on - Write,COW)是一种优化技术,它可以延迟数据的复制,直到需要修改数据时才进行复制,以减少不必要的内存消耗。
	//编译器默认给出vector<T>赋值拷贝的是浅拷贝(值拷贝)
	
	//对于数值类型的值拷贝,删除源对象不会对被赋值的对象产生影响,但是对于地址赋值有很大影响
	
	std::vector<string> v7;
	v7.push_back("我");
	v7.push_back("爱");
	v7.push_back("C");
	v7.push_back("h");
	v7.push_back("i");
	v7.push_back("n");
	v7.push_back("a");

	std::vector<string> v8 = v7;
	std::vector<string> v9 = v7;
	printf("%p\n", v7[0]);
	printf("%p\n", v8[0]);
	printf("%p\n", v9[0]);
	//对于 std::vector<string>,C++ 标准库中的实现通常会采用深拷贝。

	cout << endl;

	//对于 std::string,C++ 标准库中的实现通常会采用深拷贝。
	std::string s1 = "Do you best";
	std::string s2(s1);
	std::string s3 = s1;

	//输出string类的字符串首地址
	printf("%p\n", s1.data());
	printf("%p\n", &s1[0]);//注意:这里的写法与上面作用一样,[]的优先级高于&
	printf("%p\n", s2.data());
	printf("%p\n", s3.data());
}

输出:

000000000014FC10
000000000014FC60
000000000014FCB0

000000000014F750
000000000014F750
000000000014F790
000000000014F7D0

我们通过结果可以晓得了VS采用的是深拷贝模式

 使用迭代器区间构造vecotr<T> 测试

//使用不同模板迭代器区间构造vector
void test_vector4()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);

	vector<int> v2(v1.begin(), v1.end() - 2);

	std::string s1("Believe yourself");
	vector<int> v3(s1.begin(), s1.end());//转换成ACAII码

	list<int> lt;
	lt.push_back(10);
	lt.push_back(20);
	lt.push_back(30);
	vector<int> v4(lt.begin(), lt.end());

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto e : v3)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto e : v4)
	{
		cout << e << " ";
	}
}

输出:

1 2 3 4 5
1 2 3
66 101 108 105 101 118 101 32 121 111 117 114 115 101 108 102
10 20 30

根据适合的类型调用适合的构造函数 测试

//为vector的构造岔路口指明方向
void test_vector5()
{
	// VS2022这里做了优化,会匹配正确的构造函数,调用 vector(size_t n, const T& val = T()) 构造v1
	// VS2019则会调用迭代器区间初始化,对于vector<int> 若二者同时存在,仍会调用迭代器模板构造函数,因为是接收任意类型的参数
	vector<string>v1(10);
	vector<string>v2(10, "xxxx");//隐式类型转换调用默认构造后,初始化v2
	//vector<int> v3(10, 100);//调用迭代器区间构造,但int不能解引用,报错

	//提供明确的构造函数调用,或者通过类型强制转换来明确指定要调用的构造函数
	vector<int> v3(static_cast<size_t>(10), 100);//可以写成这样,调用vector(size_t n, const T& val = T())初始化v3
	vector<int> v6(static_cast<int>(10), 100);
	vector<int> v4(10, 100);

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto e : v3)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto e : v4)
	{
		cout << e << " ";
	}
}

输出:


xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
100 100 100 100 100 100 100 100 100 100
100 100 100 100 100 100 100 100 100 100

 内置类型也可以构造 测试

//内置类型也可以调用构造:为了兼容模板
void test_vector6()
{
	int i = 0;
	int j(1);
	int k(i);
	int l = int();
	int h(int(1));

	cout << i << " ";
	cout << j << " ";
	cout << k << " ";
	cout << l << " ";
	cout << h << " ";
}

输出:

0 1 0 0 1

以自定义类模板构造vector 测试

 这里我们自定义A类,完成vector的各种构造

class A
{
public:
	A() // 默认构造函数
		: _a1(0), _a2(0)
	{}
	A(int a1)//单参数隐式类型转换
		:_a1(a1)
	{}
	A(int a1, int a2)//多参数隐式类型转换
		:_a1(a1), _a2(a2)
	{}
	A(const A& a)
	{
		_a1 = a._a1;
		_a2 = a._a2;
	}

	friend std::ostream& operator<<(std::ostream& os, const A& a);

private:
	int _a1;
	int _a2;
};
// 在类外定义友元函数operator<<
std::ostream& operator<<(std::ostream& os, const A& a)
{
	os << "(" << a._a1 << "," << a._a2 << ")";
	return os;
}
//单参数和多参数对象隐式类型转换
void test_vector7()
{
	A aa1(1);// 显式调用单参数构造函数
	A aa2 = 1;// 使用初始化列表进行 单参数 隐式转换 -> 构造对象 -> 拷贝构造 (编译器优化成构造)
	A aa3 = { 1 };// 使用初始化列表进行单参数隐式转换 -> 构造对象 -> 拷贝构造 (编译器优化成构造)
	A aa4{ 1 };//C++11:赋值符可以省略 (编译器优化成构造)

	// 显式调用 多参数 隐式类型转换 构造对象
	A aa5(2, 2);// 显式调用多参数构造函数
	A aa6 = { 2,2 };// 使用初始化列表进行 多参数 隐式转换 -> 构造对象 -> 拷贝构造 (编译器优化成构造)
	A aa7{ 2,2 };// 使用初始化列表进行 多参数 隐式转换 -> 构造对象 -> 拷贝构造  (编译器优化成构造)

	const A& aa8 = { 1,1 };//引用的是中间产生的临时对象
	
	//注意:上面的结果是等价的
	// 多参数隐式类型转换只能通过初始化列表来初始化

	
	//这里的隐式类型转换不同于上面,这里参数个数不固定
	bit::vector<int> v1({ 1,2,3,4,5,6 });//隐式类型转换->构造临时对象 ->构造
	bit::vector<int> v2 = { 1,2,3,4,5,6 };//隐式类型转换->构造临时对象 ->赋值 (编译器优化成构造)
	//可以看成initializer_list拷贝构造v2(优化成构造)
	bit::vector<int> v3{ 1,2,3,4,5,6 };//同上

	//同一类型传给initializer_list
	bit::vector<A> v4 = {};//默认构造 -> 这样会多走一步A的默认构造(避免这样初始化空的对象)
	bit::vector<A> v5{};//默认构造 -> 同上
	bit::vector<A> v6 = { 1,A(1),A(2,2),A{1},A{2,2} ,{1},{2,2} };//注意:当T是A,new T[n]时,A需要有默认构造
	//构造完初始化列表根据初始化列表的迭代器来进一步对v6拷贝构造
	/*
	auto v1 = std::vector<int>({ 1,2,3,4,5,6 });
	auto v2 = std::vector<int>{ 1,2,3,4,5,6 };
	auto v3 = std::vector<int>{ 1,2,3,4,5,6 };
	但是这样多出多出一步赋值操作,有损性能,所以尽量避免这样写
	*/

	cout << "v1 :";
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "v2 :";
	for (const auto& e : v2)
	{
		std::cout << e << " ";
	}
	cout << endl;
	cout << "v3 :";
	for (const auto& e : v3)
	{
		std::cout << e << " ";
	}
	cout << endl;
	cout << "v4 :";
	for (const auto& e : v4)
	{
		std::cout << e << " ";
	}
	cout << endl;
	cout << "v5 :";
	for (const auto& e : v5)
	{
		std::cout << e << " ";
	}
	cout << endl;
	cout << "v6 :";
	for (const auto& e : v6)
	{
		std::cout << e << " ";
	}
}

输出:

v1 :1 2 3 4 5 6
v2 :1 2 3 4 5 6
v3 :1 2 3 4 5 6
v4 :
v5 :
v6 :(1,7667810) (1,6619254) (2,2) (1,6619256) (2,2) (1,0) (2,2)

这里要注意的是:初始化列表为空虽然会先走A类的默认构造,但并不会下一步走vector的初始化列表构造,而是它的默认构造,因此没有元素进入到vector<A>中

 对于vector<string>浅拷贝的调整

//对于vector<string>的调整
void test_vector8()
{
	//错误演示:
	vector<string> v1;
	v1.push_back("11111111111");
	v1.push_back("11111111111");
	v1.push_back("11111111111");
	v1.push_back("11111111111");
	v1.push_back("11111111111");//扩容后,原来的空间被delete掉,因为是值拷贝所以两个对象指向同一空间,销毁一个空间会影响另一个对象
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

运行后报错:

memcpy 函数的工作原理

memcpy 是 C 标准库函数,用于将一段内存中的内容复制到另一段内存中。其原型如下:

void *memcpy(void *dest, const void *src, size_t n);
  • dest:目标内存地址
  • src:源内存地址
  • n:要复制的字节数

memcpy 按照二进制格式逐字节进行复制,不关心数据的具体类型。

使用 memcpy 的优缺点

优点:
  1. 高效性:由于 memcpy 直接在内存级别进行复制,因此通常比手动逐元素复制更快。
  2. 适用于内置类型:对内置类型(如 intfloatchar 等)进行复制时,memcpy 能高效且正确地工作。
缺点:
  1. 浅拷贝memcpy 执行的是浅拷贝,即只复制指针值或内存地址,而不复制指针指向的内容。这在处理涉及资源管理的自定义类型时会导致问题。
  2. 不处理资源管理:对于包含动态内存分配、文件句柄、互斥锁等资源的自定义类型,memcpy 无法正确处理这些资源的生命周期,可能导致内存泄漏、双重释放或资源竞争等问题。

浅拷贝示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    int id;
    char *name;
} Person;

int main() {
    Person p1;
    p1.id = 1;
    p1.name = (char *)malloc(20 * sizeof(char));
    strcpy(p1.name, "John Doe");

    Person p2;
    memcpy(&p2, &p1, sizeof(Person));

    // 修改p1的name,p2的name也会改变
    strcpy(p1.name, "Jane Doe");

    printf("p1: %d, %s\n", p1.id, p1.name);
    printf("p2: %d, %s\n", p2.id, p2.name);

    // 释放内存
    free(p1.name);
    free(p2.name);  // 错误:p1和p2的name指向同一块内存,双重释放

    return 0;
}

在这个例子中,由于 memcpy 执行的是浅拷贝,p1p2name 成员指向同一块内存。这会导致在修改 p1.namep2.name 也被修改,且在释放 p1.namep2.name 时会发生双重释放错误。

解决方法

对于自定义类型,尤其是那些涉及资源管理的类型,应该使用深拷贝。可以通过自定义复制函数实现深拷贝,例如:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int id;
    char *name;
} Person;

void deepCopyPerson(Person *dest, Person *src) {
    dest->id = src->id;
    dest->name = (char *)malloc(strlen(src->name) + 1);
    strcpy(dest->name, src->name);
}

int main() {
    Person p1;
    p1.id = 1;
    p1.name = (char *)malloc(20 * sizeof(char));
    strcpy(p1.name, "John Doe");

    Person p2;
    deepCopyPerson(&p2, &p1);

    // 修改p1的name,p2的name不会改变
    strcpy(p1.name, "Jane Doe");

    printf("p1: %d, %s\n", p1.id, p1.name);
    printf("p2: %d, %s\n", p2.id, p2.name);

    // 释放内存
    free(p1.name);
    free(p2.name);

    return 0;
}

这个例子中,deepCopyPerson 函数实现了深拷贝,确保每个 Person 实例有自己独立的 name 内存空间。

总结

memcpy 是一个高效的内存复制工具,对于内置类型的数据能很好地工作。但在处理涉及资源管理的自定义类型时,必须小心浅拷贝带来的问题,建议使用深拷贝以确保资源的正确管理和释放。

今天的分享到此为止,我们下期再见~

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值