string的底层简单实现(造轮子)

news2024/11/24 7:52:11

文件:String.h ----- 头文件

           String.cpp ----- 源文件

           Test.cpp ----- 源文件

实现细节:

实现带参构造:

在实现带参构造建议不使用初始化列表,初始化去写不太好:

:_str(new char[strlen(str)+1])

用初始化列表要在这new空间,String要开自己的空间,字符串初始化的时候,把字符串拷贝给str,因为这样才能修改与扩容,+1的原因是为了多开一个空间给\0

,_size(strlen(str))
,_capacity(_size)

注意:这样的话,_size初始化也要strlen,_capacity也可以要strlen

那如果先初始化_size(strlen(str)),再用_size取初始化_str呢?

这就犯了一个致命错误:初始化列表出现的顺序并不是真正的顺序,是按声明顺序,_str(_size)先走,给了个随机值,这就坑死了,那去改声明顺序,就显得不太好,所以,建议在这不使用初始化列表,直接建立在函数体里面,就不会受这些乱七八糟的影响了

String(const char* str)
{
	_size = strlen(str);
	_capacity = _size;//_capacity不包含\0,不要+1,所以我们在开空间的时候才会多开一个
	_str = new char[_capacity + 1];
	strcpy(_str, str);//将str拷贝给到_str
}

注意:_capacity不包含\0,不要+1,所以我们在开空间的时候才会多开一个

测试函数的位置:

在类外面定义函数的话,在编译时会发生链接错误:

//String.h 文件
namespace my_home
{
	class String
	{
	public:
		//无参
		String()
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};

	void test_string1()
	{
		string s1;
		string s2("hello world");
	}

}

1.类里面的默认是内联,内联不会放到符号表,就不会冲突;
2.在类外面定义全局的函数,因为头文件(String.h)在String.cpp文件包含了一份,在Test.cpp也包含了一份,那么这个函数就会在两个cpp生成的目标文件各自有一份,最后链接合到一起的时候有两份就冲突了(链接错误);

解决链接错误的方法:
1.用static修饰函数,静态的,只有在当前文件可建,相当于不进入符号表
2.内联也可以解决问题,与静态达到的效果是类似的,内联还有另外一层展开,本质也是只在当前文件可建,不进入符号表
3.最标准的还是去做声明和定义的分离

无参构造的空指针解引用: 

其实上面无参的实现存在问题:空指针解引用

在String类的public添加函数:

const char* c_str()
{
	return _str;
}

因为当前还没有重载<<,所以仅仅打印字符串:

void test_string1()
{
	String s1;//s1是空的string对象,使用空指针进行初始化的
	String s2("hello world");
	cout << s1.c_str() << endl;//空指针char*在打印的时候,char*会认为是字符串,会直接解引用,遇到\0才会终止,造成了空指针的解引用
	cout << s2.c_str() << endl;
}

程序崩溃 

1.s1是空的string对象,使用空指针进行初始化的;
2.空指针char*在打印的时候,char*会认为是字符串,会直接解引用,遇到\0才会终止,造成了空指针的解引用;

所以,我们应该对无参构造进行修改:

String()
	: _str(new char[1]{'\0'}) // _str(nullptr)
	, _size(0)
	, _capacity(0)
{}

为了美观,我们可以将无参和带参进行整合·:全缺省构造函数

String(const char* str = "")//不要写成nullptr问题与上面一样,字符串有\0,没必要写成"\0"这样其实是两个\0
{
	_size = strlen(str);
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

注意:不要写成nullptr问题与上面一样,字符串有\0,没必要写成"\0"这样其实是两个\0 

类中的声明与定义分离

短小,频繁调用的函数我们直接写在类里面,默认是内联函数
下面几个是适合做内联的函数:

String(const char* str = "")
{
	_size = strlen(str);
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}
~String()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}
const char* c_str() const
{
	return _str;
}
size_t size() const
{
	return _size;
}
size_t capacity()const
{
	return _capacity;
}
char& operator[](size_t pos)
{
	assert(pos < _size);//防止越界
	return _str[pos];
}
const char& operator[](size_t pos) const
{
	assert(pos < _size);//防止越界
	return _str[pos];
}

那么当前的程序支持范围否吗?

显然,当前的情况并不支持范围for:因为其实范围for的底层就是迭代器 

简单迭代器的实现:(在某些场景下会有缺陷)

