【C++】string类的模拟实现

news2025/7/9 18:22:17

文章目录

    • 一、string类的构造、拷贝构造、赋值重载以及析构
      • 1.构造函数
      • 2.拷贝构造
      • 3.swap问题
      • 4.赋值重载
      • 5.析构函数
    • 二、常用接口
      • 1.c_str
      • 2.[]
      • 3.迭代器和范围for
      • 4.size和capacity
    • 三、插入
      • 1.reserve和resize
      • 2.push_back
      • 3.append
      • 4.+=
      • 5.insert
    • 四、删除
      • 1.erase
      • 2.clear
    • 五、查找
      • 1.find
    • 六、运算符重载
      • 流插入<<和流提取>>
    • 七、总体代码

一、string类的构造、拷贝构造、赋值重载以及析构

1.构造函数

分为无参和带参这两种构造函数。无参构造函数默认构造空字符串"",所以我们只需要给一个缺省值即可。

string(const char* str = "")
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
	strcpy(_str, str);
}

对于这里的capacity问题,这里是字符的个数,不包括\0,所以要给\0预留位置。

2.拷贝构造

对于拷贝构造和赋值是默认成员函数,不写编译器会自动生成,对于内置类型完成浅拷贝,对于自定义类型调用拷贝构造

对于string类型来说,如果不写拷贝构造会导致浅拷贝问题(只完成值拷贝)

image-20221116080701479

所以我们需要进行深拷贝:

  • 传统写法
string(const string& s)
		{
			_str = new char[s._capacity + 1];
			_capacity = s._capacity;
			_size = s._size;

			strcpy(_str, s._str);
		}
  • 现代写法

传统写法比较循规蹈矩,现代写法更加灵活,拷贝构造的现代写法可以通过构造出tmp,然后把tmp和s2进行交换(swap)

注意:我们需要把s2的_str置为nullptr,如果不置为空,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)
        :_str(nullptr)
        ,_size(0)
        ,_capacity(0)
        {
			string tmp(s._str);
			swap(tmp);//this->swap(tmp)
		}

3.swap问题

对于上面现代写法swap的问题:标准库有一个swap,string也有一个swap,有什么区别?

s1.swap(s2);
swap(s1,s2);

image-20221116100220826

image-20221116100242124

第二个swap交换代价比较大,需要三次深拷贝(拷贝+赋值+赋值),造成空间损耗,所以我们可以提供一个成员函数swap交换string,直接交换,swap中的swap要指定作用域std::,否则需要从局部找,再去全局找,发现参数不匹配

4.赋值重载

默认生成的赋值重载也会导致浅拷贝,所以我们需要实现深拷贝。同时,对于赋值重载,我们不要直接去进行销毁,有可能自己给自己赋值,导致自身进行销毁。同时,为了安全起见,我们最好利用tmp来进行赋值

  • 传统写法
string& operator =(const string& s)
{
    if (this != &s)
    {
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._str);
        delete[] _str;
        _str = tmp;
		_size = s._size;
		_capacity = s._capacity;
    }
    return *this;
}
  • 现代写法
string& operator = (const string& s)
{
    if (this != &s)
    {
        //string tmp(s._str);
		string tmp(s);
		swap(tmp);
    }
    return *this;
}

但是此方法仍然可以简化,不需要临时tmp,直接进行传值传参,更加简洁

	//直接传值传参
string& operator = (string s)
{
    swap(s);
    return *this;
}

5.析构函数

析构函数比较简单,直接delete[]释放空间即可

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

二、常用接口

下面几个常用的接口实现比较简单,我们先一起来看一看:

1.c_str

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

2.[]

//普通对象:可读可写
char& operator[](size_t pos)
{
    assert(pos < _size);
	return _str[pos];
}
//const对象:可读不可写
const char& operator[](size_t pos) const
{
    assert(pos < _size);
    return _str[pos];
}

3.迭代器和范围for

  • 迭代器

迭代器有普通迭代器以及const修饰的迭代器,所以我们可以实现两种不同的迭代器,其中,const迭代器可读不可写

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;
}
  • 范围for

实现完迭代器之后,对于范围for我们自然可以直接使用:

image-20221110093019376

4.size和capacity

直接返回值即可,比较简单

