☰
C++习题精解:从数据类型到指针与引用的高频考点剖析
2026/10/15 18:57:42 网站建设 项目流程

简介:广州大学计算机课程配套的C++习题答案,以单个doc文档形式整理,面向正在学习C++程序设计的学生及需要巩固基础语法细节的开发者。内容围绕变量与数据类型、运算符与表达式、控制结构、函数、数组和指针等章节展开,以选择题带解析的方式覆盖标识符规则、八进制与十六进制常量、运算符优先级、自增自减表达式求值、指针与引用赋值等高频考点,并配有数学表达式转C++算术表达式、用逻辑表达式表示整除与区间条件等实践练习,有助于读者梳理易错概念、提升编程表达准确性。资源包体积仅1.55MB,包含1个doc文件,携带方便;目前已有442人学习,适合作期末复习或自学查漏补缺的参考资料。

1. 这份 C++ 习题答案,不该只拿来抄:先讲清楚它帮你解决什么问题

不少刚碰 C++ 的同学都有过这种经历:书看完了,语法似乎都懂,一打开编译器的习题就卡住——选择题四个选项看着都对,程序跑出来的结果跟手算的完全不一样。网上搜答案,搜到的要么只有最终结果没有推导,要么代码风格跟教材不一致,抄上去还报错。这份某高校计算机 C++ 课程的习题与解答资源,覆盖了从基础概念、数据类型、运算符优先级,到控制结构、指针引用的完整知识点,每一道题都给了明确的答案和代码验证。它不是让你背答案交作业用的,而是当你做完题、对完答案之后,能把每个选项背后的语法规则真正吃透的参考。适合正在学 C++ 基础、准备期末考或者刚入门的同学,也适合那些想快速排查自己知识盲区的自学者。下面我按章节把题目背后真正值得关注的点拆开讲。

2. 把选择题变成知识点清单:数据类型、常量与标识符的七个易错判断

2.1 选择题里的高频考点,其实是编译原理的入门

习题 1.1 的第 2 题问源程序变为目标程序必须经过什么,答案是编译。不少同学在这里选错成汇编或者解释,本质上是没分清这几个概念的关系。编译是把整个源文件翻译成目标代码文件,汇编是把汇编语言转成机器码,解释是逐行翻译逐行执行。C++ 是编译型语言,所以必然走编译这一步。这道题背后牵出的知识点是:一个 .cpp 文件从编写到运行,经历了预处理、编译、汇编、链接四个阶段。后面第 5 题说的“溢出”属于运行错误,也是这个链条上的概念——编译时编译器只检查语法和类型,溢出这种值域问题要等程序跑起来才知道。

另一个容易踩坑的是第 4 题,int a=0; double x=5.16;哪个是编译错误。答案是a=a%x。%运算符要求两个操作数都是整型,这里 x 是 double,类型不匹配,编译直接报错。但x=a/x和x=x*a为什么合法?因为 C++ 有隐式类型转换,int 会被提升成 double 参与运算。考试时这种题考察的就是类型转换规则和运算符的操作数要求。我一般会提醒学生:遇到这种题不要只看类型,还要看运算符。

第 6 题考察关键字识别,sizeof、case、extern都是关键字。这里有个常见误判:把IF和Static当关键字,因为 C++ 是大小写敏感的,if才是关键字,IF只是一个普通标识符,Static同理。第 7 题合法的标识符是_abc,new是关键字不能用,π不是 ASCII 字符集,C++ 标准标识符只允许字母、数字和下划线,且不能以数字开头。

2.2 常量与数值类型的陷阱,全在这几道题里

第 9 题到第 11 题是数值常量表示的集中考察点。八进制常量以 0 开头,所以015合法,0a0不是(八进制里没有 a),080非法(8 不在八进制数字范围内),0x10是十六进制前缀 0x。十六进制常量的坑在第 10 题,0xg非法,因为 g 不是十六进制数字。浮点常量的判断规则更细:-1e3.5非法,因为指数部分必须是整数;e-5非法,因为 e 前面必须有数字;5.0e(1+4)非法,因为指数不能是表达式。而12.34、-1e+5、0.1E-12这样的合法写法要记住:数字部分可以带小数点,指数部分用 e 或 E 加上正负号和整数。

