☰
C++期末卷子里的概念地图:从常量到指针的认知链路
2026/11/2 9:54:04 网站建设 项目流程

简介:这份资源是华南理工大学《C++程序设计基础》A卷期末试卷的PDF电子版,面向正在学习C++语法、准备课程考试或需要刷题巩固基础的高校学生与自学者。试卷为闭卷形式,共四大题、满分100分、考试时间120分钟,内容覆盖常量与变量、逻辑运算符与表达式、自增自减、条件运算符、for与while循环、switch语句、函数原型与重载、const关键字、数组与指针、字符串处理及结构体等核心考点,并配有单项选择题的逐题解析,便于对照理解易错点。资源包共1个PDF文件,大小约288KB,轻量易存,适合打印练习或电子端随时查阅。目前已有116人学习,可作为期末冲刺与知识点查漏补缺的实战材料。

1. 一份C++期末卷子,为什么值得当成“概念地图”来读

很多人第一次看到“C++程序设计基础A卷”这种标题,第一反应是:这不就是一份考完就扔的试卷吗?其实换个角度看,它更像一张被压缩过的概念地图——一张卷子能把常量、运算符、流程控制、函数、数组、指针这些基础概念全串起来,说明这些点之间本来就有一条清晰的认知链路。你之所以觉得C++难,往往不是因为某个知识点本身有多玄学,而是因为不知道这些概念在整张地图上各自站在什么位置。这份试卷的价值,恰恰在于它用题目把位置标了出来:哪道题在考“值能不能改”,哪道题在考“名字绑定的是谁”,哪道题在考“内存里到底存了什么”。把这张地图读懂了,再回头看教材里的定义,就不会觉得每个词都认识、连起来却不知道在说什么。接下来就顺着卷子里的高频考点,把C++基础里最容易让人懵的几个概念一个个拆开讲清楚。

2. 从常量到表达式:C++的“值”到底怎么分类

2.1 常量不是“不会变的变量”,而是编译期就定死的值

卷子第一题问“下列选项中哪个是不合法的常量”,选项里出现了0xab、"student"、618E3和count。这道题真正想让人分清的是:常量在C++里不是一个“我打算不改它”的约定,而是语言层面就规定好形态的字面值。0xab是十六进制整型字面量,"student"是字符串字面量,618E3是科学计数法表示的浮点字面量,它们都有固定的写法规则;而count只是一个标识符,它可能是个变量名,也可能根本没定义,但它本身不构成常量。

打个比方,常量就像身份证号,格式是定死的,你一看就知道它属于哪一类;变量则像昵称,叫什么都行,但昵称本身不携带“我是谁”的完整信息。所以判断一个东西是不是常量,不能看它“看起来像不像数字”,而要看它是否符合字面量的书写规则。这也是为什么const int MAX = 1000;里的MAX虽然不能改,但它在语法分类上仍然是一个“被const修饰的对象”,和字面量常量不是同一个层面的东西。理解这一层,后面看到const引用、const指针才不会混。

2.2 逻辑表达式考的是“短路”,不是数学等价

卷子里有一道经典题:int a=1,b=1; --a && --b;执行后 a 和 b 分别是多少。很多人会下意识觉得两个--都会执行,于是算出 0 和 0。但&&是逻辑与,它有一个硬规则:左边为假,右边直接不执行。--a先把 a 从 1 减到 0,0 在逻辑判断里是假,于是--b被跳过,b 保持 1。答案是 a=0、b=1。

这个规则叫“短路求值”,通俗地说就是“前面已经判了死刑,后面就不用再审了”。它不是为了省事,而是有实际意义的:比如if (p != nullptr && p->value > 0),如果指针为空,后半句根本不该碰,否则程序直接崩。卷子里另一道“大于2或小于-2”的逻辑表达式题,考的是德摩根律的等价变形,本质也是让人习惯用逻辑运算去描述区间,而不是用数学不等式直接照搬。这两类题放在一起,其实是在提醒:C++里的逻辑表达式首先是“求值顺序”问题,其次才是“真假”问题。

