第七天 数据的有序排兵布阵(数组、字符串与指针初探)
- 总时长: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. 联合体的核心特征
- 覆盖效应:因为共用内存,你在同一时刻只能往里面存一个成员的值。如果你给另一个成员赋值,之前那个成员的值就会被无情覆盖。
- 大小计算:联合体变量的大小,等于其占用内存空间最大的那个成员的大小。
- 语法访问:和结构体一样,同样使用点操作符
.。
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 题)
-
下列关于 C++
string类的说法,正确的是?( ) A.string对象的长度在创建后不能改变。 B. 可以使用+运算符直接连接一个string对象和一个char类型的字符。 C.string的length()和size()方法返回的值可能不同。 D.string对象必须以'\0'结尾,且这个结尾符计入length()。 -
阅读下述代码,请问修改
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; -
若串 S = “copyright”,其非空子串的个数是( )。 A. 72 B. 45 C. 46 D. 36
-
运行以下代码片段,最终的行为是( )。
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的地址 -
以下哪个不是 C++ 中的基本(内置)数据类型? A.
intB.floatC.structD.char -
下面关于数组的说法,错误的是( )。 A. 数组在内存中占据连续的存储空间 B. 数组定义后,其长度是固定的,不能在运行中随意变大 C. 访问数组元素时,下标可以是从 1 开始的任意正整数 D. 数组名代表了该数组在内存中的起始地址
-
一个字符串中任意个连续的字符组成的子序列称为该字符串的子串,则字符串
abcab有( )个内容互不相同的子串。 A. 12 B. 13 C. 14 D. 15 -
假设有如下的结构体定义和变量声明,那么在 C++ 中正确访问
s的score成员的方式是( )。cpp struct Student { string name; int score; }; Student *p = new Student; Student s;A.p.scoreB.s->scoreC.p->scoreD.*p.score -
设有如下程序段,运行后输出的结果是( )。
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 -
下列关于 C++ 联合体(
union)的叙述中,正确的是( )。 A. 联合体中的所有成员在内存中占用各自独立的存储空间 B. 联合体的大小等于其中所有成员所占内存空间的总和 C. 在同一时刻,联合体中所有成员都能同时被赋予有效的新值 D. 联合体中的多个成员共用同一块内存空间,后赋值的成员会覆盖先赋值的成员
(注:教师答案与详细解析页在下方,建议打印前单独切分)
\newpage
课后练习卷 —— 标准答案与详细解析
- 正确答案:B
-
详细解析:
- 选项 A 错误:
string是动态字符串类,其长度在创建后可以根据需要随时通过函数改变。 - 选项 B 正确:C++ 标准库对
string类重载了+运算符,完美支持string对象与char字符直接相加。 - 选项 C 错误:在
string类中,length()和size()是完全等价的同义词,返回的字符个数必然相同。 - 选项 D 错误:
string内部不依赖显式的'\0'决定边界,且length()绝对不包含任何潜在的结束符。
- 选项 A 错误:
-
正确答案:A
-
详细解析:
data是我们在主函数中定义的联合体变量名,对于普通变量,访问其内部成员应用点操作符.,即data.value = 3.14;。- 选项 C、D 错误:箭头操作符
->专门用于指针变量访问结构体或联合体成员。此处data是实体变量而非指针,故不能用->。
-
正确答案:B
-
详细解析:
- 首先数一下字符串“copyright”的长度 $len = 9$。
- 包含空串在内的所有子串计算公式为:$\frac{len \times (len + 1)}{2} + 1 = \frac{9 \times 10}{2} + 1 = 45 + 1 = 46$ 个。
- 题目要求的是非空子串的个数,因此必须把那个空的子串
""减掉:$46 - 1 = 45$ 个。故本题选 B。
-
正确答案:D
-
详细解析:
- 初始状态下,
p指向x的房间,q指向y的房间。 - 执行
p = q;语句时,是将q变量里存储的“房间编号(地址)”复制一份给p。 - 执行完后,
p放弃了原本指向的x,改去指向了q当时指向的目标(即y的地址)。这没有改变任何整型变量本身的值,因此选 D。
- 初始状态下,
-
正确答案:C
-
详细解析:
- C++ 的基本(内置)数据类型主要包括:整型(
int)、浮点型(float,double)、字符型(char)、布尔型(bool)等。 struct(结构体)是程序员根据需求自定义的复合数据类型,不属于基本数据类型范畴。
- C++ 的基本(内置)数据类型主要包括:整型(
-
正确答案:C
-
详细解析:
- 选项 A、B、D 均是数组的正确特性(内存连续、长度固定、数组名即首地址)。
- 选项 C 错误:在 C/C++ 语言中,数组的下标严格从 0 开始,而不是从 1 开始。若定义
int a[5];,合法的下标是0到4。
-
正确答案:B
-
详细解析:
- 列举字符串
"abcab"的所有不同子串: - 空串:
""(1个) - 长度为 1 的子串:
"a","b","c"(3个独特的) - 长度为 2 的子串:
"ab","bc","ca","ba"(4个独特的) - 长度为 3 的子串:
"abc","bca","cab"等不重复提取。 - 总计不同内容子串数(含空串后按不同内容去重):共 13 个。故选 B。
- 列举字符串
-
正确答案:C
-
解析:
s是普通结构体变量,访问用s.score。p是结构体指针(Student *p),对于结构体指针,必须使用箭头操作符->来访问其成员,即p->score。故选 C。
-
解析:
a = 10, b = 20。p1指向a,p2指向b。- 语句
*p1 = *p2;的意思是:把p2指向房间里的值(即b的值20)赋值给p1指向的房间(即修改a的值)。 -
因此执行后
a变成了 20,b保持 20 不变。输出结果为20 20。故选 B。 -
解析:
- 选项 A、B 描述的是结构体(
struct)的特征。 - 选项 C 错误,联合体同一时间只能有效保存最后一次写入的值。
- 选项 D 正确:联合体成员共用同一块内存,后赋值的成员会覆盖先赋值的成员。
- 选项 A、B 描述的是结构体(
—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com