本人声明
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 ' '"
<<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," ");
spc=s1.find(" ",spc+1);
}
cout<<s1<<endl;
cout<<"--------------------"<<endl;
cout<<"Back to original text"<<endl;
cout<<"--------------------"<<endl;
spc=s1.find(" ");
while(spc!=string::npos){
s1.replace(spc,6," ");
spc=s1.find(" ",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
manosolireap@outlook.com
相关文章
Last.fm Open Sources C++ Moost Library Now Available
CppDepend Pro License for C\C++ open source project contributors
栏目导航
上一篇:技术文章翻译(六) – 基于GTK+创建一个GUI程序
下一篇:技术文章翻译(八) – 理解C++文件处理