C++初阶:STL之string类

一.为什么学习string类?

在C语言中没有字符串这一数据类型,都是用字符数组来处理字符串,C++也支持这种C风格的字符串。除此之外,C++还提供了一种自定义数据类型--string,string是C++标准模板库(STL)中的一个字符串类,包含在头文件string中,它能更方便快捷地定义,操作字符串。用C++ string类来定义字符串,不必担心字符串中字符溢出,内存不足等情况,而且string类中重载的运算符和提供的多个函数足以完成我们针对字符串所需要的所有操作。

1.C语言中的字符串

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

二.标准库中的string类

2.1.string类

1.string是表示字符串的字符串类;
2.该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作;
3.string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
4.不能操作多字节或者变长字符的序列。

注意:

在使用string类时,必须包含#include<string>头文件以及using namespace std

2.2.string类的常用接口说明

string类对象的常见构造

          (constructor)函数名称                   功能说明
                string() (重点) 构造空的string类对象,即空字符串
      string(const char* s) (重点)     用C-string来构造string类对象
          string(size_t n, char c)     string类对象中包含n个字符c
     string(const string&s) (重点)                 拷贝构造函数

案例:

#include<iostream>
#include<string>
using namespace std;int main()
{//string()string s1;//构造一个空字符串cout << s1 << endl;//string (const char* s)string s2("hello world");//用C-string来构造string类对象cout << s2 << endl;//string& operator= (const char* s);		string s3 = "hello world";//类型转换,将一个字符串赋值给到string对象cout << s3 << endl;//string(size_t n, char c)string s4(5, 'c');//用5个c字符组成的字符串来构造string类对象cout << s4 << endl;//string(const string& str)string s5(s2);//用原有的字符串s2进行拷贝构造cout << s5 << endl;//string(const string& str, size_t pos, size_t len = npos)string s6(s3, 6, 3);//从s3字符串中的pos位置开始拷贝len个字符cout << s6 << endl;return 0;
}

运行结果:

扩展:

basic_string::npos
static const size_type npos = -1;

npos是一个静态成员常量,类型一般是string::size_type,多用于容器中,用来表示不存在的位置。由于size_t为无符号整型,于是-1就被转换为无符号整数类型,因此npos也就成了该类别的最大无符号值。

string类对象的容量操作

              函数名称                        功能说明
            size(重点)             返回字符串有效字符长度
               length             返回字符串有效字符长度
              capacity                   返回空间总大小
          empty (重点)检测字符串是否为空串,是返回true,否则返回false
           clear (重点)                   清空有效字符
         reserve (重点)                为字符串预留空间
          resize (重点)将有效字符的个数该成n个,多出的空间用字符c填充

详解:

size:获取字符串长度(不包括末尾\0)

length:获取字符串长度(不包括末尾\0)

注意:

size()与length()的方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一
致,一般情况下基本都是用size()。

capacity:获取当前string类对象能够存储的有效字符量

注意:

在VS平台下,capacity的默认大小为15字节(不包括末尾\0),它的容量会随着字符串的变长而发生相应的变化。

案例:

int main()
{string s1("hello world");//用size()和length()两个函数求得字符串s的结果是一样的,都不包括末尾的'\0'cout << s1.size() << endl;cout << s1.length() << endl;cout << s1.capacity() << endl;cout << "--------------------" << endl;string s2;cout << s2.size() << endl;cout << s2.length() << endl;cout << s2.capacity() << endl;cout << "--------------------" << endl;string s3("hello world,we are togethere!");cout << s3.size() << endl;cout << s3.length() << endl;cout << s3.capacity() << endl;cout << "--------------------" << endl;return 0;
}

运行结果:

empty:判断字符串是否为空

clear:清空字符串的内容

注意:

clear()只是将string中有效字符清空,不改变底层空间大小。

案例:

int main()
{string s1("hello world");cout << s1 << endl;cout << s1.size() << endl;cout << s1.capacity() << endl;cout << s1.empty() << endl;s1.clear();cout << s1 << endl;cout << s1.size() << endl;cout << s1.capacity() << endl;cout << s1.empty() << endl;return 0;
}

运行结果:

resize:将字符串的长度(size\length)变为n

reserve:为字符串预留空间

注意:

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不会改变容量大小。

案例:

int main()
{//reserve:为字符串预留空间,知道需要多少空间,提前开辟空间,减少扩容,提高效率//扩容string s1("hello world");cout << s1.size() << endl;cout << s1.capacity() << endl;s1.reserve(50);cout << s1.size() << endl;//不会发生改变cout << s1.capacity() << endl;cout << s1 << endl;cout << "------------" << endl;//扩容+初始化string s2("hello world");//s2.resize(50);//resize:将有效字符的个数改成n个,多出的空间用字符c填充s2.resize(50, 'x');//多出的空间用字符x填充cout << s2.size() << endl;//会发生改变cout << s2.capacity() << endl;cout << s2 << endl;cout << "------------" << endl;//比size小,删除数据,保留前5个s2.resize(5);cout << s2.size() << endl;cout << s2.capacity() << endl;//不会发生变化cout << s2 << endl;return 0;
}

运行结果:

扩展:

string在Windows下是如何进行增容的?我们使用下面这段代码进行测试:

int main()
{string s;//s.reserve(100);size_t sz = s.capacity();cout << "making s grow:\n";cout << "capapcity changed:" << sz << '\n';for (int i = 0; i < 100; i++){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capapcity changed:" << sz << '\n';}}return 0;
}

运行结果:

可以看到在Windows下的VS中,capacity大概是以1.5倍的速度进行增容的;而在Linux下的g++中,capacity大概是以2倍的速度进行增容的。

但是,我们往往会优先考虑使用reserve为字符串预留空间。只要知道需要多少字节的空间,并提前进行开辟空间,就可以减少扩容,提高运行效率。

string类对象的访问及遍历操作

                   函数名称                    功能说明
           operator[] (重点)返回pos位置的字符,const string类对象调用
                 begin+endbegin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
                rbegin+rendrbegin获取最后一个字符的迭代器 + rend获取第一个字符前一个位置的迭代器
                     范围for

C++11支持更简洁的范围for的新遍历方式

operator[]:返回pos位置的字符

at:访问指定下标位置处的字符

注意:

operator[]和at的功能是相似的,但是不一样的地方在于对越界的处理:operator[]直接使用断言报错,而at则会抛出异常。

案例一:

