跳转至

03-数组与字符串:一组数据怎么放

目录页:见 C++ 小白教程目录

目标

  • 用数组保存一组数字,并能遍历、统计、查找
  • 理解下标从 0 开始,以及“越界”为什么危险
  • 会用 std::string 处理字符串:拼接、长度、访问字符、读整行

先决知识

  • 会用 if/for/while 写基础逻辑(见 01 章 4、5)
  • 会写函数并能调用(见 02 章 2、3)

1. 数组(array):把很多同类型数据排成一排

你现在遇到的第一个“容器”,通常就是数组。

  • 专业名称:数组(array)
  • 类比:一排连续编号的储物格,每个格子装同一种东西(同一种类型)
  • 作用:用一个名字存一组数据,方便用循环批量处理
  • 规则/坑点:下标从 0 开始;数组大小固定;越界访问会产生严重问题

1.1 怎么声明与初始化

最常见的写法:

#include <iostream>

int main() {
    int a[5] = {10, 20, 30, 40, 50};
    std::cout << "a[0]=" << a[0] << '\n';
    std::cout << "a[4]=" << a[4] << '\n';
    return 0;
}

你需要抓住 3 个点:

  • int a[5]:表示有 5 个 int 格子
  • a[0] 是第 1 个格子,a[4] 是第 5 个格子
  • a[5] 不存在(越界)

再看两种很实用的初始化:

int a[5] = {0};

含义:把第 1 个元素设为 0,剩下的也会被置为 0(新手阶段可以把它理解成“全清零”)。

int a[5] = {};

含义:把所有元素都置为 0(更直观)。

1.2 下标(index):为什么从 0 开始

  • 专业名称:下标/索引(index)
  • 类比:储物格的编号,从 0 号开始贴标签
  • 作用:用 a[i] 精确定位第 i 个元素
  • 规则/坑点:合法范围永远是 0 ~ (n-1);一旦写到 a[n] 就越界

你先把这句当成“铁律”:

  • 长度是 n 的数组,最大下标永远是 n-1

2. 遍历数组:用循环把整排格子走一遍

2.1 读入 N 个数,求和与最大值(可运行)

#include <iostream>

int main() {
    const int n = 5;
    int a[n] = {};

    for (int i = 0; i < n; i++) {
        std::cin >> a[i];
    }

    int sum = 0;
    int maxValue = a[0];
    for (int i = 0; i < n; i++) {
        sum += a[i];
        if (a[i] > maxValue) {
            maxValue = a[i];
        }
    }

    double avg = static_cast<double>(sum) / n;
    std::cout << "sum=" << sum << '\n';
    std::cout << "max=" << maxValue << '\n';
    std::cout << "avg=" << avg << '\n';
    return 0;
}

这里你第一次看到 static_cast<double>(sum)

  • 专业名称:显式类型转换(explicit cast)
  • 类比:给数据换一个“包装规格”,让它按新的规则参与计算
  • 作用:避免整数除法丢小数,让结果按浮点数算
  • 规则/坑点:不要迷信“自动帮你转对”,新手阶段遇到“整数除法”就主动转成 double

3. 数组和函数:为什么通常要“数组 + 长度”一起传

你一定会想把“求最大值”封装成函数。问题是:函数拿到数组时,通常还需要数组长度。

入门先记结论(原因会在第 04 篇指针里讲透):

  • 把数组交给函数时,常见做法是:传数组 + 传长度

可运行例子:

#include <iostream>

int maxOf(const int a[], int n) {
    int maxValue = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] > maxValue) {
            maxValue = a[i];
        }
    }
    return maxValue;
}

int main() {
    const int n = 5;
    int a[n] = {};
    for (int i = 0; i < n; i++) {
        std::cin >> a[i];
    }

    std::cout << "max=" << maxOf(a, n) << '\n';
    return 0;
}

注意这句:

  • const int a[]:表示函数承诺“不修改数组内容”

4. 字符串(std::string):一串字符,不是“单个字符”

前面你见过 'Q'(一个字符),现在学的是 "qtcraft" 这种一串字符。

  • 专业名称:字符串(string),标准库字符串类型:std::string
  • 类比:一串连续的字符珠子,你可以数长度、拼接、访问第几个珠子
  • 作用:保存和处理文本(用户名、路径、提示语、整行输入……)
  • 规则/坑点:cin >> s 读到空格就停;读整行要用 getline

4.1 基础操作:拼接、长度、访问字符(可运行)

#include <iostream>
#include <string>

