C++ STL之string容器的模拟实现

news2024/10/5 12:07:33

目录

一、经典的string类问题

1.出现的问题

2.浅拷贝

3.深拷贝 

二、string类的模拟实现

1.传统版的string类

2.现代版的string类(采用移动语义)

3.相关习题*

习题一

习题二

4.写时拷贝

5.完整版string类的模拟实现[注意重定义]

MyString.h

MyString.c

test.c


一、经典的string类问题

1.出现的问题

模拟实现string容器的 构造、拷贝构造、赋值运算符重载以及析构函数 

出错代码如下: 

// 类的定义
namespace imitate
{
	class string
	{
	public: 
		//string()            //无参构造函数
		//	:_str(nullptr)
		//{}

		//string(char* str)  //有参构造函数
		//	:_str(str)
		//{}

		string()
			:_str(new char[1])
		{
			_str[0] = '\0';
		}
		string(char* str)   //构造函数在堆上开辟一段strlen+1的空间+1是c_str
			:_str(new char[strlen(str)+1])
		{
			strcpy(_str, str); //strcpy会拷贝\0过去
		}

		//string(char* str="")   //构造函数在堆上开辟一段strlen+1的空间+1是c_str
		//	:_str(new char[strlen(str) + 1])
		//{
		//	strcpy(_str, str); //strcpy会拷贝\0过去
		//}
		size_t size() const
		{
			return strlen(_str);
		}
		bool empty()
		{
			return _str == nullptr;
		}
		char& operator[](size_t i)  //用引用返回不仅可以读字符,还可以修改字符
		{
			return _str[i];
		}

		~string()          //析构函数
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}
				const char* c_str() //返回C的格式字符串
		{
			return _str;
		}

	private:
		char* _str;
	};
}
// 测试
using namespace imitate;
void TestString1()
{
	string s1("hello");
	string s2;
	for (size_t i = 0; i < s1.size(); i++)
	{
		s1[i] += 1;
		std::cout << s1[i] << " ";
	}
	std::cout << std::endl;
	for (size_t i = 0; i < s2.size(); i++)
	{
		s2[i] += 1;
		std::cout << s2[i] << " ";
	}
	std::cout << std::endl;
}
void TestString2()
{
	string s1("hello");
    
    //采用默认的构造函数
	string s2(s1); // 出错点 1
	std::cout << s1.c_str() << std::endl;
	std::cout << s2.c_str() << std::endl;

	string s3("world");
    
    //采用
	s1 = s3; // 出错点 2
	std::cout << s1.c_str() << std::endl;
	std::cout << s3.c_str() << std::endl;
}
int main()
{
	TestString1();
	TestString2();
	return 0;
}

出错点1:

string s2(s1); // 出错点 1

采用默认的拷贝构造函数,导致s2._str拷贝了s1._str所指向的地址,导致s1,s2的_str都指向同一块空间。测试函数2结束后,s2、s1指向相同的地址会被释放两次,会导致程序崩溃。

出错点2:

s1 = s3; // 出错点 2

采用默认赋值运算符重载,s3._str被赋予了s1._str所指向的地址,导致s1,s2的_str都指向同一块空间。测试函数2结束后,s3、s1指向相同的地址会被释放两次,会导致程序崩溃。

总结:采用 默认拷贝构造函数默认赋值运算符重载 会导致一块空间会被多个类对象指向,导致多次释放同一空间,致使程序崩溃。这种拷贝方式,称为浅拷贝。

2.浅拷贝

浅拷贝指创建了一个新的对象,其中包含了原始对象的所有属性和值,但是对于原始对象中引用的其他对象,浅拷贝只是复制了其引用,而不是复制其实际值

在浅拷贝中,新对象和原始对象之间共享同一个内存地址,所以如果修改其中一个对象的属性,则另一个对象的属性也会随之改变,因为它们引用同一个对象。浅拷贝通常比深拷贝更快,因为它只需要复制对象的引用,而不需要递归地复制对象的所有子对象。

3.深拷贝 

深拷贝指创建一个新对象,该对象是原始对象的完全副本,包括原始对象的所有属性和嵌套对象的属性深拷贝会递归地复制所有嵌套的对象,而不仅仅是复制其引用。因此,原始对象和副本对象之间没有共享任何内存地址。

