第七讲:C++中的string类

目录

1、认识string类

2、标准库中的string类

2.1、string类的常见接口

2.1.1、构造与赋值重载

2.1.2、迭代器

2.1.3、容量

2.1.4、访问

2.1.5、修改

2.1.6、字符串操作

2.1.7、成员常量

2.1.8、非成员函数

2.1.9、转换函数

2.2、vs和g++下的string

2.2.1、vs下的string

2.2.2、g++下的string

3、OJ题

3.1、仅仅反转字母

3.2、第一个出现一次的字符

3.3、最后一个单词的长度

3.4、字符串是否回文

3.5、字符串相加

4、模拟实现

5、补充


1、认识string类

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一系列字符串的库函数, 但是这些库函数与字符串是分离开的,不太符合面向对象的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

在OJ题中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

2、标准库中的string类

string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类。这个类独立于所使用的编码来处理字节,string 类可以存储多字节或变长字符序列,但在操作时是基于字节而非字符的。

string类的文件介绍

2.1、string类的常见接口

只讲解最常用的接口,对其他的接口感兴趣的可以点击string类文件的介绍查看。

2.1.1、构造与赋值重载
string(); // 构造空的string类对象,即空字符串
string (const string& str); // 用string对象构造string对象
string (const char* s); // 用C式字符串构造string对象
string (size_t n, char c); // 用n个c构造string对象
string& operator= (const string& str); // 赋值重载
string& operator= (const char* s); 
string& operator= (char c); 
2.1.2、迭代器
// 正向迭代器
iterator begin(); // 返回第一个位置
iterator end(); // 返回第一个位置的下一个位置const_iterator begin() const; 
const_iterator end() const; // 反向迭代器
reverse_iterator rbegin(); // 返回最后一个位置 
reverse_iterator rend(); // 返回最后一个位置的上一个位置const_reverse_iterator rbegin() const; 
const_reverse_iterator rend() const; 

注:关于迭代器的原理,可以看看下面的string的模拟实现。

例如:

int main()
{string s1;string::iterator t1 = s1.begin();while (t1 != s1.end()){cout << *t1;++t1;}cout << endl;string s2("hello world");string::iterator t2 = s2.begin();while (t2 != s2.end()){cout << *t2;++t2;}cout << endl;string s3(s2);string::iterator t3 = s3.begin();while (t3 != s3.end()){cout << *t3;++t3;}cout << endl;string s4(10, 'a'); string::iterator t4 = s4.begin();while (t4 != s4.end()){cout << *t4;*t4 += 1;++t4;}cout << endl;const string s5(s2);string::const_reverse_iterator t5 = s5.rbegin();while (t5 != s5.rend()){cout << *t5;++t5;}cout << endl;string s6;s6 = s4;string::const_iterator t6 = s6.begin();while (t6 != s6.end()){cout << *t6;++t6;}cout << endl;return 0;
}
2.1.3、容量
size_t size() const; // 返回有效字符长度
size_t length() const; // 返回有效字符长度
void resize (size_t n); // 将有效字符的个数改为n个
void resize (size_t n, char c); // 将有效字符的个数改成n个,多出的空间用字符c填充
size_t capacity() const; // 返回空间的总大小
void reserve (size_t n = 0); // 预留空间
void clear(); // 清空字符串
bool empty() const; // 判断是否为空,是返回true,否返回false

注意:

1、size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。

2、clear()只是将string中有效字符清空,不改变底层空间大小。 

3、resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时,resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。

注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