size_t size() const
{
    return _size;
}

size_t capacity() const
{
	return _capacity;
}

三、插入

1.reserve和resize

  • reserve

在已知开多少空间是调用,避免频繁扩容,具体实现要开辟新的空间,在进行拷贝,对旧空间进行释放

void reserve(size_t n)
{
    if (n > _capacity)
    {
        char* tmp = new char[n + 1];
		strcpy(tmp,_str);
		delete[] _str;
        _str = tmp;
		_capacity = n;
    }
}
  • resize

resize需要分情况:

1.元素个数大于容量,需要扩容,多出来的用’\0’(默认情况下)来进行填充

2.元素个数小于原有的,需要删除

void resize(size_t n, char ch = '\0')
{
    if (n > _size)
    {
        reserve(n);
        for (size_t i = _size; i < n; i++)
        {
            _str[i] = ch;
        }
        _size = n;
        _str[_size] = '\0';
    }
    else
    {
        _str[n] = '\0';
        _size = n;
    }
}

2.push_back

尾插一个字符,我们需要考虑扩容问题,我们需要判断capacity是否为0的情况,同时,尾插之后’\0’要重新处理

void push_back(char ch)
{
    //开辟2倍空间
    if (_size == _capacity)
    {
        size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);
    }
    _str[_size] = ch;
    ++_size;
    _str[_size] = '\0';
}

3.append

这里开多少空间取决于插入字符串的长度,我们需要计算,然后决定开多少空间(直接开2倍可能不够用)

//2倍不一定够用
void append(const char* str)
{
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
	}
    strcpy(_str + _size, str);
	_size += len;
}

4.+=

实现了push_back和append之后,对于+=来说,简直就是手到擒来,直接调用即可

//字符
string& operator+=(char ch)
{
    push_back(ch);
	return *this;
}
//字符串
string& operator+=(const char* str)
{
	append(str);
	return *this;
}

5.insert

insert的问题比较多

  • 插入字符

image-20221116125328193

image-20221110103116787

这里存在着一个很大的问题:

pos=0的时候,–end会变成-1(但是不要忽略了,end的类型是size_t,怎么可能是-1,此时有人会说了,可以把end改成int类型,但是实际上这样子会发生隐式类型提升,范围小往大的提升,也就是int会提升为size_t,还是没解决问题)这里太坑了,悄悄提升

所以解决的方式有两种:

1.强转

image-20221116125803359

2.把=号给去掉

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 end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end-1];
        --end;
	}
    _str[pos] = ch;
    ++_size;
    return *this;
}

image-20221116130041030

  • 插入字符串

插入字符串,要把插入的字符串拷贝过来,但是不要把’\0’顺便拷贝过来,所以不要用strcpy而是要用strncpy

同时,要防止越界问题

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)
    {
        _str[end] = _str[end - len];
        --end;
	}
    strncpy(_str + pos, str, len);
    _size += len;
	return *this;
}

四、删除

1.erase

说到erase,自然要跟npos联系起来,npos是string类的静态成员变量,静态成员变量要在类外定义的:

size_t string::npos = -1

普通成员对象可以给缺省值,在构造函数初始化列表完成初始化,但是静态成员变量不会在初始化列表阶段进行初始化,静态成员变量不属于某个具体的对象,属于整个类,所以需要在类外初始化

但是有一个**特例**,const静态成员变量可以在声明时定义(只针对整型):

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

		const static size_t npos = -1;

erase实现:

建议这个地方自己画个图辅助理解

1.如果len太长,直接把pos之后的删除即可

2.只需要删除部分,挪动数据

string& erase(size_t pos,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;
}

2.clear

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

五、查找

1.find

从pos处开始查找字符或者字符串,找到返回下标值,没找到则返回npos

对于字符串的查找可以调用strstr

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

六、运算符重载

流插入<<和流提取>>

对于流插入和流提取我们之前就在日期类接触了。不能重载成成员,会让this指针抢占第一个位置问题。所以需要定义成全局的

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

对于<<和c_str()的区别:<<按照size进行打印,跟\0没有关系,而c_str()遇到\0结束

image-20221110205012838

所以用流插入读取比较好一点。

  • >>