int main()
{string s("abcdefgh");cout << s << endl;for (size_t i = 0; i < s.size(); i++){//可以对字符进行修改s[i]++;}cout << s << endl;for (size_t i = 0; i < s.size(); i++){cout << s[i] << " ";}cout << endl;const string s2("hello bite");//s2[0] = 'h';//编译失败,因为const类型对象不能修改//通过at函数,遍历打印s2的每个字符for (size_t i = 0; i < s2.size(); ++i){cout << s2.at(i) << " ";}cout << endl;return 0;
}

运行结果: 

案例二:

int main()
{string s1("hello world");try{s1.at(100);}catch (const exception& e){cout << e.what() << endl;}return 0;
}

运行结果:

迭代器是泛化的指针,STL算法利用迭代器对容器对容器中的元素序列进行操作,迭代器提供了访问容器中每个元素的方法。

begin:获取第一个字符的迭代器

end:获取最后一个字符下一个位置的迭代器

注意:

string提供了begin()与end()函数,这一对函数返回的是头尾元素的迭代器(可以理解为指针)。begin()返回第一个元素的迭代器,end()返回最后一个元素的迭代器。

rbegin:获取最后一个字符的迭代器

rend:获取第一个字符前一个位置的迭代器

注意:

string提供了rbegin()与rend()这样一对函数,用于返回一个逆向迭代器,rbegin()返回的是逆向遍历的第一个元素,即倒数第一个元素,rend()返回的是逆向遍历的末尾元素的后一个位置,即第一个元素的上一个位置,其实这个位置已经不在容器中了。

案例:

void Func(const string& s)
{//迭代器iterator//string::iterator it = s.begin();//err,要用const迭代器,且不允许写,只能遍历和读容器的数据string::const_iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;
}//正向迭代器,const正向迭代器,反向迭代器,const反向迭代器
int main()
{string s1("hello world");//迭代器iterator:可以遍历和读取容器的数据string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout << endl;Func(s1);//反向迭代器//string::reverse_iterator rit = s1.rbegin();auto rit = s1.rbegin();//auto可以自动推导类型while (rit != s1.rend()){cout << *rit << " ";++rit;}cout << endl;return 0;
}

运行结果:

范围for:对一组序列中的每个对象进行操作

如果想对一组序列中的每个对象进行操作,可以使用C++中提供的基于范围的for语句。该语句遍历序列中每个元素并对序列中的每个值进行某种操作。

案例:

int main()
{string s1("hello world");string::iterator it = s1.begin();//begin()指向起始位置,end()指向最后一个字符的下一个位置,左闭右开while (it != s1.end()){cout << *it << " ";++it;}cout << endl;//范围forfor (auto ch : s1){cout << ch << " ";}cout << endl;//修改for (auto& ch : s1){ch++;}for (auto ch : s1){cout << ch << " ";}cout << endl;return 0;
}

运行结果:

注意:

auto ch : s1 只能读,不能写;auto& ch : s1 可读可写。范围for的底层实现原理还是迭代器,所以一个类如果不支持迭代器就不支持范围for。

string类对象的修改操作

                函数名称                        功能说明
              push_back               在字符串后尾插字符c
                append           在字符串后追加一个字符串
          operator+= (重点)             在字符串后追加字符串str
               c_str(重点)                   返回C格式字符串
          find + npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
                   rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
                  substr在str中从pos位置开始,截取n个字符,然后将其返回

push_back: 在字符串后尾插字符c

append:在字符串后追加一个字符串

operator+=:在字符串后追加字符串str

注意:

在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。与 push_back和append相比,更加推荐使用operator+=,因为它可以追加一个字符,也可以追加一个字符串,甚至可以追加一个对象。

案例:

int main()
{string s1("hello");//插入单个字符s1.push_back(' ');s1.push_back('!');cout << s1 << endl;//插入字符串s1.append(" world");cout << s1 << endl;//+=:在字符串后面追加字符串strs1 += ' ';s1 += '!';s1 += " world";cout << s1 << endl;return 0;
}

运行结果:

c_str:返回C格式字符串

案例:

int main()
{string s1("hello world");cout << s1 << endl;//流插入是按照size去打印的cout << s1.c_str() << endl;//返回C形式的字符串,遇到'\0'则终止cout << (void*)s1.c_str() << endl;//按指针打印cout << "--------------------" << endl;cout << s1 << endl;cout << s1.c_str() << endl;s1 += '\0';s1 += '\0';s1 += "xxxxx";cout << s1 << endl;cout << s1.c_str() << endl;cout << "--------------------" << endl;string filename("test.cpp");//string类给我们提供了一个接口函数c_str,帮助我们将string对象转换为C字符串//否则编译会报错:不存在从"std:string"到"const char*"的适当转换函数//FILE * fopen ( const char * filename, const char * mode );FILE* fout = fopen(filename.c_str(), "r");//加c_str()是为了兼容cif (fout == nullptr)perror("fopen fail");char ch = fgetc(fout);while (ch != EOF){cout << ch;ch = fgetc(fout);}fclose(fout);return 0;
}

运行结果:

find:从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

npos:

rfind:从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置

sbustr:在str中从pos位置开始,截取n个字符,然后将其返回

案例:

int main()
{string file("string.cpp.tar.zip");size_t pos = file.find('.');if (pos != string::npos){//substr:在str中从pos位置开始,截取n个字符,然后将其返回//string suffix = file.substr(pos, file.size() - pos);string suffix = file.substr(pos);//给缺省值,则有多少取多少cout << suffix << endl;}string url("http://www.cplusplus.com/reference/string/string/find/");cout << url << endl;size_t start = url.find("://");if (start == string::npos){cout << "invalid url" << endl;return -1;}start += 3;size_t finish = url.find('/', start);string address = url.substr(start, finish - start);cout << address << endl;return 0;
}

运行结果:

扩展:

find_first_of:从起始位置开始找出所有相关字符
find_last_of:从最后位置开始找出所有相关字符

案例:

int main()
{string str("Please,replace the vowels in this sentence by asterisks.");size_t found1 = str.find_first_of("aeiou");while (found1 != string::npos){str[found1] = '*';found1 = str.find_first_of("aeiou", found1 + 1);}cout << str << endl;size_t found2 = str.find_last_of("aeiou");while (found2 != string::npos){str[found2] = '*';found2 = str.find_first_of("aeiou", found2 + 1);}cout << str << endl;return 0;
}

运行结果:

string类非成员函数

                    函数名称                   功能说明
                   operator+尽量少用,因为传值返回,导致深拷贝效率低
              operator>> (重点)              输入运算符重载
              operator<< (重点)              输出运算符重载
                   getline (重点)              获取一行字符串
       relational operators (重点)                   大小比较

operator+:尽量少用,因为传值返回,导致深拷贝效率低

案例:

int main()
{string s1("hello ");string s2("world");string s3 = s1 + s2;cout << s1 << endl;cout << s3 << endl;s1 += s2;cout << s1 << endl;return 0;
}

运行结果:

注意:

operator+ 和 operator+=:它们的作用都是对字符串进行尾插,但是 operator+= 会对当前字符串进行修改,而 operator+ 则不会对当前字符串进行修改。

operator>>: 输入运算符重载

operator<<:输出运算符重载

getline:获取一行字符串

案例:

int main()
{string str;getline(cin, str);cout << str << endl;return 0;
}

运行结果:

vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证的,32位平台下指针占4个字节。

vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字
符串的存储空间:

  1. 当字符串长度小于16时,使用内部固定的字符数组来存放;
  2. 当字符串长度大于等于16时,从堆上开辟空间。
union _Bxty
{ //storage for small buffer or pointer to larger onevalue_type _Buf[_BUF_SIZE];pointer _Ptr;char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量。
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。 

g++下string的结构 

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指
针将来指向一块堆空间,内部包含了如下字段:

  1. 空间总大小;
  2. 字符串有效长度;
  3. 引用计数。
struct _Rep_base
{size_type _M_length;size_type _M_capacity;_Atomic_word _M_refcount;
};

 注意:指向堆空间的指针,用来存储字符串。

三.牛刀小试

题一:仅仅反转字母

题目描述:

给你一个字符串 s ,根据下述规则反转字符串:

  • 所有非英文字母保留在原有位置。
  • 所有英文字母(小写或大写)位置反转。

返回反转后的 s

示例 1:

输入:s = "ab-cd"
输出:"dc-ba"

示例 2:

输入:s = "a-bC-dEf-ghIj"
输出:"j-Ih-gfE-dCba"

示例 3:

输入:s = "Test1ng-Leet=code-Q!"
输出:"Qedo1ct-eeLg=ntse-T!"

实现:

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){size_t begin = 0, end = s.size() - 1;while (begin < end){while (begin < end && !isLetter(s[begin])){++begin;}while (begin < end && !isLetter(s[end])){--end;}swap(s[begin], s[end]);}return s;}
};

题二:字符串中的第一个唯一字符

题目描述:

给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。

示例 1:

输入: s = "leetcode"
输出: 0

示例 2:

输入: s = "loveleetcode"
输出: 2

示例 3:

输入: s = "aabb"
输出: -1

实现:

class Solution
{
public:int firstUniqChar(string s){int countA[26] = { 0 };//统计每个字符出现的次数for (auto ch : s){countA[ch - 'a']++;}// 按照字符次序从前往后找只出现一次的字符for (int i = 0; i < s.size(); ++i){if (countA[s[i] - 'a'] == 1)return i;}return -1;}
};

题三:字符串最后一个单词的长度

题目描述:

计算字符串最后一个单词的长度,单词以空格隔开,字符串长度小于5000。(注:字符串末尾不以空格为结尾。)

输入描述:输入一行,代表要计算的字符串,非空,长度小于5000。

输出描述:输出一个整数,表示输入字符串最后一个单词的长度。

示例:

  输入:hello nowcoder
  输出:8
  说明:最后一个单词为nowcoder,长度为8  

实现:

int main()
{string str;//cin >> str;//err,cin遇到空格或换行就会停止读取getline(cin, str);//获取一行字符串size_t pos = str.rfind(' ');if (pos != string::npos){cout << str.size() - pos - 1 << endl;//-1是减去空格}else//没有空格,只包含一个单词{cout << str.size() << endl;}return 0;
}

题四:验证回文串

题目描述:

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false

示例 1:

输入: s = "A man, a plan, a canal: Panama"
输出:true
解释:"amanaplanacanalpanama" 是回文串。

示例 2:

输入:s = "race a car"
输出:false
解释:"raceacar" 不是回文串。

示例 3:

输入:s = " "
输出:true
解释:在移除非字母数字字符之后,s是一个空字符串""。由于空字符串正着反着读都一样,所以是回文串。

实现:

class Solution 
{
public:bool isLetterOrNumber(char ch){return (ch >= '0' && ch <= '9')|| (ch >= 'a' && ch <= 'z')|| (ch >= 'A' && ch <= 'Z');}bool isPalindrome(string s) {// 先小写字母转换成大写,再进行判断for (auto& ch : s){if (ch >= 'a' && ch <= 'z')ch -= 32;}int begin = 0, end = s.size() - 1;while (begin < end){while (begin < end && !isLetterOrNumber(s[begin]))++begin;while (begin < end && !isLetterOrNumber(s[end]))--end;if (s[begin] != s[end]){return false;}else{++begin;--end;}}return true;}
}

题五:字符串相加

题目描述:

给定两个字符串形式的非负整数 num1num2 ,计算它们的和并同样以字符串形式返回。

你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

示例 1:

输入:num1 = "11", num2 = "123"
输出:"134"

示例 2:

输入:num1 = "456", num2 = "77"
输出:"533"

示例 3:

输入:num1 = "0", num2 = "0"
输出:"0"

实现:

class Solution
{
public :string addStrings(string num1, string num2){int end1 = num1.size() - 1, end2 = num2.size() - 1;int next = 0;//进位//为字符串预留空间,避免开辟空间,提高效率string strRet;strRet.reserve(num1.size() > num2.size() ? num1.size() + 1 : num2.size() + 1);while (end1 >= 0 || end2 >= 0){/*int val1 = 0;if (end1 >= 0){val1 = num1[end1] - '0';}int val2 = 0;if (end2 >= 0){val2 = num1[end2] - '0';}*/int val1 = end1 >= 0 ? num1[end1] - '0' : 0;int val2 = end2 >= 0 ? num2[end2] - '0' : 0;int ret = val1 + val2 + next;//if (ret > 9)//{//	ret -= 10;//	next = 1;//进位置为1//}//else//{//	next = 0;//}next = ret / 10;ret = ret % 10;//将运算的结果进行头插//strRet.insert(0, 1, '0' + ret);//将运算结果进行尾插strRet += ('0' + ret);--end1;--end2;}//防止最后的进位没有处理赶紧,比如9+1if (next == 1){strRet.insert(0, 1, '1');}reverse(strRet.begin(), strRet.end());return strRet;}
};

四.string类的模拟实现

上面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。

前提:为了避免和string类发生冲突,我们采用命名空间的方式来隔离冲突域。如下所示:

namespace bit
{class string {public://...private:size_t _size;size_t _capacity;char* _str;};
}

4.1.基本成员函数

构造函数

版本一:无参构造函数

采用初始化列表的方式,首先将_size_capacity的初始值设为0,然后给字符数组_str开辟一个字节大小的空间,并将其初始化为\0,同时也是为了同带参构造函数一样,在析构时,统一进行delete[]。

string()//不要给空指针,解引用可能会导致程序崩溃:_str(new char[1])//加上[1]是为了同带参构造函数一样,在析构时,统一delelte[], _size(0), _capacity(0)
{_str[0] = '\0';
}

版本二:带参构造函数

与无参构造函数不同,我们这里并不是直接将_size和_capacity初始化为0。首先调用strlen函数统计字符串str中有效字符个数(不包含\0),并采用初始化列表的方式对_size进行初始化,同时在函数体内将_capacity的初始值置为与_size相同大小。然后为_str开辟_capacity+1大小的空间。最后再调用strcpy函数,将字符串str拷贝到字符数组_str中。

string(const char* str):_size(strlen(str))
{_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);
}

版本三:全缺省构造函数

我们将上述的无参与带参构造函数合二为一,归并为全缺省的构造函数。同时对_capacity的初始值做一些改进,避免后续问题的出现。

//string(const char* str = nullptr)//err,会导致空指针解引用
//string(const char* str = '\0')//err,\0的ascii码是0
string(const char* str = "")//或者const char* str = "\0":_size(strlen(str))
{_capacity = _size == 0 ? 3 : _size;//要确保_capacity不能为0,否则调用push_back()插入字符时会失败,导致程序崩溃_str = new char[_capacity + 1];strcpy(_str, str);
}

拷贝构造函数

在一个类中若没有显示定义拷贝构造函数,对于内置类型不作处理,而对于自定义类型则会调用类中提供的默认拷贝构造函数,但此时则会造成浅拷贝的问题。为了解决这个问题,我们必须显示地实现一个拷贝构造函数。

​string(const string& s):_size(s._size),_capacity(s._capacity)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);
}

扩展:

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

深拷贝:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

赋值运算符重载

赋值运算符重载与拷贝构造相类似,都存在浅拷贝问题。为了避免调用默认赋值运算符重载函数而导致的内存泄漏,我们需要手动实现以达到深拷贝。

string& operator=(const string& s)
{//检查自己给自己赋值if (this != &s){/*delete[] _str;_str = new char[s._capacity + 1];//开空间失败会导致异常strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;*/char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}

析构函数

析构函数所要完成的工作:释放掉string类对象所申请的内存空间,并将_size和_capacity置为0。

~string()
{delete[] _str;_str = nullptr;_size = _capacity = 0;
}

4.2.容器操作函数

size

size()主要用于获取当前字符串的有效长度。注意:要加const加以修饰,否则会造成权限放大。

size_t size() const
{return _size;
}

capacity

capacity()主要用于获取空间总大小。注意:要加const加以修饰,否则会造成权限放大。

size_t capacity() const
{return _capacity;
}

clear

clear()主要用于清空有效字符。直接在_str[0]的位置存放上一个\0即可,并将_size置为0即可。

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

empty

empty()主要用于判断字符串是否为空,直接判断0==_size是否成立。

bool empty() const
{return 0 == _size;
}

reserve

当所需容量n大于旧容量_capacity时,我们就会选择进行扩容。首先要对n>_capacity是否成立进行判断,目的是防止缩容,导致程序异常,然后开辟一块n+1字节大小的空间,接着将旧空间的内容拷贝到新空间并释放旧空间的内容,最后让_str指向新空间并更新_capacity的大小。

void reserve(size_t n)
{if (n > _capacity)//防止缩容,导致程序异常{char* tmp = new char[n + 1];//开辟新空间,包含'\0'strcpy(tmp, _str);//将旧空间的内容拷贝到新空间delete[] _str;//释放旧空间_str = tmp;_capacity = n;//包含有效字节的空间,不包含'\0'}
}

resize

resize(size_t n, char ch = '\0'):主要是将有效字符个数变为n,若有空间多出则用字符ch填充。此时分三种情况进行讨论:

  1. 情况一:若n<_size,则将多余的数据直接进行删除,并将下标为n位置处的元素改为\0;
  2. 情况二:若_size<n<_capacity,则把多出的空间用字符ch填充,并将下标为n位置处的元素改为\0;
  3. 情况三:若n>_capacity,则先调用reserve函数进行扩容,再把多出的空间用字符ch填充,最后再将下标为n位置处的元素改为\0。 
void resize(size_t n, char ch = '\0')
{if (n < _size)//=时不作处理{//删除数据,保留前n个_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity){reserve(n);}size_t i = _size;while (i < n){_str[i] = ch;++i;}_size = n;_str[_size] = '\0';}
}

4.3.访问及遍历操作函数

operator[ ]

operator[ ]主要用于返回pos位置的字符,它有两种实现形式:一个是用const string类对象进行调用但可读不可写,一个是用string类对象进行调用但可读可写。

版本一:给const对象调用,不允许修改

//给const对象调用,不允许修改
const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}

版本二:给非const对象调用,允许修改

//给非const对象调用,允许修改
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}

begin

begin()主要用于获取第一个字符的迭代器,它有两种实现形式:非const迭代器和const迭代器。

版本一:非const迭代器

//非const迭代器
typedef char* iterator;iterator begin()
{return _str;
}

版本二:const迭代器

//const迭代器
typedef const char* const_iterator;//可以修改,但指向的内容不能修改const_iterator begin() const
{return _str;
}

end

end()主要用于获取最后一个字符的迭代器,它有两种实现形式:非const迭代器和const迭代器。它往往会和begin()配套使用。

版本一:非const迭代器

//非const迭代器
typedef char* iterator;iterator end()
{return _str + _size;
}

版本二:const迭代器

//const迭代器
typedef const char* const_iterator;//可以修改,但指向的内容不能修改const_iterator end() const
{return _str + _size;
}

4.4.修改操作函数

push_back

void push_back(char ch)主要用于在字符串后尾插字符ch。在插入字符之前,首先要判断当前容量是否充足,即_size+1是否大于_capapcity,若大于则先调用reserve函数进行扩容,再进行字符的插入,否则直接进行字符的插入。因为_size指向\0的位置,所以可以把字符直接放在这个位置,同时将_size进行自增并加上\0,否则会发生乱码。

void push_back(char ch)
{if (_size + 1 > _capacity){reserve(_capacity * 2);}_str[_size] = ch;++_size;//要加'\0',否则会发生乱码_str[_size] = '\0';//insert(_size, ch);
}

append

void append(const char* str)主要用于在字符串后追加一个字符串。首先要计算所追加字符串的长度len,然后要判断当前容量是否充足,即_size+len是否大于_capapcity,若大于则先调用reserve函数进行扩容,再进行字符串的插入,否则直接进行字符串的插入。

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

operator+=

operator+=主要用于在字符串后追加字符串或字符,有两种形式:operator+=(char ch)用于追加单个字符,operator+=(const char* s)用于追加字符串。

版本一:operator+=(char ch)

这里主要通过调用push_back()对单个字符进行追加。又因为要改变自身,所以我们要返回*this。同时采用引用返回,可以有效减少拷贝。

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

版本二:operator+=(const char* s)

这里主要通过调用append()对单个字符进行追加。又因为要改变自身,所以我们要返回*this。同时采用引用返回,可以有效减少拷贝。

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

c_str

c_str()主要用于将string类对象转换成C格式字符串。

const char* c_str()
{return _str;
}

find

find主要用于从字符串pos位置开始往后找字符或字符串,有两种形式:size_t find(char ch, size_t pos = 0)用于从pos位置开始查找字符ch,size_t find(const char* str, size_t pos = 0)用于从pos位置开始查找字符串str。

版本一:size_t find(char ch, size_t pos = 0)

首先判断pos位置是否合法,若合法则从pos位置开始依次向后遍历,然后与字符ch逐个进行比较,若相同则返回下标所在位置,若不相同则继续往后比较。若查找失败,则直接返回npos。

size_t find(char ch, size_t pos = 0)
{assert(pos < _size);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)

首先判断pos位置是否合法,若合法则调用函数strstr,将字符串_str与字符串str从pos位置开始进行逐一比较并返回指针p。若p的值为nullptr则表明查找失败,若p的值不为nullptr则直接返回相应字符串的下标。

size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);//strstr(str1,str2)用于判断字符串str2是否是str1的子串//如果是,则返回str1字符串从str2第一次出现的位置开始到str1结尾的字符串;否则,返回NULLchar* p = strstr(_str + pos, str);if (p == nullptr){return npos;}else{return p - _str;}
}

