《C++ Primer Plus 第6版》第16章 string类和标准模板库 学习笔记——std::string类

主要内容:

  • 标准C++ string类
  • 模板auto_ptr、unique_ptr和shared_ptr
  • 标准模板库(STL)
  • 容器类
  • 迭代器
  • 函数对象(functor)
  • STL算法
  • 模板initializer_list(C++11)

1. string类

string类由头文件string支持,string类包含大量的方法,包括若干构造函数、用于将字符串赋给变量、合并字符串、比较字符串、访问各个元素的重载运算符、用于在字符串中查找字符和子字符串的工具。

1.1 构造字符串

string的构造函数:string有7个构造函数,C++11新增2个构造函数。

使用构造函数时进行了简化:

(1)string实际上是模板具体化basic_string<char>的一个typedef;

(2)省略了与内存管理相关的参数

size_type是一个依赖于实现的整型,是在头文件string中定义的。

string类将string::npos定义为字符串的最大长度,通常为unsigned int的最大值。

下表中使用NBTS(null-terminated string)来表示以空字符结束的字符串——传统的C字符串。

表1 string类的构造函数
构造函数描述
string(const char* s)

将string对象初始化为s指向的NBTS

string(size_type n, char c)创建一个包含n个元素的string对象,其中每个元素都被初始化为字符c
string(const string& str)

将一个string对象初始化为string对象str(复制构造函数)

string()创建一个默认的string对象,长度为0(默认构造函数)
string(const char* s, size_type n)将string对象初始化为s指向的NBTS的前n个字符,即使超过了NBTS结尾

template<class Iter>

string(Iter begin, Iter end)

将string对象初始化为区间[begin, end)内的字符,其中begin和end的行为就像指针,用于指定位置,范围包括begin在内,但不包括end
string(const string& str, string size_type pos = 0, size_type n = npos)将一个string对象初始化为对象str中从位置pos开始到结尾的字符,或从位置pos开始的n个字符
string(string&& str) noexceptC++11新增,将一个string对象初始化为string对象str,并可能修改str(移动构造函数)
string(initializer_list<char> il)

C++11新增,将一个string对象初始化为初始化列表il中的字符

// 程序清单16.1  str1.cpp
// str1.cpp -- introducing the string class
#include <iostream>
#include <string>
// using string constructors
int main()
{
    using namespace std;
    string one("Lottery Winner!");     // ctor #1
    cout << one << endl;               // overloaded <<
    string two(20, '$');               // ctor #2
    cout << two << endl;
    string three(one);                 // ctor #3
    cout << three << endl;
    one += " Oops!";                   // overloaded +=
    cout << one << endl;
    two = "Sorry! That was";
    three[0] = 'P';
    string four;                       // ctor #4
    four = two + three;                // overloaded +, =
    cout << four << endl;
    char alls[] = "All's well that ends well";
    string five(alls, 20);             // ctor #5
    cout << five << "!\n";
    string six(alls+6, alls+10);       // ctor #6
    cout << six << ", ";
    string seven(&five[6], &five[10]); // ctor #6 again
    cout << seven << "...\n";
    string eight(four, 7, 16);         // ctor #7
    cout << eight << " in motion!" << endl;
    return 0;
}

1.2 string类输入

对于类,很有帮助的一点是,知道有哪些输入方式可用。

对于C-风格字符串,有3种方式:

char info[100];
cin >> info;
cin.getline(info, 100);        //read a line, 丢弃\n
cin.get(info, 100);            //read a line, leave \n in queue

对于string对象,有2种方式:

string stuff;
cin >> stuff;
getline(cin, stuff);           //read a line, 丢弃\n

两个版本的getline()都有一个可选参数,用于指定使用哪个字符来确定输入的边界:

cin.getline(info, 100, ':');   //read up to :, 丢弃:
getline(cin, stuff, ':');      //read up to :, 丢弃:

string版本的getline()将自动调整目标string对象的大小,使之刚好能够存储输入的字符:

string版本的getline()函数从输入中读取字符,并将其存储到目标string中,直到发生下列三种情况之一:

  • 到达文件尾,此时输入流的eofbit将被设置,这意味着方法fail()和eof()都将返回true;
  • 遇到分界字符(默认为\n),此时将把分界字符从输入流中删除,但不存储它;
  • 读取的字符数达到最大允许值(string::npos和可供分配的内存字节数中较小的一个),此时将设置输入流的failbit,这意味着方法fail()将返回true。