//在类里面定义类型有两种方式,一种是内部类,另一种是typedef
typedef char* iterator;//在String类里typedef了iterator,类型char*,属于String这个类域
//其实迭代器模拟的是指针的行为
typedef const char* const_iterator;
iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}
const_iterator begin()const
{
	return _str;
}
const_iterator end()const
{
	return _str + _size;
}
//主要是底层是一个物理数组

1.在类里面定义类型有两种方式,一种是内部类,另一种是typedef ,这样就是为什么说有的容器的迭代器都一个名,但是不全是指针实现的,list就不是由原生指针;
2.在String类里typedef了iterator,类型char*,属于String这个类域;
3.其实迭代器模拟的是指针的行为;
4.迭代器是种封装的体现,屏蔽了底层实现的细节,提供了统一的类似访问容器的方式,不需要关心容器底层结构和实现细节;

//范围for
for (auto e : s2)
{
	cout << e << " ";
}
cout << endl;
//迭代器
String::iterator it = s2.begin();
while (it != s2.end())
{
	cout << *it << endl;
	++it;
}

这时候范围for便也可以遍历,侧面说明了范围for底层就是迭代器

但是for是傻瓜式模仿,一但begin()写成Begin()就行不通

浅拷贝问题:
class String
{
public:
	String(const char* str = "")
	{
		// 构造String类对象时,如果传递nullptr指针,可以认为程序非
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};
// 测试
void TestString()
{
	String s1("hello bit!!!");
	String s2(s1);
}

说明:上述 String 类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认 的,当用 s1 构造 s2 时,编译器会调用默认的拷贝构造。最终导致的问题是, s1 s2 共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃 ,这种拷贝方式,称为浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来 。如果 对象中管理资源 ,最后就会 导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规

