第四章:数组和字符串

本章学习如何处理一个对象的集合,例如20个int或一个字符串。

一、什么是数组

数组的一种解释:“a group of elements forming a complete unit, for example an array of solar panels.”
数组的特征:
1)数组是元素的集合;
2)数组中包含的所有元素都是同一类型;
3)数组中的元素构成一个完整的集合。

1、为什么需要数组

当一次需要操作多个同种类型的数据时,如果像第三章一样声明多个变量,每个变量保存一个数据,将是非常繁琐的工作,所以我们需要数组,便于一次存储多个数据,也更方便处理。
下面将一个长度为5的整型数组初始化为0:

int myNumbers [5] = {0};

下面是一个长度为5的字符型数组:

char myCharacters [5];

上面的数组称为静态数组,因为数组中元素类型和数组大小是固定不能改变的。

2、静态数组的声明和初始化

C ++中的数组声明遵循一个简单的语法:

ElementType ArrayName [constant_number of elements] = {optional initial values};

除了第一小节中统一初始化的方式之外,也可以用下面的方式指定数组每个元素的值:

int myNumbers [5] = {34, 56, -21, 5002, 365};

也可以通过以下方式将数组元素统一初始化为0:

int myNumbers [5] = {}; // initializes all integers to 0

也可以初始化一部分元素,剩余元素将自动初始化为0:

int myNumbers [5] = {34, 56}; 
// initialize first two elements to 34 and 56 and the rest to 0

也可以将数组的长度定义为常量,然后在数组定义中使用该常量表示数组长度:

const int ARRAY_LENGTH = 5; 
int myNumbers [ARRAY_LENGTH] = {34, 56, -21, 5002, 365};

如果已知数组中元素的初始值,则可以在声明数组时省略数组长度值:

int myNumbers [] = {2016, 2052, -525}; // array of 3 elements

上面的代码创建了一个包含三个整数的数组,初始值为2016,2052和-525。

到目前为止声明的数组称为静态数组,因为数组的长度是一个常量, 此数组不能容纳超出指定长度的数据量,如果静态数组中有空间未使用,它消耗的内存也是一样的。
在程序执行时决定长度的数组称为动态数组。

3、数据在数组中的存储方式

一维数组的存储方式就像在书架上摆放一排书籍,需要注意的是,C++中数组的下标从0开始。类似于架子上的五本书,包含五个整数的数组myNumbers如下图所示:
在这里插入图片描述
数组占用的存储空间由五个块组成,每个块大小相等,由数组中要保存的数据类型定义,在本例中为整数。 编译器为数组myNumbers保留的内存量因此是sizeof(int)* 5。一般来说, 编译器为数组保留的内存的字节大小为:
Bytes consumed by an array = sizeof(element-type) * Number of Elements

4、访问数组中的数据

使用索引访问数组中的元素,数组中的第一个元素的索引为0,因此,存储在数组myNumbers中的第一个整数值是myNumbers [0],第二个是myNumbers [1],依此类推,第五个是myNumbers [4]。换句话说,数组中最后一个元素的索引始终为:(数组的长度-1)。
当访问索引N处的元素时,编译器使用第一个元素的内存地址(索引0)作为起始点,然后通过N * sizeof(元素)的偏移量来跳过N个元素以到达地址包含第(N + 1)个元素。
C ++编译器不检查索引是否在数组的实际定义范围内。我们可以在仅包含10个元素的数组中获取索引1001处的元素,从而使程序出错。确保数组安全访问的责任完全在于程序员。
下面程序演示了如何声明、初始化和访问一个整型数组:

#include <iostream>
using namespace std;


int main() 
{
	int myNumbers[5] = { 34, 56, -21, 5002, 365 };
	cout << "First element at index 0: " << myNumbers[0] << endl; 
	cout << "Second element at index 1: " << myNumbers[1] << endl; 
	cout << "Third element at index 2: " << myNumbers[2] << endl; 
	cout << "Fourth element at index 3: " << myNumbers[3] << endl; 
	cout << "Fifth element at index 4: " << myNumbers[4] << endl;

	return 0;
}

