string类的详细模拟实现
string类的模拟实现
文章目录
- string类的模拟实现
- 前言
- 1. 类的框架设计
- 2. 构造函数与析构函数
- 3. 拷贝构造与重载赋值运算符函数
- 4. 运算符重载
- 5. 成员函数
- 6. 迭代器的实现
- 7. 非成员函数
- 8. 单元测试
- 总结
前言
在现代编程中,字符串处理是每个程序员都会遇到的基本任务。C++标准库提供了强大的std::string
类,但了解其背后的原理对于深入理解语言特性至关重要。本文将介绍如何从零开始实现一个简单的String
类,这不仅能够帮助我们更好地理解C++,还能够提升我们解决问题的能力。
1. 类的框架设计
首先需要确定成员变量有哪些,结合“先描述,再组织”的方针,要对String类进行描述,那么就必须先确定成员变量,下一步才有资格谈论功能实现(函数声明与定义)。这里我们需要实现的string本质上是一个可实现自动扩容的字符数组!所以成员变量采用char*声明的字符数组+字符串长度大小+字符数组容量
,通过字符数组实现存储,字符串长度和字符数组容量共同作为控制扩容逻辑的参考值。
当然,在确定成员变量后接着就需要定夺功能实现了,首先必须要有的是构造、析构函数,拷贝构造、重载复制运算符函数(这两者拷贝函数有时需要显式delete避免调用),特定的运算符重载,基本功能函数等。直接一次性声明出所有的类内成员变量与函数是不现实的,很难免会出现遗漏功能或逻辑不自洽。所以一般都是一边完善类的声明,一边实现函数定义,下面我便于后续描述直接给出了本文涉及的所有函数功能声明。
(注:仅挑选实现了std::string类中部分高使用率的函数)
class String
{
public:// 基础构造函数String(const char* str = "");// 拷贝构造函数String(const String& s);// 赋值运算符函数String& operator=(const String& s);// 析构函数~String();// 重载运算符friend std::ostream& operator<<(std::ostream& out, const String& s);friend std::istream& operator>>(std::istream& in, const String& s);bool operator==(const String& s);char operator[](size_t index)const;char& operator[](size_t index);String operator+(const String& s)const;String operator+(const char c)const;String operator+(const char* str)const;String& operator+=(const String& s);String& operator+=(const char c);String& operator+=(const char* str);// 成员函数size_t size()const;size_t capacity()const;const char* c_str()const;void reserve(size_t n);void resize(size_t n, char c = '\0');void push_back(const char c);void append(const char* str);void insert(size_t pos, const char c, size_t n = 1);void insert(size_t pos, const char* str);void erase(size_t pos, size_t n = npos);void swap(String& s);size_t find(const String& s, size_t pos = 0)const;size_t find(const char* str, size_t pos = 0)const;size_t find(const char c, size_t pos = 0, size_t n = 0)const;String substr(size_t pos = 0, size_t len = npos)const;// (仿)迭代器typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator cbegin();const_iterator cend();private:char* m_str;size_t m_size;size_t m_capacity;
private:static const int npos;
};
在实现具体函数功能前需要声明:
任何类内非静态函数,其参数列表第一个参数永远是类自身的self指针!(例如这里实现String类,即为:String* self)
这是后面函数实现中有时显式无参却能操作类内对象、仅有一个参数却能实现两对象之间的对比亦或其他操作的理论基础!
2. 构造函数与析构函数
String::String(const char* str):m_size(strlen(str)) // 注意缺省参数在声明和定义处仅出现一次
{m_capacity = m_size;m_str = new char[m_capacity + 1]; // 多一个字节存放'\0'strcpy(m_str, str);
}String::~String()
{delete[] m_str;m_str = nullptr;m_size = m_capacity = 0;
}
注意这里的构造函数使用了全缺省参数实现,通过这种方法可实现通过一个函数兼任无参构造与单参数构造函数。定义内容上,在String类实例化对象时实现堆区创建指针数组,以便后续对其扩容。
注意:函数参数使用缺省值实现时,缺省值定义的出现在声明和定义中仅能出现一次!
析构函数中需要注意delete
的对象是数组,所以需要相应使用delete[] 数组名
的方式对数组申请的堆区空间进行释放。
3. 拷贝构造与重载赋值运算符函数
String::String(const String& s)
{m_size = s.m_size;m_capacity = s.m_capacity;m_str = new char[m_capacity + 1];strcpy(m_str, s.m_str);
}
String& String::operator=(const String& s)
{char* tmp = new char[s.m_capacity];strcpy(tmp, s.m_str);delete[] this->m_str;this->m_str = tmp;this->m_size = s.m_size;this->m_capacity = s.m_capacity;return *this;
}
实现类的拷贝功能类型函数时,特别需要注意深浅拷贝的问题,很好的辨别方法是观察成员变量是否有是在堆区创建的,如果有,一般都需要避免浅拷贝的问题。在复制给新对象时,需要对该成员变量在堆区新开辟空间,接着才能进行堆空间之间内容的值拷贝。
对于拷贝赋值运算符而言,返回值一般以
self&
形式存在,很大一部分原因在于通过这种方法可以实现链式调用,例如:String s1("haha"); String s2; String s3;s3 = s2 = s1;
由于赋值运算符的运算逻辑是自右向左的,所以仅需一条语句就可将s1的值拷贝给s2、s3对象。
4. 运算符重载
- 流插入运算符和流提取运算符
std::ostream& operator<<(std::ostream& out, const String& s)
{out << s.m_str;return out;
}std::istream& operator>>(std::istream& in, String& s)
{char ch;ch = in.get();int i = 0;char buff[128]{ 0 };while (ch != '\n' && ch != ' '){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 127){buff[i] = '\0';s += buff;}return in;
}
当我们使用流提取(<<)时,由于不会导致String对象的成员变量(字符数组)内数组大小发生改变,并不会发生扩容,所以可以采用简单的直接提取字符数组进行输出(out << s.m_str)
然而使用流插入(>>)时, 很难免传入的新字符串大小超过原字符数组的容量承载极限,有可能导致扩容,所以我们需要对插入的数据进行分批从缓冲区获取,分批插入字符数组,如此一来当我们调用插入之类的函数时就可以一边插入一边扩容,这里采用了 +=
运算符实现插入操作,当然也可以使用 insert
、push_back
之类的函数实现。当然,后面我们会给出这类插入函数的实现。
- 双等号运算符
bool String::operator==(const String& s)
{return strcmp(m_str, s.m_str) == 0;
}
这里采用了C语言库中的strcmp
函数,通过对两个String对象的字符数组进行对比判断两对象是否等价。
- 下标运算符
char String::operator[](size_t index)const
{assert(index < m_size);return m_str[index];
}
char& String::operator[](size_t index)
{assert(index < m_size);return m_str[index];
}
提供了重载实现:
- 如果const String对象调用[]运算符,那么返回的 char 类型不会因其改变影响对象内的字符数组内值的变化。
- 如果String对象(non_const)调用时,返回指定下标位置的引用,外部对其进行修改时可以引起字符数组内指定位置值同步发生改变。
- 加号运算符
String String::operator+(const String& s)const
{String tmp;size_t capacity_sum = capacity() + s.capacity();tmp.reserve(capacity_sum); // 开空间// 字符串拷贝strcpy(tmp.m_str, m_str);strcat(tmp.m_str, s.m_str);tmp.m_size = size() + s.size();return tmp;
}
String String::operator+(const char c)const
{String tmp; // 初始化和定义tmp.reserve(capacity() + 1);size_t size = this->size();// 赋值strcpy(tmp.m_str, m_str);tmp.m_str[size] = c;tmp.m_str[size + 1] = '\0';tmp.m_size = size + 1;return tmp;
}
String String::operator+(const char* str)const
{assert(str);String tmp;size_t size_1 = size();size_t size_2 = strlen(str);tmp.reserve(size_1 + size_2 + 1);// 赋值strcpy(tmp.m_str, c_str());strcat(tmp.m_str, str);return tmp;
}
可以看到重载了三种加号运算符,分别实现了追加String对象、char字符、char*类型字符串,从内部实现来看,无一不是调用reserve()函数实现扩容,具体的reserve()函数定义将会在后面给出。不过可以确定的是,+
运算符也属于插入类型的运算符函数,不可避免的存在扩容的问题,所以模型都属于扩容+赋值
。
- 加等运算符
String& String::operator+=(const String& s)
{this->append(s.m_str);return *this;
}
String& String::operator+=(const char c)
{this->push_back(c);return *this;
}
String& String::operator+=(const char* str)
{this->append(str);return *this;
}
由于operator+=
的功能和append()
函数基本相同,所以采用代码复用的方式实现,后面给出 append() 函数的具体实现。
5. 成员函数
size
和capacity
size_t String::size()const
{return m_size;
}size_t String::capacity()const
{return m_capacity;
}
出于对数据的保护,使用接口对外提供访问。
c_str
const char* String::c_str()const
{return m_str;
}
便于外部直接访问字符数组,尤其是外部C语言函数调用时,不接受String类型参数,只能通过 c_str() 函数进行传递,注意返回值类型为 const char* ,可以避免外部对指针的操作引起内部数组变化。
reserve
和resize
void String::reserve(size_t n) // 设定容量,不影响size值
{if (n > m_size){char* cmp = new char[n + 1];strcpy(cmp, m_str);delete[] m_str;m_str = cmp;m_capacity = n;}
}
void String::resize(size_t n, char c)
{if (n <= m_size){m_str[n] = c;m_size = n;}else{if (n >= m_capacity) // 考虑扩容{reserve(n);}for (size_t i = m_size; i < n; i++) // 填充值{m_str[i] = c;}m_str[n] = '\0';m_size = n;}
}
reserve():控制容量
resize(): 控制size大小,可能影响容量(需要扩容)
push_back
和append
void String::push_back(const char c)
{if (m_size == m_capacity) // 考虑扩容{m_capacity = (m_capacity == 0) ? (4) : (m_capacity * 2);reserve(m_capacity);}m_str[m_size++] = c;m_str[m_size] = '\0';
}void String::append(const char* str)
{assert(str);const size_t size = strlen(str);if (size >= m_capacity - m_size) // 考虑扩容{m_capacity = size + m_size + 1;reserve(m_capacity);}// 拷贝strcpy(m_str + this->size(), str); // 从中间部分开始拷贝m_size += size;
}
都属于引起字符数组size增大的函数,存在扩容的可能,所以内部对size与capacity值进行判断,如有需要先扩容再插入。
insert
和erase
void String::insert(size_t pos, const char c, size_t n)
{assert(pos <= m_size);while(n >= m_capacity - m_size) // 避免n过大内存分配不够{m_capacity = (m_capacity == 0) ? (n+1) : (m_capacity * 2);reserve(m_capacity);}size_t i = m_size + n;for (; i >= n + pos && i != 0; --i) // i!=0避免溢出 内置if语句拖慢运行时间{m_str[i] = m_str[i - n];}if (i == 1 && pos == 0) // 处理pos=0(或i=0)的情况{m_str[n] = m_str[n - 1];}//m_str[m_size + n] = '\0'; // 注意结尾的'\0' (如果上面for循环定义i = m_size + n - 1时)for (size_t j = pos; j < pos + n; ++j){m_str[j] = c;}m_size += n; // 切记更新size值
}
void String::insert(size_t pos, const char* str)
{assert(str && pos <= m_size);size_t length = strlen(str);while (length >= m_capacity - m_size){m_capacity = (m_capacity == 0) ? (4) : (m_capacity * 2);reserve(m_capacity);}// 字符串右移size_t i = m_size + length;for (; i >= length + pos && i != 0; --i) // i!=0避免溢出 内置if语句拖慢运行时间{m_str[i] = m_str[i - length];}if (i == 1 && pos == 0) // 处理pos=0(或i=0)的情况{m_str[length] = m_str[length - 1];}for (size_t j = pos, i = 0; j < pos + length; ++j, ++i){m_str[j] = str[i];}m_size += length;
}void String::erase(size_t pos, size_t n)
{assert(pos < m_size);if (n == npos || n >= m_size - pos){m_str[pos] = '\0';m_size = pos;}else{for (size_t i = pos; i <= m_size; ++i) //i <= pos + n连带复制'\0'{m_str[i] = m_str[i + n];}m_size -= n;}
}
提供了两个重载的 Insert() 函数,可实现指定位置插入n个char值或在指定位置插入字符串(const char*)。而 erase() 函数也实现了从pos位置向后删除n个字符的功能。两者都采用了缺省值参数,如不指定就插入1个字符,而 erase() 函数的n如果未指定代表极大的值,相当于删除pos值以后的所有字符。
swap
和find
void String::swap(String& s)
{std::swap(m_str, s.m_str);std::swap(m_size, s.m_size);std::swap(m_capacity, s.m_capacity);
}size_t String::find(const String& s, size_t pos)const
{assert(pos < m_size);const char* sub_str = strstr(m_str + pos, s.m_str);if (sub_str != nullptr){return sub_str - m_str;}return npos;
}
size_t String::find(const char* str, size_t pos)const
{assert(pos < m_size && str != nullptr);const char* sub_str = strstr(m_str + pos, str);if (sub_str != nullptr){return sub_str - m_str;}return npos;
}
size_t String::find(const char c, size_t pos, size_t n)const
{assert(pos < m_size && n <= m_size - pos);for (size_t i = pos; i <= m_size - n; ++i){if (m_str[i] == c){size_t j = 0;for (; j < n; ++j, ++i){if (m_str[i] != c){break;}}if (j == n) { return i - n; }}}return npos;
}
使用最简单朴素的方法实现字符串查找。
substr
String String::substr(size_t pos, size_t len)const
{assert(pos < m_size);String tmp;if (len > m_size - pos) // 取到字符串结尾{for (size_t i = pos; i <= m_size; ++i){tmp.push_back(m_str[i]);}}else{for (size_t i = pos; i < pos + len; ++i){tmp.push_back(m_str[i]);}tmp.push_back('\0');}return tmp;
}
实现字符串切割,如果 len 值过大呢?我们将其视为取pos 往后全部字符串的功能要求,所以需要设置一个极大的值赋给 len,我们在类内定义一个静态成员变量(size_t npos = -1),由于 size_t 属于无符号整形,所以 -1 代表的值极大,一般字符串都不会超过这个长度。
6. 迭代器的实现
begin
和end
char* String::begin()
{return m_str;
}
char* String::end()
{return m_str + m_size;
}
这里需要C语言的基础,指针变量+整数代表指针后移整数个指向的变量位置。
cbegin
和cend
const char* String::cbegin()
{return m_str;
}
const char* String::cend()
{return m_str + m_size;
}
上面提供两种迭代器的原因是外部利用迭代器访问时,如果是 const 对象就调用 “cbegin(), cend()” 函数,反之调用 “begin(), end()” 函数。返回非const迭代器可以实现外部改变类内字符数组的值。
7. 非成员函数
getline
与swap
istream& getline(istream& in, String& s, char delim = '\n') // 空格不作为读取终止符
{char ch = in.get();int i = 0;char buff[128]{ 0 };while (ch != delim){buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0; // 重置buff}ch = in.get(); // 循环读取}if (i != 127){buff[i] = '\0';s += buff;}return in;
}void swap(String& s1, String& s2)
{s1.swap(s2);
}
8. 单元测试
void test1()
{const String s1("xxxxx");String s2(s1);String s3 = s1 + s2;String s4 = s1 + 'w';String s5 = s1 + "hahaha";cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;// 测试扩容String ss1;ss1.push_back('s');ss1.push_back('s');ss1.push_back('s');ss1.push_back('s');ss1.push_back('s');cout << ss1 << endl;
}
运行结果:
void test2()
{String s1;s1.append("hahahaha");cout << s1 << endl;s1.insert(0, 'q', 50);cout << s1 << endl;String s2 = s1;s2.insert(0, "wwwwwwwwww");cout << s2 << endl;s2.erase(0, 10);cout << s2 << endl;
}
运行结果:
void test3()
{String s1("wewewewewe");String s2("hhhhh");s1 += s2;s1 += '6';s1 += "vocal";cout << s1 << endl;
}
运行结果:
void test4()
{String s1;String s2;cin >> s1 >> s2;cout << s1 << endl;cout << s2 << endl;String s3;getline(cin, s3);cout << s3 << endl;
}
运行结果:
void test5()
{String s1("hahaha");String s2("jiarenmen");s1.swap(s2);cout << "s1:" << s1 << '\t' << "s2:" << s2 << endl;swap(s1, s2);cout << "s1:" << s1 << '\t' << "s2:" << s2 << endl;
}
运行结果:
void test6()
{String s("hawrwcrwwwwcrw");size_t found = s.find('w', 0, 4);cout << found << endl;found = s.find("wwcrw");cout << found << endl;String s2("wc");found = s.find(s2);cout << found << endl;
}
运行结果:
void test7()
{String s("hawwwcrwwwwcrw");String s2 = s.substr(2);cout << s2 << endl;String s3 = s.substr(s.find("cr"), 4);cout << s3 << endl;
}
运行结果:
总结
通过实现自己的String
类,我们不仅加深了对C++内存管理、对象生命周期和运算符重载的理解,还提高了我们的编程技能和问题解决能力。虽然std::string
类在功能和性能上可能更加成熟,但自己动手实现一个String
类无疑是一次宝贵的学习经历。在未来的编程实践中,我们可以继续探索更多的优化方法和高级特性,使我们的String
类更加健壮和高效。
相关文章:

string类的详细模拟实现
string类的模拟实现 文章目录 string类的模拟实现前言1. 类的框架设计2. 构造函数与析构函数3. 拷贝构造与重载赋值运算符函数4. 运算符重载5. 成员函数6. 迭代器的实现7. 非成员函数8. 单元测试总结 前言 在现代编程中,字符串处理是每个程序员都会遇到的基本任…...

【EasyX】 使用说明
EasyX 使用说明 基本说明 EasyX 是针对 C 的图形库,可以帮助 C/C 初学者快速上手图形和游戏编程。 比如,可以基于 EasyX 图形库很快的用几何图形画一个房子,或者一辆移动的小车,可以编写俄罗斯方块、贪吃蛇、黑白棋等小游戏&am…...

以太坊基金会JUSTIN DRAKE确认出席Hack.Summit() 2024区块链开发者大会
以太坊基金会JUSTIN DRAKE确认将出席由Hack VC主办,AltLayer、Berachain协办,并获得了Solana、The Graph、Blockchain Academy、ScalingX、0G、SNZ以及数码港的大力支持,本次大会由Techub News承办的Hack.Summit() 2024区块链开发者盛会。 Ju…...
数学建模常用代码
SVM分类器 1.命令函数部分: clear;%清屏 clc; X load(data.txt); n length(X);%总样本数量 y X(:,4);%类别标志 X X(:,1:3); TOL 0.0001;%精度要求 C 1;%参数,对损失函数的权重 b 0;%初始设置截距b Wold 0;%未更新a时的W(a) Wnew 0;%更新a后的…...

学点儿Java_Day7_在实体类当中IDEA无法进行单元测试(@Test没有启动按钮)
在敲代码体会继承和访问修饰符的时候忽然遇到了单元测试不管用的情况,表现为没有启动按钮 经过一番折腾,发现我的测试是在具有构造函数的实体类Person当中进行的,当我把所有的构造函数删除后,启动按钮又出来了,加…...
C语言:二叉树基础
一、树 1.1 树的概念 1.树是有n个节点组成的具有层次关系的集合,是一种非线性的结构。 2.树的第一个节点称为根,根没有前驱节点。 3.除了根节点,其余每个节点都只有一个前驱节点,有0个或多个后继节点。 4.节点的度&#x…...
LeetCode热题Hot100-两数之和
充分意识到Coding能力的重要性,重启算法刷题之旅。 没想到这么简单的题目都写的磕磕绊绊。 一刷只写自己的解,二刷再看有没有其他更巧妙的方法~ 题目: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目…...

鸿蒙实战开发-如何通过拖动滑块调节应用内字体大小
介绍 本篇Codelab将介绍如何使用基础组件Slider,通过拖动滑块调节应用内字体大小。要求完成以下功能: 实现两个页面的UX:主页面和字体大小调节页面。拖动滑块改变字体大小系数,列表页和调节页面字体大小同步变化。往右拖动滑块字…...

matlab实现神经网络检测手写数字
一、要求 1.计算sigmoid函数的梯度; 2.随机初始化网络权重; 3.编写网络的代价函数。 二、算法介绍 神经网络结构: 不正则化的神经网络的代价函数: 正则化: S型函数求导: 反向传播算法&…...
增强现实与虚拟现实中的大模型应用:沉浸式体验的创新
增强现实与虚拟现实中的大模型应用:沉浸式体验的创新 1. 背景介绍 随着技术的进步,增强现实(AR)和虚拟现实(VR)正在成为越来越受欢迎的沉浸式体验方式。大模型,如神经网络和深度学习模型&…...

【数据分析案列】--- 北京某平台二手房可视化数据分析
一、引言 本案列基于北京某平台的二手房数据,通过数据可视化的方式对二手房市场进行分析。通过对获取的数据进行清冼(至关重要),对房屋价格、面积、有无电梯等因素的可视化展示,我们可以深入了解北京二手房市场的特点…...
【Golang星辰图】创造美丽图表,洞察数据:解析Go语言中的数据可视化和数据分析库
解锁数据的力量:深入研究Go语言中的数据可视化和数据分析库 前言 本文将介绍Go语言中几个优秀的数据可视化和数据分析库,以帮助开发者更好地处理和分析数据。这些库提供了丰富的功能和工具,可用于创建漂亮的可视化图表、进行数值计算和数据…...

阿里云原生:如何熟悉一个系统
原文地址:https://mp.weixin.qq.com/s/J8eK-qRMkmHEQZ_dVts9aQ?poc_tokenHMA-_mWjfcDmGVW6hXX1xEDDvuJPE3pL9-8uSlyY 导读:本文总结了熟悉系统主要分三部分:业务学习、技术学习、实战。每部分会梳理一些在学习过程中需要解答的问题,这些问题…...
Scala第十一章节(正则表达式和异常处理)
4. 正则表达式 4.1 概述 所谓的正则表达式指的是正确的,符合特定规则的式子, 它是一门独立的语言, 并且能被兼容到绝大多数的编程语言中。在scala中, 可以很方便地使用正则表达式来匹配数据。具体如下: Scala中提供了Regex类来定义正则表达式.要构造一个Regex对象࿰…...
Flutter运行MacOs网络请求报错Unhandled Exception: DioException [connection error]:...
报错信息 [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: DioException [connection error]: The connection errored: Connection failed This indicates an error which most likely cannot be solved by the library. Error: SocketException: …...

基于SpringBoot+MyBatis框架的智慧生活商城系统的设计与实现(源码+LW+部署+讲解)
目录 前言 需求分析 可行性分析 技术实现 后端框架:Spring Boot 持久层框架:MyBatis 前端框架:Vue.js 数据库:MySQL 功能介绍 前台功能拓展 商品详情单管理 个人中心 秒杀活动 推荐系统 评论与评分系统 后台功能拓…...

Godot 学习笔记(5):彻底的项目工程化,解决GodotProjectDir is null
文章目录 前言GodotProjectDir is null解决方法解决警告问题根本解决代码问题测试引用其实其它库的输出路径无所谓。 总结 前言 Godot 项目工程化上有一朵乌云,我看Godot的Visual Studio 项目的时候,发现如果是手动新建项目导入Godot包,会导…...
Openharmony
OpenHarmony 是一个开源的、多设备分布式操作系统,由开放原子开源基金会(OpenAtom Foundation)孵化及运营。它旨在提供跨多种设备的统一开发体验,支持一次开发,多端部署。OpenHarmony 的系统架构遵循分层设计原则&…...
24计算机考研调剂 | 华南师范大学
华南师范大学接收调剂研究生 考研调剂招生信息 学校:华南师范大学 专业:- 年级:2024 招生人数:- 招生状态:正在招生中 联系方式:********* (为保护个人隐私,联系方式仅限APP查看) 补充内容 课题组主要研究生物拉曼光谱技术、基于荧光的微生物快检技术、显微成像设备与相…...

【Node.js】全局变量和全局 API
node 环境中没有 dom 和 bom ,此外 es 基本上都是可以正常使用的。 如果一定要使用 dom 和bom,可以借助第三方库 jsdom 帮助我们实现操作。npm i jsdom 实例: const fs require(node:fs) const {JSDOM} require(jsdom)const dom new JS…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
智能职业发展系统:AI驱动的职业规划平台技术解析
智能职业发展系统:AI驱动的职业规划平台技术解析 引言:数字时代的职业革命 在当今瞬息万变的就业市场中,传统的职业规划方法已无法满足个人和企业的需求。据统计,全球每年有超过2亿人面临职业转型困境,而企业也因此遭…...

Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程
鸿蒙电脑版操作系统来了,很多小伙伴想体验鸿蒙电脑版操作系统,可惜,鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机,来体验大家心心念念的鸿蒙系统啦!注意:虚拟…...

yaml读取写入常见错误 (‘cannot represent an object‘, 117)
错误一:yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因,后面把yaml.safe_dump直接替换成yaml.dump,确实能保存,但出现乱码: 放弃yaml.dump,又切…...