C++String类型杂谈

一:写在前面的话

在刚刚开始学数组中的字符数组时,老师介绍了string类型,书上把它略过,老师也没有细讲只说平常用字符数组就够了。但在自己查阅了一些资料后发现C++中string类型的强大,于是自己也不断学习string类型,在这里总结一下,方便大家也便于自己以后复习。

二:String的声明与构造

1.声明



#include <string>
using namespace std;
要想使用string类型和其一系列的函数,就必须要包含头文件<string>。
string s;

这样就声明了一个string类型变量,因为没有任何的传入参数,所以这个变量它的初值就是一个空字符串。

2.常用的几个构造函数与重构函数

1)string s;
生成一个空字符串s。
2)string ss(s);
拷贝构造函数 生成s的复制品ss/使ss的初值为s。
3)string ss(s,be);
将字符串s内从位置be开始到结尾的部分作为字符串ss的初值。
4)string ss(s,be,len);
将字符串s内从位置be且开始长度最多len的部分作为字符串ss的初值。
5)string ss(cs);
将字符数组cs作为ss的初值。
6)string ss(cs,len);
将字符数组cs前len个字符作为字符串ss的初值。
7)string ss(cs,be,len);
将字符数组cs内从位置be开始且长度最多len的部分作为字符串ss的初值。
8)string ss(num,c);
生成一个字符串ss,包含num个c字符。
9)s.~string();

销毁所有字符,s变为空字符串,释放内存。

3.string类型数组


string类型不仅可以定义普通的变量,也可以像其他类型如int,float,char等一样定义数组,即字符串数组。

例:

string s[3]={"abc","ef","la"};

cout<<s[2]<<' '<<s[0]<<' '<<s[1][1];

输出:“la abc f”。

s字符串数组的状况如下表:

s[0]

a

b

c

s[1]

e

f

 

s[2]

l

a

 

在一个字符串数组包含若干个元素(例子中为3个),每一个元素都相当于一个单独的字符串变量。

所以并不要求每个字符串元素具有相同的长度,即使对同一个元素而言,它的长度也是可以变化的,当向某一个元

素重新赋值,其长度就可能发生变化。当向某一个元素重新赋值,其长度就可能发生变化。

在字符串数组的每一个元素中存放一个字符串,而不是一个字符,这是字符串数组与字符数组的区别。如果用字符

数组存放字符串,一个元素只能存放一个字符,用一个一维字符数组只能存放一个字符串,用二维数组才能实现和

一维字符串数组那样一个数组存放若干字串的效果。与单独的字符串同样的,字符串数组中每个元素末尾并没有'\0'

结束符。

那字符串数组是如何存储的呢?实际上,编译系统为每一个字符串变量分配4个字节,在这个存储单元中,并不是直

接存放字符串本身,而是存放字符串的地址。在本例中,就是把字符串″abc″的地址存放在s[0],把字符串″ef″ 的地址

存放在s[1],把字符串″la″的地址存放在s[2],上表只是一个示意,在字符串变量中存放的是字符串的指针(字符串的

地址)。 

素以可以发现字符串数组是很方便好用的。


三:String的操作函数


操作函数是string中的重中之重,这里分门别类地进行介绍。


1.字符数组与字符串的转化



1)c_str()

c=s.c_str();
c_str()函数返回一个指向字符数组的指针常量(const char*),内容与原string字符串相同。
const char* c;
string s="1234";
c=s.c_str();
cout<<c<<endl;
这时输出的是s的内容"1234"。
s="abcd";
cout<<c<<endl;
这时输入的是更改后的s的内容"abcd"。
显然这样并不是我们想要的独立的字符数组,所以通常要用strcpy(,)等函数来操作c_str()返回的指针常量。
char c[10];
string s="1234";
strcpy(c,s.c_str());
这样c就是一个值为s字符串中内容的独立的字符数组了,后面对其操作也会很方便。
c_str() 是以const char* 形式传回string字符串,如果一个函数要求char*参数,也可以直接使用c_str()传回的char*指针:
string s ="Hello World!";
printf("%s",s.c_str()); 
输出“Hello World!”。
值得注意的一点是,string类型中的字符串末尾并没有字符数组中的'\0',所以c_str()在转换时会在字符数组常量末尾加上一个'\0',而……
2)data()
data()函数并不会加上末尾那个'\0',其余与c_str()函数一样。
3)copy()
copy(char c,int n,int pos),将字符串中从位置pos(pos可以没有,默认从0开始)开始的n个字符赋给字符数组c(要有足够的空间),会有一个返回值为实际复制的字符的个数。
char c[20];
string s="12345";
int n=3,pos=2;
int x=s.copy(c,n,pos);
cout<<x<<' '<<s<<' '<<c;
输出“3 12345 345”。
如果复制的目标字符数组已经有值,会替换掉从目标字符数组从0开始的n个字符,如:
char cc[20]="abcde";
s.copy(cc,n,pos);
cout<<cc;
输出“345de”。