scanf和cin一样,都拿不到’ ‘和’\0’

所以要读取一个 一个的字符,我们可以用get函数

istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}

这个代码存在缺点:输入很长内容时,+=会大量扩容,效率降低

istream& operator>>(istream& in, string& s)
{
    s.clear();
    char buff[128] = { '\0' };
    size_t i = 0;
    char ch = in.get();
    while (ch != ' ' && ch != '\n')
    {
        if (i == 127)
        {
            s += buff;
            i = 0;
        }
        buff[i++] = ch;
        ch = in.get();
    }
    if (i >= 0)
    {
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

七、总体代码

#pragma once
#include <assert.h>
namespace hwc
{
	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()
		{
			_str = new char[1];
			_str[0] = '\0';
			_capacity = _size = 0;
		}*/
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		//s2(s1)
		//传统写法
		/*string(const string& s)
		{
			_str = new char[s._capacity + 1];
			_capacity = s._capacity;
			_size = s._size;

			strcpy(_str, s._str);
		}*/
		
		//现代写法

		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			swap(tmp);//this->swap(tmp)
		}

	/*	string& operator =(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/

		//现代写法
		//string& operator = (const string& s)
		//{
		//	if (this != &s)
		//	{
		//		//string tmp(s._str);
		//		string tmp(s);
		//		swap(tmp);
		//	}
		//	return *this;
		//}
		//直接传值传参
		string& operator = (string s)
		{
			swap(s);
			return *this;
		}
		~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对象,可读不可写
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp,_str);
				delete[] _str;

				_str = tmp;
				_capacity = n;
			}
		}

		void resize(size_t n, char ch = '\0')
		{
			if (n > _size)
			{
				reserve(n);
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				_str[n] = '\0';
				_size = n;
			}

		}
		//开辟2倍
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		//2倍不一定够用
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		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 end = _size+1;
			while (end >pos)
			{
				_str[end] = _str[end-1];
				--end;
			}
			_str[pos] = ch;
			++_size;
			return *this;
		}
		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)
			{
				_str[end] = _str[end - len];
				--end;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;
		}

		string& erase(size_t pos,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;
		}

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

		size_t find(const char ch, size_t pos = 0)
		{
			assert(pos < _size);
			while (pos < _size)
			{
				if (_str[pos] == ch)
				{
					return pos;
				}
				++pos;
			}
			return npos;
		}
		size_t find(const char* str, size_t pos = 0)
		{
			const char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		const static size_t npos = -1;
	};


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

	/*istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}*/
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char buff[128] = { '\0' };
		size_t i = 0;
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)
			{
				s += buff;
				i = 0;
			}
			buff[i++] = ch;
			ch = in.get();
		}
		if (i >= 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}



	void test_string1()
	{
		string s1("hello world");
		string::iterator it1 = s1.begin();
		while (it1 != s1.end())
		{
			cout << *it1 << " ";
			++it1;
		}
		cout << endl;
		for (auto ch : s1)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

	void test_string2()
	{
		string s1("hello");
		s1 += ' ';
		s1 += "world hello world";

		string s2;
		s2 += 'X';
	}

	void test_string3()
	{
		string s1("hello world");
		s1.insert(0, 'X');
		cout << s1.c_str() << endl;
		s1.insert(12, 'Y');
		cout << s1.c_str() << endl;
		s1.insert(13,"CWH");
		cout << s1.c_str() << endl;
		s1.insert(0, "hwc");
		cout << s1.c_str() << endl;

	}
	void test_string4()
	{
		string s1("hello world");
		s1.erase(3, 4);
		cout << s1.c_str() << endl;
	}

	void test_string5()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;
		s1.resize(16, 'X');
		cout << s1.c_str() << endl;

		string s2("hello world");
		s2.resize(5);
		cout << s2.size() << endl;
		cout << s2.capacity() << endl;
		cout << s2.c_str() << endl;
	}

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

		s1.insert(5, 'X');
		cout << s1.size() << endl;
		cout << s1.capacity()<< endl;

		cout << s1 << endl;
		cout << s1.c_str() << endl;

		string s2;
		cin >> s2;
		cout << s2 << endl;
	}

	void test_string7()
	{
		string s1("hello world");
		string s2(s1);

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

		string s3("HWC");
		s1 = s3;
		cout << s1 << endl;
		cout << s3 << endl;

		s1.swap(s2);
		swap(s1, s2);
		cout << s1 << endl;
		cout << s2 << endl;

	}
}

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

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