深拷贝可以防止副本对象的修改影响原始对象,因为它们是完全独立的。但是,深拷贝可能比浅拷贝更耗时,因为它需要递归地复制所有嵌套的对象。

如果类中有指向堆内存的指针或者使用了动态内存分配函数(如new、malloc等),则需要手动编写拷贝构造函数或赋值操作符来实现深拷贝。

二、string类的模拟实现

1.传统版的string类

C++ 的一个常见面试题是让你实现一个 String 类,限于时间,不可能要求具备 std::string 的功能,但至少要求能正确管理资源。
具体来说:能像 int 类型那样定义变量,并且支持赋值、复制。能用作函数的参数类型及返回类型。能用作标准库容器的元素类型,即 vector / list / deque 的 value_type。(用作 std::map 的 key_type 是更进一步的要求,本文从略)。换言之,你的 String 能让以下代码编译运行通过,并且没有内存方面的错误。
 

添加了自定义的 拷贝的构造函数 赋值运算符重载 

代码如下:

// 传统版的string类的模拟————类的定义
// ...
namespace imitate
{
	class string
	{
	public:
		string()
			:_str(new char[1])
		{
			_str[0] = '\0';
		}
		string(const char* str)
			:_str(new char[strlen(str) + 1])
		{
			strcpy(_str, str);
		}
		string(const string& s)
			:_str(new char[s.size() + 1])
		{
			strcpy(_str, s._str);
		}
		size_t size() const
		{
			return strlen(_str);
		}
		char& operator[](size_t i)
		{
			return _str[i];
		}
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}
		const char* c_str()
		{
			return _str;
		}
		// const 是因为对 str 不需要修改,安全性更高
        // 参数 & 是因为不需要传值拷贝、效率高
        // 返回值 & 是为了连续赋值(效率高)
		string& operator=(const string& str)
		{
			// 检查是否自己给自己赋值
			if (this != &str)
			{
                // 因为str为const修饰,所以要调用size()函数的话应给函数加上const修饰
				char* tmp = new char[str.size() + 1];
				strcpy(tmp, str._str);
				delete[] _str;
				_str = tmp;
			}
			return *this;
		}

	private:
		char* _str;
	};
}
// 测试代码
// ...
using namespace imitate;
void TestString()
{
	string s1;
	string s2("hello");
	for (size_t i = 0; i < s2.size(); i++)
	{
		std::cout << s2[i] << " ";
	}
	std::cout << std::endl;
	s1 = s2;
	for (size_t i = 0; i < s1.size(); i++)
	{
		std::cout << s1[i] << " ";
	}
}
int main()
{
    TestString();
    return 0;
}

2.现代版的string类(采用移动语义)

现代版的string类采用swap函数的原因:实现C++11中的移动语义

C++的移动语义

可以在不进行任何拷贝操作的情况下,将一个对象的所有权从一个对象转移到另一个对象。这个过程使用右值引用(rvalue references)实现。

移动语义的主要目的是优化C++代码的性能,减少不必要的内存分配拷贝操作,提高程序的效率。当一个对象被移动而不是拷贝时,可以避免拷贝构造函数和析构函数的调用,从而提高程序的性能。

代码如下:

// 现代版的string类模拟————类的定义
// ...
namespace imitate
{
	class string
	{
	public:
		string()
			:_str(new char[1])
		{
			_str[0] = '\0';
		}
		string(const char* str)
			:_str(new char[strlen(str) + 1])
		{
			strcpy(_str, str);
		}
		string(const string& s) 
		// 使用swap函数,进行直接把所有权交予另一个对象
			:_str(nullptr)
		{
			string tmp(s._str);
			std::swap(_str, tmp._str);
		}
		size_t size() const
		{
			return strlen(_str);
		}
		char& operator[](size_t i)
		{
			return _str[i];
		}
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}
		const char* c_str()
		{
			return _str;
		}
		string& operator=(const string& str)
		{
			if (this != &str)
			{
				//tmp 会在函数结束时自动调用其析构函数进行资源的释放
				string tmp(str._str);
				std::swap(_str, tmp._str);
			}
			return *this;
		}
	private:
		char* _str;
	};
}
// 测试代码
// ...
void TestString1()
{
	string s1;
	string s2("hello");
	string s3(s2);
	s1 = s3;
}
int main()
{
	TestString1();
	return 0;
}

