技术文章翻译(七) -- 深入研究C++字符串流处理过程

本人声明

1.本栏仅为归档自己看到的优秀文章;
2.文章版权归原作者所有;
3.因为个人水平有限,翻译难免有错误,请多多包涵。

原文地址

https://www.codeguru.com/cpp/cpp/string/digging-into-c-string-stream-processing.html

文章正文

深入研究C++字符串流处理过程

作者: Manoj Debnath 发表于:2017.05.12
在任何编程语言中,字符串的处理都是一种常见操作。并且,几乎所有的编程语言都提供了某种形式的库,通过调用库中的API来进行字符串处理。问题在于字符串基本上是一组字符,而不能被视为原始数据类型,如int,float,char等。但是,在编程中,由于频繁使用字符串,我们又需要字符串支持这样的行为(视为原始数据类型操作)。例如,一般来说,字符串变量不能直接用字符串内容赋值,或者在代码中将两个简单字符串按照某种逻辑进行连接。在库中提供特定API的想法,是为了减少与其操作相关的复杂性,并以原始数据类型的方式处理它,这种观念对于字符串特别重要,尽管实际上字符串并非原始数据类型,。本文描述了C ++标准库提供的字符串处理方案。

字符串处理

字符串处理包括字符串类型的定义以及字符串操作的各种方法,例如搜索,插入,擦除,替换,比较和字符串的连接。首先,让我们看看进行字符串的定义、赋值操作以及字符串变量在内存中的逻辑表示形式。

string greetings = "Hello String";

这里写图片描述

图 1: 字符串在内存中的定义

注意,这与C中字符指针的字符串声明方式(char * str =“Hello String”)不同;char *是以’\0’作为终止符的,它将表示如下:

'H','e','l','l','o',' ', 'S','t','r','i','n','g','\0'

C ++字符串并不包含终止字符’\ 0’。这是C与C ++中字符串处理之间非常根本和非常重要的区别。

在C ++中,命名空间std中的头文件定义了用于操纵不同长度字符序列的basic_string模板。这个库在某种意义上是泛型的,它使用模板来创建各种字符串类型的簇,例如:

namespace std {

   typedef basic_string<char> string;
   ...
   typedef basic_string<wchar_t> wstring;
   ...
}

wchar_t类型的字符通常用于支持Unicode字符集,该字符集是16位字符,但其标准并不是固定的。wstring是wchar_t字符类型的字符串表示形式。

一个字符串可以借助于构造函数来初始化,方式如下:

string s1 ("Hello String");    // direct initialization,
                               // created from const char *
string s2 = "Hello String";    // copy initialization
string s3 (10, 'z');           // filled with 10 z's
string s4 = string (10, 'z');  // copy initialization,
                               // with 10 z's
string s5 = s4;                // copy s4 into s5

string类

如前文所述,C ++字符串处理的特点是基于字符串类。为了使字符串操作更简单,字符串类除了提供搜索,擦除,插入和替换的功能之外;还对几个操作符进行了重载,例如复制,连接和比较。在执行时,这些类操作自动进行内存分配和修改,而不涉及程序员的代码内部复杂性。在没有初始化的情况下,创建的字符串对象的大小始终以0开始的。字符串大小会在复制或初始化字符串内容时被修改。让我们以一个简单的例子来加深理解。

#include <iostream>
#include <string>
using namespace std;

string global_str1("Enter a string: ");
int main(int argc, char **argv)
{
   string separator(60, '-');
   string s1;
   cout<<"uninitialized string size, s1 =
      "<<s1.size()<<endl;
   cout<<"initialized string size, global_str =
      "<<global_str1.size()<<endl;
   cout<<global_str1;
   getline(cin,s1);
   // the size() and length() are equivalent
   cout<<"Text entered is: '"<<s1<<"' of size =
      "<<s1.length()<<endl;

   string s2(global_str1, 2, 10);
   cout<<"substring of global_str copied to s2 is 
      '"<<s2<<"', size = "<<s2.size()<<endl;

   // create more than one paragraph of text
   string text,para;
   cout<<separator<<endl<<"Enter some text:
      "<<endl;
   while(true){
      getline(cin, para);
      if(para.size()==0)
         break;
      // string concatenated with overloaded +
      // operator
      text += "\n" + para;
   }
   cout<<separator<<endl<<"Text you entered
      is ..."<<endl;
   cout<<text<<endl;
   cout<<separator<<endl<<"Text you entered in
      reverse ...\n"<<endl;
   for(int i=text.size();i>=0;i--){
      cout<<text[i];
   }

   return 0;
}