字符常量的判断也有讲究。第 12 题答案是'\101',这是转义字符表示法,\101是八进制 ASCII 码,对应大写字母 A。"a"是字符串常量不是字符常量,'name'这种单引号里多个字符在 C++ 里是非法的,a没有引号是标识符。这里有个值得注意的点:'\101'这种写法在考试里反复出现,它考察的是转义序列\后面跟八进制数这个语法。实际上 C++ 还支持'\x41'这种十六进制转义,但教材里一般只强调八进制写法。

常量类型合法示例非法示例判断依据
八进制整型015080、0a0以 0 开头,只能含 0-7
十六进制整型0x1f、0xaf0xg以 0x 开头,只能含 0-9 和 a-f
浮点型12.34、-1e+5、0.1E-12-1e3.5、e-5指数必须是整数,e 前必须有数字
字符常量'\101'"a"、'name'单引号包裹单个字符,支持转义

2.3 交换变量的四种写法,暴露了赋值与内存的本质

第 13 题问哪种写法不能交换 a 和 b 的值,答案是a=b; b=a;。这个错误很经典——先把 b 赋给 a,a 原来的值就丢了,再执行 b=a 时两边都是原来的 b 值。正确的三种写法里,t=a; a=b; b=t;是引入临时变量,最直观;a=a+b; b=a-b; a=a-b;是利用算术运算交换,不需要临时变量。这里要提醒的是,算术交换在整数溢出时可能出问题,但作为考试题它考察的是赋值语句的执行顺序理解。

第 15 题考察指针赋值:p1=a是错误的,因为 p1 是int*类型,a 是 int 类型,两者类型不匹配,直接把变量的值赋给指针变量在编译期就会被拒绝。而*p2=b是给指针指向的内存赋值,p2=p1是指针变量之间的赋值,c=*p1*(*p2)是间址取值的算术运算。这道题背后的知识点是:指针变量存的是地址,给它赋值只能赋地址值或者同类型指针变量,不能直接赋普通变量的值。第 16、17 题考察引用:pb=pa是把 pa 引用的变量的值赋给 pb 引用的变量,不是改变引用关系——引用一旦初始化就不能再绑定到其他变量。第 17 题里通过引用交换 ra 和 rb,本质是交换了 a 和 b 的值,所以结果是 a=3、b=5。

// 交换变量的三种正确写法验证 #include <iostream> using namespace std; int main() { int a = 3, b = 5; // 方法一:临时变量 int t = a; a = b; b = t; cout << "a=" << a << " b=" << b << endl; // a=5 b=3 // 方法二:算术交换 a = 3; b = 5; a = a + b; // a=8 b = a - b; // b=3 a = a - b; // a=5 cout << "a=" << a << " b=" << b << endl; // a=5 b=3 // 方法三:引用交换 a = 3; b = 5; int &ra = a, &rb = b; t = ra; ra = rb; rb = t; cout << "a=" << a << " b=" << b << endl; // a=5 b=3 return 0; }

这块的逻辑是:引用在定义时必须绑定到变量,之后对引用赋值操作的是它绑定的那个变量本身。所以t=ra取出的是 a 的值,ra=rb把 b 的值赋给 a,rb=t把之前保存的 a 的值赋给 b。这样一轮操作下来,a 和 b 的值确实交换了。参数说明里值得注意的一点是:int &ra=a这个初始化过程不产生新的内存,ra 就是 a 的别名,这在函数传参时可以避免拷贝开销。

3. 运算符优先级与表达式求值:错得最多的题,全在这几道里

3.1 前缀后缀自增自减,永远是最容易翻车的点

