【C++ Primer Plus】学习笔记 3


前言

该笔记内容为书第四章——复合类型,加油加油


一、数组

数组(array)是一种数据格式,能够存储多个同类型的值。例如,数组可以存储60个int 类型的值,每个值都存储在一个独立的数组元素中,计算机在内存中依次存储数组的各个元素。
要创建数组,可使用声明语句。数组声明应指出以下三点:1.存储在每个元素中的值的类型;2.数组名;3.数组中的元素数。
在 C++中,可以通过修改简单变量的声明,添加中括号(其中包含元素数目)来完成数组声明。例如,下面的声明创建一个名为months的数组,该数组有12个元素,每个元素都可以存储一个 short 类型的值:

short months[12];//creates array of 12 short

在这里插入图片描述

1. 数组的初始化规则

C++有几条关于初始化数组的规则,它们限制了初始化的时刻,决定了数组的元素数目与初始化器中值的数目不相同时将发生的情况。
只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组:

int cards[4]={36810};// okay
int hand[4];// okay
hand[4]={5679};// not allowed
hand = cards;// not allowed

然而,可以使用下标分别给数组中的元素赋值。
初始化数组时,提供的值可以少于数组的元素数目。例如,下面的语句只初始化hotelTips的前两个元素:

float hotelTips[5]={5.02.5};

如果只对数组的一部分进行初始化,则编译器将把其他元素设置为0。因此,将数组中所有的元素都初始化为0非常简单——只要显式地将第一个元素初始化为0,然后让编译器将其他元素都初始化为0即可:

long totals[500]={0};

如果初始化为{1}而不是{0},则第一个元素被设置为1,其他元素都被设置为0。
如果初始化数组时方括号内([])为空,C++编译器将计算元素个数。例如,对于下面的声明:

short things[]={1538};

编译器将使things 数组包含4个元素。

2. C++11数组初始化方法

首先,初始化数组时,可省略等号(=):

double earnings[4] {1.2e4,1.6e4,1.1e4,1.7e4};// okay with C++11

其次,可不在大括号内包含任何东西,这将把所有元素都设置为零:

unsigned int counts[10]={};//all elements set to 0
float balances[100] {};//all elements set to 0

第三,列表初始化禁止缩窄转换:

long plifs[]={25923.0};//not allowed
char slifs[4]{'h','i',1122011,'\0'};// not allowed
char tlifs[4]{'h''i'112'\0'};//allowed

在上述代码中,第一条语句不能通过编译,因为将浮点数转换为整型是缩窄操作,即使浮点数的小数点后面为零。第二条语句也不能通过编译,因为1122011超出了char变量的取值范围(这里假设char变量的长度为8位)。第三条语句可通过编译,因为虽然112是一个int值,但它在char 变量的取值范围内。

二、字符串

字符串是存储在内存的连续字节中的一系列字符。C++处理字符串的方式有两种。第一种来自C语言,常被称为C-风格字符串(C-style string)。本章将首先介绍它,然后介绍另一种基于 string类库的方法。
存储在连续字节中的一系列字符意味着可以将字符串存储在char数组中,其中每个字符都位于自己的数组元素中。C-风格字符串具有一种特殊的性质:以空字符(null character)结尾,空字符被写作\0,其ASCII码为0,用来标记字符串的结尾。例如:

char dog[8]='b''e','a','u''x'' ''I''I'};// not a string!
char cat[8]={'f','a','t','e','s','s','a','\0'};
// a string!

这两个数组都是 char 数组,但只有第二个数组是字符串。空字符对 C-风格字符串而言至关重要
例如,C++有很多处理字符串的函数,其中包括cout 使用的那些函数。它们都逐个地处理字符串中的字符,直到到达空字符为止。如果使用 cout显示上面的 cat这样的字符串,则将显示前7个字符,发现空字符后停止。但是,如果使用cout 显示上面的 dog 数组(它不是字符串),cout将打印出数组中的8个字母,并接着将内存中随后的各个字节解释为要打印的字符,直到遇到空字符为止。由于空字符(实际上是被设置为0的字节)在内存中很常见,因此这一过程将很快停止。但尽管如此,还是不应将不是字符串的字符数组当作字符串来处理。
在cat 数组示例中,将数组初始化为字符串的工作看上去冗长乏味——使用大量单引号,且必须记住加上空字符。不必担心,有一种更好的、将字符数组初始化为字符串的方法——只需使用一个用引号括起的字符串即可,这种字符串被称为字符串常量(sting constant)或字符串字面值(string literal),如下所示:

char bird[11]="Mr. Cheeps";//the \0 is understood
char fish[]="Bubbles";//let the compiler count

用引号括起的字符串隐式地包括结尾的空字符,因此不用显式地包括它。
在这里插入图片描述
应确保数组足够大,能够存储字符串中所有字符,包括空字符。使用字符串常量初始化字符数组是这样的一种情况,即让编译器计算元素数目更为安全。让数组比字符串长没有什么害处,只是会浪费一些空间而已。这是因为处理字符串的函数根据空字符的位置,而不是数组长度来进行处理。C++对字符串长度没有限制。
在确定存储字符串所需的最短数组时,别忘了将结尾的空字符计算在内。

1.拼接字符串常量

C++允许拼接字符串字面值,即将两个用引号括起的字符串合并为一个。事实上,任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将自动拼接成一个。下面所有的输出语句都是等效的:

cout << "I'd give my right arm to be" " a great violinist.\n";
cout <<"I'd give my right arm to be a great violinist.\n";
cout <<"I'd give my right ar"
"m to be a great violinist.\n"

注意,拼接时不会在被连接的字符串之间添加空格,第二个字符串的第一个字符将紧跟在第一个字符串的最后一个字符(不考虑\0)后面。第一个字符串中的\0字符将被第二个字符串的第一个字符取代。

2.在数组中使用字符串

#include <iostream>
#include <cstring>// for the strlen()function
int main()
{
	using namespace std;
	const int Size =15;
	
	char name1[Size];// empty array
	char name2[Size]="C++owboy";//initialized 

	cout <<"Howdy!I'm"<< name2;
	cout <<"!What's your name?\n";
	cin >>name1;
	cout<<"Well,"<<name1<<",your name has ";
	cout << strlen(name1)<<" letters and is stored\n";
	cout << "in an array of"<< sizeof(name1)<< " bytes.\n";
	cout <<"Your initial is "<< name1[0]<< ".\n";
	name2[3]='\0';//set to null character
	cout <<"Here are the first 3 characters of my name: ";
	cout<<name2<<endl;
	return 0;
}

在这里插入图片描述根据上述代码,sizeof运算符指出整个数组的长度:15字节,但strlen()函数返回的是存储在数组中的字符串的长度,而不是数组本身的长度。另外,strlen()只计算可见的字符,而不把空字符计算在内。因此,对于 Basicman,返回的值为8,而不是9。如果 cosmic 是字符串,则要存储该字符串,数组的长度不能短于strlen(cosmic)+1。
由于 name1和name2是数组,所以可以用索引来访问数组中各个字符。例如,该程序使用name1[0]找到数组的第一个字符。另外,该程序将name2[3]设置为空字符。这使得字符串在第3个字符后即结束,即使数组中还有其他的字符。

3.字符串输入

之后例子将只展示代码部分,不含头文件

using namespace std;
const int ArSize=20;
char name[ArSize];
char dessert[ArSize];
cout <<"Enter your name:\n";
cin >> name;
cout << "Enter your favorite dessert:\n";
cin >> dessert;
cout<<"I have some delicious"<< dessert;
cout << " for you," << name << ",\n";
return 0;

在这里插入图片描述

cin 使用空白(空格、制表符和换行符)来确定字符串的结束位置,这意味着 cin 在获取字符数组输入时只读取一个单词。读取该单词后,cin 将该字符串放到数组中,并自动在结尾添加空字符。这个例子的实际结果是,cin 把 Alistair 作为第一个字符串,并将它放到 name 数组中。这把 Dreeb 留在输入队列中。当 cin 在输入队列中搜索用户喜欢的甜点时,它发现了Dreeb,因此cin 读取 Dreeb,并将它放到 dessert数组中。
在这里插入图片描述
除此之外,另一个问题是,输入字符串可能比目标数组长。像这个例子一样使用cin,确实不能防止将包含30个字符的字符串放到20个字符的数组中的情况发生。

4.每次读取一行字符串输入

每次读取一个单词通常不是最好的选择,istream中的类(如cin)提供了一些面向行的类成员函数:getline()和 get()。这两个函数都读取一行输入,直到到达换行符。然而,随后getline()将丢弃换行符,而 get()将换行符保留在输入序列中。下面详细介绍它们,首先介绍getline()。

4.1 面向行的输入:getline()

getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。要调用这种方法,可以使用 cin.getline()。该函数有两个参数。第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数。如果这个参数为20,则函数最多读取19个字符,余下的空间用于存储自动在结尾处添加的空字符。getline()成员函数在读取指定数目的字符或遇到换行符时停止读取。例如,假设要使用 getline()将姓名读入到一个包含 20个元素的name 数组中。可以使用这样的函数调用:

cin.getline(name,20);

这将把一行读入到 name 数组中——如果这行包含的字符不超过 19个。(getline()成员函数还可以接受第三个可选参数,这将在第17章讨论。)
在这里插入图片描述

4.2 面向行的输入:get()

istream类有另一个名为get()的成员函数,该函数有几种变体。其中一种变体的工作方式与 getline()类似,它们接受的参数相同,解释参数的方式也相同,并且都读取到行尾。但 get不再读取并丢弃换行符,而是将其留在输入队列中。假设我们连续两次调用get():

cin.get(name,ArSize);
cin.get(dessert,Arsize)://a problem

由于第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符便是换行符。因此get()认为已到达行尾,而没有发现任何可读取的内容。如果不借助于帮助,get()将不能跨过该换行符。
幸运的是,get()有另一种变体。使用不带任何参数的 cin.get()调用可读取下一个字符(即使是换行符),因此可以用它来处理换行符,为读取下一行输入做好准备。也就是说,可以采用下面的调用序列:

cin.get(name,ArSize);// read first line
cin.get();//read newline
cin.get(dessert,Arsize);//read second line

另一种使用 get()的方式是将两个类成员函数拼接起来(合并),如下所示:

cin.get(name,ArSize).get();//concatenate member functions

之所以可以这样做,是由于 cin.get(name,ArSize)返回一个 cin 对象,该对象随后将被用来调用 get()函数。同样,下面的语句将把输入中连续的两行分别读入到数组name1和name2中,其效果与两次调用cin.getline()相同:

cin.getline(name1,ArSize).getline(name2,ArSize);

5.混合输入字符串和数字

using namespace std;
cout << "What year was your house built?\n";
int year;
cin >> year;
cout <<"What is its street address?\n";
char address[80];
cin.getline(address,80);
cout<<"Year built:"<<year << endl;
cout <<"Address:"<< address << endl;
cout << "Done!\n";
return 0;

在这里插入图片描述
会发现用户根本没有输入地址的机会。问题在于,当cin 读取年份,将回车键生成的换行符留在了输入队列中。后面的 cin.getline()看到换行符后,将认为是一个空行,并将一个空字符串赋给 address 数组。解决之道是,在读取地址之前先读取并丢弃换行符。这可以通过几种方法来完成,其中包括使用没有参数的get()和使用接受一个 char 参数的 get(),如前面的例子所示。可以单独进行调用:

cin >>year;
cin.get();// or cin.get(ch);

也可以利用表达式 cin>>year 返回 cin 对象,将调用拼接起来:

(cin>>year).get();//or(cin>>year).get(ch);

按上述任何一种方法修改代码后,它便可以正常工作。
C++程序常使用指针(而不是数组)来处理字符串。我们将在介绍指针后,再介绍字符串这个方面的特性。下面介绍一种较新的处理字符串的方式:C++string类。

三、String类

要使用 string类,必须在程序中包含头文件string。string类位于名称空间 std 中,因此必须提供一条using 编译指令,或者使用 std::string来引用它。string类定义隐藏了字符串的数组性质,能够像处理普通变量那样处理字符串。
在很多方面,使用string对象的方式与使用字符数组相同。

  • 可以使用 C-风格字符串来初始化 string 对象。
  • 可以使用 cin 来将键盘输入存储到 string 对象中。
  • 可以使用 cout 来显示 string 对象。
  • 可以使用数组表示法来访问存储在 string对象中的字符。

string对象和字符数组之间的主要区别是,可以将string对象声明为简单变量,而不是数组:

string str2="panther";//create an initialized string
string str1;//create an empty string object

类设计让程序能够自动处理string的大小。例如,str1的声明创建一个长度为0的string对象,但程序将输入读取到 str1中时,将自动调整 str1的长度:

cin >> str1;//str1 resized to fit input

这使得与使用数组相比,使用string对象更方便,也更安全。从理论上说,可以将char数组视为一组用于存储一个字符串的 char 存储单元,而 string 类变量是一个表示字符串的实体。

1.C++11字符串初始化

C++11也允许将列表初始化用于C-风格字符串和 string 对象:

char first_date[]={"Le Chapon Dodu"};
char second_date[] {"The Elegant Plate"};
string third_date={"The Bread Bowl"};
string fourth_date {"Hank's Fine Eats"};

2. 赋值、拼接和附加

