string对象
1.初始化字符串
string word1="Game";/*赋值运算符;*/
string word2("Over");//转换构造函数;
string word3(3,'!');//生成由提供的字符组成,且长度等于提供的数的的string对象;
//提供的字符只能只有一个。如果超过一个,将使用最后一个字符。如:string word3(3,'ah');//word3=”hhh”
2.连接字符串
string phrase=word1+" "+word2+word3;//连接;
//之所有可以使用+运算符,是因为string对象重载了+运算符
3.字符串长度
成员函数size()、length()返回string对象的字符个数;
cout<<"The phrase has "<<phrase.size()<<" characters in it.";//string对象的字符个数;
cout<<"The phrase has "<<phrase.length()<<" characters in it.\n\n";//string对象的字符个数;
4.索引字符串
string对象存储一个char型值的序列。给对象提供下标运算符([])和索引号就可以访问
其中的任意一个char型值。
cout<<"The characters at the position 0 is:"<<phrase[0]<<"\n\n";//索引,char型值的序列;
cout<<"Changing the character at the postion 0.\n";
phrase[0]='L';
ps:当使用string 对象和下标运算符时,C++编译器不执行边界检查。这意味着编译器不检查程序是否试图访问不存在的元素。
访问非法的序列元素可能导致灾难性的后果,因为这可能覆盖掉计算机内存中的关键数据。这样可能导致程序崩溃,所以在使用下标运算符访问数据时要小心。
5.检查匹配
成员函数find()来检查一个字符串字面值是否包含在另一个字符串字面值中。该成员函数的返回值是要搜索的string对象在调用string对象中第一次出现的位置。
cout<<phrase.find("Over")<<endl;//检查匹配;注意:单引号 与 双引号的区别
另外,find()提供一个可选实参,用于指定查找子字符串的起始位置。
location = phrase.find("eggplant", 5);//将从string对象phrase的位置5开始查找字符串字面值"eggplant"
如果要找的字符串不存在呢?那么,find()将会返回string类中定义的一个特殊常量,该常量通过string::npos来访问。
通过string::npos访问的常量表示string对象可能的最大长度。因此,它比对象中的任意可能的合法位置都要大
。通俗地讲,它表示“一个不可能存在的位置”。这是说明无法找到子字符串的绝对返回值。
if (phrase.find("eggplant",5)==string::npos) {//不存在匹配;
cout<<"'eggplant' is not in the phrase.\n\n";
}
6.移除指定的子字符串
erase()成员函数从string对象中移除指定的子字符串。
调用方法一:指定子字符串的起始位置和长度:
phrase.erase(4,5);//删除从起始位置(包含起始位置)开始的长度为n的子字符串;
cout<<"The phrase is now:"<<phrase<<endl;
调用方法二:只提供子字符串的起始位置。这种方法将把从指定的起始位置开始到string对象结尾的全部字符都删除。
phrase.erase(4);//删除从起始位置(包含起始位置)开始到末尾的字符串;
调用方法三:不提供实参:
phrase.erase();//删除所有的字符;
7.判断string对象是否为空
empty()成员函数返回bool型值,如果string对象为空,则返回true,否则返回false。
if (phrase.empty()) {//empty()成员函数返回bool型值,如果string对象为空,则返回true,否则返回false
cout<<"\nThe phrase is no more.\n";
}
数组
1.创建数组
声明数组的方法和声明 和已见过的变量的方式非常类似:提供一个类型和一个名称。编译器必须知道数组的大小,这样才能预留出需要的内存空间。数组大小的信息可以用方括号括起来,然后置于数组名之后。
const int MAX_ITEMS=10;
string inventory[MAX_ITEMS];
ps:声明数组的时候,可以通过提供一个初始值列表来对数组进行初始化。初始值列表是用花括号括起来的元素序列,其中元素用逗号隔开。如下例所示:
string inventory[MAX_ITEMS] = {"sword", "armor", "shield"};
这段代码声明了大小为MAX_ITEMS的string对象数组inventory。数组的前3个元素初始化为"sword"、"armor"和"shield"。
如果在使用初始值列表的时候忽略元素个数,那么创建的数组大小就等于列表中元素的个数。下面给出一个例子:
string inventory[] = {"sword", "armor", "shield"};
因为初始值列表中有3个元素,因此这行代码创建了一个大小为3的数组inventory,其中元素是"sword"、"armor"和"shield"。
2.数组的索引
索引数组的方式和索引string对象非常类似。可以使用索引号和下标运算符([])来访问任意单个元素。
int numItems=0;
inventory[numItems++]="sword";
inventory[numItems++]="armor";
inventory[numItems++]="shield";
cout<<"Your items:\n";
for (int i=0; i<numItems; i++) {
cout<<inventory[i]<<endl;
}
cout<<"\nYou trade your sword for a battle axe.";
inventory[0]="battle axe";
cout<<"\nYour items:\n";
for (int i=0; i<numItems; i++) {
cout<<inventory[i]<<endl;
}
3.使用数组元素的成员函数
使用数组元素成员函数的方法是写出数组元素,在后面跟上成员选择运算符和成员函数名称.
cout<<inventory[0].size()<<" letters in it.\n";
4.数组边界
if (numItems<MAX_ITEMS) {
inventory[numItems++]="healing postion";
} else {
cout<<"You have too many items and can't carry another.";
}
那么,如果使用数组边界以外的元素会怎样?这要视情况而定,因为这是在使用计算机内存中未知的部分。最坏的情况是,如果试图给数组边界外的元素赋值,将导致程序行为不可预测,甚至程序崩溃。
可以在使用索引号之前对其进行测试,以确保它是合法的数组位置。这种做法叫做边界检查。如果要使用的索引可能不合法,那么边界检查是必不可少的。
5.理解C风格字符串
有string对象之前,C++程序员使用以空字符结尾的字符数组表示字符串。这些字符数组现在称为C风格字符串,因为这种表示字符串的习惯是从C程序开始的。
声明和初始化C风格字符串的方法和其他数组一样:
char phrase[] = "Game Over!!!";
C风格字符串以一个称为空字符的字符结尾。空字符可以写成'\0'。上面的代码不需要使用空字符,因为它已经存储在字符串的结尾处。
所以,从技术上而言,phrase有13个元素(然而,使用C风格字符串的函数则认为phrase的长度为12,这是合理的,并且与string
对象的工作原理一致)。
至于其他任意类型的数组,可以在定义时指定数组大小。因此,声明和初始化C风格字符串的另一种方式是:
char phrase[81] = "Game Over!!!";
这行代码创建了一个可以容纳80个可打印字符的C风格字符串(另外还有一个终止空字符)。
C风格字符串没有成员函数,但是作为标准库一部分的cstring文件中包含了各种使用C风格字符串的函数。
string 对象的优点在于,它们被设计为可以和C风格字符串很好地结合使用。例如,
下面给出的都是C风格字符串和string对象的合法用法:
string word1 = "Game";
char word2[] = " Over";
string phrase = word1 + word2;
if (word1 != word2) {
cout << "word1 and word2 are not equal.\n";
}
if (phrase.find(word2) != string::npos) {
cout << "word2 is contained in phrase.\n";
}
string对象可以和C风格字符串连接起来,但结果仍然是一个string对象(所以char phrase2[]
= word1 + word2;会产生错误)。可以使用关系运算符比较string对象和C风格字符串,甚至还可以将C风格字符串用作string对象成员函数的实参。
C风格字符串和数组有共同的缺点,其中最大的一个是它们的长度是固定的。因此,应当遵循的原则是:只要可能就使用string对象,但是如果有必要的话,需做好使用C风格字符串的准备。
6.创建多维数组
const int ROWS=3;
const int COLUMNS=3;
char board[ROWS][COLUMNS]= {{'O','X','O'},{' ','X','X'},{'X','O','O'}};
可以只声明而不初始化多维数组。如下例所示:
char chessBoard[8][8];
7.多维数组的索引
cout<<"Here's the tic-tac-toe board:\n";
for (int i=0; i<ROWS; i++) {
for (int j=0; j<COLUMNS; j++) {
cout<<board[i][j];
}
cout<<endl;
}
为数组的每一维提供一个值可以索引多维数组中的单个元素。
小结:
● for循环可以重复执行代码段。在for循环中,可以提供初始化语句、测试表达式和在每次循环迭代后执行的动作语句。
● for循环经常用于对序列进行计数或遍历序列。
● 对象是组合了数据(称为数据成员)和函数(称为成员函数)的经过封装的聚合体。
● string对象定义在文件string中,是标准库的一部分。string对象用于存储字符序列,并且有成员函数。
● string的定义方式使它可以直观地与已知的一些运算符一起使用,如连接运算符和关系运算符。
● 所有string对象都有成员函数。这些成员函数可以获取string对象的长度、检查字符串是否为空、查找子字符串以及移除子字符串。
● 数组提供了存储和访问任意类型序列的方法。
● 数组的局限在于它们的长度是固定的。
● 使用下标运算符可以访问string对象和数组中的单个元素。
● 在试图访问string对象或数组中的某个元素时,边界检查没有被强制实现。因此,边界检查要由程序员完成。
● C风格字符串是以空字符结尾的字符数组,而且是C语言中表示字符串的标准方法。尽管在C++中使用C风格字符串是完全合法的,但使用string对象操作字符序列的方式更为可取。
● 多维数组可以用多个下标来访问数组元素。例如,棋盘可以表示成包含8×8个元素的二维数组。
问与答
问:不需要包含头文件就可以使用int 或char类型,那么使用字符串时为什么要包含string文件?
答:int和char是内置类型,它们在C++程序中总是可用的。另一方面,string类型不是内置类型,它作为标准库的一部分定义在文件string中。
问:为什么应当使用string对象而不是C风格字符串?
答:string 对象对比C风格字符串有其优势。最明显的一点在于,它的大小可动态调整。使用string时不用指定长度限制。
问:应当使用length()成员函数还是size()成员函数来获取string对象中字符的数目?
答:length()和size()返回的值相同,都可以使用。
问:什么是判定函数?
答:判定函数是指返回true或false的函数。string对象的成员函数empty()就是一个判定函数。
问:如果试图给数组边界外的元素赋值会怎样?
答:C++允许这样的赋值。然而,结果会不可预测,而且可能导致程序崩溃。因为这样更改了计算机内存中某些未知部分。
标准模板库
标准模板库(Standard Template Library,STL)代表一个强大的、已经被很好地完成的编程任务的集合。它提供了一组容器、算法和迭代器等。
那么,什么是容器?容器可以用于存储和访问同一类型值的集合。数组也能做到这一点,但是与简单但忠实的数组比起来,STL容器更灵活且更强大。STL定义了各种容器类型,每种容器的工作原理不同,可以满足不同的需求。STL中定义的算法和容器一起使用。
算法是游戏程序员在处理一组组数据时经常重复使用的函数,包括排序、查找、复制、合并、插入以及移除容器元素。
算法的妙处在于,同一个算法可以用于处理多种不同的容器类型。
迭代器是标识容器中不同元素的对象,能够用来在元素间移动。它对于循环访问容器非常有用。另外,STL算法需要使用迭代器。看到某个容器类型的具体实现后,以上内容将显得更有意义。
1.使用vector
vector类定义了STL提供的一种容器。它满足动态数组(大小根据需要增长和缩小的数组)的一般性描述。另外,vector还定义了用于操作向量(vector)元素的成员函数。也就是说,向量实现了比数组的全部还多的功能。
对比数组,向量有其优势,包括以下几点:
● 向量可以根据需要增长,而数组不能。这意味着如果在游戏中使用一个向量存储敌人的对象,它的大小可以增长以适应创建的敌人的数目。如果使用数组,就必须创建一个能存储最大数目敌人的数组。如果在游戏过程中,数组所需空间比预想的要
大,那就非常不妙了。
● 向量可以和STL算法一起使用,但数组不能。这意味着使用向量就获得了如查找和排序这样复杂的内置功能。如果使用数组,必须自行编写实现这些功能的代码。
对比数组,向量还是有一些缺点,包括以下几点:
● 向量需要一些额外的内存开销。
● 向量大小增长时可能会带来性能上的损失。
● 在某些游戏控制台系统下可能无法使用向量。
综上所述,向量(还有STL)在大多数项目中是很受欢迎的工具。
2.使用向量的准备工作
在声明一个向量之前,必须将含有其定义的头文件包含进来:
#include <vector>
STL中的所有组件都属于std名称空间。
3.向量的声明
要声明自己的向量,在vector后面加上需要使用的对象类型(用<和>括起来),然后加上向量的名称。
vector<string> inventory;
声明向量还有其他方法。可以声明有初始大小的向量,方法是在向量名后面的括号中指定一个值。
vector<string> inventory(10);
上面一行代码声明了一个存储string对象元素且初始大小为10的向量。
还可以在声明向量时用相同的值初始化向量的所有元素。只需要在元素数目之后再提供一个初始值,如下所示:
vector<string> inventory(10, "nothing");
上面一行代码声明了一个大小为10的向量,且全部10个元素都初始化为"nothing"。
最后,还可以用另一个向量的内容声明和初始化一个向量。
vector<string> inventory(myStuff);
上面一行代码创建了一个新的向量,其内容和向量myStuff相同。
4.使用push_back()成员函数
push_back()成员函数在向量的最后添加一个新的元素。
inventory.push_back("sword");
inventory.push_back("armor");
inventory.push_back("shield");
5.使用size()成员函数
size()成员函数仅仅返回向量的大小。
cout<<"You have "<<inventory.size()<<" items.\n";
6.向量的索引
正如数组一样,可以通过下标运算符对向量进行索引。注意,循环变量i 是unsigned int型,因为size()的返回值是无符号整型。
cout<<"Your items:\n";
for (unsigned int i=0; i<inventory.size(); i++) {
cout<<inventory[i]<<endl;
}
PS:尽管向量是动态的,但不可以使用下标运算符增加向量的大小。例如,下面的代码非常危险,并且无法增加向量inventory的大小:
vector<string> inventory; //creating an empty vector
inventory[0] = "sword"; //may cause your program to crash!
正如数组一样,可以尝试访问不存在的元素位置,但这有潜在的灾难性后果。上面的代码更改了计算机内存中的某个未知部分,可能导致程序崩溃。
如果要在向量的最后添加新元素,请使用push_back()成员函数。
7.调用元素的成员函数
cout << inventory[0].size() << " letters in it.\n";
正如数组一样,可以通过在向量元素后面加上成员选择运算符和成员函数名来访问其成员函数。
8.使用pop_back()成员函数
pop_back()成员函数移除向量的最后一个元素,并且将其大小减1。
inventory.pop_back();
9.使用clear()成员函数
clear()成员函数移除了向量的全部元素,并将其大小设置为0。
inventory.clear();
10.使用empty()成员函数
vector的成员函数empty()的作用和string的成员函数empty()一样。如果vector对象为空,则返回true;否则返回false。
if (inventory.empty()) {
cout << "\nYou have nothing.\n";
} else {
cout << "\nYou have at least one item.\n";
}
使用迭代器
迭代器是将容器的潜力发挥到极致的关键。迭代器可以用于循环访问序列容器。另外,STL的某些重要部分需要用到迭代器。许多容器的成员函数和STL算法将迭代器作为其实参。因此,如果希望从成员函数和算法中获益,就必须使用迭代器。
1.迭代器的声明
vector<string>::iterator myIterator;
这行代码为包含string对象的向量声明了一个名为myIterator的迭代器。如果要声明自己的迭代器,按照下面的模式操作:先写下容器类型,接着是容器包含的对象的类型(用<和>括起来),然后是作用域解析运算符(符号::),最后是iterator和新迭代器的名称。
那么何谓迭代器?迭代器是标识容器中某个特定元素的值。给定一个迭代器,可以访问元素的值;给定正确类型的迭代器,就可以修改其值。迭代器还可以通过常见的算术运算符在元素之间移动。
可以将迭代器想象成贴在容器中某个特定元素上的便签。迭代器虽然不是元素本身,但它是引用元素的一种方式。具体而言,我们可以使用myIterator引用向量inventory中的
特定元素。即可将myIterator便签贴在向量inventory中的特定元素上。一旦贴上以后,就能够通过该迭代器访问甚至修改相应元素。
vector<string>::const_iterator iter;
上面的代码为一个包含string对象的向量创建了名为iter的常量迭代器。除了不能用来修改其引用的元素以外,常量迭代器与常规迭代器几乎一样。由常量迭代器引用的元素必须保持不变。可以将常量迭代器想象成提供了只读访问权限。然而,迭代器自身可以改变。
即如果需要,可以让iter在向量inventory之中移动。然而,无法通过iter修改任何元素的值。使用常量迭代器,便签的位置可以改变,但是被便签标记的元素不能改变。
如果常量迭代器是带限制的常规迭代器,为何还要使用它们?首先,这使程序的意图比较清晰。在使用常量迭代器时,很显然不需要修改它引用的元素。其次,这样更加安全。
使用常量迭代器能够避免容器元素的意外修改(如果试图通过常量迭代器修改元素,编译器将会报错)。
PS:使用push_back()可能使引用向量的所有迭代器无效。
2.循环访问向量
cout<<"Your items:\n";
for (iter=inventory.begin(); iter!=inventory.end(); iter++) {
cout<<*iter<<endl;
}
2.1. 调用向量成员函数begin()
循环的初始化语句将inventory.begin()的返回值赋给iter。成员函数begin()返回的迭代器引用容器中的第一个元素。
所以在本例中,该语句将引用inventory 第一个元素(等于"sword"的string对象)的迭代器赋值给iter。
2.2. 调用向量成员函数end()
此循环的测试表达式比较了inventory.end()的返回值和iter,以确保两者不相等。end()成员函数返回容器中最后一个元素之后的一个迭代器。即循环将继续下去,直到iter 经过了inventory中的所有元素。
vector的成员函数end()返回的迭代器指向向量中最后一个元素之后—— 而不是最后一个元素。因此,无法从end()返回的迭代器获得元素值。这可能看起来有悖直观,但是能很好地用于遍历容器的循环之中。
2.3. 迭代器的更新
循环中的行为表达式++iter对iter进行递增操作,即将它从向量中的一个元素移动到下一个元素。视迭代器而定,还可以对它进行其他数学运算来使其在容器中移动。然而在大多数情况下,您会发现只需要递增操作。
2.4. 迭代器的解引用
程序在循环体中将*iter发送给cout。将解引用运算符* 置于iter之前,这样就可以显示该迭代器引用的元素(不是迭代器自身)的值。这样做相当于在告诉程序:“将它当做迭代器引用的内容来对待,而不是迭代器自身。”
3. 修改向量元素的值
首先,程序设置myIterator,使其引用inventory的第一个元素。
myIterator = inventory.begin();
然后修改第一个元素的值。
*myIterator = "battle axe";
上面的赋值语句通过*对myIterator解引用,意思是:“将"battle axe"赋值给myIterator引用的元素。”赋值语句不会修改myIterator。该语句执行之后,myIterator仍然引用向量的第一个元素。
4.访问向量元素的成员函数
cout << "\nThe item name '" << *myIterator << "' has ";
cout << (*myIterator).size() << " letters in it.\n";
代码(*myIterator).size()的意思是:“调用myIterator解引用后所得对象的成员函数size()。”因为myIterator引用的string对象等于"battle axe",所以代码返回值10。
PS:无论何时要通过对迭代器解引用来访问数据成员或成员函数,请用一对圆括号将解引用后的迭代器括起来,这样可以确保点运算符应用到迭代器引用的对象。
代码(*myIterator).size()不是最优雅的写法,因此C++提供了一种可选的、更直观的方式完成相同的任务,如下面两行代码所示。
cout << "\nThe item name '" << *myIterator << "' has ";
cout << myIterator->size() << " letters in it.\n";
一般而言,可以使用->运算符访问迭代器引用对象的成员函数或数据成员。
语法糖是一种更好的、可选的语法,它用易于理解的语法来代替晦涩的语法。例如,不将代码写作(*myIterator).size(),而是使用->运算符提供的语法糖,将代码写作myIterator->size()。
5.使用向量的成员函数insert()
inventory.insert(inventory.begin(), "crossbow");
有一种形式的insert()成员函数将新元素插入至向量中给定迭代器引用的元素之前。此种形式的insert()需要两个实参:第一个为一个迭代器,第二个为需要插入的元素。
在本例中,程序将"crossbow"插入至inventory 中第一个元素之前。
PS:对向量调用insert()成员函数会使所有引用了插入点之后的元素的迭代器失效,因为所有插入点之后的元素都下移了一位。
6.使用向量的成员函数erase()
inventory.erase((inventory.begin() + 2));
有一种形式的erase()成员函数可以从向量中移除一个元素。该形式的erase()接受一个实参:引用需要移除元素的迭代器。本例中,传递的实参(inventory.begin() + 2)等于引用
inventory中第三个元素的迭代器。于是程序移除了等于"armor"的string对象。因此,所有随后的元素都上移一位。该形式的erase()成员函数返回一个迭代器,它引用移除的元素之后的那个元素。
PS:对向量调用erase()成员函数会使所有引用了移除点之后的元素的迭代器失效,因为所有移除点之后的元素都上移了一位。
http://www.cnblogs.com/SelaSelah/archive/2012/10/26/2740557.html
http://greatverve.cnblogs.com/archive/2012/10/24/cpp-int-string.html