string类模拟实现(c++)(学习笔记)

string

  • 1.构造函数
    • 1.1 不带参构造
    • 1.2 带参数的构造函数
    • 1.3 合并两个构造函数。
  • 2. 析构函数
  • 3.拷贝构造函数
  • 4. 赋值运算符重载
  • 5. size()/capacity()
  • 6. 解引用[]
  • 8.iterator迭代器
  • 7.Print()
  • 8.> ==
  • 8. push_back()&append()
    • 8.1 reserve()
  • 9. +=
  • 10.insert()
    • 10.1 任意位置插入一个字符
    • 10.2 在任意位置插入字符串
  • 11. resize()
  • 12 erase()
  • 13. swap()
  • 14.find()
    • 14.1 查找字符
    • 14.2 查找字符串
  • 15 <<和>>
    • 15.1 流提取
    • 15.2 流插入

框架:

namespace abc
{class string{private:char* _str;  size_t _size;  //有效字符大小size_t _capacity; //总容量};}

1.构造函数

因为string类构造函数有多种形式,这里只实现两个最常用的。不带参数的,带参数的。

1.1 不带参构造

示例1·:cout对空指针解引用报错

		string():_str(nullptr),_size(0),_capacity(0){}char* c_str(){return _str;}void Test1(){abc::string s1;cout << s1.c_str() << endl;//该行报错}

报错原因:首先s1的字符串指向的为空指针。c_str会返回一个c格式的字符串,但是cout<<s1.c_str()会自动识别类型,识别为字符串类型,打印就会解引用。造成空指针访问报错。
改正:如果换成标准库里的string就不会报错,因为它赋的不是空指针,是空字符串。

改1:
在这里插入图片描述
如果这样直接赋值的化,成员变量_str为非const变量,会出现权限放大的错误。这样做也不可行。

改2:
申请一个字符的空间,然后函数体里面初始化。

		string():_str(new char[1]), _size(0), _capacity(0){_str[0] = '\0';}

修改成这样就可以了。这样后续既可以修改字符串内容,也可以打印空字符串。

1.2 带参数的构造函数

示例2:

		string(const char* str)   //加const是因为常量字符串必须用const接收,不然会在传参时出错:_str(str)   //     *******该行会报错,_size(strlen(str)),_capacity(strlen(str)+1){}void Test1(){abc::string s2("hello world");cout << s2.c_str() << endl;}

报错原因:
因为str为const类型,而成员变量_str为非const类型,赋值产生权限放大。给_str加const不可取,会导致后续没法修改字符串内容。

改1:还是开空间,能存上常量字符串(”hello“),并且还能保证能修改内容,初始化列表初始化不方便,选择在函数体初始化。

		string(const char* str): _size(strlen(str)){_capacity = _size;//容量就是能装有效字符的个数_str = new char[_capacity + 1]; //开的空间要多包含一个\0strcpy(_str, str);//拷贝字符串内容}

1.3 合并两个构造函数。

看起来是两个无参有参的构造函数,其实可以合并成一个,因为第一个无参的就是一个空字符串。

示例:

		string(const char* str = "")  //使用缺省函数来合并: _size(strlen(str)){_capacity = _size;//容量就是能装有效字符的个数_str = new char[_capacity + 1]; //开的空间要多包含一个\0strcpy(_str, str);//拷贝字符串内容}

2. 析构函数

构造函数写完对应写析构函数,只需要保证new和delete符号匹配即可。
示例:

		//析构函数~string(){delete[] _str;  //都用带括号的_str = nullptr;_size = _capacity = 0;}

3.拷贝构造函数

拷贝构造函数逻辑上不难。
示例:

		//拷贝构造函数string(const string& str):_size(str._size), _capacity(str._capacity){_str = new char[_capacity + 1];strcpy(_str, str._str);}void Test1(){abc::string s1;abc::string s2("hello world");abc::string s3(s1);abc::string s4(s2);cout << s1.c_str() << endl;cout << s2.c_str() << endl;cout << s3.c_str() << endl;cout << s4.c_str() << endl;}

程序运行正确,没有报错。

4. 赋值运算符重载

示例:(经典标0)

		//赋值运算符重载string& operator=(const string& s){	_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];strcpy(_str, s._str);return *this;}void Test2(){abc::string s1;abc::string s2("hello world");abc::string s3("i love you peter");s2 = s3;cout << s1.c_str() << endl;cout << s2.c_str() << endl;cout << s3.c_str() << endl;}

问题:
1.首先s2的空间没有释放,导致内存泄露问题。
2. 没有考虑拷贝多少的问题,比如相等空间可以i直接赋值,大空间给小空间,小空间给大空间的问题。

优化1:首先要将s2的内存释放掉,然后申请一块新空间,赋给s2。(这样可以不用考虑原因2的三种情况,简化逻辑)

		string& operator=(const string& s){delete[] _str;//释放空间_str = new char[s._capacity + 1]; //申请新空间strcpy(_str, s._str);   //拷贝_size = s._size;_capacity = s._capacity;return *this;}

问题:
3.如果内存申请失败,该版本会弄丢s2的原有值。

优化2:
使用临时变量开空间,开成功再赋回去。

		string& operator=(const string& s){char* tem = new char[s._capacity + 1]; //先申请新空间strcpy(tem, s._str);   //拷贝//没有抛异常,往下执行delete[] _str;//释放空间_str = tem;   //赋回来_size = s._size; _capacity = s._capacity;return *this;}

问题:如果自己给自己赋值,原地不动就好了。
优化3:

		string& operator=(const string& s){if (this != &s){char* tem = new char[s._capacity + 1]; //先申请新空间strcpy(tem, s._str);   //拷贝//没有抛异常,往下执行delete[] _str;//释放空间_str = tem;   //赋回来_size = s._size;_capacity = s._capacity;}return *this;}

5. size()/capacity()

示例:

		size_t size(){return _size;}size_t capacity(){return _capacity;}void Test3(){const abc::string s1;const abc::string s2("hello world");abc::string s3("i love you peter");cout << s1.size() << endl;   //报错cout << s2.capacity() << endl;  //报错}

问题:调用的两个函数,都出现了权限放大的错误。
改正:给this加上const即可。

		size_t size() const{return _size;}size_t capacity() const{return _capacity;}

6. 解引用[]

示例:

		char& operator[](size_t pos){assert(pos < _size);  //pos位置得合法return _str[pos];}void Test3(){abc::string s2("hello world");for (size_t i = 0; i < s2.size(); i++){cout << (s2[i]) << " ";}}

问题:如果const对象解引用,会产生权限放大的错误,得把this加const,解决问题。但const对象返回值也得是const(防止对象被修改)。这就与非const对象产生矛盾。

改正:再次重载一个适合const对象的引用函数,即可解决问题。(运算符重载yyds)

		char& operator[](size_t pos){assert(pos < _size);  //pos位置得合法return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);  //pos位置得合法return _str[pos];}

8.iterator迭代器

迭代器是一种比较方便的访问有序对象的一种通用方法。
示例:

	public://迭代器typedef char* iterator;public:iterator begin(){return _str;}iterator end(){return _str + _size;}
//测试代码void Test4(){abc::string s2("hello world");abc::string::iterator it = s2.begin();while (it != s2.end()){cout << *it << " ";it++;}cout << endl;// 范围forfor (auto ch : s2){cout << ch << " ";}}

解释:因为范围for的底层就是迭代器,如果迭代器底层实现好了的话,范围for也可以用。

问题:这个代码同样仅仅考虑了非const的问题,因此再写一组重载。

改:加一组const迭代器就可以了。

	public://迭代器typedef const char* const_iterator;  //const型public:const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}void Test4(){const abc::string s2("hello world");abc::string::const_iterator it = s2.begin();while (it != s2.end()){cout << *it << " ";it++;}cout << endl;// 范围forfor (auto ch : s2){cout << ch << " ";}}

7.Print()

该函数实现不难,仅仅需要注意区分const和非const即可。
示例:

		void Print()const{// 范围forfor (auto ch : *this){cout << ch << " ";}}void Test5(){const abc::string s2("hello world");s2.Print();}

因为前面已经实现好const和非const迭代器,这样Print函数,const对象调用const迭代器,非const对象调用非const迭代器。能成功运行。

8.> ==

string类的比大小遵循c语言的方式,使用strcmp复现。
示例:注意const和非const对象。

		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 _str > s._str || _str == s._str;}bool operator<(const string& s)const{return !(_str>=s._str);}bool operator<=(const string& s)const{return _str < s._str || _str == s._str;}bool operator!=(const string& s)const{return !(_str==s._str);}

注意:仅仅写出来>+==就可以把其他都复用出来了。

8. push_back()&append()

该算法实现一个字符串增和一个字符增。
示例:

void reserve(size_t n){char* tem = new char[n + 1];//多开一个空间存\0strcpy(tem, _str);delete[] _str;_str = tem;_capacity = n;}void append(const char* str){size_t len = strlen(str);//需要扩容if(_size + len > _capacity){//防止new_capacity比len小size_t new_capacity = (2 * _capacity) > (_size + len) ? (2 * _capacity) : (_size + len);reserve(new_capacity);}strcpy(_str + _size, str);_size += len;}void push_back(char ch){//容量不够需要扩容if (_size + 1 > _capacity){reserve(2 * _capacity);}_str[_size++] = ch;  _str[_size] = '\0';  // 别忘了\0}void Test7(){abc::string s2("hello world");const char ch = 'a';s2.push_back(ch);s2.append("xxxxaaa");}

问题:但是当字符串为空的时候,如果添加字符的话,push_back会报错,因为capacity为0,导致扩容还是0。

改:为了简单起见,我们只需要保证capacity不为0即可,可以在构造函数中修改。如下:

		string(const char* str = ""): _size(strlen(str)){_capacity = (_size == 0 ? 3 : _size);//容量就是能装有效字符的个数_str = new char[_capacity + 1]; //开的空间要多包含一个\0strcpy(_str, str);//拷贝字符串内容}

这样即可解决问题。

8.1 reserve()

该函数的实现发方法仍然有一些问题,就是当要保留的空间小于原有的空间。函数不做改动。
改:加一个判断语句即可。

		void reserve(size_t n){if (n > _capacity){char* tem = new char[n + 1];//多开一个空间存\0strcpy(tem, _str);delete[] _str;_str = tem;_capacity = n;}}

9. +=

使用上面的append和push,实现此函数轻而易举。
示例:

		string& operator+=(const char* str){this->append(str);return *this;}string& operator+=(char ch){this->push_back(ch);return *this;}void Test7(){abc::string s2("hello world");const char ch = 'a';//s2.push_back(ch);//s2.append("xxxxaaa");s2 += "hello world";s2 += 'a';}

10.insert()

10.1 任意位置插入一个字符

示例:
在pos位置插入一个字符ch。

		//在第pos位置插入void insert(size_t pos, char ch){//位置合法assert(pos <= _size);if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t  end = _size - 1;while (end >= pos){_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;}

问题:
1.插入一个字符串后,结尾没加\0。
2.最好不要用end>=pos,因为都是无符号数,当pos和end同时等于0,end–,end变成了-1(但是是无符号数),因此会导致越界访问。

改正:
1.不能让end等于0,让end到1就结束,从前往后赋值。
2.最后加上\0。

		void insert(size_t pos, char ch){//位置合法assert(pos <= _size);if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t  end = _size;while (end > pos)  {_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;_str[_size] = '\0';  //没有写\0}

10.2 在任意位置插入字符串

示例:

		//插入字符串void insert(size_t pos, const char* str){assert(str);assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t new_capacity = (2 * _capacity) > (_size + len) ? (2 * _capacity) : (_size + len);reserve(new_capacity);   }//从后往前一个字符一个字符移动,记得最后不\0,到插入位置结束size_t end = _size - 1;while (end >= pos&&end!=-1){_str[end + len] = _str[end];end--;}int i = 0;  //计数size_t count = len;while (count--){_str[pos++] = str[i++];}_size += len;_str[_size] = '\0';}

问题:
1.同样是end和pos的关系,应该从前往后赋值,然后让后面的作为结束条件,这样就防止end越界了。

改正:
1.使用后面的下标作为结束条件,值得学习。
2.挪动完数据后,需要拷贝,可以使用strncpy进行拷贝。

				//插入字符串void insert(size_t pos, const char* str){assert(str);assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t new_capacity = (2 * _capacity) > (_size + len) ? (2 * _capacity) : (_size + len);reserve(new_capacity);}//从后往前一个字符一个字符移动,记得最后不\0,到插入位置结束size_t end = _size + len;while (end > pos+len-1){_str[end] = _str[end - len];end--;}//按字节拷贝strncpy(_str + pos, str, len);_size += len;}

11. resize()

当容量小于size时,直接赋\0;当容量>size&&<capacity时,直接填充指定字符;当容量>capacity时,先扩容再填充指定字符。
示例:

	//比_size小,就直接阶段、比_capacity大就用\0填充void resize(size_t n, char ch = '\0'){if (n <= _size){_str[n] = '\0';}//需不需要异地扩容else{if (n <= _capacity){memset(_str + _size, ch, n - _size);//最后再加上\0;_str[n] = '\0';}else{//char* tem = new char[n+1];  //要多申请一个空间//strcpy(tem, _str);//delete[] _str;//_str = tem;reserve(n);memset(_str + _size, ch, n - _size);_str[n] = '\0';_capacity = n;}}_size = n;}

优化:
此代码逻辑有些冗余,可以修改成如下代码,更加简洁。

				//比_size小,就直接阶段、比_capacity大就用\0填充void resize(size_t n, char ch = '\0'){if (n <= _size){_str[n] = '\0';}//需不需要异地扩容else{if (n > _capacity){reserve(n);_capacity = n;}memset(_str + _size, ch, n - _size);_str[n] = '\0';}_size = n;}

12 erase()

擦除给定位置的n个字符。

		// pos为位置void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos||len>=_size-pos){_str[pos] = '\0';_size = pos + 1;}else{size_t end = pos;while (end + len <= _size){_str[end] = _str[end + len];end++;}_size -= len;}}

问题:else的代码有一些冗余,可以使用strcpy来简化

优化:

				// pos为位置void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || len >= _size - pos){_str[pos] = '\0';_size = pos + 1;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}

13. swap()

交换两个string对象的内容。

		//1.   swap(s1,s2)//2.   s1.swap(s2)void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}

对于两种swap的方式,很明显第二种更高效,第一种需要拷贝构造,第二种自己实现的方式则不需要。

14.find()

14.1 查找字符

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

14.2 查找字符串

使用strstr()库函数,复现。

		size_t find(const char* str, size_t pos = 0){assert(str);char* p = strstr(_str + pos, str);   //查找字符串的库函数if (p == nullptr){return npos;}else{return p - _str;}}

15 <<和>>

15.1 流提取

因为this指针的原因,不能将其定义为成员函数。

	ostream& operator<< (ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}void Test8(){abc::string s1("hello world");s1 += '\0';s1 += "aaaaaaaaaa";cout << s1 << endl;  //hello worldaaaaaaaaaacout << s1.c_str() << endl; //hello world}

注意:这里要简单提一下:为什么两个输出的函数不一样。
第一个s1是我们自己实现的函数,它是根据字符串的个数打印的。
而第二个s1.c_str返回的是指针,编译器会根据指针来打印,遇到\0就停止!

15.2 流插入

示例:

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

报错:输入“hello world”
该代码不能完成功能,因为in>>ch,读不进去空格和回车。所以根本跳不出循环。

改正:换一种方式获取缓冲区数据。使用get()函数。

	istream& operator>> (istream& in, string& s){char ch = in.get();   //可以读到空格while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}

问题:1.频繁+=会导致频繁扩容。
2.连续两次读取数据不会清除第一次的数据。

优化:1.使用缓冲区的概念,一下+=一个缓冲区。
2.每次提取之前,要清空字符。

void clear(){_str[0] = '\0';_size = 0;}istream& operator>> (istream& in, string& s){s.clear();  //每次读取前,清空schar buf[128];  //申请一个缓冲区char ch = in.get();int i = 0;while (ch != ' ' && ch != '\n'){buf[i++] = ch;if (i == 127)  //剩一个位置给\0{buf[i] = '\0';s += buf;i = 0;}ch = in.get();}if (i != 0)   //缓冲区有内容,再加上{buf[i] = '\0';s += buf;}return in;}

以上就是string类部分库函数实现。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

把微信好友不小心删了,有什么办法找回?

常见的重新加回微信好友有以下几种&#xff1a; ①通过微信群找回&#xff0c;看有无共同的群&#xff0c;重新将对方加回来 ②通过朋友圈的动态找回 ③如果是加不久的好友&#xff0c;可以在微信新的朋友功能里重新添加 ④通过共同好友&#xff0c;让朋友圈推荐好友名片找…

微信小程序--数字化会议OA系统之首页搭建

一、Flex弹性布局 布局的传统解决方案&#xff0c;基于盒状模型&#xff0c;依赖 display属性 position属性 float属性。它对于那些特殊布局非常不方便&#xff0c;比如&#xff0c;垂直居中就不容易实现。 2009年&#xff0c;W3C提出了一种新的方案—-Flex布局&#xff0c;可…

【10】基础知识:React - DOM的diffing算法

一、虚拟 DOM 中 key 的作用 react/vue 中的 key 有什么作用&#xff1f;key的内部原理是什么&#xff1f; 简单来说&#xff1a; key 是虚拟 DOM 对象的标识&#xff0c;在更新显示时 key 起着极其重要的作用&#xff0c;提高渲染效率&#xff0c;防止渲染错误。 详细的说…

如何通过Photoshop将视频转换成GIF图片

一、应用场景 1、将视频转有趣动图发朋友圈 2、写CSDN无法上传视频&#xff0c;而可以用GIF动图替代 3、其他 二、实现步骤 1、打开Photoshop APP 2、点击文件——导入——视频帧到图层 3、选择视频文件 4、配置视频信息&#xff0c;按照图片提示配置完毕之后点击确定&…

入门小白拥有服务器的建议

学习网络知识 当我们拥有了一台服务器以后,需要提前准备学习一些网络、服务器、互联网方便的知识, 以备在后续学习工作中使用。 建议的网络知识学习清单: 1. 网络基础知识:包括网络拓扑结构、协议、IP地址、子网掩码、网关等基础概念。 2. 网络安全:包括网络攻击类型、防…

Spring中注入的使用

目录 一、什么是注入&#xff08;Injection&#xff09; 1.1 为什么要注入 二、注入的基本使用 三、Spring注入原理分析 一、什么是注入&#xff08;Injection&#xff09; 注入就是通过Spring的配置文件&#xff0c;为所创建对象的成员变量进行赋值 1.1 为什么要注入 书接上…

idea dubge 详细

目录 一、概述 二、debug操作分析 1、打断点 2、运行debug模式 3、重新执行debug 4、让程序执行到下一次断点后暂停 5、让断点处的代码再加一行代码 6、停止debug程序 7、显示所有断点 8、添加断点运行的条件 9、屏蔽所有断点 10、把光标移到当前程序运行位置 11、单步跳过 12、…

wps/word 之 word中的两个表格 如何合并成为一个表格(已解决)

第一步&#xff1a;新建两个表格&#xff1a; 如何实现上面的两个表格合并呢&#xff1f; 分别选定每个表格&#xff0c;然后鼠标右键---》表格属性 在表格属性中的 表格---》选择 无文字环绕。 第二个表格按照同样的方法 设置 无文字环绕。 然后将中的文本行删去即可以了。选…

提升品牌形象:利用OLED透明拼接屏进行品牌展示

在当今数字化时代&#xff0c;OLED透明拼接屏作为一项引人注目的新兴技术&#xff0c;正逐渐改变着各行各业的显示方式。 OLED透明拼接屏技术 OLED透明拼接屏采用有机发光二极管&#xff08;OLED&#xff09;技术&#xff0c;能够提供卓越的显示效果。 与传统的液晶显示屏相比…

喜报!迅镭激光荣膺“江苏省智能制造领军服务机构”!

近日&#xff0c;“2023江苏省智能制造领军服务机构”名单揭晓&#xff0c;迅镭激光凭借在智能制造领域的强劲实力和突出的行业影响力位列其中&#xff0c;摘得该项殊荣。 近年来&#xff0c;智能制造正在成为全球传统工业和制造业转型升级的主要方向&#xff0c;越来越多的企业…

C++中的智能指针

智能指针是在 C 14 C14 C14中新引入的&#xff0c;所以在编译的时候最好加入 " − s t d c 14 " "-stdc14" "−stdc14"的编译选项。智能指针一共有两种&#xff0c;分别是 u n i q u e _ p t r unique\_ptr unique_ptr和 s h a r e d _ p t…

试着写几个opencv的程序

一、认识opencv OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源计算机视觉库&#xff0c;旨在提供丰富的图像处理和计算机视觉功能&#xff0c;以帮助开发者构建视觉应用程序。OpenCV最初由英特尔开发&#xff0c;现在由社区维护和支持。它支持…

【UE4 反射系统】 UCLAS UFUNCTION UPROPERTY 宏简单解析 持续更新

目录 0 引言1 C如何实现反射机制1.1 使用代码生成工具实现反射机制 2 UE4的反射系统2.1 ****.generated.h头文件2.2 GENERATED_BODY()2.3 反射宏 UCLASS 等2.4 UHT和UBT 3 基本宏的使用3.1 UCLASS3.2 UFUNCTION3.3 UPROPERTY &#x1f64b;‍♂️ 作者&#xff1a;海码007&…

Axure RP医疗在线挂号问诊原型图医院APP原形模板

医疗在线挂号问诊Axure RP原型图医院APP原形模板&#xff0c;是一款原创的医疗类APP&#xff0c;设计尺寸采用iPhone13&#xff08;375*812px&#xff09;&#xff0c;原型图上加入了仿真手机壳&#xff0c;使得预览效果更加逼真。 本套原型图主要功能有医疗常识科普、医院挂号…

美创科技信创数据安全「利基者」!

近日&#xff0c;第一新声研究部正式发布《2023年中国信创网络安全产品竞争力象限》&#xff08;下称“象限报告“&#xff09;。 ◼︎ 象限报告综合考虑企业占有率、在技术/应用上的成熟度、在客户方面的交付完成度及口碑、产品在市场/营销/商业模式/行业拓展等战略上的领先性…

解决Drag and drop is not supported导致无法将物理机上的文件拖入Ubuntu

问题起因 因为需要拷贝一个文件从物理机到虚拟机&#xff0c;但是我又不想用有关ftp的程序或者协议&#xff0c;但是直接拖又报错Drag and drop is not supported&#xff0c;索性上网查询了一下解决方法&#xff0c;自己记录一下。 解决方法 安装下面两个程序 sudo apt in…

python小游戏:小球碰撞

创建带图形界面的游戏通常需要使用图形库。Python有很多图形库&#xff0c;其中比较流行的包括Pygame, PyOpenGL, Panda3D等。在这里&#xff0c;我将用Pygame作为示例来编写一个简单的游戏。 在运行下面的代码之前&#xff0c;请确保你已经安装了Pygame库。你可以使用以下命令…

工控机连接Profinet转Modbus RTU网关与水泵变频器Modbus通讯配置案例

Profinet转Modbus RTU网关是一个具有高性能的通信设备&#xff0c;它能够将工控机上的Profinet协议转换成水泵变频器可识别的Modbus RTU协议&#xff0c;实现二者之间的通信。通过这种方式&#xff0c;工控机可以直接控制水泵变频器的运行状态&#xff0c;改变其工作频率&#…

美格智能SLM927智能模组,轻松打造功能丰富的智能终端

近年来&#xff0c;智能终端加速渗入各行各业&#xff0c;使用场景不断拓展&#xff0c;视觉、听觉、语音、传感等交互能力融入智能终端产品之中&#xff0c;带来涵盖工作和生活各场景的智能化浪潮。 美格智能作为全球领先的无线通信模组及解决方案提供商&#xff0c;深入洞察…

Linux网络编程系列之UDP组播

Linux网络编程系列 &#xff08;够吃&#xff0c;管饱&#xff09; 1、Linux网络编程系列之网络编程基础 2、Linux网络编程系列之TCP协议编程 3、Linux网络编程系列之UDP协议编程 4、Linux网络编程系列之UDP广播 5、Linux网络编程系列之UDP组播 6、Linux网络编程系列之服务器编…