9. string 类和标准模板库

9.1 string 类

9.1.1 string 类的构造函数

构造函数 描述
string(const char* s) 将string对象初始化为S指向的NBTS(null-terminated-string)
string(size_type n, char c) 创建一个包含n个元素的string对象,其中每个元素都被初始化为字符c
string(const string & str) 将一个string对象初始化为string对象str (复制构造函数)
string( ) 创建一个默认的sting对象,长度为0 (默认构造函数)
string(const char *s, size_ type n) 将string对象初始化为s指向的NBTS的前n个字符,**即使超过了NBTS结尾 **
template\<class lter\><br>string(lter begin, Iter end) 将string对象初始化为区间[begin, end)内的字符,其中begin和end的行为就像指针,用于指定位置
string(const string & str, size_type pos = 0, size_type n = npos) 将一个sring对象初始化为对象str中从位置pos开始到结尾的字符,或从位置pos 开始的n个字符
string(string && str) noexcept 这是C++11新增的,它将一个 string对象初始化为string对象str,并可能修改str (移动构造函数)
string(initializer_list<char> il) 这是C++11新增的,它将一个string对象初始化为初始化列表il中的字符

9.1.2 string 类输入

9.1.2.1 string 类输入方法

对于 C-style 字符串,有三种输入方式:

char info[100];
cin >> info;               // 默认读取一个单词
cin.getline(info, 100);    // 默认读取一行,并用 \0 代替 \n
cin.get(info, 100)         // 读取一行,不过将 \n 留在

对于string对象,有两种输入方法:

string stuff;
cin >> string;          // 默认读取一个字符
getline(cin, stuff);    // 默认读取一行,但 \n 会被抛弃

两个版本的getline()都有一个可选参数,用来指定终止字符(默认为\n):

cin.getline(info, 100, '.');
getline(cin, stuff, '.');

两个版本的getline()有两个值得注意的区别

  • 在功能上,它们之间的主要区别在于,string版本的函数会自动调整对象大小,使之刚好能存储输入的字符串。
  • 在设计方面的一个区别是,读取C-style字符串的函数是istream类的成员方法,而string版本的是独立的函数。
    对于C-style字符串,**cin是调用对象;而对于string对象,cin是一个函数参数**。这一点对于operator>>同样适用。

9.1.2.2 string 类输入详解

string输入函数虽然可以自动调整string对象的大小,但也存在一些限制

  1. string对象存在最大允许长度,由string::npos指定,通常为最大的unsigned int值。
  2. 程序可以使用的内存量也是一个限制因素。

stringgetline()函数从输入中读取字符,并将储存到string对象中,直到发生以下三种情况之一:

  1. 到达文件尾(在命令行中,文件尾可以通过特殊的组合键,如Ctrl+c,实现),eofbit被设置,方法fail()eof()都将返回true
  2. 遇到终止字符,将会从输入流中删除终止字符,但不会存储它。
  3. 读取的字符数达到最大限制(string::npos和程序可以使用的内存量之中较小的一个),将设置输入流的failbit,方法fail()会返回true

stringoperator>>()函数的行为与此类似,只是第二种情况变为不断读取直到遇到空白字符并将其留在输入流(空格、换行符、制表符等使isspace()函数返回true的字符)。

9.1.2.3 使用 string 类

  1. 比较字符串:string类对6个逻辑字符串都进行了重载。在比较时,将ASCII码小的字符视为小的字符。
  2. 确定字符串的长度:size()length()都可以用来确定字符串中的字符数量。
  3. 搜索子字符(串):
方法原型 描述
size_type find(const basic_string &str, size_type pos = 0) const noexcept; 从字符串的pos位置开始,查找子字符串str。如果查到,返回子字符串首次时其首字符的索引;否则,返回string::npos
size_type find(const CharT *s, size_type pos = 0) const; 从字符串的pos位置开始,查找子字符串s(以\n为结尾)。如果查到,返回子字符串首次时其首字符的索引;否则,返回string::npos
size_type find(const CharT* s, size_type pos, size_type count) const; 从字符串的pos位置开始,查找子字符串s的前n个字符组成的子字符串。如果查到,返回子字符串首次时其首字符的索引;否则,返回string::npos
size_type find(CharT ch, size_type pos = 0 ) const noexcept; 从字符串的pos位置开始,查找子字符ch。如果查到,返回子字符首次时的索引;否则,返回string::npos

