C++STL——string类详解及其模拟实现

news2024/11/24 17:53:30

C++STL——string类

1. STL简介

STL全称standard template libaray,译为标准模板库

  • 需要注意,STL不是C++的标准库,而是C++标准库的重要组成部分
  • STL是一个包含众多数据结构和算法软件框架

下面展示STL的六大组件:

在这里插入图片描述


本章,我们将对STL中的容器——string部分常用的功能进行说明和使用,最后对string进行简单的模拟实现

本章思维导图:
在这里插入图片描述注:本章思维导图已经同步导入至资源

2. string类

头文件<string>

我们先来看看string类是如何被声明的:

typedef basic_string<char> string;

可以看到:

string类是被类模板basic_string用数据类型char实例化后得到的一个具体的类的别名

同时规定:

  • string类是一个用来表示字符串的类
  • 标准string类提供的接口和许多标准容器的接口类似,但也额外提供了处理单字节字符的接口(处理字符串的常规操作)
  • 不能用来操作多字节或者变长字节的字符序列

需要注意:

string类是包含在C++标准库中的,因此如果要使用string

  • 一种方式:使用域作用限定符,例如std::string
  • 另一种方式:使用using namespace std打开命名空间

2.1 成员函数

2.2.1 默认成员函数

在这里插入图片描述

2.1.1.1 constructor——构造函数

string类的构造函数string()被重载成了许多形式,但我们只需掌握以下三种即可:

string();	//方法一:构造一个空串
string (const string& str);		//方法二:拷贝构造
string (const char* s);		//方法三:用一个常量字符串构造

例如:

#include <string>
#include <iostream>

nt main()
{
	std::string s1;	
	std::string s2("hello world");
	std::string s3(s2);

    //string类里面重载了流插入<<和流提取>>运算符
	std::cout << "字符串s1为:" << s1 << std::endl;
	std::cout << "字符串s2为:" << s2 << std::endl;
	std::cout << "字符串s3为:" << s3 << std::endl;

	return 0;
}

output:

字符串s1为:
字符串s2为:hello world
字符串s3为:hello world
2.2.1.2 operator =——赋值运算符重载
string& operator= (const string& str);	//类类型之间的赋值
string& operator= (const char* s);	//利用隐式类型转换,先将字符串s实例化为一个string类型对象,再进行赋值
string& operator= (char c);		//和第二种方法类似

例如:

#include <string>
#include <iostream>

int main()
{
	std::string s1, s2, s3;

	s1 = "hello world";
	s2 = 'c';
	s3 = s1;

	std::cout << "字符串s1为:" << s1 << std::endl;
	std::cout << "字符串s2为:" << s2 << std::endl;
	std::cout << "字符串s3为:" << s3 << std::endl;

	return 0;
}

output:

字符串s1为:hello world
字符串s2为:c
字符串s3为:hello world

2.2.2 size()/length——获取有效长度

size_t size() const;
size_t length() const;
  • 这两个函数的效果是一模一样的
  • 但是更建议使用函数size()
  • 字符串的有效长度是不包括结束字符‘\0’,其结果就和C语言的strlen()函数一样

例如:

#include <string>
#include <iostream>

int main()
{
	std::string s1;
	std::string s2("hello world");

	std::cout << "sizs of s1: " << s1.size() << std::endl;
	std::cout << "sizs of s2: " << s2.size() << std::endl;

	return 0;
}

output:

sizs of s1: 0
sizs of s2: 11

2.2.3 capacity()——获取最大容量

size_t capacity() const;
  • 一般来说,这个最大容量同样指的是可以存放有效字符的最大容量,也不包括结束符‘\0’

需要注意,由于不同平台所用的库不同,因此当用同一个字符串构造string对象时,分配给其用来存储字符的初始空间也不一定相同(即capacity不一定相同),例如:

对于相同的代码:

#include <string>
#include <iostream>

int main()
{
	std::string s1;
	std::string s2("nice to meet you");

	std::cout << "capacity of s1 is: " << s1.capacity() << std::endl;
	std::cout << "capacity of s2 is: " << s2.capacity() << std::endl;

	return 0;
}

在VS下,output:

capacity of s1 is: 15
capacity of s2 is: 31

在Linux下,output:

capacity of s1 is: 0
capacity of s2 is: 16
  • 实际上,这两个平台的扩容机制也完全不同,之后我们会进行演示

