C++ String的模拟实现

一.基本框架

1.成员变量

string类的成员变量分别是存储字符串的一段空间_str,表示字符串的有效字符个数_size和表示存储有效字符空间的_capacity。

private:char *_str;size_t _size;// 有效字符的个数size_t _capacity;// 存储有效字符的空间

还有一个string类的特殊成员,npos表示size_t的最大值,一般表示表示string的结束位子。

private:char* _str;size_t _size;size_t _capacity;public:const static size_t npos;
};const size_t string::npos = -1;​

-1也可以表示size_t类型的最大值。

注意:

  1. 对于【npos】这个参数,首先我们知道对于静态成员变量,它的规则是在类外定义,类里面声明,定义时不加static关键字;
  2. 但如果静态成员变量有const修饰,这时它可以在类内直接进行定义,这样的特性只针对于整型,对于其他类型则是不适用的;
  3. npos就是const static修饰的成员变量,可以直接在类内进行定义。

 2.构造函数

 1.2.1 全缺省构造函数


  首先,string类的有参构造函数其实可以设计为全缺省函数,缺省值设置为空串,当没有传入参数时使用缺省值,将_str设置为空串,这样就可以不需要定义无参构造函数了。其次,如果传入了参数,就将_size和_capacity设置为形参的长度,然后开一段大小和形参相同的空间给_str,最后将值拷贝过去即可。

  

	//构造函数string(const char* str = ""):_size(strlen(str)), _capacity(_size){//cout << "构造函数" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

1.2.2 拷贝构造函数 

 string的拷贝构造函数,关于string的拷贝构造函数,我的建议是用传入的string类型中的_str去构造一个临时变量,然后交换临时变量和当前类内部的成员变量的内容。

  

void swap(string& s) {std::swap(s._str, _str);std::swap(s._size, _size);std::swap(s._capacity, _capacity);}//拷贝构造string(const string& s):_str(nullptr), _size(0), _capacity(0){//cout << "拷贝构造函数" << endl;string tmp(s._str);swap(tmp);}

1.2.3 移动构造

  
   没啥好说的,直接交换右值即可。

	//移动构造string(string&& s) noexcept{//cout << "移动构造函数" << endl;swap(s);}

3 赋值重载

1.3.1 默认重载

默认重载的赋值运算符功能很简单,就是将原有对象的所有成员变量一一赋值给新对象,并且返回新对象,这和默认拷贝构造函数的功能类似。

  因此其代码和拷贝构造函数代码差不多,只不过不用初始化,因为在构造时已经初始化完毕。

  

//拷贝赋值string& operator=(const string& s) {//cout << "拷贝赋值函数" << endl;string tmp(s);swap(tmp);return *this;}

1.3.2 移动重载

  直接交换资源就好。

//移动赋值
string& operator=(string&& s) noexcept {//cout << "移动赋值函数" << endl;swap(s);return *this;
}

4.析构函数

 把_str空间释放,置空,并且把_size和_capacity 置0 

//析构函数~string() {delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}

二 容量函数

2.1 size()

顾名思义返回字符串的长度(以字符数为单位)

size_t size() const {return _size;
}

2.2 capacity()

返回当前为 string 分配的存储空间的大小,以字符表示。

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

2.3 reserve()

  表示请求更改容量(_capacity),使字符串容量适应计划的大小更改为最多 n 个字符。

注意是有效字符,不包含标识字符,而在具体实现的时候,我们在底层多开一个空间给\0。

这里我们的做法是,如果n >_capacity,那么我们就开始扩容, 先 创建一个新数组,把其大小扩容为n+1,留一个空间给 \0  ,然后将原本的 str中的内容拷贝进新数组,然后delete str开的空间,最后将str指向新空间,更新_capacity的内容。