注意noexcept是在C++11之后才有的。

string库还提供了相关的方法,它们的重载方式与find()相同。

  1. refind()查找源字符串中目标字符串最后一次出现的位置。
  2. find_first_of()查找源字符串中目标字符串中任何一个字符首次出现的位置。
  3. find_last_of()查找源字符串中目标字符串中任何一个字符最后一次出现的位置。
  4. find_first_not_of()查找源字符串中首次出现不属于目标字符串的任意字符的位置。
  5. find_last_not_of()查找源字符串中最后一次出现不属于目标字符串的任意字符的位置。

另外,string对象还重载了operator=operator+operator+=等运算符。

9.1.2.4 string 类的其他功能

  • 删除一个字符串的部分或全部内容;
  • 将一个字符串的部分或全部内容替换为另一个字符串的部分或全部内容;
  • 将一个字符串的部分或全部内容插入到另一个字符串中;
  • 将一个字符串的部分或全部内容与另一个字符串的部分或全部内容进行比较;
  • 从一个字符串中提取子字符串;
  • 交换两个字符串的部分或全部内容。

自动调节大小的功能:

改变字符串内容一般都会涉及到字符串长度的改变,这时不能简单地将已有的字符串加大,因为旁边的内存可能已经被占用了。因此通常的做法是为string重新分配一块地址,并释放原来的地址。
如果一个string对象中储存的字符串的长度会频繁地变化,会造成很多不必要的开销。这时,可以用额外的内存占用来换取这些开销的减少——使用reserve()方法改变string类的大小(但是不能低于默认的最小大小,也不能低于size()length())。

string str1 = "aloha0123456789";    // 5个字符
string str2 = "aloha01234567890";   // 16个字符
string str3 = "Please remember, Still water runs deep, my brother.中文";    // “中文”被视为六个字符,其实只有53个
cout << "size(): capacity()\n";
cout << str1.size() << ": " << str1.capacity() << endl;
cout << str2.size() << ": " << str2.capacity() << endl;
cout << str3.size() << ": " << str3.capacity() << endl;
str2.reserve(10);
cout << "after reserve(10):\n";
cout << str2 << ": " << str2.capacity() << endl;
str2.reserve(15);
cout << "after reserve(15):\n";
cout << str2 << ": " << str2.capacity() << endl;
str2.reserve(70);
cout << "after reserve(70):\n";
cout << str2 << ": " << str2.capacity() << endl;
size(): capacity()
15: 15
16: 16
57: 57
after reserve(10):
aloha01234567890: 16
after reserve(15):
aloha01234567890: 16
after reserve(70):
aloha01234567890: 70

9.2 智能指针模板类

在异常处理中,可能还没释放动态分配的内存程序就抛出异常。这样会造成内存泄漏。
要解决这个问题,可以使用C++中的智能指针代替普通指针。与普通指针相比,智能指针是一个类,在栈解退过程中会自动调用其构造函数,并在构造函数中释放指针所指的内存。

C++中提供了三个智能指针模板:auto_ptrunique_ptrshared_ptrauto_ptr是C++98提供的解决方案,C++11中以将其摒弃(但是一般会向后兼容)。另外,还有一个特殊的智能指针weak_ptr,需要配合shared_ptr使用。

9.2.1 使用智能指针

要使用智能指针,必须要包含头文件memory。使用语法:

auto_ptr<Data_Type> ap(new Data_type);
unique_ptr<Data_Type> up(new Data_type);
shared_ptr<Data_Type> sp(new Data_type);
auto_ptr<string> ps1(new string("Still water runs deep."));
cout << *ps1 << endl;
auto_ptr<string> ps2 = ps1;
cout << *ps2 << endl;