在这里插入图片描述

2.2.4 operator []/at()——获取指定位置的字符

      char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;

      char& at (size_t pos);
const char& at (size_t pos) const;

相同点:

  • 和普通的字符数组一样,string类类型的对象也可以通过类似[下标]的方式获得指定位置的字符
  • 这个函数被重载成了两份,分别给非cosnt对象和const对象使用

不同点:

  • 如果传入的pos大于size(),那么对于operator [],则会直接报错
  • 而对于at(),则会抛出异常

有了这两个成员函数,我们就可以对string对象存储的数据进行遍历访问了:

#include <string>
#include <iostream>

int main()
{
	std::string s1("hello world");

	for (int i = 0; i < s1.size(); i++)
		std::cout << s1[i] << ' ';
	std::cout << std::endl;

	for (int i = 0; i < s1.size(); i++)
		std::cout << s1.at(i) << ' ';
	std::cout << std::endl;

	return 0;
}

output:

h e l l o   w o r l d
h e l l o   w o r l d

2.2.5 iterator——迭代器

迭代器是一个用来访问容器数据的对象,其提供了统一的方式来遍历容器中的数据

  • 对于string类,我们可以将迭代器看成一个指针,其指向string对象存储的某个字符
  • 我们可以通过迭代器来访问或者修改容器中的数据
  • 尽管前面的[]运算符访问和修改string存储的数据十分方便,但必须说明,STL中,对于访问和遍历数据迭代器才是最常用的

以下是几种获取string类型迭代器的常见方式:

2.2.5.1 begin()/end()
      iterator begin();
const_iterator begin() const;
/*****************************************/
      iterator end();
const_iterator end() const;
  • begin()即返回一个指向字符序列第一个字符的迭代器;end()即返回一个指向字符序列**最后一个字符(即‘\0’)**的迭代器
  • begin() constend() const则是针对const对象做出的函数重载

注意:

由于string类实际上就是存储字符序列的类,因此针对它的迭代器iterator,我们可以将其看成为一个指向char类型的指针char*;而const_iterator则对应的是const char*

  • 有些小伙伴可能会疑惑:为什么const_iterator不写成const iterator
  • 首先我们要清楚对于const对象,其返回的迭代器应该具有这样的功能:允许访问(遍历)数据,但不允许修改数据
  • const iterator本质上修饰的是迭代器iterator本身,我们可以看作是char* const,这样子的效果是不能改变迭代器指向,但是可以改变迭代器指向数据的内容,这显然是不符合要求的
  • 但**const_iterator本质上修饰的就是迭代器指向的数据**,我们可以看作是const char*,这样就可以符合要求

示例:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1("hello world");

	string::iterator it = s1.begin();

    //对每个字符进行+1操作再进行打印
	while (it != s1.end())
	{
		(*it)++;
		cout << *it << ' ';
		it++;
	}
	cout << endl;

	return 0;
}

output:

i f m m p ! x p s m e
2.2.5.2 rbegin()/ rend()
      reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
/*****************************************/
      reverse_iterator rend();
const_reverse_iterator rend() const;
  • begin()/end()类似,只是返回的是反向迭代器
  • 所谓的反向迭代器即rbegin()返回一个指向字符序列最后一个有效字符的迭代器;rend()返回一个指向字符序列**第一个字符之前的字符(被认为是反向末端)**的迭代器
  • 如果反向迭代器的指向可以修改,那么例如对于rbegin()的返回结果进行+1操作,就会使迭代器指向倒数第二个字符
  • 反向迭代器和正向迭代器的返回类型原理类似,故不作赘述

例如:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1("hello world");

	string::reverse_iterator it = s1.rbegin();
	while (it != s1.rend())
	{
        cout << *it << ' ';
		it++;
	}
	cout << endl;

	return 0;
}

output:

d l r o w   o l l e h

2.2.6 容量管理

在这里插入图片描述

2.2.6.1 reserve
void reserve (size_t n = 0);
  • reserve成员函数会将string的最大容量capacity扩展为n
  • 需要注意,由于不同平台依赖的库不同,所以reserve最终的效果也会不同,但是无论如何,reserve()绝不会影响到存储的数据

下面就来看看在VS和Linux两个平台上reserve()函数的不同之处:

对于同样一份代码:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1("hello world");
	string s2 = s1;

	cout << "the capacity of s1 is " << s1.capacity() << endl;
	s1.reserve(100);
	cout << "after reserve(100), the capacity of s1 is " << s1.capacity() << endl;
	s1.reserve(50);
	cout << "after reserve(50), the capacity of s1 is " << s1.capacity() << endl << endl;

	cout << "the capacity of s2 is " << s2.capacity() << endl;
	s2.reserve(1);
	cout << "after reserve(1), the capacity of s2 is " << s2.capacity() << endl;

	return 0;
}

VS:

output:

the capacity of s1 is 15
after reserve(100), the capacity of s1 is 111
after reserve(50), the capacity of s1 is 111

the capacity of s2 is 15
after reserve(1), the capacity of s2 is 15

Linux:

output:

the capacity of s1 is 11
after reserve(100), the capacity of s1 is 100
after reserve(50), the capacity of s1 is 50

the capacity of s2 is 11
after reserve(1), the capacity of s2 is 11

可以总结出二者的不同:

  • VS分配空间时,总会比给定值多几个空间;而Linux则是给多少开多少
  • VS的reserve()函数不能缩小空间,只能扩大空间;而Linuxreserve函数可以缩小空间

同时它们也有一个共同点:

  • 无论给定值再怎么小,reserve()都不会影响到原来的数据
2.2.6.2 resize()
void resize (size_t n);
void resize (size_t n, char c);
  • resize()函数也是对最大容量的管理。但是它既可以缩小容量,同时也能影响到原有数据
  • 同时,这个函数还有初始化的功能:
    • 如果传入的n大于size小于capacity,那么如果字符c被指明,那么剩余的空间就会被字符c填充;如果字符c没被指明,那么剩余的空间就会被空字符‘\0’填充
    • 如果传入的n大于capacity,那么就会在扩容之后进行跟上面一样的初始化(填充)操作

例如:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1("hello");
	string s2 = s1;

	cout << "the capacity of s1 is " << s1.capacity() << endl;
	s1.resize(s1.capacity(), 'c');
	cout << s1 << endl << endl;

	cout << "the capacity of s2 is " << s2.capacity() << endl;
	s2.resize(100);
	cout << s2 << endl;
	cout << "the capacity of s2 is " << s2.capacity() << endl;
	cout << "the size of s2 is " << s2.size() << endl;

	return 0;
}

output:

the capacity of s1 is 15
hellocccccccccc

the capacity of s2 is 15
hello
the capacity of s2 is 111
the size of s2 is 100

2.2.7 增 operator +=

注:本篇只讲最常用的string添加字符/字符串的方法。其他方法还有如

  • 👉append

  • 👉push_back

  • 👉insert

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

例如:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1;
	string s2;
	string s3;

	s1 += "hello world";
	s2 += s1;
	s3 += 'c';

	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;

	return 0;
}

output:

hello world
hello world
c

2.2.8 删 erase

string& erase (size_t pos = 0, size_t len = npos);
  • 即删除从pos位置开始的len个字符

  • 注意:nposstring里面定义的一个const静态全局变量

    const static size_t npos = -1;
    
  • 无符号整形npos的值为-1,因此它的实际值为unsigned int的最大值

  • 如果npos用来表示一个长度,那么它通常用来说明直到字符串的尾

  • 如果npos用来表示一个返回值,那么它通常用来说明没有找到目标

例如:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1("hello world");

	s1.erase(2, 2);
	cout << s1 << endl;

	s1.erase(1);
	cout << s1 << endl;

	return 0;
}

output:

heo world
h

2.2.9 查 find/rfind

注:本篇只讲述最常用的find和rfind。其他方法还有如:

  • 👉find_first_of
  • 👉find_last_of
  • 👉find_first_not_of
  • 👉find_last_not_of
size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;
size_t find (char c, size_t pos = 0) const;
  • 即从pos位置开始,寻找目标出现的下标

  • rfind()find()使用类似,故不作赘述

例如:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1("hello world !!!");

	size_t pos1 = s1.find("ld");
	size_t pos2 = s1.find("l", 6);
	size_t pos3 = s1.find("nice");
	size_t pos4 = s1.rfind("!");

	cout << "pos1 =  " << pos1 << endl;
	cout << "pos2 =  " << pos2 << endl;
	cout << "pos3 =  " << pos3 << endl;
	cout << "pos4 =  " << pos4 << endl;

	return 0;
}

output:

