C++ STL string类模拟实现

news2024/11/27 15:33:24

目录

string类成员变量

一.构造函数

二.析构函数

三.拷贝构造

四.size(),capacity()

五.operator [ ]

六. operator =

 七.字符串比较

 八.reserve()

九.push_back(),append()

十.operator+=

 十一.insert()

 十二.迭代器

 十二.erase()

十三.swap()

 十四.find()

十五.流提取,流输出

十六.对比库string和我们的String


上期我们已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。同时模拟实现string类对我们自身对类与对象的理解由进一步的提高。

string类成员变量

对于一个String类要有基本的存储体,和存储字符个数,还有存储容量。

class String
{
public:
    //成员函数
private:
	char* _str;   //存储字符串
	int _size;    //字符个数
	int _capacity;//容量
    static const size_t npos = -1;
};

一.构造函数

在实现构造函数的时候,我们需要知道一般一个类要有一个默认构造函数,同时string类也要支持常量字符串初始化。

	//默认构造函数
    String()
		:_str(new char[1]),_size(0),_capacity(0)
	{
		_str[0] = '\0';
	}
    //支持常量字符串初始化
	String(const char* str)
		:_size(strlen(str))
	{
		_capacity = _size == 0 ? 3 : _size;
		_str = new char[_capacity + 1];//在实际开辟空间的时候,多开一个字节,用于存储‘\0’
		strcpy(_str, str);
	}

这然写当然是没有问题的,但是其实还有更好的写法:


	//既是默认构造,又能接收常量字符串构造
	String(const char* str = "")
		:_size(strlen(str))
	{
		_capacity = _size == 0 ? 3 : _size;
		_str = new char[_capacity+1];//在实际开辟空间的时候,多开一个字节,用于存储‘\0’
		//将str数据拷贝到_str里
		strcpy(_str, str);
	}

注意:在设置_capacity时候,如果刚开始的时候,容量尽量不要是0。以免后面在进行倍数扩容是出现不必要的麻烦和判断。

二.析构函数

析构函数没有什么好介绍的大家直接看吧

    ~String()
	{
		delete[] _str;
		_size = _capacity = 0;
 	}

注意:虽然我们不写析构函数,编译器自己也会生成一个析构函数,但是今天这里编译器自己生成的不靠谱,因为我们由需要释放申请的堆空间。如果我们不去写那就会造成内存泄漏。

三.拷贝构造

拷贝构造就是使用一个已经有的String对象来初始化另一个String对象。

当然如果我们不写编译器也会自动生成一个,但是生成的这一个也是不靠谱的,因为编译器生成的只会做浅拷贝。

注意: 显然底层的_str 地址是一样的,那么就会导致对其中一个的操作必然会影响到另外一个。而且析构的时候会对同一块空间释放两次,导致出现出现一些内存问题。

深拷贝:

	//拷贝构造:使用一个已经有的String对象来初始化另一个String对象。
	//1.注意深拷贝
	String(const String& s)
	{
		_capacity = s._capacity;
		_size = s._size;
        //深拷贝重新开的空间
		_str = new char[_capacity+1];
		strcpy(_str, s._str);
	}

 底层存储的地址不同,自然两者不再有任何影响。

四.size(),capacity()

一个String由对字符个数(长度)的管理,由于在类里面是私有成员,我们就要提供成员函数以供外部获取,也不能让外部修改——返回值const。针对普通对象和 const 对象分别提供。

	//普通对象调用
    const int size() 
	{
		return _size;
	}

    const int capacity() 
	{
		return _capacity;
	}
    
    
    //const对象调用
    const int size() const
	{
		return _size;
	}

	const int capacity() const
	{
		return _capacity;
	}

	

五.operator [ ]

[ ]运算符的重载是使得String类能够像数组一样去访问字符串的每一个成员字符。在重载时针对const 对象也要有考虑。考虑到越界的检查。

	//operator[]普通对象调用
	char& operator[](const int index)
	{
		assert(index >= 0 && index < _size);
		return _str[index];
	}
	//operator[] const 对象调用,且不允许修改
	const char& operator[](const int index) const
	{
		assert(index >= 0 && index < _size);
		return _str[index];
	}

 

