C++Primer 3.1: String

  1. String
    1. 1. Namespace using Declarations
    2. 2. Library string Type
      1. string Definition and Initializing
      2. string Operations
      3. string IO
      4. string::size_type Type
      5. string Comparison
      6. string Addition

String

在C++中,除了内置的基本类型,还有很多库定义了抽象数据类型。其中最重要之一就是string,支持可变长度字符串(variable-length character strings)。

1. Namespace using Declarations

scope operator(::) tells compiler to look in the scoop of the left-hand operand for the name of the right-hand operand.

范围操作符(::)会告诉compiler去哪个namespace寻找变量的定义。

我们可以在文件头使用using关键字来声明我们想要用的namespace members,这样在后续的代码中都不需要在指定cin或者cout是来自std namespace

using std::cin;
using std::cout;

也可以直接在程序中通过::来指定namespace:

std::cout<<"Hello world"<<std::endl;

注意通常我们不能在头文件中使用using关键字来指明想要使用的namespace,因为头文件会被展开到包含它的源文件,这样每个包含它的源文件就会使用到这个namespace里面所定义的operation,这是不对的。

Tips:

不要在头文件(.h)文件中使用using来指定namespace

2. Library string Type

A string is a variable-length(可变长度)sequence of characters.

要使用stringtype,我们必须包含string头文件。且因为string是库的一部分,被定义在stdnamespace中,所以在我们代码中如果想要使用string,需要如下代码:

#include <string>
using std::string;

string Definition and Initializing

string的初始化可以分为两种:

  • copy initialize:通过赋值操作符=right-hand initializer copy 到被创建的left-hand variable。新创建的object是initializer的一份copy。复制初始化总是调用复制构造函数。
  • direct initialize:不通过赋值操作符=,而是通过调用与实参相匹配的构造函数。

关于copy initializedirect initialize的具体区别,可以查看here。因为其中涉及到构造函数等知识,会在后面详细提到。string具体的定义与初始化例子如下:

string s1; // default initialization; s1 is an empty string
string s2 = s1; // copy initialize
string s2(s1); // direct initialize
string s3 = "hello world"; // copy initialize; s3 is a copy of the literal
string s3("hello world"); // direct initialize; s3 is a copy of the literal
string s4(10, 'c'); // direct initialize; s4 has 10 copies of the character 'c'

注意s4的这种初始化方法只能通过direct initialize实现。即最终得到s4cccccccccc

string Operations

常见的string操作总结如下表:

Operations Meaning
os << s Writes s onto output string os. Return os.
is >> s Reads whitespace-separated string from is to s. Return is.
getline(is, s) Reads a line of input from is to s. Return is.
s.empty() Returns true if s is empty; otherwise return false.
s.size() Returns the number of characters in s. Compatible with STL container.
s.length() Equivalent to s.size(). Compatible with C style.
s[n] Returns a reference to the char at position of n in s. **Position starts at 0.**
s1 + s2 Returns a string that is the concatenation of s1 and s2.
s1 == s2 s1 and s2 are equal if they contain the same characters. Case-sensitive.
<,<=,>=,> Comparisions are case-sensitive and use dictionary ordering.
s.substr(start, length) Returns a substring in s, from start to start + length.

上述常见操作需要注意的几点:

  • cin>>s读取stdin输入是whitespace-separated,即碰到空白字符就会结束,如果想要读取一整个句子(即忽略空白符),要使用getline()
  • s.size()s.length()都是得到s的字符个数,只不过size()是为了兼容STL容器习惯,length()是为了兼容C的strlen()习惯。
  • 两个字符串相加相当于合并两个字符串,string并没有重载-操作。想要得到一个string的子串可以使用substr()

string IO

在OJ模式的编程题中(例如牛客网),我们需要自己处理输入输出,所以在此说一说string如何处理输入输出:

string s;
while(cin>>s)     // read until end-of-file, whitespace is separator
  cout<<s<<endl;