不过在我测试时,第一段代码会输出“3 12345 345wp 緒懵縲咐w?>”可以看到c字符数组在复制的字符之后出现了乱码,而在把c数组空间增加到113及以上时就没有了乱码,这个问题尚未找到原因。


2.大小与容量函数


1)length()与size()
string字符串用来计算现有字符串长度的函数有length()和size(),他们是等价的。
string s="12345";
inta=s.size(),b=s.length();
cout<<a<<''<<b;
输出“5 5”。
2)capacity()

size()是指它已经保存的元素的数目,而capacity()则是在不分配新的内存空间的前提下它最多可以保存多少元素(即容量)。

3)reserve()
void reserve(size_type size);
将字符串的容量设置为size。如果size指定的数值要小于当前字符串中的字符数,容量将被设置为可以恰好容纳原字符的数值(即非强制性缩减)。它最大的用处是为了避免反复重新分配缓冲区内存而导致效率降低,或者在使用某些STL操作(例如copy)之前保证缓冲区够大。如果没有设置size,默认为0。
4)max_size()
返回这个string字符串最多可包含的字符数。当程序执行了长度超过max_size()的操作,编译器会抛出length_error异常。max_size()的值与编译器有关,对于不同的编译器,max_size()的值不一定相同。
5)resize()
resize(string::size_type size, char val);
改变原有字符串的长度为size,当size大于原长度时,多出的部分用val来填充,如果未指定val,则val默认为空(ASCLL:0);当size小于原长度时,从开始起截取size个字符,即相当于把后面的部分删除。
string s="abcde";
s.resize(10,'c');//s的值变为"abcdeccccc"。
s.resize(7);//s的值变为"abcdecc"。
同时这里我也要简单介绍一下string::size_type这个类型,它就是size()一系成员函数返回值的类型,也是这里长度所用的类型,当然直接用int类型等也是可行的,比如说1)中的a变量,正确的话当用size_type类型储存,不过这里内容较多,不做细谈。
6)empty()
用来判断一个字符串是否为空,空则返回true,不空则返回false。



3.元素存取

我们可以使用下标操作符[]和函数at()对string字符串包含的字符进行访问。但值得注意的是下标[]并不检查索引是否有效(有效索引[0,s.length())),如果索引失效,会引起未定义的行为。而at()会进行检查,如果使用at()的时候索引无效,会抛出out_of_range异常。
例外的是,对于常量string字符串的操作符[]对索引值s.length()仍然有效,其返回值是'/0'。其他的各种情况,s.length()索引都是无效的。
举例如下:
const string cs("const string");
string s("string");
Str[3];//正常的索引。
s.at(3);//正常的索引。
s[100];//未定义的索引。
s.at(100);//异常。
s[s.length()]//未定义行为。
cs[cs.length()] //返回 ‘/0'。
s.at(s.length());//异常。
cs.at(cs.length()) //异常。
因为string会进行重新分配,所以尽量不要使用下面这样的引用或指针赋值:
char& r=s[2];
char* p= &s[3];
一旦发生重新分配,r,p立即失效。

4.比较函数

C++字符串支持常见的比较操作符(>,>=,<,<=,==,!=),甚至支持字符串与字符数组的比较(如str<”hello”)。在使用>,>=,<,<=这些操作符的时候是根据“当前字符特性”将字符按字典顺序进行逐一得比较。字典排序靠前的字符小,比较的顺序是从前向后比较,遇到不相等的字符就按这个位置上的两个字符的比较结果确定两个字符串的大小。同时,在之前的所有字符都相等的情况下,字符多的字符串大。

举例如下:
string s("abcd");
cout<<(s="abcd");s="abcd"//真,输出1。
cout<<(s<="abcde");//真,输出1。
cout<<(s>"xyz")<<endl;//假,输出0。
另一个功能强大的比较函数是成员函数compare()。他支持多参数处理,支持用索引值和长度定位子串来进行比较。他返回一个整数来表示比较结果,相等返回0,大于返回1,小于返回-1。
举例如下:
s.compare("abcd");//相等,返回0。
s.compare("zxy");//小于,返回-1。
s.compare("ab");//大于,返回1。
s.compare(s);//相等,返回0。
s.compare(0,2,"abcd",2,2);
//用字符串s中从位置0开始2个字符"ab"与字符串"abcd"中从位置2开始的2个字符"cd"进行比较,"ab"<"cd",返回-1。
s.compare(1,2,"xyz",2);
//用字符串s中从位置1开始的一个字符"b"与字符串"xyz"从头开始的2个字符"xy"比较,"b"<"xyz",返回-1。

5.内容更改函数

string s="asdfg",ns;