3.相关习题*

习题一

习题二

注意:用一个对象初始化另一个对象时,例如 MyClass obj1; MyClass obj2 = obj1;

采用的是 拷贝构造函数 而非使用 重载运算符

4.写时拷贝

写时拷贝(Copy-On-Write,简称COW)是一种优化技术,它可以避免在数据复制时不必要的内存开销。写时拷贝的实现方式是,在数据需要被修改时,才会复制数据,而在数据未被修改时,共享同一份数据。

写时拷贝通常是通过使用智能指针实现的。智能指针可以跟踪指向的对象的引用计数,并在需要时进行复制。具体来说,当一个智能指针被拷贝时,它的引用计数会增加,而指向的对象不会被复制。当一个指向对象的智能指针需要修改对象时,它会先检查引用计数是否为1,如果是,说明这个对象没有被其他智能指针共享,可以直接修改,否则,它会先复制一份对象,并把引用计数减1,然后对复制后的对象进行修改。

5.完整版string类的模拟实现[注意重定义]

注意:

1.size_t imitate::string::npos = -1;不能放到头文件中,避免重定义问题。

2.将函数标记为"inline"可以消除重定义错误。

3.在头文件中定义了这些函数需要将它们标记为 inline,否则每个包含该头文件的源文件都会生成该函数的定义,从而导致链接错误。

MyString.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>

namespace imitate
{
	class string
	{
	public:
		typedef char* iterator;
		static size_t npos;       //insert用的位置
	public:
		string(const char* str = " ")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		string(const string& s)
			: _size(0)
			, _capacity(0)
			, _str(nullptr)
		{
			string tmp(s._str);
			this->swap(tmp);
		}
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_size = 0;
				_capacity = 0;
				_str = nullptr;
			}
		}


		string& operator=(string& s);


		void swap(string& s);


		void push_back(char c);
		

		void append(const char* str);
		void append(const string& s);


		string& operator+=(char c);
		string& operator+=(const char* str);
		string& operator+=(const string& s);


		string& insert(size_t pos, char c);
		string& insert(size_t pos, const char* str);
		string& insert(size_t pos, const string& s);

		string& erase(size_t pos, size_t len = npos);

		size_t find(char c, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		size_t find(const string& s, size_t pos = 0);

		void resize(size_t newsize, char c = '\0');

		iterator begin()                //iterator迭代器的原理
		{
			return _str;
		}
		iterator end()
		{
			return (_str + _size);
		}

		const char* c_str()
		{
			return _str;
		}

		char& operator[](size_t i) const
		{
			return _str[i];
		}

		bool operator<(const string& s)
		{
			return strcmp(_str, s._str);
		}
		bool operator<=(const string& s)
		{
			return (strcmp(_str, s._str) == -1) || (strcmp(_str, s._str) == 0);
		}
		bool operator>(const string& s)
		{
			return strcmp(_str, s._str);
		}
		bool operator>=(const string& s)
		{
			return (strcmp(_str, s._str) == 1) || (strcmp(_str, s._str) == 0);
		}
		bool operator=(const string& s)
		{
			return strcmp(_str, s._str) == 0;
		}
		bool operator!=(const string& s)
		{
			return strcmp(_str, s._str);
		}

		bool empty()
		{
			return _size == 0;
		}
		size_t size() const
		{
			return _size;
		}
		size_t capacity()
		{
			return _capacity;
		}

	private:
		char* _str;
		size_t _size;             //已经有多少个有效字符个数
		size_t _capacity;         //能存多少个有效字符个数 \0不是有效字符,\0是标识结束的字符


		void CheckFull()
		{
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 6 : _capacity * 2;
				char* tmp = new char[newcapacity + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = newcapacity;
			}
		}
	};

	inline std::ostream& operator<<(std::ostream& _out, const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			_out << s[i];
		}
		return _out;
	}

	// 将函数标记为"inline"可以消除重定义错误

    // 在头文件中定义了这些函数需要将它们标记为 inline
	// 否则每个包含该头文件的源文件都会生成该函数的定义,从而导致链接错误
	inline std::istream& operator>>(std::istream& _in, string& s)
	{
		while (1)
		{
			char c;
			c = _in.get();
			if (c == ' ' || c == '\n')
			{
				break;
			}
			else
			{
				s += c;
			}
		}
		return _in;
	}
}

