【C++修炼之路 第五章】模拟实现 string 类


开发日志:

/*
* 开发日志
* 1、基本 string 类框架:string 域(自定义命名空间) +  私有成员
* 2、基本函数:一般构造 + 拷贝构造 + 析构 

以下分组实现一些 string 类常见常用的函数
* 3、基本访问操作:c_str() + size() + operator[]
* 4、实现两类迭代器:begin() + end() 
* 5、增加字符操作:reserve + push_back + append + operator+=
* 6、插入与删除:insert + erase
* 7、查找:find
* 8、各种重载函数:大小比较 + 赋值重载
* 9、提取子串 与 清理:substr + clear
* 10、string 的 神奇 swap 函数
* 11、流插入和流提取:<< 和 >> 
*/
 


声明:本次模拟实现 string 类,采用 声明和定义分离 的形式

声明写在 string.h 头文件中

定义写在 string.cpp 文件中

同时 其他本项目下的 .cpp 想要使用 string.h 需要用双引号引用头文件(而不是 尖括号<>)

  • 因为用 双引号 引用头文件,编译器会优先到本项目文件中找该头文件,再到库中找头文件
  • 这样,自定义的 string.h 就会被优先引用(编译器找到一个就不会再找了,因此避免了和库的 string 冲突)

0. 声明:一些注意事项

1、不想指定类域,就先框定类域

由于我们自定义了一个 命名空间,string 类 声明和定义分离,则 定义部分需要指定类域:

如下:bit 时 类域,string 是 类名

bit::string::string(const char* str)

若每个都这样写,有点麻烦,可以直接 框定类域

如 string.cpp 中

namespace bit
{string::string(const char* str)
}

2、给字符串 new 空间时,一定要 + 1(给 '\0')

由于 string 类的字符串长度不包括 '\0' ,其总容量 _capacity 和 字符串有效长度 _size 都不计算 '\0'

但是我们需要开多一个字节空间来存储 '\0',因此每次给字符串 new 空间时,一定要 + 1

如下:

_str = new char[_size + 1];

1、基本 string 类框架:类域+私有成员+基本函数(构造和析构)

string.h

namespace bit 
{class string{public:string(const char* str = ""); // 构造函数:全缺省(合并有参和无参)string(const string& s); // 拷贝构造函数~string(); // 析构函数private:char* _str;   // 指向字符串size_t _size;  // 字符串有效范围size_t _capacity;  // 字符串总空间大小};
}

