C++ 学习之六、复合类型之数组

C++ 复合类型之数组笔记。

数组简介

数组(array)是一种数据格式,能够存储多个同类型的值。

数组的声明

声明数组的通用格式如下:

typeName arrayName[arraySize]

Tips: arraySize 指定元素数目,它必须是整型常数(如 10 )或 const 值,也可以是常量表达式(如 8*sizeof( int )),即其中所有的值在编译时都是已知的。具体地说,arraySize 不能是变量。后面将介绍使用 new 运算符来避开这种限制。

数组与类型是强关联的,如 float f[20]f 的类型不是“数组”,而是“float 数组”。

数组的初始化

函数中未初始化的数组的元素值是不确定的,这意味着元素的值为以前驻留在该内存单元中的值。只有在定义数组时才能初始化,此后就不能重新初始化了,不能将一个数组赋给另一个数组

1
2
3
4
int cards[4] = {3, 6, 8, 10};   // 数组列表初始化
int hand[4]; // 数组声明
hand[4] = {5, 6, 7, 8}; // 非法,只能在上面声明的时候直接初始化,现在的 hand[4] 表示第 5 个元素
hand = cards; // 非法,不能将一个数组赋给另一个

几种初始化方式:

1
2
3
4
5
float n_float[3] = {0.1, 0.555, 6.8};   // 列表初始化,可列出所有元素
long n_long[10] = {5, 7}; // 只列出一部分元素,编译器将把其它元素设置为 0
short n_short[] = {1, 2, 3, 4, 5}; // 如果 [] 内元素个数为空,C++ 编译器将根据初始化内容来计算元素个数,这里为有 5 个元素,通常这种做法在初始化非 char 数组时不可取,因为不利于排错
double n_double[3]{1.2e12, 1.3e5, 5.9e9}; // C++11 方式,可省略等号
int n_int[8] = {}; // C++11 方式,大括号内不包含任何内容,将把所有元素初始化为 0,同上等号也可以省略

Tips: 上述所有列表初始化同其它列表初始化一样,禁止缩窄操作。

特殊的数组 —— 字符串

C 风格字符串

char 数组中存储多个 char 类型的值,其中有一种特殊情况,如果 char 数组最后一个元素为 \0,即以空字符结尾,这时这个数组就是一个字符串,可以看出,字符串是一种特殊的 char 数组。

1
2
3
4
5
char c_array[5] = {'z', 'h', 'o', 'u', 'x'};    // char 数组
char c_string[5] = {'z', 'h', 'o', 'u', '\0'}; // 字符串
char c_string[5] = "zhou"; // 一种更好的初始化字符串的方法,用双引号括起的字符串隐式地包含结尾的空字符,所以元素个数要比双引号的字符个数多 1 个
char c_string[] = "zhou"; // 初始化字符串时,不写元素个数更方便
char c_string[] = {"zhou"}; // 列表初始化方式

空字符 '\0' 对 C 风格字符串有着至关重要的意义。如 cout 打印字符串时,它将逐个打印字符直到遇到空字符才停止。

C 风格字符串特点 —— 拼接字面值

有时候字符串很长,无法放到一行中。C++ 允许拼接字符串字面值,即将两个用引号括起的字符串合并为一个。任何两个由空白(空格、制表符和换行符)分隔的字符串常量将自动拼接成一个,如以下两句意义相同。

1
2
cout << "This is a" " string";  // 将自动拼接为 "This is a string"
cout << "This is a string";

Tips: 拼接时,前一个字符串末尾的 '\0' 将被后一个字符串中的第一个字符代替,两者之间不会自动加上空白。

数组中使用字符串

在数组中使用字符串,最常用的有两种方法——将数组初始化为字符串、将键盘或文件输入读入到数组中。

数组初始化为字符串前面已详细介绍。

而字符串输入,有以下几种方式:

  • cin>> 输入方式。cin>> 使用空白(空格、制表符和换行符)来确定字符串的结束位置,cin>> 从缓冲区中读取数据,若缓冲区中第一个字符是空白,cin>> 会将其忽略并清除继续读取下一个字符,若缓冲区为空,则继续等待。cin>> 获取字符数组输入时只读取一个单词,读取该单词后,cin>> 将该字符放到数组中,并自动在结尾添加空字符。cin>> 会将结尾的空白保留在输入序列中。
  • cin cin.getline(char *, int)。读取一行输入,直到到达换行符,丢弃换行符。它将使用空字符来替换换行符。
  • cin cin.get(char *, int)。读取一行输入,直到到达换行符,将换行符保留在输入序列中。

Tips: strlen() 用于计算可见的字符数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// CinInputArray.cpp 通过控制台输入姓名,年龄,身高,体重,判断是否肥胖
#include <iostream>
#include <cstring> // 含有 strlen() 函数原型
using namespace std;
void bMICal(float, float); // 函数原型,注意 void 也要表示出来