4、reserve(size_t n=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

例如:

int main()
{string s1("hello world");cout << s1.size() << endl;cout << s1.length() << endl;cout << s1.capacity() << endl;s1.clear();cout << s1.size() << endl;cout << s1.length() << endl;cout << s1.capacity() << endl;cout << s1.empty() << endl;s1 = "i am a student";cout << s1.size() << endl;cout << s1.length() << endl;cout << s1.capacity() << endl;s1.reserve(100);cout << s1.size() << endl;cout << s1.length() << endl;cout << s1.capacity() << endl;s1.resize(50, 'o');cout << s1.size() << endl;cout << s1.length() << endl;cout << s1.capacity() << endl;string::iterator t1 = s1.begin();while (t1 != s1.end()){cout << *t1;++t1;}cout << endl;return 0;
}
2.1.4、访问
char& operator[] (size_t pos); // 返回pos位置的字符
const char& operator[] (size_t pos) const; 
char& back(); // 返回最后一个元素
const char& back() const;
char& front(); // 第一个元素
const char& front() const; 
// 范围for

例如:

int main()
{string s1("i love you");for (int i = 0; i < s1.size(); i++){cout << s1[i];}cout << endl;cout << s1.front() << endl;cout << s1.back() << endl;string s2("aaaaaaaaaaaa");for (auto& e : s2) // 在底层实际上用的还是迭代器{cout << e;e += 1;}cout << endl;for (auto e : s2){cout << e;}return 0;
}
2.1.5、修改
string& operator+= (const string& str);	// 追加字符串
string& operator+= (const char* s); // 重载
string& operator+= (char c); // 重载
string& append (const string& str); // 追加字符串
string& append (const char* s); // 重载
string& append (size_t n, char c) // 重载
void push_back (char c); // 尾插字符
string& insert (size_t pos, const char* s); // pos位置前插入字符串

注意:在string尾部追加字符时,s.push_back(c)和s.append(1, c)以及s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

例如:

int main()
{string s1("i am");s1 += " student";for (char s : s1){cout << s;}cout << endl;s1.push_back('!');s1.append("do you like me?");for (auto s : s1){cout << s;}cout << endl;s1.insert(13, " ");for (auto s : s1){cout << s;}return 0;
}
2.1.6、字符串操作
const char* c_str() const; // 返回C式字符串
size_t find (const char* s, size_t pos = 0) const; // 从pos位置向后开始查找字符串
size_t find (char c, size_t pos = 0) const; 
size_t rfind (const char* s, size_t pos = npos) const; // 从pos位置向前查找字符串
size_t rfind (char c, size_t pos = npos) const; 
string substr (size_t pos = 0, size_t len = npos) const; // 从pos位置开始截len个长度的字符串构造对象

例如: 

int main()
{string s1("i am");const char* s2 = s1.c_str();s1 += " student! do you like me?";size_t n = s1.find("like me", 0);string s3 = s1.substr(n, 7);for (auto e : s3){cout << e;}cout << endl;return 0;
}
2.1.7、成员常量
static const size_t npos = -1; // size_t类型的最大值
2.1.8、非成员函数
string operator+ (const string& lhs, const string& rhs); // 运算符重载
istream& operator>> (istream& is, string& str); // 流提取
ostream& operator<< (ostream& os, const string& str); // 流插入
istream& getline (istream& is, string& str); // 获取一行字符串,默认遇换行结束
istream& getline (istream& is, string& str, char delim); // 可以指定遇到delim结束
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); // 大于等于

例如: 

int main()
{string s1("100");s1 = s1 + "100";cout << s1 << endl;string s2;getline(cin, s2);cout << s2 << endl;cout << (s1 == s2) << endl;string s3;cin >> s3;cout << s3 << endl;cout << string::npos << endl;return 0;
}
2.1.9、转换函数
int stoi (const string&  str, size_t* idx = 0, int base = 10); // 转换为整数
string to_string (int val); // 转换为string类型

例如:

int main()
{string s1("100");int n = stoi(s1);n += 50;string s2 = to_string(n);for (auto e : s2){cout << e;}cout << endl;return 0;
}

上面的几个接口了解一下,下面的OJ题目中会有一些体现他们的使用,此外还可以看string的模拟实现,来理解string类的使用。string类中还有一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。 

2.2、vs和g++下的string

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

2.2.1、vs下的string

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字 符串的存储空间: 当字符串长度小于16时,使用内部固定的字符数组来存放当字符串长度大于等于16时,从堆上开辟空间。

2.2.2、g++下的string

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指 针将来指向一块堆空间,内部包含了如下字段: 空间总大小、字符串有效长度、引用计数。

写时拷贝是写时再进行深拷贝,是通过“浅拷贝”和引用计数机制,以及“延迟复制”策略来实现的。当创建对象或赋值时,多个对象可以共享同一份数据(不立即复制),只有当其中某个对象需要修改这份共享数据时,才会为它创建一份独立的拷贝(即“写”操作触发复制)。

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为0,说明该对象为资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

3、OJ题

3.1、仅仅反转字母

仅仅反转字母

参考:

3.2、第一个出现一次的字符