MyString.c

#include"MyString.h"

size_t imitate::string::npos = -1;

imitate::string& imitate::string::operator=(string& s)
{
	string tmp(s);
	string::swap(tmp);
	return *this;
}

void imitate::string::swap(string& s)
{
	std::swap(_size,s._size);
	std::swap(_capacity ,s._capacity);
	std::swap(_str, s._str);
}

void imitate::string::push_back(char c)
{
	this->CheckFull();
	_str[_size] = c;
	_size++;
	_str[_size] = '\0';
}

void imitate::string::append(const char* str)
{
	size_t len = strlen(str);
	if ((_size + len) > _capacity)
	{
		size_t newcapacity = _size + len;
		char* tmp = new char[newcapacity + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = newcapacity;
	}
	strcpy(_str + _size, str);
	_size += len;
}
void imitate::string::append(const string& s)
{
	if ((_size + s._size) > _capacity)
	{
		size_t newcapacity = _size + s._size;
		char* tmp = new char[newcapacity + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = newcapacity;
	}
	strcpy(_str + _size, s._str);
	_size += s._size;
}

imitate::string& imitate::string::operator+=(char c)
{
	this->push_back(c);
	return *this;
}
imitate::string& imitate::string::operator+=(const char* str)
{
	this->append(str);
	return *this;
}
imitate::string& imitate::string::operator+=(const string& s)
{
	this->append(s);
	return *this;
}

imitate::string& imitate::string::insert(size_t pos, char c)
{
	assert(pos <= _size);
	this->CheckFull();
	size_t end = _size;
	while (end >= pos)
	{
		_str[end + 1] = _str[end];
		end--;
	}
	_str[pos] = c;
	_size++;
	return *this;
}
imitate::string& imitate::string::insert(size_t pos, const char* str)
{
	assert(pos < _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		size_t newcapacity = _size + len;
		char* tmp = new char[newcapacity + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = newcapacity;
	}
	size_t end = _size;
	while (end >= pos)
	{
		_str[end + len] = _str[end];
		end--;
	}
	for (size_t i = 0; i < len; ++i, ++pos)
	{
		_str[pos] = str[i];
	}
	_size += len;
	return *this;
}
imitate::string& imitate::string::insert(size_t pos, const string& s)
{
	assert(pos < _size);
	size_t len = s._size;
	if (_size + len > _capacity)
	{
		size_t newcapacity = _size + len;
		char* tmp = new char[newcapacity + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = newcapacity;
	}
	size_t end = _size;
	while (end >= pos)
	{
		_str[end + len] = _str[end];
		end--;
	}
	for (size_t i = 0; i < len; ++i, ++pos)
	{
		_str[pos] = s._str[i];
	}
	_size += len;
	return *this;
}

imitate::string& imitate::string::erase(size_t pos, size_t len)
{
	assert(pos < len);
	if (len >= _size-pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		for (size_t i = pos + len; i <= _size; ++i)
		{
			_str[i - len] = _str[i];
		}
		_size = _size - len;
	}
	return *this;
}

size_t imitate::string::find(char c, size_t pos)
{
	iterator it = begin();
	while (it != end())
	{
		if (*it == c)
		{
			return it - begin();
		}
		it++;
	}
	return npos;
}
size_t imitate::string::find(const char* str, size_t pos)
{
	char* p = strstr(_str + pos, str);
	if (!p)
	{
		return npos;
	}
	return p - _str;
}
size_t imitate::string::find(const string& s, size_t pos)
{
	char* p = strstr(_str + pos, s._str);
	if (!p)
	{
		return npos;
	}
	return p - _str;
}

void imitate::string::resize(size_t newsize, char c)
{
	if (_size < newsize)
	{
		_str[newsize] = '\n';
		_size = newsize;
	}
	else
	{
		if (newsize > _capacity)
		{
			size_t newcapacity = newsize;
			char* tmp = new char[newcapacity + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = newcapacity;
		}
		for (size_t i = _size; i < newsize; ++i)
		{
			_str[i] = c;
		}
		_size = newsize;
		_str[_size] = '\0';
	}
}

test.c

#include"MyString.h"

using namespace imitate;

void test1()
{
	string s1;
	string s2("Hello");
	string s3(s2);
	string s4(" World!");

	s2.push_back('!');
	s2.append(" World!");
	s3.append(s4);

	s4 += s2;
	s2 += "Hi! Hi! Hi!";

}
void test2()
{
	string s2("Hello");
	string s3(s2);
	string s4(" World!");

	s2.insert(2, s4);
}
void test3()
{
	string s1("Hello");
	string s2(" World!");
	s1.append(s2);
	s1.find('a', 2);
	s1.find("World!", 7);
	s1.find(s2);

	std::cin >> s1;

	std::cout << s1;
}

int main()
{
	test3();
	std::cout << string::npos;
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/457784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

磁珠的工作原理

磁珠是一个耗能元器件&#xff0c;他能把频率相对较高的信号以热量的形式耗散掉&#xff0c;保留频率相对较低的信号。 主要有这种插件的磁珠&#xff0c;还有这种贴片的磁珠。 下面我们来看下磁珠具体工作原理。 磁珠的构造我们可以简单的看成一个导线穿过环形铁氧体的磁性材…

[渗透教程]-015-网络与系统渗透

文章目录 1.0基本概念2.0 网络与系统渗透基本原理2.1 渗透测试2.2 入侵和预防2.3 案例一:从信息收集到入侵提权2.3.1 从域名到ip2.3.2 从IP获取旁站2.3.3 收集系统与⽹络配置详细信息2.3.4 踩点2.3.5发现漏洞2.3.6漏洞利用2.3.7维持系统控制权2.3.8清理访问痕迹2.4 案例二:Goo…

TryHackMe-Jeff(boot2root | Hard?)

Jeff 你能破解杰夫的网络服务器吗&#xff1f; 如果你发现自己在暴力破解SSH&#xff0c;你就做错了。 端口扫描 循例nmap 进80&#xff0c;是一个空页面&#xff0c;查看源代码 将jeff.thm加入hosts 上gobuster /admin是空页面&#xff0c;/backups也没东西&#xff0c;/up…

Centos安全加固策略

目录 密码安全策略 设置密码的有效期和最小长度 设置用户密码的复杂度 登录安全策略 设置用户远程登录的安全策略 安全的远程管理方式 访问控制 限制root用户登录 修改ssh 22端口 设置登录超时时间 限制IP访问 安全审计 审核策略开启 日志属性设置 查看系统登录…

基础巩固、探寻Java装箱和拆箱的奥妙!

前言 今天在逛某知名论坛的时候&#xff0c;看到一篇"请不要使用包装类型&#xff0c;避免造成性能损失"的文章。一下子就吸引了我的注意。大意就是&#xff0c;能用基本数据类型就尽量用基本数据类型&#xff0c;因为包装类型自动拆箱、装箱会带来性能损失尤其是循环…

函数式编程#3纯函数的概念

纯函数的概念 文章目录 纯函数的概念纯函数的两种形式&#xff1a;调用目标本身,不会改变函数内部,不受函数外部影响 函数的副作用如何理解"相同的输入得到相同的输出"不是纯函数的映射关系是纯函数的映射关系 纯函数的两种形式&#xff1a; 调用目标本身,不会改变 …

gcc编译 与交叉编译(x86 to arm) (一)单个文件编译

1.1、gcc编译单个c程序&#xff08;hello.c) gcc hello.c -o hello (hello是生成的可执行程序的名字&#xff09;1.2、交叉编译hello.c 源平台&#xff1a; UOS_X86_64 目标平台&#xff1a;UOS_arm 方法&#xff1a;使用现成的交叉编译工具链 参考资料&#xff1a;交叉编译…

【UE】water插件的简单使用

UE Editor版本&#xff1a;4.26 目录 一、岛屿外观修改 二、波浪参数设置 三、水体海洋的颜色设置 四、 水体河流 五、创建可浮在水体上的actor 一、岛屿外观修改 1. 保证“Landmass”和“Water”插件已启用 启用后&#xff0c;搜索water可以看到如下组件 2. 激活地形编…

LeetCode:6390. 滑动子数组的美丽值

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340; 算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 一、&#x1f331;6390. 滑动子数组的美丽值 题目描述&#xff1a;给你一个长度为 n 的整…

vulnhub DC:4渗透笔记

靶场下载地址:https://vulnhub.com/entry/dc416-2016,168/ 信息收集 使用nmap确定靶场ip地址 扫描ip确定开放端口 开放22 80端口&#xff0c;访问一下网页端(这边断了一次靶机ip改为192.168.100.138) 漏洞利用 登录框尝试爆破 发现用户名密码admin happy 登录进入后发现这里…

【自然语言处理】【大模型】LaMDA:用于对话应用程序的语言模型

LaMDA&#xff1a;用于对话应用程序的语言模型 《LaMDA: Language Models for Dialog Applications》 论文地址&#xff1a;https://arxiv.org/abs/2201.08239 相关博客 【自然语言处理】【大模型】LaMDA&#xff1a;用于对话应用程序的语言模型 【自然语言处理】【大模型】Dee…

如何衡量 SLO 的有效性?

衡量 SLO 及错误预算策略是否有效&#xff0c;其实就是看实际运行后&#xff0c;是否真的能达到我们的期望。我们可以从下面三个关键维度来看。 SLO 达成情况。我们用达成&#xff08;Met&#xff09;&#xff0c;或未达成&#xff08;Missed&#xff09;来表示。“人肉”投入…

阿里EGES

EGES&#xff1a;Billion-scale Commodity Embedding for E-commerce Recommendation in Alibaba 阿里的EGES是Graph Embedding的一个经典应用&#xff0c;在内容冷启和物料召回上面有较多的落地潜力。主要思想是根据用户交互的物料作为节点构建物料图&#xff0c;在传统的Dee…

(二)AIGC—Stable Diffusion(2)

越往后&#xff0c;加的噪声越多&#xff0c;逐渐递增 正常的话&#xff0c;类似RNN&#xff0c;前向传递&#xff0c;不利于模型训练。 如果直接从x0到xt最好&#xff0c;DPPM这篇论文就实现了这一目标 beta这一参数在扩散过程是已知的&#xff0c;前期设计好&#xff0c;从0…

从0搭建Vue3组件库(六):前端流程化控制工具gulp的使用

随着前端诸如webpack&#xff0c;rollup&#xff0c;vite的发展&#xff0c;gulp感觉似乎好像被取代了。其实并没有&#xff0c;只不过它从台前退居到了幕后。我们仍然可以在很多项目中看到它的身影&#xff0c;比如elementplus、vant等。现在gulp更多的是做流程化的控制。 比如…

【算法题解】28.子集的递归解法

这是一道 中等难度 的题 题目来自&#xff1a; https://leetcode.cn/problems/subsets/ 题目 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 …

51单片机(二)成功点亮LED

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其实STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

nuxt3 + pinia + swiper +element-plus + less + 腾讯地图 创建项目及使用

一。先说优点 1、基于Vue3&#xff1a; Nuxt3是基于Vue.js 3开发的&#xff0c;Vue.js 3是目前最流行的前端框架之一。 这意味着你可以利用Vue3的所有优势&#xff0c;如性能优化、响应式编程、更好的TypeScript支持等。2、服务端渲染&#xff08;SSR&#xff09;&#xff1a…

C++之引用的介绍

目录 前言 引用 1.引用的概念 2.引用特性 3.引用的权限 4. 使用场景 4.1 做参数 4.2 做返回值 5.引用和指针的区别 前言 相信大家都看过水浒传&#xff0c;里面的英雄人物除了自己的名字外都有自己的称号&#xff0c;比如&#xff1a;花和尚——鲁智深&#xff0c;豹…

centOS7.9安装nginx

此示例为安装nginx-1.20.1 &#xff08;小版本无差别&#xff09; 安装过程 sftp上传nginx-1.20.1.tar.gz文件到机器&#xff08;在root目录下&#xff09; #cd /usr/local/nginx/sbin #./nginx -V (查看版本&#xff0c;如果已经有安装过的话会出现版本号) 切换到loca…