GESP编程能力等级认证一本通(C++ 四级)

978-7-115-69159-0
作者: 王桂平张兵郑兰
译者:
编辑: 吴晋瑜
分类: 其他

图书目录:

详情

 “GESP编程能力等级认证一本通”是专门为中小学生编写的一套学习C++编程和算法的图书。本套图书严格围绕中国计算机学会(CCF)发布的《CCF编程能力等级认证标准C++&Python认证标准》而设计。   本书对应C++四级,共15章,内容包括二维及多维数组、指针变量及应用、指针与数组的综合应用、排序基本概念及sort函数的使用、结构体、函数进阶、递归函数、递推算法基础、递推算法进阶、递推与递归的综合应用、简单的排序算法、排序综合应用、算法及算法复杂度、文件输入/输出、异常处理机制。   本书配备了题库、课件、课程视频(在线)等资源,可用作中小学编程社团的教材,也可以作为青少年编程培训机构的培训教材,还可以作为青少年编程等级考试和编程竞赛的入门参考书。

图书摘要

版权信息

书名:GESP编程能力等级认证一本通(C++四级)

ISBN:978-7-115-69159-0

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。


版  权

主  编 王桂平 张 兵 郑 兰

副 主 编 王 旭 王 冬 易 黎

责任编辑 吴晋瑜

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

内 容 提 要

“GESP编程能力等级认证一本通”是专门为中小学生编写的一套学习C++编程和算法的图书。本套图书严格围绕中国计算机学会(CCF)发布的《CCF编程能力等级认证标准C++ & Python》而设计。

本书对应C++四级,共15章,内容包括二维及多维数组、指针变量及应用、指针与数组的综合应用、排序基本概念及sort函数的使用、结构体、函数进阶、递归函数、递推算法基础、递推算法进阶、递推与递归的综合应用、简单的排序算法、排序综合应用、算法及算法复杂度、文件输入/输出、异常处理机制。

本书配备了题库、课件、课程视频(在线)等资源,可用作中小学编程社团的教材,也可以作为青少年编程培训机构的培训教材,还可以作为青少年编程等级考试和编程竞赛的入门参考书。

前  言

在数字化浪潮席卷全球的当下,编程已成为青少年迈向未来、拥抱科技的关键技能之一。国家对编程教育高度重视,早在2017年,国务院发布的《新一代人工智能发展规划》明确提出,要在中小学阶段设置人工智能相关课程,逐步推广编程教育。教育部随后在《教育信息化2.0行动计划》中进一步强调,要完善课程方案和课程标准,充实适应信息时代、智能时代发展需要的人工智能和编程课程内容。这些政策的出台,为青少年编程教育指明了方向,奠定了坚实的基础。

在这样的背景下,中国计算机学会(CCF)发起了编程能力等级认证(GESP)项目,旨在为青少年计算机和编程学习者提供一个权威、公正的学业能力验证平台。GESP覆盖中小学全学段,通过科学分级的考试体系,全面考查学生在编程知识、操作能力以及理论框架等方面的掌握程度。其中,C++作为一门广泛应用于系统软件、应用软件、游戏开发等多个领域的编程语言,既是编程教育的普及语言,也是信息学奥林匹克竞赛的指定语言。GESP C++认证共分为一至八级,每一级针对不同年龄段和学习阶段的学生设定了明确的知识目标和技能要求。

本书正是为了满足广大青少年编程学习者的需求精心编写而成。本书由“傲梦少年”联盟发起,编委会成员包括各级教师进修学院的教研员,以及来自大中小学的资深教师,他们凭借丰富的教学经验和对编程教育的深刻理解,确保了本书内容的科学性、实用性和针对性。

本书紧密围绕GESP C++ 四级的考试大纲,内容包括二维及多维数组、指针变量及应用、指针与数组的综合应用、排序基本概念及sort函数的使用、结构体、函数进阶、递归函数、递推算法基础、递推算法进阶、递推与递归的综合应用、简单的排序算法、排序综合应用、算法及算法复杂度、文件输入/输出、异常处理机制。此外,本书的附录A介绍了本书配套资源的使用方法,附录B是基础知识练习答案。

在内容编排上,我们注重循序渐进,从基础概念入手,逐步引导学生深入学习编程的思维方式和实践技巧。同时,书中配备了大量生动有趣的实例和习题,旨在帮助学生更好地理解和掌握所学知识,提高编程实践能力。

此外,本书的编写还充分考虑了国家编程教育政策的要求,致力于激发青少年对编程的兴趣,培养他们的逻辑思维和创新能力。通过学习本书,学生不仅能够为参加GESP C++ 四级认证考试做好充分准备,还将在编程的道路上迈出坚实的一步,为未来的学习和发展打下坚实的基础。

我们相信,在国家政策的引领下,在社会各界的共同努力下,青少年编程教育将迎来更加美好的明天。希望本书能够成为广大青少年编程学习者的良师益友,助力他们在编程的世界里绽放光彩,开启通往未来科技的大门。