例1:修改字符串对象的大小

输出结果如下:
这里写图片描述
图2:例1的输出结果

与C风格字符串不同,string的下标是以0开始,并以length() - 1结尾的,C ++字符串函数可以将下标位置作为参数和要操作的字符数。 C++字符串还会重载流读入运算符(>>),来支持从cin中读取字符串的语句。

string str1;
cin>>str1;

在这种情况下,输入结果由空白字符进行分隔的。这意味着,针对“Hello string”的输入,提取的结果为由空白字符终止的“Hello”。这就是为字符串类重载getline函数的原因。

getline(cin, str1);

此函数从键盘(通过cin对象)读取字符串到str1中,字符串由换行符(’\ n’)分隔,而不是像重载的流输入运算符那样按照空格分割。

字符串类还提供了成员函数的重载版本,称为assign,可用于复制字符串对象中指定数量的字符。

string str1, str2;
string str3="I saw a saw to saw a tree";
str1.assign(str3);
// target string, start index, no. of characters
str2.assign(str3, 2, 3);

字符串的连接和比较

字符串类不但重载了像+和+ =这样的运算符来实现字符串的连接操作;而且定义了==,!=,<,>,<=和> =等运算符来进行字符串比较操作。但是,它们并不违反通用的运算符优先级规则,例如+操作的优先级高于赋值运算符=和+=。

string s1, s2("higher"), s3(" you "),
   s4(" go");
s1 = s2 + s3 + s4;

这里有一个专门的重载成员函数,用来连接或追加一个字符串。

s1.append(" the lighter you feel");
// append from 14

字符串比较是按字典顺序完成的,我们可以使用逻辑运算符或比较成员函数,来进行字符串的的比较。

string s1("ac"), s2("ab");
if(s1==s2)
   cout<<"s1==s2";
else if(s1>s2)
   cout<<"s1>s2";
else
   cout<<"s1<s2";

当字符串之间进行比较时,比如s1和s2,如果s1的字典顺序大于s2,则返回正数。如果结果相等,则返回0;否则就返回负值。

int k = s1.compare(s2);
if(k==0)
   cout<<"s1==s2";
else if(k>0)
   cout<<"s1>s2";
else
   cout<<"s1<s2";

字符串比较操作可以针对子字符串或部分字符串进行。在这种情况下,我们可以使用比较函数的重载版本。

string s1("synchronize"), s2("sync");
int k = s1.compare(0,4,s2); //s1==s2

第一个参数0指定起始下标;第二个参数4表示长度;第三个参数代表进行比较的字符串。

一些更常见的字符串操作

利用字符串类的成员函数,可以进行的一些其他常见操作如下。

  • 类字符串提供用于交换字符串的交换函数。
string s1("tick"),s2("tock");
cout<<"Before swap "<<s1<<"-"<<s2<<endl;
s1.swap(s2);
cout<<"After  swap "<<s1<<"-"<<s2<<endl;
  • 成员函数substr用于从字符串中提取子字符串。第一个参数是提取字符串的起始下标,第二个参数是提取字符串长度。
string s1("...tolerant as a tree");
cout<<s1.substr(3, 8);
  • 获取字符串相关特性信息的成员函数如下:
string s1;
cout<<"Is empty string?
   "<<(s1.empty()?"yes":"no")<<endl;
cout<<"Capacity: "<<s1.capacity()<<endl;
cout<<"Maximum size: "<<s1.max_size()<<endl;
cout<<"Length: "<<s1.length()<<endl;
cout<<"------------------------"<<endl;
s1="fiddler on the roof";
cout<<"Is empty string?
   "<<(s1.empty()?"yes":"no")<<endl;
cout<<"Capacity: "<<s1.capacity()<<endl;
cout<<"Maximum size: "<<s1.max_size()<<endl;
cout<<"Length: "<<s1.length()<<endl;
  • **字符串的子字符串检索,删除,替换和文本插入操作。
#include <iostream>
#include <string>
using namespace std;