为避免重复释放同一块内存,auto_ptr采用ownership的方法(可能定义了一个标志所有者的bool变量):当将一个auto_ptr的值传递(赋值运算符或复制构造函数)给另一个auto_ptr时,将转交所有权,前者将不再引用此对象。
这样虽然确实保证了对象所在内存不会被重复释放,但是可能会出于各种意外而使用原来的auto_ptr,这显然是一个风险。

使用shared_ptr可以避免这种风险。shared_ptr类中有一个counter,每次调用析构函数时将counter减 1 ,并确保只有当counter为 1(初始值)时,其析构函数才会释放内存。

如果使用unique_ptr,程序不会等到运行时才报错,在编译时编译器就会报错。这是因为unique_ptr禁止使用一个可能会长期存在的左值进行初始化。但是神奇的是,unique_ptr允许使用一个临时的右值进行初始化,并且能正确地运行(unique_ptr使用了移动构造函数和右值引用):

auto_ptr<string> ps1(new string("Still water runs deep."));
unique_ptr<string> ps2;
ps2 = ps1;                    // error
unique_ptr<string> ps3;
ps3 = unique_ptr<string>(new string("aloha!"));

另外,如果要使用new[],就一定要使用unique_ptr,因为只有uinque_ptr有使用new[]delete[]的版本。使用示例:

unique_ptr<string[]> pad(new string[5]);

9.2 模板类 vector

在头文件vector中定义了vector类。可以像使用数组一样使用vector(虽然vector的用法要更多)。
要创建vector,可以使用通常的<type>语法来指定要存储的数据的类型,并通过构造函数指定数据的数量。

#include <vector>
// ...
vector<int> arri(5);

9.2.1 使用 vector 类

因此可以用索引语法访问vector对象中的数据。

可对vector进行的操作:

  1. 所有容器都有的方法:
    • size():返回容器中的元素个数。
    • swap():交换两个容器的内容。
    • begin():返回指向容器中的一个元素的迭代器。
    • end():返回指向一个超出容器尾的迭代器。
  2. vector及其他部分容器具有的方法:
    • push_back():在容器结尾添加元素。
    • erase():删除指定“[)”区间的元素。
    • insert():在指定位置前插入一组数据。第一个参数为迭代器,指定插入位置。插入的数据可以用两个迭代器确定,也可以使用新增的初始化列表语法。

STL通过为每种容器都定义一个迭代器,给所有的容器都提供了统一的接口。
可以像下面这样使用迭代器(其实STL为下面的操作提供了更简便的方法):

vector<int> nums(5);
nums  = {1, 2, 3, 4, 5};
vector<int>::iterator pd = nums.begin();
for(pd; pd != nums.end(); ++pd)
    cout << *pd << '\t';

9.2.2 可以对 vector 进行的其他操作

  1. foreach(),前两个迭代器参数指定要进行操作的区间,第三个函数指针参数指定要进行的操作。

    vector<Review>::iterator pr;
    for (pr = books.begin(); pr != books.end(); pr++)
    	ShowReview(*pr);
    
    // 等价于以下代码
    for_each(books.begin(), books.end(), ShowReview);
    
  2. Random_shuffle(),接受两个指定区间的迭代器参数,并随机排列该区间中的元素。这个函数要求容器支持随机访问。

  3. sort():该函数也要求容器支持随机访问。该函数有两个版本:

    • 接受两个指定区间的迭代器,使用定义在容器内的<运算符对容器内的元素进行排序。

    • 接受两个指定区间的迭代器,并接受一个指定操作的函数指针。

      bool WorseThan(const Review & r1, const Review & r2)
      {
          if(r1.title < r2.title)
              return true;
          else if(r1.title == r2.title && r1.rating < r2.rating)
          	return true;
          else
              return false;
      }
      sort(books.begin(), books.end(), WorseThan);
      

猜你喜欢

转载自www.cnblogs.com/alohana/p/12571711.html