作者

2026年1月

编 委 会

(排名不分先后)

主 编:王桂平 张 兵 郑 兰

副主编:王 旭(重庆市巴蜀中学校)

    王 冬(重庆智慧教育创新中心)

    易 黎(重庆市开州区教师进修学校)

参 编:沈菊颖(重庆市育才中学校)

    向 毅(重庆市巴蜀中学校)

    刘思婧(重庆市巴蜀中学校)

    袁华容(重庆市沙坪坝区树人博文小学校)

    潘以华(重庆市北碚区王朴中学校)

    李正平(重庆市育才中学校)

    王卫坚(苏州科技城外国语学校)

    何双双(重庆市忠县中学校)

    余 强(重庆第二十九中学校)

    傅祥斌(重庆市第八中学校)

    唐瑶琼(重庆科学城南开景阳小学校)

    王思宇(重庆巴蜀小学校)

    梁威鹏(温州大学)

    张凯亮(重庆第三十中学校)

    王建明(重庆市潼南中学校)

    刘素琴(重庆市北碚区王朴中学校)

    涂月婷(重庆市华新实验小学)

    吴泽伟(广东省汕头市潮南区博崇实验学校 )

    王思宇(重庆市巴蜀小学校)

资源与支持

资源获取

本书提供如下资源:

配套视频课程(在线课程)

源代码

PPT文件

