资讯动态

C++编程实例100篇:从语法学习到工程实践的进阶路径

发布时间:2026/10/16 20:07:02 来源:尧图企业网站定制
简介这是一份以100个可运行C程序为线索的编程实例集覆盖基础语法、面向对象、函数、文件读写、图形编程与经典算法等主题适合初学者跟练也适合开发者查漏补缺。随包提供源码、工程配置与可执行文件可对照验证运行结果便于理解代码逻辑与编译流程。资源含557个文件包括107个cpp源码、106个bmp位图、106个obj目标文件、100个exe程序以及dsp/dsw等工程文件与少量头文件压缩包约2.47MB。实例中还包括基于BGI的图形演示与配套素材适合作为C课程设计或自学上手的参考库。已有42000余人学习下载是一套轻量而系统的编程练习资源。1. C编程实例100篇打通「语法全会、动手全废」的那堵墙学完C语法再打开编辑器却写不出第一行这种症状几乎每个自学者都经历过。指针、引用、构造函数都能背可真要落笔实现一个功能却常常半天憋不出一段能编译的代码。C编程实例100篇这类练习集恰好是针对这个问题的解药把庞大的C知识拆成100个能独立完成的小题目每个题目只聚焦一个知识点做完一个就收获一次从编译到运行的正反馈。这套流程适合刚学完语法需要大量巩固练习的初学者适合准备校招笔试的在校生也适合初入公司需要写C小工具的开发者。这篇笔记会从实例选题、工程组织、高频错误到代码质量讲一套可以直接照做的完整练习路径。2. 实例怎么选100篇的难度分层、题目设计与排序100篇不是随便凑数的它的价值建立在合理的选题结构上。如果只是把网上零散的小题拼成100个难度忽高忽低知识点跳来跳去练起来很容易中途放弃。我见过不少练习合集数字是凑够了但前20篇还在讲变量定义第21篇突然跳到多线程这种编排对新手是劝退而不是帮助。想让100篇真正可执行难度分层、题目设计、排序逻辑三个环节要提前想清楚。2.1 难度分层404020的三档配法把100篇按难度切成三档是让整套练习真正可执行的前提。第一档是入门大约40篇覆盖变量、运算符、分支、循环、函数、数组和字符串的基础操作。这一档的目标很直接看到题目不用翻语法书能独立写出能编译运行的代码。第二档是进阶大约40篇覆盖类与对象、封装继承多态、运算符重载、STL容器与算法、流与文件操作。这一档的目标是让学习者有能力拆解一个中等规模的需求把“实现一个类”当成日常操作。第三档是工程大约20篇聚焦移动语义、智能指针、异常安全、多线程和常用设计模式。到了这一档代码能不能跑已经不是重点重点是能否说出每个设计选择背后的代价。这个比例不是拍脑袋定的。入门档太少学完后面对类和模板会心虚入门档太多又容易在简单的循环里消磨耐心。进阶档占最大比例因为大多数实际工作场景是在写业务逻辑而业务逻辑的核心就是类和标准库容器的组合。工程档放在最后20篇像一个过渡带让学习者在刷完语法后有足够空间消化那些真正影响代码质量的概念。如果时间紧张我一般建议至少把前两档的一半做完再往后深入因为工程档里的例子对语法基础的要求很高基础不牢去碰移动语义和智能指针很容易变成照抄模板。这里还有一个容易被忽略的细节分档之后每一档内部最好也保持难度递增。以入门档为例前10篇只用顺序结构中间10篇加入分支最后20篇才系统地练循环和函数。这样每一篇都只引入一个不确定因素出问题时定位范围小学习者的挫败感也低。分档的意义就在于让练习者始终处于“有一点挑战但够得着”的状态而不是一次性丢出一堆难易不分的题目。2.2 题目设计一句话需求加输入输出样例实例的题目描述质量直接决定了练起来顺不顺畅。我见过不少实例集题目写了一长串背景故事读完还要自己提炼需求这对新手很不友好。好题目应该满足一个标准用一句话讲清楚要做什么再配一组输入和输出样例。设计题目时可以用固定模板行为描述、输入格式、输出格式、样例。行为描述尽量用动词开头比如“实现一个函数把字符串按空格拆分成单词数组”输入格式说清楚数据类型和范围比如“第一行一个整数n1≤n≤1000”输出格式说明每一行是什么样例至少两个一个覆盖普通情况一个覆盖边界情况比如空串、最大值、负数。实例编号行为描述输入样例输出样例021判断一个整数是否为素数17YES022输入一行单词输出出现次数最多的单词hello world hellohello 2023实现一个队列类支持入队、出队和判空push 1 push 2 pop1这种一板一眼的设计不是没有用它其实在训练一种关键的工程能力从需求描述到输入输出约束的转化。真到工作中产品提需求很少给清边界习惯了实例里这种明确约束再看那些模糊需求时至少知道该反问哪些问题。设计题目时另一个注意点是样例要能手工推导不要让学习者为了验证一个输出还要再写一段计算脚本。有人觉得样例太详细会让人不动脑我的看法相反练习题的价值在实现过程不在读题过程把理解成本降下来精力才能花在写代码上。2.3 排序逻辑让每篇练习踩在前一篇的肩膀上实例的顺序必须按知识依赖来排而不是按难度随机打散。我经常见到一些练习集把“实现一个链表”排在很靠前理由是“直观”。但链表依赖指针、动态内存、析构函数新手第一次接触只会照抄然后一脸茫然。合理的做法是每引入一个新知识点时实例里同时用到的是前一篇已经出现过的内容。具体排序可以参照三条原则先内置后自定义前20篇只用内置类型和标准库不出现自己写的类先底层后封装先写手动管理的数组和结构体再引入类把这些封装起来先单线程后并发多线程实例一定要放到最后。按这个思路前30篇可以全是函数和数组的组合第31篇引入结构体第40篇才出现第一个自定义类。每条原则背后是同一个考量让每一个新概念出现时周围的代码都是学习者写法最熟悉的部分这样注意力能全部集中在新知识点上。排序对自学者还有一个隐藏作用它是一种天然的复习机制。当一篇实例同时用到类的构造、STL容器和文件操作时学习者被迫把前面分散的知识串在一起这种串接只靠重复做题是练不出来的。如果你拿到一套现成的100篇实例发现顺序比较随意我一般的做法是自己重排索引把依赖关系理顺后再开始而不是硬着头皮按原顺序刷。宁可先跳过几篇也比在知识断档里反复怀疑自己强。3. 把100个实例组织成工程CMake与编译调试的最小闭环实例练习最容易犯的一个错是只写一个几十行的 main.cpp跑通就丢下一篇从零开始。这样练50篇代码组织能力几乎没有提升。实例数量一多编译、运行、调试的效率会直接影响练习节奏。所以从第1篇开始就建立一套固定的工程结构反而是投入产出比最高的准备。3.1 标准代码骨架从注释到验证样例同样是实例代码组织方式不同练习效果差很多。我见过不少新手把每一篇都写成一个塞满全局变量的 main 函数跑通就完事。更好的做法是给每一篇实例一个固定骨架题目注释、需求分析、实现代码、验证样例。整个文件的结构保持一致读代码的人一眼就能找到入口。下面是一篇完整实例的代码骨架题目是“统计一行文本中每个单词的出现次数”输入输出约束写在注释里。// 题目统计一行文本中每个单词的出现次数 // 输入一行文本单词之间用空格分隔长度不超过 10000 // 输出每行一个单词和出现次数按单词字典序排列 #include iostream #include map #include sstream #include string int main() { std::string line; // 读取整行避免空格导致只读一半 std::getline(std::cin, line); std::istringstream stream(line); std::string word; std::mapstd::string, int freq; // 按空白切分单词逐一统计 while (stream word) { freq[word]; } // map 默认按键升序排列直接输出即可 for (const auto entry : freq) { std::cout entry.first : entry.second std::endl; } return 0; }这个实例里有几个点值得停下来想一下。std::istringstream 把一行字符串变成输入流operator 自动跳过空白天然实现了按空格拆词不需要手写切分逻辑。std::mapstd::string, int 的 operator[] 在键不存在时会自动插入一个值为0的条目所以 freq[word] 一句就同时完成了插入和自增不用先判断键是否存在。范围 for 遍历 map 时entry.first 是键entry.second 是值。这段代码虽然短但把容器、IO流、结构化绑定三个知识点的组合用得很典型比单独背语法更能建立“这些特性可以一起用”的意识。3.2 CMake 统一管理新增实例不用改构建脚本100个实例如果用命令行一条一条编译时间都浪费在敲命令和找历史命令上。从第1篇开始用 CMake 组织工程是最值得投入的前期准备。推荐的目录结构如下每个子目录放一个阶段的实例文件名前缀跟实例编号保持一致。cpp100/ ├── CMakeLists.txt ├── 01_basic/ │ ├── 001_hello.cpp │ ├── 002_fibonacci.cpp │ └── ... ├── 02_advanced/ │ ├── 041_stack_class.cpp │ └── ... └── 03_project/ ├── 081_smart_pointer.cpp └── ...顶层 CMakeLists.txt 可以写成下面这样它做的事情是把每个子目录里的 .cpp 文件收集起来逐个生成同名的可执行文件。cmake_minimum_required(VERSION 3.16) project(cpp100 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 遍历阶段目录自动收集所有源文件 foreach(dir IN ITEMS 01_basic 02_advanced 03_project) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}) aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/${dir} DIR_SRCS) foreach(src ${DIR_SRCS}) get_filename_component(exe_name ${src} NAME_WE) add_executable(${exe_name} ${src}) endforeach() endif() endforeach()这段构建脚本里aux_source_directory 会把目录下所有 .cpp 文件加入列表get_filename_component 取每个文件去掉扩展名的名字作为可执行文件名。这样以后每新增一个实例只需要把对应的 .cpp 文件放进合适目录重新执行 cmake 和 make 就会自动生成目标CMakeLists.txt 完全不用改。CMAKE_CXX_STANDARD 设置为 17是为了让范围 for、auto、智能指针这些现代语法在实例里直接可用。有一点需要留意如果某个目录里放了测试辅助文件比如一个公共头文件用 aux_source_directory 会把其中的 .cpp 也编成可执行文件这时建议改成显式列出源文件不要让收集逻辑把你的构建脚本变成黑匣子。3.3 编译与调试最小工作流g 与 AddressSanitizer实例的编译建议从一条固定命令起步尤其是从一开始就带上警告和内存检查参数。我习惯在项目根目录建一个 build 目录构建和运行分开做。下面是针对单个实例文件的快速编译命令。# 进入实例所在目录 cd cpp100/01_basic # 单文件快速编译打开警告并开启内存检查 g -stdc17 -Wall -Wextra -fsanitizeaddress,undefined -g 001_hello.cpp -o 001_hello # 运行 ./001_hello这几个参数的作用值得记一下。-g 生成调试信息崩溃时能定位到具体行号。-Wall 和 -Wextra 打开常见警告比如未使用变量、无符号整数比较。-fsanitizeaddress,undefined 会在运行时检查越界访问、未定义行为和使用已释放内存这些问题平时不一定触发崩溃开了它基本能当场报出来。唯一的代价是程序运行变慢一些但对实例练习来说完全无所谓。调试时最常用的是 gdb几个命令就够。遇到程序崩溃或输出不对先 bt 查看调用栈再用 frame 跳到对应的层级接着 print 打印变量。对于读入相关实例一个很有效的技巧是重定向输入把样例保存成文件运行时用 符号喂给程序这样每次运行都能复现同样的输入不用反复手敲。提示练习阶段建议始终打开 -fsanitizeaddress它能让多数“输出莫名其妙”的问题变成带文件名和行号的直接报错这是排查内存问题最划算的一步。4. 实例练习高频避坑C100篇里最常踩的5个问题刷实例的过程里真正消耗精力的往往不是语法而是一批只看代码很难发现的运行期问题。这些问题不解决会让学习者误以为“是我不适合学C”其实只是少了一点经验。下面这5个坑在100篇的实题目里出现频率最高每一条都按“现象、原因、解决”的顺序说清楚。4.1 cin 与 getline 混用缓冲区残留读出一行空串现象程序先执行 std::cin n; 读了一个整数紧接着用 std::getline(std::cin, line); 读一行字符串结果 line 的内容是空串。原因operator 在读完整数后会把末尾的换行符留在输入缓冲区中。getline 读到这个换行符认为“这一行已经结束了”直接返回。这个换行符并不会导致程序出错但是会让字符串读取结果完全偏离预期。解决在两次读取之间调用 std::cin.ignore();它默认丢弃输入流中的下一个字符正好把残留的换行吞掉。int n; std::string line; std::cin n; std::cin.ignore(); // 丢弃缓冲区里残留的换行符 std::getline(std::cin, line);之后读取 n 后面的整行就正常了。这个坑在练习输入输出类实例时几乎必踩而且很难靠报错信息定位因为程序完全正常运行只是数据不对。更稳妥的习惯是如果一行内容的格式不确定就统一用 getline 读取再用 std::istringstream 解析从源头上避免混用。4.2 数组越界不一定崩溃内存损坏的隐蔽现场现象声明 int arr[10]; 然后执行 arr[10] 5; 之后打印某个局部变量发现它的值变成了5而程序没有报告任何错误。原因C 不检查数组下标。arr[10] 访问的是数组之外紧邻的内存很可能正是编译器安排的另一个局部变量的位置。写了 arr[10]等于悄悄改了那个变量的值。程序不崩溃反而让问题更难被发现。解决优先使用 std::vector 或 std::array需要越界安全时用 at()它会抛异常而不是静默越界。如果非要用原生数组搭配 -fsanitizeaddress 编译越界会在第一次发生时被检测到并报出来。我在练习里发现许多“莫名其妙得到负数”“循环次数不对”的问题根源都在数组越界开着这个选项一下就现形了。4.3 循环里 erasevector 迭代器失效现象遍历 std::vector 时调用 erase 删除元素程序偶尔正常偶尔崩溃或者出现漏删元素。原因vector 删除元素后所有指向被删除位置及其之后的迭代器都会失效。循环里的 it 是在已经失效的迭代器上执行属于未定义行为表现因编译器和数据规模而异。解决接收 erase 返回的迭代器它指向被删除元素的下一个位置循环体内不要再额外执行 。std::vectorint v {1, 2, 3, 4, 5}; // 删除所有等于3的元素 for (auto it v.begin(); it ! v.end();) { if (*it 3) { it v.erase(it); // erase 返回下一个有效迭代器 } else { it; } }如果一次要删除多个满足条件的元素更推荐用 std::remove_if 配合 erase它先把不满足条件的元素移动到前面再统一删除尾部整个过程只涉及一次真正的搬移代码也更短。这个坑在实现“学生信息管理”“联系人列表”这类增删改查实例时出现的频率很高值得在练习时专门留意。4.4 delete 之后继续用指针悬空指针的未定义行为现象new 一个对象delete 之后发现指针仍然指向原来的地址于是又通过这个指针调用了一次方法程序时好时坏。原因delete 只是释放了堆内存并没有把指针变量本身的地址值清零。指针仍然指向那块已归还给系统的内存继续使用就是未定义行为。如果系统把那块内存分配给了别的对象你还会通过旧指针修改到其他对象的数据几乎不可能排查。解决delete 后立即把指针置为 nullptr置空后再误用会直接触发空指针访问错误能立刻暴露。更根本的做法是优先使用 std::unique_ptr 和 std::shared_ptr让智能指针在析构函数里自动释放资源从根源上避免手动管理。int* p new int(42); delete p; p nullptr; // 置空防止误用 auto sp std::make_uniqueint(42); // 不需要手动 delete离开作用域自动释放4.5 没写拷贝构造浅拷贝与双重释放现象类里有一个指针成员构造函数用 new 分配内存没有实现拷贝构造函数。代码里把这个类的对象按值传给函数或按值返回程序运行结束或返回时崩溃。原因默认的拷贝构造函数做的是逐字节复制。两个对象的指针成员指向同一块堆内存当两个对象依次析构时同一块内存被 delete 了两次第二次 delete 触发崩溃。这个错误在返回值优化缺失的旧编译环境下尤其明显但新手往往意识不到是拷贝构造的问题。解决遵循三/五法则只要自定义了析构函数就必须同时考虑拷贝构造和拷贝赋值。更稳妥的选择是用 std::string、std::vector 这类标准库容器替代裸指针成员让编译器生成的默认拷贝行为本身就正确。class Text { std::string data; // 用标准库类型管理内存避免手工维护 public: explicit Text(const std::string str) : data(str) {} };这一条在练习“自己封装字符串”“实现自定义容器”时尤其要注意。封装的类一旦涉及指针或动态数组第一步就是检查三个特殊成员函数是否齐全。等到程序崩溃再去查费时费力而且大概率查不出直接原因。5. 从能跑到能看给100个实例做一轮代码质量体检实例跑通只是完成了50%剩下的一半是让代码经得起追问。很多实例能运行但经不起修改换个输入规模就超时加一个功能就要改三处别人接手根本不敢动。给每篇实例做一轮质量体检把基础习惯补齐这套练习的价值会翻倍。5.1 const 引用与 RAII实例里最容易被忽略的好习惯实例跑通之后第一轮体检应该从 const 入手。函数参数能写成 const 引用的就不要按值传递。按值传入 std::vector 会触发一次完整的拷贝对这个拷贝的修改不会影响外部调用方反而掩盖了拷贝构造带来的开销和潜在问题。改成 const 引用后代码意图也更清楚不复制同时保证函数内不能修改原数据。// 按值传入触发拷贝外部不可见白白多一次复制 void print(std::vectorint values); // 改为 const 引用不复制同时保证函数内不能修改原数据 void print(const std::vectorint values) { for (int v : values) { std::cout v ; } }RAII 是 C 里值得单独列出来的资源管理思路含义是让资源生命周期绑定到对象生命周期。文件句柄、互斥锁、堆内存都属于资源放进一个管理类后离开作用域时析构函数会自动释放就不会再有“忘关文件”“忘释放内存”的问题。std::ifstream 和 std::unique_ptr 就是 RAII 的现成例子实例里凡是打开文件、申请内存的地方都应该优先用它们而不是手动调 close 或 delete。很多学员在练习时觉得 RAII 是抽象概念其实它就在每一次文件读取和动态内存分配里。5.2 错误处理返回码与异常的选择标准实例里写文件读取、除法运算、内存分配时都要面对一个选择错误怎么报告。一条简单的经验是错误发生在局部、调用方知道怎么处理时用返回码错误会沿调用链向上传递、中间层不关心细节时用异常。实例练习建议把两种写法各用几篇再形成自己的偏好。// 返回码风格适合简单实例调用路径直白 bool open_config(const std::string path, std::ifstream ifs) { ifs.open(path); return ifs.is_open(); } // 异常风格适合错误必须穿过多层调用的情况 double divide(double a, double b) { if (b 0.0) { throw std::invalid_argument(divisor cannot be zero); } return a / b; }返回码的缺点是容易被忽略调用方不检查返回值错误就悄悄放过去了。异常的问题则相反它强制调用方处理但也不是所有场景都适合用异常高频循环里抛异常会明显拖慢速度。实例练习里我一般的做法是函数内部能处理就处理掉处理不了就向上抛让调用点决定怎么办。不要在一篇实例里混用两种风格一致性比选哪种风格更重要。5.3 最小测试三个用例守住的正确性实例要做对靠肉眼盯输出很快就会漏掉边界情况。给每个实例配一组最小测试是投入产出比最高的练习习惯。三个用例基本够用一个正常输入一个边界输入一个非法输入。以加法函数为例测试可以这样写。#include cassert int add(int a, int b) { return a b; } int main() { assert(add(2, 3) 5); // 正常输入 assert(add(0, 0) 0); // 边界都为0 assert(add(-1, 1) 0); // 边界正负抵消 return 0; }assert 在 Release 编译下会被去除定义所以它只适合练习阶段不适合作为线上错误检查。真正工程里会使用专门的测试框架比如 GTest 或 Catch2。练习初期用 assert 的好处是没有额外依赖一个头文件就能跑。写测试时要注意测试用例要模仿真实输入的格式尤其是涉及整行读取的实例如果测试里用 std::cin建议用重定向把样例喂给程序这样测试结果才稳定不会因为手动输入多敲了一个空格导致误判。6. 100篇练完之后把实例改造成一个小项目的具体技巧练完100篇最容易踏出的一步是把两个实例拼成一个组合体。以“统计单词频率”和“文件读取”为例可以拼接成“统计整个文件的词频并按次数排序”。改造步骤其实只有三步第一步把输入从 std::cin 换成 std::ifstream文件名由命令行参数传入第二步把统计结果从用 std::map 自动排序改成按次数排序这里会用到 std::sort 配合 lambda第三步把输出写到 result.txt让程序从演示脚本变成一个能处理真实文件的工具。这一步值得做的原因在于它强迫你把输入来源、输出去向、处理逻辑三个部分解耦。真实项目里这三个部分几乎总是独立演进的输入从控制台改成网络输出从文件改成数据库但核心处理逻辑保持不变。在实例阶段体验一次这种改造比背十遍“高内聚低耦合”都管用。改造成小项目时你也会第一次遇到“改了一处导致另一处不工作”的问题这正是从练习转向工程的关键节点。我自己的习惯是每个阶段练完挑一篇印象最深的实例重新改造一遍看看能不能换一个输入来源或者加一个约束条件。这个小习惯让我后来接手别人代码时第一反应不是抱怨结构乱而是先判断哪些地方能改、哪些地方动了会导致连锁反应。C实例练习的意义不在于做完100篇而在于做完之后面对一个没有现成答案的问题时敢动手拆、能拆得动。希望这套流程能帮到你先把第一批实例跑起来跑完10个再根据手感调整节奏比把时间花在反复挑选资料上值得得多。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