六. operator =

operator=重载赋值运算符,是使用一个已经有的String对象赋值给另一个String对象。这里我们需要考虑,左操作数容量的大小。如果左操作数容量足够还好,就算多了也即是浪费一点空间的问题,不够就会很麻烦,需要扩容。所以为了设计简单,无论左操作数容量是否足够,我们都直接重新开空间。


	// operator=重载赋值运算符
	void operator=(const String& s)
	{
		char* tmp = new char[s.capacity() + 1];
		_size = s.size();
		_capacity = s.capacity();
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
	}

 连续赋值,我们得operator[]返回值就要是赋值的左操作数本身。

	// operator=重载赋值运算符
	String& operator=(const String& s)
	{
		if (this != &s)//str=str时无需多余的运算
		{
			char* tmp = new char[s.capacity() + 1];
			_size = s.size();
			_capacity = s.capacity();
			strcpy(tmp, s._str);
			delete[] _str;
			_str = tmp;
			return *this;
		}
    }

 注意:

  • 这里不可以使用realloc来扩容,因为我们使用new来开辟的空间,直接new新的空间进行拷贝。
  • 我们要尽量先将数据拷贝到临时的tmp里,再将_str释放掉,如果使用首先将_str释放了,如果在new的结果出现差错,就会触发异常,导致原数据丢失。而且如果面对同一个对象相互赋值时也会出现同样的问题。

 七.字符串比较

字符串比较我们只需要实现一个等于,和大于或者小于,其他的直接复用。

//相等
	bool operator==(const String& s)const
	{
		return strcmp(_str, s._str)==0;
	}
	//小于
	bool operator<(const String& s)const
	{
		return  strcmp(_str, s._str) < 0;
	}
	//不等于
	bool operator!=(const String& s)const
	{
		return !(*this == s);
	}
	//小于等于
	bool operator<=(const String& s)const
	{
		return  *this == s || *this < s;
	}
	//大于
	bool operator>(const String& s)const
	{
		return  !( * this == s || *this < s);
	}
	//大于等于
	bool operator>=(const String& s)const
	{
		return  *this == s || *this > s;
	}

 八.reserve()

重新设置容量,我们采取的方案也是,重新开空间,拷贝原来的数据。

	//重新设置容量
	void reserve(size_t capacity)
	{
		if (capacity > _capacity)//不允许容量的缩减
		{
			char* tmp = new char[capacity + 1];
			_capacity = capacity;
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
		}
	}

 注意:可以使得容量变大,但是一般不允许容量减小。

九.push_back(),append()

push_back 尾部插入一个字符,append()尾部插入一个字符串。注意每次进行插入,都需要容量判断,以保证正常的插入。

//重新设置容量
	void reserve(int capacity)
	{
		char* tmp = new char[capacity + 1];
		_capacity = capacity;
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
	}

	void push_back(char ch)
	{
		//容量不足时
		if (_size + 1 >= _capacity)
		{
			reserve(_capacity * 2);
		}
		_str[_size++] = ch;
		_str[_size] = '\0';
	}

	void append(const char* s)
	{
        容量不足时
		int len = strlen(s);
		if (len + _size >= _capacity)
		{
			reserve(_capacity + len);
		}
		strcpy(_str + _size, s);
		_size += len;
	}

注意:

append的扩容,不能采取2倍扩容。因为有可能插入的字符串本身长度就超过了原容量的二倍。

十.operator+=

可以+=一个字符,或者+=一个字符串,或者+=一个String对象。运算符重载+=的效果和append和push_back基本一致,但是用起来的感觉却大不相同。这里我们复用append和push_back。

    String& operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}

	String& operator+=(const char* s)
	{
		append(s);
		return *this;
	}

	String& operator+=(const String& s )
	{
		append(s._str);
		return *this;
	}

 十一.insert()