string版本的operator>>()函数的行为与此类似,只是它不断读取,直到遇到空白字符并将其留在输入队列中,而不是不断读取,直到遇到分界字符并将其丢弃。空白字符指空格、换行符和制表符。

// strfile.cpp -- read strings from a file
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>

int main()
{
    using namespace std;
    ifstream fin;
    fin.open("tobuy.txt");
    if(fin.is_open() == false)
    {
        cerr << "Can;t open file. Bye.\n";
        exit(EXIT_FAILURE);
    }
    string item;
    int count = 0;
    getline(fin, item, ':');
    while(fin)       // while input is good
    {
        ++count;
        cout << count << ": " << item << endl;
        getline(fin, item, ':');
    }
    cout << "Done\n";
    fin.close();
    return 0;
}

注意:通常,对于程序要查找的文本文件,应将其放在可执行程序或项目文件所在的目录中,否则必须提供完整的路径名。在Windows系统中,C-风格字符串中的转义序列\\表示一个斜杠:

fin.open("C:\\CPP\\Progs\\tobuy.txt");   //file = C:\CPP\Progs\tobuy.txt

注意:将 : 指定为分界字符后,换行符将被视为常规字符。

下面是文件tobuy.txt的内容:

sardines:chocolate ice cream:pop corn:leeks:

cottage cheese:olive oil:butter:tofu:

下面是程序的输出:

1: sardines

2: chocolate ice cream

3: pop corn

4: leeks

5: 

cottage cheese

6: olive oil

7: butter

8: tofu

9: 

Done

1.3 使用字符串

  • 可以使用不同方式来创建string对象、显示string对象的内容、将数据读取和附加到string对象中、给string对象赋值、将两个string对象连结起来;
  • 可以比较字符串;
  • 可以确定字符串的长度。size()和length()成员函数都返回字符串中的字符数(length()成员来自较早版本的string类,size()则是为提供STL兼容性而添加的);
  • 可以以多种不同的方式在字符串中搜索给定的子字符串或字符,表16.2描述了find()方法的4个重载版本(string::npos是字符串可存储的最大字符数,通常是无符号int或无符号long的最大取值);
  • string库还提供了相关的方法:rfind()、find_first_of()、find_last_of()、find_first_not_of()和find_last_not_of(),它们的重载特征标都与find()方法相同。

       rfind()方法查找子字符串或字符最后一次出现的位置;

       find_first_of()方法在字符串中查找参数中任何一个字符首次出现的位置;

       find_last_of()方法功能与此相同,只是它查找最后一次出现的位置;

       find_first_not_of()方法在字符串中查找第一个不包含在参数中的字符;

表16.2 重载的find()方法
方法原型描述

size_type find(const string& str,

size_type pos = 0) const

从字符串的pos位置开始,查找子字符串str。如果找到,则返回该子字符串首次出现时其首字母的索引;否则,返回string::npos

size_type find(const char* s,

size_type pos = 0)const

从字符串的pos位置开始,查找子字符串s。如果找到,则返回该子字符串首次出现时其首字母的索引;否则,返回string::npos

size_type find(const char* s,

size_type pos = 0, size_type n)

从字符串的pos位置开始,查找s的前n个字符组成的子字符串。如果找到,则返回该子字符串首次出现时其首字母的索引;否则,返回string::npos

size_type find(char ch,

size_type pos = 0)const

从字符串的pos位置开始,查找字符ch。如果找到,则返回该字符首次出现的位置;否则,返回string::npos

程序清单16.3建立了一个非图形版本的Hangman拼字游戏。

// 程序清单16.3 -- hangman.cpp
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <cctype>
using std::string;
const int NUM = 26;
const string wordlist[NUM] = {"apiary", "beetle", "cereal", "danger", "ensign", "florid"};  //还有好多次懒得写了,注意需要26个单词