习题 1.1 的第 20、21、22 题集中在自增自减运算符上。i+++j的值是 3,这里编译器采用贪心匹配原则,从左到右尽量多地匹配运算符,所以解析成(i++)+j,i 先参与运算后自增,结果是 1+2=3。++i+j的值是 4,i 先自增变成 2,再加 j 得 4。第 22 题问哪个表达式合法,答案是a+++b,它被解析成(a++)+b。++(a++)非法,因为(a++)的结果是右值,不能再次自增;a++b中间没有运算符,语法错误;a++++b会被解析成(a++)++ + b,(a++)是右值不能自增,编译错误。

这里要展开讲的是++运算符的副作用和序列点问题。C++ 标准里,同一个表达式中对一个变量多次自增自减是未定义行为,比如a++ + a++这种写法不同编译器结果可能不同。考试题里一般不会出这种未定义行为,但自己写代码时要避免。

第三道高频题是第 23 题:++i||--j&&++k,初始 i=0、j=1、k=2。这里涉及两个规则:一是优先级,&&高于||,所以先算--j&&++k;二是短路求值,||左侧++i先执行,i 变成 1,值为真,整个表达式短路,右侧不再执行。所以结果是 1。这种题的关键是:短路求值会导致一部分表达式不执行,从而影响变量的最终值。第 24 题同理,++x||++y中 x 初始为 1,++x后 x=2 为真,++y不执行,y 保持 1,所以 x=2、y=1。

3.2 复合赋值与逗号表达式的求值顺序

第 26 题x += x -= x * x,x 初始为 5。这种复合赋值运算符的结合性是右结合的,所以等价于x += (x -= (x * x))。先算x * x = 25,再算x -= 25,也就是x = x - 25 = -20,最后算x += -20,x = -20 + (-20) = -40。这里最核心的认知是:复合赋值运算符右边的表达式是在当前 x 值的基础上计算的,每一步操作都会改变 x 的值,下一步操作是基于新的 x。

第 28 题逗号表达式( x = 4 * 5, x * 5 ), x + 25。逗号运算符从左到右依次求值,整个表达式的值是最后一个子表达式的值。内层括号里先执行x = 20,再求x * 5 = 100,这个值被丢弃;然后外层求x + 25,注意此时 x 已经被赋值为 20,所以结果是 20+25=45。这道题考察的是逗号表达式的两个特征:从左到右求值、结果取最后一个表达式,以及赋值对后续表达式的影响。

// 验证第 26 题的逐步求值 #include <iostream> using namespace std; int main() { int x = 5; // 等价于 x += (x -= (x * x)) int t1 = x * x; // t1 = 25 x = x - t1; // x = 5 - 25 = -20 x = x + x; // x = -20 + (-20) = -40,注意这里 x 已经被修改 cout << "x = " << x << endl; // 输出 -40 return 0; }

这段验证代码的逻辑是:把复合赋值拆成单步操作来理解。t1 = x * x先计算乘法,然后x = x - t1执行减法赋值,最后x = x + x执行加法赋值。每一步都基于上一步更新后的 x 值,所以最终结果是 -40 而不是 0。参数说明里要注意:如果把最后一步写成x += t1会得到 -40 吗?不会,因为t1是 25,x已经是 -20,x + t1 = 5,所以这一步必须用x + x才能模拟出复合赋值里x += (某个值)的真实含义——这个“某个值”是x - t1的结果,也就是 -40,而不是 25。

3.3 关系表达式的连续比较陷阱

第 25 题是很多人的盲区:数学关系 1<x<5 在 C++ 里不能写成1<x<5,因为 C++ 里<是左结合的,1<x先求值得到一个 bool 值,然后这个 bool 值再跟 5 比较。如果 1<x 为真,结果是 1<5 也为真;如果 1<x 为假,结果是 0<5 也为真——所以无论 x 取什么值,1<x<5恒为真。正确答案是1<x && x<5,或者用!(x<=1)&&!(x>=5)。后一种写法用到了德摩根律的变形。这道题的价值在于:写代码时连续比较必须用逻辑运算符连接,这是 C++ 与数学表达式的根本差异。

