C++ string字符串的使用和简单模拟实现

目录

前言

1. string简介

2. string的使用和简单模拟实现

2.1 string类的定义

2.2 string(),~string()和c_str()

2.2 size,重载符号[ ],begin和end函数

2.3 push_back,reserve,append,+=运算符重载

2.4 insert和erase函数

2.5 find和substr函数

2.6 比较运算符的重载

2.7 cout<<和cin>>运算符重载

总结


前言

本文讲解string串的使用和一些简单的模拟实现,内容丰富,干货多多!


1. string简介

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数和字符串是分离的。不符合面向对象程序设计的思想,而且底层空间需要用户自己管理,如果不细心,容易访问越界。

所以C++标准库以string类来表示字符串,更加简单,方便。

  1. 字符串是表示字符序列的对象。
  2. 标准string类通过类似于标准字节容器的接口提供了对此类对象的支持,但添加了专门设计用于操作单字节字符串的特性。
  3. string类是basic_string类模板的实例化,该模板使用char(即字节)作为其字符类型,具有默认的char_traits和allocator类型(有关模板的更多信息,请参阅basic_string)。
  4. 请注意,该类处理字节独立于所使用的编码:如果用于处理多字节或变长字符序列(如UTF-8),则该类的所有成员(如length或size)及其迭代器仍将以字节(而不是实际编码的字符)进行操作。

2. string的使用和简单模拟实现

2.1 string类的定义

string类是本贾尼C++之父实现的,但是初次实现难免有许多不足,如接口函数过多,接口函数重载过多,导致string类十分复杂。我们对string类进行简单的模拟实现,不过是实现一些常用的接口函数,主要是粗浅地了解其中的原理。

  • 因为string这个容器专门针对字符,没有使用类模版,所以定义和声明需要分离,准备两个文件string.h和string.cpp。string.h存放类的声明,string.cpp各种类成员函数和变量的定义。
  • 为了不跟C++标准库里面的string发生命名冲突,可以将类放在命名空间中,并且这两个文件可以使用同一个命名空间,编译的过程中就会合并。
  • 因为string物理存储空间本质上是连续的,不是链表那种随机存储的。迭代器使用char*原生的字符指针就可以模拟,不过实际的string的迭代器基本是用类封装实现。
#define _CRT_SECURE_NO_WARNINGS 
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;

namespace Rustle
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

        //构造函数
		//string();
		string(const char* str = "");
        //拷贝构造函数
		string(const string& s);
        //赋值拷贝函数
		//string& operator=(const string& s);
		string& operator=(string tmp);
        //析构函数
		~string();

		void swap(string& s);
		const char* c_str() const;
		size_t size() const;

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

		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);

		bool operator<(const string& s)const;
		bool operator>(const string& s)const;
		bool operator<=(const string& s)const;
		bool operator>=(const string& s)const;
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;
		void clear();
	private:
		char* _str = nullptr;//置空
		size_t _size = 0;
		size_t _capacity = 0;

		const static size_t npos;
    };

	istream& operator>>(istream& is, string& str);
	ostream& operator<<(ostream& os, const string& str);
}
	

2.2 string(),~string()和c_str()

  • 构造函数如果使用第一种写法,将所有成员变量使用初始化列表进行初始化。一般来说是可以的,但是每一次都需要调用strlen这个库函数,会消耗时间。
  • 可能有的人会用第二种写法,交换一下初始化列表中的顺序,先将_size初始化,之后的成员变量直接使用_size就行。但是初始化列表初始化的顺序跟函数中初始化列表顺序无关,只跟成员变量声明的顺序有关。
  • 最好的解决方案就是第三种构造函数的写法,先使用初始化列表进行初始化,然后在函数内部进行动态开辟一块与str相同大小的空间,使用strcpy拷贝str字符串的内容,strcpy还会自动在字符串末尾加上斜杠0。
  • 需要注意的是,_size指的是字符串的大小,_capacity指的是斜杠0之前的字符个数,不包含斜杠0。所以之前_str中空间大小事_size+1,给斜杠0预留一个空间。
  • 析构函数先释放_str指向的空间,然后_str置为空指针,其他两个成员变量置为0。
  • 有些时候需要像C语言一样访问字符串,而string是一个类无法直接访问,c_str函数就是解决这种问题,这个函数可以获取_str指针,即第一个字符的地址。