char c[20]="qwe";
首先谈一谈赋值,最常用的赋值方法当然是使用操作符=,新值可以是string字符串(如:ns=s;) 、字符数组(如:;ns=cs;)甚至单一字符(如:ns='j';),要想清空一个字符串可以直接赋空(如:ns="";),除了用操作符=赋值,还可以……
1)assign()
assign(),这个成员函数可以更灵活的对字符串进行赋值。
举例说明:
ns.assign(s); //直接把s的值赋给ns。
ns.assign(s,1,3);//把s中从位置1开始的3个字符"sdf"赋给字符串。
ns.assign(s,2,string::npos);//把字符串s从位置2开始到结尾赋给ns。
ns.assign("zxc"); //直接赋给ns值"zxc"。
ns.assign("abcdefg",5);//把"abcde"赋给字符串。
ns.assign(5,'x');//把五个x赋给字符串。
2)clear()
清空字符串,没什么可说的(如:ns.clear())。
3)erase()
删除函数,也可以用来清空字符串(如:ns.erase())。
例:
s.erase(1,2);//删除字符串s中从位置1开始的2个字符,新的s的值为"afg"。
s.erase(1);//删除字符串s中从位置1开始直到末尾的所有字符,新的s的值为"a"。
4)replace()
替换函数,s.replace(pos, n, ss)用ss替换s中从索引pos开始(包括pos,pos从0开始)后的n个字符的子串。
例:
s.replace(1,3,"zx");//将字符串s中从索引1开始的3个字符的字串"sdf"替换为"zx",新的s的值为"azxg"。
5)insert()
插入函数,这个函数需要你指定一个安插位置,被插入的字符串将放在这个索引的后面。
insert(int pos, const char *s);//在索引pos后插入字符串数组s。
insert(int pos, const char *s, int n);//在索引pos后插入字符数组s的前n个字符。
insert(int pos,const string &s);//在索引pos后插入字符串s。
insert(int pos,const string &s, int poss, int n);//在索引pos后插入字符串s从索引poss开始的n个字符。
insert(int pos, int n, char ch);//在索引pos后插入n个字符ch。
例:
s.insert(2,"zxcy",1,3);//在字符串s的位置2后插入字符串"zxcy"从位置1开始的3个字符,s的新值为"asxcydfg"。
s.insert(2,1,'c')//在字符串s的位置2后插入1个字符'c',s的新值为"ascdfg"。
6)append()
增加函数,这个函数是指在原字符串末尾增加新的字符,字符串。格式与assign()相仿。
举例(直接从assign那里复制改了改后-,-)如下:
ns.append(s); //直接把s的值接在ns后面。
ns.append(s,1,3);//把s中从位置1开始的3个字符"sdf"接在ns后面。
ns.append(s,2,string::npos);//把字符串s从位置2开始到结尾接在ns后面。
ns.append("zxc"); //直接把"zxc"接在ns后面。
ns.append("abcdefg",5);//把"abcde"接在ns后面。
ns.append(5,'x');//把五个x接在ns后面。
当然,其实string字符串可以直接使用"+","+="来简单直观的进行增加。
例:
ns=ns+s; <==> ns+=s;//都是简单的直接把s接在ns尾部。
例如:ns+=s;ns+=c;ns+='a';
也可以用push_back()来简单的在末尾添加单个字符(如:ns.push_back('a'))。
7)substr()
截取子串,也是非常实用又简单的一个函数。
s.substr(pos);//截取s中从索引pos开始到末尾的所有字符的子串,并返回。
s.substr(pos, n);//截取s中从索引pos开始的n个字符的子串,并返回。
例:s.substr(2,2);截取s中从2开始的三个字符的子串"df"。
8)find()一系
find()一系是string中非常强大的查找函数,下面举出几个比较常用的函数。
样例:string s("Hello hello the worllld"),s1("ll");
(需要说明的是一般的,位置与索引都是从0开始,之前每次说都会强调一次,后面如无特殊说明就默认如此)
s.find(s1);查找s中第一次出现s1的位置,并返回。(样例为2)
s.rfind(s1);查找s中最后次出现s1的位置,并返回。(样例为20)
s.find_first_of(s1);查找在s1中任意一个字符在s中第一次出现的位置,并返回。(2)
s.find_last_of(s1);查找在s1中任意一个字符在s中最后一次出现的位置,并返回。(21)
s.find_first_not_of(s1);查找s中第一个不属于s1中的字符的位置,并返回。(0)
s.find_last_not_of(s1);查找s中最后一个不属于s1中的字符的位置,并返回。(22)
同时也需要注意的是find()和rfind()查找出来的位置是查找出来的s1的首字符的位置。
那么如果没有找到会怎样呢?这时find()他们会返回一个值“string:;npos”,这个值输出的话一般会是“-1”,之前assign()与append()函数中用到的也正是它,但它的类型其实是“string::size_type”,也就是find()他们返回的类型,“string::npos”作为string中的一个长度参数,表示“直到字符串结束”。
同时,find()系每个函数都还可以再加一个参数“pos”(如:s.find(s1,pos)),即从s字符串的位置pos开始向后进行find,其他如rfind()等等也是如此。

6.输入输出函数

完全可以用“cin>>”和“cout<<”来进行输入输出,此外的还有“getline(cin,str);”等,对于输入输出一系列函数,要说的还有很多,之后也会再为此写一些东西。

四:小结

至此string的大部已经说完了,还有一些细枝末节和stringstream没有谈(还不会),以后会再写一些关于他们的东西。

有什么错误欢迎指出。

虽然是蒟蒻写的,但也是原创,转载也要和我说一声哦。