int main() {
    std::string name = "qtcraft";
    std::string msg = "hello, " + name;

    std::cout << "msg=" << msg << '\n';
    std::cout << "len=" << msg.size() << '\n';

    if (!msg.empty()) {
        std::cout << "first=" << msg[0] << '\n';
        std::cout << "last=" << msg[msg.size() - 1] << '\n';
    }

    return 0;
}

这里 size()empty() 你只记两条规则就够用:

  • s.size() 返回字符个数(长度)
  • s.empty() 判断是否为空串(长度是否为 0)

访问字符时的坑点:

  • s[i] 不会帮你检查越界;i 超了就会出问题
  • 你想要“带检查”,可以用 s.at(i)(越界会抛异常),新手阶段先知道有这个选项即可

4.2 读字符串:读单词 vs 读整行

4.2.1 cin >> s:读一个“词”

std::string s;
std::cin >> s;

它会跳过前导空白,然后读到下一个空白为止,所以输入 hello world,读到的是 hello

4.2.2 getline(cin, line):读一整行

#include <iostream>
#include <string>

int main() {
    std::string line;
    std::getline(std::cin, line);
    std::cout << "line=" << line << '\n';
    return 0;
}

如果你前面已经用过 cin >> 读数字/单词,再用 getline 读整行,最容易踩到一个经典坑:上一句输入留下的换行会被 getline 立刻读走,导致读到空行。

一个新手好用的写法是这样:

std::string line;
std::getline(std::cin >> std::ws, line);

这里 std::ws 也是操纵符(见 01 章 0.4 对“操纵符”的解释),作用是:先吃掉前导空白(包括那个残留的换行),再开始读整行。


常见坑

  • 错误:数组下标从 1 开始用
    结果:要么漏掉第一个元素,要么访问越界
    正确:长度 n 的数组,下标永远是 0 ~ n-1

  • 错误:访问 a[n](越界)
    结果:输出乱、程序崩溃、结果不稳定(越界是典型“玄学 bug”)
    正确:循环条件用 i < n,不要写 i <= n

  • 错误:把 cin >> s 当成“读一整行”
    结果:遇到空格就截断
    正确:读整行用 std::getline

  • 错误:cin >> 后直接 getline
    结果:读到空行
    正确:用 std::getline(std::cin >> std::ws, line) 或先清掉换行

  • 错误:用 s[i] 访问字符串但 i 可能越界
    结果:行为不稳定
    正确:先判断范围,或用 s.at(i) 做越界检查


小练习

练习 1:统计 5 个数(数组基础)

  • 题目:读入 5 个整数,输出 sum、max、min、avg
  • 输入/输出:输入 5 个整数,输出四个统计值
  • 约束:avg 用小数输出
  • 提示:avg = (double)sum / nstatic_cast<double>(sum) / n
  • 目标:练数组 + 循环 + 基础统计

练习 2:反转数组(下标练习)

  • 题目:读入 6 个整数,把数组原地反转并输出
  • 输入/输出:输入 6 个整数,输出反转后的 6 个整数
  • 约束:必须原地反转(不额外开第二个数组)
  • 提示:交换 a[l]a[r],然后 l++r--
  • 目标:练下标边界与交换逻辑

练习 3:冒泡排序(算法入门)

  • 题目:读入 8 个整数,使用冒泡排序从小到大排序并输出
  • 输入/输出:输入 8 个整数,输出排序后的序列
  • 约束:只能用数组、循环、if;先不要用库排序
  • 提示:每一轮把最大的“冒”到末尾
  • 目标:练双重循环与比较交换

练习 4:统计整行文本(字符串遍历)

  • 题目:读入一整行字符串,统计字母、数字、空格的数量
  • 输入/输出:输入一行文本,输出三个计数
  • 约束:把整行读进来(必须用 getline
  • 提示:判断字符范围:'0'~'9''a'~'z''A'~'Z',空格是 ' '
  • 目标:练 string + 遍历 + 条件判断

练习 5:回文串判断(综合)

  • 题目:读入一行字符串,判断是否回文(从左读和从右读一样)
  • 输入/输出:输入一行,输出 palindrome=1palindrome=0
  • 约束:先只处理原样比较(不忽略空格、不忽略大小写)
  • 提示:双指针:l 从左、r 从右,向中间走
  • 目标:练 string 下标、边界控制、循环条件

小结

你现在应该掌握:

  • 数组的声明与初始化,以及下标从 0 开始的规则
  • 用循环遍历数组做统计、查找,并把逻辑封装成函数(数组 + 长度)
  • std::string 的常用操作:拼接、长度、访问字符
  • 读单词用 cin >>,读整行用 getline,并知道如何处理残留换行

下一篇会进入指针与引用:你会真正理解“数组 + 长度为什么要一起传”,以及内存地址到底是什么。