5、修改数组中的数据

下面程序展示了如何给数组中的元素赋值:

#include <iostream>
using namespace std;

constexpr int Square(int number) { return number*number; }


int main() 
{
	const int ARRAY_LENGTH = 5;

	// Array of 5 integers, initialized using a const
	int myNumbers[ARRAY_LENGTH] = { 5, 10, 0, -101, 20 };

	// Using a constexpr for array of 25 integers
	int moreNumbers[Square(ARRAY_LENGTH)];

	cout << "Enter index of the element to be changed: ";
	int elementIndex = 0;
	cin >> elementIndex;

	cout << "Enter new value: ";
	int newValue = 0;
	cin >> newValue;

	myNumbers[elementIndex] = newValue;
	moreNumbers[elementIndex] = newValue;

	cout << "Element " << elementIndex << " in array myNumbers is: ";
	cout << myNumbers[elementIndex] << endl;

	cout << "Element " << elementIndex << " in array moreNumbers is: ";
	cout << moreNumbers[elementIndex] << endl;

	return 0;
}

需要注意的是:
声明数组时最好初始化数组,否则它们将包含垃圾值。

二、多维数组

上述一维数组类似于在一排书架上存放书籍。那么,二维数组如下所示,类似于太阳能电池板阵列,与书架不同,太阳能电池板可以在两个维度上展开:长度和宽度:
在这里插入图片描述
在上图中,六块太阳能电池板以二行和三列的二维排列方式放置。我们可以将此排列视为两个元素的一维数组,每个元素本身是一个包含三个面板的一维数组。
在C ++中,不仅可以使用二维数组,根据需要也可以使用多维数组。

1、多维数组的声明和初始化

在C ++中,我们可以通过指定每个维度的长度来声明多维数组,因此,上图中太阳能电池板的二维整型数组为:

int solarPanels [2][3];

初始化上述数组:

int solarPanels [2][3] = {{0, 1, 2}, {3, 4, 5}};

一个包含三行三列的数组如下所示:

int threeRowsThreeColumns [3][3] = {{-501, 206, 2016}, {989, 101, 206}, {303, 456, 596}};

需要注意的是:
尽管C ++允许我们使用多维数组,但数组的内存始终是一维的,编译器将多维数组映射到内存空间,该内存空间仅在一个方向上扩展。
也可以使用如下方式声明多维数组,这与上面solarPanels数组声明的结果是一样的:

int solarPanels [2] [3] = {0,1,2,3,4,5};

2、访问多维数组中的元素

由于二维数组是一种多维数组,下面都拿二维数组来举例说明。
可以将二维数组看成包含一维数组的一维数组。因此,当处理一个三行和三列的二维整型数组时,可以将其视为包含三个元素的一维数组,其中每个元素又是一个包含三个整数的一维数组。
当访问此数组中的某个整数时,使用第一个下标来寻址整数所在的一维数组,使用第二个下标来寻址整数在该一维数组中的位置。例如下面数组,值为205的整数元素位于[0] [1]位置。 值为456的元素位于[2] [1]位置:

int threeRowsThreeColumns [3][3] = {{-501, 205, 2016}, {989, 101, 206}, {303, 456, 596}};

三、动态数组

设想一个存储医院医疗记录的应用程序,程序员没有办法知道他的应用程序可能需要处理的记录数量的上限是多少,他可以做出一个假设,这个假设大于某个合理限度值。在这中情况下,他保留了大量内存,其中有很多是空闲的,没有被使用,这降低了系统的性能。
那么如何才能不使用静态数组,而是选择优化内存消耗的动态数组,并根据执行时对资源和内存的需求进行扩展,是需要考虑的问题。
C ++以std :: vector的形式提供方便易用的动态数组,如下面代码所示:

#include <iostream>
#include <vector>

using namespace std;