 所以,如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

深拷贝:

传统写法:自己开空间,自己拷贝

String(const String& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

现代写法:让别人帮自己拷贝(交换)

交换_str,如果_str是随机值(因为对于内置类型编译器未必会初始化),交换给tmp,tmp作为局部对象,出了作用域要被销毁,就相当于释放野指针了,因此,可以在声明时给缺省值

void swap(String& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
String(const String& s)
{
	String tmp(s.c_str());
	swap(tmp);
}
流赋值拷贝: (深拷贝)

知晓了拷贝构造,其实复制拷贝跟拷构造差不多,只不过赋值拷贝是运算符重载, 复制拷贝的问题不仅仅是浅拷贝,还导致了之前的数据被覆盖,导致内存泄漏,需要我们自行实现:

传统写法:自己开空间

String& operator=(const String& s)
{
    //防止自己把自己释放了
	if (this != &s)
	{
		delete[] _str;

		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}

	return *this;
}

现代写法:抢夺资源(与拷贝构造不同,复制拷贝还将tmp给析构了,而拷贝构造只是tmp指向nullptr)(代码块也是一个域,出了域调用析构函数)

String& operator=(const String& s)
{
	//防止自己把自己释放了
	if (this != &s)
	{
		String tmp(s);//调用拷贝构造
		swap(tmp);
	}

	return *this;
}

最新版本:s传给tmp会调用拷贝构造(现代写法的浓缩)(只用拷贝构造才必须用引用)

String& operator=(String tmp)
{
	swap(tmp);
	return *this;
}
swap:
void test_string7()
{
	String s1("hello world");
	String s2;
	std::swap(s1, s2);
	s1.swap(s2);
}

对于库里面的swap与我们自行实现得swap有什么区别吗?

库中swap实现:

 

C++98的库的swap模板,会深拷贝3次,所以我们自行实现的是面对C++98版本更推荐的

为了防止这个问题,其实库里面就已经为我们解决了这个问题:

交换字符串对象 x 和 y 的值,这样在调用此函数后,x 的值是调用之前位于 y 上的值,y 的值是 x 的值
这是泛型算法交换的重载,它通过相互转移对其内部数据的所有权到另一个对象(即,字符串交换对其数据的引用,而不实际复制字符)来提高其性能:它的行为就像调用了 x.swap(y)

面对模板和实例化共存,是优先走实例化的(有现成吃现成的),所以以上两种其实效率一样 

流提取:分隔问题

in会跳过   ' '   '\n'  , 有分隔符的概念,读整数,浮点数,字符...

那么,在实现String的流提取时,需要读到分隔符达到结束的标志,然而单单的in会直接不读空格和换行。在C语言中,可以用getc()来解决问题,C++其实也有解决的办法:get()

这时候虽然到达了输入的目的,但是如果String有东西,输入的内容直接接在原来的String变量后面,这时候我们应该自行写一个clear(),清除数据,保留原来空间:

void clear()
{
	_str[0] = '\0';
	_size = 0;
}
istream& operator>>(istream& in, String& s)
{
	s.clear();
	char ch;//C语言用getc()解决
	ch = in.get();//C++中,有get(),他就不用管什么分隔符,因为他不涉及任何类型,会一个字符一个字符的读//in会跳过' ,'\n',有分隔符的概念,读整数,浮点数,字符...
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

优化:面对输入大数据,+=会不断地扩容,会有消耗,效率降低

解决:提供一个buffer,每次来的字符不往String变量(s)进行+=,而是将每次来的字符都把它放到buffer里面,如果buffer差一个满了(最后一个放\0),用Stringd变量进行+=buffer,相当于把buffer当成一个缓冲区:(buffer是栈上的,开得快,且临时)

istream& operator>>(istream& in, String& s)
{
	s.clear();
	const int N = 256;
	char buffer[N];
	int i = 0;
	char ch;//C语言用getc()解决
	ch = in.get();//C++中,有get(),他就不用管什么分隔符,因为他不涉及任何类型,会一个字符一个字符的读//in会跳过' ,'\n',有分隔符的概念,读整数,浮点数,字符...
	while (ch != ' ' && ch != '\n')
	{
		buffer[i++] = ch;
		if (i == N - 1)
		{
			buffer[i] = '\0';
			s += buffer;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)//说明buffer还有字符没有+=到s
	{
		buffer[i] = '\0';
		s += buffer;
	}
	return in;
}

实现代码:

头文件:String.h

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

using namespace std;
namespace home
{
	class String
	{
	public:
		//在类里面定义类型有两种方式,一种是内部类,另一种是typedef
		typedef char* iterator;//在String类里typedef了iterator,类型char*,属于String这个类域
		//其实迭代器模拟的是指针的行为
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin()const
		{
			return _str;
		}
		const_iterator end()const
		{
			return _str + _size;
		}
		String(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		void swap(String& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//显示提供拷贝构造
		String(const String& s)
		{
			String tmp(s.c_str());
			swap(tmp);
		}
		String& operator=(String tmp)
		{
			swap(tmp);
			return *this;
		}
		~String()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		const char* c_str() const
		{
			return _str;
		}
		size_t size() const
		{
			return _size;
		}
		size_t capacity()const
		{
			return _capacity;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);//防止越界
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);//防止越界
			return _str[pos];
		}
		//声明和定义分离
		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		String& operator += (char ch);
		String& operator +=(const char* str);
		
		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);

		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		String substr(size_t pos = 0, size_t len = npos);

	private:
		//内置类型未必初始化
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		static const size_t npos = -1;//在这可以写,核心原因是有const
		
		//static const double N = 1.1;//只有整型才可以

		/*static const int N = 1;
		int buff[N];*/
	};
	//const size_t String::npos = -1;
	//为了支持不属于String的比较(但其实可以隐式类型转换(调用构造)的,感觉没必要)(必须要有一个类类型变量)
	bool operator<(const String& s1, const String& s2);
	bool operator<=(const String& s1, const String& s2);
	bool operator>(const String& s1, const String& s2);
	bool operator>=(const String& s1, const String& s2);
	bool operator==(const String& s1, const String& s2);
	bool operator!=(const String& s1, const String& s2);
	//不写成成员函数是因为String会抢占cout位置
	ostream& operator<<(ostream& out, const String& s);
	istream& operator>>(istream& in, String& s);

	void test_string1();
	void test_string2();
	void test_string3();
	void test_string4();
	void test_string5();

}

源文件:String.cpp

#include"String.h"
namespace home
{
	//const size_t String::npos = -1;
	void String::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];//为了\0
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}
	void String::push_back(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';//注意
	}
	void String::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}

	String& String::operator += (char ch)
	{
		push_back(ch);
		return *this;
	}
	String& String::operator +=(const char* str)
	{
		append(str);
		return *this;
	}
	void String::insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		// 挪动数据//注意有些代码的隐式类型转化
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}

