火龙信奥
  • 分享
  • 课程
  • 题库
  • 打卡
    • 代码对战
    • 快速对战
  • 题单
  • 团队
  • 荣誉墙
  • 商城
  • 登录 / 注册

第七天 数据的有序排兵布阵(数组、字符串与指针初探)

作者: 作者的头像   huolong , 时间:2026-08-27 13:47:51 , 所有人可见, 阅读  38

第七天 数据的有序排兵布阵(数组、字符串与指针初探)

  • 总时长:6 小时(上午 3 小时,下午 3 小时)
  • 培训目标:通过生动的生活化比喻,帮助零基础学生彻底理解一维数组、字符串、结构体与指针的基本原理,攻克历年初赛选择题与代码阅读常考盲区。

上午场:一维数组与字符串(09:00 - 12:00)

一、 为什么要用数组?(从变量到批量管理)

1. 独立变量的局限性

在前几天的学习中,我们学会了用变量来存数据。例如,要记录全班 5 个同学的数学成绩:

int s1 = 90;
int s2 = 85;
int s3 = 92;
int s4 = 78;
int s5 = 95;

如果班级有 50 个人、500 个人,或者我们要对全校几万名学生的成绩进行排序、查找、求平均分,定义几万个变量(s1, s2, s3 ... s50000)显然是不可能的。我们需要一种能批量管理同类数据的容器——数组(Array)。

2. 什么是数组?

  • 定义:数组是一组相同类型数据的有序集合。
  • 两大核心特征:
  • 类型相同:一个数组里的所有元素必须是同一种数据类型(比如全是整数 int,或者全是字符 char)。不能在一个数组里既放整数又放小数。
  • 内存连续:在计算机的底层内存里,数组的各个元素是紧挨在一起、排成一排存放的。

二、 一维数组的定义、初始化与下标越界

1. 一维数组的语法格式

在 C++ 中,定义一维数组的语法为:

数据类型 数组名[数组长度];

例如:

int scores[5]; // 定义了一个能存放5个整型数据的数组

2. 下标:数组元素的“门牌号”

计算机里的数组有一个极其重要的特点:下标(Index)从 0 开始! 如果我们定义了 int scores[5];,那么这 5 个元素的合法下标分别是 0, 1, 2, 3, 4。 * scores[0] 代表第 1 个元素。 * scores[4] 代表第 5 个元素。 * scores[5] 大忌! 这是根本不存在的第 6 个元素。

3. 致命陷阱:数组越界(Array Out of Bounds)

💡 经典考点警示:在初赛选择题或代码阅读题中,常常会出现访问不存在的下标的陷阱。 * 如果你写了 scores[5] = 100; 或者 cout << scores[-1];,这就叫数组越界。 * 在 C++ 中,编译器可能不会报错,但在运行时会导致未定义行为(Undefined Behavior),轻则数据错乱,重则程序直接崩溃。


三、 字符数组与 C++ 的 string 类

1. 字符数组(C 风格字符串)

如果要存储一句话或一个单词(比如 "hello"),用单个字符变量太麻烦,我们用字符数组:

char word[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
// 或者简写为:
char word[] = "hello";
  • '\0' 的秘密:在传统的 C 语言字符数组中,字符串的结尾必须有一个隐式的空字符 '\0'(其 ASCII 码值为 0),用来标志“字符串到这里结束了”。

2. 现代 C++ 的神器:string 类

在现代 C++ 编程中,处理字符串我们更喜欢用标准库提供的 string 类(需要包含头文件 #include <string>)。 * string 的优势: 1. 长度动态可变:不需要像普通数组那样在一开始就死死规定死最大长度。 2. 支持直接拼接:可以用 + 运算符把两个字符串连起来,或者把一个字符加到字符串后面。 3. 常用内置函数: * .length() 或 .size():获取字符串的长度(这两个函数在 string 中返回值完全相同)。 * s[i]:像数组一样通过下标访问第 $i$ 个字符。


四、 子串的概念与计算公式

  • 什么是子串(Substring):原字符串中任意一段连续的字符所组成的新字符串。
  • 注意:“连续”是关键!比如字符串 "abc","ac" 就不是它的子串,因为不连续;而 "ab"、"bc"、"b" 都是它的子串。
  • 子串个数计算公式:
  • 如果一个字符串的长度为 $len$,它包含的非空子串总数公式为: $$\text{非空子串数} = \frac{len \times (len + 1)}{2}$$
  • 如果算上空串 "",则总数再加 1: $$\text{总子串数} = \frac{len \times (len + 1)}{2} + 1$$
  • 例题:字符串 "copyright" 长度为 9,其总子串数(含空串)为 $\frac{9 \times 10}{2} + 1 = 46$ 个。

---

下午场:结构体、联合体与指针初识(14:00 - 17:00)

一、 结构体(struct)—— 自定义复合数据

1. 为什么需要结构体?

前面的数组要求“所有元素类型必须一致”。但现实世界中,一个物体往往包含多种属性。 比如一个“学生”档案: * 姓名:字符串 (string) * 年龄:整数 (int) * 成绩:浮点数 (float) 如果要用散装的变量存,非常混乱。C++ 允许我们用 struct(结构体)把这些不同类型的数据打包绑在一起,形成一个自定义的新数据类型。

2. 结构体的定义与使用

// 1. 定义结构体类型(注意大括号后边的分号 ';' 绝对不能丢!)
struct Student {
    string name;  // 名字
    int age;      // 年龄
    float score;  // 分数
};

int main() {
    // 2. 用自定义的类型定义变量
    Student s1;

    // 3. 用点(.)操作符访问和修改其内部成员
    s1.name = "张三";
    s1.age = 18;
    s1.score = 95.5;
}

二、 联合体(union)—— 内存共享的艺术

1. 什么是联合体?

联合体(Union,又称共用体)的外表看起来和结构体非常像,但它们的内部灵魂有着天壤之别。 * 结构体(Struct):每个成员都有自己独立的内存空间,总大小是所有成员大小的总和。 * 联合体(Union):所有成员共用同一块内存空间!

2. 联合体的核心特征

  1. 覆盖效应:因为共用内存,你在同一时刻只能往里面存一个成员的值。如果你给另一个成员赋值,之前那个成员的值就会被无情覆盖。
  2. 大小计算:联合体变量的大小,等于其占用内存空间最大的那个成员的大小。
  3. 语法访问:和结构体一样,同样使用点操作符 .。
union Data {
    int num;       // 占4字节
    float value;   // 占4字节
    char symbol;   // 占1字节
}; // 这个union总大小是4字节(取最大者)

三、 指针初识(Pointer)—— 探秘内存地址

1. 什么是内存地址?

想象一下,计算机的内存就像是一个巨大的宾馆,里面有几亿个房间。每个房间都有一个唯一的编号,这个编号就叫做地址(Address)。 当我们写 int x = 100; 时,操作系统会在内存里找一个空房间把 100 放进去,这个房间的编号比如说是 0x6ffe00。 * 房间里住的人是谁?是 100(这叫变量的值)。 * 房间的门牌号是多少?是 0x6ffe00(这叫内存地址)。

2. 什么是指针变量?

  • 指针:本质上就是一个专门用来存放内存地址的变量。
  • 两个核心符号:
  • & (取地址符):放在变量前面,表示“获取这个变量在内存中的房间编号(地址)”。
  • * (解引用符):放在指针变量前面,表示“顺着这个地址找到那个房间里住的数值”。

3. 基础代码剖析

int x = 101;     // 宾馆某个房间里住了 101 个人
int *p = &x;     // 我们造了一个指针 p(管家),把 x 的房间号(&x)交给了 p

这时候,p 手里就握着 x 的房间钥匙。

那如果遇到下面这种代码呢?大家注意听,历年必考陷阱:

int x = 101;
int y = 201;

int *p = &x; // p 拿着 x 的钥匙
int *q = &y; // q 拿着 y 的钥匙

p = q;       // 这一句干了什么?
  • 深度剖析 p = q;:
  • q 里面存的是 y 的地址。
  • p = q 是把 q 手里的钥匙复制了一份给 p。
  • 结果:指针 p 改变了指向,它不再指向 x 了,而是改去指向 y 的内存地址。
  • 注意:在这个过程中,x 和 y 变量本身的数值(101 和 201)没有发生任何改变,q 的指向也没有变。这在初赛选择题里考过好多次,记住:改的是指针的“指向”,没改房间里的本体数字!

---

随堂与课后实战强化练习卷(学生版)

班级:__ 姓名:__ 得分:__

一、 选择题(共 10 题)

  1. 下列关于 C++ string 类的说法,正确的是?( ) A. string 对象的长度在创建后不能改变。 B. 可以使用 + 运算符直接连接一个 string 对象和一个 char 类型的字符。 C. string 的 length() 和 size() 方法返回的值可能不同。 D. string 对象必须以 '\0' 结尾,且这个结尾符计入 length()。

  2. 阅读下述代码,请问修改 data 的 value 成员以存储 3.14,正确的方式是( )。 cpp union Data { int num; float value; char symbol; }; union Data data; A. data.value = 3.14; B. value.data = 3.14; C. data->value = 3.14; D. value->data = 3.14;

  3. 若串 S = “copyright”,其非空子串的个数是( )。 A. 72 B. 45 C. 46 D. 36

  4. 运行以下代码片段,最终的行为是( )。 cpp int x = 101; int y = 201; int *p = &x; int *q = &y; p = q; A. 将 x 的值赋为 201 B. 将 y 的值赋为 101 C. 将 q 指向 x 的地址 D. 将 p 指向 y 的地址

  5. 以下哪个不是 C++ 中的基本(内置)数据类型? A. int B. float C. struct D. char

  6. 下面关于数组的说法,错误的是( )。 A. 数组在内存中占据连续的存储空间 B. 数组定义后,其长度是固定的,不能在运行中随意变大 C. 访问数组元素时,下标可以是从 1 开始的任意正整数 D. 数组名代表了该数组在内存中的起始地址

  7. 一个字符串中任意个连续的字符组成的子序列称为该字符串的子串,则字符串 abcab 有( )个内容互不相同的子串。 A. 12 B. 13 C. 14 D. 15

  8. 假设有如下的结构体定义和变量声明,那么在 C++ 中正确访问 s 的 score 成员的方式是( )。 cpp struct Student { string name; int score; }; Student *p = new Student; Student s; A. p.score B. s->score C. p->score D. *p.score

  9. 设有如下程序段,运行后输出的结果是( )。 cpp #include <iostream> using namespace std; int main() { int a = 10, b = 20; int *p1 = &a, *p2 = &b; *p1 = *p2; cout << a << " " << b << endl; return 0; } A. 10 20 B. 20 20 C. 10 10 D. 20 10

  10. 下列关于 C++ 联合体(union)的叙述中,正确的是( )。 A. 联合体中的所有成员在内存中占用各自独立的存储空间 B. 联合体的大小等于其中所有成员所占内存空间的总和 C. 在同一时刻,联合体中所有成员都能同时被赋予有效的新值 D. 联合体中的多个成员共用同一块内存空间,后赋值的成员会覆盖先赋值的成员


(注:教师答案与详细解析页在下方,建议打印前单独切分)

\newpage


课后练习卷 —— 标准答案与详细解析

  1. 正确答案:B
  2. 详细解析:

    • 选项 A 错误:string 是动态字符串类,其长度在创建后可以根据需要随时通过函数改变。
    • 选项 B 正确:C++ 标准库对 string 类重载了 + 运算符,完美支持 string 对象与 char 字符直接相加。
    • 选项 C 错误:在 string 类中,length() 和 size() 是完全等价的同义词,返回的字符个数必然相同。
    • 选项 D 错误:string 内部不依赖显式的 '\0' 决定边界,且 length() 绝对不包含任何潜在的结束符。
  3. 正确答案:A

  4. 详细解析:

    • data 是我们在主函数中定义的联合体变量名,对于普通变量,访问其内部成员应用点操作符 .,即 data.value = 3.14;。
    • 选项 C、D 错误:箭头操作符 -> 专门用于指针变量访问结构体或联合体成员。此处 data 是实体变量而非指针,故不能用 ->。
  5. 正确答案:B

  6. 详细解析:

    • 首先数一下字符串“copyright”的长度 $len = 9$。
    • 包含空串在内的所有子串计算公式为:$\frac{len \times (len + 1)}{2} + 1 = \frac{9 \times 10}{2} + 1 = 45 + 1 = 46$ 个。
    • 题目要求的是非空子串的个数,因此必须把那个空的子串 "" 减掉:$46 - 1 = 45$ 个。故本题选 B。
  7. 正确答案:D

  8. 详细解析:

    • 初始状态下,p 指向 x 的房间,q 指向 y 的房间。
    • 执行 p = q; 语句时,是将 q 变量里存储的“房间编号(地址)”复制一份给 p。
    • 执行完后,p 放弃了原本指向的 x,改去指向了 q 当时指向的目标(即 y 的地址)。这没有改变任何整型变量本身的值,因此选 D。
  9. 正确答案:C

  10. 详细解析:

    • C++ 的基本(内置)数据类型主要包括:整型(int)、浮点型(float, double)、字符型(char)、布尔型(bool)等。
    • struct(结构体)是程序员根据需求自定义的复合数据类型,不属于基本数据类型范畴。
  11. 正确答案:C

  12. 详细解析:

    • 选项 A、B、D 均是数组的正确特性(内存连续、长度固定、数组名即首地址)。
    • 选项 C 错误:在 C/C++ 语言中,数组的下标严格从 0 开始,而不是从 1 开始。若定义 int a[5];,合法的下标是 0 到 4。
  13. 正确答案:B

  14. 详细解析:

    • 列举字符串 "abcab" 的所有不同子串:
    • 空串:"" (1个)
    • 长度为 1 的子串:"a", "b", "c" (3个独特的)
    • 长度为 2 的子串:"ab", "bc", "ca", "ba" (4个独特的)
    • 长度为 3 的子串:"abc", "bca", "cab" 等不重复提取。
    • 总计不同内容子串数(含空串后按不同内容去重):共 13 个。故选 B。
  15. 正确答案:C

  16. 解析:

    • s 是普通结构体变量,访问用 s.score。
    • p 是结构体指针(Student *p),对于结构体指针,必须使用箭头操作符 -> 来访问其成员,即 p->score。故选 C。
  17. 解析:

  18. a = 10, b = 20。p1 指向 a,p2 指向 b。
  19. 语句 *p1 = *p2; 的意思是:把 p2 指向房间里的值(即 b 的值 20)赋值给 p1 指向的房间(即修改 a 的值)。
  20. 因此执行后 a 变成了 20,b 保持 20 不变。输出结果为 20 20。故选 B。

  21. 解析:

    • 选项 A、B 描述的是结构体(struct)的特征。
    • 选项 C 错误,联合体同一时间只能有效保存最后一次写入的值。
    • 选项 D 正确:联合体成员共用同一块内存,后赋值的成员会覆盖先赋值的成员。

—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com

关于火龙

  • 关于我们
  • 学员获奖
  • 预约试听
  • ACM课程
  • CSP课程
  • 学习指南

帮助中心

  • 用户协议
  • 打字练习
  • 在线画图
  • DevC++下载
  • CSP报名
  • GESP官网

推荐课程

  • C++零基础入门(可试看)
  • C++进阶提升
  • GESP考级辅导
  • GESP打卡
  • CSP-J/S打卡

公众号

火龙信奥公众号二维码

地址:义乌市北门街188号新天地商厦二楼2F 邮箱:wdlok305@126.com

© 2017-2026 义乌市睿码科技有限公司版权所有 浙ICP备2021013995号

火龙信奥
请输入登录信息


请完成安全验证
验证码底图 滑块
向右拖动滑块完成验证
请输入用户名 / 绑定的手机号码



请输入注册信息(手机号验证码注册)





验证码5分钟有效,60秒内不可重复获取,每日最多3次

微信登录

微信登录二维码

正在生成二维码...

账号已过期,请续期。
去续期

绑定手机号

📱

为了更好地保护您的账号安全,享受完整的平台服务

请您尽快绑定手机号码