int main()
{
const int nameSize = 20;
char name[nameSize] = {};
int age;
float weight, height;
cout << "What is your name? ";
// 或者使用 cin.getline(name, nameSize); 此时换行符会被丢弃,无需处理
cin.get(name, nameSize).get(); //cin.get(name, nameSize) 读入 name,返回 cin 对象,再使用 get() 处理掉换行符
cout << "How old are you? ";
cin >> age;
cout << "Please input your weight kg: ";
cin >> weight;
cout << "Please input your height m: ";
cin >> height;
cout << name << " is " << age << " years old!" << endl;
cout << "You are " << weight << "kg" << " and " << height << "m" << endl;
bMICal(weight, height);
return 0;
}

void bMICal(float weight, float height)
{
float bmi = weight / (height * height);
cout << "Your BMI is " << bmi << endl;
if (bmi < 18.5)
{
cout << "体重过低!";
}
else if (bmi >= 18.5 && bmi <= 23.9)
{
cout << "体重正常!";
}
else if (bmi >= 24.0 && bmi <= 27.9)
{
cout << "超重!";
}
else if (bmi >= 28)
{
cout << "肥胖!";
}
cout << endl;
}

读取空行和其他问题

getline(char *, int)get(char*, int) 读取空行时,将发生什么情况?当前的做法是,当 get(char*, int)(不是 getline(char*, int))读取空行后将设置失效位(failbit)。接下来的输入将被阻断,但可以用下面的命令来恢复输入:

cin.clear();

另一个问题是,输入字符串可能比分配的空间长。如果输入行包含的字符数比指定的多,则 void getline(char*, int)void get(char*, int) 将把余下的字符留在输入队列中,而 void getline(char*, int) 还会设置失效位,并关闭后面的输入。将在后面章节讨论。

C++ string 类

ISO/ANSI C++98 标准通过添加 string 类扩展了 C++ 库,只需要包含头文件 string。string 类位于名称空间 std 中,因此需要提供一条 using 编译指令。

string 字符串初始化

1
2
string str1 = "This is a string!";  // C-风格字符串初始化方式
string str2 = {"This is a string!"}; // 列表初始化方式

string 特性

  • 不同于 C-风格字符串只能使用 strcpy() 函数将一个字符串复制给另一个,string 直接可将一个 string 赋给另一个。

    1
    2
    3
    string str1 = "This";   // string 对象将根据字符串的长度自动调整自己的大小
    string str2; // 未初始化的 string 对象长度为0
    str2 = str1; // 语法正确
  • string 类简化了字符串合并操作。

    传统 C-风格字符串使用 strcat() 函数拼接字符串,string 字符串,可以使用运算符 + 将两个 string 对象合并起来,还可以使用 += 将字符串附加到 string 对象的末尾。

    1
    2
    3
    string str3;
    str3 = str1 + str2; // str3 为 str1 和 str2 的拼接
    str1 += str2; // 将 str2 附加到 str1 末尾
  • string 类 I/O

从输入读取字符串到 string 对象时,每次读取一行,而且是使用以下代码方式:

1
2
cin.getline(charr, charrSize);  // C-风格字符串读取输入
getline(cin, str); // string 对象读取输入

字符串字面值

  • 普通类型

    1
    char array[] = "This is array!";
  • wchar_t 类型字符串

    1
    wchar_t array[] = L"This is array!";
  • char16_t 和 char32_t 类型字符串

    1
    2
    char16_t array[] = u"char16_t style array!";
    char32_t array[] = U"char32_t style array!";
  • C++11 新增一种类型是原始(raw)字符串

    输入原始字符串时,所有的字符以原样显示,特殊字符也不会被转义,按回车键不仅会移动到下一行,还将在原始字符串中添加回车符。

    1
    2
    char array[] = R"(This is ray array, \" will not be translate to ".)";  // 以 R"( 开头,以 )" 结束。
    char array[] = R"+-(This is ray array, \" will not be translate to ".)+-"; // 允许在开头 "( 之间添加除空格、左括号、右括号、斜杠和控制字符(如制表符和换行符)以外的任意数量的基本字符,然后以相同的格式结尾。

    Tips: 可将前缀 R 与其他字符串前缀结合,以标识 wchar_t 等类型的原始字符串。可将 R 放在前面,也可将其放在后面,如 Ru、UR 等。

二维数组

每个数组都可以看作是一行数据,二维数组更像是一个表格——既有数据行又有数据列。

C++ 没有提供二维数组的类型,但用户可以创建每个元素本身都是数组的数组。

可以这样声明一个二维数组:

int maxtemps[4][5]; // maxtemps 是一个包含 4 个元素的数组,其中每个元素都是一个由 5 个整数组成的数组。

初始化二维数组

可以像类似一维数组一样初始化

1
2
3
4
5
6
7
8
int btus[5] = {23, 26, 24, 3, 5};
int maxtemps[4][5] = // 二维数组初始化
{
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15},
{90, 91, 92, 93, 94}
};