C++ Primer 笔记之string

string表示可变长的字符序列,vector存放的是某种给定类型对象的可变长序列

os<<s 将s写到输出流os当中,返回os

is>>s 从is中读取字符串赋给s,字符串以空白分隔,返回is

getline(is,s) 从is中读取一行赋给s,返回is

auto len = len.size(); //len的类型是string::size_type

由于size函数返回的是一个无符号整型数,因此切记,如果在表达式中混用了带符号数和无符号数

将可能产生意想不到的结果。例如,假设n是一个具有负值的int,则表达式s.size()<n的判断结果几乎

肯定是true。这是因为负值n会自动地转换成一个比较大的无符号值。

Tip:如果一条表达式中已经有了size()函数就不要再使用int了,这样可以避免混用int和unsigned可能带来的问题。

相等性运算符(==和!=)分别检验两个string对象相等或不相等,string对象相等意味着它们的长度相同而且

所包含的字符也全都完全相同。关系运算符<=,<,>,>=分别检验一个string对象是否小于,小于等于,大于,

大于等于另外一个string对象。上述这些运算符都依照(大小写敏感的)字典顺序:

1.如果两个string对象的长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,

就说较短string对象对应位置上的字符相同,就说较短string对象小于较长string对象。

2.如果两个string对象在某些对应的位置不一致,则string对象比较的结果其实是string对象中第一对相异字符

比较的结果。

下面是string对象比较的一个示例:

string str = "Hello";

string phrase = "Hello World";

string slang = "Hiya";

根据规则1可判断,对象str小于对象phrase;根据规则2可判断,对象slang既大于str也大于phrase

字面值和string对象相加

即使一种类型并非所需,我们也可以使用它,不过前提是该种类型可以自动转换成所需的类型。因为标准库

允许把字符字面值和字符串字面值转换成string对象,所以在需要string对象的地方就可以使用这两种字面值

来替代。

string s1 = "hello",s2 = "world"; //在s1和s2中都没有标点符号

string s3 = s1 + "," + s2 + ‘\n‘;

当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧

运算对象至少有一个是string:

string s4 = s1 + ","; //正确:把一个string对象和一个字面值相加

string s5 = "hello" + ","; //错误:两个运算对象都不是string

//正确:每个加法运算符都有一个运算对象时string

string s6 = s1 + "," + "world";

string s7 = "hello" + "," + s2; //错误:不能把字面值直接相加

因为某些历史原因,也为了与C兼容,所以C++语言中的字符串字面值并不是标准库类型string

的对象。切记,字符串字面值与string是不同的类型

处理string对象中的字符

cctype头文件中的函数

isalnum(c) 当c是字母或数字时为真

isalpha(c) 当c是字母时为真

iscntrl(c) 当c是控制字符时为真

isdigital(c) 当c是数字时为真

isgraph(c) 当c不是空格但可打印时为真

islower(c) 当c是小写字母时为真

isprint(c) 当c是可打印字符时为真(即c是空格或c具有可视形式)

ispunct(c) 当c是标点符号时为真(即c不是控制字符,数字,字母,可打印空白中的一种)

isspace(c) 当c是空白时为真(即c是空格,横向制表符,纵向制表符,回车符,换行符,进纸符的一种)

isupper(c) 当c是大写字母时为真

isxdigit(c) 当c是十六进制数字时为真

tolower(c) 如果c是大写字母,输出对应的小写字母;否则原样输出c

toupper(c) 如果c是小写字母,输出对应的大写字母;否则原样输出c

建议:使用C++版本的c标准库头文件

C++标准库中除了定义C++语言特有的功能外,也兼容了c语言的标准库。C语言的头文件形如name.h。

C++则将这些文件命名为cname.也就是去掉了.h后缀,而在文件名name之前添加了字母c,这里c表示这是

一个属于C语言标准库的头文件。

因此,cctype头文件和ctype.h头文件的内容是一样的,只不过从命名规范上来讲更符合C++语言的

要求。特别的,在名为cname的头文件中定义的名字从属于命名空间std,而定义在名为.h的头文件中的则

不然。

一般来说,C++程序使用命名为cname的头文件而不使用name.h的形式,标准库中的名字总能在命名

空间std中找到。如果使用.h形式的头文件,程序员就不得不时刻牢记哪些是从C语言那儿继承过来的,

哪些又是C++语言所独有的。

处理每个字符?使用基于范围的for语句