insert支持在 index 位置插入一个字符,插入一个字符串。

    void insert(size_t index, char ch)
	{
		//判断位置是否合法
		assert(index >= 0 && index < _size);
		//判断是否需要扩容
		if (_size + 1 >= _capacity)
		{
			reserve(_capacity * 2);
		}
		//挪动数据
		int end = _size+1;//end=_size+1,将‘\0’一起拷进去。
		//注意:这里的end时int,index是size_t类型,进行比较的时候
		//会发生类型提升,int --> size_t,当index=0,循环结束的条件是end为-1,
		// 但是由于类型提升,end实际在比较的时候的值是一个很大的数。因此仍会进入循环。
		//int end = _size;
		//while (end >= index)
		//{
		//	_str[end + 1] = _str[end];
		//}
		//如果我们代码这样写就会避免当index=0时,end的结束条件是-1。
		while (end >= index+1)
		{
			_str[end] = _str[end - 1];
			end--;
		}
		//插入数据ch	
		_str[index] = ch;
		_size++;
	}

	void insert(size_t index, const char* str)
	{
		//判断位置是否合法
		assert(index >= 0 && index < _size);
		int len = strlen(str);
		if (len + _size >= _capacity)
		{
			reserve(_capacity + len);
		}
		//挪动数据
		int end = _size+len;
		while (end >= index + len)
		{
			_str[end] = _str[end-len];
			end--;
		} 
		//插入数据
		int j = 0;
		for (int i = index; i < index + len; i++)
		{
			_str[i] = str[j++];
		}
		_size += len;
		
	}

 十二.迭代器

string的迭代器,底层就是指针。迭代器提供了一种通用的遍历容器的手段。

	typedef char* iterator;
	typedef const char* const_iterator;
    //普通对象调用
	iterator begin()
	{
        //返回字符数组的第一个位置
		return _str;
	}
	iterator end()
	{
        //返回字符数组最后一个字符的下一个位置,与begin形成前闭后开。
		return _str + _size;
	}

    //const 对象调用,返回值const_iterator-->const char*
	const_iterator begin()const
	{
		return _str;
	}
	const_iterator end()const
	{
		return _str + _size;
	}

注意:

const_iterator-->const char* 迭代器,迭代器本身可以修改,但是迭代器所指向的内容不允许修改。

范围for的底层即使借助了迭代器实现遍历的。

 十二.erase()

erase支持从某一个位置开始删除后面的len个字符。


	//删除pos位置之后的len个字符
	void erase(size_t pos, size_t len = npos)
	{
		assert(pos >= 0 && pos < _size);
		if (pos + len > _size || len == npos)
		{
			_str[pos] = '\0';
			_size = pos;
			return;
		}
		//1.挪动数据
		strcpy(_str + pos, _str + pos + len);
		//2.挪动数据
		//int index = pos;
		//while (index + len < _size)
		//{
		//	_str[index] = _str[index + len];
		//	index++;
		//}
		_size -= len;
	}

注意:erase仅仅只是删除字符,减少了字符串的长度,但是并不会影响到容量的。

十三.swap()

string类自己也提供了一个,swap交换函数。上期我们在介绍string接口的时候也是介绍了这个接口,还特意提到了,string提供的swap要比std的swap效率高的多。

	//string提供的
    void swap(String &str)
	{
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
		std::swap(_str, str._str);
	}


    //std提供的交换函数
    template<class T>
    void swap(T& e1,T& e2)
    {
	    T tmp = e1;
	    e1 = e2;
        e2 = tmp;
    }    

注意:string提供的swap之所以效率要比std提供的高,因为string提供的是一个类的成员函数,仅仅做类的私有成员变量的交换就可以了。而std提供的swap函数,是一个全局函数,交换的过程需要经过三次深拷贝。

 十四.find()

是string提供了从字符串中的某一位置开始查找一个字符,和查找字符串的功能。

size_t find( const char ch, size_t pos = 0)
	{
		assert(pos < _size);
		for (int i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}

	size_t find(const char* str, size_t pos = 0)
	{
		assert(pos < _size);
		char* pindex = strstr(_str + pos, str);
		if (pindex == nullptr)
		{
			return npos;
		}
		else
		{
			return pindex - _str;
		}
	}

注意:strstr就是一个查找字串的函数,底层是一个暴力的查找算法。找到字串返回字串的手字符地址,没找到就返回nullptr。我们仅仅只要用的字串首字符地址,减去存储的首地址就是中间间隔的字符数,也就是找到的字串首字符的下标。

十五.流提取,流输出

//1.重载流输入
istream& operator>>(istream& in, String& str)
{
	str.clear();
	char ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		str.push_back(ch);
		ch = in.get();
	}

	return in;
}
//2.重载流输入
istream& operator>>(istream& in, String& str)
{
	str.clear();
	char buffer[128];
	char ch = in.get();
	int i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buffer[i++] = ch;
		if (i == 127)
		{
			buffer[i] = '\0';
			str += buffer;
			i = 0;
		}
		ch = in.get();
	}
	if (i != 0)
	{
		buffer[i] = '\0';
		str += buffer;
	}

	return in;
}

