C++复习 第四章 复合类型 第一节

目录

4.1 数组

        4.1.1 数组的声明

4.1.2 数组的初始化

4.1.3 数组初始化方法

4.2 字符串

        4.2.1 拼接字符串常量

        4.2.2 在数组中使用字符串

        4.2.3 字符串输入

        4.2.4 每次读取一行字符串

        面向行的输入:getline()

        面向行的输入:get()

        空行和其他问题


4.1 数组

        4.1.1 数组的声明

        数组是一种数据格式,可以存储多个相同类型的数据。例如,数组可以存储60或更多的 int 类型,当然只要程序允许访问的空间可以包含这么多 int 类型的值。

        要创建一个数组,可以使用声明语句。一个合法的声明语句应当包含以下三点:

  • 存储值的类型;
  • 数组的名称;
  • 数组中的元素数。

        例如,下面就是一些合法的声明数组的语句:

//int 是数组中存储元素的类型
//number 是数组名称
//10 是数组最大容纳数值个数
int number[10];    

//short 是数组中存储元素的类型
//quantity 是数组名称
//255 是数组最大容纳数值个数
unsigned char quantity[255];

//通用格式:
typeName arrayName[arraySize];

        数组之所以被称为复合类型,是因为它是使用其他类型来创建的。不能仅仅将某种东西声明为数组,它必须是特定类型的数组。没有通用类型的数组,例如一个数组中既能存储 double 类型的值,又能存储 int 类型的值。

        数组的索引格式与声明数组的格式异曲同工,都是由数组名中括号索引下标组成。下面的代码就是将存储在数组下标 25 位处的值输出。

//数组名:quantity 
//中括号:[] 
//索引下标:25
cout << quantity[25] << endl;

        这里就发生了一个问题,因为数组的索引是从零开始的,那么如果在数组声明的时候表示数组中最大容纳数值个数为 255 个,那么数组的索引就是从 0 开始 一直到 254。输出数组下标 25 位处的值就相当于输出数组中第 26 个值。

        编译器不会检查使用的下标是否有效,如果通过索引访问数组中的 520 下标元素,那么会发生数组越界,而数组越界编译器是不会报错的。同时也不允许将值赋给数组越界的访问元素,这种赋值可能会破坏数据或代码,也可能导致程序异常终止。       

4.1.2 数组的初始化

        只有在定义数组的时候才能够使用数组初始化的方法,之后就不可以再次使用,也不能够将一个数组赋给另一个数组。但是数组允许使用下表分别给每个数组元素复制。并且初始化时提供给数组的值可以少于数组元素的数目。如果只给数组的部分元素赋值,那么编译器将自动的把其他元素赋值为 0。这样,如果想要将数组内的所有元素全部赋值为 0,只需要将数组内的第一个元素赋值为 0,这样编译器会自动的将剩余元素全部赋值为 0。如果初始化的时候未声明数组大小,即方括号内数值为空,那么编译器将自动计算数组大小。

//允许
int number[10] = {1, 2, 3, 4};    
//允许
double qualtity[5];     
//允许,并且数组大小为 2
long long population[] = {114514, 5201234};        
//不允许,因为没有在数组的声明语句中初始化数组
qualtity[5] = {7, 8, 9, 10, 11};  
//不允许,不能将一个数组赋值给另一个数组
number = qualtity;   

4.1.3 数组初始化方法

        数组的初始化的一些注意事项:

  1. 初始化数组时可以省略等于号。
  2. 可以不在打括号内包含任何东西,这将把所有元素都设置为 0。
  3. 列表初始化禁止缩窄转换。       

        缩窄转换:指类型转换时从表示范围较大的类型转变为表示范围相对较小的类型。例如:double -> int / int -> char。

4.2 字符串

        C++处理字符串有两种方式,第一种来自C语言,常被称为C风格字符串(C-style string),另一种基于 string 类库的方法。

        存储在连续字节中的一系列字符意味着可以将字符存储在一个 char 类型字符数组中,其中每个元素都位于相应位置的数组元素中。C风格字符串有一个特点:每个字符串的末尾都会被添加一个空字符(null character),具体表示为 \0,其 ASCII 值为 0,用来标记字符串结尾。