如果想对string对象中的每个字符做点儿什么操作,目前最好的办法是使用C++11新标准提供的一种语句:

范围for(range for)语句。这种语句遍历给定序列中每个元素并对序列中的每个值进行某种操作,其语法形式是:

for (declaration : expression)

statement

其中,expression部分是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量将被用于访问

序列中的基础元素。每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值。

一个string对象表示一个字符的序列,因此string对象可以作为范围for语句中的expression部分。举个例子,

把string对象中的字符每行一个输出出来:

string str("some thing");

//每行输出str中的一个字符

for (auto c : str) //对于str中的每个字符

cout << c << endl; //输出当前字符,后面紧跟一个换行符

for循环把变量c和str联系了起来,其中我们定义循环控制变量的方式与定义任意一个普通变量

是一样的。此例中,通过使用auto关键字让编译器来决定变量c的类型,这里c的类型是char.每次迭代,

str的下一个字符被拷贝给c,因此该循环可以读作“对于字符串str中的每个字符c,”执行某某操作。此例中

的“某某操作”即输出一个字符,然后换行。

举个稍微复杂的例子,使用范围for语句和ispunct函数统计string对象中标点符号的个数:

string s("Hello World!!!");

//punct_cnt的类型和s.size的返回类型一样;

decltype(s.size()) punct_cnt = 0;

//统计s中标点符号的数量

for (auto c : s) //对于s中的每个字符

if (ispunct(c)) //如果该字符是标点符号

++punct_cnt; //将标点符号计数值加1

cout << punct_cnt

<< "punctuation characters in " << s << endl;

程序的输出结果将是:

3 punctuation characters in Hello World!!!

这里我们使用decltype关键字声明计数变量punct_cnt,它的类型是s.size函数返回值的类型,也就是

string::size_type.使用范围for语句处理string对象中的每个字符并检查其是否是标点符号。如果是,

使用递增运算符给计数变量加1.最后,待范围for语句结束后输出统计结果。

使用范围for语句改变字符串中的字符

如果想要改变string对象中字符的值,必须把循环变量定义成引用类型。记住,所谓引用只是给对象

的一个别名,因此当使用引用作为循环控制变量时,这个变量实际上被依次绑定到了序列的每个元素上。

使用这个引用,我们就能改变它绑定的字符。

新的例子不再是统计标点符号的个数了,假设我们想要把字符串改写为大写字母的形式。为了做到

这一点可以使用标准库函数toupper,该函数接收一个字符,然后输出其对应的大写形式。这样,为了

把整个string对象转换成大写,只要对其中的每个字符调用toupper函数并将结果再赋给原字符就可以了。

string s("Hello World!!!");

//转换成大写形式

for (autp &c : s) //对于s中的每个字符(注意:c是引用)

c = toupper(c); //c是一个引用,因此赋值语句将改变s中字符的值

cout << s << endl;

上述代码的输出结果将是:

HELLO WORLD!!!

每次迭代时,变量c引用string对象s的下一个字符,赋值给c也就是在改变s中对应字符的值。因此当执行

下面的语句时,

c = toupper(c); //c是一个引用,因此赋值语句将改变s中字符的值

实际上改变了c绑定的字符的值。整个循环结束后,str中的所有字符都变成了大写形式

只处理一部分字符?

如果要处理string对象中的每一个字符,使用范围for语句是个好主意。然而,有时我们需要访问的只是其

中一个字符,或者访问多个字符但遇到某个条件就要停下来。例如,同样是将字符改为大写形式,不过新的

要求不再是对整个字符串这样做。而仅仅把string对象中的第一个字母或第一个单词大写化。

要想访问string对象中的单个字符有两种方式:一种是使用下标,另一种是使用迭代器。

下标运算符([])接收的输入参数是string::size_type类型的值,这个参数表示要访问的字符的位置;返

回值是该位置上的引用。

string对象的下标从0计起。如果string对象s至少包含两个字符,则s[0]是第一个字符,s[1]是第二个字

符,s[s.size()-1]是最后一个字符。

Note:string对象的下标必须大于等于0而小于s.size()

使用超出此范围的下标将引发不可预知的结果,以此推断,使用下标访问空string也会引发不可预知的结果。

下标的值称作“下标”或“索引”,任何表达式只要它的值是一个整型值就能作为索引。不过,如果某个索引是

带符号类型的值将自动转换成由string::size_type表达的无符号类型