//重载流输出
ostream& operator<<(ostream& out,const String& str)
{
	for (auto e : str)
	{
		cout << e;
	}
	return out;
}

注意:流插入选取一个就可以,第一种实现的容易产生容量的浪费,第二种加入一个缓冲区,对容量利用更好。

十六.对比库string和我们的String

 我们能够看到,库里面的string要比我们的多16字节。这是因为库里面的string多包涵了一个16字节的字符数组。当我们存储的字符串小于16字节时,就直接存储在数组中。如果大于了16字节,再向堆上开辟空间存储。

//std库中实现的string类私有成员变量
class string
{
public:
	//....
private:
	char* _str;
	size_t _size;
	size_t _capacity;
	char __str[16];
};

在g++中的实现方式也是不一样。在g++中除了必要的长度,容量,指针以外还会有一个引用计数。

//g++下string类私有成员
class string
{
public:
	//...
private:
	char* _str;
	size_t _size;
	size_t _capacity;
	size_t _refcount;//引用计数
};

在拷贝时,g++下不会首先使用深拷贝,而是首先使用浅拷贝,并且引用计数加一。只有其中一个对象发生写入改变时才会开辟新的空间进行深拷贝,我们称这种机制也叫做,写时拷贝。这种机制普遍存在linux中,一定程度上可以节省空间和提升效率。

十七.完整代码示例

#pragma once
#include<cstring>
#include<iostream>
#include<cassert>
using namespace std;

//g++下string类私有成员
class string
{
public:
	//...
private:
	char* _str;
	size_t _size;
	size_t _capacity;
	size_t _refcount;//引用计数
};
class string
{
	//....
private:
	char* _str;
	size_t _size;
	size_t _capacity;
	char __str[16];
};