第 27 题条件表达式a<b?a:c<d?c:d,条件是右结合的,所以解析成a<b ? a : (c<d ? c : d)。a=1、b=2,a<b为真,整个表达式的值是 a,也就是 1。如果 a<b 为假,才会去计算c<d?c:d。这种嵌套条件表达式读起来费劲,实际开发中我一般建议用括号显式标明层级,或者直接用 if-else 替代,可读性更好。

4. 控制结构专项:if、switch、循环的执行流程与常见误写

4.1 条件语句的悬挂 else 与短路陷阱

习题 2.1 的第 1 题给了int i=0, x=1, y=0,问哪个语句能让 i 变成 1。if(x||y) i++正确,因为 x 为真,||短路,i 自增。if(x&&y)中 x 为真但 y 为假,整体为假;if(x==y)中 1 不等于 0;if(!x)中 x 非零,取反为假。这道题的关键是理解&&、||的短路特性:&&左侧为假时右侧不执行,||左侧为真时右侧不执行。

第 2 题的函数关系式分段判断,答案是 c 选项。这里要注意的是分段函数的边界条件处理:x<0时返回 -1,x==0时返回 0,x>0时返回 1。c 选项写成if(x<=0)再嵌套if(x<0),逻辑等价且覆盖了所有情况。a 选项的问题是if(x>=0)内嵌套if(x==0),当 x=0 时外层为真进入,内层x==0为真执行y=0,看起来没错,但 x=0 时最后会执行外层的 else 吗?不会,因为 if-else 配对时 else 与最近的 if 配对,这里的 else 与if(x==0)配对。这种悬挂 else 问题在多层嵌套时特别容易出错,我一般建议用花括号明确块结构。

第 3 题是 switch 的穿透问题:

int i = 2; switch(i) { case 1: i++; case 2: i--; // 从 case 2 进入 case 3: ++i; break; // 穿透执行 case 3 case 4: --i; default: i++; }

i 初始为 2,进入 case 2,执行i--后 i=1,没有 break,继续穿透到 case 3,执行++i后 i=2,遇到 break 退出。结果 i=2,答案选 b。这个题的坑在于:switch 的 case 默认是穿透的,必须显式写 break 才能跳出。实际编码中漏写 break 是最常见的运行时逻辑 bug 之一,尤其在 Windows 平台下编译不会报任何错误。

4.2 三种循环语句的边界条件对比

习题 2.1 第 4 到第 7 题集中考察循环次数。第 4 题while(!x && i<3),x=0、i=0,条件为真进入循环,执行x++后 x=1,i++后 i=1,此时!x为假,循环只执行 1 次。这是&&短路的一个变体应用:!x变成假后,后面的i<3不再影响结果。第 5 题do-while:i初始 3,进入循环i--后 i=2,条件i!=1为真再执行一次,i--后 i=1,条件为假退出,共执行 2 次。do-while 和 while 的核心区别是前者至少执行一次,这个特性在读文件等场景里很实用。

第 6 题的 for 循环是个很有意思的陷阱:

for (i=0, j=5; i=j; ) { cout << i << j << endl; i++; j--; }

循环条件是i=j,这是一个赋值表达式,不是比较。j 初始为 5,赋值后 i=5 非零为真,循环执行 5 次,直到 j 减到 0,i=0为假退出。第 7 题的 b 选项do { ++k; } while(k>=0)死循环,因为 k 从 0 开始递增永远大于等于 0。这里有个细节:int 溢出是未定义行为,但实际运行时 k 溢出后会变成负数,在某些编译器上可能结束循环,所以死循环的答案是 b 而不是 d。d 选项for(; i; i--)中 i 从 3 递减到 0 退出,不是死循环。

4.3 阅读程序题的输出推导方法

习题 2.2 的第 3 题是 continue 的经典用法:

int i = 1; while (i <= 10) { if (++i % 3 != 1) continue; else cout << i << endl; }

i 从 1 开始,++i后 i=2,2%3=2 不等于 1,continue 跳过 cout。接着 i=3,3%3=0 不等于 1,继续;i=4,4%3=1,输出 4。一路推下去,输出的是 4、7、10。这道题考察的是 continue 的作用位置:它跳到循环体的末尾,不是跳到条件判断处。++i在条件判断里就已经执行了,continue 不会跳过它。

第 4 题是 do-while 加 break:

int i = 0, j = 5; do { i++; j--; if (i > 3) break; } while (j > 0);

i 从 0 开始,每次循环 i 加 1、j 减 1,当 i=4 时i>3成立,break 跳出。此时 j=1,输出i=4 j=1。第 5 题的 for 循环for(i=1, j=5; i<j; i++)内层j--,i 和 j 同时向 3 靠拢,i=3、j=3 时条件i<j为假退出。第 7 题嵌套循环加 continue:

for(i=0; i<=3; i++) { x++; for(j=0; j<=3; j++) { if (j % 2) continue; x++; } x++; }

内层循环中 j 为偶数时 x 加 1,j 为奇数时 continue 跳过。外层循环每次 x 加 2,内层加 2 次,所以每次外层循环 x 加 4,4 次循环共加 16,加上内层 j 偶数的 2 次,结果 x=16。

5. 指针与引用的验证方法:从语法辨析到完整程序验证

5.1 三种访问方式对比:名访问、引用访问、间址访问

习题 1.5 的思考题 4 给出了一个验证程序,分别用名访问、引用访问、间址访问三种方式操作同一个变量。这道题值得认真跑一遍,因为它揭示了一个关键点:指针和引用在语法上都能修改原变量的值,但底层机制不同。指针变量本身是一个独立的变量,存储的是地址,需要解引用才能访问目标;引用是目标变量的别名,不分配单独的内存空间。验证程序里cout<<&a输出的是 a 的地址,cout<<*(&a)通过地址访问输出值,cout<<*pa是指针解引用,cout<<ra是直接使用引用。

习题 1.5 思考题 3 里有一段对比说明:引用必须在定义时绑定,之后不能重新绑定;指针可以在程序运行过程中改变指向。这个特性决定了它们在函数参数传递中的不同应用场景。常见做法是:需要修改实参且不允许空值时用引用,需要指向不同对象或可能为空时用指针。

// 验证三种访问方式 #include <iostream> using namespace std; int main() { int a; cout << "a="; cin >> a; // 从键盘读入 a 的值 a = a + 5; // 名访问:直接用变量名修改 cout << &a << endl; // 输出变量 a 的地址 cout << *(&a) << endl; // 地址访问:通过取址再解引用输出值 int *pa = &a; // 定义指针变量 pa,存储 a 的地址 cout << *pa << endl; // 间址访问:通过指针解引用输出值 int &ra = a; // 定义引用 ra,绑定到 a cout << ra << endl; // 引用访问:直接用引用名输出值 return 0; }

这段代码的逻辑分层很清晰:名访问直接操作,地址访问通过&取地址再用*解引用,指针访问先用int* pa=&a保存地址再用*pa取值,引用访问int &ra=a绑定后直接用。参数说明里值得注意的是:cin >> a设置了初始值,后续a=a+5修改了值,所以三种访问方式输出的都是修改后的值。如果你想看看修改前和修改后的区别,可以把a=a+5这行注释掉再跑一次。

5.2 字符型常量与 ASCII 码的嵌套验证

习题 1.1 第 12 题里的'\101'实际上是八进制转义,101 转换成十进制是 65,正好是 ASCII 码表中大写字母 A。这在输入验证场景里很常见:需要处理控制字符时,用转义序列表示比手工拼不可见字符更可靠。源码里'\n'是新行,'\t'是制表符,'\101'是特定 ASCII 字符。C++ 转义字符表里,\x开头接十六进制数也合法,但教材里更强调八进制写法,因为历史原因很多教科书沿用了 C 语言早期习惯。

5.3 引用与指针的 const 约束对比

习题 1.5 思考题 5 给出了只读约束的多种形式:const int a=5是常量标识符;int* const p=&a是指针常量,p 本身不能改指向;const int* p=&a是指向常量的指针,不能通过 p 修改 a 的值,但 p 可以指向其他变量;const int* const p=&a两者都不可变;const int &ra=a是常引用,不能通过 ra 修改 a。这些组合在函数传参时非常实用:函数参数写成const int*或const int&可以承诺不修改传入的数据,同时避免拷贝大对象的开销。

判断一道题是否理解到位,可以看能不能回答这个问题:int a=5; const int* p=&a; *p=10;合法吗?不合法,编译器会报错,因为 p 是指向常量的指针,不能通过它修改 a 的值。但a=10合法,因为 a 本身不是常量。这个区分是考试和面试都喜欢问的点。

6. 避坑记录:程序能编译但结果不对,问题出在哪

6.1 赋值号当等号用

现象:判断两个变量是否相等时,if(a=b)编译通过,程序输出了“9 等于 9”,两个输入值明明不等。

原因:a=b是赋值表达式,C++ 里 if 语句会把任何表达式当作条件判断,赋值表达式的值是赋完后的值。b 输入 9,非零即为真,所以走了正确分支。

解决:条件判断必须写if(a==b)。我一般会用编译器的-Wall警告选项,开启后if(a=b)会提示assignment instead of comparison。如果你用集成开发环境,也要注意有没有开警告级别,默认配置下这种错误很容易漏掉。

6.2 switch 遗漏 break 导致穿透

现象:程序输入成绩等级 C,预期输出“70-79”,实际输出连着输出了“70-79”和“60-69”两行。

原因:case 语句没有 break,执行完当前分支后继续往下执行。

解决:每个 case 末尾补上 break,或者用 return 直接返回。检查源码时我会先扫一遍所有 case 的结尾,看到case 1: ... case 2:这种紧挨着的写法就要警惕。有些场景反而可以利用穿透来合并多个 case,比如多个值映射到同一个结果时写case 'A': case 'a':。

6.3 变量定义放在 if-else 语句体里导致未定义

现象:编译报错undeclared identifier,提示 a 没有定义。

原因:if(c=='y') int a=1; else int a=0;中 a 的作用域在语句块内部,出了块就没了,后面的cout<<a引用不到。

解决:把变量声明提到 if-else 之前,语句体内只做赋值。这是 C++ 变量作用域的基本规则,但在考试题里容易被忽略。实际编码时我习惯把所有变量声明放在作用域开头,避免这种问题。

6.4 浮点数直接比较相等

现象:判断a+b == c时,数学上成立但程序输出 false。

原因:浮点数在计算机里是二进制近似存储,0.1+0.2 的结果不是精确的 0.3,而是 0.30000000000000004,直接比较等于会失败。

解决:用差的绝对值跟一个很小的阈值比较,fabs(a+b-c) < 1e-9。工程上常用std::abs配合std::numeric_limits<double>::epsilon()来做近似比较。考试里一般不会出这种题,但自己写代码时几乎一定会遇到。

从那以后我每跑一个 C++ 程序,不管题目多简单,都会先开警告选项、再用边界值测一遍输入,尤其是 0、负数和超大数,最后才下结论说程序没问题。以前总觉得自己手算的结果一定对,被这几道题的隐性坑虐过几次之后,才明白编译器通过只是第一步,逻辑正确才是更难的功课。希望这份习题答案的拆解能帮你省下一点踩坑的时间,把每个知识点背后的原理都过一遍,而不是只把答案抄下来。

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

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

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

立即咨询