namespace Greg
{
   //1.
    string::string(const char* str)
		:_str(new char[strlen(str) + 1])
        ,_size(strlen(str))
        ,_capacity(strlen(str))
	{
		assert(str);
		strcpy(_str, str);
	}

    //2.
    string::string(const char* str)
		:_size(strlen(str))
        ,_str(new char[_size + 1])
        ,_capacity(_size)
	{
		assert(str);
		strcpy(_str, str);
	}

	//全缺省构造函数
    string::string(const char* str)
		:_size(strlen(str))
	{
		assert(str);

		//初始化列表和函数内部初始化混合着用
		_str = new char[_size + 1];
		_capacity = _size;
		strcpy(_str, str);
	}

	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}

	const char* string::c_str() const
	{
		return _str;
	}
}

写一个测试函数,也放在Rustle命名空间中,这样就string前面不用加域名限制符。

namespace Rustle
{
	void test_string1()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;
    }
}

 运行结果如下:

2.2 size,重载符号[ ],begin和end函数

  • size是获取字符个数的函数,直接返回_size就好。
  • [ ]下标访问符,跟vector容器作用相似,访问pos下标的元素,需要先断言检查pos是不是在合理的范围,然后直接返回_str[pos]即可。不过返回类型是字符类型的引用,这样可以对该字符进行修改
  • begin函数是返回字符串的第一个字符的地址,还有一个const修饰函数,算是函数重载。因为string类对象可能也被const修饰。
  • end函数返回的是字符串最后一字符的下一个位置,由于下标是从0开始的,直接返回_str+_size即可。
    string::iterator string::begin()
	{
		return _str;
	}

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

	string::const_iterator string::begin() const
	{
		return _str;
	}

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

	size_t string::size() const
	{
		return _size;
	}

	char& string::operator[](size_t pos)
	{
		assert(pos < _size);
		return _str[pos];
	}

	const char& string::operator[](size_t pos) const
	{
		assert(pos < _size);
		return _str[pos];
	}
}

写一个测试函数,用下标访问,迭代器访问,还有范围for循环访问。范围for的底层就是需要识别有没有begin和end函数。

	void test_string1()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		for (size_t i = 0; i < s1.size(); i++)
		{
			cout << s1[i] << " ";
		}
		cout << endl;

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

		string::iterator it1 = s1.begin();
		while (it1 != s1.end())
		{
			cout << *it1 << " ";
			++it1;
		}
		cout << endl;

		const string s3("xxxxxx");
		string::const_iterator it2 = s3.begin();
		while (it2 != s3.end())
		{
			cout << *it2 << " ";
			++it2;
		}
		cout << endl;
	}

运行结果如下:

2.3 push_back,reserve,append,+=运算符重载

接口函数声明如下,其中+=运算符重载函数有两个重载,针对的是字符和字符串的。

		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);

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

  • reserve就是调整容量的函数。我们需要手动扩容。先开辟一个新容量大小的空间,然后使用strcpy库函数将原字符串内容拷贝到tmp指针指向的空间上,再释放_str指向的空间。让_str指针指向tmp指向的空间,修改_capacity的大小。
  • push_back函数是在字符串的末尾加上一个字符。首先,我们要判断字符串的容量是否足够。当_size和_capacity相等时,说明字符串容量已满,需要扩容。我们定义一个newcapacity变量,如果_capacity等于0,说明还没有开空间,先给四个字符大小的容量大小,如果不等于0,按两倍扩容。扩容之后,在_size下标位置添加ch字符,并且需要单独处理斜杠0,加在新字符的后一个位置。修改_size。
  • append函数在原字符串上追加新字符串,会覆盖原字符串。先定义len表示新字符串字符的个数,再判断加上新字符串后是否超过容量,超过容量要扩容。然后使用strcpy拷贝新字符串,再修改_size。
  • +=运算符重载针对字符和字符串的两个函数,分别复用push_back和append函数即可。需要返回*this。
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{   //给斜杠0预留一个位置
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;

			_str = tmp;
			_capacity = n;
		}
	}

	void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(newcapacity);
		}

		_str[_size] = ch;
		_str[_size + 1] = '\0';//单独处理斜杠0
		++_size;
	}

	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
		    reserve(_size + len);
		}

		strcpy(_str + _size, str);
		_size += len;
	}

    //复用push_back和append函数
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}

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

 