while(getline(cin, s))    
  // read input a line at a time until end-of-file, ignore whitespace
  cout<<s<<endl;

注意这里需要说明一下为什么可以通过cin来判断是否到达end-of-file。首先,cin是一个istream类型的object,其用于从输入流中(stdin)读取数据。然后,在istream类中对操作符>>进行了重载,分别定义了包括boolintunsigned等在内的多种格式化输入。详细地说,操作符>>被定义为接受两个operands(操作对象):

  • left-hand operand必须是一个istream类型的object,即cin
  • right-hand operand是一个接收cin中的值的object,即s

所以,>>操作符的含义就是从左边的cin中读取标准输入流中的值,并且赋值给右边的s,同时return left-hand operand,即返回istream类型的对象cin。因为input operator(i.e. >>)返回值为cin(注意是>>操作符的返回值而不是cin的返回值,cin本身是一个object,不存在返回值),所以可以在一条单独的语句中进行多次读取操作:

std::cin >> v1 >> v2;
// equavilent to below:
(std::cin >> v1) >> v2;

简单来说,就是std::cin中有两个输入值,第一个>>操作符从cin中读取第一个输入值并赋给v1,第二个>>操作符从cin中读取第二个输入值并赋给v2

同时,流istream类中定义了4个标志位来表示流的状态:badbiteofbitfailbitgoodbit。当从流中读取到EOF结束符时则会将eofbit置为ture,或者读取到错误数据(例如类型不一致)等,failbit也会被置为true,这个时候cin作为返回值其结果不再是true,所以整个输入流程会结束。具体可以看here

string::size_type Type

为了能够在各种不同类型的机器上使用library types,一个类通常会定义一些伴生类型(companion types)。例如,string::size_type就是一个伴生类型,尽管我们不能知道这个类型的精确定义,但是我们知道它是一个unsigned type,以保证它足够大来保存任何string的size。我们可以直接定义这个类型,也可以让compiler帮助我们推断:

string::size_type len = s.size();
auto len = s.size();

需要注意的是因为lenunsigned,所以如果我们在进行if(len < n)比较的时候,如果nint类型且为负数,那么会发生隐式类型转换,n会变为最大的unsigned,也就是说这个条件判断永远为true

string Comparison

从上面的string常用操作表中可以看到string class重载了比较操作符,这也就说明比较两个string是有意义的:

  • 两个string相等指的是这两个stringsize相同,每个字符也相同(case-sensitive)
  • 字符串s1 > s2的意义为:
    • s1s2的第一个不相同的字符c1 > c2(ascii code)。
    • s1.size() > s2.size()且较短的字符串s2与较长的字符串s1的子串相等。

Conclusions:

两个字符串比较即比较其每一个字符。谁的第一个不相同的字符大谁就大。

string Addition

两个string结合可以直接使用重载的操作符+,非常方便。但是将一个literalstring相加则有可能出现问题:

string s1 = "hello", s2 = "world";
string s3 = s1 + ',' + s2 + '\n';  // ok
string s4 = s1 + ", ";  // ok, adding string s1 and a literal
string s5 = "hello" + ", ";  // error: two literal cannot add
string s6 = s1 + ", " + "world";  // ok, s1 is string
string s7 = "hello" + ", " + s2; // error: "hello" cannot add to ", "

其中s7不能编译通过是因为这个表达式的运算顺序为("hello + ", ") + s2,第一个加法操作是没有意义(不能操作,编译器不知道如何操作)的。因为对于string literal,是没有重载+操作符的。

Conclusions:

只有string才能进行加法操作,这要求+操作符两边至少有一个是string类型。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 gzrjzcx@qq.com

文章标题:C++Primer 3.1: String

文章字数:1.9k

本文作者:Alex Zou

发布时间:2019-11-24, 13:19:35

最后更新:2024-07-10, 03:02:36

原始链接:https://www.hellscript.cc/2019/11/24/subposts_cppPrimer/CPN-3-1-String/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

有钱的捧个钱场,没钱的借钱也捧个钱场