相关文章

Nginx

What is Nginx&#xff1f; Nginx 同 Apache 一样都是一种 Web 服务器。基于 REST 架构风格&#xff0c;以统一资源描述符&#xff08;Uniform Resources Identifier&#xff09;URI 或者统一资源定位符&#xff08;Uniform Resources Locator&#xff09;URL 作为沟通依据&…

基于51单片机的多功能时钟温度计proteus仿真原理图

本系统是由AT89S52单片机为控制核心&#xff0c;具有在线编程功能&#xff0c;低功耗&#xff0c;能在3V超低压环境中工作&#xff1b;时钟电路由内部时钟电路外接晶振提供&#xff0c;它是一种高性能、低功耗、带RAM的可随时调整时钟电路&#xff0c;工作电压为3V&#xff5e;…

数据中台与大数据、数据仓库、数据湖、BI的区别

一、什么是数据中台 数据中台是一种将企业沉睡的数据变成数据资产&#xff0c;持续使用数据、产生智能、为业务服务&#xff0c;从而实现数据价值变现的系统和机制。通过数据中台提供的方法和运行机制&#xff0c;形成汇聚整合、提纯加工、建模处理、算法学习&#xff0c;并以…

电源管理ISL95869HRTZ、ISL95808HRZ概述、规格和应用

ISL95869完全符合英特尔IMVP9规范&#xff0c;并为处理器的主输入轨道电源提供了完整的解决方案。它提供了一个电压调节器(VR)与两个集成和一个外部门驱动器。VR可以配置为3-&#xff0c;2-或1-相位&#xff0c;提供最大的灵活性。虚拟现实采用串行控制总线SVID (serial contro…

es环境搭建

1.es与es-head的搭建 1.1 es7.6.2 每个es都是自成一个集群&#xff0c;不同于solar还需要zk来搭建集群 1.1.1 下载安装 https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-6-2 因为占用内存实在是太大了&#xff0c;我在服务器上装了运行不起来&#xff…

Flameshot源码编译方法

一、简介 Flameshot是一款功能强大但易于使用的屏幕截图软件&#xff0c;中文名称火焰截图。Flameshot 简单易用并有一个CLI版本&#xff0c;所以你也可以从命令行来进行截图。Flameshot 是一个Linux发行版中完全免费且开源的截图工具。 二、在线安装 在线安装方法很简单&…

java基于web的自行车租赁系统ssh

目 录 摘 要 I Abstract II 第1章 绪论 1 1.1 课题背景 1 1.2 课题研究的意义 1 1.3 课题的目标 2 1.4 研究内容与章节安排 2 第2章 可行性分析 3 2.1 经济可行性 3 2.2 技术可行性 3 2.3 操作可行性 4 2.4法律可行性 4 2.5业务流程分析…

win10实现nfs文件共享II

文章目录&#xff08;一&#xff09;在服务器A设置共享目录&#xff08;二&#xff09;在客户端B安装nfs,挂载目录&#xff08;一&#xff09;在服务器A设置共享目录 步骤1&#xff1a;在D盘新建目录“nfs”,将其目录设置为共享目录。 步骤2&#xff1a;点击权限&#xff0c;设…

税票贷产品的准入与额度判断有哪些逻辑

近两周&#xff0c;番茄风控的课程中&#xff0c;涉及的税票贷产品课程干货满满。 今天我们再跟大家讲一下关于税票贷中风控的核心准入策略与额度判断有哪些逻辑是需要关注的&#xff1f; 先来说下税务的数据&#xff0c;然后再来讲下发票类型的数据。 一.关于税务的风控准入策…

PCB Layout爬电距离、电气间隙如何确定-安规

PCB Layout爬电距离、电气间隙如何确定 爬电距离&#xff1a;沿绝缘表面测得的两个导电零部件之间或导电零部件与设备防护界面之间的最短路径。 电气间隙&#xff1a;在两个导电零部件之间或导电零部件与设备防护界面之间测得的最短空间距离。即在保证电气性能稳定和安全的情况…