写个测试函数,测试刚刚是模拟实现的函数。

	void test_string2()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.push_back('x');
		cout << s1.c_str() << endl;

		s1.append("aaaaaa");
		cout << s1.c_str() << endl;

		s1 += 'y';
		cout << s1.c_str() << endl;

		s1 += "dfsdf";
		cout << s1.c_str() << endl;

	}

运行结果如下:

2.4 insert和erase函数

  • 上面是insert和erase函数的定义。insert函数从pos位置开始插入字符或者字符串,erase函数从pos位置开始,删除len个字符,其中len变量给了缺省值npos。
  • npos是无符号整数,现在令npos = -1。如果 size_t 是 32 位的,那么 npos 等于 2^32 - 1,即 4294967295。如果 size_t 是 64 位的,那么 npos 等于 2^64 - 1,即 18446744073709551615。总之是一个非常大的数字,表示直接到末尾。
class string
{
public:
    void insert(size_t pos, char ch);
	void insert(size_t pos, const char* str);
	void erase(size_t pos, size_t len = npos);

private:
	const static size_t npos;
}

  • npos是一个静态变量需要定义和声明分离。
  • 实现针对字符插入的insert函数。先判断是否需要扩容,然后需要挪动元素,当你定义一个无符号整型end变量时,尽量不要让无符号整数遇到大于等于或者小于等于符号,会有坑。
  • 因为如果你while循环继续的条件是end >= pos,并且此时pos等于0的情况下,你不断让end减1,当end减到0时,再次减去1会变成-1,如上面所说相当于 2^32 - 1,会造成无限循环。
  • 有两种解决方法,第一种就是不要出现等于符号,控制好end的位置。第二种是强转pos为int,这样使用等于判断就不会出现无限循环的情况。
  • 针对字符串插入的insert函数,使用上面第一种方法挪动元素,while循环继续的条件比较难写出来,需要画图理解。
  • 实现erase函数,先判断删除的字符个数和从pos位置的字符到结尾字符个数的大小关系。如果大于原字符的个数,直接将斜杠0放在pos位置的字符即可,在修改_size的大小。如果小于,需要挪动元素,可以使用strcpy将删除字符的最后一个位置拷贝到pos位置,就完成了删除和挪动的操作。
    const size_t string::npos = -1;

//1.
void string::insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}

	//size_t无符号整数遇到大于等于有坑
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	++_size;
}

//2.
void string::insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}

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

//1.
void string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);

	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	size_t end = _size + len;
	while (end > pos + len - 1)//!(pos + len - 1)
	{
		_str[end] = _str[end - len];
		--end;
	}
	memcpy(_str + pos, str, len);
	_size += len;
}

//2.
void string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);

	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	int end = _size;
	while (end >= (int)pos)
	{
	    _str[end + len] = _str[end];
		--end;
	}
	memcpy(_str + pos, str, len);
	_size += len;
}

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

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

写个测试函数。测试一下模拟实现的函数。

	void test_string3()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.insert(0, 'x');
		cout << s1.c_str() << endl;

		string s2("helloworld");
		s2.insert(5, "xxxx");
		cout << s2.c_str() << endl;

		s2.erase(5, 4);
		cout << s2.c_str() << endl;
	}

运行结果如下:

2.5 find和substr函数