		_str[pos] = ch;
		++_size;
	}

	void String::insert(size_t pos, const char* s)
	{
		assert(pos <= _size);

		size_t len = strlen(s);
		if (_size + len > _capacity)
		{
			// 大于2倍,需要多少开多少,小于2倍按2倍扩
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}

		size_t end = _size + len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}

		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = s[i];
		}

		_size += len;
	}

	void String::erase(size_t pos, size_t len)
	{
		assert(pos < _size);

		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 -= len;
		}
	}

	size_t String::find(char ch, size_t pos)
	{
		assert(pos < _size);

		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}

		return npos;
	}

	size_t String::find(const char* str, size_t pos)
	{
		assert(pos < _size);

		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;
		}
	}

	String String::substr(size_t pos, size_t len)
	{
		assert(pos < _size);

		// len大于剩余字符长度,更新一下len
		if (len > _size - pos)
		{
			len = _size - pos;
		}

		String sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}

		return sub;
	}
	bool operator<(const String& s1, const String& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}
	bool operator<=(const String& s1, const String& s2)
	{
		return s1 < s2 || s1 == s2;
	}
	bool operator>(const String& s1, const String& s2)
	{
		return !(s1 <= s2);
	}
	bool operator>=(const String& s1, const String& s2)
	{
		return !(s1 < s2);
	}
	bool operator==(const String& s1, const String& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}
	bool operator!=(const String& s1, const String& s2)
	{
		return !(s1 == s2);
	}

	ostream& operator<<(ostream& out, const String& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	istream& operator>>(istream& in, String& s)
	{
		s.clear();
		const int N = 256;
		char buffer[N];
		int i = 0;
		char ch;//C语言用getc()解决
		ch = in.get();//C++中,有get(),他就不用管什么分隔符,因为他不涉及任何类型,会一个字符一个字符的读//in会跳过' ,'\n',有分隔符的概念,读整数,浮点数,字符...
		while (ch != ' ' && ch != '\n')
		{
			buffer[i++] = ch;
			if (i == N - 1)
			{
				buffer[i] = '\0';
				s += buffer;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)//说明buffer还有字符没有+=到s
		{
			buffer[i] = '\0';
			s += buffer;
		}
		return in;
	}
	

}

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

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

相关文章

如何在 Jupyter Notebook 中直接设置全局随机种子的方法及易错地方、notebook和pycharm中设置随机种子的区别

结论&#xff1a; 在 Jupyter Notebook 中直接设置全局随机种子的方法是确保每个单独的代码块中都调用相同的 set_seed 函数。这是最简单且有效的方法。在每个代码块开头设置随机种子&#xff0c;确保代码在每次执行时具有相同的随机数生成顺序。 易错地方&#xff1a; …

mac配置git的sshkey

在MAC中配置Git的SSH Key&#xff1a; 1.打开终端 2.生成SSH密钥&#xff0c;输入以下命令&#xff1a; ssh-keygen -t rsa -b 4096 -C “你自己的账号电子邮件地址” 按回车键后&#xff0c;系统会提示你输入文件保存路径&#xff0c;默认为~/.ssh/id_rsa直接按回车键使用默…

数据结构初阶之排序(上)

排序的概念及其应用 排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使⼀串记录&#xff0c;按照其中的某个或某些关键字的⼤⼩&#xff0c;递增或递减的排列起来的操作。 排序的应用 如下图&#xff1a; 样例数组 下面我们给出一组乱序的数组&#xff0c;接下来的算…

程序员进阶架构知识体系、开发运维工具使用、Java体系知识扩展、前后端分离流程详解、设计模式开发实例汇总专栏分享

场景 作为一名开发者&#xff0c;势必经历过从入门到自学、从基础到进阶、从学习到强化的过程。 当经历过几年企业级开发的磨炼&#xff0c;再回头看之前的开发过程、成长阶段发现确实是走了好多的弯路。 作为一名终身学习的信奉者&#xff0c;秉承持续学习、持续优化的信念…

GitHub推出全新AI模型平台:简化开发者体验

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

《计算机网络》(第8版)第8章 互联网上的音频/视频服务 复习笔记

第 8 章 互联网上的音频/视频服务 一、概述 1 多媒体信息的特点 多媒体信息&#xff08;包括声音和图像信息&#xff09;最主要的两个特点如下&#xff1a; &#xff08;1&#xff09;多媒体信息的信息量往往很大&#xff1b; &#xff08;2&#xff09;在传输多媒体数据时&a…

【网络】TCP协议——TCP连接相关、TCP连接状态相关、TCP数据传输与控制相关、TCP数据处理和异常、基于TCP应用层协议

文章目录 Linux网络1. TCP协议1.1 TCP连接相关1.1.1 TCP协议段格式1.1.2 确定应答(ACK)机制1.1.3 超时重传机制 1.2 TCP连接状态相关1.2.1 TIME_WAIT状态1.2.2 CLOSE_WAIT 状态 1.3 TCP数据传输与控制相关1.3.1 滑动窗口1.3.2 流量控制1.3.3 拥塞控制1.3.4 延迟应答1.3.5 捎带应…

草的渲染理论

Unity引擎提供了基础的terrain工具&#xff0c;可以制作地形&#xff0c;在上面刷树刷草。对于树&#xff0c;Unity是支持带LOD的Prefab&#xff0c;不同距离显示不同细节的模型&#xff0c;效果还不错。对于草&#xff0c;Unity支持两种方式来刷草&#xff0c;一种是Add Grass…

汇凯金业:解读区块链概念、类型与独特优势

区块链作为一种具有革命性的创新技术&#xff0c;正在逐渐改变我们的生活和商业模式。它的去中心化、安全可靠、不可篡改等特性&#xff0c;为解决许多传统领域中的问题提供了新的思路和方法。 一、区块链的基本概念 区块链是一种具有创新性的计算机技术应用模式&#xff0c;…

C#复习之类和对象

知识点一&#xff1a;什么是类 基本概念&#xff1a; 具有相同特征 具有相同行为 一类事物的抽象 类是对象的模板 可以通过类创建出对象 类的关键字 Class 知识点二&#xff1a;类声明在哪里 类一般声明在namespace语句块中 知识点三&#xff1a;类声明的语法 知识点四&#xf…

html+css 实现文字滚动的按钮

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽效果&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 文…

2024年前端趋势:全栈或许是不容错过的选择!

近年来&#xff0c;前端开发的技术不断推陈出新&#xff0c;2024年也不例外。在这个变化迅速的领域&#xff0c;全栈开发逐渐成为一股不容忽视的趋势。无论你是经验丰富的开发者&#xff0c;还是刚刚入门的新手&#xff0c;掌握全栈技术都能让你在竞争中脱颖而出。而在这个过程…

Spring -- 拦截器

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 文章目录 1. 自定义拦截器2. 注册配置拦截器3. 拦截器详解3.1 拦截路径3.2 拦截器执行流程 3.3 DispatcherServlet源码分析3.3.1 初始化:3.3.2 处理请求3.3.3 适配器 拦截器是Spring框架提供的…

vue项目删除无用的依赖

1.安装依赖检查工具 npm i depcheck 2.查看无用的依赖 npx depcheck 3.手动删除pageage.json中的无用的依赖&#xff08;如果有sass和sass-loader不要删&#xff0c;会引起项目报错&#xff09; 4.全部删除完成之后&#xff0c;删除package-lock.json和yarn.lock文件&#x…

【文件解析漏洞复现】

一&#xff0e;IIS解析漏洞复现 1.IIS6.X 方式一&#xff1a;目录解析 搭建IIS环境 在网站下建立文件夹的名字为.asp/.asa 的文件夹&#xff0c;其目录内的任何扩展名的文件都被IIS当作asp文件来解析并执行。 访问成功被解析 方式一&#xff1a;目录解析 在IIS 6处理文件解…

图纸加密与零信任沙箱:构建企业数据安全的双重保障

在这个信息爆炸的时代&#xff0c;数据安全如同一场没有硝烟的战争。深信达SDC沙盒防泄密系统&#xff0c;以其零信任沙盒技术&#xff0c;为企业提供了一个坚不可摧的“金钟罩铁布衫”&#xff0c;确保企业图纸安全“坚如磐石”。 一、数据安全的“冰与火之歌” 数据安全是一…

如何简便改文件名

在出OI题的时候&#xff0c;有时候想要方便地把输入输出文件的文件名都改掉&#xff0c;类似于将a1.in,a2.in,…,a50.in都改成b1.in,b2.in,…,b50.in 我用gpt写了一个python代码 import osdef rename_files(base_name, new_name, num_files):for i in range(1, num_files 1)…

函数实例讲解 (一)

文章目录 函数中的引用、运算符、通配符1、引用2、运算符3、通配符 函数的类别、输入方式、结果检查1、函数类别2、输入方式3、结果检查 数组的基本概念1、数组极其元素的概念2、数组的书写3、数组的类型4、内存数组的存储位置5、数组公式与普通公式的区别 逻辑判断函数之IF1、…