代码如下:

	void reserve(size_t n) {if (n > _capacity) {char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}

2.4 resize()

其实它的情况大体上可以分为插入数据和删除数据两种情况。

1.对于插入数据:直接调用【reserve】提前预留好空间,然后搞一个for循环将字符ch尾插到数组里面去,最后再在数组末尾插入一个\0标识字符;
2.对于删除数据:如果 n 小于当前字符串长度,则当前值将缩短为 n ,删除第 n 个字符以后的字符,然后重置一下_size的大小为n即可。

代码如下:

	void resize(size_t n, char ch = '\0') {if (_size > n) {_str[n] = '\0';_size = n;}else{reserve(n);while (_size < n) {_str[_size] = ch;++_size;}_str[n] = '\0';}}

2.5 clear()

  顾名思义就是清除字符串,擦除string的内容,该内容变为空字符串(长度为 0 个字符)。

	void clear(){_str[0] = '\0';_size = 0;}

三 元素访问

3.1  operator[]

元素访问操作相对来说用的最多的就是operator[] .

  对它进行调用时可能进行的是写操作,也可能进行读操作,所以为了适应const和非const对象,operator[]应该实现两个版本的函数,并且这个函数处理越界访问的态度就是assert直接断言,而at对于越界访问的态度是抛异常。

char& operator[](size_t pos) {assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const {assert(pos < _size);return _str[pos];}

3.2 find()

  查找字符串中的第一个匹配项, 在string中搜索由其参数指定的序列的第一个匹配项。

 直接从pos位置遍历即可。

	size_t find(char ch, size_t pos = 0) {for (size_t i = pos; i < _size; i++) {if (_str[i] == ch){return i;}}//找不到返回nposreturn npos;}
size_t find(const char* sub, size_t pos = 0) {const char* p = strstr(sub + pos, sub);if (p) {return p - _str;}else {return npos;}
}

四 修改 

 4.1 push_back()

在字符串尾部插入单个数据。

首先,当我们的_size ==_capacity 的时候,我们要准备扩容,但是这里我们该如何扩容呢?

我的建议是,如果是初次扩容的时候,建议扩为四个字节,但以后我们每次都扩容为二倍,然后再_size的位置插入一个数据即可,注意补'\0',

  在后续完成了insert的时候,也可以复用insert函数。

 代码如下:

	void push_back(char ch) {if (_size == _capacity) {reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}

4.2 append()

追加到字符串, 通过在当前值的末尾附加其他字符来扩展。

  1. 我们可以直接调用strcpy接口来进行字符串的尾插,但是需要注意一点,那就是【string】类的字符串函数是不会进行自动扩容的,所以我们需要判断一下是否需要进行扩容,在空间预留好的情况下进行字符串的尾插即可实现。
  2. 但注意,这里我们不能扩容两倍,假如我们扩容两倍后的空间,依旧不够追加的字符串的大小呢?我们这里建议直接扩容为 待插入的字符串长度+_size
  3. 其次,如果已经实现了【insert】函数的情况下。我们可以直接复用【insert】函数也可实现对应的操作。

代码实现:

	void append(const char* str) {size_t len = strlen(str);if (_size + len > _capacity) {reserve(_size + len);}strcpy(_str + _size, str);_size += len;}

4.3 operator+=

追加到字符串,通过在当前值的末尾附加其他字符来扩展

在这里我们只实现添加字符和字符串的操作。

我们可以直接复用【push_back】和 【append] 的操作来实现。

代码如下:

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

4.4 insert()

插入到字符串中,在 pos(或 p)指示的字符之前将其他字符插入

 4.4.1 指定位置插入字符

  1. 首先assert先断言,确保插入位置合法
  2. 如果插入的总长度大于当前容量,就进行扩容,将容量扩展到 0或者2倍
  3. 接着,从字符串的末尾开始,依次往后移,给插入字符留下足够的空间
  4. 最后,使用一个循环将指定数量的字符ch插入到指定位置pos之后。
	// insert(0, 'x')void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity) {reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos) {_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;}

4.4.2 指定位置插入字符串

  1.  首先使用assert断言来确保插入位置pos不超过当前字符串的长度_size
  2. 如果插入后的总长度_size+len超过当前容量_capacity,就扩容到_size+len的长度
  3. 接着,将字符串的末尾开始,依次往后移动字符,为插入字符串成留出足够的位置。
  4. 然后使用一个循环将字符串中字符赋值到pos之后的指定位置
  5. 最后,更新字符串的长度_size
	void insert(size_t pos, const char* str) {assert(pos <= _size);size_t len = strlen(str);if (len + _size > _capacity) {/*reserve(_capacity == 0 ? 4 : _capacity * 2);*/reserve(_size + len);}size_t end = _size;while (end >= pos&&end!=npos) {_str[end + len] = _str[end];--end;}strncpy(_str + pos, str, len);_size += len;}

注意:这里在头部插入时有可能end会等于-1 ,插入字符串时需要判断字符串长度,才能进行元素的移动和元素的写入。

4.5 erase() 函数 

意思很简单,就是从字符串中删除字符

对于删除,思路很简单,分为两种情况下的删除:

1.如果当前位置加上要删除的长度大于字符串的长度,即【 pos + len >= _size】,此时的意思即为删除pos之后的所有元素;
2.除了上述情况,就是在字符串内正常删除操作。我们只需利用strcpy来进行,将pos+len之后的字符串直接覆盖到pos位置,这样实际上就完成了删除的工作。

代码如下:
 

void erase(size_t pos, size_t len = npos) {assert(pos < _size);size_t end = pos+len;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;}
}

五 字符串操作

  5.1 c_str()

获取等效的 C 字符串,返回指向一个数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),表示string对象的当前值

const char* c_str() const {return _str;
}

 5.2 比较操作

直接上代码吧。

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

5.3 substr

从字符串中提取子字符串

  1. 首先,使用断言assert验证起始位置pos是否小于字符串的长度,避免越界。
  2. 如果len为npos或者pos+len超过了字符串的长度,n被设置为从pos到字符串末尾的长度
  3. 接下来,创建一个临时的字符串对象tmp,并使用reserve()为该字符串分配足够的容量以容纳字符串
  4. 然后,使用一个循环,依次将pos到pos+n的字符添加到tmp字符串中
  5. 最后,返回存储子字符串tmp字符串对象
		//截取string中的一段string substr(size_t pos, size_t len = npos) {string s;size_t end = pos + len;if (len == npos || pos + len > _size) {len = _size - pos;end = _size;}s.reserve(len);for (size_t i = pos; i < len; i++) {s += _str[i];}return s;}

六 非成员函数重载

6.1 operator<<

 将字符串插入流, 将符合 str 值的字符序列插入到 os 中。

  1. 函数的参数是一个输出流对象和一个常量字符串引用
  2. 遍历每一个字符,将字符依次输出到给定的流对象中
  3. 最后,函数返回输出流对象的引用
	ostream& operator<<(ostream& out, const string& s){for (auto ch : s)out << ch;return out;}

6.2 operator>>

从流中提取字符串, 输入流中提取字符串,将序列存储在 str 中,该序列被覆盖(替换 str 的先前值)

  1. 函数的参数是一个输入流对象和一个字符串引用
  2. 首先清空字符串,然后读取输入流中的字符
  3. 在处理输入之前,函数会检查并跳过缓存区前面的空格或者换行符
  4. 然后,将字符一个接一个添加到字符缓冲区中,直到遇到换行符或者空格为止
  5. 如果字符缓冲区已满,将缓冲区的内容追加到字符串中,并清空缓冲区
  6. 最后,函数将最后一个字符缓冲区的内容追加到字符串中(如果缓冲区不为空)。函数返回输入流对象的引用。
istream& operator>>(istream& in, string& s)
{s.clear();char buff[129]{};size_t i = 0;char ch;ch = in.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;
}

七 完整代码+ 测试代码

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
#include<assert.h>
using namespace std;namespace My {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(const char* str = ""):_size(strlen(str)), _capacity(_size){//cout << "构造函数" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s) {std::swap(s._str, _str);std::swap(s._size, _size);std::swap(s._capacity, _capacity);}//拷贝构造string(const string& s):_str(nullptr), _size(0), _capacity(0){//cout << "拷贝构造函数" << endl;string tmp(s._str);swap(tmp);}//拷贝赋值string& operator=(const string& s) {//cout << "拷贝赋值函数" << endl;string tmp(s);swap(tmp);return *this;}//移动构造string(string&& s) noexcept{//cout << "移动构造函数" << endl;swap(s);}//移动赋值string& operator=(string&& s) noexcept {//cout << "移动赋值函数" << endl;swap(s);return *this;}//析构函数~string() {delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}char& operator[](size_t pos) {assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const {assert(pos < _size);return _str[pos];}size_t capacity() const {return _capacity;}size_t size() const {return _size;}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 ch = '\0') {if (_size > n) {_str[n] = '\0';_size = n;}else{reserve(n);while (_size < n) {_str[_size] = ch;++_size;}_str[n] = '\0';}}size_t find(char ch, size_t pos = 0) {for (size_t i = pos; i < _size; i++) {if (_str[i] == ch){return i;}}//找不到返回nposreturn npos;}size_t find(const char* sub, size_t pos = 0) {const char* p = strstr(sub + pos, sub);if (p) {return p - _str;}else {return npos;}}//截取string中的一段string substr(size_t pos, size_t len = npos) {string s;size_t end = pos + len;if (len == npos || pos + len > _size) {len = _size - pos;end = _size;}s.reserve(len);for (size_t i = pos; i < len; i++) {s += _str[i];}return s;}void push_back(char ch) {if (_size == _capacity) {reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* str) {size_t len = strlen(str);if (_size + len > _capacity) {reserve(_size + len);}strcpy(_str + _size, str);_size += len;}string& operator+=(char ch) {push_back(ch);return *this;}string& operator+=(const char* str) {append(str);return *this;}// insert(0, 'x')void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity) {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(_capacity == 0 ? 4 : _capacity * 2);*/reserve(_size + len);}size_t end = _size;while (end >= pos&&end!=npos) {_str[end + len] = _str[end];--end;}strncpy(_str + pos, str, len);_size += len;}void erase(size_t pos, size_t len = npos) {assert(pos < _size);size_t end = pos+len;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;}}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 || *this == s;}bool operator>(const string& s) const {return !(*this <= s);}bool operator>=(const string& s) const {return !(*this < s);}bool operator!=(const string& s) const {return !(*this == s);}void clear(){_str[0] = '\0';_size = 0;}private:char* _str;size_t _size;size_t _capacity;public:const static size_t npos;};const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& s){for (auto ch : s)out << ch;return out;}istream& operator>>(istream& in, string& s){s.clear();char buff[129]{};size_t i = 0;char ch;ch = in.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;}void test_string1(){string s1("hello world");cout << s1.c_str() << endl;string s2;cout << s2.c_str() << endl;for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << " ";}cout << endl;string::iterator it = s1.begin();while (it != s1.end()){(*it)++;cout << *it << " ";++it;}cout << endl;for (auto& ch : s1){ch++;cout << ch << " ";}cout << endl;cout << s1.c_str() << endl;}void test_string2(){string s1("hello world");cout << s1.c_str() << endl;s1.push_back(' ');s1.append("hello bit hello bit");cout << s1.c_str() << endl;s1 += '#';s1 += "*********************";cout << s1.c_str() << endl;string s2;s2 += '#';s2 += "*********************";cout << s2.c_str() << endl;}void test_string3(){string s1("hello world");cout << s1.c_str() << endl;s1.insert(5, '%');cout << s1.c_str() << endl;s1.insert(s1.size(), '%');cout << s1.c_str() << endl;s1.insert(0, '%');cout << s1.c_str() << endl;}void test_string4(){string s1("hello world");string s2("hello world");cout << (s1 >= s2) << endl;s1[0] = 'z';cout << (s1 <  s2) << endl;cout << s1 << endl;cin >> s1;cout << s1 << endl;/*char ch1, ch2;cin >> ch1 >> ch2;*/}void test_string5(){string s1("hello world");s1.insert(5, "abc");cout << s1 << endl;s1.insert(0, "xxx");cout << s1 << endl;s1.erase(0, 3);cout << s1 << endl;s1.erase(5, 100);cout << s1 << endl;s1.erase(2);cout << s1 << endl;}void test_string6(){string s1("hello world");cout << s1 << endl;s1.resize(5);cout << s1 << endl;s1.resize(25, 'x');cout << s1 << endl;}void test_string7(){string s1("test.cpp.tar.zip");//size_t i = s1.find('.');//size_t i = s1.rfind('.');//string s2 = s1.substr(i);//cout << s2 << endl;string s3("https://legacy.cplusplus.com/reference/string/string/rfind/");//string s3("ftp://www.baidu.com/?tn=65081411_1_oem_dg");// 协议// 域名// 资源名string sub1, sub2, sub3;size_t i1 = s3.find(':');if (i1 != string::npos)sub1 = s3.substr(0, i1);elsecout << "没有找到i1" << endl;size_t i2 = s3.find('/', i1 + 3);if (i2 != string::npos)sub2 = s3.substr(i1 + 3, i2 - (i1 + 3));elsecout << "没有找到i2" << endl;sub3 = s3.substr(i2 + 1);cout << sub1 << endl;cout << sub2 << endl;cout << sub3 << endl;}void test_string8(){string s1("hello world");string s2 = s1;cout << s1 << endl;cout << s2 << endl;string s3("xxxxxxxxxxxxxxxxxxx");s2 = s3;cout << s2 << endl;cout << s3 << endl;}void test_string9(){string s1("hello world");cin >> s1;cout << s1 << endl;cout << s1.size() << endl;cout << s1.capacity() << endl;}
}

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

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

相关文章

【数据结构】线段树算法总结(单点修改)

知识概览 用作单点修改的线段树有4个操作&#xff1a; pushup&#xff1a;由子节点的信息计算父节点的信息build&#xff1a;初始化一棵树modify&#xff1a;修改一个区间query&#xff1a;查询一个区间 线段树用一维数组来存储&#xff1a; 编号是x的节点&#xff0c;它的父节…

LM358 双运算放大器 用于误差放大器和电压跟随器等电路

LM358 内部包括有两个独立的、高增益、内部频率补偿的双运算放大器&#xff0c;适合于电源电压范围很宽的单电源审用&#xff0c;也适用于双电源工作模式&#xff0c;在推荐的工作条件下&#xff0c;由源电流与电源电压无关。它的使用范围句括传感放大器、直流增益模组&#xf…

D40|单词拆分+多重背包

139.单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用 输入: s "leetcode", wordDict ["le…

C语言的分支和循环语句

各位少年&#xff0c;今天和大家分享的是分支语句循环体语句&#xff0c;C语言是结构体的程序设计语言&#xff0c;这里的结构指的是&#xff08;顺序结构&#xff09;&#xff08;选择结构&#xff09;&#xff08;循环结构&#xff09;C语言是能够实现这三种结构的&#xff0…

原型链污染[JavaScript]

一、原型链污染 此类型一般存在以nodejs编写的后端程序当中&#xff0c;其中Express是一个流行的Node.js Web应用程序框架 1.JavaScript 1.1 原理 引入 解释&#xff1a;直接先读代码来理解原型链污染 // let jack {b:2} console.log(typeof jack) // 它的类型是obejct con…

ECMAScript基础入门:从语法到应用

在此之前我以及发布过关于JavaScript基础知识点大家也可以参考 大家有关于JavaScript知识点不知道可以去 &#x1f389;博客主页&#xff1a;阿猫的故乡 &#x1f389;系列专栏&#xff1a;JavaScript专题栏 &#x1f389;ajax专栏&#xff1a;ajax知识点 &#x1f389;欢迎关注…

nodejs+vue+ElementUi摄影作品图片分享工作室管理系统

第1周 2.21&#xff5e;2.27 查阅资料&#xff0c;学习vscode开发平台和vue框架技术 第2周 2.28&#xff5e;3.6 对软件功能需求进行分析, 软件功能模块划分及软件界面设计 第3周 3.7&#xff5e;3.13 撰写并提交毕业设计开题报告、英文资料翻译 第4周 3.14&#xff5…

将html转化成图片

如何将指定html内容转化成图片保存&#xff1f;这个问题很值得深思&#xff0c;实际应用中也很有价值。最直接的想法就是使用canvas&#xff0c;熟悉canvas的同学可以尝试一下。这里不做太多的说明&#xff0c;本文采用html2canvas库来实现。 html2canvas库的使用非常简单&…

2024年pmp考试还有多久啊?怎么备考?

一般来说每年3、6、9、12月考试&#xff0c;一年四次&#xff0c;具体时间以官网通知为准。报考时间提前2个月报名&#xff0c;2023年的最后一次考试时间是11月&#xff0c;已经截止报名了。所以下一次考试就得等到2024年3月了。 想知道怎么备考先来分析一下现在的“新”考纲&…

基于ERC20代币协议实现的去中心化应用平台

文章目录 内容简介设计逻辑ERC20TokenLoanPlatform 合约事件结构体状态变量函数 Remix 运行实现部署相关智能合约存款和取款贷款和还款 源码地址 内容简介 使用 solidity 实现的基于 ERC20 代币协议的借贷款去中心化应用平台(极简版)。实现存款、取款、贷款、还款以及利息计算的…

KubeSphere金丝雀发布流量分布调节不生效(将所有流量按比例分配给灰度发布版本)

如题 金丝雀发布按照流量比例访问不能生效 1、自制应用生成的路由添加注释: nginx.ingress.kubernetes.io/service-upstream:"true" 2、项目网关开启 3、完成 以上&#xff0c;祝好。

DBA-MySql面试问题及答案-上

文章目录 1.什么是数据库?2.如何查看某个操作的语法?3.MySql的存储引擎有哪些?4.常用的2种存储引擎&#xff1f;6.可以针对表设置引擎吗&#xff1f;如何设置&#xff1f;6.选择合适的存储引擎&#xff1f;7.选择合适的数据类型8.char & varchar9.Mysql字符集10.如何选择…