insert

insert()用于在字符串的某个位置插入一个字符或字符串,有两种形式:string& insert(size_t pos, char ch)用于在pos位置插入一个字符ch,string& insert(size_t pos, const char* str)用于在pos位置插入一串字符str。

版本一:string& insert(size_t pos, char ch)

在插入字符之前,首先要判断插入位置是否合法,然后要判断当前容量是否充足,即_size+1是否大于_capapcity,若大于则先调用reserve函数进行扩容。在插入数据之前,先将pos位置之后的数据由后向前依次向后移动一位,再在pos位置插入字符ch,同时将_size的值+1。

string& insert(size_t pos, char ch)
{assert(pos <= _size);if (_size + 1 > _capacity){reserve(2 * _capacity);}//size_t end = _size;//size_t是无符号数,设pos=0,则当end=0时,--end会变成-1,而-1的无符号数为最大值,所有要改成有符号数,但依旧会报错,因为会发生隐式类型提升(end是有符号数,pos是无符号数),可以将pos改为有符号数//while (end >= pos)//end>=(int)pos//{//	_str[end + 1] = _str[end];//	--end;//}size_t end = _size + 1;while (end > pos){_str[end + 1] = _str[end];--end;}_str[pos] = ch;++_size;return *this;
}

版本二:string& insert(size_t pos, const char* str)