class String
{
public:
	typedef char* iterator;
	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 == 0 ? 3 : _size;
		_str = new char[_capacity+1];//在实际开辟空间的时候,多开一个字节,用于存储‘\0’
		//将str数据拷贝到_str里
		strcpy(_str, str);
	}

	//拷贝构造:使用一个已经有的String对象来初始化另一个String对象。
	//1.注意深拷贝
	String(const String& s)
	{
		_capacity = s._capacity;
		_size = s._size;
		_str = new char[_capacity+1];
		strcpy(_str, s._str);
	}

	//operator[]普通对象调用
	char& operator[](const size_t index)
	{
		assert(index >= 0 && index < _size);
		return _str[index];
	}
	//operator[] const 对象调用,且不允许修改
	const char& operator[](const size_t index) const
	{
		assert(index >= 0 && index < _size);
		return _str[index];
	}
	
	// operator=重载赋值运算符
	String& operator=(const String& s)
	{
		if (this != &s)//str=str时无需多余的运算
		{
			//注意:这里不可以使用realloc来扩容,因为我们使用new来开辟的空间,直接new新的空间进行拷贝。
			//细节:我们要尽量先将数据拷贝到临时的tmp里,再将_str释放掉,如果使用首先将_str释放了,
			//如果在new的结果出现差错,就是出发异常,导致原数据丢失。
			char* tmp = new char[s.capacity() + 1];
			_size = s.size();
			_capacity = s.capacity();
			strcpy(tmp, s._str);
			delete[] _str;
			_str = tmp;
			return *this;
		}
	}
	//重新设置容量
	void reserve(size_t capacity)
	{
		if (capacity > _capacity)//不允许容量的缩减
		{
			char* tmp = new char[capacity + 1];
			_capacity = capacity;
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
		}
	}

	void push_back(char ch)
	{
		//容量不足时
		if (_size + 1 >= _capacity)
		{
			reserve(_capacity * 2);
		}
		_str[_size++] = ch;
		_str[_size] = '\0';
	}

	void append(const char* s)
	{
		int len = strlen(s);
		if (len + _size >= _capacity)
		{
			reserve(_capacity + len);
		}
		strcpy(_str + _size, s);
		_size += len;
	}

	String& operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}

	String& operator+=(const char* s)
	{
		append(s);
		return *this;
	}

	String& operator+=(const String& s )
	{
		append(s._str);
		return *this;
	}

	void insert(size_t index, char ch)
	{
		//判断位置是否合法
		assert(index >= 0 && index < _size);
		//判断是否需要扩容
		if (_size + 1 >= _capacity)
		{
			reserve(_capacity * 2);
		}
		//挪动数据
		int end = _size+1;
		//注意:这里的end时int,index是size_t类型,进行比较的时候
		//会发生类型提升,int --> size_t,当index=0,循环结束的条件是end为-1,
		// 但是由于类型提升,end实际在比较的时候的值是一个很大的数。因此仍会进入循环。
		//int end = _size;
		//while (end >= index)
		//{
		//	_str[end + 1] = _str[end];
		//}
		//如果我们代码这样写就会避免当index=0时,end的结束条件是-1。
		while (end >= index+1)
		{
			_str[end] = _str[end - 1];
			end--;
		}
		//插入数据ch	
		_str[index] = ch;
		_size++;
	}

	void insert(size_t index, const char* str)
	{
		//判断位置是否合法
		assert(index >= 0 && index < _size);
		int len = strlen(str);
		if (len + _size >= _capacity)
		{
			reserve(_capacity + len);
		}
		//挪动数据
		int end = _size+len;
		while (end >= index + len)
		{
			_str[end] = _str[end-len];
			end--;
		} 
		//插入数据
		int j = 0;
		for (int i = index; i < index + len; i++)
		{
			_str[i] = str[j++];
		}
		_size += len;
		
	}

	//删除pos位置之后的len个字符
	void erase(size_t pos, size_t len = npos)
	{

		assert(pos >= 0 && pos < _size);
		if (pos + len > _size || len == npos)
		{
			_str[pos] = '\0';
			_size = pos ;
			return;
		}
		//1.挪动数据
		strcpy(_str + pos, _str + pos + len);
		//2.挪动数据
		//int index = pos;
		//while (index + len < _size)
		//{
		//	_str[index] = _str[index + len];
		//	index++;
		//}
		_size -= len;
	}

	void swap(String &str)
	{
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
		std::swap(_str, str._str);
	}

	size_t find( const char ch, size_t pos = 0)
	{
		assert(pos < _size);
		for (int i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}

	size_t find(const char* str, size_t pos = 0)
	{
		assert(pos < _size);
		char* pindex = strstr(_str + pos, str);
		if (pindex == nullptr)
		{
			return npos;
		}
		else
		{
			return pindex - _str;
		}
	}

	void clear()
	{
		_str[0] = '\0';
		_size = 0;
	}


	//相等
	bool operator==(const String& s)const
	{
		return strcmp(_str, s._str)==0;
	}
	//小于
	bool operator<(const String& s)const
	{
		return  strcmp(_str, s._str) < 0;
	}
	//不等于
	bool operator!=(const String& s)const
	{
		return !(*this == s);
	}
	//小于等于
	bool operator<=(const String& s)const
	{
		return  *this == s || *this < s;
	}
	//大于
	bool operator>(const String& s)const
	{
		return  !( * this == s || *this < s);
	}
	//大于等于
	bool operator>=(const String& s)const
	{
		return  *this == s || *this > s;
	}

	const size_t size() 
	{
		return _size;
	}

	const size_t size() const
	{
		return _size;
	}

	const size_t capacity() const
	{
		return _capacity;
	}

	const size_t capacity()
	{
		return _capacity;
	}

	const char* c_str()
	{
		return _str;
	}

	~String()
	{
		delete[] _str;
		_size = _capacity = 0;
 	}



private:
	char* _str;
	size_t _size;
	size_t _capacity;
	static const size_t npos = -1;
};