第一个出现一次的字符

参考:

3.3、最后一个单词的长度

最后一个单词的长度

参考:

注意:无论是scanf还是cin都有一个特点就是遇到空格或者换行就会结束。

3.4、字符串是否回文

验证回文串

参考:

3.5、字符串相加

字符串相加

参考:

上面的这个题时间复杂度比较高,我们可以进行优化一下:

4、模拟实现

我们只实现string类的主要的功能。

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('\0')),_size(0),_capacity(0){}String(const char* str):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}*/String(const char* str="")    //上面的两个构造函数合并这一个构造函数。:_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}/*String(const String& s)//传统写法的构造函数{_str = new char[s.capacity()+1];strcpy(_str, s._str);_size = s.size();_capacity = s.capacity();}*/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)    //不写这个在vs不报错,但是在其他编译器上不写这个可能会出现错误。一般情况下,编译器不会对自定义类型处理, _size(0)          //但在vs上,编译器把_str处理为nullptr,_size和_capacity处理为0。不处理的情况下,默认为随机值,等到出作用域销毁的, _capacity(0)*/     //时候,也就是delete的时候就会出现错误。{String tmp(s._str);swap(tmp);}~String(){delete[] _str;_str = nullptr;_size = _capacity = 0;}//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);//		swap(tmp);//	}//	return *this;//}String& operator=(String s)//极致的现代写法{swap(s);return *this;}// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////普通的成员函数char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const//为const对象提供的重载形式{assert(pos < _size);return _str[pos];}const char* c_str() const{return _str;}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 c = '\0'){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);while (_size < n){_str[_size] = c;++_size;}_str[_size] = '\0';}}size_t find(char ch, size_t pos = 0){for (size_t i = pos; i < _size; i++){if (ch == _str[i]){return i;}}return npos;}String substr(size_t pos, size_t len = npos){String s;size_t end = pos + len;if (len == npos || len + pos >= _size){len = _size - pos;end = _size;}s.reserve(len);for (size_t i = pos; i < end; i++){s += _str[i];}return s;}size_t find(const char* sub, size_t pos = 0){const char* tmp = strstr(_str + pos, sub);if (tmp){return tmp - _str;}elsereturn npos;}void push_back(const char ch){if (_capacity == _size){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}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& operator+=(const String& s){append(s._str);return *this;}void insert(size_t pos,char ch){assert(pos <= _size);if (_capacity == _size){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size+1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;}void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}size_t end = _size+1;while (end > pos){_str[end+len-1] = _str[end-1];--end;}strncpy(_str + pos, str, len);_size += len;}void erase(size_t pos = 0, size_t len = npos){assert(pos < _size);if (len == npos || len + pos > _size){_str[pos] = '\0';_size = pos;}else{size_t begin = pos + len;while (begin <= _size){_str[begin - len] = _str[begin];++begin;}_size -= len;}}void clear(){_str[0] = '\0';_size = 0;}/*size_t size(){return _size;}size_t capacity(){return _capacity;}*/size_t size() const//其实写了下面的两个const修饰的成员函数就没必要写上面的两个成员函数了。{return _size;}size_t capacity() const{return _capacity;}// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////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);}bool operator!=(const String& s) const{return !(*this == s);}// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private:char* _str;size_t _size;size_t _capacity;public:const static size_t npos;//为了在类外也能访问,所以公开了。};const size_t String::npos = -1;// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////*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[129];size_t i = 0;char ch;        //流提取默认遇到空格或者换行结束//in >> ch;     //这种写法是不行的,因为流提取没法提取空格或者换行。ch = in.get();//这里的get函数是库里面的函数,这个函数是可以拿到空格和换行的。while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 128){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;
}ostream& operator<<(ostream& out, const String& s)
{/*for (size_t i = 0; i < s.size(); i++){out << s[i];}*/for (auto ch : s)//范围for也是可以的。out << ch;return out;
}

5、补充

编码表:值和符号一一映射的关系。

ASCII表被制作时仅仅考虑到了英文,为了让电脑能呈现其他国家的语言,所以就制作了Unicode统一码,也叫万国码。Unicode又可以分为UTF-8、UTF-16、UTF-32。UTF-8是兼容ASCII表的,对于常见的汉字,用UTF-8编码占两个字节,越是不常用的汉字占的字节数越多,但不超过4个字节。