在插入字符之前,首先要判断插入位置是否合法,然后要判断当前容量是否充足,即_size+len是否大于_capapcity,若大于则先调用reserve函数进行扩容。在插入数据之前,先将pos位置之后的数据由后向前依次向后移动len位,再调用函数strncpy将字符串str拷贝至_str+pos位置,同时将_size的值+len。

string& 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;while (end > pos + len - 1)//当len=1时,相当于插入了一个字符{_str[end] = _str[end - len];--end;}//法二/*size_t end = _size;for (size_t i = 0; i < _size + 1; i++){_str[end + len] = _str[end];}*///拷贝插入strncpy(_str + pos, str, len);_size += len;return *this;
}

erase

string& erase(size_t pos, size_t len = npos)主要用于删除从pos位置开始长为len的字符。首先要判断pos的位置是否合法,然后对len的取值进行判断。若len的值为npos或者pos+len>=_size,则直接将pos位置以后的数据进行删除,这里主要是通过_str[pos] = '\0'进行实现,最后再更新_size;若len的值不为npos且pos+len<_size,则调用函数strcpy将_str + pos + len位置开始的数据拷贝至_str + pos位置,最后再更新_size。

string& erase(size_t pos, size_t len = npos)
{assert(pos < _size);//不给npos,则从pos位置开始将后面数据全部删完if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;
}