Redis真没那么难,这份大佬实战笔记也太可了,吹爆

Redis的技术全景 Redis一个开源的基于键值对&#xff08;Key-Value&#xff09;NoSQL数据库。使用ANSI C语言编写、支持网络、基于内存但支持持久化。性能优秀&#xff0c;并提供多种语言的API。 我们要首先理解一点&#xff0c;我们把Redis称为KV数据库&#xff0c;键值对数据…

图解LeetCode——775. 全局倒置与局部倒置(难度:中等)

一、题目 给你一个长度为 n 的整数数组 nums &#xff0c;表示由范围 [0, n - 1] 内所有整数组成的一个排列。 全局倒置 的数目等于满足下述条件不同下标对 (i, j) 的数目&#xff1a; 0 < i < j < nnums[i] > nums[j]局部倒置 的数目等于满足下述条件的下标 i 的…

【Opencv实战】识别水果的软件叫什么?一款超好用的识别软件分享,一秒鉴定(真是活~久~见~啊)

导语 Hello&#xff0c;大家好呀&#xff01;我是木木子吖&#xff5e; 一个集美貌幽默风趣善良可爱并努力码代码的程序媛一枚。 听说关注我的人会一夜暴富发大财哦——不信你试试&#xff01; 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&…

redis(二)

一、短信登录 1.1导入黑马点评项目 导入黑马点评项目 首先&#xff0c;导入课前资料提供的SQL文件 其中的表有&#xff1a; tb_user&#xff1a;用户表tb_user_info&#xff1a;用户详情表tb_shop&#xff1a;商户信息表tb_shop_type&#xff1a;商户类型表tb_blog&#xf…

Golang入门(1)—— helloworld 初体验

没有多少雄心壮志&#xff0c;就是想在B站上跟一个视频&#xff0c;写一个helloworld 。 还是老配方&#xff0c;还是IDEA&#xff0c;简单的下载了一个go插件之后&#xff0c;就可以new go文件了。然后根据提示下载了一个最新的go版本&#xff0c;设置好环境变量。写了如下&am…

Redis事务相关源码探究

文章目录Redis事务源码解读1、MULTI 声明事务2、命令入队3、EXEC 执行事务4、WATCH 监听变量Redis事务源码解读 源码地址&#xff1a;https://github.com/redis/redis/tree/7.0/src 从源码来简单分析下 Redis 中事务的实现过程 1、MULTI 声明事务 Redis 中使用 MULTI 命令来…

Ubuntu18.04 安装完成后的开发配置

Ubuntu18.04 安装完成后的开发配置1 终端工具2 屏幕录制工具3 屏幕截图工具4 向日葵远程操作与开机自启动设置5 MarkDown笔记工具6 ROS 机器人操作系统安装7 编程开发工具8 机器人仿真环境Gazebo 安装Webots 安装CoppeliaSim 安装Mujuco 安装Pybullet 安装测试平台&#xff1a;…

【c#】前后端分离练习小项目学习笔记----纯干货

c#实现前端页面CRUD结果图涉及到的技术设计流程代码展示总结结果图 涉及到的技术 1、HttpClient请求网络 2、webapi接口用法 3、sqlsugar数据库使用 4、Json序列化、反序列化 设计流程 刚开始做这个小项目&#xff0c;很懵。按照平常中项目的写法&#xff0c;就是先配置好数据…

模式识别与机器学习(更新中)

模式识别与机器学习 使用的教材&#xff0c;PPT为1 公式推导部分直接去看白板推导2&#xff0c;不管是书上还是ppt都写的极其模糊 先说重点&#xff1a; 贝叶斯算概率参数估计 第二讲 贝叶斯学习基础 贝叶斯公式 先验概率是非条件概率 似然概率是给定变量的条件下观测变…

贤鱼的刷题日常--P1665 正方形计数--题目详解

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;学习了解–P1665 正方形计数 ✅创作者&#xff1a;贤鱼 ⏰预计时间&#xff1a;5分钟 &#x1f389;个人主页&#xff1a;贤鱼的个人主页 &#x1f525;专栏系列&#xff1a;c &#x1f341;贤鱼的个人社区&#xff0c;欢迎…