下面的程序使用下标运算符输出string对象中的第一个字符:

if (!s.empty()) //确保确实有字符需要输出

cout << s[0] << endl; //输出s的第一个字符

在访问指定字符之前,首先检查s是否为空。其实不管什么时候只要对string对象使用了下标,都要确认那个

位置上确实有值。如果s为空,则s[0]的结果将是未定义的。

只要字符串不是常量,就能为下标运算符返回的字符赋新值。例如,下面的程序将字符串的首字符改成了大写

形式:

string s("some string");

if(!s.empty()) //确保s[0]的位置确实有字符

s[0] = toupper(s[0]); //为s的第一个字符赋一个新值

程序的输出结果将是:

Some string

使用下标执行迭代

另一个例子是把s的第一个词改成大写形式:

//依次处理s中的字符直至我们处理完全部字符或者遇到一个空白

for (decltype(s.size()) index = 0;

index != s.size() && !isspace(s[index]); ++ index)

s[index] = toupper(s[index]); //将当前字符改成大写形式

程序的输出结果将是:

SOME string

在上诉程序中,for循环使用变量index作为s的下标,index的类型是由decltype关键字决定的。

首先把index初始化为0,这样第一次迭代就会从s的首字符开始;之后每次迭代将index加1以得

到s的下一个字符。循环体负责将当前字母改写为大写形式。

for语句的条件部分涉及一点新知识,该条件使用了逻辑与运算符(&&)。如果参与运算的

两个运算对象都为真,则逻辑与结果为真;否则结果为假。对这个运算符来说,最重要的一点是,

C++语言规定只有当左侧运算对象为真时才会检查右侧运算对象的情况。如此例所示,这条规定

确保了只有下标取值在合理范围之内时才会真的用此下标访问字符串。也就是说,只有在index

达到s.size()之前才会执行s[index].随着index的增加,它永远不可能超过s.size()的值,所以

可以确保index比s.size()小。

提示:注意检查下标的合法性

使用下标 时必须确保其在合理范围之内,也就是说,下标必须大于等于0而小于字符串的size()

的值。一种简便易行的方法是,总是设下标的类型为string::size_type,因为此类型是无符号数,

可以确保下标不会小于0.此时,代码只需保证下标小于size()的值就可以了。

Tip: C++标准并不要求标准库检查下标是否合法。一旦使用了一个超出范围的下标,就会产生

不可预知的结果。

使用下标执行随机访问

在之前的示例中,我们让字符串的下标每次加1从而按顺序把所有字符改写成了大写形式。其实

也能通过计算得到某个下标值,然后直接获取对应位置的字符,并不是每次都得从前往后依次访问。

例如,想要编写一个程序把0到15之间的十进制数转换成对应的十六进制形式,只需初始化一个

字符串令其存放16个十六进制“数字”:

const string hexdigits = "0123456789ABCDEF"; //可能的十六进制数字

cout << "Enter a series of number between 0 and 15"

<< "separated by apaces. Hit ENTER when finished: "

<< endl;

string result; //用于保存十六进制的字符串

string::size_type n; //用于保存从输入流读取的数

while (cin >> n)

if (n < hexdigits.size()) //忽略无效输入

result += hexdigits[n]; //得到对应的十六进制数字

cout << "Your hex number is: " << result << endl;

假设输入的内容如下:

12 0 5 15 8 15

程序的输出结果将是:

Your hex number is: C05F8F

上述程序的执行过程是这样的:首先初始化变量hexdigits令其存放从0到F的十六

进制数字,注意我们把hexdigits声明成了常量,这是因为在后面的程序中不打算

再改变它的值。在循环内部使用输入值n作为hexdigits的下标,hexdigits[n]的

值就是hexdigits内位置n处的字符。例如,如果n是15,则结果是F;如果n是12,则

结果是C,以此类推。把得到的十六进制数字添加到result内,最后一并输出。

无论何时用到字符串的下标,都应该注意检查其合法性。在上面的程序中,下标

n是string::size_type类型,也就是无符号类型,所以n可以确保大于或等于0。在实际

使用时,还需检查n是否小于hexdigits的长度。

时间: 2024-08-25 04:57:05

C++ Primer 笔记之string的相关文章

第二十三章,(C++ primer笔记)string的理解(C++)