GBK全称《汉字内码扩展规范》,之所以会有GBK的存在是因为有些生僻字或者古汉字用Unicode不能很好的兼容,因而制作出了更符合中国的GBK编码,使用了双字节的编码方案。 

Windows上一般使用GBK,Linux上一般用UTF-8。

乱码一般是指存储方式和解释方式不同而导致的解析出的一些没有规律的字符或者数字等。

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

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

相关文章

elementUI 前端表格table数据导出(一)

为啥前端导出不在赘述了第一步&#xff1a;安装xlsxnpm install xlsx第二步&#xff1a;创建js文件html2excelimport * as XLSX from xlsx;const htmlToExcel {getExcel(dom, title temp){var excelTitle title;// const XLSX require("xlsx");var wb XLSX.util…

LabVIEW 波形图表横坐标显示当前日期

LabVIEW 程序如何实现波形图表横坐标显示当前日期一、XY Graph 时间关联逻辑&#xff08;右上角分支&#xff09;功能本质实时采集当前系统时间&#xff08;秒数形式&#xff0c;基于 1904 基准&#xff09;&#xff0c;直接映射为 XY Graph 的 X 轴时间标识&#xff0c;实现动…

Android Soundtrigger唤醒相关时序学习梳理

本文所写内容是在高通芯片平台相关代码基础上学习整理汇总&#xff0c;如有描述不当之处&#xff0c;欢迎指正&#xff01;1、SoundTrigger注册唤醒监听事件回调流程&#xff08;SoundTrigger HAL层到ADSP层&#xff0c;不包括FWK层&#xff09;//(1)SoundTriggerSession 回调 …

OSPF实验以及核心原理全解

OSPF&#xff08;Open Shortest Path First&#xff0c;开放式最短路径优先&#xff09;是一种基于链路状态的内部网关协议&#xff08;IGP&#xff09;&#xff0c;广泛应用于中大型网络中。它通过维护网络拓扑信息&#xff0c;使用 SPF&#xff08;最短路径优先&#xff09;算…

Using Spring for Apache Pulsar:Transactions

本节介绍Spring for Apache Pulsar如何支持事务。OverviewSpring for Apache Pulsar事务支持是基于Spring Framework提供的事务支持构建的。在高层&#xff0c;事务资源向事务管理器注册&#xff0c;事务管理器反过来处理注册资源的事务状态&#xff08;提交、回滚等&#xff0…

在Ubuntu上从零开始编译并运行Home Assistant源码并集成HACS与小米开源的Ha Xiaomi Home

目录1. 前言&&叠甲2. 使用的环境3. 相关链接4. 前期步骤4.1 安装路径提前说明4.2 Ubuntu 相关依赖安装4.3 Python源码编译安装4.3.1 编译安装4.3.2 换源4.3.3 环境变量5. 构建Home Assistant源码5.1 clone源码5.2 创建虚拟Python环境5.3 安装项目依赖5.4 安装项目5.5 运…

【实习篇】之Http头部字段之Disposition介绍

Http头部字段之DispositionDisposition头部字段介绍RFC规范介绍RFC 6266与RFC 2047实习的时候公司将一个某个关于下载的Bug交给了我来修&#xff0c;看了代码和日志后发现是Disposition字段的规范兼容性惹的锅&#xff0c;因为有些协议使用的是老协议&#xff0c;我们的项目没有…

VM文件管理与Vi/vim操作

[rootlocalhost /]# sudo mkdir /opt [rootlocalhost /]# sudo mkdir /opt/tmp [rootlocalhost /]# sudo touch /opt/tmp/a.txt [rootlocalhost /]# ls /opt/tmp/ a.txt [rootlocalhost /]# 3.步骤1&#xff1a;创建文件并插入日期时间vi /tmp/newfile在vi编辑器中输入以下命令…

【Android】安卓四大组件之内容提供者(ContentProvider):从基础到进阶

你手机里的通讯录&#xff0c;存储了所有联系人的信息。如果你想把这些联系人信息分享给其他App&#xff0c;就可以通过ContentProvider来实现。。 一、什么是 ContentProvider ‌ContentProvider‌ 是 Android 四大组件之一&#xff0c;负责实现‌跨应用程序的数据共享与访问…

Vue-19-前端框架Vue之应用基础组件通信(二)

