当前位置: 首页 > news >正文

string类的模拟实现(C++)

一、前言

想要模拟实现一个库中的类,那就要首先要熟悉如何使用这个类。建议通过下面博客,完成对C++string类的学习。

C++的string类-CSDN博客

二、模拟实现

我们将从string的成员函数即成员变量入手,模拟实现string类。

成员变量

string类的实现并未给出对应的标准,因此实现比较多样。为了防止出现命名冲突,我们将自己实现的string独自封装到单独的命名空间。下面是string的成员变量的实现。

namespace MyString
{class string{private:char* _a;size_t _size;size_t _capacity;};}

string我们可以看成是由数组、数组大小、数组空间构成的一个类。

成员函数

由于string的成员函数写的十分冗余,被业内也大量吐槽,因此我们实现几个主要的成员方法,也是最常用的几个成员方法。

构造函数:

构造函数用来实现函数调用时的初始化,需要注意的是,在初始化时,初始化列表阶段走的顺序不是写入初始化列表的顺序,而是成员变量的声明顺序。

需要注意两个个坑点:

坑点一:const char* 不能去初始化char*

当我们开始实现时,发现程序报错了,这是为什么呢?

这是因为:const char*类型的值不能用于初始化char*类型的值。

在C++中,const char*和char*之间的区别与C语言中的相同:const char*是指向常量字符的指针,意味着通过这个指针你不能修改所指向的字符。这种类型通常用于指向字符串字面量,因为这些字面量在程序中是只读的,存储在程序的常量区。

那就意味着,我们无法用_a指向str。因此我们需要给_a额外开辟一块空间,将str的值拷贝到这块空间。

坑点二:初始化列表走的顺序是变量声明的顺序

string::string(const char* str):_a(new char[_capacity + 1]),_size(strlen(str)), _capacity(_size + 1)
{}

有了上面的经验,我们想把_a开辟一块新空间,把内容拷贝进去。但是上述的代码也是不正确的!

我们出初始化先走的是_a,给_a开辟空间的时候,_capacity并未初始化,因此_capacity + 1的大小是未定义的。

正确方法:

为了合理的初始化顺序(重点观众声明顺序),我们采用初始化列表和函数体共同使用的方法去初始化构造函数。

string::string(const char* str):_size(strlen(str)), _capacity(_size)
{_a = new char[_capacity + 1];	//多开一个存储\0strcpy(_a, str);		//strcpy会给dest字符串,自动添加\0
}

我们先控制好大小之后,再去开辟空间,完成对_a的初始化。

优化:

先说明一个知识。“” 这个字符串代表的是常量字符串,长度是0。

int main()
{cout << strlen("") << endl;return 0;
}

上述给出的构造函数,并不是默认构造,因为我们没有给出缺省值。下面通过给出一个默认的常量字符串,完成初始化。

string::string(const char* str = ""):_size(strlen(str))		//默认常量字符串长度: 0, _capacity(_size) 
{_a = new char[_capacity + 1];	//多开一个存储\0strcpy(_a, str);		//strcpy会给dest字符串,自动添加\0
}

当我们不给出参数时,会使用缺省的长脸字符串,_size == 0 ;  _capacity == 0;  _a的大小是1(给\0预留空间)。

通过初始化列表与函数体的结合使用,这个函数便实现了默认构造与构造函数的结合

析构函数

析构函数的任务是完成数据的销毁与资源的释放。在string中,需要完成如下。