函数原型如下,find函数是查找某个字符或者字符串的位置,查找到返回该字符的下标位置或者该字符串第一个字符的位置。如果没有找到返回-1,是一个极大的数。substr函数是从pos位置开始,取下原字符串的子串,返回一个string类的对象。

    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);

  • 查找字符,直接遍历整个字符串查找,找到返回下标,没找到返回-1。
  • 查找字符串,可以直接使用strstr库函数,或者使用其他查找子串的算法。
  • 实现substr函数,先判断子串字符个数是否小于从pos位置开始的字符个数。如果大于,直接拷贝pos位置的字符串。如果小于,创建一个string类的临时对象,先调整容量为len个,这样就不会在频繁扩容。然后使用for循环一个个加等。
	size_t string::find(char ch, size_t pos)
	{
		for (size_t i = 0; i < _size; i++)
		{
			if (_str[i] == ch)
				return i;
		}
		return npos;
	}

	size_t string::find(const char* str, size_t pos)
	{
		const char* end = strstr(_str, str);

		return end - _str;
	}

	string string::substr(size_t pos, size_t len)
	{
		//子串长度大于从原字符串给定位置开始到结束的长度,直接拷贝返回
		if (len > _size - pos)
		{
			string sub(_str + pos);
			return sub;
		}
		else
		{
			string sub;
			sub.reserve(len);
			for (size_t i = 0; i < len; i++)
			{
				sub += _str[pos + i];
			}

			return sub;
		}
	}

写一个测试用例,用于分割网址。

	void test_string4()
	{
		string s1("helloworld");
		cout << s1.find('o') << endl;
		cout << s1.find("orl") << endl;
		
		string url("https://legacy.cplusplus.com/reference");
		size_t pos1 = url.find(":");
		string url1 = url.substr(0, pos1);
		cout << url1 << endl;

		size_t pos2 = url.find('/', pos1 + 3);
		string url2 = url.substr(pos1 + 3, pos2 - (pos1 + 3));
		cout << url2 << endl;

		string url3 = url.substr(pos2 + 1);
		cout << url3 << endl;

	}

运行结果如下:

2.6 比较运算符的重载

	bool operator<(const string& s)const;
	bool operator>(const string& s)const;
    bool operator<=(const string& s)const;
	bool operator>=(const string& s)const;
	bool operator==(const string& s)const;
	bool operator!=(const string& s)const;

比较运算符,是比较字符的ASCii码值,可以写完<和==的逻辑,然后其他进行复用。

	bool string::operator<(const string& s)const
	{
		return strcmp(_str, s._str) < 0;
	}

	bool string::operator>(const string& s)const
	{
		return !(*this < s) && !(*this == s);
	}

	bool string::operator<=(const string& s)const
	{
		return *this < s || *this == s;
	}

	bool string::operator>=(const string& s)const
	{
		return *this < s || *this == s;
	}

	bool string::operator==(const string& s)const
	{
		return strcmp(_str, s._str) == 0;
	}

	bool string::operator!=(const string& s)const
	{
		return !(*this == s);
	}

2.7 cout<<和cin>>运算符重载

重载流插入<<和流提取>>这两个操作符,是为了方便打印和输入。并且这是放在全局的函数。

	istream& operator>>(istream& is, string& str);
	ostream& operator<<(ostream& os, const string& str);

  • 流插入<<函数容易实现,直接for循环遍历打印每个字符即可,不过你可以按照你的意愿打印任何形式。
  • 流提取<<函数比较难实现。首先写一个clear函数,清理掉之前的字符串里的字符,可以直接将斜杠0放在下标为0的位置,再修改_size就好了。
  • 首先,我们不能直接使用is >> ch来提取字符,因为一遇到空格或者换行就表示分割。可以使用is.get()函数完成输入操作。然后,我们先创建一个字符数组,输入的字符填到字符数组先,满了在加载字符串中,可以防止频繁扩容,带来的消耗。
	ostream& operator<<(ostream& os, const string& str)
	{
		for (size_t i = 0; i < str.size(); i++)
		{
			os << str[i];
		}

		return os;
	}

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

    istream& operator>>(istream& is, string& str)
	{
		//空格和换行表示多个值的分割
		//is >> ch; //scanf("%c", &ch);

		str.clear();
		int i = 0;
		char buff[128];
		char ch = is.get();

		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			//0~126的位置放字符了,留一个位置给斜杠0
			//减少频繁扩容
			if (i == 127)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}

			ch = is.get();
		}

		if (i != 0)
		{
			buff[i] = '\0';
			str += buff;
		}

		return is;
	}

写个测试函数。

	void test_string7()
	{
		//string s1("hello world");
		string s1;
		cout << s1 << endl;

		cin >> s1;
		cout << s1 << endl;
	}

 运行结果如下:

 