swap

void swap(string& s)主要用于实现对象之间的数据交换,为了提高程序的运行效率,这里通过调用std库中的swap函数进行功能实现。

void swap(string& s)
{//调用std库中的swapstd::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size);
}

4.5.非成员函数

relational operators

relational operators是关系运算符重载,主要进行大小比较。常见的关系运算符有:>, ==, >=, <, <=, !=等。我们要明白的一点是:在比较两个string类对象时,并不是拿它们所对应的字符串长度进行比较,而是去比较它们所对应字符的ASCLL码值。

operator>

主要通过调用函数strcmp,对两个字符串自左向右逐个字符进行比较。若比较结果大于0则返回true,否则返回false。

bool operator>(const string& s) const
{//strcmp用于比较两个字符串并根据比较结果返回整数//两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止return strcmp(_str, s._str) > 0;
}
operator==

主要通过调用函数strcmp,对两个字符串自左向右逐个字符进行比较。若比较结果等于0则返回true,否则返回false。

bool operator==(const string& s) const
{return strcmp(_str, s._str) == 0;
}
operator>=

复用关系运算符:operator >和operator==

bool operator>=(const string& s) const
{//return *this > s || s == *this;//err,因为s是const对象,不能调用非const的成员函数,所以要改写成const成员函数return *this > s || *this == s;
}
operator<

复用关系运算符:operator >=,并进行取反操作。

bool operator<(const string& s) const
{return !(*this >= s);
}
operator<=

复用关系运算符:operator >,并进行取反操作。

bool operator<=(const string& s) const
{return !(*this > s);
}
operator!=

复用关系运算符:operator ==,并进行按位取反操作。

bool operator!=(const string& s) const
{return !(*this == s);
}

operator<<

operator<<用于输出运算符重载,但是要将其定义为类外的全局函数,同时进行引用返回,这样可以减少拷贝。