int main(int argc, char **argv)
{
   string s1("They are allone and the same,"
   "The mind follows matter, and whatever "
   "it thinks of is also material");
   cout<<"--------------------"<<endl;
   cout<<"Original text"<<endl;
   cout<<"--------------------"<<endl;
   cout<<s1<<endl;
   cout<<"--------------------"<<endl;
   cout<<"Replaced ' '(space) with '&nbsp;'"
      <<endl;
   cout<<"--------------------"<<endl;
   // erased the extra word 'all' from
   // 'allone'
   // in the text
   s1.erase(9,3);
   size_t spc=s1.find(" ");
   while(spc!=string::npos){
      s1.replace(spc,1,"&nbsp;");
      spc=s1.find(" ",spc+1);
   }
   cout<<s1<<endl;
   cout<<"--------------------"<<endl;
   cout<<"Back to original text"<<endl;
   cout<<"--------------------"<<endl;
   spc=s1.find("&nbsp;");
   while(spc!=string::npos){
      s1.replace(spc,6," ");
      spc=s1.find("&nbsp;",spc+1);
   }
   cout<<s1<<endl;
   cout<<"--------------------"<<endl;
   cout<<"Inserting new text into existing
      text"<<endl;
   cout<<"--------------------"<<endl;
   s1.insert(0,"Matter or Transcendence? ");
   cout<<s1<<endl;

   return 0;
}

编者注:在前面的示例代码中,每组48个连字符被缩减为20个连字符组,以适应可用空间,且不会破坏代码行。

  • 字符串类中的与C样式字符串相关的操作
    C ++中的字符串类还提供了部分函数,这些函数可以将字符串对象转换为基于C样式指针的字符串。以下示例说明了这些操作是如何完成的。
#include <iostream>
#include <string>
using namespace std;

int main(int argc, char **argv)
{
   string s1("ABC");

   // copying characters into allocated memory
   int len=s1.length();
   char *pstr1=new char[len+1];
   s1.copy(pstr1,len,0);
   pstr1[len]='\0';
   cout<<pstr1<<endl;

   // string converted to C-style string
   cout<<s1.c_str()<<endl;

   // function data() returns const char *
   // this is not a good idea because pstr3
   // can become invalid if the value of s1 changes
   const char *pstr3=s1.data();
   cout<<pstr3;

   return 0;
}
  • 使用字符串迭代器
    我们可以按以下方式使用字符串对象迭代器。
    ···
    string s1(“a rolling stone gathers no moss”);
    for(string::iterator iter=s1.begin();iter!=s1.end();iter++)
    cout<<*iter;
    ···
  • 字符串和IO流
    C ++流对象的IO操作可用于直接对内存中的字符串进行操作。它为此提供了两个支持类。一个用于输入的isstringstream,一个用于输出的ostringstream。它们分别是基于模板类basic_istringstream和basic_ostringstream的重新定义。
typedef basic_istringstream<char> istringstream;
typedef basic_ostringstream<char> ostringstream;

除了具有用于自身内存格式化的成员函数之外,这些模板类还提供与istream和ostream相同的功能。

ostringstream对象使用字符串对象来存储输出数据。它有一个名为str()的成员函数,该函数用于返回该字符串的副本。 ostringstream对象使用流插入运算符,将字符串和数值的集合输出到对象中。在流插入运算符的帮助下,数据被附加到内存中的字符串。

istringstream对象可以将数据从内存中的字符串中,输入到程序变量中。数据以字符的形式存储。从工作方式上来看,来自istringstream对象的输入,比较类似于来自任意文件的输入,输入字符串的结尾被istringstream对象解释为文件结束标记。

为了更清楚地说明这个类的功能,让我们用一个简单的例子来阐述。

#include <iostream>
#include <string>
#include <sstream>
using namespace std;

int main(int argc, char **argv)
{
   ostringstream out;
   out<<"Float value = "<<1.3<<endl
      <<"and int value =
         "<<123<<"\t"<<"tabbed"<<endl;
   cout<<out.str();

   string s1("0 1 2 3 4 5 6 7 8 9");
   istringstream in(s1);
   while(in.good()){
      int ival;
      in>>ival;
      cout<<ival<<endl;
   }
   return 0;
}

示例2:关注istringstream对象

  • 输出结果
    这里写图片描述
    图3: 示例2的输出结果

  • 结论

除了一些封装好的方便功能之外,标准C ++库类字符串提供了字符串处理所需的全部内容。尽管C ++支持两种方式,但最好坚持面向对象的方式处理字符串,而不是采用C风格的字符串处理方式。这条“经验法则”不仅可以增强代码的可读性,还可以减少代码中的错误。

关于作者

Manoj Debnath
[email protected]

相关文章

Last.fm Open Sources C++ Moost Library Now Available
CppDepend Pro License for C\C++ open source project contributors

栏目导航
上一篇:技术文章翻译(六) – 基于GTK+创建一个GUI程序
下一篇:技术文章翻译(八) – 理解C++文件处理

猜你喜欢

转载自blog.csdn.net/u014337397/article/details/80979724