总结

通过本篇文章,相信你对string容器有了更深入的了解。现在你可以自己尝试实现一个简单的string容器,锻炼一下自己的代码能力。

创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!

ee192b61bd234c87be9d198fb540140e.png

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DDPM公式推导(三)

2 Background 扩散模型【53】是一种以 p θ ( x 0 ) : ∫ p θ ( x 0 : T ) d x 1 : T p_\theta\left(\mathbf{x}_0\right):\int p_\theta\left(\mathbf{x}_{0: T}\right) d \mathbf{x}_{1: T} pθ​(x0​):∫pθ​(x0:T​)dx1:T​ 形式的潜在变量模型&#xff0c;其中 x 1…

机器真的能思考、学习和智能地行动吗?

In this post, were going to define what machine learning is and how computers think and learn. Were also going to look at some history relevant to the development of the intelligent machine. 在这篇文章中&#xff0c;我们将定义机器学习是什么&#xff0c;以及…

BerkeleyDB练习

代码; #include <db.h> #include <stdio.h>int main() {DB *dbp;db_create(&dbp, NULL, 0);printf("Berkeley DB version: %s\n", db_version(NULL, NULL, NULL));dbp->close(dbp, 0);return 0; } 编译运行

Android studio在Ubuntu桌面上 创建桌面图标,以及导航栏图标

Android studio在Ubuntu桌面上 创建桌面图标&#xff0c;以及导航栏图标 1. 下载Android studio for Lunux 免安装版本之后&#xff0c;解压 2. 通过控制台运行 ~/Documents/android-studio-2024.1.1.2-linux/android-studio/bin$ ./studio.sh 3. 选择菜单&#xff0c;Tools…

1586. 扫地机器人

问题描述 Mike同学在为扫地机器人设计一个在矩形区域中行走的算法,Mike是这样设计的:先把机器人放在出发点 (1,1)(1,1) 点上,机器人在每个点上都会沿用如下的规则来判断下一个该去的点是哪里。规则:优先向右,如果向右不能走(比如:右侧出了矩形或者右侧扫过了)则尝试向…

基于51单片机的烟雾报警器设计-ADC0809

一.硬件方案 火灾报警器采用51单片机为核心控制器&#xff0c;利用气体传感器MQ-2、ADC0809模数转换器、DS18B20温度传感器等实现基本功能。通过这些传感器和芯片&#xff0c;当环境中可燃气体浓度或温度等发生变化时系统会发出相应的灯光报警信号和声音报警信号&#xff0c;以…

28.启动与暂停程序

上一个内容&#xff1a;27.设计注入功能界面 以它 27.设计注入功能界面 的代码为基础进行修改 点击添加游戏按钮之后就把游戏启动了 CWndINJ.cpp文件中修改&#xff1a; void CWndINJ::OnBnClickedButton1() {// TODO: 在此添加控件通知处理程序代码/*ExeLst.InsertItem(0, L…

Vue I18n国际化插件

Vue I18n国际化插件 安装目录结构及文件内容./locales/lang/zh.js./locales/lang/en.js./locales/index.js main.js引入页面具体使用及语言切换&#xff08;Vue3&#xff09;刷新保存原语言&#xff0c;App.vue添加路由守卫注意点 中文文档&#xff1a; https://kazupon.githu…

69. UE5 RPG 使用Gameplay Cue 实现技能表现效果

在上一章中&#xff0c;我们实现了敌人的攻击技能的特效和音效。如果我们在多人模式下打开&#xff0c;发现&#xff0c;其它客户端看不到对应的效果。 造成这种问题的原因是因为敌人的技能是运行在服务器端的&#xff0c;它只复制到拥有它的客户端&#xff0c;而敌人的效果对于…

英伟达与斯坦福携手,打造未来全息XR眼镜:头带时代的终结

在XR(扩展现实)技术的演进过程中,一个显著的挑战在于如何平衡设备的便携性与视觉体验。传统的XR设备由于需要厚重的头带固定光学器件和显示器,不仅增加了体积,还为用户带来了社交上的不便。然而,随着英伟达与斯坦福大学戈登韦茨斯坦教授领导的研究团队的合作,这一难题似…