pos1 =  9
pos2 =  9
pos3 =  4294967295
pos4 =  14

2.2.10 改

注:虽然string类有专门用于修改字符串的函数👉replace,但是由于效率原因并不常用。

实际使用中,一般都是用[]下标访问和迭代器访问来修改数据

2.2.11 c_str——获得C语言类型的字符串

const char* c_str() const;

例如:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1("hello world");
	const char* str = s1.c_str();

	cout << str << endl;

	return 0;
}

output:

hello world

2.2.12 substr——获得子串

string substr (size_t pos = 0, size_t len = npos) const;
  • 获得从pos位置开始,长度为len的子串
  • 同时将这个子串存储到string类中并进行返回

例如:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1("hello world");
	string s2 = s1.substr(0, 5);	//hello
	string s3 = s1.substr(5);		//world
	
	cout << s2 << s3 << endl;

	return 0;
}

output:

hello world

2.2 非成员函数

2.2.1 operator <</operator >>——流插入/流提取运算符重载

  • 有了<<流插入运算符重载,我们就可以利用std::cout来向屏幕打印string存储的字符序列
  • 有了>>流提取运算符重载,我们就可以用std::cinstring类的数据
  • 注意:
    • cin类似于C语言的scanf,如果碰到空白字符就会停止读取。因此cin只能用于读取不带空格的字符序列
    • 原来的数据会被输入端新字符给覆盖
    • 如果输入的字符长度大于capacity,那么就会对这个string对象进行扩容,直到可以存储输入的字符序列

例如:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1, s2;

	cin >> s1;
	cin >> s2;

	cout << "s1 = " << s1 << endl;
	cout << "s2 = " << s2 << endl;

	return 0;
}

input:

hello
nice to meet you

output:

s1 = hello
s2 = nice

2.2.2 getline——输入字符串

istream& getline (istream& is, string& str);
  • 同样是从标准输入流向string对象输入数据
  • 原来的数据会被输入端新字符给覆盖
  • getline()类似于C语言的gets()只有遇到换行才会停止读取。因此可以读取带空格的字符序列
  • 如果输入的字符长度大于capacity,那么就会对这个string对象进行扩容,直到可以存储输入的字符序列

例如:

#include <string>
#include <iostream>
using namespace std;

int main()
{
	string s1;
	getline(cin, s1);

	cout << s1 << endl;

	return 0;
}

output:

hello world

2.2.3 relational operators——关系运算符重载

(1)	

bool operator== (const string& lhs, const string& rhs);
bool operator== (const char*   lhs, const string& rhs);
bool operator== (const string& lhs, const char*   rhs);

(2)	

bool operator!= (const string& lhs, const string& rhs);
bool operator!= (const char*   lhs, const string& rhs);
bool operator!= (const string& lhs, const char*   rhs);

(3)	

bool operator<  (const string& lhs, const string& rhs);
bool operator<  (const char*   lhs, const string& rhs);
bool operator<  (const string& lhs, const char*   rhs);

(4)	

bool operator<= (const string& lhs, const string& rhs);
bool operator<= (const char*   lhs, const string& rhs);
bool operator<= (const string& lhs, const char*   rhs);

(5)	

bool operator>  (const string& lhs, const string& rhs);
bool operator>  (const char*   lhs, const string& rhs);
bool operator>  (const string& lhs, const char*   rhs);

(6)	

bool operator>= (const string& lhs, const string& rhs);
bool operator>= (const char*   lhs, const string& rhs);
bool operator>= (const string& lhs, const char*   rhs);

  • C++string类关系运算符的逻辑与C语言字符串比较函数strcmp的逻辑类似,故不再赘述

3. string类简单模拟实现

#include <iostream>
#include <assert.h>

namespace TESY
{
    class string
    {
    public:
        //构造函数
        string(const char* str = "")
			:_capacity(strlen(str))
			,_size(strlen(str))
		{
            assert(str);	//不能传入空指针    
                
			_str = new char[_size + 1];	//特别注意,这里开空间要多开一个给结束符'\0'留空间
			strcpy(_str, str);
		}
        //拷贝构造(深拷贝)
        string(const string& str)
		{
			char* temp = new char[str._capacity + 1];
			strcpy(temp, str._str);
			_str = temp;

			_capacity = str._capacity;
			_size = str._size;	
		}
		