2.3 自增自减的坑:前置和后置不是“先后”这么简单

卷子里出现了++(a++)、a++++b、++(++a)这类选项,问哪个表达式合法。初学者容易把++当成一个可以随便叠加的符号,但C++对自增自减有明确的约束:后置++的结果是一个右值,你不能对一个右值再取地址或再自增;前置++要求操作数是一个可修改的左值。所以++(a++)不合法,因为a++产生的是临时值;a++++b在词法上就会被拆成a++ ++b,同样不合法;++(++a)里内层++a返回的是左值引用,外层再自增是合法的。

这里的关键区分是“左值”和“右值”:左值可以理解成“有门牌号的房子”,你能找到它、改它;右值更像“快递送来的样品”,你能看能用,但不能给它挂牌。自增自减运算符对操作数有左值要求,就是因为它们要“改房子”,不是“看样品”。把这个区分记住,后面学引用、指针、函数返回值时,会反复用到同一套判断逻辑。

3. 流程控制与函数:程序“怎么走”和“怎么拆”

3.1 循环和switch考的是“边界有没有被真正检查”

卷子里有一道 for 循环题:for (i=10; i=count; i--),很多人第一眼会看成i==count,以为在比较,实际上中间那个是赋值号。i=count的结果是 count 的值,如果 count 非零,条件为真,循环会一直跑;如果 count 为零,循环一次都不跑。题目给的条件让答案落在“一次都不执行”上,考的就是“你有没有看清那个等号”。

另一道 switch 题更典型:m=2,进入case 2输出"2",但没有break,于是继续穿透到default输出"default",最终结果是2default。switch 的穿透不是bug,是语言特性,它允许你把多个 case 合并处理,但代价是每个分支结束必须自己记得写break。这就像一排没装弹簧的门,推开一扇,后面的门会跟着一起开,除非你手动挡住。卷子里还有一道“哪个程序段没有形成死循环”,四个选项分别对应 while 条件不变、do-while 正常递减、for 条件写成赋值、while(1) 里带 break,考的都是“循环的出口到底在哪里”。判断死循环不能靠感觉,要看循环变量有没有被真正修改、条件表达式有没有可能变成假。

3.2 函数原型和重载:名字相同不等于可以随便重

卷子里有一道题问“下列函数原型有错误的是”,选项里出现了Func(int a, b, c);。C++要求每个参数都必须有自己的类型,int a, b, c这种写法只在变量声明里合法,在函数参数列表里不行,因为编译器需要知道 b 和 c 的类型。另一道重载题问“以下正确的重载函数是”,正确选项是参数列表不同的两个同名函数,而返回类型不同、参数名不同都不构成重载。

通俗地说,函数重载看的是“调用时能不能唯一确定该进哪个门”,而编译器判断的唯一依据是参数的类型、个数和顺序,跟返回类型无关。你写两个同名函数,一个返回int一个返回double,参数完全一样,编译器没法在调用点区分,所以不合法。卷子里还混入了默认参数的重载场景:int same(int,int=1);和int same(int,int=3);看起来参数个数不同,但调用same(5)时两个都能匹配,产生二义性,同样不行。这一块的血泪经验是:重载不是“名字一样就行”,而是“调用点必须无歧义”。

3.3 函数指针和引用参数:传进去的到底是“值”还是“位置”

卷子里有一道题:int sum(int*,int); int(*p)(int*,int)=sum;然后问哪个调用不正确。正确调用包括p(a,b)、(*p)(a,b)和sum(a,b),而(&p)(a,b)是错的,因为&p取的是函数指针变量本身的地址,类型对不上。这道题考的是函数指针的“解引用等价性”:p、*p、sum在调用场景下可以互换,但&p不行。