//word 是一个字符串
char word[9] = {'B', 'e', 'a', 't', 'l', 'e', 's', 's', '\0'};
//speak 不是一个字符串
char speak[3] = {'w', 't', 'f'};

        上述两个数组都是 char 类型的数组,但是只有 word 数组是字符串。空字符对于C风格字符串来说至关重要,许多函数和方法都会用到空字符。例如:ostream 类中的 cout 对象,使用 cout 来输出C风格字符串的时候通过读取空字符来确定什么时候结束输出。像上述的 word 数组中存储的字符串,cout 只会输出其中的前 8 个元素,读取到第九个元素为空字符结束输出。但是如果用来输出 speak 字符数组,将会出现很糟糕的后果。虽然空字符在内存中很常见,所以 cout 的输出会很快停止,但是我们仍不应该使用非字符串的字符数组来当作字符串使用。

        将字符数组初始化为字符串的方法很简单,只需要使用英式的双引号扩起要初始化的字符串即可,这种字符串被称为字符串常量(string constant)或字符串字面值(string literal)。但是在初始化字符串的时候别忘记设置足够大的空间用来存储字符串中的所有字符——包括空字符。

//word[9] = {'B', 'e', 'a', 't', 'l', 'e', 's', 's', '\0'}
char word[9] = "Beatless";
//speak[6] = {'w', 't', 'f', '\0', '\0', '\0'}
//存储完字符串后剩余的空间将全部被存储为空字符
char speak[6] = "wtf";

         值得注意的是:在确定字符串所需要的最小存储空间的时候别忘记将空字符也考虑进去。

        还有一点对于使用过 python 的小伙伴也值得注意:双引号用于字符串(大于等于两个字符),单引号用于字符常量(单个字符)。

//"s" 表示的是一个字符串的内存地址
//这条语句实际上是在将一个内存地址赋值给 char 类型变量
//由于地址在C++中是一个独立的数据类型
//所以编译器将不会允许这样的不合理做法
char ch = "s";

        4.2.1 拼接字符串常量

        C++允许拼接字符串字面值,即将两个用双引号扩起的字符串合并为一个。

#include <iostream>
using namespace std;
int main(){
    cout << "字符串常量可以" "拼接输出" << endl;
    cout << "即使是换行";
    cout << "也可以显示在同一行里" << endl;
    return 0;
}


运行结果:
字符串常量可以拼接输出
即使是换行也可以显示在同一行里

        注意,拼接字符串的时候不会在两个字符串之间添加空格,第二个字符串的输出将会紧跟在第一个字符串后面,第一个字符串的空字符(\0)将会被替换为第二个字符串的第一个字符。

        4.2.2 在数组中使用字符串

        如果想要计算一个数组的长度,有两种方法:

  1. 如果数组是 char 类型,可以使用 <cstring> 头文件内的函数 strlen(char * arrayName);
  2. 通用类型,可以使用 sizeof(arrayName) / sizeof(arrayName[0]);
#include <iostream>
#include <cstring>

int main(){
    using namespace std;

    char name[20] = "Beatless";
    int name_length = strlen(name);
    cout << "name_length = " << name_length << endl;

    //sizeof(number) == sizeof(int) * 10;
    //sizeof(number[0]) == sizeof(int);
    //cnt = sizeof(int) * 10 / siezeof(int)
    int number[10];
    int number_length = sizeof(number) / sizeof(number[0]);
    cout << "number_length = " << number_length << endl;
    return 0;
}


运行结果:
name_length = 8;
number_length = 10;

解释:
第一种方法返回的长度不包括空字符,所以返回的长度是beatless八个字母的长度
第二种方法返回的长度是数组的长度,无论数组种存储的字符是不是空字符都包含在长度中

        对于字符串数组,其长度不能短于 strlen(arrayName)+1 因为需要额外一个空间来存储空字符来表示这是一个字符串数组。

        4.2.3 字符串输入

        对于使用 cin 来读取字符串可以通过使用空白(空格,制表符以及换行符)来确定字符串的结束位置。

#include <iostream>
using namespace std;
int main(){
    char name[10];
    char job[50];
    
    cout << "请输入一个人物的名称:" << endl;
    cin >> name;
    cout << "请输入这个人物的职业:" << endl;
    cin >> job;

    cout << name << "是" << job << endl;
    return 0;
}

        两次输入通过回车来进行分割与通过空格分割程序所运行出来的结果不一样:

//通过换行分割两次输入
运行结果:
请输入一个人物名称:
胡桃
请输入这个人物的职业:
往生堂堂主
胡桃是往生堂堂主