meilisearch的分页

Elasticsearch 做为老牌搜索引擎&#xff0c;功能基本满足&#xff0c;但复杂&#xff0c;重量级&#xff0c;适合大数据量。 MeiliSearch 设计目标针对数据在 500GB 左右的搜索需求&#xff0c;极快&#xff0c;单文件&#xff0c;超轻量。 所以&#xff0c;对于中小型项目来说…

探地雷达正演模拟,基于时域有限差分方法,四

突然发现第三章后半部分已经讲了使用接收记录成像的问题&#xff0c;所以这一章只讲解简单的数据分析。 &#xff08;均以宽角法数据为例子&#xff0c;剖面法数据处理方式都是相同的&#xff09;假设&#xff0c;我们现在已经获得了一个GPR记录&#xff0c;可以是常用的.sgy格…

DAY03 HTML

文章目录 一 表格1. 表格的语法2. 表格的可选标记3. 不规则的单元格&#xff08;合并单元格&#xff09;4. 表格的属性5. 表格的大小 二 列表1. 有序列表2. 无序列表3. 属性4. 列表的嵌套5. 定义列表【了解】 三 表单(重点)1. 表单的语法2. 表单的控件分类3. input元素4. selec…

为什么说Python 是胶水语言?

​ "Python 是胶水语言"这一说法是指它很擅长将不同的程序或代码库连接在一起&#xff0c;能够让来自不同编程语言或框架的组件无缝协作。Python 具有丰富的库和简单的语法&#xff0c;使得它可以轻松调用其他语言编写的程序或使用不同技术栈的模块。 ​ 以下是几个…

如何区分人工智能生成的图像与真实照片(下)

4 功能上的不合理性 AI 生成的图像往往会因为缺乏对现实世界物体结构和相互作用的了解&#xff0c;而产生各种功能不合理之处。这些不合理之处主要表现在以下几个方面&#xff1a; 4.1 构图不合理 物体关系不合逻辑: AI 生成的图像中&#xff0c;物体和人物之间的关系可能不符…

哈希表、递归在二叉树中的应用-1372. 二叉树中的最长交错路径

题目链接及描述 1372. 二叉树中的最长交错路径 - 力扣&#xff08;LeetCode&#xff09; 题目分析 题目所述&#xff0c;计算在二叉树中交替遍历的最大深度【左->右->左】【右->左->右】&#xff0c;例如对于从当前根节点root出发&#xff0c;则此时遍历方向有两个…

持续集成jenkins+gitee

首先要完成gitee部署&#xff0c;详见自动化测试git的使用-CSDN博客 接下来讲如何从git上自动拉取代码&#xff0c;实现jenkins无人值守&#xff0c;定时执行测试&#xff0c;生成测试报告。 需要这三个安装包 由于目前的jenkins需要至少java11到java17的版本&#xff0c;所以…

JavaScript——初识:JavaScript的组成、输入和输出语句... | JavaScript基础:变量,数据类型转换

目录 初识JavaScript JavaScript的组成 输入和输出语句 ECMAScript 6保留关键字 变量的命名规范 注意事项 JavaScript基础 变量的数据类型 数据类型分类 数据类型转换 转换为字符串型 转换为数字型 转换为布尔型 例题 初识JavaScript JavaScript的组成 Java…

搭建自己的AI模型应用网站:JavaScript + Flask-Python + ONNX

1. 前言 本文作者以一个前端新手视角&#xff0c;部署自己的神经网络模型作为后端&#xff0c;搭建自己的网站实现应用的实战经历。目前实现的网页应用有&#xff1a; AI 语音服务主页AI 语音识别AI 语音合成AI CP号码生成器 欢迎大家试用感受&#xff0c;本文将以博客基于G…

大数据—“西游记“全集文本数据挖掘分析实战教程

项目背景介绍 四大名著&#xff0c;又称四大小说&#xff0c;是汉语文学中经典作品。这四部著作历久不衰&#xff0c;其中的故事、场景&#xff0c;已经深深地影响了国人的思想观念、价值取向。四部著作都有很高的艺术水平&#xff0c;细致的刻画和所蕴含的思想都为历代读者所…