int main()
{
    using std::cout;
    using std::cin;
    using std::tolower;
    using std::endl;
    std::srand(std::time(0));
    char play;
    cout << "Will you play a word game? <y/n> ";
    cin >> play;
    play = tolower(play);
    while(play == 'y')
    {
        string target = wordlist[std::rand() % NUM];
        int length = target.length();
        string attempt(length, '-');      //记录玩家猜对的字母
        string badchars;
        int guesses = 6;        //玩家有6次机会
        cout << "Guess my secret word. It has " << length << " letters, and you guess\n"
             << "one letter at a time. You get  " << guesses << " wrong guesses.\n";
        cout << "Your word: " << atempt << endl;
        
        while(guesses > 0 && attempt != target)
        {
            char letter;
            cout << "Guess a letter: ";
            cin >> letter;
            if(badchars.find(letter) != string::npos || attempt.find(letter) != string::npos)
            {
                cout << "You already guessed that. Try again.\n";
                continue;
            }
            int loc = target.find(letter);
            if(loc == string::npos)
            {
                cout << "Oh, bad guess!\n";
                --guesses;
                badchars += letter;    //add to string
            }
            else
            {
                cout << "Good guess!\n";
                attempt[loc] = letter;
                // check if letter appears again
                loc = target.find(letter, loc+1);
                while(loc != string::npos)
                {
                    attempt[loc] = letter;
                    loc = target.find(letter, loc+1);
                }
            }
            cout << "Your word: " << attempt << endl;
            if(attempt != target)
            {
                if(badchars.length() > 0)
                {
                    cout << "Bad choices: " << badchars << endl;
                }
                cout << guesses << " bad guesses left\n";
            }
        }
        if(guesses > 0)
        {
            cout << "That's right!\n";
        }
        else
        {
            cout << "Sorry, the word is " << target << ".\n";
            cout << "Will you play another? <y/n>";
            cin >> play;
            play = tolower(play);
        }
    }
    cout << "Bye\n";
    return 0;
}

1.4 string还提供了哪些功能

(1)自动调整大小功能

每当程序将一个字母附加到字符串末尾时,不能仅仅将已有的字符串加大,因为相邻的内存可能被占用了。因此,可能需要分配一个新的内存块,这样效率低。因此很多C++实现分配一个比实际字符串大的内存块,为字符串提供了增大空间。如果字符串不断增大,超过了内存块的大小,则程序将分配一个大小为原来两倍的新内存块,以提供足够的增大空间。

方法capacity()返回当前分配给字符串的内存块的大小,reserve()方法让您能够请求内存块的最小长度。

//程序清单16.4 str2.cpp
// str2.cpp -- capacity() and reserve()
#include <iostream>
#include <string>
int main()
{
    using namespace std;
    string empty;
    string small = "bit";
    string larger = "Elephants are a girl's best friend";
    cout << "Sizes:\n";
    cout << "\tempty: " << empty.size() << endl;
    cout << "\tsmall: " << small.size() << endl;
    cout << "\tlarger: " << larger.size() << endl;
    cout << "Capacities:\n";
    cout << "\tempty: " << empty.capacity() << endl;
    cout << "\tsmall: " << small.capacity() << endl;
    cout << "\tlarger: " << larger.capacity() << endl;    
    empty.reserve(50);
    cout << "Capacity after empty reserve(50): " << empty.capacity() << endl;
    return 0;
}

下面是使用某种C++实现时,16.4程序的输出:

Size:

                empty: 0

                small: 3

                larger: 34

Capacirties:

                empty: 15

                small: 15

                larger:47

Capacity after empty reserve(50): 63

注意,该实现使用的最小容量为15个字符,这比标准容量选择(16的倍数)小1。

由string对象转换为C-风格字符串:使用c_str()方法。   fout.open(filename.c_str());

1.5 字符串种类

本节将string类看作是基于char类型的,string库实际上是基于一个模板类:

template<class charT, class traits = char_traits<charT>, class Allocator = Allocator<charT> >
basic_string {
    // ...
}

模板basic_string有4个具体化,每个具体化都有一个typedef名称:

typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
typedef basic_string<char16_t> u16string;   //C++11
typedef basic_string<char32_t> u32string;   //C++11

traits类描述关于选定字符类型的特定情况,如如何对值进行比较。对于wchar_t、char16_t、char32_t和char类型,有预定义的char_traits模板具体化,它们都是traits的默认值。

Allocator是一个管理内存分配的类。对于各种字符类型,都有预定义的allocator模板具体化,它们都是默认的。它们使用new和delete。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值