        //赋值运算符重载(深拷贝)
        string& operator=(const string& str)
		{
			if (this != &str)
			{
				char* temp = new char[str._capacity + 1];
				strcpy(temp, str._str);

				delete[] _str;
				_str = temp;
				_capacity = str._capacity;
				_size = str._size;
			}

			return *this;
		}
		
        //尾插一个字符串
        void append(const char* str)
		{
			int len = strlen(str);
            
            //检查容量
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			strcpy(_str + _size, str);
			_size += len;
		}
		
        //尾插一个字符
        void push_back(const char c)
		{
            //检查容量
			if (_size >= _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
				reserve(newCapacity);
			}

			_str[_size] = c;
			_size++;
			_str[_size] = '\0';
		}
		
        //尾插
        string& operator+=(char c)
		{
			push_back(c);

			return *this;
		}
        string& operator+=(const char* str)
		{
			append(str);

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

			return *this;
		}

		//清空
        void clear()
		{
			delete[] _str;
			_size = _capacity = 0;
			_str = new char[1];
			_str[0] = '\0';
		}
		
        //交换两个string类的内容
        void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_capacity, s._capacity);
			std::swap(_size, s._size);
		}
		
        //返回C类型的字符串
        const char* c_str()const
		{
			return _str;
		}

        //返回长度
        size_t size()const
		{
			return _size;
		}
        //返回最大容量
        size_t capacity()const
		{
			return _capacity;
		}
        //判空
        bool empty()const
		{
			return _size == 0;
		}
		
        //修改容量
        void resize(size_t n, char c = '\0')
		{
			char* temp = new char[n + 1];
			int len = strlen(_str);

			if (n < len)
			{
				strncpy(temp, _str, n);
				temp[n] = '\0';
			}
			else
			{
				strncpy(temp, _str, len);
				for (int i = 0; i < n - len; i++)
					temp[len + i] = c;
				temp[n] = '\0';
			}

			delete[] _str;
			_str = temp;

			_size = n;
			_capacity = n;
		}
        
        //扩容
        void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* temp = new char[n + 1];
				strcpy(temp, _str);
				delete[] _str;

				_str = temp;

				_capacity = n;
			}
		}

        //下标访问
        char& operator[](size_t index)
		{
			assert(index <= _size);

			return _str[index];
		}
        const char& operator[](size_t index)const
		{
			assert(index <= _size);

			return _str[index];
		}
		
        //迭代器访问
        typedef char* iterator;
        typedef const char* const_iterator;

		iterator string::begin()
		{
			return _str;
		}
		iterator string::end()
		{
			return _str + _size;
		}
		const_iterator string::begin()const
		{
			return _str;
		}
		const_iterator string::end()const
		{
			return _str + _size;
		}


        //关系运算符重载
        friend bool operator<(const string& lhs, const string& rhs)
        {
            return strcmp(lhs._str, rhs._str) < 0;
        }
        friend bool operator<=(const string& lhs, const string& rhs)
        {
            return !(strcmp(lhs._str, rhs._str) > 0);
        }
        friend bool operator>(const string& lhs, const string& rhs)
        {
            return strcmp(lhs._str, rhs._str) > 0;
        }
        friend bool operator>=(const string& lhs, const string& rhs)
        {
            return (strcmp(lhs._str, rhs._str) < 0);
        }
        friend bool operator==(const string& lhs, const string& rhs)
        {
            return strcmp(lhs._str, rhs._str) == 0;
        }
        friend bool operator!=(const string& lhs, const string& rhs)
        {
            return strcmp(lhs._str, rhs._str) != 0;
        }

        // 返回c在string中第一次出现的位置
        size_t find(char c, size_t pos = 0) const
		{
			for (int i = pos; i < size(); i++)
			{
				if ((*this)[i] == c)
					return i;
			}

			return npos;
		}

        // 返回子串s在string中第一次出现的位置
        size_t find(const char* s, size_t pos = 0) const
		{
			char* ret = strstr(_str + pos, s);

			if (ret == nullptr)
				return npos;
			else
				return ret - _str;
		}

        // 在pos位置上插入字符c/字符串str
        string& insert(size_t pos, char c)
		{
			assert(pos <= _size);
			
            //检查容量
			if (_size >= _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
				reserve(newCapacity);
			}
			
            //挪动数据
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}

			_str[pos] = c;
			_size++;

			return *this;
		}
        string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			
            //检查容量
			int len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			
            //挪动数据
			size_t end = _size + len;
			while (end > pos)
			{
				_str[end] = _str[end - len];
				end--;
			}

			strncpy(_str + pos, str, len);
			_size += len;

			return *this;
		}
		
        //从pos位置开始删除len个字符
        string& erase(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}

			return *this;
		}
		
        //返回以从pos位置开始,长度为len的子串为内容的string对象
        string substr(size_t pos = 0, size_t len = npos) const
		{
			assert(pos < _size);

			string temp;

			size_t end = pos + len;
			if (len == npos || len + pos > _size)
				end = _size;

			temp.reserve(end - pos);
			temp._size = end - pos;
			strncpy(temp._str, _str + pos, end - pos);
			temp[end - pos] = '\0';

			return temp;
		}
		
        //析构
        ~string()
		{
			delete[] _str;
			_capacity = _size = 0;
		}
		
        //流插入
        friend std::ostream& operator<<(std::ostream& cout, const string& str)
        {
            cout << str._str;
        }

    private:
        char* _str;
        size_t _capacity;
        size_t _size;

        static const size_t npos = -1;
    };

    std::ostream& operator<<(std::ostream& cout, const string& str);
    bool operator<(const string& lhs, const string& rhs);
    bool operator<=(const string& lhs, const string& rhs);
    bool operator>(const string& lhs, const string& rhs);
    bool operator>=(const string& lhs, const string& rhs);
    bool operator==(const string& lhs, const string& rhs);
    bool operator!=(const string& lhs, const string& rhs);
};

