ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C语言入门:从翁恺课程到编程环境搭建与核心概念解析

2026/8/8 1:04:09 拓冰建站 浏览量
C语言入门:从翁恺课程到编程环境搭建与核心概念解析 1. 项目概述为什么从翁恺老师的C语言课开始如果你正在寻找一个系统、扎实且能真正带你入门的C语言学习起点那么浙江大学翁恺老师的《C语言程序设计》课程几乎是一个无需犹豫的选择。这门课在国内外各大慕课平台上的口碑已经持续了近十年它之所以能成为无数编程初学者的“第一课”核心原因在于其独特的教学理念不炫技不跳跃从计算机如何“思考”的根本讲起。很多自学教程一上来就扔给你一堆语法规则告诉你int是整数printf是打印但很少告诉你为什么需要int计算机底层是怎么处理这些数据的一个最简单的程序从你敲下键盘到屏幕上显示出结果中间到底经历了什么翁恺老师的课恰恰填补了这个认知断层。我最初接触编程时也走过弯路啃过那种罗列语法的“字典式”教材结果就是看似学了不少一遇到实际问题就无从下手因为脑子里没有建立起程序运行的完整图景。直到跟着翁恺老师的视频和讲义过了一遍才恍然大悟原来编程语言是人和计算机沟通的“协议”而C语言是这个协议里非常接近底层硬件的一种学习它本质上是在学习计算机科学最基础的思想。这门课的第一周“程序设计与C语言”就是为你搭建这幅“全景图”的关键。它不会立刻让你写出复杂的代码但会为你后续学习指针、内存管理这些C语言核心难点打下坚不可摧的地基。所以这份笔记的目的不仅仅是复述课程内容而是结合我自身从入门到实际开发的经验对第一周的核心知识点进行深度解读、补充和实战化延伸。我会重点剖析那些初学者容易卡壳的概念并补充课程中限于课时未深入展开但对理解C语言至关重要的背景知识。无论你是零基础的大学生还是想重温基础的在职开发者这份笔记都能帮你更顺畅地跨过“从零到一”这道最重要的门槛。2. 核心概念拆解程序、设计与语言第一周的内容看似基础但信息密度极高。翁恺老师用几个核心问题串联起了整个编程世界的轮廓。我们需要把这些点连成线再扩展成面。2.1 程序究竟是什么从“做菜”到“执行指令”课程开篇就会问什么是程序最通俗的类比是“菜谱”。菜谱程序本身是静态的文字描述代码它规定了做菜的步骤算法。只有当你计算机的CPU拿起厨具内存、硬盘等硬件按照菜谱一步步操作才能最终得到菜肴运行结果。但这个类比可以再深入一层。菜谱是用中文写的CPU看不懂中文。所以我们需要一个翻译把中文菜谱翻译成CPU能理解的机器语言二进制指令。C语言编译器如GCC、Clang就是这个“翻译官”。你写下的C代码高级语言经过编译器的处理变成了可执行文件机器码。当你双击这个可执行文件时操作系统会把它加载到内存中然后CPU逐条执行里面的指令。这里有一个关键细节可执行文件里存储的不仅仅是CPU指令还有程序运行所需的数据、内存布局信息等。你可以用size命令Linux/macOS或查看PE结构Windows来粗略了解一个可执行文件的构成。理解“程序是存储在磁盘上的静态文件运行时要被加载到动态的内存中”这一概念对后续理解指针、数组、内存分配至关重要。2.2 程序设计解决问题的思维框架程序设计不等于写代码。写代码只是设计思想的最终表达。翁恺老师强调的程序设计核心是“抽象”和“分解”。抽象Abstraction忽略无关细节抓住核心本质。比如我们要编写一个“计算全班平均分”的程序。我们不需要关心每个同学的名字、学号只需要关心“分数”这个数据。于是我们抽象出“分数”这个概念在C语言里用float或double类型的变量来表示。更进一步我们抽象出“求和”与“求平均”这两个操作。分解Decomposition把大问题拆解成一系列小步骤或小模块。计算平均分可以分解为1. 获取分数个数N2. 循环N次每次读入一个分数并累加3. 总和除以N得到平均值4. 输出结果。这个过程就是算法的雏形。在初学阶段我强烈建议养成在写代码前先写伪代码或画流程图的习惯。哪怕只是用中文在注释里写下步骤也能极大地理清思路避免边写边想导致的逻辑混乱。注意很多新手会陷入“语法细节”的泥潭比如纠结%f和%lf的区别却忽略了整体逻辑。程序设计思维是比任何具体语法都更重要的能力。先想清楚“做什么”和“怎么做”再去找C语言里“怎么表达”。2.3 C语言的角色为什么是它翁恺老师会简述C语言的历史和特点高效、灵活、接近硬件。这里我想补充一些背景让你明白为什么在Python、Java如此流行的今天学习C语言依然有价值。承上启下的关键层C语言诞生于1972年它比汇编语言高级不用直接操作寄存器、内存地址又比后来的Java、Python等语言底层可以直接操作内存。它像是高级语言和硬件之间的一层“薄薄的封装”。学习C语言你能清晰地看到高级语言特性如数组、结构体是如何映射到内存的连续字节上的。这种理解是通透的。系统编程的基石操作系统Linux内核、数据库MySQL、编译器GCC、嵌入式系统其核心部分几乎都是用C/C写的。因为它们需要对硬件进行精细控制追求极致的性能和效率。如果你想深入这些领域C是必经之路。理解其他语言的钥匙很多现代语言Go, Rust的语法和设计思想都深受C影响。理解了C的指针、内存管理你再去看Java的引用、Python的列表底层实现CPython解释器是用C写的会有一种“原来如此”的豁然开朗感。C语言就像汽车的手动挡。开自动挡Python更轻松但学会手动挡C你才能真正理解发动机计算机系统是如何工作的。当自动挡车出现一些复杂问题时你的知识储备会让你知道从哪里入手排查。3. 第一个程序Hello World的深度剖析几乎所有的编程入门都从“Hello, World!”开始。翁恺老师的课程也不例外。但请不要小看这寥寥几行代码里面包含了C程序最基本的结构和大量隐含信息。#include stdio.h int main() { printf(Hello World!\n); return 0; }让我们逐行进行“显微镜式”的观察3.1#include stdio.h头文件与编译过程这行代码的作用是“包含标准输入输出头文件”。stdio.h里声明了printf、scanf等函数的原型。没有这行声明编译器看到printf时会不认识它报“未定义的引用”错误。背后的原理C语言的编译过程分为预处理、编译、汇编、链接四个阶段。预处理编译器首先处理所有以#开头的指令。#include就是告诉预处理器“请把stdio.h文件里的全部内容原封不动地复制到我这里来。”你可以用gcc -E hello.c -o hello.i命令生成预处理后的文件.i文件看看里面到底被加入了多少代码通常会非常长。编译将预处理后的C代码.i文件翻译成汇编代码.s文件。汇编将汇编代码翻译成机器指令生成目标文件.o或.obj文件。链接将我们程序的目标文件和printf函数实际所在的库文件如libc.a“链接”在一起生成最终的可执行文件。所以#include本质是一种文本替换发生在真正的编译开始之前。理解这一点就能明白为什么头文件里通常只放函数声明、宏定义、类型定义而不放函数的具体实现否则会导致重复定义。3.2int main()程序的唯一入口main函数是C程序执行的起点和终点。操作系统把程序加载起来后就从main函数的第一条语句开始执行。返回值类型intmain函数返回一个整数给操作系统通常用return 0;表示程序正常结束。非0值如return 1;表示程序异常退出。这个返回值有时会被父进程比如命令行Shell捕获用于判断程序是否执行成功。在命令行中你可以通过echo $?Linux/macOS或echo %ERRORLEVEL%Windows查看上一个命令的退出码。参数列表()这里的括号是空的表示main函数不接受任何参数。实际上main函数可以带参数int main(int argc, char *argv[])用于接收命令行参数。argc是参数个数argv是参数字符串数组。这是与操作系统交互的一个重要接口。3.3printf(Hello World!\n);与return 0;printf函数它的功能是把格式化的数据打印到标准输出通常是屏幕。双引号里的内容是一个字符串常量\n是一个转义字符代表换行Newline。没有\n下一次输出就会紧跟在“Hello World!”后面。实操细节printf的返回值是成功打印的字符数。这个特性在调试或精细控制输出时偶尔有用但初学者可暂不深究。return 0;如前所述向操作系统报告程序正常结束。在C99标准之后如果main函数执行到末尾没有遇到return语句编译器会自动帮你加上return 0;。但显式地写出return 0;是一个非常好的习惯它使代码意图更清晰也兼容更老的标准。3.4 从编辑到运行完整的工具链实践翁恺老师的课程可能会推荐IDE如Dev-C但我强烈建议初学者在早期就接触命令行编译这对理解整个构建过程有巨大帮助。环境准备Linux/macOS系统通常自带GCCGNU Compiler Collection。打开终端Terminal输入gcc --version检查。Windows推荐安装MinGW-w64或直接使用WSLWindows Subsystem for Linux。也可以使用Visual Studio的开发者命令行工具内含cl.exe。命令行编译四步曲假设文件名为hello.c编辑用任何文本编辑器VSCode、Vim、Notepad编写上面的代码保存为hello.c。编译链接打开命令行切换到文件所在目录执行gcc hello.c -o hello-o hello指定生成的可执行文件名为helloWindows下为hello.exe。运行# Linux/macOS ./hello # Windows hello.exe观察输出屏幕上应该显示Hello World!。为什么推荐命令行透明你能清楚地看到从源代码到可执行文件的每一步。IDE点一下“运行”按钮背后也是调用了这些命令。可控你可以方便地添加各种编译选项例如-Wall -Wextra开启所有警告帮助发现代码中的潜在问题。-stdc11指定使用C11标准进行编译。-g在可执行文件中加入调试信息便于用GDB调试。通用命令行环境在服务器、嵌入式开发中无处不在早点熟悉没有坏处。4. 编程环境搭建与避坑指南工欲善其事必先利其器。一个顺手的编程环境能极大提升学习效率和体验。除了上面提到的命令行GCC这里再详细对比几种常见方案。4.1 集成开发环境IDE vs 代码编辑器 命令行特性集成开发环境 (如 Code::Blocks, Dev-C, CLion)代码编辑器 命令行 (如 VSCode, Sublime Text GCC)优点开箱即用图形化界面友好集成了编译、调试、项目管理功能适合纯新手快速上手。轻量、灵活、可高度定制。能让你透彻理解编译流程技能可迁移性强。插件生态丰富。缺点隐藏了底层细节容易成为“黑箱”。某些IDE配置老旧或非标准可能和教学环境有差异。需要手动配置初期有一定学习成本。调试功能需要额外配置如VSCode配置launch.json。推荐度初学者可短期使用用于熟悉语言本身但建议尽快过渡。强烈推荐作为主力环境。VSCode是当前最流行的选择配置一次受益终身。4.2 使用VSCode配置C语言环境详细步骤VSCode本身只是一个编辑器需要配合插件和工具链才能进行C语言开发。安装编译器如前所述确保系统已安装GCCMinGW-w64 for Windows。安装VSCode从官网下载安装。安装必要插件C/C (Microsoft)提供代码智能感知补全、跳转、调试支持。Code Runner可以一键运行多种语言的代码非常方便。配置基础设置关键步骤创建一个专门的文件夹用于存放C语言项目例如C_Learning。用VSCode打开这个文件夹。在该文件夹下新建一个.vscode文件夹里面新建三个文件tasks.json(用于配置编译任务)launch.json(用于配置调试)c_cpp_properties.json(用于配置编译器路径和智能感知)以下是简化版的配置文件示例你可以根据注释修改c_cpp_properties.json(告诉VSCode编译器在哪){ configurations: [ { name: Win32, // 配置名称 includePath: [ ${workspaceFolder}/** ], defines: [], compilerPath: C:/mingw64/bin/gcc.exe, // **重点修改为你的gcc.exe实际路径** cStandard: c11, cppStandard: c17, intelliSenseMode: gcc-x64 } ], version: 4 }tasks.json(定义编译任务){ tasks: [ { type: shell, label: C/C: gcc build active file, command: gcc, args: [ -fdiagnostics-coloralways, -g, ${file}, -o, ${fileDirname}/${fileBasenameNoExtension}.exe ], options: { cwd: ${workspaceFolder} }, problemMatcher: [$gcc], group: { kind: build, isDefault: true } } ], version: 2.0.0 }launch.json(定义调试配置){ version: 0.2.0, configurations: [ { name: (gdb) Launch, type: cppdbg, request: launch, program: ${fileDirname}/${fileBasenameNoExtension}.exe, args: [], stopAtEntry: false, cwd: ${workspaceFolder}, environment: [], externalConsole: true, // 使用外部控制台避免输入输出问题 MIMode: gdb, miDebuggerPath: C:/mingw64/bin/gdb.exe, // **修改为你的gdb.exe路径** setupCommands: [ { description: Enable pretty-printing for gdb, text: -enable-pretty-printing, ignoreFailures: true } ], preLaunchTask: C/C: gcc build active file // 调试前先执行编译任务 } ] }使用编写hello.c后按F5即可启动调试并运行使用Code Runner插件的话可以按CtrlAltN快速编译运行。避坑指南VSCode配置中最常见的问题就是路径错误。compilerPath和miDebuggerPath必须指向你电脑上真实的GCC和GDB可执行文件路径。在Windows上路径中的斜杠要用/或双反斜杠\\。如果遇到“无法找到任务‘gcc’”之类的错误请仔细检查tasks.json中的command是否是gcc并且gcc已在系统环境变量PATH中。4.3 在线编译器快速验证的利器如果你不想在本地配置环境或者需要在不同电脑间同步代码在线编译器是很好的补充。推荐Compiler Explorer (godbolt.org)优点功能极其强大。可以实时看到C代码对应的汇编代码对于理解代码底层行为有莫大帮助。支持多种编译器GCC, Clang, MSVC和版本对比。缺点主要用于代码片段分析和学习不适合大型项目开发也没有输入交互功能。其他Replit, OnlineGDB等提供了更接近完整IDE的在线体验可以处理输入输出适合运行和测试小程序。在线工具可以作为学习的“第二屏幕”用于快速验证语法、对比编译器行为但不能替代本地开发环境。扎实的编程能力离不开对本地工具链的熟练使用。5. 初识变量与算术运算从“盒子”到“表达式”在展示了Hello World之后课程自然会引入如何让程序进行“计算”。这就涉及到C语言中两个最基础的概念变量和运算符。5.1 变量数据的容器与内存的标签翁恺老师会用“盒子”来比喻变量这个比喻非常形象。我想在这个基础上补充一些关于内存的视角。当你声明int price 0;时你做了两件事向编译器申请资源告诉编译器“我需要一块足够存放一个int类型数据的内存空间”。在大多数现代系统上一个int通常占4个字节32位。给这块空间起名你给它起了个名字叫price。从此在代码中你使用price这个标识符就是在操作那块内存空间里的值。变量的三要素类型Type如int,float,double,char。类型决定了数据在内存中占用的字节数大小。数据的解释方式格式。同样的二进制位int解释为整数float解释为浮点数结果天差地别。可以对数据执行的操作。例如int可以进行取模运算%而float不行。名称Name即标识符。C语言有命名规则以字母或下划线开头后续可以是字母、数字、下划线。区分大小写。应使用有意义的英文单词命名如totalPrice,studentCount避免使用拼音或a1,x2这种无意义的名字。值Value存储在内存空间中的具体数据。可以通过赋值运算符来改变。一个重要的概念左值与右值左值 (lvalue)指代一个内存位置、可以出现在赋值号左边的表达式。变量名就是典型的左值。右值 (rvalue)指代一个具体的数值、可以出现在赋值号右边的表达式。常量如100、算术表达式的结果如price 20是右值。 理解这一点有助于避免写出100 price;这样的错误100是右值不能被赋值。5.2 基本数据类型与sizeof运算符C语言提供了几种基本数据类型翁恺老师的课会逐一介绍。这里我强调一下它们的典型大小和范围这取决于编译器和系统但以下是最常见的情况类型典型大小取值范围近似说明char1字节-128 到 127 或 0 到 255字符型也可用于小整数int4字节-2,147,483,648 到 2,147,483,647最常用的整型short2字节-32,768 到 32,767短整型节省空间long4或8字节范围大于等于int长整型long long8字节约 ±9.2e18超长整型(C99)float4字节约 ±3.4e38精度6-7位有效数字单精度浮点数double8字节约 ±1.7e308精度15-16位有效数字双精度浮点数推荐默认使用void无无表示“无类型”用于函数返回值或指针如何查看具体大小使用sizeof运算符。sizeof是C语言的一个关键字/运算符用于计算类型或对象所占用的内存字节数。它在编译时就能确定结果返回一个size_t类型的值一种无符号整数。#include stdio.h int main() { printf(Size of char: %zu byte(s)\n, sizeof(char)); printf(Size of int: %zu byte(s)\n, sizeof(int)); printf(Size of float: %zu byte(s)\n, sizeof(float)); printf(Size of double: %zu byte(s)\n, sizeof(double)); int a; printf(Size of variable a: %zu byte(s)\n, sizeof(a)); // 也可以对变量使用 return 0; }注意打印sizeof结果时格式符使用%zuC99标准它是专门为size_t类型设计的。在某些旧编译器上可能需要用%u或%lu并做强制类型转换。5.3 算术运算符与表达式求值C语言提供了丰富的运算符第一周主要接触算术运算符,-,*,/,%取模求余数。几个关键点整数除法与浮点数除法这是新手最常见的错误之一。int a 5, b 2; double c; c a / b; // 错误a/b是整数除法结果为2。将整数2赋值给double变量c得到2.0。 printf(c %f\n, c); // 输出 2.000000 c (double)a / b; // 正确将a强制转换为double再进行除法结果是浮点数除法得到2.5。 printf(c %f\n, c); // 输出 2.500000规则如果两个操作数都是整数/执行整数除法结果截断小数部分。只要有一个操作数是浮点数就执行浮点数除法。想要得到浮点数结果至少要将一个操作数转为浮点类型。取模运算符%只能用于整数。a % b的结果是a除以b后的余数其符号与a相同。例如-5 % 2的结果是-1。运算符优先级与结合性和数学一样*,/,%优先级高于,-。同一优先级从左向右结合左结合性。不确定时多用括号来明确计算顺序这能增加代码可读性避免错误。赋值运算符它不是“等于”而是“赋予”。a b 1;的意思是“计算b1的值然后将这个值存入a代表的内存空间”。赋值表达式本身也有值其值就是被赋予的值。这允许链式赋值a b 5;但不推荐影响可读性。5.4 实战练习从“读入-计算-输出”模式开始第一周的编程练习基本都遵循同一个模式读入使用scanf从用户输入获取数据。计算用变量和运算符进行算术或逻辑运算。输出使用printf将结果格式化输出。一个经典的例子计算两个整数的和。#include stdio.h int main() { int a, b, sum; // 1. 声明变量 printf(请输入两个整数用空格隔开); scanf(%d %d, a, b); // 2. 读入。注意是取地址运算符必须要有。 sum a b; // 3. 计算 printf(它们的和是%d\n, sum); // 4. 输出 return 0; }关于scanf的注意事项第一个大坑scanf中的格式字符串%d %d要和输入的数据类型严格匹配。%d对应int%f对应float%lf对应double注意printf中float和double都用%f但scanf中double必须用%lf。符号不能丢a表示变量a的地址。scanf需要知道将读入的数据存放在内存的哪个位置。忘记写是初学者最常犯的错误之一会导致程序运行时崩溃或行为异常。scanf的返回值是成功读入的数据项个数。可以利用这一点进行简单的输入验证例如if (scanf(%d %d, a, b) ! 2) { printf(输入错误\n); }。6. 常见问题与排查技巧实录学习第一周你可能会遇到各种各样的问题。下面我整理了一些典型问题及其解决方法很多都是我曾经踩过的坑。6.1 编译错误Compiler Errors编译错误是语法错误编译器无法将你的代码翻译成机器码。必须全部修正才能生成可执行文件。error: expected ‘;’ before ‘return’原因上一行语句末尾缺少分号;。C语言中几乎每条语句都以分号结尾。排查检查报错行return所在行的上一行补上分号。error: ‘printf’ undeclared原因忘记包含stdio.h头文件或者头文件名拼写错误如#include studio.h。排查检查文件开头是否有#include stdio.h。error: ‘main’ must return ‘int’原因在一些严格的编译设置下main函数的返回值类型必须是int不能是void。尽管有些编译器允许void main()但这不符合C语言标准。解决始终使用int main()并在结尾return 0;。warning: implicit declaration of function ‘scanf’原因同样是因为没有包含stdio.h。在C99标准中调用未声明的函数会报错而不仅仅是警告。解决确保包含了必要的头文件。6.2 链接错误Linker Errors链接错误发生在编译之后链接器无法找到函数或变量的定义。undefined reference toprintf原因虽然包含了头文件声明了printf但链接时找不到printf的实现库。这通常不是你的代码问题而是环境配置问题。排查检查编译器安装是否完整。对于MinGW确保安装了包含运行时库的完整版本。检查编译命令是否正确。如果是命令行编译确保命令是gcc hello.c -o hello而不是gcc -c hello.c-c选项只编译不链接。在IDE中检查项目设置是否指定了正确的工具链。6.3 运行时错误Runtime Errors与逻辑错误程序能运行但结果不对或中途崩溃。程序运行后一闪而过看不到输出原因程序执行完毕控制台窗口自动关闭。这在Windows下直接双击运行.exe文件时很常见。解决推荐在命令行中运行程序。在程序最后return 0;之前加上getchar();等待用户按一个键后再结束。如果是IDE通常可以在设置中找到“运行后暂停控制台”的选项。使用scanf后程序跳过输入或行为异常原因输入缓冲区遗留问题。例如先读了一个整数scanf(“%d”, a);然后想读一个字符scanf(“%c”, ch);第二个scanf会立刻读到上次输入整数后按下的回车符\n而不会等待你输入新字符。解决在读取字符前用getchar()清空缓冲区while ((c getchar()) ! ‘\n’ c ! EOF);。在scanf的格式字符串中加入空格来消耗空白字符scanf(” %c”, ch);注意%c前的空格。更深层建议对于新手在简单的控制台程序中可以考虑使用fgets读取一整行字符串然后再用sscanf从字符串中解析数据这样能更好地控制输入。但这超出了第一周的范围可以先作为知识储备。整数运算结果溢出现象两个很大的正整数相加结果变成了负数。原因int类型的取值范围有限约±21亿。当计算结果超出这个范围时会发生“溢出”就像汽车里程表从99999变回00000一样。排查检查你的数据范围。如果可能很大考虑使用long long类型%lld格式符打印或者使用浮点数double。浮点数比较的陷阱现象if (1.0/3.0 * 3.0 1.0)这个条件判断可能为假。原因浮点数在计算机中是以二进制近似存储的存在精度误差。直接比较两个浮点数是否相等可能不可靠。解决比较浮点数时通常比较它们的差的绝对值是否小于一个很小的数epsilon。#include math.h // 需要包含math.h头文件 double a 1.0 / 3.0 * 3.0; double b 1.0; if (fabs(a - b) 1e-12) { // fabs是求绝对值的函数 printf(a 和 b 在精度范围内相等。\n); }6.4 调试Debugging初探使用printf大法对于初学者最直接有效的调试方法就是在代码中插入printf语句打印关键变量的值观察程序执行到哪一步以及数据的变化是否符合预期。#include stdio.h int main() { int a, b, sum; printf([DEBUG] 程序开始。\n); // 标记1 printf(请输入两个整数); scanf(%d %d, a, b); printf([DEBUG] 读入 a%d, b%d\n, a, b); // 标记2检查输入是否正确 sum a b; printf([DEBUG] 计算 sum%d\n, sum); // 标记3检查计算是否正确 printf(和是%d\n, sum); printf([DEBUG] 程序结束。\n); // 标记4 return 0; }这是一种简单粗暴但极其有效的方法。随着学习深入你会接触到更强大的调试器如GDB它可以让你单步执行、设置断点、随时查看变量效率更高。但在初期熟练使用printf进行调试是每个程序员的必修课。学习C语言的第一周就像学习一门新语言的字母和发音。它可能有些枯燥充满了看似琐碎的规则分号、括号、符号。但请相信这些规则是后续构建复杂程序大厦的基石。理解“程序是静态指令集”、“变量是内存的命名容器”、“编译链接的流程”远比死记硬背一两个语法点重要得多。当你能够独立地在本地环境完成代码的编辑、编译、运行和简单调试并理解其背后的过程时你就已经成功地迈出了成为程序员的第一步。接下来的旅程将是学习如何使用这些基本的“砖块”去构建判断分支、循环、模块函数以及更复杂的数据结构。保持耐心多动手敲代码多思考“为什么”你会在解决一个又一个具体问题的过程中获得持续的成就感。