//重载流输出
ostream& operator<<(ostream& out,const String& str)
{
	for (auto e : str)
	{
		cout << e;
	}
	return out;
}

1.重载流输入
//istream& operator>>(istream& in, String& str)
//{
//	str.clear();
//	char ch = in.get();
//	while (ch != ' ' && ch != '\n')
//	{
//		str.push_back(ch);
//		ch = in.get();
//	}
//
//	return in;
//}
//2.重载流输入
istream& operator>>(istream& in, String& str)
{
	str.clear();
	char buffer[128];
	char ch = in.get();
	int i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buffer[i++] = ch;
		if (i == 127)
		{
			buffer[i] = '\0';
			str += buffer;
			i = 0;
		}
		ch = in.get();
	}
	if (i != 0)
	{
		buffer[i] = '\0';
		str += buffer;
	}

	return in;
}

//std提供的交换函数
template<class T>
void swap(T& e1,T& e2)
{
	T tmp = e1;
	e1 = e2;
	e2 = tmp;
}



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

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

相关文章

【雕爷学编程】Arduino动手做(12)---霍尔磁场传感器模块5

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

数据归一化:优化数据处理的必备技巧

文章目录 &#x1f340;引言&#x1f340;数据归一化的概念&#x1f340;数据归一化的应用&#x1f340;数据归一化的注意事项与实践建议&#x1f340;代码演示&#x1f340;在sklearn中使用归一化&#x1f340;结语 &#x1f340;引言 在当今数据驱动的时代&#xff0c;数据的…

Vue在页面输出JSON对象,测试接口可复制使用

效果图&#xff1a; 数据处理前&#xff1a; 数据处理后&#xff1a; 代码实现&#xff1a; HTML: <el-table height"600" :data"tableData" border style"width: 100%" tooltip-effect"dark" size"mini"><el-…

Django笔记之数据库函数之日期函数

日期函数主要介绍两个大类&#xff0c;Extract() 和 Trunc() Extract() 函数作用是提取日期&#xff0c;比如我们可以提取一个日期字段的年份&#xff0c;月份&#xff0c;日等数据 Trunc() 的作用则是截取&#xff0c;比如 2022-06-18 12:12:12&#xff0c;我们可以根据需求…

深度学习基础知识笔记

深度学习要解决的问题 1 深度学习要解决的问题2 应用领域3 计算机视觉任务4 视觉任务中遇到的问题5 得分函数6 损失函数7 前向传播整体流程8 返向传播计算方法1 梯度下降9 神经网络整体架构 11 神经元个数对结果的影响12 正则化和激活函数1 正则化2 激活函数 13 神经网络过拟合…

人工智能可解释性(二)(梯度计算,积分梯度等)

目录 1.定义 2.详述 2.1局部解释 可视化方法 梯度计算 2.2积分梯度Integrated Gradients&#xff08;梯度计算进阶&#xff09; 2. 3全局解释 2.3.1Activation Maximization 2.3.2GAN,VAE 2. 4用一个可解释模型解释不可解释模型 2. 4.1LIME 局部解释 参考文献 1.定义 可…

access怎么做进销存?借助access开发进销存管理应用

我不太推荐使用Access&#xff0c;因为他的缺点还是比较明显的&#xff1a; 1、软件自身限制 不能用于互联网&#xff1a;使用Access制作好的管理软件&#xff0c;访问页只能在局域网中使用&#xff1b;只能在Windows上运行&#xff1a;Access仅支持windows的运行环境&#x…

从零开始学习 Java:简单易懂的入门指南之多态(十)

多态&包&final&权限修饰符&代码块 第一章 多态1.1 多态的形式1.2 多态的使用场景1.3 多态的定义和前提1.4 多态的运行特点1.5 多态的弊端1.6 引用类型转换1.6.1 为什么要转型1.6.2 向上转型&#xff08;自动转换&#xff09;1.6.3 向下转型&#xff08;强制转换…