本章完
如果本篇有任何错误或讲述不清的地方,欢迎各位在评论区讨论并指出
下一篇,我们将继续STL的学习——vector
请添加图片描述

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

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

相关文章

强化学习中的深度Q网络

深度 Q 网络&#xff08;Deep Q-Network&#xff0c;DQN&#xff09;是一种结合了深度学习和强化学习的方法&#xff0c;用于解决离散状态和离散动作空间的强化学习问题。DQN 的核心思想是使用深度神经网络来近似 Q 函数&#xff0c;从而学习复杂环境中的最优策略。 以下是 DQN…

二叉树进阶OJ题

目录 一、前序遍历非递归 二、中序遍历非递归 三、后序遍历非递归 四、二叉树转链表 五、二叉树的最近公共祖先 六、二叉树的层序遍历1 七、二叉树的层序遍历2 一、前序遍历非递归 题目描述&#xff1a;写出二叉树前序遍历的非递归形式。 链接&#xff1a;前序遍历 思…

css之svg 制作圆及旋转

1.代码 <template><div class"loading-box"><div class"circle-container"><svg width"75" height"75" class"move-left-to-right"><circle cx"37.5" cy"37.5" r"26&…

leetcode42接雨水问题

接雨水 题目描述 题目分析 核心思想&#xff1a; 代码 java版本&#xff1a; package com.pxx.leetcode.trapRainWaterDoublePoniter;public class Solution1 {public int trap(int[] height) {if (height.length 0) {return 0;}int n height.length;int left 0;int righ…

Linux之高级IO

目录 IO基本概念五种IO模型钓鱼人例子五种IO模型高级IO重要概念同步通信 VS 异步通信阻塞 VS 非阻塞其他高级IO阻塞IO非阻塞IO IO基本概念 I/O&#xff08;input/output&#xff09;也就是输入和输出&#xff0c;在著名的冯诺依曼体系结构当中&#xff0c;将数据从输入设备拷贝…

2023亚马逊云科技re:Invent,与全球合作伙伴探索更多发展可能

一年一度的全球云计算、科技圈的狂欢“Party”又双叒叕要来了&#xff01;2023年11月27日&#xff0c;2023亚马逊云科技re:Invent正式向全球云计算从业者、合作伙伴发出邀请&#xff0c;相聚拉斯维加斯&#xff0c;共同开启一场创新探索之旅&#xff01; 全球合作伙伴相约拉斯维…

ffmpeg开发 环境配置

ffmpeg开发简图 1 下载ffmpeg开发包 https://ffmpeg.org/download.html 包含三个版本&#xff1a;Static、Shared以及Dev Static --- 包含3个应用程序&#xff1a;ffmpeg.exe , ffplay.exe , ffprobe.exe&#xff0c;体积都很大&#xff0c;相关的DLL已经被编译到exe里面去…

