c++-string
文章目录
- 前言
- 一、STL库介绍
- 二、标准库中的string类
- 1、string类介绍
- 2、string类使用
- 3.1 string类的构造函数
- 3.2 string类对象的容量操作
- 3.3 string类对象的遍历操作
- 3.4 string类对象的访问操作
- 3.5 string类对象的修改操作
- 3.6 string类对象的字符串操作
- 三、模拟实现string类
- 四、windows下的VS中的string类和Linxu下的g++中的string类。
前言
一、STL库介绍
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且
是一个包罗数据结构与算法的软件框架。
STL的版本:
(1). 原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本–所有STL实现版本的始祖。
(2). P. J. 版本
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
(3). RW版本
由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
(4). SGI版本
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。
STL的六大组件:

二、标准库中的string类
1、string类介绍
当我们查看c++文档中关于string类的定义时,可以发现string类是依靠basic_string类模板显示实例化出来的一个类。

我们可以查看basic_string这个类模板是这样定义的。并且根据basic_string这个类模板生成了4个模板实例。string类只是其中一个。


那么为什么要根据该模板生成4个模板实例呢?
这是因为在计算机刚发明时,因为计算机只能存储1010的二进制位数据,而美国人使用的字母和符号是无法直接存储到计算机内的。所以美国人就想到了一套ASCII码方式。
在计算机中,所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0),例如,像a、b、c、d这样的52个字母(包括大写)以及0、1等数字还有一些常用的符号(例如*、#、@等)在计算机中存储时也要使用二进制数来表示,而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则,于是美国有关的标准化组织就出台了ASCII编码,统一规定了上述常用符号用哪些二进制数来表示。
这样使用0-127就将全部字符都表示出来了,即还不到一个字节,所以ASCII码中每个字符占一个字节。

但是随着计算机在全世界范围被使用,计算机中只显示这127个欧美字符是不行的,还需要显示其它国家的语言。那么像我们中国使用的是象形文字,ASCII编码那一套对我们就不适用了。所以又出现了一种unicode码的编码方式。
统一码(Unicode),也叫万国码、单一码,由统一码联盟开发,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。
统一码是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
如果把各种文字编码形容为各地的方言,那么统一码就是世界各国合作开发的一种语言。在这种语言环境下,不会再有语言的编码冲突,在同屏下,可以显示任何语言的内容,这就是统一码的最大好处。就是将世界上所有的文字用2个字节统一进行编码。那样,像这样统一编码,2个字节就已经足够容纳世界上所有语言的大部分文字了。
在统一码中,汉字“字”对应的数字是23383。在统一码中,我们有很多方式将数字23383表示成程序中的数据,包括:UTF-8、UTF-16、UTF-32。UTF是“UCS Transformation Format”的缩写,可以翻译成统一码字符集转换格式,即怎样将统一码定义的数字转换成程序数据。所以在c++中为了应对不同的编码格式,才根据basic_string模板实例化了4中类。
在string类中一个字符占1个字节,在wstring类中一个字符占2个字节,在u16string类中一个字符占2个字节,在u32string类中一个字符占4个字节。




2、string类使用
string类有对应的构造函数和析构函数,还有赋值运算符的重载函数。

3.1 string类的构造函数


int main()
{//会调用string类的默认构造函数string()string s1;//会调用string类的string(const char* s)构造函数,因为参数不为string对象的引用,而是字符串地址。string s2("hello world");//下面的对于s3的初始化,应该先将"hello string"字符串隐式转换为临时string对象,然后调用构造函数将该临时对象初始化//然后再调用拷贝构造将临时对象拷贝给s3对象。但是编译器会直接优化为调用构造函数。// 构造 + 拷贝构造 -> 优化为构造//调用的也是string(const char* s)构造函数string s3 = "hello string";//会调用拷贝构造函数string (const string& str)string s4(s3);//此时会调用string (const string& str, size_t pos, size_t len = npos)这个构造函数,因为第一个参数为string对象的引用//意思为从s3字符的下标为6的位置开始拷贝,拷贝的长度为6。string s5(s3, 6, 6);cout << s5 << endl;//当拷贝的长度大于s3的长度时,此时会只拷贝到s3末尾就停止了。//该构造函数的第三个参数缺省值npos为-1,而-1的补码按无符号整数解析就是int表示的最大的无符号整数//所以如果第三个参数不给的话就是默认将s3字符串拷贝到结尾再停止。string s6(s3, 6, 12);cout << s6 << endl;//此时会调用string (const char* s,size_t n)这个构造函数,因为第一个参数为字符串地址。//该函数表示的意思是将"hello world"字符串的前5个字符拷贝到s7对象中。string s7("hello world", 5);cout << s7 << endl;//当第二个参数n大于字符串长度时,会拷贝到'\0'就停止了string s8("hello worlda", 20);cout << s8 << endl;//此时会调用string (size_t n,char c)这个构造函数。//会将s9中初始化为10个*字符string s9(10, '*');cout << s9 << endl;return 0;
}
3.2 string类对象的容量操作

注意:
(1). size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
(2). clear()只是将string中有效字符清空,不改变底层空间大小。
int main()
{string s1("hello world");string s2("");//返回字符串有效字符长度cout << s1.size() << endl;//返回字符串有效字符长度cout << s1.length() << endl;//返回容器可以容纳的最大元素数。cout << s1.max_size() << endl;//返回空间总大小cout << s1.capacity() << endl;//检测字符串是否为空串cout << s1.empty() << endl;//清空有效字符s1.clear();cout << s1.empty() << endl;//缩容,将s1对象的capacity缩到和size一样大//但是缩容的开销很大,一般不会使用s1.shrink_to_fit();cout << s1.capacity() << endl;return 0;
}
resize和reserve区别
当使用string存储字符串时,如果字符串长度太大时,string会自动进行扩容。如果字符串过大,但是string自动扩容每次扩的都很少时,此时想要直接申请够存储字符串的空间,就可以使用reserve和resize。reserve和resize这两个成员函数可以预先开辟指定大小的空间。当需要知道开辟多少空间时,可以使用reserve提前将空间开好,减少扩容,提高效率。因为要考虑对齐等因素,开的空间会比实际要求的大,但是绝对不会比实际要求的少。
注意:
(1). resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
(2). reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。即reserve只会改capacity,不会改size。而resize会将capacity和size都改变。
int main()
{//扩容string s1("hello world");s1.reserve(100);cout << s1.size() << endl;cout << s1.capacity() << endl;//扩容+初始化//如果resize不给第二个参数则会初始化为'\0'string s2("hello world");s2.resize(100, 'x');cout << s2.size() << endl;cout << s2.capacity() << endl;string s3("hello world");s3.resize(100);return 0;
}

可以看到使用resize会将预先开辟出来的空间初始化。而reserve只是将空间开辟出来,并不会进行初始化。所以reserve只会改capacity,不会改size。而resize会将capacity和size都改变。如果不给第二个参数会初始化为’\0’,如果给第二个参数则会将后面的值初始化为给定的字符。



resize还可以删除数据,当传入的第一个参数比size小时,就会删除数据,比capacity大时,就会扩容。
int main()
{//扩容+初始化//当resize第一个参数比capacity大时,就会扩容string s1("hello world");s1.resize(16,'x');cout << s1.size() << endl;cout << s1.capacity() << endl;//当resize第一个参数比size小时,就会删除数据。string s2("hello world");s2.resize(5);cout << s2.size() << endl;cout << s2.capacity() << endl;cout << s2 << endl; //helloreturn 0;
}
3.3 string类对象的遍历操作
string类的迭代器。

当我们想要遍历string类中存的字符串的每一个字符时,可以使用三种方法来遍历。
循环遍历:
int main()
{string s1("hello world");for (int i = 0; i < s1.size(); ++i){cout << s1[i] << " ";}cout << endl;return 0;
}
范围for::
其实使用范围for,范围for底层还是使用迭代器,范围for只是对迭代器又封装了一层。
int main()
{string s1("hello world");for (auto ch : s1){cout << ch << " ";}cout << endl;return 0;
}
迭代器:
可以先将迭代器·想象为指针,begin就是返回字符串的起始地址,而end就是返回结束位置的下一个字符的指针。
int main()
{string s1("hello world");string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

迭代器除了有正向迭代器外,还有被const修饰的正向迭代器,还有反向迭代器和被const修饰的反向迭代器。当想要遍历被const修饰的对象时,此时就需要使用const_iterator迭代器。并且const_iterator迭代器只能访问对象内容,并不能改变对象的内容。
其中begin()和end都有两种,一种是不被const修饰的,一种是被const修饰的。当创建的为iterator迭代器时,会自动去调用不被const修饰的begin(),当创建的为const_iterator迭代器时,会自动去调用被const修饰的begin()。

void func(const string& s)
{//const 正向迭代器,只能遍历和读数据,不能写数据。string::const_iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;//const 反向迭代器,只能遍历和读数据,不能写数据。string::const_reverse_iterator rit = s.rbegin();while (rit != s.rend()){cout << *rit << " ";++rit;}cout << endl;
}
int main()
{string s1("hello world");//正向迭代器,可以遍历数据,也可以读写数据string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout << endl;//创建一个反向迭代器,将从后向前遍历s1中的字符串。可以遍历数据,也可以读写数据//string::reverse_iterator rit = s1.rbegin();//此时可以使用auto来自动推断变量的类型。//auto是根据右边的返回值来推左边的类型的,虽然可以简化代码,但是代码的可读性差了auto rit = s1.rbegin();while (rit != s1.rend()){cout << *rit << " ";++rit;}cout << endl;func(s1);return 0;
}
3.4 string类对象的访问操作

使用operator[]和a都可以访问指定位置的元素,只不过当使用[]越界访问时会直接中止程序,而使用at越界访问时会抛异常。
int main()
{string s1("hello world");//使用 [] 越界访问会中止程序。//s1[100];//使用 at 越界访问会抛异常try{s1.at(100);}catch (const exception& e){cout << e.what() << endl;}//返回字符串的第一个字符,也可以修改这个字符cout << s1.front() << endl;s1.front() = '!';cout << s1 << endl;//返回字符串的最后一个字符,就是\0前面的字符,也可以修改这个字符cout << s1.back() << endl; //返回ds1.back() = '#';cout << s1 << endl;string s2("hello\0world");cout << s2.back() << endl; //返回oreturn 0;
}
3.5 string类对象的修改操作

字符串的尾插::
int main()
{string s1("hello");//push_back()为尾插一个字符。s1.push_back(' ');s1.push_back('!');cout << s1 << endl;//append是尾插字符串s1.append("world");cout << s1 << endl;//+=可以尾插一个字符,也可以尾插字符串//+=的底层还是调用的push_bakc和append,+=只是多了一层封装,使用起来更加简单明了s1 += ' ';s1 += '!';s1 += "world";cout << s1 << endl;return 0;
}
当一直向string类中插入字符时,就要涉及到扩容的问题了。可以看到在VS下每次扩容为原来的1.5倍。
int main()
{//观察扩容情况string s;size_t sz = s.capacity();cout << "capacity changed: " << sz << endl;for (int i = 0; i < 100; ++i){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed: " << sz << endl;}}return 0;
}

可以看到第一次扩容为两倍扩容,那么为什么呢?
这是因为在string中当字符串长度小于16时,其实并没有动态申请空间存放字符串,而是将字符串存放到string类的_buf数组内,当字符串长度大于16后,才开始进行扩容,然后动态申请空间,将字符串存到动态申请的空间中。这样做的目的是防止了每有一个小的字符串就要动态申请空间,然后申请的空间很小就会产生很多碎片空间而浪费空间。
可以看到当字符串长度小于16时,就会存在_Buf数组中。

当我们使用sizeof查看s对象大小时,可以看到大小为28,这是因为除了两个int型变量和一个指针变量外,还有_Buf数组的16个字节的大小。

string扩容在不同的编译器下是不同的,比如windows下VS1.5倍扩容,linux下g++2倍扩容。并且g++中没有buf数组。这是因为VS用的PJ版STL,g++中用了SGI版本STL。

insert的使用

int main()
{string s1("hello world");string s2("world");//调用string& insert(size_t pos, const string& str)函数,在s1对象的pos位置插入s2对象的全部字符串s1.insert(0, s2);cout << s1 << endl;//调用string& insert(size_t pos, const string& str, size_t subpos, size_t sublen);函数//在s1对象的pos位置插入s2对象的从subpos位置开始长度为sublen的字符串。//如果sublen的长度超过s2的长度,则会将s2的字符串全部插入到s1中,然后就停止。s1.insert(0, s2, 0, 4);cout << s1 << endl;//调用string& insert(size_t pos, const char* s)函数,在pos位置插入字符串ss1.insert(0, "hello");cout << s1 << endl;//调用string& insert(size_t pos, const char* s, size_t n)函数//在s1对象的pos位置插入s字符串的前n个字符,如果n大于字符串s的长度,则会将字符串s全部插入s1对象的字符串中,然后就停止。s1.insert(0, "hello", 3);cout << s1 << endl;//调用string& insert(size_t pos, size_t n, char c)函数//在s1对象的pos位置插入n个c字符s1.insert(0, 2, 'c');cout << s1 << endl;//调用iterator insert(const_iterator p, size_t n, char c)函数//在s1对象的字符串的begin()向后移5个位置处插入n个字符c。s1.insert(s1.begin() + 5, 3, 'c');cout << s1 << endl;//调用iterator insert(const_iterator p, char c)函数//在s1对象的字符串的begin()向后移5个位置处插入字符c。s1.insert(s1.begin() + 5, 'y');cout << s1 << endl;return 0;
}
erase使用
insert和erase不推荐经常使用,因为它们在字符串中间插入或删除数据,都会挪动数据,效率比较低,开销还很大

int main()
{string s1("hello world");//会调用string& erase(size_t pos = 0, size_t len = npos)函数//将s1对象的字符串从pos位置开始,向后删除len个字符s1.erase(5, 1);cout << s1 << endl;//会调用string& erase(size_t pos = 0, size_t len = npos)函数//因为没有给第二个参数,而npos=-1,即代表无符号最大的数,所以会将s1pos位置之后的字符全部删除s1.erase(5);cout << s1 << endl;//会调用iterator erase(const_iterator p)函数//将s1.begin()向后移5个位置,然后将后面的一个字符删除s1.erase(s1.begin() + 5);cout << s1 << endl;//会调用iterator erase(const_iterator first, const_iterator last)函数//将s1.begin()向后移5个位置,然后到s1.end()之间的字符都删除s1.erase(s1.begin() + 5, s1.end());cout << s1 << endl;return 0;
}
replace使用

int main()
{string s1("hello world");string s2("abcdefgh");//调用string& replace(size_t pos, size_t len, const string& str)函数//将s1对象的字符串从pos位置开始的len长度的字符替换为s2对象中的字符串//如果len大于s1对象的字符串的长度,则会将s1的全部字符串替换为s2的字符串。//s1.replace(0, 5, s2); //abcdefgh world//s1.replace(0, 50, s2); //abcdefghcout << s1 << endl;//调用string& replace(const_iterator i1, cosnt_iterator i2, const string& str)函数//将s1.begin()向后移5个位置,然后到s1.end()的位置之间的字符串替换为s2对象的字符串//s1.replace(s1.begin() + 5, s1.end(), s2);cout << s1 << endl;//调用string& replace(size_t pos, size_t len, const string& str, size_t subpos, size_t sublen)函数//将s1对象的字符串从pos位置开始的len长度的字符替换为s2对象中的字符串从subpos位置开始的sublen长度的字符//s1.replace(0, 5, s2, 0, 5);cout << s1 << endl;//调用string& replace(size_t pos, size_t len, const char* s)函数//将s1对象的字符串从pos位置开始的len长度的字符替换为s字符串//如果len大于s1对象的字符串的长度,则会将s1的全部字符串替换为s字符串。//s1.replace(0, 5, "abcde");cout << s1 << endl;//调用string& replace(const_iterator i1, cosnt_iterator i2, const char* s)函数//将s1.begin()向后移5个位置,然后到s1.end()的位置之间的字符串替换为s字符串//s1.replace(s1.begin() + 5, s1.end(), "abcde");cout << s1 << endl;//调用string& replace(size_t pos, size_t len, const char* s, size_t n)函数//将s1对象的字符串从pos位置开始的len长度的字符替换为s字符串的前n个字符//s1.replace(0, 5, "abcde", 3);cout << s1 << endl;//调用string& replace(const_iterator i1, cosnt_iterator i2, const char* s, size_t n)函数//将s1.begin()向后移5个位置,然后到s1.end()的位置之间的字符串替换为s字符串的前n个字符//s1.replace(s1.begin() + 5, s1.end(), "abcde", 3);cout << s1 << endl;//调用string& replace(size_t pos, size_t len, size_t n, char c)函数//将s1对象的字符串从pos位置向后len长度的字符替换为n个c字符//s1.replace(0, 5, 5, 'x');cout << s1 << endl;//调用string& replace(const_iterator i1, cosnt_iterator i2, size_t n, char c)函数//将s1.begin()向后移5个位置,然后到s1.end()的位置之间的字符串替换为n个c字符s1.replace(s1.begin() + 5, s1.end(), 5, 'x');cout << s1 << endl;return 0;
}
assign使用
assign为调用的对象分配一个新的字符串来替代原来的内容。

int main()
{string s1;string s2("The quick brown fox jumps over a lazy dog.");//调用string& assign(const string& str)函数//将s1对象的字符串替换为s2对象的字符串s1.assign(s2);cout << s1 << endl;//调用string& assign(const string& str, size_t subpos, size_t sublen)函数//将s1对象的字符串替换为s2对象从subpos位置开始向后sublen长度的字符串s1.assign(s2, 10, 20);cout << s1 << endl;//调用string& assign(const char* s)函数//将s1对象的字符串替换为字符串ss1.assign("hello world");cout << s1 << endl;//调用string& assign(const char* s, size_t n)函数//将s1对象的字符串替换为字符串s的前n个字符s1.assign("hello world", 5);cout << s1 << endl;//调用string& assign(size_t n, char c)函数//将s1对象的字符串替换为n个c字符s1.assign(10, 'x');cout << s1 << endl;return 0;
}
swap使用

我们知道在std库中有一个swap的函数模板,该模板实例的swap为浅拷贝,即会将string类的内容都互换,而string类里面的swap函数只是将两个string类类型对象的指向字符串的指针互换一下,即完成了内容的互换,所以string类里面的swap函数效率更高。

int main()
{string s1("hello world");string s2("world hello");cout << s1 << endl;cout << s2 << endl;//将s1和s2的字符串内容互换s1.swap(s2);cout << s1 << endl;cout << s2 << endl;return 0;
}
3.6 string类对象的字符串操作

find使用

int main()
{string s1("The quick brown fox jumps over a lazy dog.");string s2("quick");//调用size_t find(const string& str, size_t pos = 0) const noexcept;函数//从s1对象的字符串的起始位置开始找s2对象的字符串,并返回该字符串在s1对象中第一次出现的位置。//如果找不到就返回npos.size_t pos = s1.find(s2,0);cout << pos << endl;//调用size_t find(const char* s, size_t pos = 0) const;函数//从s1对象的字符串的起始位置开始找s字符串,并返回该字符串在s1对象中第一次出现的位置。//如果找不到就返回npos.size_t pos1 = s1.find("quick", 0);cout << pos1 << endl;//调用size_t find(const char* s, size_t pos, size_type n) const;函数//从s1对象的字符串的起始位置开始找s字符串的前n个字符组成的字符串,并返回该字符串在s1对象中第一次出现的位置。size_t pos2 = s1.find("br", 0, 2);cout << pos2 << endl;//调用size_t find(char c, size_t pos = 0) const noexcept;函数//从s1对象的字符串的起始位置开始找字符c,并返回该字符在s1对象中第一次出现的位置。size_t pos3 = s1.find('b', 0);cout << pos3 << endl;return 0;
}
find和replace练习
将字符串中的空格都替换为%20。
下面的代码可以完成替换,但是使用replace将1个字符替换为3个字符时,可能s1对象的容量不够,会需要扩容,此时会增加开销。
int main()
{string s1("The quick brown fox jumps over a lazy dog.");//练习,将s1字符串中的空格都换为%20size_t pos = s1.find(' ');while (pos != string::npos){s1.replace(pos, 1, "%20");pos = s1.find(' ', pos + 3);}cout << s1 << endl;return 0;
}
所以可以先提前遍历s1对象的字符串中有多少空格,然后提前将s1的空间开好,避免replace时频繁扩容增加开销。
int main()
{string s1("The quick brown fox jumps over a lazy dog.");size_t num = 0;for (auto ch : s1){if (ch == ' '){++num;}}//提前开空间,避免replace时频繁开空间s1.reserve(s1.size() + 2 * num);//练习,将s1字符串中的空格都换为%20size_t pos = s1.find(' ');while (pos != string::npos){s1.replace(pos, 1, "%20");pos = s1.find(' ', pos + 3);}cout << s1 << endl;return 0;
}
虽然我们提前将s1的空间开好,可以提高一些效率,但是replace方法替换都会移动数据,移动数据的开销更大。所以我们可以重新创建一个string类类型对象,用追加的形式将转换好的字符都尾插到新对象中。然后将新对象赋值给s1。
这样以空间换时间,可以使程序的效率提高一些。
int main()
{string s1("The quick brown fox jumps over a lazy dog.");string newStr;size_t num = 0;for (auto ch : s1){if (ch == ' '){++num;}}//提前将newStr的空间开好newStr.reserve(s1.size() + 2 * num);for (auto ch : s1){if (ch != ' '){newStr += ch;}else{newStr += "%20";}}s1 = newStr;cout << newStr << endl;return 0;
}
c_str()使用

c_str是返回一个以’\0’符结尾的字符串。直接打印s1遇到\0不会停止,因为<<符号的重载函数是按s1.size打印的,size有多大,就打印多少个字符。但是c_str返回的是一个字符串的地址,即一个const char类型的指针,所以遇到\0会停止。即c_str返回的是c语言中的字符串。c_str返回c语言中的字符串,就是为了兼容c语言的一些接口,因为有一些c语言的函数需要传入以’\0\结尾的字符串,即const char类型的指针。而不是传入c++中的string类类型的对象。


int main()
{string s1("hello world");cout << s1 << endl;cout << s1.c_str() << endl;cout << (void*)s1.c_str() << endl;s1 += '\0';s1 += '\0';s1 += "xxxxxxxx";//直接打印s1对象,遇到\0不会停止打印cout << s1 << endl;//而s1.c_str()返回的是一个c语言中的字符串,所以遇到\0就会结束cout << s1.c_str() << endl;return 0;
}
substr使用
substr为截取字符串的作用。

int main()
{string s1("hello world");//substr函数为截取字符串,即从pos位置截取len长度的字符串返回。//如果第二个参数没有,则默认从pos位置将字符串全部截取然后返回。string tmp = s1.substr(0,5);cout << tmp << endl;//练习,截取文件名的后缀string file("string.cpp");size_t pos = file.find('.');if (pos != string::npos){string suffix = file.substr(pos);cout << suffix << endl;}return 0;
}
rfind使用

int main()
{string s1("hello world hello");string s2("hello");//调用size_t rfind(const string& str, size_t pos = npos)const noexcept;函数//从s1对象的字符串的pos位置开始向前查找对象s2的字符串,找到了就返回字符串在s1中的位置//如果不传第二个参数就默认从s1字符串的末尾开始查找。size_t pos1 = s1.rfind(s2, 0);cout << pos1 << endl;string file("string.cpp.tar.zip");//从后向前找字符'.'size_t pos = file.rfind('.');if (pos != string::npos){string suffix = file.substr(pos);cout << suffix << endl;}return 0;
}
find配合substr查找字符串练习
查找url路径中的地址。
int main()
{string url("https://legacy.cplusplus.com/reference/string/string/rfind/");cout << url << endl;size_t start = url.find("://");if (start == string::npos){cout << "invalid url" << endl;}start += 3;size_t finish = url.find('/', start);//打印://到第一个/之间的字符串string address = url.substr(start, finish - start);cout << address << endl;return 0;
}
find_first_of使用

int main()
{//将s1字符串中的abcd都换为*string s1("The quick brown fox jumps over a lazy dog.");//在s1对象的字符串中查找"abcd"字符串中的字符第一个出现的位置,并且将该位置返回size_t found = s1.find_first_of("abcd");while (found != string::npos){s1[found] = '*';//继续向后找s1对象的字符串中的"abcd"字符found = s1.find_first_of("abcd", found + 1);}cout << s1 << endl;return 0;
}
find_last_of使用
find_first_of为从前向后查找,而find_last_of就是从后向前查找。

int main()
{//将s1字符串中的abcd都换为*string s1("The quick brown fox jumps over a lazy dog.");//在s1对象的字符串中查找"abcd"字符串中的字符最后一个出现的位置,并且将该位置返回size_t found = s1.find_last_of("abcd");while (found != string::npos){s1[found] = '*';//继续向前找s1对象的字符串中的"abcd"字符found = s1.find_last_of("abcd", found + 1);}cout << s1 << endl;return 0;
}
find_first_not_of使用
find_first_of为查找s1中含有字符串中字符的位置返回,而find_first_not_of是查找s1中不是这样字符串中字符的位置返回。

int main()
{//将s1字符串中除了abcd的字符都换为*string s1("The quick brown fox jumps over a lazy dog.");//在s1对象的字符串中查找不是"abcd"字符串中的字符的第一个出现的位置,并且将该位置返回size_t found = s1.find_first_not_of("abcd");while (found != string::npos){s1[found] = '*';//继续向后找s1对象的字符串中不是"abcd"的字符found = s1.find_first_not_of("abcd", found + 1);}cout << s1 << endl;return 0;
}
find_last_not_of使用
find_first_not_of是从前向后找,而find_last_not_of为从后向前找。

三、模拟实现string类
当我们知道了string类的大致操作后,我们也可以模拟实现一下string类。
当我们写到有参构造函数时,会发现形参str在初始化列表赋值给_str时发生了错误。这是因为str为const char类型,而_str为char类型,将const char类型赋值给char,发生了权限放大,所以会出现报错。

那么上面这个问题该怎么解决呢?
我们可以将_str也改为const char类型,但是将_str改为const char类型后,虽然上面的情况不报错了,但是又遇到了新的问题。当我们对[]操作符进行重载时,会看到发生了错误,因为函数返回的是一个char类型的引用,而_str[pos]是一个const char的类型的字符,所以又发生了权限放大。

并且如果将_str设为const char类型,以后就不可以修改_str字符串的值了,这是肯定不可以的,所以我们不能将_str设为const char类型。那么我们的有参构造函数就不能像上面那样在初始化列表中将成员变量全部初始化了,我们需要像下面那样,将_str在构造函数内初始化。先使用new申请空间,然后使用strcpy函数进行拷贝,这样就完成了_str的初始化。

然后我们测试时发现报出了异常,这是因为当创建对象使用默认的无参构造函数时,此时_str为nullptr空指针,然后s1.c_str()返回的是一个nullptr,当使用cout<<nullptr时,因为cout<<有自动识别类型的特性,而s1.c_str()返回的是一个const char类型的指针,但是cout<<不会直接将这个指针打印出来,它会打印出来这个指针指向的字符串,即将这个指针解引用,找到指针指向的字符串进行打印,当遇到’\0’就停止。而此时s1.c_str()返回的是nullptr,所以cout<<就会对nullptr进行解引用,然后就会造成对空指针解引用。就会出现异常了。


所以在无参构造函数中不能将_str初始化为nullptr。我们可以在无参构造函数中将_str赋值为’\0’,这样当打印时遇到’\0’就停止打印,而第一个就是’\0’,就会打印空字符串了。

然后我们将析构函数也写出来。

然后我们可以将有参构造函数和无参构造函数和为一个全缺省构造函数。此时需要注意的时缺省参数给的缺省值,不能给str的值为nullptr空指针,而要使str指向’\0’字符,即str的值为’\0’字符所在的空间,而不是str的值为0。

然后我们再来看拷贝构造函数。我们知道编译器默认生成的拷贝构造函数为浅拷贝,即如果使用编译器自带的默认拷贝构造函数,那么会出现两个string类类型对象指向同一片空间的情况,例如下面的图片中的情况。并且在s2对象销毁时调用析构函数delete[] _str后,在s3对象销毁时,也会调用析构函数再次delete[] _str,而此时的_str已经被s2对象的析构函数给delete[]过了,所以对s2的_str进行了两次delete[],故出现了错误。并且使用浅拷贝,当改变s2对象中_str的值时,s3对象中_str的值也会改变。


所以我们需要自己重写深拷贝的拷贝构造函数。拷贝构造函数也有初始化列表,所以我们在初始化列表中将_size和_capacity直接初始化为要拷贝对象的_size和_capacity。然后在拷贝构造函数内重新申请_capacity+1大小的空间给新对象,并且使用strcpy将s对象的_str内容拷贝给新对象的_str。此时可以看到s2对象的_str指向的空间和s3对象的_str指向的空间不是一片空间。但是s2对象和s3对象的内容都相同,并且修改s2对象的内容时不会影响s3对象的内容。


接下来我们实现=赋值运算符的重载函数。该函数就需要分如下三种情况。这样我们实现赋值运算符的重载函数时还需要进行判断。但是我们也可以不进行判断,直接将s1对象的_capacity设置为和s2的_capacity一样,然后将s1对象_str的空间释放,重新为s1的_str开辟一个大小为_capacity的新空间,然后将s2的_str的内容拷贝给s1的_str。

下面的代码可以实现s1=s2,即将s2赋值给s1,但是当将自己赋值给自己时就会打印乱码。这是因为在赋值运算符重载函数中刚开始就将s1对象的_str的空间释放了,然后这个空间的值就为随机值了,然后strcpy(_str,s._str)将随机值拷贝过来给s1的_str,所以s1的_str就为随机值了。


所以我们要先判断一下,如果是s1=s1的情况时,就不需要做任何操作。并且我们提前释放s1对象的_str的空间也有安全隐患。例如如果我们申请空间不成功时,此时s1对象的_str的数据就造成丢失了。所以我们应该先申请空间,然后再释放s1的_str的空间。

然后我们再实现size()函数用来返回对象的字符串长度。

当我们实现了size()函数后,我们实现一个Print函数用来遍历对象的字符串。然后我们会发现在测试时可以调用size()函数和使用[]操作符的重载函数,但是在Print函数中使用size()函数和[]操作符的重载函数发生了错误。这是因为在Print中使用size()函数和[]操作符的重载函数发生了权限放大。因为在Print函数中s被const修饰,所以当调用成员函数size()和[]操作符的重载函数时传入的this指针为const string类型,而成员函数size()和[]操作符的重载函数的this为string*类型,所以发生了权限放大。


此时就应该将size()函数和[]操作符的重载函数使用const修饰,这样Print函数中就可以调用这两个函数了。此时虽然Print函数可以调用这两个函数了,但是[]操作符的重载函数被const修饰了,那么就不能使用[]操作符来改变string类类型对象中_str的值了,例如s[1]++就不可以了,这肯定是不合理的。

所以此时我们就需要写两个[]操作符的重载函数,一个[]操作符的重载函数被const修饰,这个函数给被const修饰的对象调用。另一个[]操作符的重载函数不使用const修饰,这个函数给那些需要改变对象的值的情况调用。因为编译器会去调用最匹配的[]操作符的重载函数,所以这样的设计就合理了。

然后我们再使用指针来实现string类的迭代器。使用迭代器可以读数据,也可以修改数据。



我们在前面说过范围for在底层就是使用迭代器来完成的,其实范围for就类似于宏的替换,当遇到范围for的语法就在底层替换为迭代器。所以支持了迭代器就支持了范围for。范围for会在底层傻瓜式的调用迭代器的begin、end等方法。所以只要实现了begin、end方法就可以使用范围for了。但是如果我们将begin改为Begin(),此时再使用范围for就会出错,也为范围for只能傻瓜式的调用begin和end等方法,如果没有名字为这些的函数,就会使用不了范围for。


当我们在测试时调用begin和end函数可以调用,但是在Print函数中调用begin和end时就会发生错误,这是因为此时又发生了权限放大,在Print函数中,对象s被const修饰,而begin()和end()的隐藏形参为string this,没有被const修饰,所以调用begin()和end()时发生了权限放大。

所以此时我们就需要再写一个被const修饰的begin和end方法,并且这两个方法返回的类型都为const char* 类型的指针,因为当创建const_iterator的迭代器就说明不希望通过这个迭代器改变对象的值,所以返回一个const char类型的指针就不能通过解引用这个指针来改变指针指向的值。但是这个指针的值是可以改变的,因为还需要通过这个指针++然后访问后面的元素。例如const char str,不能str = “abc”,但是可以str++。

此时Print函数中被const修饰的对象s可以调用被const修饰的begin和end函数,并且此时只能通过迭代器来读取对象s的数据了,并不能通过迭代器来改变对象s的值了。因为被const修饰的begin()函数返回的是const char类型的指针,不能通过解引用来修改这个指针指向的内容。

简单的实现了迭代器后,我们再来实现一下string类类型对象的比较,即实现比较符的重载函数。

上面的这些比较运算符的重载函数当里面的this和const修饰的s对象位置交换一下时,我们发现就会出错,这是因为又发生了权限放大。s对象被const修饰,当s对象调用==比较运算符的重载函数时,这个函数的隐藏参数为string * this ,而s对象被const修饰,即传入的是一个const string 类型的指针,所以发生了权限放大。

此时我们就需要将上面这些比较运算符的重载函数都使用const修饰。这也使我们明白了,那些string类的成员函数中,内部不修改成员变量的值的成员函数都建议加上const修饰,这样才能减少代码出错的概率。

像c_str()这样的函数使用const修饰后,那些被const修饰的对象也可以调用这些函数了。


而那些有时候需要修改成员变量数据的成员函数,有时候又需要被const修饰的对象调用的函数。就需要写两个版本,一个不被const修饰的版本用于修改成员变量数据时调用。一个被const修饰的版本用于给被const修饰的对象调用。


然后我们再来实现尾插字符和尾插字符串的函数。但是当尾插字符和字符串时可能会遇到容量不够的情况,此时就需要进行扩容。

所以我们要先实现reserve扩容函数,然后再实现尾插字符和尾插字符串的函数。当我们像下面这样实现了reserve函数后,我们测试时会发现这个reserve函数会进行缩容,即当我们传入的值n比s1对象的字符串长度小时,此时也会开辟一个n+1大小的新空间,然后将原来的数据拷贝过去,此时因为使用的strcpy拷贝,即strcpy拷贝是遇到’\0’才停止,所以tmp中的数据还是原来的_str,但是因为n小于_str字符串的长度,开辟的空间为n+1大小,根本不够放原来的_str字符串的,所以就会出现错了。


所以我们就需要在扩容时先判断一下,如果n<_capacity时,就不进行缩容操作了,只有当n>_capacity时才进行增容操作。

然后我们再实现push_back函数,此时我们测试时会发现给s1的字符串尾插一个字符后,打印的字符串出现了乱码。这是因为在push_back()中我们将_str原来的’\0’字符变为了ch字符,所以插入ch字符后,_str字符串就没有了’\0’字符串结束符。



所以我们需要在尾插字符ch后,重新将_str的最后一个字符后设置一个’\0’字符串结束符。

此时当我们测试时,又会发现一个新的问题,即当我们给一个_capacity为0的对象调用push_back()函数进行尾插时,因为我们的扩容写的为_capacity2,所以当_capacity为0时,_capacity2还是0,即我们没有给对象扩容成功。


此时我们有两种方法可以解决这个问题,第一个办法是在构造函数中不让_capacity的起始为0,即在构造函数初始化时就开辟少量的空间,然后使_capacity不为0。第二个办法就是在push_back函数调用reserve()函数进行扩容时先判断_capacity的值,如果_capacity为0,就不给reserve函数传参为_capacity*2,而传入一个具体的值。
第一种办法:

第二种办法:

然后我们再实现append()函数。因为append函数是尾插一个字符串,所以开辟的空间为_size+len大小。然后将字符串插入到原字符串的后面,即_str+_size的位置。因为_str为字符串首字符的位置,而_str+_size为字符串的最后一个字符的后面的位置,即为’\0’的位置。而尾插str就是将str插入到这个位置。并且因为strcpy函数中会在最后将目标字符串的后面加上’\0’字符,所以我们不需要关系乱码问题。
将strcpy换成strcat也可以实现,但是使用strcat还需要自己去找\0的位置,然后在\0的位置后追加str字符串,但是本来就知道\0的位置为 _ str + _ size,所以使用strcat会多执行不必要的代码。增加开销。

实现了尾插字符和尾插字符串,+=字符相当于上面这两个函数的结合,所以接下来我们就实现+=运算符的重载函数。因为+=运算符的重载函数可能需要处理单个字符或一个字符串,所以我们对+=进行了重载,当+=一个字符时在底层调用push_back()函数,当+=一个字符串时在底层调用append()函数。

我们知道c++中的string类中,reserve函数为扩容函数,并且不会对扩容的空间进行初始化。而resize函数会对扩容的空间进行初始化,如果调用resize函数时只传入一个参数,则会将扩容的空间初始化为’\0’,如果传入了第二个参数,则会将扩容的空间初始化为第二个参数。所以我们可以尝试来实现一下resize函数。c++中是对resize函数进行了重载,写了两个版本,一个是一个参数的版本,还有一个是两个参数的版本。而我们可以尝试使用半缺省参数的方法将这两个函数和为一个函数。可以看到在测试代码中,对象s1的最后面加上了’\0’字符,并且s1和s2的_size和_capacity都被改变了。而且官方实现的resize中,当n小于_str字符串的长度时,会将字符串直接删除为长度为n,并且也不会实现缩容,因为缩容需要很大的代价,缩容也是开辟新空间,再将值拷贝到新的空间中,也会有很大的开销。所以我们自己在实现时也不能缩容,需要像实现reserve一样进行判断。



然后我们接着实现在某位置插入字符或字符串,还有在某位置删除字符串。我们知道在c++官方的string库中有一个size_t的变量npos,这个变量的值为-1,但是该变量为无符号整型,即其实npos表示int型整数表示的最大值。我们自己实现string类时可以将npos定义为静态成员变量,因为有很多成员函数都使用npos来作为缺省值。当我们定义静态成员变量npos时,发现不可以给静态成员变量缺省值。这是因为普通成员变量给缺省值是在初始化列表中对成员变量进行初始化,而静态成员变量并没有初始化列表进行初始化,所以不可以给静态成员变量缺省值。

但是c++语法中允许被const修饰的静态成员变量在定义时进行初始化,并且这种语法只支持整型。double类型的静态成员变量使用const修饰时就不能在在定义时被初始化了。

可能这种语法是为了下面的情况时更方便。

但是我们自己在写代码时还是使用正常的写法,将静态成员变量在类中定义,在全局域中进行初始化。

然后我们来实现在某位置插入n个字符。我们想到的是当end>=pos时,就将end位置的字符向后移n位,这样移到最后就会在pos位置空出n个位置用来插入n个字符,但是当pos为0时,因为end为size_t类型,为无符号整型,所以当end=-1时会按最大的int型整数来算end值,所以此时end还是大于pos的,就还会执行while后面的语句,这样就会出现越界访问了,所以会报异常。


此时我们可以将end=_size+1,然后当pos为0时,就不会再出现越界的现象了。


当实现了某位置插入字符后,我们再写insert的重载函数来实现某位置插入字符串。其基本思路和上面插入n个字符类似,只不过在拷贝数据时需要使用strncpy函数,因为strcpy函数遇到’\0’就停止拷贝,如果str=“abc\0defg”,则使用strcpy只会拷贝abc,就会少拷贝字符。

当insert函数实现了,其实前面写的push_back函数和append函数就可以复用insert函数来实现了。

下面我们再来实现erase函数,在c++库中的erase函数在删除字符串时也不会进行缩容,所以我们实现erase时也不需要进行缩容处理。


接下来我们实现string库里面的swap函数,我们知道在std库中有一个swap函数的模板,而这个模板的调换两个对象是先创建一个临时对象,然后再调用对象的赋值运算符等进行交换,这个开销是很大的。而string类中的swap函数只是将三个成员变量交换一下即可,所有string类中的swap函数效率更高。

然后我们再实现find函数进行字符或字符串的查找。下面实现的是查找字符,如果找到了就返回该字符第一次出现的下标,如果没有出现该字符就将npos返回。

然后我们再实现查找子串,在内部使用strstr函数来查找子串,strstr返回的是子串的地址,而find函数返回的是子串第一次出现的下标,所以我们可以使用子串的地址减_str的头地址,就得到了子串的下标。

然后我们再来实现<<流插入符的重载函数和>>流提取符的重载函数。我们在实现日期类的流插入符重载函数和流提取符重载函数时,使用了友元函数的概念,那么我们实现这两个函数必须要实现成友元函数吗?当然不需要,因为对于对象的<<流插入我们不需要访问对象的_str成员变量,我们可以使用迭代器或者范围for来遍历对象的字符串。

然后我们使用同样的思路来实现流提取符重载函数。但是下面的代码在测试时我们发现当输入为空格或换行符时,并没有结束输入,还是一直让我们输入。这是因为c或c++中规定多个字符之间的间隔是空格或者换行,所以使用in>>ch时,会将空格或者换行当作字符之间的间隔,然后不会提取到缓冲区里面去,这样就会一直没有结束符,而一直循环输入。


此时我们可以使用get()函数来提取数据,get()函数会将输入的所有字符都放入缓冲区中。此时就可以正常输入了。但是我们发现当测试时,又出现了乱码,这是因为在函数中为s+=ch,当结束时没有’\0’字符串结束符了。并且库里面的流提取符重载函数会清除对象中原来字符串的内容。



所以我们实现的流提取符重载函数也要先清除原来的字符串内容,我们需要写一个clear成员函数,在每次使用流提取符重载函数时先将对象的字符串清空。


这样每次输入时都会将之前的字符串先清空。我们使用+=来给对象s每次追加一个字符,当我们输入的字符串比较长时,+=就会频繁的进行扩容,所以我们可以进行一下优化,库里面是使用了一个buff数组,每当buff数组满了之后就进行一次扩容。这样可以减少扩容的频率。

到此我们模拟实现string类就基本完成了,库里面还有很多方法,我们只模拟实现了string类比较常见的一些方法。
四、windows下的VS中的string类和Linxu下的g++中的string类。
在windows下的VS编译器中,string类中有一个大小为16的Buf数组,但是只能存15个有效字符,因为最后一个空间要留个’\0’,当string类中的字符串大小小于15时,字符串的内容就存到_Buf这个数组中,当string类中的字符串大小大于15时,就会存在动态申请的堆区中了。使用_Buf数据可以使string类的字符串很短时就不需要动态申请内存了,这样也减少了内存碎片的产生。而每个string类大小为28个字节,因为_Buf占16个字节,size_t类型的size和capacity共占8字节,还有一个指针指向动态申请的空间,也占4个字节。



在Linux下的g++编译器中,string类类型对象的大小在32位电脑下为4字节,在64位电脑下为8字节,即只有一个指针的大小。在g++中string是通过写时拷贝实现的,string对象内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数
- 指向堆空间的指针,用来存储字符串。


在Linux下的g++中创建一个string类类型的对象是这样实现的。

当通过拷贝构造创建s2时,g++中这样处理。即先将s2中的_ptr指针也指向s1的空间,s1和s2先同时访问这一片空间,并且此时引用计数_refcount为2,引用计数是为了防止重复析构的,因为如果s1和s2都指向一个空间的话,那么会调用两次析构函数来释放这片空间,但是有了_refcount的概念后,只有当_refcount1时,才调用析构函数,而如果_refcount不为1时,如果有一个指向这片空间的对象销毁了,_refcount就 -1。

如果向s2对象中插入数据或删除数据时,即对这片空间进行写操作时,此时会新开辟一片空间,然后将s1指向的空间的内容都拷贝到新空间去,这样s2修改的就是新空间的内容了,就不会影响s1指向的空间里的内容。而如果只对s2对象进行读操作时,就不需要进行上面的拷贝操作,这样就减少了一次深拷贝。这就是写时拷贝的大致思想。

相关文章:
c++-string
文章目录 前言一、STL库介绍二、标准库中的string类1、string类介绍2、string类使用3.1 string类的构造函数3.2 string类对象的容量操作3.3 string类对象的遍历操作3.4 string类对象的访问操作3.5 string类对象的修改操作3.6 string类对象的字符串操作 三、模拟实现string类四、…...
KNN-K近邻算法(K-Nearest Neighbors)
k近邻算法的特点 思想极度简单应用数学知识少(近乎为零)效果好(缺点?)可以解释机器学习算法使用过程中的很多细节问题更完整的刻画机器学习应用的流程 k近邻算法 k近邻算法整体是这样的一个算法,我们已经知道的这些数据点其实是…...
ChatGPT:理解HTTP请求数据格式:JSON、x-www-form-urlencoded和form-data
ChatGPT:理解HTTP请求数据格式:JSON、x-www-form-urlencoded和form-data 使用postman发送一个post请求,在body里面加上了form-data数据,namexxx,age23,为什么输出request.body()得到的是这样的结果 -------…...
字符集、IO流(一)
字符集、IO流(一) 各位同学,前面我们已经学习了File类,通过File类的对象可以对文件进行操作,但是不能操作文件中的内容。要想操作文件中的内容,我们还得学习IO流。但是在正式学习IO流之前,我们还需要学习一个前置知识叫做字符集,只有我们把字符集搞明白了,再学习IO流…...
相乘(蓝桥杯)
相乘 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。 小蓝发现,他将 1 至 1000000007 之间的不同的数与 2021 相乘后再求除以 1000000007 的余数,会得到不同的数。 小蓝想知道,能不能在 1 …...
[AFCTF 2018]你能看出这是什么加密么
最开始是我对rsa的小小的理解 rsa也就是非对称加密算法,拥有公开的加密密钥和解密密钥,这也是我们写脚本的基础 选取素数p和q,计算乘积npq,以及(n)(p-1)(q-1)。(欧拉函数) 选择一个e值作为密钥…...
基于springboot+vue的重庆旅游网(前后端分离)
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…...
pymysql执行delete删除操作
视频版教程 Python操作Mysql数据库之pymysql模块技术 执行delete操作,雷同前面的update操作 from pymysql import Connectioncon Nonetry:# 创建数据库连接con Connection(host"localhost", # 主机名port3306, # 端口user"root", # 账户…...
25862-2010 制冷与空调用同轴套管式换热器
声明 本文是学习GB-T 25862-2010 制冷与空调用同轴套管式换热器. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了制冷与空调用同轴套管式换热器(以下简称"换热器")的术语和定义、基本参数、要 求、试验、检验规则、标…...
JetBrains 产品安装插件(plugins)的两种方式
安装分为在线、离线两种方式: 在线方式: File > Settings > Plugins 搜索插件 Install 即可 离线方式: 官网:https://plugins.jetbrains.com/ 搜索到插件后,点击 "Get",选择自己安装的…...
SOLIDWORKS二次开发
SOLIDWORKS是一套三维设计软件, 采用特征建模、变量化驱动可方便地实现三维建模、装配和生成工程图。SOLIDWORKS软件本身所具有的交互方式,可以使用户对已生成模型的尺寸、几何轮廓和相互约束关系随时进行修改, 而不需要编程。SOLIDWORKS软件本身的方程式可以实现简…...
Linux下压缩和解压缩
在Linux下,您可以使用多种命令来进行文件和目录的压缩和解压缩操作。以下是一些常见的压缩和解压缩命令: tar:tar命令可用于创建和提取tar压缩文件。例如,要创建一个名为archive.tar的.tar文件,可以使用以下命令&#…...
爬虫入门基础-HTTP协议过程
在进行网络爬虫开发之前,了解HTTP协议的基本过程是非常重要的。HTTP协议是Web通信的基础,也是爬取网页数据的核心。本文将为您详细介绍HTTP协议的过程,帮助您理解爬虫背后的网络通信机制。让我们一起来探索吧! 一、什么是HTTP协议…...
数据结构 第一章作业 绪论 西安石油大学
绪论第1章 1.简述下列概念:数据、数据元素、数据项、数据对象、数据结构、逻辑结构、存储结构、抽象数据类型。 答案: 数据:是客观事物的符号表示,指所有能输入到计算机中并被计算机程序处理的符号的总称。如数学计…...
HTML5福利篇--使用Canvas画图
目录 一.Canvas元素 1.Canvas元素定义 2.使用JavaScript获取页面中的Canvas对象 二.绘制图形 1.绘制直线 2.绘制矩形 (1)rect() (2)strokeRect() (3)fillRect()和clearRect()函数 3.绘制圆弧 4.…...
基于Matlab实现图像目标边界描述
图像目标边界描述是图像处理中的一个重要问题。边界描述可以用于目标检测和识别、图像分割等应用。Matlab提供了强大的图像处理工具箱,可以方便地实现图像目标边界描述。本文介绍一种基于边缘检测的图像目标边界描述方法,并提供一个简单的案例源码。 文章…...
汽车电子——产品标准规范汇总和梳理(自动驾驶)
文章目录 前言 一、分级 二、定位 三、地图 四、座舱 五、远程 六、信息数据 七、场景 八、智慧城市 九、方法论 总结 前言 见《汽车电子——产品标准规范汇总和梳理》 一、分级 《GB/T 40429-2021 汽车驾驶自动化分级》 《QC/T XXXXX—XXXX 智能网联汽车 自动驾…...
redis部署与管理
目录 一、关系数据库与非关系型数据库: 1. 关系型数据库: 2.非关系型数据库: 二、关系型数据库和非关系型数据库区别: (1)数据存储方式不同: (2)扩展方式不同…...
MySQL 事件
文章目录 1.简介2.事件调度器3.创建事件4.查看事件5.修改事件6.删除事件参考文献 1.简介 MySQL 事件(Event)事件是根据时间表运行的任务,类似于 Unix crontab 和 Windows 定时任务。 一个事件可调用一次,也可周期性地启动。它由…...
软件项目费用计算方法
计算软件项目的费用是项目管理的关键组成部分之一。费用计算方法可以帮助您确定项目的总成本,包括开发、测试、维护和其他相关费用。以下是一些常见的软件项目费用计算方法,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