string::~string()	
{_size = _capacity = 0;delete[] _a;_a = nullptr;	//需要让指针置空}

c_str

c_str可以返回数组,并且返回的类型兼容C语言数组的属性。

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

[]重载

由于[]存在读和写两种两种需求,所以需要写出重载函数,来完成读和写的功能。在返回时,应尽量采用引用返回。同时应该注意assert去检查给出的下标是否合法。

char& string::operator[] (size_t pos)
{assert(pos <= _size - 1);	//pos是size_t,所以 >=0 恒成立return _a[pos];
}const char& string::operator[] (size_t pos) const
{assert(pos <= _size - 1);return _a[pos];
}

capacity、size

size_t string::capacity() const
{return _capacity;
}

应该用const修饰函数,保证const成员与非const成员都可以访问。

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

reserve

reserve函数用来完成空间的修正。对_capacity进行修正。一般来说空间大小只增不减,因此只有当作新空间大小大于原来空间大小的时候,才能进行开辟空间操作。

对于C++而言,开辟空间我们一般用new。因此reserve函数的实现必然包含以下步骤:1.开辟新空间 2.内容拷贝 3.释放旧空间(防止泄露)

void string::reserve(size_t n)	//开辟空间,对_capacity作出修改
{//1.重新开空间	2.深拷贝 3.释放旧空间if (n > _capacity){char* tmp = new char[n + 1];memcpy(tmp, _a, _size + 1);delete[] _a;_a = tmp;_capacity = n;	//将空间修正为n}}

push_back

功能是尾插。需要额外注意,空间是否还有剩余。在尾插完成之后,需要人为添加\0,使得C++能够兼容C。

void string::push_back(char ch)
{if (_size == _capacity)	//利用reserve扩容{reserve(_capacity == 0 ? 4 : 2 * _capacity);}_a[_size] = ch;++_size;	//_size类比length_a[_size] = 0;	//补充\0,符合C语言的规范
}

append

字符串的追加。字符串的追加也需要扩容,但是不是简单的二倍扩容这么简单,扩容时需要保证扩容后的大小必须可以容纳新的字符串。

在追加时可以用strcpy函数,strcpy的特性在代码中有所体现。

void string::append(const char* str)	//追加字符串
{size_t len = strlen(str);if (len + _size > _capacity)reserve(len + _size);strcpy(_a + _size, str);	//从_size处,完成内容的拷贝/*strcpy :自动添加\0,可从任意位置开始拷贝*/_size += len;
}

operator+=

完成字符与字符串的追加。返回的*this就是对象

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

insert

完成任意位置的插入。需要额外注意  1.头插和尾插能不能完成。 2.\0需要完成移动

因此我们直接借助下标end = _size+1;即可完成数据的移动


void string::insert(size_t pos, char ch)
{assert(pos <= _size);		// = 是尾插if (_size == _capacity)	//利用reserve扩容{reserve(_capacity == 0 ? 4 : 2 * _capacity);}size_t end = _size + 1;	//保证可以完成头插while (end > pos)	//后移(包含\0){_a[end] = _a[end - 1];--end;}_a[pos] = ch;++_size;//_a[_size] = 0;	//可有可无,\0也发生了后移
}

void string::insert(size_t pos, const char* str)
{assert(pos <= _size);		//不需要检查是否>0size_t len = strlen(str);if (len + _size > _capacity)reserve(len + _size + 1);	//对this指针对象进行扩容int end = _size;	//防止出现size_t的死循环while (end >= (int)pos){_a[end + len] = _a[end];--end;}strncpy(_a + pos, str, len);	_size += len;}

注意,当需要完成字符串的插入时,最好用int作为end的类型,防止出现死循环,同时强转pos类型(防止出现提升)。


/*
strncpy 函数在C语言中用于拷贝字符串。它的原型是:

char *strncpy(char *dest, const char *src, size_t n);
这个函数从源字符串 src 拷贝至目标字符串 dest,最多拷贝 n 个字符。
如果源字符串的长度小于 n,strncpy 会在目标字符串后面添加额外的空字符 ('\0'),直到总共拷贝了 n 个字符。
如果源字符串的长度大于或等于 n,则不会在目标字符串后面添加空字符。
*/

erase

用来完成删除操作。值得一提的是,缺省参数在声明时可以给出,在定义时不可以给出。


void string::erase(size_t pos, size_t len)		//声明给出,定义不给出缺省
{assert(pos < _size);		// == _size是\0的位置,不能删除if (len == npos || pos + len >= _size)	//len过长时{_a[pos] = 0;_size = pos;}else	//len的长度合适时{int cur = pos + len;while (cur <= (int)_size){_a[pos++] = _a[cur++];}_size -= len;}
}

clear

Erases the contents of the string, which becomes an empty string (with a length of 0 characters).

清楚内容,长度清零。

void string::clear()
{_size = 0;_a[0] = 0;
}

函数重载

只要完成 ==  >就可以服用全部重载。


bool string::operator<(const string& s) const
{return strcmp(_a, s._a) < 0;		//内部可使用private成员
}bool string::operator==(const string& s) const
{return strcmp(_a, s._a) == 0;
}bool string::operator<=(const string& s) const
{return *this == s || *this < s;
}bool string::operator>(const string& s) const
{return !(*this <= s);
}bool string::operator>=(const string& s) const
{return !(*this < s);
}bool string::operator!=(const string& s) const
{return !(*this == s);
}

迭代器函数

对于迭代器的实现有很多种方式,可以采用下标,可以采用指针。实现方式没有明确规定,我们这里采用指针来实现。

public:
    typedef char* iterator;        //typedef受到访问权限的限制
    typedef const char* const_iterator;
 

在类的内部,将迭代器iterator有指针类型实现。typedef收到访问权限的限制。


string::iterator string::begin()
{return _a;
}string::iterator string::end()
{return _a + _size;	//end返回的是最后一个元素的下一个位置。
}string::const_iterator string::begin() const
{return _a;
}string::const_iterator string::end() const
{return _a + _size;
}

需要注意的是end是最后一个元素的下一个位置。

/*
在C++中,迭代器的`end()`函数代表的是最后一个元素的下一位置。
它是用来标记容器的超出末端的位置,所以在进行遍历等操作时,通常会使用这个迭代器来检查是否到达了容器的末尾。
例如,如果你有一个 vector 容器,并且想要遍历它,你可以这样做:

std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << std::endl;
}
在这个循环中,`it` 会在每次迭代后递增,直到它等于 `vec.end()`,这时循环结束。
在循环体中,`*it` 是有效的并且指向当前的元素,当 `it` 达到 `vec.end()` 时,它不再指向任何元素,因此不应该被解引用。

*/

赋值重载与拷贝构造

string内部提供了swap函数,我们可以借助swap函数进行标题函数的实现。

void string::swap(string& s)
{std::swap(_a, s._a);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}

在swap内部,交换三个成员变量。

拷贝构造的实现:1.完成初始化 2.swap 一下tmp与*this

//s1(s2)
string::string(const string& s)	//在进入函数体之前,先对this进行初始化:_a(nullptr), _size(0), _capacity(_size){string tmp(s._a);swap(tmp);
}

/*
在C++中,未初始化的对象是不应该调用析构函数的。
析构函数是用来释放对象所拥有的资源的,如果一个对象没有被正确地初始化
它可能没有分配资源,或者分配的资源处于未知的状态
在这种情况下调用析构函数可能会导致未定义行为(UB),包括程序崩溃或者数据损坏。
*/
 

赋值重载的实现:同样借助swap函数。假设S2 = S3,那我们利用S3生成一个形参,swap S2 形参即可。交换完成之后,形参出作用域也可以自动销毁。同时为了保证=的连续性,返回类型应该是string对象类型。

// s2 = s3
string& string::operator=(string tmp)	//直接形参接收
{swap(tmp);return *this;		//保证返回类型也是string,可以连等
}

resize

resize用来修改size的大小。可分为两个情况讨论。void string::resize(size_t n, char ch)    //更新size

当n >= _size 和 n < _size。分别对应扩大size和缩小size

void string::resize(size_t n, char ch)	//更新size
{if (n <= _size){_size = n;_a[_size] = 0;}else{reserve(n);		//函数内部开辟n + 1个空间while (_size < n){_a[_size++] = ch;}_a[_size] = 0;}
}

find

用来查找内容,可以查找字符或者字符串。

查找字符

size_t string::find(char ch, size_t pos)	//函数的定义不能有缺省值
{for (size_t i = pos; i < _size; ++i){if (_a[i] == ch)return i;}return npos;
}

查找字符串有两种方法:方法一,使用strstr函数


size_t string::find(const char* sub, size_t pos)
{const char* ptr = strstr(_a + pos, sub);if (ptr == nullptr)return npos;return ptr - _a;
}

方法二:自己实现功能的查找

需要遍历str1,去找到第一个匹配的字符,匹配成功之后后移

/*
思路:遍历原数组,匹配往后走。
*/size_t string::find(const char* sub, size_t pos = 0)
{assert(pos < _size);size_t i = pos;for (; i < _size; ++i)		//遍历原数组{if (_a[i] == sub[0]){size_t len = strlen(sub);size_t m = i;for (size_t j = 0; j < len; ++j)	//循环:判断{if (m >= _size || sub[j] != _a[m])		{break;}++m;	//后移}if (m - i == len)	//如果完全一致(长度替代flag)return i;}}return npos;}

m - i == len则表示两个字符串完全一致。不能直接在判断语句写出m++。由于m在每次比较后都会递增,所以m - i的计算可能不会反映实际的匹配长度,因为m可能会超出实际不匹配的位置。即位置匹配成功之后,才可以m++

substr

用来生成子串。其核心逻辑是1.设定好大小开空间 2.拷贝内容

开大小:确定合适的长度        拷贝内容:确定合适的终止位置end


//1.设定好大小开空间 2.拷贝内容
string string::substr(size_t pos, size_t len)
{assert(pos < _size);string tmp;size_t end = pos + len;//设定大小if (pos + len >= _size || len == npos)	//len太长{len = _size - pos;end = _size;}tmp.reserve(len);	//开空间for (size_t i = pos; i < end; i++)	//内容拷贝{tmp += _a[i];}return tmp;		//临时对象不用引用返回。
}

全局函数

    ostream& operator<<(ostream& out, const string& s);

string函数支持直接流提取与流插入的操作。

流提取

ostream& MyString::operator<<(ostream& out, const string& s)
{for (auto ch : s)	//写了迭代器之后,就可以用范围forout << ch;return out;
}

流插入

我们借助一个插入缓冲区,来防止过多的浪费空间。步骤:1.输入内容 2.流入缓冲区 3.从缓冲区提取内容 4.继续从将内容读取到缓冲区中  最后需要清除缓冲区


istream& MyString::operator>>(istream& in, string& s)
{s.clear();	//清空对象char ch = in.get();size_t i = 0;char* buff = new char[129];		//建立缓冲区while (ch != ' ' && ch != 10){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;}


 

相关文章:

string类的模拟实现(C++)

一、前言 想要模拟实现一个库中的类&#xff0c;那就要首先要熟悉如何使用这个类。建议通过下面博客&#xff0c;完成对Cstring类的学习。 C的string类-CSDN博客 二、模拟实现 我们将从string的成员函数即成员变量入手&#xff0c;模拟实现string类。 成员变量 string类的…...

C++_sizeof的相关知识点

1.指针的大小永远是固定的&#xff0c;取决于处理器位数&#xff0c;32位就是 4 字节&#xff0c;64位就是 8 字节 2.数组作为函数参数时会退化为指针&#xff0c;大小要按指针的计算 int func(char array[]) {printf("sizeof%d\n", sizeof(array));printf("s…...

Istio Proxy的Envoy代理架构中,Upstream提供的功能是:

Istio Proxy的Envoy代理架构中&#xff0c;Upstream提供的功能是&#xff1a; A. 接收来自Envoy连接和请求的主机&#xff0c;并返回响应 B. 连接的一组逻辑相同的上游主机 C. 将下游主机连接到Envoy的主机&#xff0c;用来发送请求并接受响应 选择A Istio Proxy的Envoy代理架…...

LeetCode 热题 HOT 100 (015/100)【宇宙最简单版】

【栈】No. 0155 最小栈【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&#xff01; …...

【HarmonyOS】鸿蒙应用实现截屏

【HarmonyOS】鸿蒙应用实现截屏 组件截屏 通过componentSnapshot的get函数&#xff0c;将需要截图的组件设置id传进去即可。 import { componentSnapshot } from kit.ArkUI; import { image } from kit.ImageKit;/*** 截图*/ Entry Component Preview struct SnapShotPage {S…...

Conda包依赖侦探:conda inspect命令全解析

Conda包依赖侦探&#xff1a;conda inspect命令全解析 在Conda环境中&#xff0c;管理包及其依赖关系是一项重要任务。conda inspect命令是一个强大的工具&#xff0c;它可以提供包的详细信息&#xff0c;包括依赖关系、链接、版本等。这对于诊断环境问题、理解包的依赖结构以…...

数模——灰色关联分析算法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 一、基本概念了解 1.什么是灰色系统&#xff1f; 2.什么是关联分析&#xff1f; 二、模型原理 三、建模过程 1.找母序列&#xff08;参考序列&am…...

Python爬虫技术 第27节 API和RESTful服务

Python 爬虫技术是一种自动化获取网页内容的方法&#xff0c;通常用于数据收集、信息抽取或自动化测试。在讲解 Python 爬虫技术时&#xff0c;我们通常会涉及到以下几个关键概念&#xff1a; HTTP 请求&#xff1a;爬虫通过发送 HTTP 请求来获取网页内容&#xff0c;这是爬虫与…...

音视频入门基础:WAV专题(4)——FFmpeg源码中获取WAV文件音频压缩编码格式、采样频率、声道数量、采样位数、码率的实现

音视频入门基础&#xff1a;WAV专题系列文章&#xff1a; 音视频入门基础&#xff1a;WAV专题&#xff08;1&#xff09;——使用FFmpeg命令生成WAV音频文件 音视频入门基础&#xff1a;WAV专题&#xff08;2&#xff09;——WAV格式简介 音视频入门基础&#xff1a;WAV专题…...

环境变量在Conda中的魔法:控制包安装的秘诀

环境变量在Conda中的魔法&#xff1a;控制包安装的秘诀 Conda不仅是Python和其他语言包的包管理器&#xff0c;它还是一个强大的环境管理器。在使用Conda时&#xff0c;环境变量可以极大地增强其功能&#xff0c;允许用户控制包的安装过程&#xff0c;实现定制化的安装策略。本…...

VS Code C/C++ MSVC编译器

官方教程 通过快捷方式打开VS Code是编译不了的,需要对tasks.json修改(Tasks: Configure default build task) 先创建tasks.json 复制这段配置到tasks.json,记得修改VsDevCmd.bat的路径 {"version": "2.0.0","windows": {"options"…...

【技巧】IDEA 个性化配置

【技巧】IDEA 个性化配置 自动补全 关闭大小写区分 自动导包 插件 Rainbow Brackets 彩色括号 更容易区分是哪个括号...

`pytest` 中一些常用的选项

下面列出的参数和功能涵盖了 pytest 中一些常用的选项&#xff0c;但 pytest 还有许多其他参数和功能。以下是一些补充的 pytest 命令行参数和功能&#xff1a; 其他命令行参数 测试配置 --confcutdir<path>: 只加载指定目录及其子目录中的配置文件。例如 --confcutdirs…...

fme从json中提取位置到kml中

fme从json中提取位置到kml中 简单参考,我自己要用的,越弄越复杂。 概述-模板总体结构 数据就是官方提供的数据,模板的基本节结构是读模块+转换器+写模块,最近爬取一些json文件,用到了。 1.使用json读模块读取数据 首先检查一下源数据 使用文本打开数据集,可以看到非缩…...

在Ubuntu 18.04上安装和配置pgAdmin 4服务器模式的方法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 简介 pgAdmin 是一个针对 PostgreSQL 及其相关数据库管理系统的开源管理和开发平台。它使用 Python 和 jQuery 编写&#xff0c;支持 P…...

NiFi :1 初识这把“十年一剑”的利器

--->更多内容&#xff0c;请移步“鲁班秘笈”&#xff01;&#xff01;<--- “现在AI和数据处理密不可分&#xff0c;80%的企业可以利用Apache NiFi轻松解决复杂的数据问题&#xff0c;快速完成场景建设。犹如花上百来块钱在家享受一顿不亚于五星级西餐厅的法式大餐。对…...

Pyside6实战教程专栏目录

Pyside6实战教程&#x1f680; 专栏目录介绍 本专栏将详细地向Python开发者展示如何利用PySide6框架创建功能丰富的桌面应用程序。无论你是刚刚接触GUI编程的新手&#xff0c;还是希望快速提升自己技能水平的进阶用户&#xff0c;本文都将为你提供一系列简单易懂的教程&#xf…...

【Dash】使用 Dash Design Kit (DDK) 创建图表

一、Styling Your App The examples in the previous section used Dash HTML Components to build a simple app layout, but you can style your app to look more professional. This section will give a brief overview of the multiple tools that you can use to enhan…...

C++ 几何算法 - 向量点乘,叉乘及其应用

一&#xff1a;点乘介绍 1. 向量点乘&#xff1a; 2. 向量点乘的性质&#xff1a; 3. 向量点乘公式&#xff1a; 4. 向量的点乘的属性&#xff1a; &#xff08;1&#xff09;&#xff1a;向量与自身做点乘&#xff0c;会得到向量长度的平方&#xff1a; &#xff08;2&#xf…...

Taro学习记录(具体项目实践)

一、安装taro-cli 二、项目文件 三、项目搭建 1、Eslint配置 在项目生成的 .eslintrc 中进行配置 {"extends": ["taro/react"], //一个配置文件&#xff0c;可以被基础配置中的已启用的规则继承"parser": "babel/eslint-parser…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

莫兰迪高级灰总结计划简约商务通用PPT模版

莫兰迪高级灰总结计划简约商务通用PPT模版&#xff0c;莫兰迪调色板清新简约工作汇报PPT模版&#xff0c;莫兰迪时尚风极简设计PPT模版&#xff0c;大学生毕业论文答辩PPT模版&#xff0c;莫兰迪配色总结计划简约商务通用PPT模版&#xff0c;莫兰迪商务汇报PPT模版&#xff0c;…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)

引言 在人工智能飞速发展的今天&#xff0c;大语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为技术领域的焦点。从智能写作到代码生成&#xff0c;LLM 的应用场景不断扩展&#xff0c;深刻改变了我们的工作和生活方式。然而&#xff0c;理解这些模型的内部…...

Golang——7、包与接口详解

包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关

在水泥厂的生产流程中&#xff0c;工业自动化网关起着至关重要的作用&#xff0c;尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关&#xff0c;为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多&#xff0c;其中不少设备采用Devicenet协议。Devicen…...

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 &#xff0c;这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器&#xff0c;右键点击 .uproject 文件&#xff0c;选择 "Generate Visual Studio project files"&#xff0c;重…...

【安全篇】金刚不坏之身:整合 Spring Security + JWT 实现无状态认证与授权

摘要 本文是《Spring Boot 实战派》系列的第四篇。我们将直面所有 Web 应用都无法回避的核心问题&#xff1a;安全。文章将详细阐述认证&#xff08;Authentication) 与授权&#xff08;Authorization的核心概念&#xff0c;对比传统 Session-Cookie 与现代 JWT&#xff08;JS…...