ostream& operator<<(ostream& out, const string& s)
{//out<<s.c_str()//err,因为这样遇到'\0'就会截止输出,从而不一定输出s.size()个字符//C的字符数组, 以\0为终止算长度,string不看\0, 以size为终止算长度for (auto ch : s){out << ch;}return out;
}

operator>>

operator<<用于输入运算符重载,需要将其定义为类外的全局函数,具体过程可以分为两步,一是清理缓存,二是读取数据。清理缓存主要是通过调用clear()函数进行;读取字符主要是通过调用get()函数进行,然后开辟一个char类型的数组用于对读取的字符进行存储,这样可以减少频繁扩容带来的损失。

istream& operator>>(istream& in, string& s)
{//清理缓存s.clear();//读取数据//get()函数是cin输入流对象的成员函数,用来从指定的输入流中提取一个字符(包括空白字符),函数的返回值就是读入的字符char ch = in.get();char buff[128];//提前开辟128字节的空间,减少频繁扩容带来的损失size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[127] = '\0';//每满一次buff,则重新加一次s += buff;i = 0;}ch = in.get();}//若是有数据且未满127字符,则直接进行读入if (i != 0){buff[i] = '\0';s += buff;}return in;
}

4.6.string类的完整实现

string.h

#pragma once
#include<assert.h>//string类的模拟实现
namespace bit
{class string{public:无参构造函数//string()//	//不要给空指针,解引用可能会导致程序崩溃//	:_str(new char[1])//加上[1]是为了同带参构造函数一样,在析构时,统一delete[]//	, _size(0)//	, _capacity(0)//{//	_str[0] = '\0';//}带参构造函数//string(const char* str)//	:_size(strlen(str))//{//	_capacity = _size;//	_str = new char[_capacity + 1];//	strcpy(_str, str);//}//无参和带参的合二为一:全缺省的构造函数//string(const char* str = nullptr)//err,会导致空指针解引用//string(const char* str = '\0')//err,\0的ascii码是0string(const char* str = "")//或者const char* str = "\0":_size(strlen(str)){_capacity = _size == 0 ? 3 : _size;//要确保_capacity不能为0,否则调用push_back()插入字符时会失败,导致程序崩溃_str = new char[_capacity + 1];strcpy(_str, str);}//拷贝构造//深拷贝string(const string& s):_size(s._size),_capacity(s._capacity){_str = new char[s._capacity + 1];strcpy(_str, s._str);}//赋值//深拷贝//s1<s2 s1=s2 s1>s2string& operator=(const string& s){//检查自己给自己赋值if (this != &s){/*delete[] _str;_str = new char[s._capacity + 1];//开空间失败会导致异常strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;*/char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}//析构~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}//将string类对象转化为C格式字符串const char* c_str(){return _str;}//给const对象调用,不允许修改const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}//给非const对象调用,允许修改char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//要加const,否则会造成权限放大size_t size() const{return _size;}//获取容量大小size_t capacity() const{return _capacity;}//非const迭代器typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}//const迭代器typedef const char* const_iterator;//可以修改,但指向的内容不能修改const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//>//不修改成员变量数据的函数,最好都加上constbool operator>(const string& s) const{//strcmp用于比较两个字符串并根据比较结果返回整数//两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止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 || s == *this;//err,因为s是const对象,不能调用非const的成员函数,所以要改写成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 reserve(size_t n){if (n > _capacity)//防止缩容,导致程序异常{char* tmp = new char[n + 1];//开辟新空间,包含'\0'strcpy(tmp, _str);//将旧空间的内容拷贝到新空间delete[] _str;//释放旧空间_str = tmp;_capacity = n;//包含有效字节的空间,不包含'\0'}}//尾插一个字符void push_back(char ch){//if (_size + 1 > _capacity)//{//	reserve(_capacity * 2);//}//_str[_size] = ch;//++_size;要加'\0',否则会发生乱码//_str[_size] = '\0';insert(_size, ch);}//尾插一串字符void append(const char* str){//size_t len = strlen(str);//if (_size + len > _capacity)//{//	reserve(_size + len);//}//strcpy(_str + _size, str);strcat(_str, str);//_size += len;insert(_size, str);}//+=//追加单个字符string& operator+=(char ch){push_back(ch);return *this;}//+=//追加字符串string& operator+=(const char* str){append(str);return *this;}//分三种情况://n<_size//_size<n<_capacity//n>_capacityvoid resize(size_t n, char ch = '\0'){if (n < _size)//=时不作处理{//删除数据,保留前n个_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity){reserve(n);}size_t i = _size;while (i < n){_str[i] = ch;++i;}_size = n;_str[_size] = '\0';}}//插入字符string& insert(size_t pos, char ch){assert(pos <= _size);if (_size + 1 > _capacity){reserve(2 * _capacity);}//size_t end = _size;//size_t是无符号数,设pos=0,则当end=0时,--end会变成-1,而-1的无符号数为最大值,所有要改成有符号数,但依旧会报错,因为会发生隐式类型提升(end是有符号数,pos是无符号数),可以将pos改为有符号数//while (end >= pos)//end>=(int)pos//{//	_str[end + 1] = _str[end];//	--end;//}size_t end = _size + 1;while (end > pos){_str[end + 1] = _str[end];--end;}_str[pos] = ch;++_size;return *this;}//插入字符串string& 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;while (end > pos + len - 1)//当len=1时,相当于插入了一个字符{_str[end] = _str[end - len];--end;}//法二/*size_t end = _size;for (size_t i = 0; i < _size + 1; i++){_str[end + len] = _str[end];}*///拷贝插入strncpy(_str + pos, str, len);_size += len;return *this;}//删除字符string& erase(size_t pos, size_t len = npos){assert(pos < _size);//不给npos,则从pos位置开始将后面数据全部删完if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}//对象之间的数据交换void swap(string& s){//调用std库中的swapstd::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size);}//查找某个字符size_t find(char ch, size_t pos = 0){assert(pos < _size);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){assert(pos < _size);//strstr(str1,str2)用于判断字符串str2是否是str1的子串//如果是,则返回str1字符串从str2第一次出现的位置开始到str1结尾的字符串;否则,返回NULLchar* p = strstr(_str + pos, str);if (p == nullptr){return npos;}else{return p - _str;}}//清空字符串void clear(){_str[0] = '\0';_size = 0;}//判断字符串是否为空bool empty() const{return 0 == _size;}private:char* _str;size_t _size;size_t _capacity;//成员变量可以给缺省值,静态成员变量不能给缺省值//static size_t npos;//声明一static const size_t npos;//声明二//static const size_t npos = -1;//声明+定义,只针对整型/*static const size_t N = 10;int _a[N];*/};//size_t string::npos = -1;//定义一const size_t string::npos = -1;//定义二//流插入//实现为全局函数ostream& operator<<(ostream& out, const string& s){//out<<s.c_str()//err,因为这样遇到'\0'就会截止输出,从而不一定输出s.size()个字符//C的字符数组, 以\0为终止算长度,string不看\0, 以size为终止算长度for (auto ch : s){out << ch;}return out;}//流提取//实现为全局函数istream& operator>>(istream& in, string& s){//清理缓存s.clear();//读取数据//get()函数是cin输入流对象的成员函数,用来从指定的输入流中提取一个字符(包括空白字符),函数的返回值就是读入的字符char ch = in.get();char buff[128];//提前开辟128字节的空间,减少频繁扩容带来的损失size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[127] = '\0';//每满一次buff,则重新加一次s += buff;i = 0;}ch = in.get();}//若是有数据且未满127字符,则直接进行读入if (i != 0){buff[i] = '\0';s += buff;}return in;}void test_string1(){string s1;//加上std可以防止程序崩溃string s2("hello world");cout << s1.c_str() << endl;//可能会崩溃,cout会自动识别类型const char* ,同时会对字符串进行解引用并打印输出,而s1为空,对空指针进行解引用会导致程序崩溃,所以不要将_str初始化为nullptrcout << s2.c_str() << endl;s2[0]++;cout << s1.c_str() << endl;cout << s2.c_str() << endl;}void test_string2(){string s1;string s2("hello world");string s3(s2);//一个修改会影响另一个,同时会析构两次cout << s2.c_str() << endl;cout << s3.c_str() << endl;s2[0]++;cout << s2.c_str() << endl;cout << s3.c_str() << endl;s1 = s3;cout << s1.c_str() << endl;cout << s3.c_str() << endl;}//引用传参void Print(const string& s){//size()函数不加const修饰,会造成权限的放大,从const的s对象调用非const的size()for (size_t i = 0; i < s.size(); ++i){cout << s[i] << " ";}cout << endl;string::const_iterator it = s.begin();while (it != s.end()){//*it='x';++it;}cout << endl;//不支持范围for的原因:首先const对象不能调用非const函数,也不能调用非const迭代器对const对象进行修改,可以设置一个const迭代器for (auto ch : s){cout << ch << " ";}cout << endl;}void test_string3(){string s1("hello world");for (size_t i = 0; i < s1.size(); ++i){s1[i]++;}cout << endl;for (size_t i = 0; i < s1.size(); ++i){cout << s1[i] << " ";}cout << endl;Print(s1);//迭代器string::iterator it = s1.begin();while (it != s1.end()){++it;//指针可以修改,内容不可以修改}cout << endl;it = s1.begin();while (it != s1.end())//左闭右开{cout << *it << " ";++it;}cout << endl;//范围for:底层调用的是迭代器for (auto ch : s1){cout << ch << " ";}cout << endl;}void test_string4(){string s1("hello world");string s2("hello world");string s3("xx");cout << (s1 < s2) << endl;cout << (s1 == s2) << endl;cout << (s1 >= s3) << endl;}void test_string5(){string s1("hello world");//s1.push_back(' ');//s1.append("xxxxxxxxxx");s1 += ' ';s1 += "xxxxxxxxxxxxxxx";cout << s1.c_str() << endl;string s2;//s2起始为空时,扩容会失败,导致无法插入数据s2 += 'a';//s2 += 'b';//s2 += 'c';cout << s2.c_str() << endl;s1.insert(5, 'x');cout << s1.c_str() << endl;s1.insert(0, 'x');//导致异常cout << s1.c_str() << endl;}void test_string6(){string s1("hello world1111111111");cout << s1.capacity() << endl;s1.reserve(10);//不会缩容cout << s1.capacity() << endl;}void test_string7(){string s1;s1.resize(20, 'x');cout << s1.c_str() << endl;s1.resize(30, 'y');cout << s1.c_str() << endl;s1.resize(10);cout << s1.c_str() << endl;}void test_string8(){string s1("11111111");s1.insert(0, 'x');cout << s1.c_str() << endl;s1.insert(3, 'x');cout << s1.c_str() << endl;s1.insert(3, "yyy");cout << s1.c_str() << endl;s1.insert(0, "yyy");cout << s1.c_str() << endl;}void test_string9(){string s1("0123456789");cout << s1.c_str() << endl;s1.erase(4, 3);cout << s1.c_str() << endl;s1.erase(4,30);cout << s1.c_str() << endl;s1.erase(2);cout << s1.c_str() << endl;}//流插入重载必须实现为友元函数?不是void test_string10(){std::string s1("0123456789");s1 += '\0';s1 += "xxxxxxxx";cout << s1 << endl;//遇到\0不终止cout << s1.c_str() << endl;//遇到\0则终止string s2;cin >> s2;cout << s2 << endl;cin >> s1;cout << s1 << endl;}
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;#include"string.h"int main()
{/*try{bit::test_string10();}catch (const exception& e){cout << e.what() << endl;}*/bit::string s1;bit::string s2;cout << sizeof(s1) << endl;//12cout << sizeof(s2) << endl;//12//字符串是存放在堆上的,不占用对象的空间bit::string s3("11111");bit::string s4("1111111");cout << sizeof(s3) << endl;//12cout << sizeof(s4) << endl;//12return 0;
}

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

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

相关文章

大型语言模型中的幻觉研究综述:原理、分类、挑战和未决问题11.15+11.16+11.17

大型语言模型中的幻觉研究综述&#xff1a;原理、分类、挑战和未决问题11.15 摘要1 引言2 定义2.1 LLM2.3 大语言模型中的幻觉 3 幻觉的原因3.1 数据的幻觉3.1.1 有缺陷的数据源3.1.2 较差的数据利用率3.1.3 摘要 3.2 来自训练的幻觉3.2.1训练前的幻觉3.2.2来自对齐的幻觉3.2.3…

易点易动固定资产管理系统场景应用一:集成ERP/财务系统

在企业的日常运营中&#xff0c;固定资产管理是一个重要而繁琐的任务。传统的手工管理方式往往效率低下且容易出错&#xff0c;给企业带来不必要的成本和风险。为了解决这一问题&#xff0c;易点易动固定资产管理系统应运而生。本文将重点介绍易点易动固定资产管理系统在集成ER…

清理mac苹果电脑磁盘软件有哪些免费实用的?

苹果电脑是一款非常流行的操作系统设备&#xff0c;其稳定性和性能一直备受用户的喜爱。然而&#xff0c;随着时间的推移&#xff0c;我们使用电脑的过程中可能会发现磁盘上存储的数据越来越多&#xff0c;这不仅占用了宝贵的硬盘空间&#xff0c;还可能导致电脑运行变慢。因此…

微服务实战系列之Token

前言 什么是“Token”&#xff1f; 它是服务端生成的一串字符串&#xff0c;以作客户端进行请求的一个令牌&#xff0c;当第一次登录后&#xff0c;服务器生成一个Token便返回给客户端&#xff1b;以后客户端只携带此Token请求数据即可。 简言之&#xff0c;Token其实就是用户身…

数据结构与算法之美学习笔记:20 | 散列表(下):为什么散列表和链表经常会一起使用?

目录 前言LRU 缓存淘汰算法Redis 有序集合Java LinkedHashMap解答开篇 & 内容小结 前言 本节课程思维导图&#xff1a; 今天&#xff0c;我们就来看看&#xff0c;在这几个问题中&#xff0c;散列表和链表都是如何组合起来使用的&#xff0c;以及为什么散列表和链表会经常…

3ds Max渲染用专业显卡还是游戏显卡?

使用3dsmax建模时&#xff0c;会面临诸多选择&#xff0c;除了用vr还是cr的决策&#xff0c;硬件选择上也存在着疑问&#xff0c;比如用专业显卡还是消费级游戏显卡&#xff1f;一般来说&#xff0c;除非是特别专业的大型项目和软件&#xff0c;且预算在5位数以上&#xff0c;常…

Android Glide加载transform CenterCrop, CircleCrop ShapeableImageView圆形图并描边,Kotlin

Android Glide加载transform CenterCrop, CircleCrop ShapeableImageView圆形图并描边&#xff0c;Kotlin import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.…

代码随想录算法训练营第二十五天| 216 组合总合 ||| 17 电话号码的字母组合

216 组合总和 ||| 暴力 class Solution {List<List<Integer>>res new ArrayList<>();List<Integer>newList new ArrayList<>();public List<List<Integer>> combinationSum3(int k, int n) {soluHelper(1,k,n,0);return res;}pr…

qt笔记之qml和C++的交互系列(一):初记

code review! —— 杭州 2023-11-16 夜 文章目录 一.qt笔记之qml和C的交互&#xff1a;官方文档阅读理解0.《Overview - QML and C Integration》中给出五种QML与C集成的方法1.Q_PROPERTY&#xff1a;将C类的成员变量暴露给QML2.Q_INVOKABLE()或public slots&#xff1a;将C类…

2024年山东省职业院校技能大赛中职组“网络安全”赛项竞赛试题-B

2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-B 一、竞赛时间 总计&#xff1a;360分钟 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A、B模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略设置 A-3 流量完整性保护 A-4 …

探索arkui(2)--- 布局(列表)--- 2(支持分组/实现响应滚动位置)

前端开发布局是指前端开发人员宣布他们开发的新网站或应用程序正式上线的活动。在前端开发布局中&#xff0c;开发人员通常会展示新网站或应用程序的设计、功能和用户体验&#xff0c;并向公众宣传新产品的特点和优势。前端开发布局通常是前端开发领域的重要事件&#xff0c;吸…

MongoDB分片集群搭建

----前言 mongodb分片 一般用得比较少&#xff0c;需要较多的服务器&#xff0c;还有三种的角色 一般把mongodb的副本集应用得好就足够用了&#xff0c;可搭建多套mongodb复本集 mongodb分片技术 mongodb副本集可以解决数据备份、读性能的问题&#xff0c;但由于mongodb副本集是…

创作者焦点:Temple of Dum-Dum(试炼 3)

《Bomkus 博士的试炼》创作的幕后花絮。 《创作者焦点》系列共分为六部分&#xff0c;重点介绍《Bomkus 博士的试炼》的游戏创作过程及其独特的游戏功能。 Temple of Dum-Dum&#xff1a; Temple of Dum-Dum 是 Bomkus 博士试炼中的第三个挑战&#xff0c;该试炼由六项体验组成…

SecureCRT 9.4.2 for Mac

SecureCRT是一款由VanDyke Software公司开发的终端仿真软件&#xff0c;它提供了类似于Telnet和SSH等协议的远程访问功能。SecureCRT专门为网络管理员、系统管理员和其他需要保密访问网络设备的用户设计。 SecureCRT具有以下特点&#xff1a; 安全性&#xff1a;SecureCRT支持…

windows服务器热备、负载均衡配置

安装网络负载平衡 需要加入的服务器上全部需要安装网络负载平衡管理器 图形化安装&#xff1a;使用服务器管理器安装 在服务器管理器中&#xff0c;使用“添加角色和功能”向导添加网络负载均衡功能。 完成向导后&#xff0c;将安装 NLB&#xff0c;并且不需要重启计算机。 …

337. 打家劫舍 III

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一个“父“房子与之相连。一番侦察之后&#xff0c;聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连…

MATLAB中zticks函数用法

目录 语法 说明 示例 指定 z 轴刻度值和标签 指定非均匀 z 轴刻度值 以 2 为增量递增 z 轴刻度值 将 z 轴刻度值设置回默认值 指定特定坐标区的 z 轴刻度值 删除 z 轴刻度线 zticks函数的功能是设置或查询 z 轴刻度值。 语法 zticks(ticks) zt zticks zticks(auto)…

ReportLab创建合同PDF

一、前言 有一个项目需要将电子签名后的报价合同和生成的发票发送给客户&#xff0c;这种发送给客户的文件一般都是使用PDF格式&#xff0c;主要是因为PDF特别适合阅读且不同平台打开文件格式不会变形&#xff0c;不过要在程序中生成PDF还是比较麻烦的&#xff0c;我们的发票是…

TCP与UDP协议

TCP与UDP协议 1、TCP协议&#xff1a; 1、TCP特性&#xff1a; TCP 提供一种面向连接的、可靠的字节流服务。在一个 TCP 连接中&#xff0c;仅有两方进行彼此通信。广播和多播不能用于 TCP。TCP 使用校验和&#xff0c;确认和重传机制来保证可靠传输。TCP 给数据分节进行排序…

AdaBoost 算法:理解、实现和掌握 AdaBoost

一、介绍 Boosting 是一种集成建模技术&#xff0c;由 Freund 和 Schapire 于 1997 年首次提出。从那时起&#xff0c;Boosting 就成为解决二元分类问题的流行技术。这些算法通过将大量弱学习器转换为强学习器来提高预测能力 。 Boosting 算法背后的原理是&#xff0c;我们首先…