int main() 
{
	vector<int> dynArray(3); // dynamic array of int
	dynArray[0] = 365;
	dynArray[1] = -421;
	dynArray[2] = 789;

	cout << "Number of integers in array: " << dynArray.size() << endl;

	cout << "Enter another element to insert" << endl;
	int newValue = 0;
	cin >> newValue;
	dynArray.push_back(newValue);

	cout << "Number of integers in array: " << dynArray.size() << endl;
	cout << "Last element in array: ";
	cout << dynArray[dynArray.size() - 1] << endl;

	return 0;
}

输出:
Number of integers in array: 3
Enter another element to insert
88
Number of integers in array: 4
Last element in array: 88

四、C风格的字符串

C风格的字符串是字符数组的特例。例如下面一段代码:

std::cout << "Hello World";

这段代码的作用和下面这段代码是一样的:

char sayHello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0'}; 
std::cout << sayHello << std::endl;

需要注意的是,数组中的最后一个字符是空字符’\ 0’,这也称为字符串的终止字符,它告诉编译器字符串已结束。这种C风格的字符串是字符数组的一种特殊情况,使用字符串形式 "Hello World"时,编译器会在其后添加“\ 0”。
‘\ 0’可能看起来像两个字符,它确实是使用键盘输入的两个字符。 然而,反斜杠是编译器理解的特殊转义代码,\ 0表示null,也就是说,它要求编译器在其中插入null或0。
我们不能直接写成’0’,因为它会被接受为字符零,它具有非零的ASCII码48。
如果在数组中间的位置插入’\ 0’,它不会改变数组的大小,这只意味着使用数组作为输入的字符串处理将在该点停止。 如下面程序所示:

#include <iostream>
using namespace std;

int main() 
{

	char sayHello[] = { 'H','e','l','l','o',' ','W','o','r','l','d','\0' };
	cout << sayHello << endl;
	cout << "Size of array: " << sizeof(sayHello) << endl;

	cout << "Replacing space with null" << endl;
	sayHello[5] = '\0';
	cout << sayHello << endl;
	cout << "Size of array: " << sizeof(sayHello) << endl;
	
	return 0;
}

输出:
Hello World
Size of array: 12
Replacing space with null
Hello
Size of array: 12

如果声明并初始化字符数组时没有在末尾添加’\ 0’,那么在打印“Hello World”之后,输出会包含乱码。这是因为std :: cout不会因打印完数组而停止,它会一直打印,直到它到达空字符,即使会超出数组的边界。

五:C ++字符串:使用std :: string

C ++标准字符串是处理文本输入和执行字符串操作的有效且更安全的方法。std :: string的大小不像C样式字符串的char数组那样是静态的,并且当需要存储更多数据时可以扩展。 如下面代码所示:

#include <iostream>
#include <string>

using namespace std;

int main() 
{
	string greetString("Hello std::string!");
	cout << greetString << endl;

	cout << "Enter a line of text: " << endl;
	string firstLine;
	getline(cin, firstLine);

	cout << "Enter another: " << endl;
	string secondLine;
	getline(cin, secondLine);

	cout << "Result of concatenation: " << endl;
	string concatString = firstLine + " " + secondLine;
	cout << concatString << endl;

	cout << "Copy of concatenated string: " << endl;
	string aCopy;
	aCopy = concatString;
	cout << aCopy << endl;

	cout << "Length of concat string: " << concatString.length() << endl;

	return 0;
}

输出:
Hello std::string!
Enter a line of text:
kkk
Enter another:
lll
Result of concatenation:
kkk lll
Copy of concatenated string:
kkk lll
Length of concat string: 7

总结

这一章学习了数组的基础知识,包括如何声明、初始化,以及访问数组中的元素并将值写入数组。在使用数组过程中不能超过数组边界,否则会发生缓冲区溢出的错误。
然后介绍了动态数组及其使用方法,动态数组使我们不需要担心数组越界的问题,并且能在使用率低于预期最大值时更好地进行内存管理。
最后学习了C样式字符串,它是char数组的特例,其中字符串的结尾由空终止字符’\ 0’标记。并且了解到C ++在std :: string中提供了更方便的字符串操作,它提供了方便实用的函数,能够确定数组长度以及数组拼接等操作。

此文供学习交流使用,若有错误之处欢迎共同交流。