C++初阶—string类
第一章:为什么要学习string类
1.1 C语言中的字符串
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
1.2 面试题
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
第二章:标准库中的string类
2.1 string类
- 字符串是表示字符序列的类
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
- string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
- string是表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
- 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std;
2.2 string类的常用接口说明
1. string类对象的常见构造
函数名称 | 功能说明 |
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
2. string类对象的容量操作
函数名称 | 功能说明 |
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间** |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
注意:
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
3. string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[] (重点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
4. string类对象的修改操作
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
注意:
- 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
- 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
5. string类非成员函数
函数 | 功能说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
string类内部就是char*,为什么不直接用char*而是要封装成string类。
为了适应不同的编码(UTF-8、UTF-16、UTF-32等),从而表示不同国家的文字
接口示例
#include <iostream>
#include <string>
#include <list>
using namespace std;void test_string1() {string s1;string s2("hello");//输入 输出cin >> s1;//C++可以按需提取,C语言只能使用固定大小的数组cout << s1 << endl;cout << s2 << endl;//拼接字符串//相比strcat,可读性更强;strcat空间不够,不能自动扩容;//strcat要先找\0,如果字符串较长,消耗时间string ret1 = s1 + s2;//s1+s1也可以,两个s1的内容拷贝到ret1(对象+对象)cout << ret1 << endl;string ret2 = s1 + "我来了";//对象+字符串cout << ret2 << endl;
}void test_string2() {string s1("hello world");//直接调用构造函数string s2 = "hello world";//构造+拷贝构造+优化,本质是隐式类型转换(单参数构造函数支持隐式类型转换)//遍历stringfor (size_t i = 0; i < s1.size(); i++)cout << s1[i] << " ";//读cout << endl;for (size_t i = 0; i < s1.size(); i++)s1[i]++;//写cout << s1 << endl;//ifmmp!xpsme//遍历的第二种方式:迭代器string::iterator it = s1.begin();//iterator是在string这个类里面定义的,所以需要加string类域while (it != s1.end()) { //end指向最后一个有效字符的后一个位置,即\0cout << *it << " ";//读++it;}cout << endl;it = s1.begin();//while (it < s1.end()) //这里可以改为<,但不建议//因为迭代器是通用的,string空间是连续的,但其他数据结构不一定,比如listwhile (it != s1.end()) {*it = 'a';//写++it;}cout << s1 << endl;//aaaaaaaaaaa//链表示例list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator lit = lt.begin();while (lit != lt.end()) { //链表只能用!=,不能用<cout << *lit << " ";++lit;}cout << endl;
}void test_string3() {string s1("hello world");//正向遍历string::iterator it = s1.begin();//iterator是在string这个类里面定义的,所以需要加string类域while (it != s1.end()) { //end指向最后一个有效字符的后一个位置,即\0cout << *it << " ";//读++it;}cout << endl;//反向遍历//string::reverse_iterator rit = s1.rbegin();auto rit = s1.rbegin();//rbegin的返回值就是反向迭代器,所以可以使用autowhile (rit != s1.rend()) {cout << *rit << " ";//d l r o w o l l e h++rit;//这里++是从后向前倒着走}cout << endl;//原理:编译时编译器替换成迭代器//读for (auto ch : s1) //范围for不支倒序遍历。如果想修改需要引用cout << ch << " ";cout << endl;//写for (auto& ch : s1)ch++;cout << s1 << endl;
}//void func(string s) //不推荐传值传参,会调用拷贝构造
void func(const string& s) {//string::iterator it = s.begin();//迭代器支持读写,但参数有const修饰,所以报错//string::const_iterator it = s.begin();auto it = s.begin();while (it != s.end()) {//const迭代器不支持写//*it = 'a';cout << *it << " ";++it;}cout << endl;//string::const_reverse_iterator rit = s.rbegin();auto rit = s.rbegin();while (rit != s.rend()) {cout << *rit << " ";++rit;}cout << endl;
}void test_string4() {string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");func(s1);string s2(s1);//拷贝构造string s3(s1, 6, 5);//从下标6位置开始取5个字符 cout << s3 << endl;//world//npos 是一个静态成员常量值,无符号整形-1(即最大值),表示“直到字符串的末尾”。string s4(s1, 6);//从从下标6位置开始取到结束。最后一个是缺省参数nposcout << s4 << endl;//不使用缺省参数,也可以计算string s5(s1, 6, s1.size() - 6);//size是字符串长度cout << s5 << endl;string s6(10, 'a');//用10个a初始化cout << s6 << endl;string s7(++s6.begin(), --s6.end());//用迭代器区间初始化cout << s7 << endl;//aaaaaaaa//三种赋值方法string s8;s8 = s7;s8 = "xxx";s8 = 'y';
}void test_string5() {string s1("hello world");cout << s1.size() << endl;//11 size具有通用性cout << s1.length() << endl;//11cout << s1.capacity() << endl;//15s1.clear();//仅清除数据,不释放空间cout << s1.size() << endl;//0cout << s1.capacity() << endl;//15//为什么结尾是\0//需要兼容C语言,而且有些场景只能调C的接口//文件示例string filename;cin >> filename;FILE* fout = fopen(filename.c_str(), "r");//fopen函数第一个参数是字符串//const char* c_str() const; 返回一个指向数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),表示字符串对象的当前值。
}void test_string6() {string s;size_t old = s.capacity();cout << "初始" << s.capacity() << endl;for (size_t i = 0; i < 100; i++) {s.push_back('x');if (s.capacity() != old) {cout << "扩容:" << s.capacity() << endl;old = s.capacity();}}//初始15//扩容:31//扩容:47//扩容:70//扩容:105string s1;//reserve价值,确定大概要多少空间,提前开辟,减少扩容,提高效率s1.reserve(100);//申请100字符空间,但vs编译器会多开size_t old1 = s1.capacity();cout << "初始" << s1.capacity() << endl;for (size_t i = 0; i < 100; i++) {s1.push_back('x');if (s1.capacity() != old1) {cout << "扩容:" << s1.capacity() << endl;old1 = s1.capacity();}}//初始111s1.reserve(10);//一般不会减小,但STL没规定。文档说明是一个不具有约束力的缩小请求cout << s1.size() << endl;
}void test_string7() {string s1("hello world");cout << s1.size() << endl;//11cout << s1.capacity() << endl;//15//原size < resize < 原capacity (11<13<15)//s1.resize(13);//仅修改sizes1.resize(13, 'x');cout << s1 << endl;//hello worldxxcout << s1.size() << endl;//13cout << s1.capacity() << endl;//15//原capacity < resize (15<20)s1.resize(20, 'x');cout << s1 << endl;//hello worldxxxxxxxxxcout << s1.size() << endl;//20cout << s1.capacity() << endl;//31//resize < 原size (5<20)s1.resize(5);cout << s1 << endl;//hellocout << s1.size() << endl;//5cout << s1.capacity() << endl;//31//resize在string里用的并不多,这里主要为了保持接口一致性//实际中可能遇到的示例string s2;s2.resize(10, '#');//开辟10字符,并用#初始化cout << s2 << endl;//##########cout << s2.size() << endl;//10cout << s2.capacity() << endl;//15s2[0]++;//越界终止程序,但更常用s2.at(0)++;//越界可以捕获异常,程序继续执行
}void test_string8() {string s;s.push_back('#');//只能插入一个字符cout << s << endl;s.append("hello");//append用于插入字符串cout << s << endl;string ss("world");s.append(ss);cout << s << endl;//但+=更常用,既可以插入字符,也可以插入字符串s += '#';s += "world";s += ss;//尽量使用+=,因为+是传值返回string ret1 = ss + '#';string ret2 = ss + "hello";
}void test_string9() {string str;string base = "The quick brown fox jumps over a lazy dog.";str.assign(base);//将base的数据赋值给str,如果str有数据直接覆盖cout << str << endl;str.assign(base, 5, 10);//从下标5开始取10个字符 赋值cout << str << endl;}void test_string10() {// insert/erase/replace尽量不用,因为涉及挪动数据,效率不高//接口设计复杂,需要时查看文档string str("hello world");str.insert(0, 1, 'x');//在下标0插入1个字符 xstr.insert(str.begin(), 'x');//头插//string& erase(size_t pos = 0, size_t len = npos);//第一个参数是下标,第二个参数是删除个数。默认参数npos表示结束str.erase(5);//从下标5开始删到结束string s1("hello world");s1.replace(5, 1, "20%");//将下标5开始,后1个字符替换为20%cout << s1 << endl;//hello20%world//空格替换为20%//思路:创建一个新string对象s3,遍历原string对象s2//如果s2的字符不是空就添加到s3中,是空就添加20%string s2("The quick brown fox jumps over a lazy dog.");string s3;for (auto ch : s2) {if (ch != ' ')s3 += ch;elses3 += "20%";}cout << s3 << endl;//如果要求输出的是s2s2 = s3;s2.assign(s3);//swap效率最差,3次深拷贝,2次赋值//但标准库里专门为string准备了一个全局的swap(永远不会调用到需要拷贝和赋值的那个swap),所以和下方s2.swap(s3)一样swap(s2, s3);printf("s2:%p\n", s2.c_str());//s2:01155288printf("s3:%p\n", s3.c_str());//s3:01153F80s2.swap(s3);//最好的方式。该方式为交换s2和s3的指向printf("s2:%p\n", s2.c_str());//s2:01153F80printf("s3:%p\n", s3.c_str());//s3:01155288
}void test_string11() {string s1("test.cpp");//读取文件后缀//size_t find(char c, size_t pos = 0) const;//找到第一个指定字符,并返回位置,第二个缺省参数0表示从头开始//如果未找到匹配项,该函数将返回 string::npos。size_t i = s1.find('.');//从pos位置取len个字符//string substr (size_t pos = 0, size_t len = npos) const;//npos表示取到结束string s2 = s1.substr(i);//从i位置取剩余字符cout << s2 << endl;//.cppstring s3("test.cpp.tar.zip");//rfind找到倒数第一个指定字符size_t j = s3.rfind('.');string s4 = s3.substr(j);cout << s4 << endl;//.zipstring s5("https://legacy.cplusplus.com/reference/string/string/rfind/");string sub1, sub2, sub3;size_t i1 = s5.find(':');if (i1 != string::npos)//npos 是一个静态成员常量值,无符号整形-1(即最大值)sub1 = s5.substr(0, i1);//协议elsecout << "i1没有找到" << endl;size_t i2 = s5.find('/', i1 + 3);if (i2 != string::npos)sub2 = s5.substr(i1 + 3, i2 - (i1 + 3));//域名 (左闭右开,右-左就是个数)elsecout << "i2没有找到" << endl;sub3 = s5.substr(i2 + 1);//资源名cout << sub1 << endl;//httpscout << sub2 << endl;//legacy.cplusplus.comcout << sub3 << endl;//reference/string/string/rfind/
}void test_string12() {//find_first_of 在字符串中搜索与其参数中指定的任何字符匹配的第一个字符。string str("Please, replace the vowels in this sentence by asterisks.");size_t found = str.find_first_of("aeiou");while (found != string::npos) {str[found] = '*';found = str.find_first_of("aeiou", found + 1);}cout << str << endl;//Pl**s*, r*pl*c* th* v*w*ls *n th*s s*nt*nc* by *st*r*sks.//find_first_not_of 在字符串中搜索与参数中指定的任何字符都不匹配的第一个字符。//find_last_of 在字符串中搜索与参数中指定的任何字符匹配的最后一个字符。}
课堂练习
1. 917. 仅仅反转字母 - 力扣(LeetCode)
class Solution {
public:bool isLetter(char ch) {if (ch >= 'a' && ch <= 'z')return true;if (ch >= 'A' && ch <= 'Z')return true;return false;}string reverseOnlyLetters(string s) {int begin = 0, end = s.size() - 1;while (begin < end) {//下方while循环如果没有begin < end条件,//且遇到没有字母的情况就会一直++while (begin < end && !isLetter(s[begin]))//begin从左往右找字母++begin;while (begin < end && !isLetter(s[end]))//end从右往左找字母--end;swap(s[begin], s[end]);++begin;--end;}return s;}
};
2. 387. 字符串中的第一个唯一字符 - 力扣(LeetCode)
class Solution {
public:int firstUniqChar(string s) {// 使用计数排序思路//额外创建一个映射数组int countA[26] = {0};//只有26个小写字母,用0-25映射a-z//统计次数for (auto ch : s)//遍历数组countA[ch - 'a']++;//相对映射//字符存储都是用ASCII码值,用当前字符-字符a得到差值恰好映射数组下标//再遍历一遍,查看s数组中哪些字符在计数数组中只出现1次for (int i = 0; i < s.size(); ++i)//遇到计数数组存储1的第一个下标处即第一个不重复的字符//s[i] - 'a'是s数组的字符在计数数组中的位置if (countA[s[i] - 'a'] == 1)return i;return -1;}
};
3. 字符串最后一个单词的长度_牛客题霸_牛客网
#include <cstddef>
#include <iostream>
#include <string>
using namespace std;int main() {string str;getline(cin, str);//该方法遇到换行才结束// cin >> str;//该方法的问题:只能读取到第一个空格 size_t i = str.rfind(' ');//从后往前找到第一个空格,后面的部分就是最后一个单词if (i != string::npos)cout << str.size() - (i + 1) << endl;//i指向空格,size指向\0;左开又开=左-(右+1);elsecout << str.size() << endl;
}
4. 415. 字符串相加 - 力扣(LeetCode)
class Solution {
public:string addStrings(string num1, string num2) {int end1 = num1.size() - 1, end2 = num2.size() - 1;//size是个数,end是下标,所以size-1string str; // 新结果字符串int next = 0; // 进位变量while (end1 >= 0 || end2 >= 0) {// 不能这么取,因为上方循环条件可能出现某个end<0的情况// int x1 = num1[end1] - '0';int x1 = end1 >= 0 ? num1[end1] - '0' : 0; // 大于等于0才取最后字符,否则用0代替,因为加0不影响结果int x2 = end2 >= 0 ? num2[end2] - '0' : 0;// 新结果字符串每次循环的最后一位=原来两个老字符串最后一位+进位int ret = x1 + x2 + next;// 每次要调整进位next = ret / 10; // 如果新结果>10,要进位ret = ret % 10; // 新结果要保留的值是小于10,//头插法//每次得到新的结果都要插入到新字符串最前面// str.insert(0, 1, '0' + ret);//头插要挪动数据,是N^2的算法//用尾插加逆置提高效率str += ('0' + ret);--end1;--end2;}// //头插法// if (next == 1)// str.insert(0, 1, '1');//尾插+逆置if (next == 1)str += '1';reverse(str.begin(), str.end());return str;}
};
第三章:string类的模拟实现
3.1 string类的模拟实现
string.h
#pragma once
#include <assert.h>//该.h文件没有包含任何头文件也可以编译成功,
//因为.h文件是在.cpp文件中展开,而该.h文件在.cpp文件中靠后位置,并且.cpp文件中已经包含头文件
namespace bit {class string {public://模拟实现迭代器typedef char* iterator;typedef const char* const_iterator;iterator begin() {return _str;}iterator end() { //end指向\0,_size是字符串长度return _str + _size;}const_iterator begin() const {return _str;}const_iterator end() const {return _str + _size;}无参构造函数//string()// :_str(nullptr)//这种方式会导致对空指针解引用// ,_size(0)// ,_capacity(0) //{}//下方两种初始化方式都可以//string()// :_str(new char[1])// ,_size(0)// ,_capacity(0) //{// _str[0] = '\0';//}//string()// :_str(new char[1]{'\0'})// , _size(0)// , _capacity(0) {}//带参构造函数//string(const char* str)// //初始化列表顺序是按照声明顺序,所以下方会先初始化_str,但此时_capacity还没有初始化,是随机值// :_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 char* str = '\0')//这种方式类型不匹配,\0是char类型双引号的形式才是字符串类型,但这种方式不规范因为C语言规定常量字符串结尾默认有\0,这样就有2个\0//string(const char* str = "\0")string(const char* str = "")//不要加空格,加空格_str就会有空格//如果使用缺省参数,strlen是0;申请空间时开辟了1个字节,strcpy会把\0拷贝过去:_size(strlen(str)), _capacity(_size) {_str = new char[_capacity + 1];strcpy(_str, str);}传统写法s2(s1)//string(const string& s) {// _str = new char[s._capacity + 1];// strcpy(_str, s._str);// _size = s._size;// _capacity = s._capacity;//}s2 = s3//string& operator=(const string& s) {// //不考虑s2空间大小,相当于让s2指向新空间// if (this != &s) {// char* tmp = new char[s._capacity + 1];// strcpy(tmp, s._str);// delete[] _str;//释放s2// _str = tmp;//s2指向新空间// _size = s._size;// _capacity = s._capacity;// }// return *this;//}//现代写法void swap(string& s) {std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//s2(s1)string(const string& s)//有些编译器不会初始化内置类型,this指向的内置类型变量就有可能是随机值,// 交换给tmp后,出了作用域,tmp要销毁,也就是调用析构函数,这个时候就会出问题//所以需要初始化:_str(nullptr), _size(0), _capacity(0) {string tmp(s._str);swap(tmp);}现代写法s2 = s3//string& operator=(const string& s) {// //不考虑s2空间大小,相当于让s2指向新空间// if (this != &s) {// string tmp(s);// swap(tmp);//this->swap(tmp)// //s2换给个了tmp,tmp出了作用域要销毁(即调用析构函数)// }// return *this;//}//进一步优化//s2 = s3string& operator=(string tmp) {//上方是传引用传参,所以还要手动调用一次拷贝构造//这里直接传值传参解决该问题,即传参时就拷贝构造tmpswap(tmp);return *this;}~string() {delete[] _str;_str = nullptr;_size = _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];//需要多开1个空间存\0strcpy(tmp, _str);//将原数据拷贝到新空间(strcpy会拷贝\0)delete[] _str;//释放原数据的空间_str = tmp;//_str指向新空间_capacity = n;//更新新容量}}void resize(size_t n, char ch = '\0') {//分三种情况//1. n < _size 删除//2. _size < n < _capacity 插入//3. n > _capacity//1.n小于等于原字符串长度if (n <= _size) {_str[n] = '\0';_size = n;}else { //2.n大于原字符串长度reserve(n);//用reserve调整_capacitywhile (_size < n) { //_size是字符串长度,当_size作为下标时,最后一个元素的下标是_size-1,_size指向\0_str[_size] = ch;++_size;}_str[_size] = '\0';}}size_t find(char ch, size_t pos = 0) { //查找字符for (size_t i = pos; i < _size; i++)if (_str[i] == ch)return i;return npos;}size_t find(const char* str, size_t pos = 0) { //查找字符串const char* p = strstr(_str + pos, str);if (p) //如果p不为空,说明找到了return p - _str;//用找到子串的指针(子串起始位置)减去整个字符串的起始位置就是下标elsereturn npos;}string substr(size_t pos, size_t len = npos) {string s;size_t end = pos + len;//取到结束//如果取的len太大,超过原字符串结尾,需要对len处理if (len == npos || pos + len >= _size) {len = _size - pos;//这里是因为len长度太大,end = _size;}s.reserve(len);for (size_t i = pos; i < end; i++)s += _str[i];return s;}void push_back(char ch) {//检查扩容if (_size == _capacity)//reserve(_capacity * 2);//如果_capacity是0,乘2还是0reserve(_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;}该版本头插有bug//void insert(size_t pos, char ch) { // //插入数据前要判断容量// assert(pos <= _size);// if (_size == _capacity)// reserve(_capacity == 0 ? 4 : _capacity * 2);// size_t end = _size;// //插入数据需要挪动数据,最先从最后位置(即\0)// //两个变量都是无符号整型,当end减到-1时其实是最大整型值,依然满足条件,从而越界// //将end修改为int也不行,因为循环条件end >= pos这里会发生类型提升// while (end >= pos) { // _str[end + 1] = _str[end];// --end;// }// _str[pos] = ch;// _size++;//更新_size//}//正确版本-插入字符void insert(size_t pos, char ch) {//插入数据前要判断容量assert(pos <= _size);if (_size == _capacity)reserve(_capacity == 0 ? 4 : _capacity * 2);size_t end = _size + 1;//让end指向\0的后一位置,即插入字符后新字符串最后字符的位置 while (end > pos) { //如果是头插,end最终指向第二个元素_str[end] = _str[end - 1];//end前一位置的值赋值给end位置--end;}_str[pos] = ch;_size++;//更新_size}该版本头插有bug,参考上方插入字符函数//void insert(size_t pos, const char* str) {// assert(pos < _size);// size_t len = strlen(str);// if (_size + len > _capacity) //先判断是否需要扩容// reserve(_size + len);// //挪动数据// size_t end = _size;// while (end >= pos) { //end==pos时,还会再--,等于-1时其实是最大整型值,依然满足条件,从而越界// _str[end + len] = _str[end];// --end;// }// strncpy(_str + pos, str, len);//被插入字符串拷贝进原字符串。不能用strcpy,会拷贝\0// _size += len;//}//正确版本-插入字符串void insert(size_t pos, const char* str) {assert(pos < _size);size_t len = strlen(str);if (_size + len > _capacity) //先判断是否需要扩容reserve(_size + len);//挪动数据size_t end = _size + len;//end指向插入字符串后,新字符串最后字符的位置while (end > pos) {_str[end] = _str[end - len];//end-len就是原字符串最后字符位置--end;}strncpy(_str + pos, str, len);//被插入字符串拷贝进原字符串。不能用strcpy,会拷贝\0_size += len;}void erase(size_t pos, size_t len = npos) {assert(pos < _size);//1.如果指定位置开始删除的长度等于字符串结尾 or 长度等于默认值nposif (len == npos || pos + len >= _size) {_str[pos] = '\0';_size = pos;}else {size_t begin = pos + len;//begin指向删除部分的后一字符while (begin <= _size) { //从begin位置开始,把之后的字符向前挪动,包括_size指向的\0_str[begin - len] = _str[begin];//begin-len就是pos位置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 static size_t npos = -1;//const静态整型变量可以在这里初始化,其他类型都不可以};const size_t string::npos = -1;//流插入、流提取不是一定要写成友元。写成友元的目的是访问私有ostream& operator<<(ostream& out, const string& s) {这里还不能使用范围for,因为string对象被const修饰,所以需要有const修饰的迭代器//for (size_t i = 0; i < s.size(); i++)// out << s[i];//return out;//添加有const修饰的迭代器后,可以使用范围forfor (auto ch : s)out << ch;return out;}该版本需要多次扩容//istream& operator>>(istream& in, string& s) {// s.clear();//接收数据前,要清理原对象中的数据,这样才和库里面实现的一致// char ch;// //in >> ch;//cin和scanf一样获取不到空格和换行,所以导致下方就没有空格和换行导致死循环// ch = in.get();//istream提供的get类似于C语言的getchar// while (ch != ' ' && ch != '\n') {// s += ch;// //in >> ch;// ch = in.get();// }// return in;//}//该版本输入多少就开辟多少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) { //129个元素,下标是0~128buff[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;//三种遍历方式//1.[]运算符重载for (size_t i = 0; i < s1.size(); i++)cout << s1[i] << " ";cout << endl;//2.迭代器string::iterator it = s1.begin();while (it != s1.end()) {cout << *it << " ";++it;}cout << endl;//3.范围for (范围for的底层就是迭代器)for (auto ch : s1)//ch的改变不影响s1,如果要ch改变能影响到s1,那么ch就要引用cout << ch << " ";cout << endl;}void test_string2() {string s1("hello world");cout << s1.c_str() << endl;//hello worlds1.push_back(' ');s1.append("hello bit");cout << s1.c_str() << endl;//hello world hello bits1 += '#';s1 += "**********";cout << s1.c_str() << endl;//hello world hello bit#**********}void test_string3() {string s1("hello world");cout << s1.c_str() << endl;//hello worlds1.insert(5, '%');cout << s1.c_str() << endl;//hello% worlds1.insert(0, '%');cout << s1.c_str() << endl;//%hello% world}void test_string4() {string s1("hello world");string s2("hello world");cout << (s1 >= s2) << endl;//1s1[0] = 'z';cout << (s1 >= s2) << endl;//1cout << s1 << endl;cin >> s1;cout << s1 << endl;}void test_string5() {string s1("hello world");s1.insert(5, "abc");cout << s1 << endl;//helloabc worlds1.insert(0, "xxx");cout << s1 << endl;//xxxhelloabc worlds1.erase(0, 3);cout << s1 << endl;//helloabc worlds1.erase(5, 100);cout << s1 << endl;//hellos1.erase(2);cout << s1 << endl;//he}void test_string6() {string s1("hello world");s1.resize(5);cout << s1 << endl;s1.resize(15, 'x');cout << s1 << endl;}void test_string7() {string s1("test.cpp");string s5("https://legacy.cplusplus.com/reference/string/string/rfind/");string sub1, sub2, sub3;size_t i1 = s5.find(':');if (i1 != string::npos)//npos 是一个静态成员常量值,无符号整形-1(即最大值)sub1 = s5.substr(0, i1);//协议elsecout << "i1没有找到" << endl;size_t i2 = s5.find('/', i1 + 3);if (i2 != string::npos)sub2 = s5.substr(i1 + 3, i2 - (i1 + 3));//域名 (左闭右开,右-左就是个数)elsecout << "i2没有找到" << endl;sub3 = s5.substr(i2 + 1);//资源名cout << sub1 << endl;//httpscout << sub2 << endl;//legacy.cplusplus.comcout << sub3 << endl;//reference/string/string/rfind/}void test_string8() {string s1("hello world");string s2 = s1;//这里是拷贝构造cout << s1 << endl;cout << s2 << endl;string s3("xxxxxxxxx");s2 = s3;cout << s2 << endl;cout << s3 << endl;}void test_string9() {string s1;cin >> s1;cout << s1 << endl;cout << "size " << s1.size() << endl;cout << "capacity " << s1.capacity() << endl;}
}
string.cpp
#include <iostream>
#include <vector>
#include <list>
#include <string>using namespace std;#include "string.h"
int main() {//bit::test_string1();//bit::test_string2();//bit::test_string3();//bit::test_string4();//bit::test_string5();//bit::test_string6();//bit::test_string7();//bit::test_string8();//bit::test_string9();string s1;cout << s1.capacity() << endl;//15string s2("hello world");cout << s2.capacity() << endl;//15cout << sizeof(s1) << endl;//28cout << sizeof(s2) << endl;//28//标准库设计//小于16,字符串存到buff数组里面//大于等于16,存在_str指向的空间//这种设计避免碎片空间//_buff[16]//_str//_size//_capacityreturn 0;
}
3.2 浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
3.3 深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
int main() {//浅拷贝问题//1.析构两次//解决方案:使用引用计数,记录有几个对象指向这块空间//拷贝的时候要++引用计数//析构的时候要--引用计数,引用计数减到0时,说明是最后一个对象,再释放。//2.一个修改数据会影响另一个//引用计数写时拷贝,延迟拷贝。(写的时候才拷贝,且需要修改引用计数)return 0;
}
3.4 写时拷贝
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
相关文章:
C++初阶—string类
第一章:为什么要学习string类 1.1 C语言中的字符串 C语言中,字符串是以\0结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想&…...
Solon Cloud Gateway 开发:Route 的过滤器与定制
RouteFilterFactory 是专为路由过滤拦截处理设计的接口。对应路由配置 filters 1、内置的路由过滤器 过滤器工厂本置前缀说明与示例AddRequestHeaderFilterFactoryAddRequestHeader添加请求头 (AddRequestHeaderDemo-Ver,1.0)AddResponseHeaderFilterFactoryAddResponseHeade…...
【MySQL】 数据类型
欢迎拜访:雾里看山-CSDN博客 本篇主题:【MySQL】 数据类型 发布时间:2025.1.27 隶属专栏:MySQL 目录 数据类型分类数值类型tinyint类型数值越界测试结果说明 bit类型基本语法使用注意事项 小数类型float语法使用注意事项 decimal语…...
基于vue和elementui的简易课表
本文参考基于vue和elementui的课程表_vue实现类似课程表的周会议列表-CSDN博客,原程序在vue3.5.13版本下不能运行,修改两处: 1)slot-cope改为v-slot 2)return background-color:rgb(24 144 255 / 80%);color: #fff; …...
vim的多文件操作
[rootxxx ~]# vim aa.txt bb.txt cc.txt #多文件操作 next #下一个文件 prev #上一个文件 first #第一个文件 last #最后一个文件 快捷键: ctrlshift^ #当前和上个之间切换 说明:快捷键ctrlshift^,…...
spring spring-boot spring-cloud发布以及适配
https://spring.io/blog/2024/10/01/from-spring-framework-6-2-to-7-0 看了 spring 的官网,提到 2025 年 spring 会跟随 jdk 25 LTS发布后,接着发布 Spring Framework 7.0 GA,与之对应 spring 系列的组件版本情况如下。 Spring Framework版…...
【快速上手】阿里云百炼大模型
为了创建自己的知识库,本文介绍一下阿里云的百炼大模型,方便大家快速上手!快速查询自己想要的内容。 一、入口页 阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 二、大模型的选择 首先前提条件是 1、账号不能欠费 2、需…...
Linux:多线程[2] 线程控制
了解: Linux底层提供创建轻量级进程/进程的接口clone,通过选择是否共享资源创建。 vfork和fork都调用的clone进行实现,vfork和父进程共享地址空间-轻量级进程。 库函数pthread_create调用的也是底层的clone。 POSIX线程库 与线程有关的函数构…...
010 mybatis-PageHelper分页插件
文章目录 添加依赖配置PageHelper项目中使用PageHelper注意事项 PageHelper分页插件介绍 https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md 使用方法 添加依赖 <dependency><groupId>com.github.pagehelper</groupId>&l…...
【huawei】云计算的备份和容灾
目录 1 备份和容灾 2 灾备的作用? ① 备份的作用 ② 容灾的作用 3 灾备的衡量指标 ① 数据恢复时间点(RPO,Recoyery Point Objective) ② 应用恢复时间(RTO,Recoyery Time Objective) 4…...
CVE-2023-38831 漏洞复现:win10 压缩包挂马攻击剖析
目录 前言 漏洞介绍 漏洞原理 产生条件 影响范围 防御措施 复现步骤 环境准备 具体操作 前言 在网络安全这片没有硝烟的战场上,新型漏洞如同隐匿的暗箭,时刻威胁着我们的数字生活。其中,CVE - 2023 - 38831 这个关联 Win10 压缩包挂…...
回顾:Maven的环境搭建
1、下载apache-maven-3.6.0 **网址:**http://maven.apache.org 然后解压到指定的文件夹(记住文件路径) 2、配置Maven环境 复制bin文件夹 的路径D:\JavaTool\apache-maven-3.6.0\bin 环境配置成功 3、检查是否配置成功 winR 输入cmd 命令行输入mvn -v…...
从零到全栈开发
HTML:超文本标记语言 CSS:层叠样式表 HTML可以理解为框架----(毛坯房) CSS 可以理解为装修----(装修) 学习工具: Vscode应用----扩展(中文) AI ----KiMi ,豆…...
单片机-STM32 WIFI模块--ESP8266 (十二)
1.WIFI模块--ESP8266 名字由来: Wi-Fi这个术语被人们普遍误以为是指无线保真(Wireless Fidelity),并且即便是Wi-Fi联盟本身也经常在新闻稿和文件中使用“Wireless Fidelity”这个词,Wi-Fi还出现在ITAA的一个论文中。…...
两种交换排序算法--冒泡,快速
目录 1.冒泡排序原理 2.快速排序原理 3.冒泡代码实现 4.快速排序代码实现 1.冒泡排序原理 冒泡排序(Bubble Sort)是一种简单的排序算法,基本思想是通过反复交换相邻的元素,直到整个序列有序。它的名字来源于较大的元素像气泡…...
langchain基础(一)
模型又可分为语言模型(擅长文本补全,输入和输出都是字符串)和聊天模型(擅长对话,输入时消息列表,输出是一个消息)两大类。 以调用openai的聊天模型为例,先安装langchain_openai库 1…...
【学术会议征稿】第五届能源、电力与先进热力系统学术会议(EPATS 2025)
能源、电力与先进热力系统设计是指结合物理理论、工程技术和计算机模拟,对能源转换、利用和传输过程进行设计的学科领域。它涵盖了从能源的生产到最终的利用整个流程,旨在提高能源利用效率,减少能源消耗和环境污染。 重要信息 官网…...
MyBatis框架基础学习及入门案例(2)
目录 一、数据库建表(tb_user)以及添加数据。 (1)数据库与数据表说明。 (2)字段与数据说明。 二、创建模块(或工程)、导入对应所需依赖坐标。 三、编写MyBatis核心主配置文件。(解决JDBC中"硬编码"问题) (1&…...
Salesforce Too Many Email Invocations: 11
在 Salesforce 中,“Too Many Email Invocations: 11” 错误通常表示您的组织在单个事务中超过了 Apex 电子邮件调用的限制。Salesforce 设置这些限制是为了防止滥用并确保公平使用。以下是解决该问题的方法: 理解限制 Salesforce 允许每个事务中最多进…...
2274. 不含特殊楼层的最大连续楼层数
2274. 不含特殊楼层的最大连续楼层数 题目链接:2274. 不含特殊楼层的最大连续楼层数 代码如下: class Solution { public:int maxConsecutive(int bottom, int top, vector<int>& special) {ranges::sort(special);int res max(special[0] …...
RGB 转HSV空间颜色寻找色块
文章目录 前言一、绿色确定二、红色确定总结 前言 提示:这里可以添加本文要记录的大概内容: 项目需要: 将RGB颜色空间转换为HSV颜色空间以寻找颜色,主要基于以下几个原因: 直观性: HSV颜色空间更符合人类…...
Kafka生产者ACK参数与同步复制
目录 生产者的ACK参数 ack等于0 ack等于1(默认) ack等于-1或all Kafka的同步复制 使用误区 生产者的ACK参数 Kafka的ack机制可以保证生产者发送的消息被broker接收成功。 Kafka producer有三种ack机制 ,分别是 0,1…...
计算机图形学试题整理(期末复习/闭or开卷/>100道试题/知识点)
1.各种坐标变换,会产生变换前后维度改变的是(投影变换)。 A)建模变换;B)观察变换;C)投影变换;D)视口变换 不同的坐标变换对维度的影响如下: 建模…...
Ubuntu 24.04 安装 NVIDIA Container Toolkit 全指南:让Docker拥抱GPU
Ubuntu 24.04 安装 NVIDIA Container Toolkit 全指南:让Docker拥抱GPU 前言一、环境准备1.1 验证驱动状态 二、安装NVIDIA Container Toolkit2.1 添加官方仓库2.2 执行安装 三、配置Docker运行时3.1 更新Docker配置 四、验证安装结果4.1 运行测试容器 五、实战应用 …...
python3+TensorFlow 2.x(三)手写数字识别
目录 代码实现 模型解析: 1、加载 MNIST 数据集: 2、数据预处理: 3、构建神经网络模型: 4、编译模型: 5、训练模型: 6、评估模型: 7、预测和可视化结果: 输出结果ÿ…...
aws(学习笔记第二十六课) 使用AWS Elastic Beanstalk
aws(学习笔记第二十六课) 使用aws Elastic Beanstalk 学习内容: AWS Elastic Beanstalk整体架构AWS Elastic Beanstalk的hands onAWS Elastic Beanstalk部署node.js程序包练习使用AWS Elastic Beanstalk的ebcli 1. AWS Elastic Beanstalk整体架构 官方的guide AWS…...
GestureDetector组件的功能与用法
文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了ListView响应事件的内容,本章回中将介绍GestureDetector Widget.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍的GestureDetector是一个事件响应Widget,它可以响应双击事件&…...
【HuggingFace项目】:Open-R1 - DeepSeek-R1 大模型开源复现计划
项目链接:https://github.com/huggingface/open-r1 概述 Open-R1 是由 HuggingFace 发布的一个完全开放的项目,旨在通过三个主要步骤复现 DeepSeek-R1 的完整训练流程。这个项目的目标是让更多人能够理解和使用 DeepSeek-R1 的技术方案,从而…...
K8S中数据存储之配置存储
配置存储 在Kubernetes中,ConfigMap和Secret是两种核心资源,用于存储和管理应用程序的配置数据和敏感信息。理解它们的功能和最佳实践对于提高Kubernetes应用程序的安全性和配置管理的效率至关重要。 ConfigMap ConfigMap是一种API对象,允许…...
群辉折腾日记【连续剧】
安装群辉6.23版本 对比不同的版本以及自己的硬件条件,我选择了6.2.3稳定养老版本,硬件参数可以看之前的文章:pve (群辉、软路由、win/linux)折腾日记 之前年轻气盛喜欢折腾,秉持着一个原则,可以不用,但不能…...
AIGC视频生成模型:慕尼黑大学、NVIDIA等的Video LDMs模型
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍慕尼黑大学携手 NVIDIA 等共同推出视频生成模型 Video LDMs。NVIDIA 在 AI 领域的卓越成就家喻户晓,而慕尼黑大学同样不容小觑,…...
Hadoop 与 Spark:大数据处理的比较
💖 欢迎来到我的博客! 非常高兴能在这里与您相遇。在这里,您不仅能获得有趣的技术分享,还能感受到轻松愉快的氛围。无论您是编程新手,还是资深开发者,都能在这里找到属于您的知识宝藏,学习和成长…...
VB6.0 显示越南语字符
近期接到客户咨询,说是VB6.0写软件界面上显示越南语乱码,需要看看怎样解决。 我在自己电脑上也试了下,确实显示越南语结果是乱码。编辑器里乱码,运行起来界面上也是乱码。 经过一天的折腾,算是解决了问题,…...
微信小程序中实现进入页面时数字跳动效果(自定义animate-numbers组件)
微信小程序中实现进入页面时数字跳动效果 1. 组件定义,新建animate-numbers组件1.1 index.js1.2 wxml1.3 wxss 2. 使用组件 1. 组件定义,新建animate-numbers组件 1.1 index.js // components/animate-numbers/index.js Component({properties: {number: {type: Number,value…...
网络仿真工具Core环境搭建
目录 安装依赖包 源码下载 Core安装 FAQ 下载源码TLS出错误 问题 解决方案 找不到dbus-launch 问题 解决方案 安装依赖包 调用以下命令安装依赖包 apt-get install -y ca-certificates git sudo wget tzdata libpcap-dev libpcre3-dev \ libprotobuf-dev libxml2-de…...
JavaScript 的 Promise 对象和 Promise.all 方法的使用
JavaScript 中的 Promise 对象 什么是 Promise? Promise 是一种用于处理异步操作的对象。它代表一个尚未完成但预计将来会完成的操作及其结果。 主要特点: 状态: Pending(进行中): 初始状态,既未成功,也未失败。Fu…...
农产品价格报告爬虫使用说明
农产品价格报告爬虫使用说明 # ************************************************************************** # * * # * 农产品价格报告爬虫 …...
Java 大视界 -- Java 大数据中的隐私增强技术全景解析(64)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
实战Linux Swap扩展分区
文章目录 定义命令格式案例注释 定义 Swap分区是Linux系统中的一种虚拟内存实现方式,它是磁盘上预留的专用区域。当系统的物理内存不足时,会将部分不活跃的数据从物理内存移动到Swap分区,从而释放更多可用内存空间。 命令格式 关闭Swap分区…...
doris:Parquet导入数据
本文介绍如何在 Doris 中导入 Parquet 格式的数据文件。 支持的导入方式 以下导入方式支持 Parquet 格式的数据导入: Stream LoadBroker LoadINSERT INTO FROM S3 TVFINSERT INTO FROM HDFS TVF 使用示例 本节展示了不同导入方式下的 Parquet 格式使用方法…...
Synology 群辉NAS安装(6)安装mssql
Synology 群辉NAS安装(6)安装mssql 写在前面mssql 2019:成功安装说明,这个最终成功了 mssql 2022没有成功1. pull image2.启动mssql docker container 远程连接 写在前面 mssq是一个重要节点。 这是因为我对mysql没有一丝好感。虽然接触了许…...
使用.NET 8构建高效的时间日期帮助类
使用.NET 8构建高效的时间日期帮助类 在现代Web应用程序中,处理日期和时间是一个常见的需求。无论是记录日志、生成报告还是进行数据分析,正确处理日期和时间对于确保数据的准确性和一致性至关重要。本文将详细介绍如何使用ASP.NET Core和C#构建一个高效…...
第26篇 基于ARM A9处理器用C语言实现中断<二>
Q:基于ARM A9处理器怎样编写C语言工程,使用按键中断将数字显示在七段数码管上呢? A:基本原理:主程序需要首先调用子程序set_A9_IRQ_stack()初始化IRQ模式的ARM A9堆栈指针;然后主程序调用子程序config_GIC…...
dm8在Linux环境安装精简步骤说明(2024年12月更新版dm8)
dm8在Linux环境安装详细步骤 - - 2025年1月之后dm8 环境介绍1 修改操作系统资源限制2 操作系统创建用户3 操作系统配置4 数据库安装5 初始化数据库6 实例参数优化7 登录数据库配置归档与备份8 配置审计9 创建用户10 屏蔽关键字与数据库兼容模式11 jdbc连接串配置12 更多达梦数据…...
Nginx部署的前端项目刷新404问题
1,查看问题 我部署的81端口是监听tlias项目的,我直接访问端口页面可以出现内容。 我在浏览器舒服端口之后回车,会重定向页面。但是我在重定向之后的页面刷新浏览器就会出现404的问题。 下面是刷新浏览器后的效果 2,在nginx.cnf …...
H2 Database安装部署
H2 Database H2 Database官网 H2 中文文档 安装部署H2 java版本要高于java 11 ## 下载java21 wget https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz[rootlocalhost ~]# tar xf jdk-21_linux-x64_bin.tar.gz -C /usr/local/ [rootlocalhost ~]# vi…...
Day40:列表的排序
在 Python 中,排序是处理列表数据时常用的一种操作。排序可以帮助我们按照一定的规则(如升序或降序)对列表中的元素进行排列。Python 提供了内置的排序方法 sort() 和 sorted() 来实现这一功能。 1. 使用 sort() 方法排序 1.1 sort() 方法简…...
TypeScript进阶(三):断言
文章目录 一、前言二、类型断言2.1 利用 as 实现断言2.2 利用 <> 实现断言2.3 应用示例2.3.1 如果 A,B 如果是类并且有继承关系2.3.2 如果 A,B 如果是类,但没有继承关系2.3.3 如果 A 是类,B 是接口,并且 A 类实现…...
塔罗牌(基础):大阿卡那牌
塔罗牌(基础) 大啊卡那牌魔术师女祭司皇后皇帝教皇恋人战车力量隐士命运之轮正义吊人死神节制恶魔高塔星星月亮太阳审判世界 大啊卡那牌 魔术师 作为一个起点,象征:意识行动和创造力。 一个【显化】的概念,即是想法变…...
微服务(一)
文章目录 项目地址一、微服务1.1 分析User的Domian Verb和Nouns 二、运行docker和k8s2.1 Docker1. 编写dockerfile2. 创建docker image3. 运行docker使用指定端口4. 查看当前运行的镜像5. 停止当前所有运行的docker6. 删除不用的docker images7. 将本地的image上传到hub里 2.2 …...