另一道题涉及const double &和int*的组合参数,问哪个调用不合法。const double &可以绑定临时值,所以传PI*3和字面量3.14159都行;但int*要求一个真正的整型地址,传&PI不行,因为PI是double,类型不匹配。这里容易混的是“引用”和“指针”在参数传递中的角色:引用更像“给原变量起了个别名”,指针更像“把门牌号抄给你”。const引用还能接住临时对象,普通引用不行,这个区别在卷子里反复出现,是理解C++参数传递的一把钥匙。

4. 数组、指针与字符串:最容易“看着像、其实不是”的区域

4.1 数组名不是指针,但在大多数表达式里会“退化”成指针

卷子里有一道题:int a[2][2]={1,2,3,4};问哪个不能表示a[0][0]的地址。a[0]是第一行的数组名,退化后指向a[0][0];&a[0][0]是显式取地址;*(a[0])是对第一行首元素解引用,得到的是值不是地址;*a是对二维数组第一行解引用,得到的a[0]再退化,仍然指向首元素。所以不能表示地址的是*(a[0])。

这里的关键区分是“数组名退化”和“解引用”发生在哪一层。数组名在大多数表达式里会退化成指向首元素的指针,但退化不等于它本身就是指针;sizeof(a)和&a就能看出它仍然是数组类型。二维数组可以理解成“数组的数组”,a是外层数组名,a[0]是内层数组名,每一层退化后指向的东西不一样。卷子里还有一道int x[3][4], *p;问哪个赋值正确,答案是p=*x;,因为*x等价于x[0],退化后是int*,而x本身是int(*)[4],类型不匹配。这类题翻车的人很多,根源就是把“二维数组名”和“一级指针”当成一回事。

4.2 字符串长度考的是“转义字符算几个”

卷子里有一道strlen题:char *str="\nThank\10you,\0sir! ";问长度。\n是一个字符,\10是八进制转义,表示一个字符,\0是空字符,strlen遇到它就停。所以实际参与计数的字符到\0之前为止,答案是 12。很多人会把\10看成两个字符\1和0,或者把\0后面的内容也算进去,都是对转义序列的理解不到位。

通俗地说,反斜杠在字符串里是“翻译官”,它后面的内容要按规则翻译成一个字符,而不是原样保留。\n翻译成换行,\10翻译成八进制 10 对应的字符,\0翻译成空字符。strlen只数到第一个空字符为止,这是C风格字符串的硬约定。卷子里还有一道char s[]={"Maths","C++","English","Chinese"}, **p; p=s;问p[2]输出什么,考的是指针数组和二级指针的配合:p指向s[0],p[2]等价于*(p+2),也就是s[2],输出"English"。这里容易混的是“指针数组”和“数组指针”的写法,char *s[]是数组,元素是指针;char (*s)[]才是指针,指向数组。

4.3 结构体成员访问:点号和箭头的选择取决于“你手里是什么”

卷子最后有一道结构体题:Person person[2], *pPer=person;问哪个引用形式错误。person[1].code合法,因为person[1]是对象;pPer->code合法,因为pPer是指针;(*pPer).code也合法,因为解引用后是对象;而person[1]->code错误,因为person[1]是对象不是指针,不能用箭头。

这个规则可以用一句话记住:左边是对象就用点,左边是指针就用箭头,箭头本质上是“解引用加点”的语法糖。卷子里还有一道链表结构的输出题,Node a[] = {{"China",a+1},{"France",a+2},{"USA",a}}; Node *p=a;然后连续输出p->s、p->q->s、p->q->q->s、p->q->q->q->s,考的是顺着指针链一路走。这类题不需要背,只要把每个q当成“下一站的地址”,一步一步画出来就不会乱。

5. 常见误区:这几个概念最容易在脑子里“串味”

5.1 常量 vs const变量:一个在编译期,一个在运行期约束

易混点在于两者都“不能改”。为什么混?因为日常说话里都叫“常量”。怎么区分:字面量常量是写死在代码里的值,编译器直接拿它做替换;const变量是一个有存储位置的对象,只是语法上禁止你通过这个名字修改它。卷子里const int MAX=1000;传给func时,能匹配const int&但不能匹配int&,就是因为const修饰的是“这个对象不能被改”,而不是“它没有地址”。