 string.cpp

namespace bit
{// 构造函数:全缺省(合并有参和无参)string::string(const char* str):_size(strlen(str)){_str = new char[_size + 1];strcpy(_str, str);_capacity = _size;}// 拷贝构造函数string::string(const string& s){// 要开新空间,拷贝别人的字符串,若直接 str = s.str 就是浅拷贝了_str = new char[s._capacity + 1];  strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// 析构函数string::~string() {delete[] _str;_str = nullptr;_size = _capacity = 0;}
}

1.1 为什么要自定义类域

因为 C++库中也有一个 string,这里定义自定义命名空间,才不会和 库函数空间 std  中的 string 冲突

1.2 将构造函数 设计成 全缺省

string 类的构造函数有两种需求:无参构造 和 带参构造

(1)无参构造:创建 string 变量时不赋初值

string s;

(2)带参构造: 则相反

string s("hello")

使用缺省值:""

当  创建 string 变量时不赋初值 ,string 会赋值为缺省值,即 空字符串,刚好满足 无参和带参的需求

——— 以下分组实现函数 ———

2、基本访问操作:c_str() + size() + operator[]

namespace bit
{const char* string::c_str() const{return _str;}size_t string::size() const{return _size;}char& string::operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& string::operator[](size_t pos) const{assert(pos < _size);return _str[pos];}}

2.1 c_str() 

这个函数是将 C++ 的string,转换成 C语言的 char* 类型的字符串

2.2 两种 operator[]

这是为了满足两种需求:访问字符串且需要修改 和 不需要修改 

3、实现迭代器:begin() + end() 

在本次模拟实现string类中,迭代器是定义成 char* 类型,为了遍历字符串

因此这里 typedef 设置 iterator 迭代器(定义在类中)

class string 
{
public:typedef char* iterator;typedef const char* const_iterator; // const_iterator 表示迭代器指向的内容不能修改,不是迭代器本身不能修改// ..... 其他函数};

若对 const_iterator 和 const iterator 之间有疑惑 或 分不清的:

可以看这篇博客:通俗讲解 const_iterator 和 const iterator 的区别


因为这里迭代器设置为 char* 指针类型,因此 begin() 和 end() 也就是获取指针位置

string.cpp 

namespace bit
{string::iterator string::begin(){return _str;}string::iterator string::end(){return _str + _size;}string::const_iterator string::begin() const{return _str;}string::const_iterator string::end() const{return _str + _size;}
}

4、增加字符操作:reserve + push_back + append + operator+=

string.cpp

namespace bit
{void string::reserve(size_t n){// 申请的空间大小 n 大于 当前字符串总空间大小 capacticy 才扩容if (n > _capacity) {// 手动扩容:开新空间,释放调原来的char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n; // capacity 不用 +1,不计算 '\0'}}void string::push_back(const char ch){// 一次开两倍,用 reserve,真实空间不够就扩,空间够也不缩容if (_size == _capacity) {size_t  newCapacty = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacty);}// _size++ 写前面和写后面都一样//_size++;//_str[_size - 1] = ch;  // 这个位置本来是 '\0'的//_str[_size] = '\0';  // 要帮别人'\0' 移动位置// 通过调试可以看到:你使用 ch 代替了 '\0' 会导致字符串无效(没有结束符了),因此需要把'\0'补上_str[_size] = ch;_str[_size + 1] = '\0';_size++;}void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity) {// 要多少加多少reserve(_size + len);}// 可以使用 strcat 追加字符串:遍历字符串,找到 \0 ,从这个位置开始追加字符串// 遍历一遍效率较低// 使用 strcpy 指定起始位置:_str + _size,刚好是 \0 的位置strcpy(_str + _size, str);_size += len;}// operator+= 函数 直接复用之前写的函数就好string& string::operator+=(const char ch){this->push_back(ch);return *this;}string& string::operator+=(const char* str){this->append(str);return *this;}
}

5、查找:find

这个就是在字符串中 寻找你要找的 字符或字符串,返回该字符或字符串第一次出现的下标位置(首位)

string.h

namespace bit 
{class string{// .....private:char* _str;size_t _size;size_t _capacity;//const static int npos = -1; // 特例const static size_t npos;   // 定义 静态成员常量 npos (用 const 修饰变成常量)};
}

string.cpp

namespace bit
{const size_t string::npos = -1;  // 静态成员变量类外定义size_t string::find(char ch, size_t pos){assert(pos < _size);for (int i = pos; i < _size; ++i) {if (_str[i] == ch) return i;}return npos;}size_t string::find(const char* str, size_t pos){// 这里涉及字符串匹配问题:在实践中一般会用 BF算法,即暴力算法,而不是 KMP(至于为什么自己了解一下)// 这里直接使用 strstr(这个函数底层也是 暴力)// strstr 函数:返回指向 str1 中首次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回空指针。const char* p = strstr(_str + pos, str);// 杭哥没写这个if (p == NULL) return string::npos;  // 文档是这样写的:32位下和64位下的 npos 数值不同return p - _str;  // 指针 - 指针 = 数量}}

思考问题:

1、程序中的  npos 是什么?

这个表示 一个很大的数,在我们的程序中,定义成 静态成员变量

关于 静态成员变量 的定义位置

静态成员变量声明和定义要分离,在类中声明,在类外定义

而在本章节,我们将所有函数的声明和定义分离,创建了 string.h string.cpp 两个文件

如果,你将 静态成员变量的类外定义,直接写在 string.h 文件中,就会出现程序运行的链接问题

原因:string.h 会在 string.cpp 和 test.cpp 两个文件中展开,这三个文件最后会合并成一个文件

这样导致 [ 静态成员变量的类外定义] 出现两次,会报错:重定义

因此:我们这里将 [ 静态成员变量的类外定义] 放在 string.cpp 函数中

2、为什么 const static int npos = -1   这样的写法 是  特例?

讲一个特例

之前的知识讲解过: 静态成员变量在类中声明,在类外定义,且普通成员变量可以直接给缺省值(为初始化列表服务),而 静态成员变量 不能给缺省值

但是

这样写不报错:直接给 const 修饰的 静态成员变量 赋初值

这里的赋值,也算作 静态成员变量 的定义(就不用到类外定义)

class A{
private:const static int tmp = 10; // 不报错
};

这个却会报错

const static double tmp = 10.1;

为什么一个会报错一个不会报错?

直接给结论:这个用 const 修饰静态成员,使其可以直接在类内定义 的 特例是针对于 整型类型的浮点型不可以

(整型类型是表示整型家族:int、size_t、long、char…..)

这里讲这个是提醒你有这么一个特例,而不推荐你使用,你只要看到别人的代码出现这个,你可以看得懂就好

或者说某些奇怪的规则:各大厂商写的规则,有些甚至会为了减少可移植性故意设置的

6、各种重载函数:大小比较 + 赋值重载

string.cpp

namespace bit
{bool string::operator<(const string& s) const{return _str < s._str;}bool string::operator<=(const string& s) const{return _str < s._str || _str == s._str;}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 (_str == s._str && _size == s._size && _capacity == s._capacity);}bool string::operator!=(const string& s) const{return !(*this == s);}// 赋值重载string& string::operator=(const string& s){string tmp(s);swap(tmp);return *this;}}

7、提取子串 与 清理:substr + clear

string.cpp

namespace bit
{string string::substr(size_t pos, size_t len){// 这个也要分长度的情况if (len >= _size - pos) {string tmp(_str + pos);return tmp;  // 这里需要传值返回}else {// 写法1:老实开空间+strncpy// 写法2::reserve 开空间 + for循环拷贝string sub;sub.reserve(len);for (size_t i = pos; i < pos + len; ++i) {sub += _str[i];}return sub;}}void string::clear(){_str[0] = '\0';}
}

8、string 的 神奇 swap 函数

这个 string 类的 swap 函数 是 用于帮助 string 类中的 拷贝构造函数 和 赋值重载运算符函数 写成 现代写法而发明

具体应用看这篇博客:

string.cpp

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

9、流插入和流提取:<< 和 >> 

之前我们实现 日期类 时,会将 operator<<  和  operator>> 写成类的友元函数

我们这里可以不写成友元函数

(一般 全局函数需要访问私有成员时,会写成友元函数,我们这里可以不访问私有,只需访问公公有成员)

这里也说明:流插入和流提取不一定要写成友元函数的

相关操作和优化技巧都在下面的代码中 解释了

string.h

namespace bit
{class string {// ....};// 写成全局函数ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);
}

string.cpp

namespace bit
{// 流插入ostream& operator<<(ostream& out, const string& s){for (size_t i = 0; i < s.size(); ++i) {cout << s[i];}return out;}// 流提取// 实现思想:到IO流中直接提取一个一个的 charistream& operator>>(istream& in, string& s){// 第一代写法:使用 cin ,但不能提取空格和换行// 不能这样写:cin 提取不了空格和换行(会被cin自动忽略),while会死循环/*char ch;cin >> ch;while (ch != ' ' && ch != '\n') {s += ch;in >> ch;}*/// 第二代写法:加入  IO流的 get 函数  和   清空函数 clear()// 使用 C++IO流的函数 get,可以获取 空格和换行// 同时这里还要一个问题:cin 是需要直接覆盖当前所有数据(而我们这里的思路是一个一个尾插的:s += ch;)// 因此 cin ,str += 之前,需要先清空原数据 使用前面的  clear函数// 代码如下://s.clear();//char ch = in.get();  // C++IO流的函数 get//while (ch != ' ' && ch != '\n') {//	s += ch;//	ch = in.get();//}// 第二代写法 的 问题:/*这里每次都是一个字符一个字符的相加 s += ch; 当字符非常长时,会面临频繁的扩容可以 reserve(100) 直接开大一点的空间,但如果我一次加入小几个字符,就有空间浪费了最好的办法:缓冲区思想,添加一个缓冲区数组*///  第三代写法: 添加一个缓冲区数组s.clear();int i = 0;char buff[128];char ch = in.get();  // C++IO流的函数 getwhile (ch != ' ' && ch != '\n') {buff[i++] = ch;if (i == 127) {  // 当数组满的时候,尾部添加一个 '\0' ,再尾插入 string 中buff[i] = '\0';s += buff;i = 0;}ch = in.get();}// 循环结束后,注意是否有剩余字符串未被加入 stringif (i != 0) {buff[i] = '\0';s += buff;}// 字符串很小时,没关系;字符串很大时,不用频繁地扩容return in;}
}

10、总代码

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include <utility> 
#include<assert.h>
using namespace std;namespace bit
{class string {public:typedef char* iterator;typedef const char* const_iterator; // 构造函数string(const char* str = "");string(const string& s);~string();const char* c_str() const;size_t size() const;char& operator[](size_t pos);const char& operator[](size_t pos) const;// 实现迭代器iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;void reserve(size_t n);void push_back(const char tmp);void append(const char* s);string& operator+=(const char ch);string& operator+=(const char* str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str); void erase(size_t pos, size_t len = npos);  // npos 的定义是一个 // 默认从零开始size_t find(char ch, size_t pos = 0);  size_t find(const char* str, size_t pos = 0);bool operator<(const string& s) const;bool operator<=(const string& s) const;bool operator>(const string& s) const;bool operator>=(const string& s) const;bool operator==(const string& s) const;bool operator!=(const string& s) const;//string& operator=(const string& s); // 有返回值目的是为了支持连续赋值 string& operator=(string tmp);void swap(string& s);string substr(size_t pos, size_t len = npos); // 一般这种指定长度 len 的,就会存在取完剩下的情况,都要加一个 nposvoid clear();private://int _Buff[16]; // 暂时不实现char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;const static size_t npos;};ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);
}

string.cpp

#include"string.h"namespace bit
{const size_t string::npos = -1;  // 静态成员变量类外定义string::string(const char* str): _size(strlen(str)){_str = new char[_size + 1];_capacity = _size;// 拷贝过来:strcpy(目的地,源头)strcpy(_str, str); }string::string(const string& s){string tmp(s._str);swap(tmp);}string::~string(){delete[] _str; _str = nullptr;_size = 0;_capacity = 0;}const char* string::c_str() const{return _str;}size_t string::size() const{return _size;}char& string::operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& string::operator[](size_t pos) const{assert(pos < _size);return _str[pos];}string::iterator string::begin(){return _str;}string::iterator string::end(){return _str + _size;}string::const_iterator string::begin() const{return _str;}string::const_iterator string::end() const{return _str + _size;}void string::reserve(size_t n){if (n > _capacity) {// 手动扩容char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n; // capacity 不用 +1,不计算 '\0'}}void string::push_back(const char ch){// 一次开两倍,用 reserve,真实空间不够就扩,空间够也不缩容if (_size == _capacity) {size_t  newCapacty = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacty);}_str[_size] = ch;_str[_size + 1] = '\0';_size++;}void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity) {// 要多少加多少reserve(_size + len);}// 可以使用 strcat 追加字符串:遍历字符串,找到 \0 ,从这个位置开始追加字符串// 遍历一遍效率较低// 使用 strcpy 指定起始位置:_str + _size,刚好是 \0 的位置strcpy(_str + _size, str); _size += len;}string& string::operator+=(const char ch){this->push_back(ch);return *this;}string& string::operator+=(const char* str){this->append(str);return *this;}void string::insert(size_t pos, char ch){assert(pos <= _size); // 这个杭哥没写// 插入字符,会使字符串变长,要考虑扩容if (_size == _capacity) {size_t  newCapacty = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacty);}// 在 pos 插入,后面的字符向后移// '\0' 不用单独处理:下面第一个处理的就是 '\0'for (int i = _size; i >= (int)pos; --i) {_str[i+1] = _str[i];}_str[pos] = ch;_size++;}void string::insert(size_t pos, const char* str){assert(pos <= _size);int len = strlen(str);// 插入字符,会使字符串变长,要考虑扩容if (_size+len >= _capacity) {reserve(_size + len);}for (int i = _size + len; i > pos+len-1; --i) {_str[i] = _str[i - len];}for (int i = 0; i < len; ++i) {_str[pos+i] = str[i];}_size += len;}void string::erase(size_t pos, size_t len){assert(pos < _size);if (len == npos || _size - pos <= len) { _str[pos] = '\0';_size = pos;}else {strcpy(_str + pos, _str + pos + len);_size -= len;}//_size -= len; 不能直接 - len,万一len很大呢?}size_t string::find(char ch, size_t pos){assert(pos < _size);for (int i = pos; i < _size; ++i) {if (_str[i] == ch) return i;}return npos;}size_t string::find(const char* str, size_t pos){const char* p = strstr(_str + pos, str);if (p == NULL) return string::npos;  return p - _str;}bool string::operator==(const string& s) const{return (strcmp(_str, s._str) == 0 && _capacity == s._capacity && _size == s._size);}bool string::operator<(const string& s) const{// 字典序比较大小:这里好像可以直接比较 char* 类型的字符串// 也可以用 strcmp// return strcmp(_str, s._str) < 0;return _str < s._str;}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 && *this == s);}bool string::operator>=(const string& s) const{return !(*this < s);}string& string::operator=(string tmp){swap(tmp);return *this;}// 注意:这三种代码效率上没有很大差别,但是代码精简了void string::swap(string& s)  // 注意:这里不能写 (const string& s),库里面的swap没有重载 const 类型的变量{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// 取字符子串,不只是取字符串的 str,是搞一个新的 stringstring string::substr(size_t pos, size_t len){if (len >= _size - pos) {string sub(_str + pos); // 直接传一个 char* 有多少取多少return sub;}else {string sub;sub.reserve(len);for (size_t i = 0; i < len; ++i) {sub += _str[pos + i];}return sub;}}void string::clear(){_str[0] = '\0'; // 直接毁灭所有数据_size = _capacity = 0;}ostream& operator<<(ostream& out, const string& s){for (size_t i = 0; i < s.size(); ++i) {cout << s[i];}return out;}istream& operator>>(istream& in, string& s){s.clear();int i = 0;char buff[128];char ch = in.get();  // C++IO流的函数 getwhile (ch != ' ' && ch != '\n') {buff[i++] = ch;if (i == 127) {buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0) {buff[i] = '\0';s += buff;}// 字符串很小时,没关系;字符串很大时,不用频繁地扩容return in;}
}

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

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

相关文章

10 VUE Element

文章目录 VUE1、概述2、快速入门3、Vue 指令4、生命周期5、案例 Elemant1、快速入门2、Element 布局3、常用组件-案例 VUE 1、概述 Vue 是一套前端框架&#xff0c;免除原生JavaScript中的DOM操作&#xff0c;简化书写基于MVVM(Model-View-ViewModel)思想&#xff0c;实现数据…

EB Tresos 基于S32K3芯片 ICU模块实现gpio外部中断配置[后续更新实现icu模块的其他功能]

环境&#xff1a;eb tresos 27.0.1 port 模块配置&#xff1a; 选择一个具有erq功能的引脚并配置为erq功能。如下我选择的是 PTB0 -EIRQ[8] - SIUL2_EXT_IRQ_8_15_ISR Platform 模块配置 在这个模块中配置中断的开关以及中断句柄 ICU模块配置 具体配置参考博客&#xff1a;…

一文总结代理:代理模式、代理服务器

概述 代理在计算机编程领域&#xff0c;是一个很通用的概念&#xff0c;包括&#xff1a;代理设计模式&#xff0c;代理服务器等。 代理类持有具体实现类的实例&#xff0c;将在代理类上的操作转化为实例上方法的调用。为某个对象提供一个代理&#xff0c;以控制对这个对象的…

本地部署Graphhopper路径规划服务(graphhopper.sh启动版)

文章目录 文章参考源码获取一、配置Java环境变量二、配置Maven环境变量三、构建graphhopper步骤1. 下载数据2. 配置graphhopper配置文件config-example.yml3. 在项目中启动命令行执行./graphhopper.sh build3.1|、遇到的问题3.1.1、pom.xml中front-maven-plugin-无法下载npm6.1…

linux nginx 命令记录,和转发

nginx: 查看配置文件&#xff1a;sudo find / -name nginx.conf 配置文件&#xff1a;/etc/nginx/nginx.conf 检查nginx.conf文件正确性 nginx -t -c /path/to/nginx.conf 或者 有nginx命令执行 nginx -t 查找nginx 可执行文件&#xff1a;which nginx /usr/sbin/nginx 安装Ng…

C语言中内存四区的本质分析

数据类型本质分析 1 数据类型的概念 “类型”是对数据的抽象 类型相同的数据有相同的表示形式、存储格式以及相关的操作 程序中使用的所有数据都必定属于某一种数据类型 2 数据类型的本质 数据类型可理解为创建变量的模具&#xff1a;是固定内存大小的别名。 数据类型的作…

【cuda】在老服务器上配置CUDA+cmake开发环境

在老服务器上配置CUDA+cmake开发环境 服务器x86_64,系统是centos8,cmake版本是2.8.10 背景 不能更换服务器系统无法下载CUDA安装包解决思路 使用可以至此CUDA开发的较老的cmake直接移植CUDA环境配置环境中遇到的问题 服务器无法编译cmake移植CUDA编译器及部分库,代码无法…

vue3 使用Mock

官网: http://mockjs.com/ 安装 npm install mockjs -Dsteps1: main.js 文件引入 import /api/mock.jssteps2: src/api/mock.js import Mock from mockjs import homeApi from ./mockData/home /*** 1.拦截的路径:mock拦截了正常NetWork/网络请求,数据正常响应* 2.方法* …

PVE环境中调整虚拟机磁盘大小

我的希望将PVE中的虚拟机磁盘调整一下&#xff0c;增加20GB。在查询了一些资料后&#xff0c;做一下总结教程。 环境是 PVE8.2.2 版本&#xff0c;虚拟机系统是centos7.9.2009-minimal&#xff0c; 安装系统时划分磁盘分区方式是默认分区方式&#xff08;不同分区方式下&#…

Redis的相关基础了解

1. 什么是nosql nosql【not only sql】不仅仅是sql。所有非关系型数据库的统称&#xff0c;除去关系型数据库之外都是非关系数据库 2. NOSQL和RDBMS的区别 RDBMS——关系型数据库的通常 高度组织化 结构化 数据结构化查询语言(SQL) sql语句数据和关系都存储在单独的表中数据操纵…

八、桥接模式

文章目录 1 基本介绍2 案例2.1 OperatingSystem 抽象类2.2 LinuxOS 类2.3 WindowsOS 类2.4 FileOperation 类2.5 FileAppender 类2.6 FileReplicator 类2.7 Client 类2.8 Client 类运行结果2.9 总结 3 各角色之间的关系3.1 角色3.1.1 Implementor ( 实现者 )3.1.2 ConcreteImpl…

ARTMO Table ‘db1.test_mla_result‘ doesn‘t exist解决方案

com.mysql.jdbc.JDBC4PreparedStatement3f3c966c: describe test_mla_result; Java exception occurred: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table db1.test_mla_result doesnt exist解决方案&#xff1a; 打开MySQL的command Line, 输入SHOW TABLES…

基于微信小程序的高校排课系统 /基于微信小程序的排课管理系统/课程管理系统

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&a…

Vue2和Vue3实战代码中的小差异(实时更新)

目录 前言1. 未使用自闭合标签2. 事件名连字符3. 换行符4. 弃用.sync 前言 以下文章实时更新&#xff0c;主打记录差异 1. 未使用自闭合标签 104:7 error Require self-closing on Vue.js custom components (<el-table-column>) vue/html-self-closing✖ 1 problem…

Canal监听Mysql回写Redis

目录 一、canal服务端 1.1 下载 1.2 解压 1.3 配置 1.4 启动 1.5 查看 二、canal客户端&#xff08;Java编写业务程序&#xff09; 2.1 SQL脚本 2.2 写POM 2.3 写Yaml 2.4 写业务类 2.4.1.项目结构 2.4.2 Utils.RedisUtil 2.4.3 biz.RedisCanalClientExample 一、…

麦田物语第十五天

系列文章目录 麦田物语第十五天 文章目录 系列文章目录一、构建游戏的时间系统二、时间系统 UI 制作总结 一、构建游戏的时间系统 在该游戏中我们要构建年月日天时分秒等时间的概念&#xff0c;从而实现季节的更替&#xff0c;昼夜的更替等&#xff08;不同的季节可以播种不同…

基于gaussian计算NICS值评估分子体系的芳香性和反芳香性

计算分子NICS值的基本流程 其中第1行的Bq即为NICS(0)虚原子对应的位置&#xff0c;7.5538为NICS(0)对应虚原子位置处的各向同性化学位移屏蔽值。 由于NICS值为各向同性化学屏蔽值的负值&#xff0c;因此苯的NICS(0)和NICS(1)分别为-7.5538和-10.5301&#xff0c;这也表明苯分…

通信类IEEE会议——第四届通信技术与信息科技国际学术会议(ICCTIT 2024)

[IEEE 独立出版&#xff0c;中山大学主办&#xff0c;往届均已见刊检索] 第四届通信技术与信息科技国际学术会议&#xff08;ICCTIT 2024&#xff09; 2024 4th International Conference on Communication Technology and Information Technology 重要信息 大会官网&#xf…

python生成系统测试数据

开发系统的时候,为了系统可以更好的进行测试,一般需要准备测试数据,以便可以顺利的对各种场景进行测试,使用两张表来说明怎么快速生成测试数据。 1.用户表 一般登录的时候,需要用到用户表 用户表字段如下: 用户名、密码、姓名、性别、邮箱、手机号、用户类型、地址 下…

Linux进程间通信(管道+共享内存)

进程间通信&#xff08;interprocess communication&#xff0c;简称 IPC&#xff09;指两个进程之间的通信。系统中的每一个进程都有各自的地址空间&#xff0c;并且相互独立、隔离&#xff0c;每个进程都处于自己的地址空间中。所以同一个进程的不同模块&#xff08;譬如不同…