//通过空格分割两次输入
运行结果:
请输入一个人物名称:
胡桃 往生堂堂主
请输入这个人物的职业:
胡桃是往生堂堂主

        另一个可能的隐患就是要输入的字符串长度比声明字符串的长度要长,例如上述代码中我想要在 name 数组中输入莫娜的全名,那么声明时内存所给予的空间肯定是不够的。

        4.2.4 每次读取一行字符串

        有的时候我们需要读取一句话,而这一句话是在一行中给出来的,所以我们需要一次性读取一行的内容。这时候只是用 cin 就不够了,接下来会介绍一些读入数据的方法。istream 类里包含了一些面向行的类成员函数:getline()get()。这里函数都是读取一行的输入,直到达到换行符。随后,getline() 将丢弃换行符,而 get() 将换行符保留在输入序列中。接下来首先介绍getline()方法。

        面向行的输入:getline()

        getline() 函数读取整行的数据,它使用通过回车键输入的换行符来确定输入的换行符。要调用这种方法,可以使用 cin.getline() 。该函数有两个参数,第一个参数是存储的数组名称,第二个参数是要读取多少个字符数到数组里。如果这个参数为 8,那么函数最多可以读入 7 个字符,余下的空间用于存储自动在结尾处添加的空字符。getline() 函数在读取到指定数目的字符后以及遇到换行符后停止读入。

#include <iostream>
int main(){
    using namespace std;
    
    const int Size = 20;
    char name[Size];
    char job[Size];

    cout << "请输入一个人物的名称" << endl;
    cin.getline(name, Size);

    cout << "请输入这个人物的职业" << endl;
    cin.getline(job, Size);

    cout << name << " is " << job << "." << endl;
    return 0;
}


运行结果:
请输入一个人物的名称
CHICO with HoneyWorks
请输入这个人物的职业
music groups
CHICO with HoneyWorks is music groups.

        getline() 通过换行符来确定行尾,但是不保存换行符,而是使用空字符来替换换行符。

        面向行的输入:get()

        istream 类里还有另一个成员函数 get(),该函数有几个变体,其中一个变体与 getline() 的工作方式很像,它们接受的参数相同,但是 get 并不会读取并丢弃换行符,而是将其留在输入队列中。如果将上述的 getline() 函数都替换为get 函数,那么运行结果如下:

#include <iostream>
int main(){
    using namespace std;

    const int Size = 50;
    char name[Size];
    char job[Size];

    cout << "请输入一个人物的名称" << endl;
    cin.get(name, Size);
//    cin.get();

    cout << "请输入这个人物的职业" << endl;
    cin.get(job, Size);

    cout << name << " is " << job << "." << endl;
    return 0;
}


运行结果:
请输入一个人物的名称
CHICO with Honeyworks
请输入这个人物的职业
CHICO with Honeyworks is .

        当第一次输入的时候 name 数组读取并接受一行的输入内容,当遇到结尾的换行符时停止读入,并将换行符留在了输入队列中。当第二次读取的时候,发现输入队列中的第一个字符就是换行符,便直接停止读入,以至于我们都没有输入的机会程序就直接结束了。

        但是,get 还有另一种变体,不带任何参数,作用是读取下一个字符(即使是换行符)。因此可以使用它来处理换行符,为去读取下一行来作准备。所以可以将上述程序的第一次读取后加上注释里的内容。这样程序就可以正常运行了。

        由于 cin.get(name, Size) 的返回类型是 cin 对象。所以可以使用以下语句:

cin.get(name, Size).get();

        那么相对于使用 cin.getline() 使用 cin.get() 有什么优势呢?那就是可以知道读取停止的原因:究竟是读取的字符数达到上限还是收到了换行符。具体验证方法就是验证最后读取的一个字符,而 get() 方法不会丢弃掉最后读入的字符。如果是收到了换行符而停止读入,那么留在输入序列的第一个字符就是换行符;如果是读取字符达到上限,那么输入序列的第一个字符就不是换行符。

        空行和其他问题

        当 getline() 和 get() 读取空行时,getline() 函数会在前一条 getline() 结束读取的位置开始读取;而 get() 函数读取空行后会设置失效位(failbit),从而阻断后面的读取。但是可以通过函数 cin.clear() 来恢复输入。

        另一个需要考虑的问题是:当输入超过数组最大容纳数量,则 getline() 和 get() 会将余下的的输入阻断,并且 getline() 还会设置失效位,并阻断后面的输入。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

♡すぎ♡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值