资讯动态

C++ Primer Plus第四章练习题详解:数组、结构体与指针实战

发布时间:2026/10/14 1:05:30 来源:尧图企业网站定制
你打开《C Primer Plus第六版》第四章的编程练习题时大概率会有一种“哎呀这章内容我都会”的错觉。数组、字符串、结构体、共用体、枚举、指针每一个知识点单独拎出来都挺简单可真要动手写完整程序却发现不是这里警告就是那里乱码甚至程序没结束窗口就闪退了。第四章的题全是这一类“看着基础、做着扎心”的综合练习专门考验你对复合类型的基本功是否扎实。我最近刚把这一章十道题完整刷了一遍把每道题的思路、代码、易错点都整理出来了这篇文章就给正在啃这本书的读者做个参考。不管你是刚看完第四章准备做题还是想把之前的代码翻出来对一下思路都能从里面找到点有用的东西。说明一下我手头这个版次的第四章练习题的题号顺序是这样的。不同印刷批次可能有细节差异但考点基本一致你重点看解题思路即可不用纠结题号是否完全对上。1. 项目概述与整体设计思路1.1 第四章练习题到底在考什么第四章“复合类型”是这本书第一次系统性地让你接触“由基础类型组合出来的类型”。它不像第三章那样只跟 int、double、char 这种单值打交道而是让你把一个变量当成一个“容器”这个容器里可以同时放着不同类型的数据。数组、字符串、结构体、共用体、枚举、指针和 new这些概念全部在第四章一次性铺开编程练习题的作用就是把它们串起来逼着你用它们写出真正能跑的程序。如果你只看书上的讲解和书末的复习题可能觉得“我都懂了”。但到了编程练习题就会发现真让你从零写一个包含输入、处理、输出的完整程序很多问题就冒出来了字符串数组到底该开多大cin str和cin.getline(str, n)有什么区别结构体指针用-还是.用new分配完内存后要不要delete这些细节只有动手敲代码才会真正刺痛你。1.2 做题前需要先打通的知识点第四章的题需要你同时掌握以下这些前置技能缺一个都可能卡壳数组的声明方式int arr[10]、double scores[3]知道下标从 0 开始知道数组名本质上是一个常量指针。C 风格字符串以空字符\0结尾的char数组用strlen()求长度用strcpy_s()/strcat_s()拼接复制。教材里大量使用了这个老派但重要的方式。string类第六章以前你完全可以依赖它它有size()、拼接等能力第四章已经引入了它所以练习里混用 C 风格字符串和string都是正常的。结构体声明结构体类型、定义结构体变量、成员访问用.结构体指针用-结构体数组的遍历。共用体和枚举至少要理解它们的内存布局和使用场景练习题里会有。动态内存new和delete的配对使用以及怎样通过指针访问动态分配的数组或结构体。如果以上概念里有任何一个感觉模糊建议先把教材相应小节翻一遍再做题不然很容易在写程序时反复报错浪费时间在查语法上而不是练思路。1.3 我对这套练习题的整体设计感受把十道题全部做完以后我最大的感受是出题人并不是想把题目出难而是想让你把每一种复合类型都“用一遍”。十道题里几乎每一道都换了一个侧重点侧重点之间层层递进开始是“声明结构体、输入输出”然后是“字符串的不同处理方式”中途用两到三道题反复练结构体数组和结构体指针最后一道题还要求你用array对象替代普通数组。这种安排非常适合自学者——它不指望你一上来就写出多么优雅的代码只要求你把每个语法点都亲手写对一次。所以这篇文章的定位也不是“标准答案大全”而是“解题思路避坑指南”。我把每道题的思路、参考代码、常见错误都拆开讲清楚你写的时候不用照抄关键是理解每一步在干什么然后自己重新写一遍。2. 核心细节解析与易错点2.1 数组长度和下标基本但最容易翻车第四章的练习里多次出现“用数组存储一组数据”的场景。比如填写一个结构体数组来保存好几条记录或者用char数组存姓名。这里的第一个坑就是数组长度到底开多少才够。很多新手习惯用char name[10]这种“拍脑袋”的长度结果输入一个稍微长一点的姓名就把内存踩了。在实际编程中应该给字符串预留“最长输入 1”的空间那个1是给\0准备的。教材里的一个典型写法是这样const int NameSize 30; char first_name[NameSize]; char last_name[NameSize];这里的 30 只是一个示例你完全可以根据题目要求把缓冲区设置得更大。关键是记住C 风格字符串一定要以\0结尾如果输入正好填满了数组而末尾没有结束符strlen()和cout 都会出问题甚至可能读到数组外面去。这个问题在字符串长度不定的场景下尤其隐蔽。结构体数组的下标也容易出错。当你定义了CandyBar snacks[3]遍历时下标就是 0、1、2如果用 3就会越界。编译器未必会报错但运行结果可能是错误的或者会悄悄破坏别的变量。排查起来特别费劲因为程序可能只是输出不对劲而不是崩溃。2.2 string 和 C 风格字符串混着用要注意接口差异第四章练习题有一个特点既允许你用string又要求你练 C 风格字符串。有的题目书上建议用string有的题则天然就是 C 风格字符串的活儿。两道题放在一起就能看出你对两种字符串的熟悉程度。string的好处是简单#include string #include iostream std::string dessert; std::cin dessert; std::cout dessert.size() std::endl;它的可以直接拼接size()可以直接取长度没有缓冲区溢出的担忧。而 C 风格字符串则需要#include cstring char full_name[60]; strcpy_s(full_name, last_name); strcat_s(full_name, , ); strcat_s(full_name, first_name);注意这里strcpy_s和strcat_s的第二个参数是目标缓冲区大小不是源字符串大小。如果你把目标大小写错运行时会触发安全警告甚至直接中断。很多初学者在这里困惑“为什么我在 Visual Studio 里用strcpy会报错”因为新版编译器推荐使用带_s的安全版本。如果是在命令行用 g 编译直接用strcpy/strcat是没问题的但推荐你自己主动用安全版本形成好习惯。2.3 cin、getline 和 cin.get输入方式混用会产生回车残留这是第四章练习题里最经典、也最折磨人的一个坑。cin age;在读入数字后会把换行符留在输入缓冲区里紧接着如果又用cin.getline(name, 30);它读到缓冲区里的换行符就立刻结束了给你留下一个空字符串。典型场景练习里先让你输入年龄再让你输入姓名。如果你写成int age; char name[30]; std::cin age; std::cin.getline(name, 30);程序会让你输入年龄然后getline直接跳过不会等待输入姓名。解决方案有几种用cin.get()把那个换行符吃掉std::cin age; std::cin.get(); // 吃掉换行符 std::cin.getline(name, 30);先用cin读完整行再转换数字但这在第四章阶段有点超纲。调换输入顺序先getline字符串再cin age。这个坑在做练习题时几乎必然遇到值得多花点时间搞明白。你可以在草稿纸上画一下输入缓冲区的内容变化理解了原因以后这类问题就再也不怕了。2.4 结构体、结构体数组和动态结构体指针第四章练习题大概有一半都围绕结构体展开。结构体本身不难难的是它和其他知识点的组合。比如结构体变量struct Student s; s.name xxx;结构体数组struct Student stu[3];然后循环stu[i].name结构体指针Student *p s; p-name xxx;结构体指针是很多人的第一个“卡点”。因为通常在结构体变量上访问成员用.而指针用-如果你混着写编译会直接报错。教材里还有一个经典操作用new动态分配一个结构体而不是结构体数组Pizza *pg new Pizza; std::cin.getline(pg-name, 30); std::cin pg-diameter; delete pg;这样做的好处是内存大小在运行时才确定也就是动态内存。练习题里有一道题专门练这个如果你亲手写过一遍对“栈上分配”和“堆上分配”的感觉会完全不同。动态结构体还要注意一点当你new了以后用完一定要delete。虽然程序退出后操作系统会回收内存但养成配对new/delete的习惯对未来写大型程序非常重要。我记得我早期刷题时经常忘了delete程序一样能跑就给忘了后来养成了“写完分配立即写释放”的习惯才没有在以后的项目里吃亏。2.5 字符串排序strcmp 返回值的理解有一道题要求把多个单词按字典顺序排序。看着不难但你一旦去写就会遇到strcmp的问题。C 风格字符串不能直接用或比较因为那样比较的是指针地址而不是字符串内容。正确做法是用strcmpif (strcmp(str1, str2) 0) { // str1 在字典顺序上排在 str2 前面 }strcmp的返回值是 int负数表示第一个字符串比第二个小0 表示相等正数表示第一个字符串比第二个大。如果你要实现选择排序比较完以后还要交换字符串数组中的两个元素。这时候你要交换的是整个字符数组的内容不能简单写temp words[i]因为数组名是常量指针不能直接重新赋值。专业的做法是定义临时字符数组用strcpy_s把内容拷进去再把另一个数组拷过来。这个细节我在做练习题时第一次体会到了“数组不能直接赋值”的别扭感。后来用string写就方便多了直接temp words[i];就行但 C 风格字符串的实现依然值得练习。3. 实操过程逐题拆解与参考代码3.1 练习 1结构体存储个人基本信息题目要求创建一个结构体包含姓名和年龄然后让用户输入并输出。这是结构体的入门题也是很多人的第一道“完整程序”题。我当时的代码结构大致如下#include iostream #include cstring struct Student { char name[30]; int age; }; int main() { Student s; std::cout 请输入姓名; std::cin.getline(s.name, 30); std::cout 请输入年龄; std::cin s.age; std::cout 姓名 s.name 年龄 s.age std::endl; return 0; }这里需要注意的点已经说过了名字用getline是因为姓名里可能包含空格如果只输一个单词用cin s.name也行但“空格”是很多名字里合理存在的字符所以用getline更稳妥。输入顺序上我把名字放前面、年龄放后面就是为了避免cin age的换行符残留问题。在 Visual Studio 里运行这个程序中文输出可能需要添加system(chcp 936 nul);或者在项目属性里把字符集改成“使用多字节字符集”。这些属于环境配置问题我在后面“常见问题与排查”里再展开。3.2 练习 2string 类型的简单应用这个练习通常要求你用string对象存姓名或其他信息体会它和 C 风格字符串的区别。参考代码#include iostream #include string int main() { std::string name; std::string dessert; std::cout 请输入你的姓名; std::getline(std::cin, name); std::cout 请输入你最喜欢的小吃; std::getline(std::cin, dessert); std::cout 你的姓名 name 你最喜欢的小吃是 dessert std::endl; return 0; }注意这里用的是std::getline(std::cin, name)它是string版本的getline跟cin.getline不同。前者只接受string对象第二个参数是目标变量不需要指定最大长度。这道题主要是让你感受string的便利不用管长度、不用顾虑\0想拼接、想取长度都更自然。书里之所以把string和 C 风格字符串同时教是要让你知道两条路都走得通实际开发中大多数时候用string更安全高效。做题时我犯过一个低级错误把std::getline(std::cin, name)写成了std::cin.getline(name)然后编译器报错说类型不匹配。这个错误本质上是把两个不同版本的getline混在一起了。搞清楚两个版本各自作用于什么类型就不会再犯。3.3 练习 3C 风格字符串的拼接与复制这道题一般会让你从键盘输入姓和名然后将它们拼接成一个格式化字符串输出。它强制要求用 C 风格字符串操作不能直接用string。参考思路#include iostream #include cstring const int Size 30; int main() { char first_name[Size]; char last_name[Size]; char full_name[2 * Size 2]; std::cout 输入你的名; std::cin.getline(first_name, Size); std::cout 输入你的姓; std::cin.getline(last_name, Size); strcpy_s(full_name, last_name); strcat_s(full_name, , ); strcat_s(full_name, first_name); std::cout 格式化后的字符串 full_name std::endl; return 0; }这里的full_name长度为什么是2 * Size 2Size是每个名字的最大长度两个名字加一起是2 * Size中间还有一个逗号和一个空格一共两个字符最后还要一个位置给\0所以追加2是比较稳妥的做法。你可能会想名字通常不会真的顶格输入随便开大一点行不行行但我们要养成算长度的习惯因为缓冲区溢出是最常见也是最危险的内存错误之一。另一个容易忽略的点是strcpy_s的第二个参数。不同编译器对strcpy_s的原型处理略有不同Visual Studio 里你写strcpy_s(full_name, last_name)可能会报“参数过少”需要写成strcpy_s(full_name, sizeof(full_name), last_name)。这种差异不同环境真的不一样我在学校机器上用的老编译器就不用写sizeof回家在自己电脑上就报错一度很困惑。遇到这种情况最直接的解决办法就是报错说参数过少就补上目标缓冲区大小报错说找不到strcpy_s就临时改用strcpy但心里要清楚strcpy不安全。3.4 练习 4结构体数组遍历这题往往要求定义结构体数组存放多条记录然后通过循环依次打印。它比单纯结构体变量多了一个“数组”的概念。参考代码结构#include iostream struct CandyBar { char brand[30]; double weight; int calories; }; int main() { CandyBar snacks[3] { {Mocha Munch, 2.3, 350}, {Caramel Delight, 1.9, 280}, {Hazelnut Crunch, 2.7, 410} }; for (int i 0; i 3; i) { std::cout 品牌 snacks[i].brand std::endl; std::cout 重量 snacks[i].weight std::endl; std::cout 卡路里 snacks[i].calories std::endl; std::cout ------------------- std::endl; } return 0; }结构体初始化时可以用花括号按成员顺序给值这是 C11 风格的列表初始化C98/03 只支持拷贝初始化。如果你用的编译器比较老可能不接受这种写法这时就改成逐成员赋值CandyBar snack; strcpy_s(snack.brand, Mocha Munch); snack.weight 2.3; snack.calories 350;数组初始化时还可以只初始化部分元素其余元素自动执行默认初始化比如数值类型为 0。这一点在调试时可能造成困惑你以为某个元素没被设置但它的值其实有一个默认状态。这道题本身不难但它为后面更复杂的结构体数组和动态内存题目打底。如果这道题你写得很顺手后面的结构体指针题目会更容易理解。3.5 练习 5共用体或枚举的小练习这类题会围绕union或enum中的一个小知识点展开。共用体的特点是所有成员共享同一块内存同一时刻只能保存一个成员的值。如果你先存了一个 int再存一个 double那 int 的内容就会被覆盖。所以它适合用在“多种类型但一次只用一种”的场景比如一个“值”可能是数字也可能是字符串。一个典型的共用体声明union Value { int int_val; double double_val; char str_val[20]; };枚举的典型声明enum Color { red, orange, yellow, green, blue }; Color my_color red;做题时我先会用枚举来表示某种状态比如披萨的大小等级或者糖果的颜色分类然后用共用体存储附加信息。这种题主要考你对“类型的内存布局”的理解。写的时候我记得最深刻的一点是共用体变量的内存大小等于最大成员的大小而不是所有成员之和。如果你在程序里打印sizeof(Value)会发现它比我们直观想象的要小。这就是所谓“空间换时间还是时间换空间”的老话题共用体是典型省内存的方案。3.6 练习 6用循环输入结构体数组这道题会稍微进阶一点不是直接在初始化列表里写死数据而是让用户逐一输入多条记录。此时你要注意的就不只是结构体访问还有输入方法的选择。核心代码框架#include iostream struct CandyBar { char brand[30]; double weight; int calories; }; int main() { CandyBar snacks[3]; for (int i 0; i 3; i) { std::cout 输入第 i 1 个品牌; std::cin.getline(snacks[i].brand, 30); std::cout 输入重量; std::cin snacks[i].weight; std::cout 输入卡路里; std::cin snacks[i].calories; std::cin.get(); // 吃掉换行符准备下一次 getline } for (int i 0; i 3; i) { std::cout 品牌 snacks[i].brand 重量 snacks[i].weight 卡路里 snacks[i].calories std::endl; } return 0; }这里有一个极其重要的细节循环里每次输入完数字后都要调用std::cin.get()把缓冲区里残存的换行符吃掉。如果你漏了这一步下一次循环中的getline会直接读到残留换行符并返回空字符串品牌就变成空了。我在第一次写这个循环时连续三次输入输出前两次品牌正常第三次就变成空白排查了半天才发现是换行符问题。另一个经验是把输入和输出分开处理先统一输入再统一输出这样逻辑更清晰。如果你一边输入一边输出代码也能跑但容易把循环逻辑搅在一起出现问题更难定位。3.7 练习 7动态分配结构体这道题一般要求用new动态创建一个结构体然后输入数据并输出。它是第四章里第一次让你正式接触“堆内存”跟前面 “栈上定义结构体变量” 的体验有明显区别。参考代码#include iostream #include cstring struct Pizza { char company[30]; double diameter; double weight; }; int main() { Pizza *pg new Pizza; std::cout 输入披萨公司名称; std::cin.getline(pg-company, 30); std::cout 输入披萨直径; std::cin pg-diameter; std::cout 输入披萨重量; std::cin pg-weight; std::cout 公司 pg-company std::endl; std::cout 直径 pg-diameter std::endl; std::cout 重量 pg-weight std::endl; delete pg; return 0; }注意这里访问成员用的是pg-company而不是pg.company因为pg是一个指针。如果你是第一次写这种代码很可能会混着用.和-编译器报错后才知道问题出在哪。实际上别管是.还是-它们的本质区别是左边是对象还是指针。对象用点指针用箭头就这么记。动态分配结构体的好处是内存空间是在程序运行时从堆中申请的而不是在编译时就固定。堆空间比栈空间大得多所以对于大结构体动态分配是常见做法。但用完以后delete不要忘记否则就是内存泄漏。可以给自己立一个规矩写完new的那一行紧接着先把delete写出来再写中间的代码。这样再怎么改都不会忘。如果你把delete pg忘写了程序照样能正常输出所以新手往往觉察不到。但如果在一个长期运行的程序里反复new而从不delete内存会越占越多最后系统变慢甚至崩溃。这是一个在练习题阶段看起来“无所谓”实际危害很大的坏习惯。3.8 练习 8字符串字典序排序这题对新手来说是最容易懵的一道。题目一般要求输入多个单词然后按字典顺序输出。它的难点在于C 风格字符串比较大小用strcmp而不是/交换字符串内容要复制而不是赋值。一个用选择排序实现的完整方案#include iostream #include cstring const int WordSize 30; const int WordCount 5; int main() { char words[WordCount][WordSize]; std::cout 输入 WordCount 个单词 std::endl; for (int i 0; i WordCount; i) { std::cin.getline(words[i], WordSize); } // 选择排序按字典序升序 for (int i 0; i WordCount - 1; i) { int min_index i; for (int j i 1; j WordCount; j) { if (strcmp(words[j], words[min_index]) 0) { min_index j; } } if (min_index ! i) { char temp[WordSize]; strcpy_s(temp, WordSize, words[i]); strcpy_s(words[i], WordSize, words[min_index]); strcpy_s(words[min_index], WordSize, temp); } } std::cout 排序后的结果 std::endl; for (int i 0; i WordCount; i) { std::cout words[i] std::endl; } return 0; }排序的逻辑本身跟整型数组排序没有区别关键是字符串比较和交换的写法。strcmp(words[j], words[min_index]) 0表示当前单词比候选最小单词在字典序上更靠前。交换时你不能写char* temp words[i]然后把 words 里的指针换掉因为words[i]本身是数组名它的地址不可变。必须先声明char temp[WordSize]用一个一个字符拷贝的方式也就是strcpy_s来完成交换。如果你改用std::string存储排序会容易很多直接temp words[i]就能交换。但这个练习的初衷是练 C 风格字符串所以建议还是老老实实写一遍char数组版本。如果有同学想挑战自己可以对比一下两种写法的代码量string版本大概会少十行左右而且不容易出错。这也是为什么实际项目里几乎都用string但学习时仍需要懂 C 风格字符串的原理。3.9 练习 9动态结构体数组或其他动态内存题目这道题跟第 7 题类似但通常要求动态分配一个结构体数组并把输入的数据存进去。它比“用一个new分配单个结构体”多了一个维度new []与delete []的配对。代码骨架#include iostream struct Car { char producer[30]; int year; }; int main() { int count; std::cout 输入汽车数量; std::cin count; std::cin.get(); Car *cars new Car[count]; for (int i 0; i count; i) { std::cout 输入第 i 1 辆车的生产厂商; std::cin.getline(cars[i].producer, 30); std::cout 输入第 i 1 辆车的生产年份; std::cin cars[i].year; std::cin.get(); } for (int i 0; i count; i) { std::cout cars[i].producer : cars[i].year std::endl; } delete[] cars; return 0; }注意这里我用的是Car *cars new Car[count]它是一个指针指向一个堆上的数组。访问元素时我用cars[i].producer这是因为cars[i]本身是一个Car对象用点访问成员如果我用(cars i)-producer也一样效果等同。这里特别容易混淆的点是指针指向数组首元素首元素是对象对象用点访问成员。等你以后学到“指针数组”和“数组指针”的时候这种混淆会更加明显。delete[]语法上多了一对方括号少了这对方括号行为未定义不同编译器处理方式不一样有的会崩有的不崩。我的习惯是new[]对应delete[]new对应delete永远配对这个习惯能帮你避免非常多的隐性故障。3.10 练习 10用 array 对象存储数据这道题通常让你用std::array或者std::vector替代普通数组解决“数组定长”的痛点。如果你还没学到标准模板库没关系第四章已经可以简单使用array了。参考代码#include iostream #include array int main() { std::arraydouble, 3 scores; std::cout 输入三次成绩\n; for (int i 0; i 3; i) { std::cin scores[i]; } double total 0.0; for (int i 0; i 3; i) { total scores[i]; } double average total / scores.size(); std::cout 总分 total std::endl; std::cout 平均分 average std::endl; return 0; }std::array的好处是不需要手动管理内存也不会衰减成指针scores.size()能直接拿到元素个数。它和 C 风格数组最大的区别是C 风格数组名在传入函数时会退化成首元素指针导致你丢失“这个数组有多长”的信息而std::array把大小封装成了类型的一部分。这道题在难度上其实不算高它是整章练习的“轻松结尾”让你体会一下现代 C 的便利性。但如果你之前一直习惯用传统数组第一次用array可能会有一点不适应比如它不能直接初始化成{}以外的东西遍历时更推荐用范围 for 循环。4. 常见问题与排查技巧实录4.1 编译错误速查表刷第四章练习题时我收集到的高频编译错误和解决办法整理成了下面这个表你可以贴在桌边随时看错误现象常见原因解决办法在 Visual Studio 中strcpy报错 C4996新版编译器强制推荐安全函数改用strcpy_s或者明确缓冲区大小不能将参数 1 从“char*”转换为“const char *”字符串字面量和变量类型不匹配检查是否声明为char数组或const修饰未定义标识符“strcpy_s”没有包含cstring或string.h添加#include cstring数组下标的类型错误用了非整型变量作为下标把下标换成int或size_t不允许使用不完整的类型结构体声明还没写完就使用检查结构体定义是否以分号结尾member access into incomplete type忘记包含结构体定义的头文件将结构体定义放在使用之前表格最后一行的“incomplete type”其实在第四章很少出现更多是等你以后拆分多文件时才会遇到。不过提前知道一下没坏处如果你在main函数里使用结构体但结构体定义在另一个文件里且只做了前置声明编译器不知道怎么给这个对象分配内存就会报这个错。4.2 运行期问题集锦运行期问题比编译错误更烦人因为它们不总是报错而是输出看起来“好像不太对”。第一个是最常见的“超长字符串被截断”。当你用cin.getline(name, 30);时如果用户输入了超过 29 个字符输入流会进入 fail 状态后续的cin操作都会失效。表现是程序明明有多个输入却直接跳到了末尾。解决办法是让getline的第二个参数大一些或者在输入后调用std::cin.clear()清理状态。第二个问题是“窗口一闪而过”。这是小白最容易卡住的程序运行完以后控制台窗口马上关闭你根本来不及看输出结果。解决办法是在main的return 0;之前加一行std::cin.get();如果前面用了cin 则可能需要写两个std::cin.get()因为第一个会吃掉残留换行符。或者直接用system(pause)但这不是跨平台的做法Linux 下没有pause命令。我在练习题阶段喜欢用两个cin.get()虽然看起来笨但跨平台没问题。第三个问题是“中文输出变成乱码”。这通常是因为源码文件编码和终端编码不一致。Visual Studio 默认源码是 UTF-8 或 GB2312而终端控制台通常用 GBK简体中文 Windows。文件开头添加system(chcp 936 nul);会把终端代码页切换成 GBK但这也只是权宜之计。更好的办法是写代码时统一用 UTF-8 编码终端也改成 UTF-8。不过对新手来说先用system(chcp 936 nul)把中文输出搞定别让环境问题干扰练习本身也是可以的。第四个问题是“符号重定义”。假设你同时在main.cpp里定义了结构体又在一个头文件里定义了同名结构体链接时就会报重复定义。第四章练习一般只有一个文件出现概率不高但如果你把多个练习粘贴到同一个文件里且不同练习里定义了一模一样的结构体就会遇到这个问题。解决办法是每个练习建一个独立项目或者在同一个文件里给结构体起不同的名字。4.3 调试技巧怎么快速定位“不明所以”的错误第四章的程序都比较短Debug 起来不算难。但我发现很多初学者在程序出错时第一反应是“是不是哪一行语法错了”而不是打印中间变量的值。调试技巧比语法知识更值钱因为语法错误编译器会直接告诉你位置而逻辑错误需要自己去观察。我常用的几个方法在关键位置加std::cout 到这里了 std::endl;用来确认程序流程是否到达某个分支。第四章练习题里最常见的流程问题就是循环次数多一或少一加一行输出就能立刻看出来。打印中间量的长度和内容。比如拼接字符串后先打印strlen(full_name)和full_name看看是不是预期结果。如果长度不对说明问题出在缓冲区大小或strcpy/strcat的使用上。检查输入流状态。如果getline没有按预期等待输入在调用前加一行std::cout std::cin.fail() std::endl;如果输出 1说明输入流因为上一次的错误输入进入了 fail 状态需要clear()和“吃掉坏输入”。利用 IDE 的断点调试。如果你用的是 Visual Studio直接在可疑行加断点按 F5 运行然后按 F11 逐步执行查看变量的实时值。这个方法对理解指针尤其有效你能直观地看到“指针的值”和“指针指向对象的值”的区别。我有一个印象非常深的调试案例练习里要求输入披萨信息我先输入重量再输入品牌名结果品牌名总是空字符串。我打印了输入流状态发现上一轮的cin pg-weight已经把换行符留在缓冲区然后getline(pg-company, 30)读到换行符就结束了根本不等待用户输入。解决办法是在cin pg-weight后加一个std::cin.get();。这种问题如果不调试靠肉眼很难发现。4.4 不同编译环境下的兼容性差异第四章练习题里最容易爆出环境差异的就是strcpy_s和strcat_s。Visual Studio 中它们被当作标准函数直接使用但 g 在某些版本里可能没有这两个函数必须用strcpy和strcat。如果你的代码在 电脑上写了strcpy_s拿到另一台用 g 的机器上编译可能报“没有匹配的函数”。为了减少这种困扰建议你在学习阶段统一用主流 IDE 的默认配置。如果你用的是 Visual Studiostrcpy_s是安全的如果你在 Linux 上用 g直接用strcpy。但心里要清楚strcpy可能出现越界问题写的时候自己留意缓冲区大小。如果确实遇到平台差异可以用一个条件编译来解决#ifdef _MSC_VER strcpy_s(temp, WordSize, words[i]); #else strcpy(temp, words[i]); #endif这种做法在练习题阶段有点“杀鸡用牛刀”但知道有这回事以后写跨平台代码时就会自然想到。5. 一点实操心得希望能帮你少走弯路把第四章十道题全部写完后我最想分享的不是哪道题的答案而是刷题顺序和学习节奏。如果你时间有限建议按这样的优先级去刷先做第 6 题结构体数组输入输出和第 7 题动态结构体这两题覆盖了结构体、指针、输入缓冲区多个难点刷透它们等于拿下了第四章的七成考点。然后做第 8 题字符串排序它是最容易让人放弃的一道但也是最能巩固 C 风格字符串基本功的题。第 2 题和第 10 题相对轻松可以在前面几道题刷累的时候穿插着做当作调剂。另外遇到“代码跑了但结果不对”的事情别急着怀疑编译器有问题。百分之九十九的情况是你自己的代码有问题而且多半出在缓冲区换行符、数组下标、字符串结束符这三个地方。我刷完这一章后养成的一个习惯是每次写完一个输入相关的程序都会先画一下输入缓冲区里到底剩了什么再决定下一个cin或getline是否需要多吃一个换行符。这个习惯让我在后面写文件输入输出、网络编程时也少踩了很多坑。第四章说到底就是在教你“怎么用一块连续记忆体存放复杂数据”数组、结构体、指针都是这个主题的不同侧面。等你把这些练习题都写顺了再往后看函数、指针运算、动态内存时会明显觉得轻松很多。我自己在刷完这一章后对 C 程序的“内存视角”算是真正建立起来了那种感觉就像是之前写代码只看得见表面语法现在能隐约看到数据在内存里是怎么排布的了。希望你也能刷出这种感觉。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