文章目录 1 v-model(父子相传)1.1 App.vue1.2 Father.vue1.2.1 v-model用在html标签上1.2.2 v-model用在html标签上(本质写法)1.2.3 v-model用在组件标签上1.2.4 v-model用在组件标签上(本质写法)1.3 MyInput(自定义的组件)1.4 修改modelValue1.4.1 Father.vue1.4.2 MyInput.vu…

宝塔下载pgsql适配spring ai

1.宝塔安装pgvector 1.先去github下载pgvectorpgvector/pgvector: Open-source vector similarity search for Postgres 2.把压缩包上传到系统文件的/temp下解压&#xff0c;重命名文件名为pgvector&#xff0c;之后命令操作 cd /tmp cd pgvector export PG_CONFIG/www/serv…

RK3568项目(八)--linux驱动开发之基础外设(上)

目录 一、引言 二、准备工作 ------>2.1、驱动加载/卸载命令 三、字符设备驱动开发 ------>3.1、驱动模块的加载和卸载 ------>3.2、外部模块编译模板 Makefile ------>3.3、cdev 四、LED驱动 ------>4.1、原理图 ------>4.2、驱动 五、设备树 -…

BUUCTF在线评测-练习场-WebCTF习题[GXYCTF2019]BabySQli1-flag获取、解析

解题思路打开靶场&#xff0c;题目提示是sql注入输入数据&#xff0c;判断下闭合11123报错&#xff1a;Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 1 at line 1报错提示…

“AI 曼哈顿计划”:科技竞赛还是人类挑战?

美国国会下属的经济与安全审查委员会已将“推动建立并资助一项堪比曼哈顿计划的通用人工智能研发项目”列为其对国会的核心建议之一&#xff0c;明确显示出对AI竞赛战略意义的高度重视。与此同时&#xff0c;美国能源部在近几个月中多次公开将人工智能的突破比作“下一场曼哈顿…

音频信号的预加重:提升语音清晰度

一、预加重介绍预加重是一种信号处理技术&#xff0c;主要用于增强音频信号中的高频成分。由于人类语音的频谱特性&#xff0c;尤其是在辅音和音调的表达上&#xff0c;高频成分对于语音的清晰度至关重要。然而&#xff0c;在录音和传输过程中&#xff0c;这些高频成分往往会受…

WebSocket实战:实现实时聊天应用 - 双向通信技术详解

目录一、WebSocket&#xff1a;实时通信的"高速公路"1.1 HTTP的短板&#xff1a;永远的"单相思"1.2 WebSocket的优势&#xff1a;真正的"双向对话"二、30分钟搭建聊天服务器2.1 环境准备2.2 WebSocket配置类2.3 核心消息处理器三、前端实现&…

宏集案例 | 基于CODESYS的自动化控制系统,开放架构 × 高度集成 × 远程运维

​​案例概况客户&#xff1a;MACS Sterilisationsanlagen GmbH&#xff08;Ermafa Environmental Technologies GmbH 旗下&#xff09; 应用场景&#xff1a;医疗与感染性废弃物的无害化处理控制系统应用产品&#xff1a;宏集Berghof高性能控制器设备&#xff08;一&#xff0…

学习JNI 二

创建一个名为Learn1项目&#xff08;Android Studio&#xff09;。一、项目结构二、配置 build.gradlebuild.gradle.kts(:app)plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android) }android {namespace "com.demo.learn1&quo…

基于Spring Boot+Vue的DIY手工社预约管理系统(Echarts图形化、腾讯地图API)

2.10 视频课程管理功能实现2.11手工互动&#xff08;视频弹幕&#xff09;2.8预约设置管理功能实现&#x1f388;系统亮点&#xff1a;Echarts图形化、腾讯地图API&#xff1b;文档包含功能结构图、系统架构图、用例图、实体属性图、E-R图。一.系统开发工具与环境搭建1.系统设计…

leetcode 每日一题 1353. 最多可以参加的会议数目

更多技术访问 我的个人网站 &#xff08;免费服务器&#xff0c;没有80/443端口&#xff09; 1353. 最多可以参加的会议数目 给你一个数组 events&#xff0c;其中 events[i] [startDayi, endDayi] &#xff0c;表示会议 i 开始于 startDayi &#xff0c;结束于 endDayi 。 …