5.2 数组名 vs 指针:退化是“临时行为”,不是“身份变更”

易混点在于很多表达式里数组名用起来和指针一样。为什么混?因为退化太常见,导致人以为它们等价。怎么区分:看sizeof和取地址。sizeof(数组名)得到整个数组大小,sizeof(指针)得到指针本身大小;&数组名的类型是“数组指针”,&指针的类型是“指针的指针”。卷子里二维数组那几道题,错的人基本都是把退化后的类型当成了原始类型。

5.3 函数重载 vs 默认参数:都能让“一个名字多种调用”,但规则不同

易混点在于两者都影响调用点的匹配。为什么混?因为都能让同一个函数名接受不同形式的参数。怎么区分:重载靠“参数列表不同”来区分,默认参数靠“省略尾部参数”来补全;两者混用容易产生二义性。卷子里int same(int,int=1);和int same(int,int=3);就是典型反例,参数个数看起来不同,但调用same(5)时两个都能匹配,编译器直接报错。

5.4 指针数组 vs 数组指针:读法顺序决定含义

易混点在于char *s[]和char (*s)[]长得很像。为什么混?因为*和[]的优先级容易看错。怎么区分:从名字出发,先看s先跟谁结合。char *s[]里s先跟[]结合,所以是数组,元素是char*;char (*s)[]里括号强制s先跟*结合,所以是指针,指向一个char数组。卷子里char *s[]={"Maths","C++",...}就是指针数组,**p指向它的首元素,p[2]取的是第三个字符串。

5.5 值传递 vs 引用传递:改的是“原件”还是“复印件”

易混点在于函数调用时看起来都是把变量写进去。为什么混?因为语法上都是func(x)。怎么区分:看函数原型。参数写成int是值传递,函数里改的是复印件;写成int&是引用传递,函数里改的是原件;写成int*是指针传递,传的是地址,函数里通过地址改原件。卷子里fun1(int[],int,double&)的第三个参数是引用,所以av能在函数里被赋值并带回主函数;如果写成double,主函数里的av就不会变。

6. 什么时候该用这套概念,什么时候别硬套

这套“常量—表达式—流程—函数—数组指针”的概念地图,最适合用在两个场景:一是读别人写的C++基础代码时,能快速判断每个名字背后是值、是地址还是引用;二是自己写小规模程序时,能在参数传递和数组边界上少翻车。比如卷子里“按薪水降序排列职工信息”那道题,核心就是结构体数组加选择排序,Sort(Employee*, const int)用指针传数组、用const int传长度,if(all[j].salary > all[t].salary) t=j;是比较规则,if(t!=i)决定要不要交换。这套写法不花哨,但边界清楚,适合基础阶段把逻辑跑通。

但有些时候别硬套。第一,不要用这份卷子的风格去写现代C++工程代码。卷子里大量使用裸指针、C风格字符串和手动循环,是为了考基础概念,不是为了教最佳实践;真实项目里能用std::vector、std::string、范围循环的地方,没必要为了“贴近考试”而退回手动管理。第二,不要用“背题型”代替“理解类型”。卷子里那些自增自减、二维数组地址、函数指针调用的题,如果只记答案,换一个写法照样错;真正要带走的是“左值右值”“退化”“解引用层级”这几把尺子。第三,不要在没有边界检查的情况下模仿卷子里的数组操作。卷子里的for(i=0;i<10;i++)是题目给定长度,真实数据长度是变的,硬编码下标就是给自己埋雷。

我自己的习惯是:每次看到一段C++基础代码,先问三个问题——这个名字是值还是地址?这个参数传进来的是原件还是复印件?这个数组名在这一行有没有退化?三个问题过一遍,大部分“看着对但跑不对”的坑都能提前拦住。这份A卷的价值不在于它考了多少分,而在于它把这三个问题用题目摆在了明面上,顺着题目把概念捋一遍,比零散地翻教材效率高得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询