在线评测平台(小虫OJ,https://www.xiaochongoj.cn/)

本书思维导图

异步社区7天VIP会员

要获得以上资源,读者可以扫描下方二维码,根据指引领取。

提交勘误

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎读者将发现的问题反馈给我们,帮助我们提升图书的质量。

当读者发现错误时,请登录异步社区(https://www.epubit.com),按书名搜索,进入本书页面,单击“发表勘误”,输入错误信息,单击“提交勘误”按钮即可(见右图)。本书的作者和编辑会对读者提交的错误信息进行审核,确认并接受后,将赠予读者异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

与我们联系

本书责任编辑的联系邮箱是wujinyu@ptpress.com.cn。

如果读者对本书有任何疑问或建议,请发送邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果读者有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们。

如果读者所在的学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果读者在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请将怀疑有侵权行为的链接发邮件给我们。这一举动是对作者权益的保护,也是我们持续为广大读者提供有价值的内容的动力之源。

关于异步社区和异步图书

“异步社区”(www.epubit.com)是由人民邮电出版社创办的IT专业图书社区,于2015年8月上线运营,致力于优质内容的出版和分享,为读者提供高品质的学习内容,为作译者提供专业的出版服务,实现作者与读者在线交流互动,以及传统出版与数字出版的融合发展。

“异步图书”是异步社区策划出版的精品IT图书的品牌,依托于人民邮电出版社在计算机图书领域多年来的发展与积淀。异步图书面向IT行业以及各行业使用IT技术的用户。

第1章 二维及多维数组

本章主要内容

从一维数组过渡到二维数组。

二维数组的定义和引用。

二维字符数组的应用。

三维及多维数组的概念。

1.1 数组概述

我们学过的变量,每个时刻只能存储一个数据(如整数),局限性很大。当程序中有多个数据时,我们需要为每个数据定义变量,但这会使代码变得冗长且难以维护,因此通常使用数组、集合等数据结构来集中存储和管理这些数据。

如果数据很多,具有相同的数据类型,并且存在一定的内在联系,如一个数列的前40项、100个学生的数学成绩,我们就可以把这些数据放到同一个数组中。例如,我们可以定义一个一维数组a来存储Fibonacci数列的前40项。首先将数组的所有元素初始化为0,然后初始化第1项和第2项,最后用循环递推出第3~40项。

int a[40] = {0};
a[1] = a[2] = 1;
for(int i=3; i<=40; i++)
      a[i] = a[i-1] + a[i-2];

我们可以把数组a想象成一个一维的表格,就是一行,如图1.1所示。

图1.1 用一维数组存储Fibonacci数列

用数组名a代表这一批数据,用元素的序号(下标)来区分这些数据,如a[5]和a[8]为两个不同的数据。这样处理的好处有两个:①可以省略很多个变量名,使得程序精练;②便于对这些数据作统一处理。

具体来讲,数组是一组有顺序的数据的集合,用统一的名字代表这一组数据,用序号来区分这组数据中的各个数据;数组中每个数据称为数组的元素。

数组的特点如下。

(1)具有类型属性。在定义数组时必须指定数组的类型,与普通变量的定义类似。

(2)一个数组在内存中占一片连续的存储单元,就像在学校里,一个年级各个班的教室也是挨着的。

元素的特点如下。

(1)同一个数组中的所有元素必须属于同一数据类型。

(2)用数组名和下标唯一地标识每个数组元素,例如,a是一个一维数组,a[5]是它的一个元素。

(3)在C++中用“[下标]”这种形式来表示下标,并且下标是从0开始的。但是在编程解题时,根据习惯,可以从第1个元素开始存储数据,也就是说第0个元素不用。例如,在图1.1中,第0个元素可以不用,从a[1]开始存储Fibonacci数列的每一项。

1.2 二维数组的定义和引用

我们在《GESP编程能力等级认证一本通(C++二级)》一书中专门介绍了通过编程处理数学上由数字组成的一维和二维数表。一维数表可以用一维数组存储,二维数表可以用二维数组存储。

例如,图1.2(a)所示的杨辉三角形按直角三角形显示,这样每一行除了首尾的1,其他数等于左上方和正上方2个数之和;图1.2(b)所示的是由数字构成的图案。

图1.2 具有行和列的二维数表

为了描述二维数表中的每一个数,需要告知它的行号和列号。对应到数组里,就是有两个下标——行下标和列下标。具有两个下标的数组称为二维数组。

此外,在数学上,由一些数排成非常规则的阵列,称为矩阵,如图1.3所示。数学上的矩阵,编程解题时也是用二维数组存储。

图1.3 矩阵

1.定义二维数组

定义二维数组的一般形式为

数据类型 数组名[行的数目][列的数目];

第一对方括号内填的是行的数目,第二对方括号内填的是列的数目。以下代码定义了两个二维数组a和b:

int a[3][4];   //定义a为3行4列的二维数组
float b[2][5];  //定义b为2行5列的二维数组

上面的数组a共有3×4 = 12个元素,其中第0行的4个元素为a[0][0]、a[0][1]、a[0][2]、a[0][3],第1行的4个元素为a[1][0]、a[1][1]、a[1][2]、a[1][3],第2行的4个元素为a[2][0]、a[2][1]、a[2][2]、a[2][3],如图1.4(a)所示。数组b共有2×5 = 10个元素,如图1.4(b)所示。

图1.4 二维数组

数组a的12个元素是按行顺序存储的,即在内存中先按顺序存放第0行的4个元素,然后是第1行的4个元素,最后是第2行的4个元素,如图1.5所示。这12个元素在内存中也是连续存放的,如图1.6所示。

图1.5 二维数组元素的存储顺序

图1.6 二维数组在内存中的存储情形

从以上描述可以看出,在内存中并不存在二维的存储结构,二维数组在内存中的存储情形与一维数组是完全一样的。我们可以想象,二维数组在存储时被拉伸为一维数组,如图1.7所示。在画示意图时,连续的存储空间根据需要可以横着画,也可以竖着画。

图1.7 二维数组在存储时被拉伸为一维数组

2.引用二维数组的元素

二维数组元素的引用形式为

数组名[行下标][列下标];

代码示例如下。

a[2][3] = b[1][2] + 1;
b[1][2] = b[1][1] + b[1][3];

注意,定义数组和引用数组元素时,方括号内的数字含义不一样。定义数组时,如“int a[3][4];”,方括号内的数字代表行和列的数目,即表示“几个”。引用数组元素时,如“a[2][3]”,方括号内的数字代表行和列的序号,即表示“第几个”。“几个”和“第几个”含义不一样。

3.二维数组的初始化

二维数组的初始化有两类方法:分行初始化方法和按元素排列顺序初始化方法。

(1)分行初始化方法。在花括号内再以花括号的形式将每行元素列举出来,有以下3种形式。

① 全部元素初始化。示例如下。

int a[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };

② 部分元素初始化。示例如下。

int a[2][3] = { { 1, 2 }, { 4 } };
//a[0][2]、a[1][1]、a[1][2]由编译器自动赋值为0

③ 初始化时第一维长度省略。示例如下。

int a[ ][3]={ { 1 }, { 4, 5 } };

第一维下标是根据最外围花括号内花括号的对数来确定的,比如上述例子等价于

int a[2][3]={ { 1 }, { 4, 5 } };

注意,第二维的长度不能省略!

(2)按元素排列顺序初始化方法。把所有元素按元素的排列顺序在花括号内列举出来,同样也有3种形式。

① 全部元素初始化。示例如下。

int a[2][3] = { 1, 2, 3, 4, 5, 6 };

② 部分元素初始化。示例如下。

int a[2][3] = { 1, 2, 4 };

没有获得初始值的元素,编译器会自动初始化为0。因此,如果想给所有元素初始化为0,可以用以下代码:

int a[2][3] = { 0 };

③ 初始化时第一维长度省略。示例如下。

int a[ ][3]={ 1, 2, 3, 4, 5, 6, 7, 8 };

花括号内有8个数据,前面3个是第0行的元素,中间3个是第1行的元素,最后两个是第2行的元素,编译器对第2行的最后一个元素a[2][2]自动赋值为0。这样该数组一共有3行,即上述语句等价于

int a[3][3]={1, 2, 3, 4, 5, 6, 7, 8 };

同样要注意,第二维的长度不能省略!

4.用二重循环处理二维数组

跟一维数组一样,二维数组的一个优势是可以通过循环对所有元素进行统一处理。这种统一处理有时称为遍历。所谓遍历,就是访问每个元素一次且仅一次,不能重复也不能有遗漏。遍历二维数组需要采用二重循环。例如,输出二维数组的每个元素,就需要访问每个元素一次且仅一次。

假设有一个3×4的二维数组a,则遍历该数组要用到下面的二重循环:

int i, j;                   //循环变量
for(i=0; i<3; i++ ){        //遍历第0行~第2行
    for(j=0; j<4; j++ ){    //遍历每行的第0列~第3列元素
        //在此处访问a[i][j],保证能访问每个元素一次且仅一次
    }
}

为避免混淆,一般约定用i作为行下标的循环变量,用j作为列下标的循环变量。

5.用一重循环处理二维数组中部分元素

如果只需处理二维数组中部分元素,特别是这些元素的行下标或列下标具有某种规律,往往可以用一重循环实现,详见以下代码示例及本章案例1。

int i, j, d[100][100]; //二维数组d
for(i=0; i<100; i++)   //对第2列全部元素统一处理
     d[i][2] = ...;
for(j=0; i<100; j++)  //对第1行全部元素统一处理
     d[1][j] = ...;
for(i=0; i<100; i++)  //对对角线上的元素统一处理, 对角线上的元素: 行下标=列下标
     d[i][i] = ...;

1.3 案例1:输出杨辉三角形前n行

【题目描述】

输入一个正整数n,输出杨辉三角形前n行,每个数占4个字符宽度,右对齐。

【输入描述】

输入数据占一行,为一个正整数n,1≤n≤10。

【输出描述】

输出n行,第几行就有几个数,每个数占4个字符宽度,右对齐。

【样例输入】
【样例输出】
8
 
 
 
 
1
1   1
1   2   1
1   3   3   1
1   4   6   4   1
1   5  10  10   5   1
1   6  15  20  15   6   1
1   7  21  35  35  21   7   1
【题目分析】

定义二维数组yh以存储杨辉三角形。本题可以先在二维数组yh里填写好杨辉三角形前10行各个数,然后根据输入的n值,输出前n行。

注意,二维数组行下标和列下标都是从0开始计起,但在本题,为了和习惯一致,第0行、第0列不用,从第1行、第1列开始存储。

先把第1行、第2行一共3个元素初始化为1,yh[1][1] = yh[2][1] = yh[2][2] = 1。

再用一重循环将第3~10行的第1列及主对角线上的元素初始化为1。

最后用二重循环递推出其他元素,即yh[i][j] = yh[i-1][j-1] + yh[i-1][j],这个公式的含义是每个元素 = 左上方元素 + 正上方元素。外层for循环控制循环变量i从3取到10、内层for循环控制循环变量j从2取到i-1。本书第8章和第9章会专门介绍递推。代码如下。

#include <bits/stdc++.h>
using namespace std;
int main( )
{
    int i, j, yh[21][21] = {0};          
    yh[1][1] = yh[2][1] = yh[2][2] = 1;  //第1行、第2行(第0行、第0列不用)
    for(i=3; i<=10; i++)
        yh[i][1] = yh[i][i] = 1;         //第1列和主对角线均为1
    for(i=3; i<=10; i++){
        for(j=2; j<=i-1; j++)
            yh[i][j] = yh[i-1][j-1] + yh[i-1][j];  //左上方元素+正上方元素
    }
    int n;  cin >>n;
    for(i=1; i<=n; i++){
        for(j=1; j<=i; j++)
            cout <<setw(4) <<right <<yh[i][j];
        cout <<endl;
    } 
    return 0;
}

【知识点】输出整数时的格式控制

输出浮点数时可以实现格式控制,在输出整数时也可以进行格式控制,主要的控制符及功能如下。

(1)setw(d)。输出一个整数时指定占d个字符的宽度,如果超过d位,按实际位数输出。

(2)left、right。使用setw()时,指定输出的整数是左对齐(left)还是右对齐(right),默认是右对齐。left、right可以直接放在“<<”后面,例如“cout <<left <<setw(12);”。

(3)setfill('0')、setfill('#')。使用setw()时,如果待输出的整数位数不足指定的位数,在最高位用字符'0'、'#'填充(默认用空格填充),也可以指定为其他字符。

1.4 一维和二维字符数组

如果数组类型为字符型(char),那么每个元素都为字符型。这种数组可以用来存储字符串。但是要注意,字符串末尾应该有串结束标志'\0',输出字符串时,只输出第一个'\0'前的所有字符。如果没有'\0',可能会输出一些非正常的字符。

因此,用来存储字符串的一维或二维字符数组,应该将数组元素初始化为0或'\0',二者的效果是一样的,因为'\0'就是ASCII编码值为0的字符。

例如,以下代码定义的s1是一维字符数组,可以存储一个字符串;s2是二维字符数组,每一行都可以用来存储一个字符串。

char s1[20] = {0};      //一维字符数组,可以用来存储一个字符串
char s2[10][20] = {0};  //二维字符数组,每一行都可以用来存储一个字符串

有时,可以在字符数组中设置'\0',这样可以实现只输出'\0'前的字符,详见以下案例2。

1.5 案例2:画布裁剪(GESP真题)

【题目描述】

小A在高为h、宽为w的矩形画布上绘制了一幅画。由于画布边缘留白太多,小A想适当地裁剪画布,只保留画的主体。具体来说,画布可以视为h行w列的字符矩阵,其中的字符均为ASCII码位于33和126之间的可见字符,小A只保留画布中由第x1行到第x2行、第y1列到第y2列构成的子矩阵。

小A将画布交给了你,你能帮他完成画布的裁剪吗?

【输入描述】

第1行,两个正整数h和w,分别表示画布的行数与列数。

第2行,4个正整数x1、x2、y1、y2,表示保留的行列边界。

接下来h行,每行一个长度为w的字符串,表示画布内容。

【输出描述】

输出共x2-x1+1行,每行一个长度为y2-y1+1的字符串,表示裁剪后的画布。

【样例输入1】
【样例输出1】
3 5
2 2 2 4
.....
.>_<.
.....
>_<
【样例输入2】
【样例输出2】
5 5
1 2 3 4
AbCdE
fGhIk
LmNoP
qRsTu
VwXyZ
Cd
hI
【数据范围】

对于所有测试点,保证1≤h, w≤100,1≤x1≤x2≤h,1≤y1≤y2≤w。

【题目分析】

本题需要用二维字符数组s存储输入数据中的画布内容。由于本题输入数据中x1、x2、y1、y2代表的行号和列号都是从1开始计起的,因此在存储画布时从第1行第1列开始存储。为了输出第x1行到第x2行,每行只输出从第y1到第y2个字符,可以在第y2个字符后面设置'\0',然后对第x1行到第x2行,每行从第y1个字符开始输出,第y2个字符后面的内容就不会输出了。代码如下。

#include <bits/stdc++.h>
using namespace std;
const int N = 105;
char s[N][N];  //全局变量,编译器会自动初始化为0
int n, m;
int main( )
{
    int x1, x2, y1, y2;
    cin >>n >>m;
    cin >>x1 >>x2 >>y1 >>y2;
    for (int i = 1; i <= n; i++)  //从第1行第1列开始存储
         cin >>s[i] + 1;
    for (int i = x1; i <= x2; i++) {
         s[i][y2 + 1] = 0;  //在第i行第y2个字符后面设置'\0'
         cout <<s[i] + y1 <<endl;
    }
    return 0;
}

1.6 案例3:黑白方块(GESP真题)

【题目描述】

小杨有一个n行m列的网格图,其中每个格子要么是白色,要么是黑色。

小杨想知道网格图中是否存在一个满足如下条件的子矩形:

(1)子矩形由4行4列组成;

(2)子矩形的第1行和第4行只包含白色格子;

(3)对于子矩形的第2行和第3行,只有第1个和第4个格子是白色的,其余格子都是黑色的。

请你编写程序,帮助小杨判断。

【输入描述】

第1行包含一个正整数t,代表测试用例组数。

接下来是t组测试用例。

对于每组测试用例,一共n+1行:第1行包含两个正整数n和m,含义如题面所示;之后n行,每行为一个长度为m的01串,代表网格图第i行格子的颜色,如果为0,则对应格子为白色,否则为黑色。

【输出描述】

对于每组测试用例,如果存在,输出Yes,否则输出No。

【样例输入】
【样例输出】
3
1 4
0110
5 5
00000
01100
01100
00001
01100
5 5
00000
01100
01110
00001
01100
No
Yes
No
【数据范围】

对于全部数据,保证有1≤t≤10,1≤n, m≤100。

【样例解释】

满足条件的子矩形就像下面这样:

0000
0110
0110
0000
【题目分析】

输入数据中的网格图虽然是由0和1组成的,但只能以字符串的形式读入,可以用char型数组或string型字符串。以字符串形式读入每一行后,可以转存到一个int型的二维数组w。

接下来就是要在w数组中检查是否存在样例解释中提到的4×4大小的子矩阵。可以先构造好题目中要求的4×4大小的子矩阵,将其存储在match二维数组中。由于数据量比较小,因此可以用暴力枚举的方式检查。

在w数组中枚举第i行第j列开始的4×4大小的子矩阵,这需要用四重循环实现,为了简化程序结构,定义函数check,实现检查从第xa行第ya列开始的4×4大小的子矩阵是否跟match完全一致。这样,main函数中只需要用二重循环枚举i和j所有可能的组合,再调用check函数完成检查。代码如下。

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int w[N][N];
int n, m;
int match[4][4];
//检查从第xa行第ya列开始的4×4大小的子矩阵是否跟match完全一致
bool check(int xa, int ya)
{
    for(int i=0; i<4; i++){
        for(int j=0; j<4; j++){
             if(w[xa+i][ya+j] != match[i][j])
                  return false;
        }
    }
    return true;
}
int main( )
{
    int t;
    cin >>t;
    for(int i=1; i<3; i++)  //设置好match,第1、2行,每行2个元素为1
        match[1][i] = match[2][i] = 1;
    while(t--) {  //多组测试数据处理
        cin >>n >>m;
        for(int i=1; i<=n; i++){  //从w的第1行第1列开始存储
            string s;
            cin >>s;
            for(int j=1; j<=m; j++){
                w[i][j] = s[j-1] - '0';
            }
        }
        int flag = 0;
        for(int i=1; i<=n-3; i++){
            for(int j=1; j<=m-3; j++){
                if(check(i, j)){
                    flag = 1;  break;
                }
            }
            if(flag)  break;  //break只能退出一重循环
        }
        if(flag)  cout<<"Yes" <<endl;
        else  cout<<"No" <<endl;
    }
    return 0;
}

1.7 矩阵中的特殊位置

如果矩阵的行数和列数相同,这种矩阵可以称为方阵。

对方阵,图1.8(a)两根虚线所示的位置分别是主对角线和次对角线(也称为副对角线)。

观察图1.8(a)所示的主对角线上的每个位置,行号和列号相同。因此,(1, 1),(2, 2), …, (i, i),…,(n, n)这些位置都是主对角线上的位置。这里用“(i, j)”这种形式表示第i行第j列位置。

观察图1.8(a)所示的次对角线上的每个位置,行号+列号 = 行数+1。在图1.8(a)中,行数n=6,因此,(1, 6)、(2, 5)等这些位置都是次对角线上的位置。

对方阵,还有对称位置,这里说的对称是指沿主对角线对称。例如,在图1.8(b)中,第3行第6列这个位置,沿主对角线对称的位置,是第6行第3列。

易知,(i, j)的对称位置是( j, i),即行号和列号互换。

怎么找对称位置呢?在图1.8(b)中,(3, 6)和(6, 3)这两个对称位置的连线、(i, j)和( j, i)这两个对称位置的连线都垂直于主对角线,而且两个对称位置到主对角线的距离是相等的。

特别地,主对角线上的每个位置(i, i),其对称位置就是它自己(i, i)。

图1.8 矩阵中的特殊位置

1.8 矩阵的变换——转置

矩阵的转置是把行变成列,把列变成行。转置是一种翻转。对图1.9(a)所示的3行5列的矩阵,转置是以左上角为支点进行翻转,把背面翻转到正面,转置后得到5行3列的矩阵,如图1.9(b)所示。

图1.9 矩阵的转置

注意,转置前第i行第j列的位置(i, j),转置后变成了第j行第i列(j, i),这一点跟上一节讲的对称位置有点类似。但是,任意行任意列的矩阵都可以进行转置,不限于行数和列数相同的矩阵。

1.9 练习1:矩阵的转置

【题目描述】

输入n和m,表示矩阵的行和列,再输入n行,每行有m个数,用空格隔开,为矩阵中的元素。输出转置后的矩阵。

【输入描述】

第1行为两个正整数n和m,2≤n, m≤10,表示矩阵的行和列。

接下来n行,每行m个整数,用空格隔开,表示矩阵中的元素。

【输出描述】

输出m行,每行n个整数,用空格隔开,表示转置后的矩阵。

【样例输入】
【样例输出】
3 5
78 57 13 -25 66
65 -52 34 7 -55
-33 19 -27 22 91
78 65 -33
57 -52 19
13 34 -27
-25 7 22
66 -55 91
【题目分析】

假设输入的矩阵存储在二维数组a中,转置后存储在二维数组b中,则数组a第i行、第j列上的元素,在数组b中位于第j行、第i列,因此有b[j][i] = a[i][j]。用二重for循环实现矩阵的转置。代码如下。

#include <bits/stdc++.h>
using namespace std;
int main( )
{
    int n, m, a[12][12];
    int b[12][12], i, j;
    cin >>n >>m;
    for(i=1; i<=n; i++ ){       //输入二维数组a, 第0行第0列不用
        for( j=1; j<=m; j++ )  cin >>a[i][j];
    }
    for(i=1; i<=n; i++ ){       //转置
        for(j=1; j<=m; j++ )
            b[j][i] = a[i][j];  //数组a第i行第j列的元素存储到数组b第j行第i列
    }
    for(i=1; i<=m; i++ ){       //输出
        for( j=1; j<=n-1; j++ )  cout <<b[i][j] <<" ";
        cout <<b[i][n] <<endl;
    }
    return 0;
}

1.10 练习2:二阶矩阵(GESP真题)

【题目描述】

小A有一个n行m列的矩阵A

小A认为一个2×2的矩阵D是好的,当且仅当D1,1×D2,2 = D1,2×D2,1,其中Di, j表示矩阵D的第i行第j列的元素。

小A想知道A中有多少个好的子矩阵。

【输入描述】

输入数据第1行,为两个正整数n和m。

接下来n行,每行m个整数Ai,1, Ai,2, …, Ai,m

【输出描述】

输出一行,为一个整数,表示A中好的子矩阵的数量。

【样例输入】
【样例输出】
3 4
1 2 1 0
2 4 2 1
0 3 3 0
2
【数据范围】

对于所有测试点,保证1≤n≤500,1≤m≤500,-100≤Ai, j≤100。

【样例解释】

样例中的好的子矩阵如图1.10中阴影部分所示。

图1.10 好的子矩阵

【题目分析】

用二重循环枚举从第i行第j列开始的2×2的子矩阵,判断子矩阵中对角两个元素乘积是否相等,如果相等,则计数。代码如下。

#include <bits/stdc++.h>
using namespace std;
const int N = 505;
int n, m;
int a[N][N];
int ans;
int main( )
{
    cin >>n >>m;
    for (int i = 1; i <= n; i++)  //从第1行第1列开始存储矩阵
         for (int j = 1; j <= m; j++)
              cin >>a[i][j];
    for (int i = 1; i < n; i++){  //枚举第i行第j列位置开始的2×2的子矩阵
         for (int j = 1; j < m; j++){
              if (a[i][j] * a[i + 1][j + 1] == a[i + 1][j] * a[i][j + 1])
                    ans++;
         }
     }
     cout <<ans <<endl;
     return 0;
}

1.11 拓展:三维及多维数组

在数学上,用来表示数的数轴相当于一维空间,如图1.11(a)所示。为了准确描述位置而引入的直角坐标系是二维空间,如图1.11(b)所示。我们生活在三维空间。可以引入三维坐标系表示三维空间中点的坐标,如图1.11(c)所示。

图1.11 一维、二维和三维

我们已经学习了一维和二维数组。有没有三维数组呢?其实三维数组甚至更高维数组都有。但是,四维及以上的数组非常不好理解,编程解题时很少用到。我们可以把一维数组想象成方格作业本中的一行,有多个位置(元素),如图1.12(a)所示;将二维数组想象成方格作业本中的一页,有若干行,每行有相同的列,如图1.12(a)所示;而三维数组就是一本方格作业本,有很多页,每页是一个二维数组,如图1.12(b)所示。

图1.12 方格作业本

例如,可以定义三维字符数组digit[10][5][4]来存储数字字符0~9的点阵图形,如图1.13所示。它是由10个二维数组组成的,每个二维数组存储一个数字字符的点阵图形。digit[0]存储了字符0的点阵图形,digit[9]存储了字符9的点阵图形。代码如下。

char digit[10][5][4] = {              //存储数字0~9的字符形式
    {"***","* *","* *","* *","***"},  //存储字符0的点阵图形, 有5行字符
    {" * "," * "," * "," * "," * "},  //字符1
    {"***","  *","***","*  ","***"},  //字符2
    {"***","  *","***","  *","***"},  //字符3
    {"* *","* *","***","  *","  *"},  //字符4
    {"***","*  ","***","  *","***"},  //字符5
    {"***","*  ","***","* *","***"},  //字符6
    {"***","  *","  *","  *","  *"},  //字符7
    {"***","* *","***","* *","***"},  //字符8
    {"***","* *","***","  *","***"}   //字符9
};

图1.13 三维字符数组

1.12 基础知识练习(GESP真题)

【单选题】

1.以下哪个选项能正确定义一个二维数组?(  )

A.int a[][];

B.char b[][4];

C.double c[3][];

D.bool d[3][4];

2.若有“int a[2][2] = {{0, 1}, {2, 3}};”这样的二维数组定义,则a[0][3]的值为(  )。

A.编译出错

B.1

C.3

D.0

3.以下哪个选项能正确访问二维数组array的元素?(  )

A.array[1, 2]

B.array(1)(2)

C.array[1][2]

D.array{1}{2}

4.下列关于C++语言中数组的叙述,不正确的是(  )。

A.一维数组在内存中一定是连续存放的

B.二维数组是一维数组的一维数组

C.二维数组中的每个一维数组在内存中都是连续存放的

D.二维数组在内存中可以不是连续存放的

5.一个二维数组定义为“double array[3][10];”,则这个二维数组占用内存的大小为(  )字节。

A.30

B.60

C.120

D.240

6.一个二维数组定义为“int array[5][3];”,则array[1][2]和array[2][1]在内存中的位置相差多少字节?(  )

A.2 字节

B.4字节

C.8 字节

D.无法确定

7.执行以下C++程序,输出结果为(  )。

#include <iostream>
using namespace std;
int main( ){
     int array[3][3];
     for(int i=0; i< 3; i++)
         for(int j=0; j<3; j++)
             array[i][j] = i*10 + j;
     int sum;
     for(int i=0; i<3; i++)
         sum += array[i][i];
     cout <<sum <<endl;
     return 0;
}

A.3

B.30

C.33

D.无法确定

8.下列关于C++语言中数组的叙述,不正确的是(  )。

A.一维数组可以用来表示数列

B.二维数组可以用来表示矩阵

C.三维数组可以用来表示空间中物体的形状

D.世界是三维的,所以定义四维数组没有意义

9.一个二维数组定义为“char array[3][10];”,则这个二维数组占用内存的大小为(  )字节。

A.10

B.30

C.32

D.48

10.一个三维数组定义为“long long array[6][6][6];”,则array[1][2][3]和array[3][2][1]在内存中的位置相差多少字节?(  )

A.70字节

B.198字节

C.560字节

D.无法确定

11.执行以下C++程序,输出的是(  )。

int arr[10] = {1};
string strArr = "chen a dai";
cout <<strArr[arr[1]] <<endl;

A.chen

B.c

C.chen a dai

D.dai

12.执行以下C++程序,输出的结果为(  )。

int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
for(int i=0; i<3; i++)
{
    for(int j=2; j>=0; j--)
    {
        cout <<arr[i][j] <<"";
    }
    cout <<endl;
}

A.

B.1 2 3 4 5 6 7 8 9

C.

D.9 8 7 6 5 4 3 2 1

13.执行以下C++程序,输出的结果为(  )。

int main( )
{
    int x[]={2, 0, 2, 4};
    char geSP[]="Grade Examination of SP";
    cout << geSP[sizeof(x)] << endl;
    cout << endl;
    return 0;
}

A.G

B.e

C.n

D.P

14.若有二维数组int arr[3][16];,则arr[1]占用内存的大小为(  )字节。

A.4

B.16

C.48

D.64

15.在C++中,(  )正确声明了一个3行4列的二维数组。

A.int arr[3, 4];

B.int arr[3][4];

C.int arr[4][3];

D.int arr(3, 4);

16.下面(  )正确定义了二维数组。

A.int a[3][];

B.int a[][];

C.int a[][4];

D.int a[][2] = {{1,2},{1,2},{3,4}};

17.若int arr[2][3] = {{1,2,3},{4,5,6}};,则arr[1][2]的值是(  )。

A.2

B.3

C.5

D.6

18.下面(  )正确定义了二维数组。

A.int arr[3,4];

B.int arr[3][4];

C.int arr(3,4);

D.int a[3-4];

【判断题】

1.已知数组a定义为“int a[100];”,则赋值语句“a['0'] = 3;”会导致编译错误。(  )

2.在C++语言中,可以定义四维数组,但在解决实际问题时不可能用到,因为世界是三维的。(  )

3.如果希望记录10个最长为99字节的字符串,可以将字符串数组定义为char s[100][10];。(  )

4.如果希望记录10个最长为99字节的字符串,可以将字符串数组定义为char s[10][100];。(  )

5.[(1,2)*2]*3在C++中是合法的表达式。(  )

6.定义数组int a [2024][3][16] = {2,0,2,4,3,1,6},则cout <<a[2023][2][15]的结果不确定。(  )

7.在下面的程序中,a[i][j]和一个普通的整型变量一样使用。(  )

#include<iostream>
using namespace std;
int main( )
{
    int a[10][10] = {0};
    for(int i=0; i<10; i++)
    {
        for(int j=0; j<10; j++)
        {
            if(i==j)
            {
                a[i][j] = 1;
            }
        }
    }
}

8.二维数组的行的大小必须在定义时确定,列的大小可以动态变化。(  )

9.int arr[3][]是一个正确的二维数组的声明。(  )

10.下面这行代码不合法,因为每一行都必须显式初始化3个元素。(  )

int arr[2][3] = {{1, 2}, {3}};

相关图书

Agent设计模式 图解可复用智能体架构
Agent设计模式 图解可复用智能体架构
Skills+OpenClaw:从零打造个性化AI助理
Skills+OpenClaw:从零打造个性化AI助理
AI Agent 开发实战:MCP+A2A+LangGraph 驱动的智能体全流程开发
AI Agent 开发实战:MCP+A2A+LangGraph 驱动的智能体全流程开发
Coze入门:7天玩转扣子智能体
Coze入门:7天玩转扣子智能体
十倍速开发:AI时代的Cursor编程手记
十倍速开发:AI时代的Cursor编程手记
计算流体力学大串讲轻松解锁CFD     从公式到代码的奇妙之旅
计算流体力学大串讲轻松解锁CFD 从公式到代码的奇妙之旅

相关文章

相关课程