数组
数组是一种数据形式,能够储存多个同类型的值。由于数组的存在,我们就可以避免记录数据时创建大量的变量了,例如需要记录一个月中每天的花销,可以不用创建30个变量,而是使用一个长度为30的数据进行记录。
声明数组的通用格式如下:
typeName arrayName[arraySize];
其中arraySize需要在编译之前就必须明确,因此它必须是整型常量或const值,或者常量表达式。这其实是数组的一种局限,因为很多情况下在创建时并不清楚需要多大的数组,可以通过new运算符来避免这种限制。
数组使用下标或索引来对元素进行编号,就像按次序排名一样,只不过第一位的下标是0而不是1。
int arr[3]; //声明了一个长度为3的整型数组
上面已经创建好了一个数组,通过arr[0], arr[1], arr[2]依次可以访问到数组的第一、二、三位元素。
数组初始化
int main()
{
int arr[5]; //声明了一个整型数组,长度为10
cout << arr[0] << endl;
return 0;
}
执行上述代码,程序并不会报错,这是因为在内存中并没有清空这一块内存,程序会将这块内存中的混乱的数据给输出,因此执行上述代码时会输出很奇怪的数,图片中就是arr数组在内存中的数据,还没初始化,奇奇怪怪。
因此最好在创建时对数组进行初始化,不然很容易在使用中产生奇怪的结果。
int main()
{
int arr1[3] = {1, 2, 3} //如果一开始就知道数据的值就可以通过这种方式直接赋初值
int arr2[3];
int arr2[0] = 1;
int arr2[1] = 2;
int arr2[2] = 3;
//也可以索引到每个元素进行赋初值,但是在数组大的时候非常麻烦
//当不确定数组的初值时可以全部赋0,有一种比较快捷的赋0的方法
int arr3[100] = { 0 };
//这样就能给arr3中的所有元素都赋0,这种操作快捷方便,就算数组很大也不怕啦!
}
最后一种方法实际上是对部分元素赋初值后,编译器会自动将剩下的元素进行赋0操作,那么在创建数组时,就可以把已知的数据先填上,未知的数据就交给编译器设置为0啦。
当然要注意的时,初始化需要在声明数组时就完成,在声明完数组后再进行初始化就会报错,例如:
int main()
{
int arr[5];
arr[5] = {1, 2, 3, 4, 5};
cout << arr[0];
return 0;
}
会出现报错:
error: cannot convert '<brace-enclosed initializer list>' to 'int' in assignment
如果初始化数组时,方括号内为空,例如:
int arr[] = {1, 2, 3, 4, 5};
编译器会自动计算arr中的元素个数。
C++11之后的数组初始化方法
从C++11开始,数组初始化新增了一些功能。
初始化时可以省略等于号:
int arr1[5] = {0}; //被允许
int arr2[5] {0}; //同样被允许
可不在大括号内包含任何东西,这将把所有元素都设置为0:
int arr3 = {}; //所有元素都被设置为0
int arr4 {}; //所有元素都被设置为0
列表初始化禁止缩窄转换,关于缩窄转换的规则如下:
- 从浮点数转换为整数
- 从取值范围大的浮点数转换为取值范围小的浮点数(在编译期可以计算并且不会溢出的表达式除外)
- 从整数转换为浮点数(在编译期可以计算并且转换之后值不变的表达式除外)
- 从取值范围大的整数转换为取值范围小的整数(在编译期可以计算并且不会溢出的表达式除外)
因此下面几种初始化方式都不被允许:
int arr5[] {1.0, 2.0, 3};
float arr6 {1.0, 2.0, 3.0L}; //3.0L表示long double类型下的3.0
double arr7[] {1, 2, 3.0f};
char arr8[] {'h', 'e', 'l', 'l', 'o', 1122011};
对于最后一种,因为char也是一种整型,但1122011超过了char变量的取值范围,因此也不被允许。
字符串
字符串时储存在内存中的连续字节中的一系列字符。C++处理字符串的方式有两种,一种是C风格字符串(C-style string),一种是基于string类库的方法。
C风格字符串(C-style string)
字符串可以被char类型数组进行储存,其中每个字符都位于自己的数组元素中。C风格字符串具有一种特殊的性质:以空字符结尾,空字符被写作\0,其ASCII码为0,用来标记字符串的结尾。
char ch1[6] {'h', 'e', 'l', 'l', 'o', '!'}; //不是字符串,仅是cahr数组
char ch2[6] {'h', 'e', 'l', 'l', 'o', '\0'}; //是字符串
通过数组初始化字符串看起来非常麻烦,因为它需要大量单引号,C++支持比较方便的初始化方法:
char ch1[6] = "hello"; //被允许
char ch2[] = "hello"; //被允许
char ch3[] "hello"; //不被允许,等于号不可以省略
结尾的空字符被隐式地包括了,因此不用显示地声明,但是在确定arraySize时必须给它留一个位置。
拼接字符串常量
有时候字符串非常长,C++允许拼接字符串字面值,即将两个用双引号包含的字符串拼接起来,拼接时,前一个字符串的最后一个空白符的位置将被后一个字符串的第一个字符取代。
cout << "hello world!\\n";
cout << "hello " "world!\n";
cout << "hello wo"
"rld!\n";
这三种方式都会成功输出hello world!
字符串输入
int main()
{
const int arraySize = 20;
char name[arraySize];
char dessert[arraySize];
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;
}
执行上面的代码会得到以下结果:
Enter your name:
Eric
Enter your favorite dessert:
cake
I have some delicious cake for you, Eric.
但是名字如果为Alistair Dreeb,就会出现这样的结果:
Enter your name:
Alistair Dreeb
Enter your favorite dessert:
I have some delicious Dreeb for you, Alistair.
我们并没有输入甜点的名称,程序就自动将其显示出来了,这是因为cin是通过空格或者字符串来判断是否已经完成字符串输入。就这个例子而言,cin将Alistair作为第一个字符串放到name数组中,将Dreeb作为第二个字符串放到dessert数组中。
每次读取一行的输入
按照空格读取,每次读取一个单词显然并不是一个好的选择,因此我们需要每次读取一行的方法,cin提供了这样的成员函数:getline()和get()。
getline()与get()函数都需要向其传递两个参数:数组名称以及数组大小,格式如下:
cin.getline(arrayName, arraySize);
cin.get(arrayName, arraySize);
但是在使用中,这两个函数却有一点点区别。使用getline()重写上面的代码:
int main()
{
const int arraySize = 20;
char name[arraySize];
char dessert[arraySize];
cout << "Enter your name:\n";
cin.getline(name, arraySize);
cout << "Enter your favorite dessert:\n";
cin.getline(dessert, arraySize);
cout << "I have some delicious " << dessert;
cout << " for you, " << name <<".\n";
return 0;
}
结果如下:
Enter your name:
Alistair Dreeb
Enter your favorite dessert:
Radish Torte
I have some delicious Radish Torte for you, Alistair Dreeb.
可以看到getline()函数读取了完整的姓名和甜点,并进行了正确地显示。用户在键盘中输入名称并按下回车(Enter),getline()会读取名称并将回车键生成的换行符替换为空白符。
但get()并不会替换这个换行符,它会将换行符留在输入队列中,如果将上面的代码用get()实现,那么在输入甜点时,get()会读取到输入队列中的换行符,并判断这次输入结束,导致dessert这个数组什么也没有获取到,因此需要在两次输入之间加上***cin.get()***来获取第一次输入后留在队列中的换行符
int main()
{
const int arraySize = 20;
char name[arraySize];
char dessert[arraySize];
cout << "Enter your name:\n";
cin.get(name, arraySize);
cin.get();
cout << "Enter your favorite dessert:\n";
cin.get(dessert, arraySize);
cout << "I have some delicious " << dessert;
cout << " for you, " << name <<".\n";
return 0;
}
结果与getline()中的结果一样。
混合输入字符串和数字
混合输入数字和面向行的字符串会导致问题。
int main()
{
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;
}
执行结果如下:
What year was your house built?
1966
What is its street address?
Year built: 1966
Address:
Done!
熟悉的问题,程序没有等待我们输入地址,因为cin在读取输入时,将回车生成的换行符留在了输入队列中,那么getline()在读取输入队列时首先读取到了换行符而提前结束。同样可以利用***cin.get()***来避免这个问题。
int main()
{
cout << "What year was your house built?\n";
int year;
cin >> year;
cin.get();
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;
}
执行结果如下:
What year was your house built?
1966
What is its street address?
221 Baker Street
Year built: 1966
Address: 221 Baker Street
Done!
string类
要使用string类,必须再头文件中包含string头文件,同时string类位于命名空间std中。string类定义隐藏了字符串的数组性质,因此我们可以像处理普通变量一样处理字符串。
string str1; //创建一个空白字符串
string str2 = "hello world!"; //创建一个初始化的字符串
赋值、拼接和附加
string类可以将一个string对象赋值给另一个对象,但数组表示的字符串不支持这种操作。
string str1 = "hello world!";
string str2 = str1;
string类利用运算符“+”完成字符串的拼接,还可以使用运算符“+=”来将一个字符串加到另一个字符串的末尾。
string str1 = "hello";
string str2 = " world!";
string str3 = str1 + str2; //拼接
str1 += str2; //附加