【将回声引入信号中】在语音或音频文件中引入混响或简单回声,以研究回声延迟和回波幅度对生成的回波信号感知的影响(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

积分代换和周期函数

昨晚上看书&#xff0c;有一个稳定随机过程的例题&#xff0c;涉及积分上下限代换、周期函数的微积分性质等知识点。这种题型以前肯定接触过&#xff0c;当下遇到了&#xff0c;思维仍然迷迷糊糊&#xff0c;像是一团乱麻&#xff0c;纠缠不清&#xff0c;照着答案思考了半天&a…

[Blender]Geometry nodes altermesh to UE

首先要先下载插件 AlterMesh – Use geometry nodes inside Unreal 下载对应版本的插件后 打开UE&#xff0c;在对应的设置里面挂上blender.exe的路径 去官方下载一个Blender Geometry nodes 的示例 Demo Files — blender.org​​​​​​

沃罗诺伊图(Voronoi):迷人的世界【1/2】

一、说明 Voronoi图&#xff08;也称为狄利克雷镶嵌或泰森多边形&#xff09;在自然界中无处不在。你已经遇到过他们数千次了&#xff0c;但也许没有这样称呼它。Voronoi图很简单&#xff0c;但它们具有令人难以置信的特性&#xff0c;在制图&#xff0c;生物学&#xff0c;计算…

【EI/SCOPUS征稿】第九届材料加工与制造工程国际学术会议(ICMPME 2023)

第九届材料加工与制造工程国际学术会议 2023 9th International Conference on Materials Processing and Manufacturing Engineering (ICMPME 2023) 第九届材料加工与制造工程国际学术会议(ICMPME 2023)定于2023年10月13-15日在中国南昌隆重举行。会议主要围绕“材料加工”、…

c++11 标准模板(STL)(std::basic_fstream)(五)

定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_fstream : public std::basic_iostream<CharT, Traits> 类模板 basic_fstream 实现基于文件的流上的高层输入/输出。它将 std::basic_i…

【Linux从入门到精通】文件描述符详解

文章目录 一、引言 二、引入文件描述符fd 2、1 观察fd的值 2、2 fd保存的位置 三、详解文件描述符fd 3、1 为什么要有文件描述符呢 3、2 到底什么是文件操作符呢 四、文件描述符的使用 4、1 验证文件描述符 4、1、1 验证stdin、stdout、stdout 4、1、2 验证fd值的大小顺序 4、…

IMU惯性测量单元相关技术(概念版)

重要说明&#xff1a;本文从网上资料整理而来&#xff0c;仅记录博主学习相关知识点的过程&#xff0c;侵删。 一、参考资料 新手入门系列3——Allan方差分析方法的直观理解 惯性测量单元Allan方差分析详解 IMU误差&测量模型 IMU标定之—Allan方差 IMU误差模型简介及VINS…

2023年中国日志审计市场竞争格局、市场规模、下游应用领域及行业发展趋势[图]

日志是行为或状态详细描述的载体&#xff0c;其时效性与信息丰富程度在网络安全事件分析、事件回溯和取证过程中起到重要作用。在法律层&#xff0c;日志也是重要的电子证据&#xff0c;日志记录、监控、审计手段等&#xff0c;可以帮助有效地减少信息破坏、信息泄露的问题&…

2023最新python学习方法总结!(内部机密)

不要再问我python好不好学了 我之前做过半年少儿编程老师&#xff0c;一个小学四年级的小孩子都能在我的教学下独立完成python游戏&#xff0c;植物大战僵尸简单版&#xff0c;如果要肯花时间&#xff0c;接下来的网络开发也不是问题&#xff0c;人工智能也可以学个调包也没啥问…

【TypeScript】this指向,this内置组件

this类型 TypeScript可推导的this类型函数中this默认类型对象中的函数中的this明确this指向 怎么指定this类型 this相关的内置工具类型转换ThisParameterType<>ThisParameterType<>ThisType TypeScript可推导的this类型 函数中this默认类型 对象中的函数中的this…

PLY模型格式详解【3D】

本文介绍PLY 多边形文件格式&#xff0c;这是一种用于存储被描述为多边形集合的图形对象。 PLY文件格式的目标是提供一种简单且易于实现但通用的格式足以适用于各种模型。 PLY有两种子格式&#xff1a;易于入门的 ASCII 表示形式和用于紧凑存储和快速保存和加载的二进制格式。 …