demo01: #include <iostream> int main(int argc, char** argv) { std::string line; //存string的size用size_type这个类型 std::string::size_type st; std::cout<<"请输入内容(按"回车键"结束):"<<std::endl; //获取一行数据 getline(std::cin,line); //获取字符

C++ Primer笔记8_动态内存_智能指针

1.动态内存 C++中,动态内存管理是通过一对运算符完成的:new和delete.C语言中通过malloc与free函数来实现先动态内存的分配与释放.C++中new与delete的实现其实会调用malloc与free. new分配: 分配变量空间: int *a = new int; // 不初始化 int *b = new int(10); //初始化为10 string *str = new string(10, ); 分配数组空间: int *arr = new int[10];//分配的

C++ Primer笔记2_四种类型转换_异常机制

1.类型转换 命名的强制类型转换: 有static_cast.dynamic_cast.const_cast.reinterpret_cast static_cast: 编译器隐式执行的任何类型转换都可以由static_cast完成 当一个较大的算术类型赋值给较小的类型时,可以用static_cast进行强制转换. 可以将void*指针转换为某一类型的指针 可以将基类指针强制转换为派生类指针,但是不安全. 无法将const转化为nonconst,这个只有const_cast才可以办得到 举例:

重读C++ Primer笔记

C++ Primer 5E 有符号和无符号 无符号类型和有符号类型混合运算时,有符号数会被提升至无符号类型,如果其值为负责会产生错误. int main() { unsigned int u = 10; int i = -42; std::cout<<u+i<< std::endl; // 4294967264 if sizeof(int)==4 return 0; } 列表初始化 列表初始化过程不允许损失数据类型精度,所以下面代码中的两行无法通过编译 int main() { d

C++ Primer笔记6_STL之泛型算法

1.泛型算法: 大多数算法定义在头文件algorithm中,标准库还在头文件numeric中定义了一组数值泛型算法 只读算法: 举例: find函数用于找出容器中一个特定的值,有三个参数 int val = 10;//val为我们需要查找的值 auto result = find(vec.begin(), vec.end(), val): cout << "The value "<< val << (result == vec.end() ? &qu

C++ Primer笔记13_运算符重载_总结

总结: 1.不能重载的运算符: . 和 .* 和 ?: 和 ::  和 sizeof 和 typeid 2.重载运算符有两种基本选择: 类的成员函数或者友元函数, 建议规则如下: 运算符 建议使用 所有一元运算符 成员函数 = () [] -> 必须是成员函数 += -= /= *= ^= &= != %= >>= <<= , 似乎带等号的都在这里了. 成员函数 所有其它二元运算符, 例如: –,+,*,/ 友元函数 3.前几篇中的实例,现在汇总Person类的程序:

C++ Primer笔记4_类的静态成员_IO库

1.类的静态成员 static成员变量与函数 static成员变量:必须在类外初始化:(const或引用类型变量必须在构造函数初始化列表里初始化) static成员函数: 不依赖于类,相当于类里的全局函数(可以由该类对象调用,也可以 类名::函数名()的形式调用) 不包含this指针,不能声明为const,声明为const表示不会改变对象,而static成员函数存在于任何对象之外. 相当于把访问范围限制在所在的类中!  注意:不能访问类中非static成员变量以及非static成员函数. 注意:

C++ Primer笔记3_默认实参_类初探_名字查找与类的作用域

1.默认函数实参 在C++中,可以为参数指定默认值,C语言是不支持默认参数的,Java也不支持! 默认参数的语法与使用: (1)在函数声明或定义时,直接对参数赋值.这就是默认参数: (2)在函数调用时,省略部分或全部参数.这时可以用默认参数来代替. 注意事项: (1)函数默认值只能赋值一次,或者是在声明中,或者是在定义中,都可以. (2)默认参数定义的顺序为自右到左.即如果一个参数设定了缺省值时,其右边的参数都要有缺省值.比如int f(int a, int b=1,int c=2,int d=

C++ Primer笔记7_STL之关联容器

关联容器 与顺序容器不同,关联容器的元素是按关键字来访问和保存的.而顺序容器中的元素是按他们在容器中的位置来顺序保存的. 关联容器最常见的是map.set.multimap.multiset map的元素以键-值[key-value]对的形式组织:键用作元素在map中的索引,而值则表示所存储和读取的数据. set仅包含一个键,并有效的支持关于某个键是否存在的查询. pair类型 首先介绍下pair,pair定义在utility头文件中,一个pair保存两个数据成员,类似容器,pair是一个用来生