使用 string 类时,某些操作比使用数组时更简单。例如,不能将一个数组赋给另一个数组,但可以将一个string 对象赋给另一个 string 对象:

char charr1[20];//create an empty array
char charr2[20]="jaguar";//create an initialized array
string str1;//create an empty string object
string str2="panther";//create an initialized string
charr1 = charr2;//INVALID,no array assignment
str1 =str2;//VALID,object assignment ok

string 类简化了字符串合并操作。可以使用运算符+将两个 string对象合并起来,还可以使用运算符+=将字符串附加到 string 对象的末尾。继续前面的代码,您可以这样做:

string str3;
str3 =str1 +str2;//assign str3 the joined strings
str1 += str2;// add str2 to the end of str1

3. string 类的其他操作

在 C++新增string 类之前,程序员也需要完成诸如给字符串赋值等工作。对于C-风格字符串,程序员使用C语言库中的函数来完成这些任务。头文件cstring(以前为string.h)提供了这些函数。例如,可以使用函数strcpy()将字符串复制到字符数组中,使用函数strcat()将字符串附加到字符数组末尾:

strcpy(charr1,charr2);// copy charr2 to charr1
strcat(charr1,charr2);//append contents of charr2 to char1

处理 string 对象的语法通常比使用C字符串函数简单,尤其是执行较为复杂的操作时。例如,对于下述操作:

str3 =str1 +str2;

使用 C-风格字符串时,需要使用的函数如下:

strcpy(charr3,charr1);
strcat(charr3,charr2);

另外,使用字符数组时,总是存在目标数组过小,无法存储指定信息的危险,如下面的示例所示:

char site[10]="house";
strcat(site,"of pancakes");//memory problem

函数 strcat()试图将全部 12个字符复制到数组 site 中,这将覆盖相邻的内存。这可能导致程序终止,或者程序继续运行,但数据被损坏。string类具有自动调整大小的功能,从而能够避免这种问题发生。C函数库确实提供了与 strcat()和 strcpy()类似的函数——stmcat()和 stmcpy(),它们接受指出目标数组最大允许长度的第三个参数,因此更为安全,但使用它们进一步增加了编写程序的复杂度。
下面是两种确定字符串中字符数的方法:

int len1= str1.size();// obtain length of str1
int len2= strlen(charr1);//obtain length of charr1

函数 strlen()是一个常规函数,它接受一个C风格字符串作为参数,并返回该字符串包含的字符数。函数 size()的功能基本上与此相同,但句法不同:str1不是被用作函数参数,而是位于函数名之前,它们之间用句点连接。与第3章介绍的put()方法相同,这种句法表明,str1是一个对象,而 size()是一个类方法。方法是一个函数,只能通过其所属类的对象进行调用。在这里,str1是一个string对象,而size()是string 类的一个方法。总之,C 函数使用参数来指出要使用哪个字符串,而 C++ string 类对象使用对象名和句点运算符来指出要使用哪个字符串。

4. string类I/O

可以使用 cin 和运算符<<来将输入存储到 string 对象中,使用 cout 和运算符<<来显示string 对象,其句法与处理 C-风格字符串相同。但每次读取一行而不是一个单词时,使用的句法不同。
下面是将一行输入读取到数组中的代码:

cin.getline(charr,20);

这种句点表示法表明,函数 getline()是istream 类的一个类方法(还记得吗,cin是一个istream 对象)。如前面指出的,第一个参数是目标数组;第二个参数是数组长度,getline()使用它来避免超越数组的边界。
下面是将一行输入读取到 string 对象中的代码:

getline(cin,str);

这里没有使用句点表示法,这表明这个getline()不是类方法。它将cin作为参数,指出到哪里去查找输入。另外,也没有指出字符串长度的参数,因为string对象将根据字符串的长度自动调整自己的大小。

那么,为何一个 getline()是istream 的类方法,而另一个不是呢? 在引入 string 类之前很久,C++就有istream 类。因此 istream 的设计考虑到了诸如 double 和 int 等基本 C++数据类型,但没有考虑 string 类型,所以 istream 类中,有处理 double、int 和其他基本类型的类方法,但没有处理 string对象的类方法。由于 istream 类中没有处理 string 对象的类方法,因此您可能会问,下述代码为何可行呢?
cin >>str;//read a word into the str string object
像下面这样的代码使用 istream 类的一个成员函数:
cin >>x;//read a value into a basic C++ type
但前面处理 string 对象的代码使用 string 类的一个友元函数。有关友元函数及这种技术为何可行,将在第11章介绍。另外,可以将cin和cout用于string对象,而不用考虑其内部工作原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值