【Java】ThreadPoolExecutor类参数简述

ThreadPoolExecutor类继承自AbstractExecutorService类&#xff0c;而AbstractExecutorService实现了ExecutorService接口 ThreadPoolExecutor类是Executor类中重要的实现类 1、ThreadPoolExecutor构造方法参数 在手册中&#xff0c; 一共有四种参数列表不同的构造方法。我们…

【文末送书】程序员如何化解35岁危机?

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

网易云音频数据如何爬取?

在当今数字化时代&#xff0c;音频数据的获取和处理变得越来越重要。本文将详细介绍如何使用Objective-C语言构建音频爬虫程序&#xff0c;以爬取网易云音乐为案例。我们将从Objective-C的基础知识开始&#xff0c;逐步深入到爬取思路分析、构建爬虫框架、完整爬取代码等方面&a…

android trace文件的抓取与查看方法

本地手机抓取trace 解压android trace文件的抓取与查看方法 找到config.pbtx文件&#xff0c;连接手机push进去 # push config.pbtx &#xff0c;/data/local/tmp/为自定义push到的目录 adb push config.pbtx /data/local/tmp/ adb shell # 抓取trace&#xff0c; /data/loc…

Redis原理之五种数据类型笔记

目录 String List Set ZSet ​ Hash String List Set ZSet Hash

CTF图片隐写

1.题目给出的zip文件给出提示如下。 2.用 ARCHPR爆破出密码。 3.解压后发现1.png&#xff0c;为图片隐写。 4.使用010editor打开图片&#xff0c;发现缺少png文件头。 010editor官方下载链接&#xff1a;sweetscape.com/download/010editor/ 5.添加文件头保存。 6.使用图片隐写…

vue3+highlight.js代码高亮插件使用

先安装 npm install highlight.jsmain.js中引入&#xff0c;并注册自定义指令 ..... import hljs from highlight.js window.hljs hljs import highlight.js/styles/atom-one-light.css import highlight.js/lib/common import mitt from mitt .....app.directive(highlight…

HTML5+CSS3+JS小实例:九宫格图片鼠标移入移出方向感知特效

实例:九宫格图片鼠标移入移出方向感知特效 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport&…

鲲鹏测评:电视盒子什么品牌好?双十二口碑电视盒子品牌排行榜

最近我们收到很多网友的私信不知道电视盒子怎么选&#xff0c;想要我们推荐双十二值得入手的电视盒子&#xff0c;本年度我们已经共计测评过18款电视盒子&#xff0c;通过对比测评结果&#xff0c;我们整理了电视盒子品牌排行榜&#xff0c;双十二想买电视盒子的朋友们可不要错…

充电桩绝缘检测原理与示例

1、背景 充电桩绝缘检测是保证电动车充电安全的重要环节&#xff0c;通过对充电桩绝缘检测单租的测量和评估&#xff0c;来判断充电桩是否存在漏电等安全隐患&#xff0c;从而保证用户及周围环境的电器安全。 绝缘电阻&#xff1a;是指在特定的条件下&#xff0c;电气设备与接…

【Python数据结构与算法】--- 递归算法的应用 ---[乌龟走迷宫] |人工智能|探索扫地机器人工作原理

&#x1f308;个人主页: Aileen_0v0 &#x1f525;系列专栏:PYTHON数据结构与算法学习系列专栏&#x1f4ab;"没有罗马,那就自己创造罗马~" 目录 导言 解决过程 1.建立数据结构 2.探索迷宫: 算法思路 递归调用的“基本结束条件” 3.乌龟走迷宫的实现代码: …

【兔子王赠书第9期】ChatGPT进阶:提示工程入门

文章目录 写在前面ChatGPT推荐图书关键点编辑推荐内容简介推荐理由 粉丝福利写在后面 写在前面 人类一直在寻找、制造并使用工具&#xff0c;以扩展我们的能力&#xff0c;适应我们的环境&#xff0c;甚至超越我们的生物限制。现在&#xff0c;我们正站在一个历史性的分水岭之…

Doris-Stream Load(二十六)

Stream load 是一个同步的导入方式&#xff0c;用户通过发送 HTTP 协议发送请求将本地文件或数据流导入到 Doris 中。Stream load 同步执行导入并返回导入结果。用户可直接通过请求的返回体判断本次导入是否成功。 适用场景 Stream load 主要适用于导入本地文件&#xff0c;或…