十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++入门指南:从Hello World到程序骨架与环境搭建

C++入门指南:从Hello World到程序骨架与环境搭建 1. 从“Hello World”到理解程序骨架C入门第一步很多朋友第一次接触C可能都是从一行cout “Hello World”;开始的。这行代码就像一个仪式宣告你正式踏入了系统级编程的世界。但C的魅力远不止于此它既是构建操作系统、游戏引擎、高频交易系统的基石也是理解计算机如何工作的绝佳窗口。我刚开始学的时候总觉得它语法复杂、概念繁多远不如Python、JavaScript那样“友好”。但后来在解决一个内存泄漏导致的服务器崩溃问题时我才真正体会到正是C这种对内存和硬件的直接掌控能力赋予了开发者无与伦比的性能优化空间和问题排查的清晰度。这篇文章我想和你聊聊如何从一个纯粹的“Hello World”新手迈出理解C程序骨架和核心思想的第一步避开那些我当年踩过的坑。无论你是计算机专业的学生还是希望深入理解底层机制的后端开发者或是想涉足游戏、嵌入式领域的爱好者一个扎实的C入门基础都将是你技术工具箱里最锋利的武器之一。2. 环境搭建选对工具事半功倍工欲善其事必先利其器。一个顺畅的编码环境能让你把精力集中在学习语言本身而不是和工具搏斗。2.1 编译器选择GCC、Clang与MSVCC代码需要编译器翻译成机器码才能运行。主流选择有三个GCC (GNU Compiler Collection)开源世界的标杆跨平台支持极好Linux/macOS/Windows标准支持激进社区活跃。对于初学者在Linux上它是天然首选在Windows上可以通过MinGW或MSYS2来获取。Clang/LLVM近年来势头迅猛编译速度快错误和警告信息清晰易懂对C新标准支持也非常好。macOS上的Xcode默认使用它在Linux和Windows上也可方便安装。MSVC (Microsoft Visual C)Windows平台的“土著”编译器与Visual Studio IDE深度集成对Windows特有API和开发如MFC、DirectX支持最好。我的选择建议如果你是Windows用户并且未来可能涉及Windows桌面程序开发直接从Visual Studio Community版免费开始是最省心的它自带MSVC编译器和强大的IDE。如果你是跨平台学习者或者使用Linux/macOS那么GCC或Clang是更通用的选择。我个人在Linux上偏爱GCC在macOS上则用Clang。2.2 集成开发环境IDE与编辑器Visual Studio (Windows)功能巨无霸智能提示IntelliSense、调试器、图形化界面设计器一应俱全。对于新手来说它帮你隐藏了编译链接的复杂细节一键运行体验流畅。缺点是体积庞大。VS Code (全平台)轻量级但功能可通过插件无限扩展。通过安装“C/C”扩展和配置编译器路径你可以获得接近IDE的体验。它轻快、可定制适合喜欢“自己动手组装”工具链的开发者。这也是目前非常多C开发者的选择。CLion (全平台付费)JetBrains出品以智能著称对现代CC11/14/17的支持和理解非常深入重构、代码分析功能强大。如果你有学生邮箱可以申请免费许可。终端 文本编辑器 (Vim/Emacs)极客之选追求极致效率和可控性。需要手动编写Makefile或使用CMake来管理构建过程学习曲线陡峭但不失为深入理解构建流程的好方法。2.3 一个简单的VS Code配置示例假设你选择了VS Code和MinGW版的GCCWindows。安装好VS Code和MinGW后关键步骤是配置tasks.json和launch.json。安装C/C扩展在VS Code扩展商店搜索并安装微软官方发布的“C/C”扩展。创建项目文件夹和源文件比如hello.cpp。配置编译任务 (tasks.json)按CtrlShiftP输入 “tasks: Configure Task”选择“C/C: g.exe build active file”。这会生成一个.vscode/tasks.json文件。你需要确保“command”字段指向你的g.exe正确路径例如“C:\\mingw64\\bin\\g.exe”。这个文件定义了如何编译你的代码。{ version: 2.0.0, tasks: [ { type: cppbuild, label: C/C: g.exe 生成活动文件, command: C:\\mingw64\\bin\\g.exe, args: [ -fdiagnostics-coloralways, -g, ${file}, -o, ${fileDirname}\\${fileBasenameNoExtension}.exe ], options: { cwd: ${fileDirname} }, problemMatcher: [ $gcc ], group: { kind: build, isDefault: true }, detail: 编译器: C:\\mingw64\\bin\\g.exe } ] }配置调试 (launch.json)切换到调试视图点击“创建一个 launch.json 文件”选择“C (GDB/LLDB)”。同样需要检查“miDebuggerPath”是否指向正确的gdb.exe路径。{ version: 0.2.0, configurations: [ { name: (gdb) 启动, type: cppdbg, request: launch, program: ${fileDirname}\\${fileBasenameNoExtension}.exe, args: [], stopAtEntry: false, cwd: ${fileDirname}, environment: [], externalConsole: true, // 建议设为true避免VS Code终端输入问题 MIMode: gdb, miDebuggerPath: C:\\mingw64\\bin\\gdb.exe, setupCommands: [ { description: 为 gdb 启用整齐打印, text: -enable-pretty-printing, ignoreFailures: true } ] } ] }踩坑记录初期最常见的错误就是编译器路径配置不对导致“无法找到命令”。务必在终端输入g --version确认安装和PATH环境变量配置正确。另一个坑是“externalConsole”如果设为false在调试需要输入的程序时VS Code的内置终端可能会有问题建议设为true使用系统控制台。3. 第一个程序解剖“Hello World”让我们写下并深入理解这段代码#include iostream // 预处理指令 int main() { // 主函数程序入口 // 在标准输出流通常是控制台打印信息 std::cout Hello, World! std::endl; return 0; // 向操作系统返回退出码0通常表示成功 }3.1 预处理指令#include#include iostream是一个预处理指令在编译器正式编译代码之前由预处理器处理。它的作用是把头文件iostream的内容主要是输入输出流相关的类、对象和函数的声明原封不动地插入到这条指令所在的位置。iostream是C标准库的头文件用于输入输出。没有它编译器就不认识std::cout和std::endl。注意#include后面跟着尖括号通常用于包含编译器自带的或系统路径下的标准库头文件。而包含你自己写的头文件时一般使用双引号“”如#include “myheader.h”编译器会优先在当前目录查找。3.2 主函数main()int main()是每个C可执行程序的唯一且必须的入口点。操作系统加载程序后就从这里开始执行。int是返回值类型表示主函数返回一个整数给操作系统这个值称为“退出状态码”。惯例是0表示成功非零值表示各种错误。main是函数名。()内是参数列表可以为空也可以接受命令行参数int argc, char* argv[]。{ ... }是函数体包含要执行的语句。3.3 命名空间std::std::cout和std::endl中的std::是命名空间限定符。命名空间用来避免命名冲突。C标准库的所有内容都封装在名为std的命名空间中。::是作用域解析运算符表示“在...里面寻找”。std::cout标准输出流对象通常关联到控制台。std::endl是一个操纵器作用是插入一个换行符并刷新输出缓冲区。你也可以用转义字符‘\n’只换行但不立即刷新缓冲区。在大多数简单场景下两者效果看起来一样。3.4 输出操作在这里不是位运算中的左移而是被重载为“流插入运算符”。你可以把它想象成数据流动的方向将右侧的数据字符串“Hello, World!”和std::endl“插入”到左侧的输出流cout中。它可以连续使用形成链式调用。3.5 语句与分号C中每条完整的语句都以分号;结尾。这是语法要求忘记分号是新手最常见的编译错误之一。4. 基本数据类型与变量数据的容器程序处理数据数据需要存放在内存中。变量就是给一块内存区域起的名字而数据类型决定了这块内存的大小和解释方式。4.1 基础内置类型类型含义典型大小位备注bool布尔型8 (通常)取值true或falsechar字符型8存放单个字符如 ‘a‘, ‘1‘int整型32存储整数范围约 ±21亿float单精度浮点型32约6-7位有效数字double双精度浮点型64约15-16位有效数字默认推荐void无类型N/A通常用于函数返回值或指针关于大小的关键点除了char被标准确保至少8位其他类型的大小字节数是实现定义的取决于编译器和硬件平台如32位 vs 64位系统。int通常是机器的“自然字长”。如果需要确定大小的整数C11引入了cstdint头文件提供了int32_t,uint64_t等类型。4.2 变量定义与初始化int age 25; // 拷贝初始化 double price {19.99}; // 列表初始化C11推荐能防止窄化转换 char grade ‘A‘; bool isReady true; // 声明的同时初始化多个变量但建议分开提高可读性 int x 5, y 10; // 使用 auto 进行类型推导C11 auto name “Alice”; // name 被推导为 const char* auto score 95.5; // score 被推导为 double列表初始化{}这是现代C更推荐的初始化方式。它有一个重要特性禁止“窄化转换”。例如int num {3.14};会导致编译错误因为从double到int丢失了精度。而int num 3.14;只会给出警告num最终值为3。这有助于在编译期捕获潜在错误。auto关键字让编译器根据初始化表达式自动推导变量类型。它能简化代码特别是面对复杂类型时如迭代器。但不要滥用在类型清晰、有助于代码可读性的地方应显式写出类型。4.3 常量const与constexprconst表示变量值在初始化后不可修改。它主要是一个运行时常量的承诺。const int MAX_SIZE 100; // MAX_SIZE 200; // 错误不能修改const变量constexpr(C11)表示变量是编译期常量。它的值必须在编译时就能确定。这允许编译器进行更多优化并且该变量可以用在需要编译期常量的地方如数组大小、模板参数。constexpr int ARRAY_SIZE 256; // 正确字面量是编译期可知的 int buffer[ARRAY_SIZE]; // 可以用作数组大小 constexpr double pi 3.1415926; // constexpr int userInput getInput(); // 错误getInput()是运行时函数经验之谈默认情况下对于意图不变的量优先考虑使用constexpr。如果初始化表达式不能在编译期求值比如依赖于运行时输入再退而求其次使用const。这既是性能优化也是一种更好的语义表达。5. 运算符与表达式数据的运算运算符让数据产生联系和变化。5.1 算术、关系与逻辑运算符这些和大多数语言类似但要注意C的特性整数除法5 / 2结果是2不是2.5。因为两个操作数都是整数结果也被截断为整数。要得到浮点数结果至少需要一个操作数是浮点数5.0 / 2或5 / 2.0。取模%只能用于整数类型结果是除法的余数。5 % 2结果是1。自增/自减--区分前置i和后置i。前置版本先加1然后返回加1后的值后置版本先返回当前值然后加1。在不需要使用返回值的场景下推荐使用前置版本因为它通常效率更高对于内置类型没区别但对于重载了这些运算符的复杂类型后置版本需要创建临时对象。逻辑运算符短路求值与和||或支持短路求值。对于a b如果a为false则不会计算b对于a || b如果a为true则不会计算b。这可以用来安全地编写条件判断例如if (ptr ! nullptr ptr-value 10)。5.2 位运算符C作为系统级语言提供了直接操作比特位的能力这在嵌入式、网络协议、性能优化中非常有用。按位与|按位或^按位异或~按位取反左移右移一个简单例子标志位管理const unsigned int FLAG_A 1 0; // 0001 const unsigned int FLAG_B 1 1; // 0010 const unsigned int FLAG_C 1 2; // 0100 unsigned int settings 0; settings | FLAG_A; // 设置A标志位 settings 0001 settings | FLAG_C; // 设置C标志位 settings 0101 if (settings FLAG_A) { // 检查A标志位是否被设置 std::cout “Flag A is set.” std::endl; } settings ~FLAG_C; // 清除C标志位 settings 00015.3 赋值与复合赋值运算符是赋值,-,*,/,%,,|,^,,是复合赋值运算符。a b等价于a a b但前者通常更高效且表达式意图更清晰。6. 基本输入输出与用户交互我们已见过输出cout输入使用cin。6.1 使用cin进行输入#include iostream #include string // 为了使用std::string int main() { int number; double value; std::string name; std::cout “Enter an integer, a double and your name: “; std::cin number value name; // 连续输入 std::cout “You entered: “ number “, “ value “, “ name std::endl; return 0; }cin variable从标准输入键盘读取数据并存入变量。它会跳过输入流开头的空白字符空格、制表符、换行符。链式调用和cout一样可以连续使用。类型匹配cin根据变量类型尝试解释输入。如果输入“abc”给int变量会导致输入失败cin进入错误状态后续所有输入操作都会失效。6.2 处理字符串输入getlinecin 对于字符串会读到空白符为止。如果你想读取一整行包括空格需要使用std::getline。#include iostream #include string int main() { std::string fullName; int age; std::cout “Enter your age: “; std::cin age; std::cin.ignore(); // 忽略之前输入年龄后留在缓冲区的换行符 std::cout “Enter your full name: “; std::getline(std::cin, fullName); // 读取整行 std::cout “Hello, “ fullName “. You are “ age “ years old.” std::endl; return 0; }经典陷阱混合使用cin 和getline时cin 不会消耗输入后的换行符这个换行符会留在输入缓冲区中紧接着的getline会立刻读到这个空行导致看起来被“跳过”。解决方法是在cin 后调用cin.ignore()来清除缓冲区中的换行符。这是新手必踩的坑之一。7. 控制流决定程序的走向程序并非总是直线执行需要根据条件或重复执行某些任务。7.1 条件语句if,else if,else,switchif-else是最常用的分支结构。int score 85; if (score 90) { std::cout “Grade: A” std::endl; } else if (score 80) { std::cout “Grade: B” std::endl; // 会执行这里 } else if (score 70) { std::cout “Grade: C” std::endl; } else { std::cout “Grade: F” std::endl; }switch用于基于一个整型或枚举类型的值进行多路分支。每个case末尾必须加break否则会“穿透”执行下一个case。char command ‘S‘; switch (command) { case ‘S‘: std::cout “Save file.” std::endl; break; case ‘O‘: std::cout “Open file.” std::endl; break; case ‘Q‘: std::cout “Quit.” std::endl; break; default: std::cout “Unknown command.” std::endl; break; }7.2 循环语句for,while,do-whilefor循环适合已知循环次数的情况。// 打印 0 到 9 for (int i 0; i 10; i) { std::cout i “ “; } std::cout std::endl;现代C范围for循环 (C11)遍历容器如数组、vector更简洁安全。int arr[] {1, 2, 3, 4, 5}; for (int elem : arr) { // 拷贝每个元素到elem std::cout elem “ “; } for (const int elem : arr) { // 通过常量引用访问避免拷贝推荐 std::cout elem “ “; }while循环适合循环次数未知由条件控制的情况。先判断条件再执行循环体。int count 0; while (count 5) { std::cout “Count is: “ count std::endl; count; }do-while循环先执行一次循环体再判断条件。适用于至少需要执行一次的场景。int userInput; do { std::cout “Enter a positive number: “; std::cin userInput; } while (userInput 0);循环控制break与continuebreak立即终止最内层的循环或switch语句。continue跳过当前循环迭代的剩余部分直接进入下一次循环的条件判断for循环会先执行i。8. 复合数据类型数组与结构体8.1 数组同一类型元素的集合数组在内存中是连续存储的通过下标索引访问索引从0开始。// 声明并初始化一个包含5个整数的数组 int scores[5] {95, 88, 72, 60, 100}; // 可以省略大小编译器会根据初始化列表推断 int primes[] {2, 3, 5, 7, 11}; // 访问元素 scores[0] 96; // 修改第一个元素 int firstScore scores[0]; // 读取第一个元素 // 遍历数组 for (int i 0; i 5; i) { std::cout scores[i] “ “; }关键限制大小固定声明后不能改变。没有边界检查访问scores[5]越界会导致未定义行为程序可能崩溃、输出乱码或表现异常。这是C/C中许多安全漏洞的根源。数组名退化当数组作为函数参数传递时它会“退化”为指向其首元素的指针丢失大小信息。因此通常需要额外传递一个大小参数。8.2 结构体struct不同类型数据的组合结构体允许你将多个相关的变量打包成一个整体。#include iostream #include string // 定义结构体类型 struct Student { int id; std::string name; double grade; }; // 注意这里的分号 int main() { // 声明结构体变量并初始化 Student stu1 {1001, “Alice”, 92.5}; // 使用成员访问运算符 ‘.‘ 访问成员 std::cout “ID: “ stu1.id “, Name: “ stu1.name std::endl; // 也可以先声明后逐个赋值 Student stu2; stu2.id 1002; stu2.name “Bob”; stu2.grade 88.0; // 结构体数组 Student classA[3] { {1001, “Alice”, 92.5}, {1002, “Bob”, 88.0}, {1003, “Charlie”, 75.5} }; for (int i 0; i 3; i) { std::cout classA[i].name “‘s grade is “ classA[i].grade std::endl; } return 0; }结构体是组织数据的强大工具它是后来C中“类”class的基础。在C中struct和class几乎一样主要区别是默认的成员访问权限struct是 publicclass是 private。9. 函数模块化与代码复用函数是将一段完成特定任务的代码封装起来通过名字调用是模块化编程的核心。9.1 函数定义与声明// 函数声明原型 - 告诉编译器函数的存在 int add(int a, int b); // 分号结尾 // 函数定义 - 实现函数的具体功能 int add(int a, int b) { int sum a b; return sum; // 返回值 } int main() { int result add(5, 3); // 函数调用 std::cout “5 3 “ result std::endl; return 0; }返回类型函数可以返回一个值如int也可以不返回任何值此时返回类型为void。参数列表定义函数接收的输入。调用时传递的“实参”会初始化函数定义中的“形参”。return语句结束函数执行并将控制权连同返回值如果有交还给调用者。在void函数中可以只写return;或不写函数体末尾隐式返回。9.2 参数传递值传递、引用传递、指针传递这是C函数中最核心、最容易混淆的概念之一。值传递函数获得实参的一个副本。在函数内修改形参不影响外部的实参。void swap_by_value(int a, int b) { int temp a; a b; b temp; // 这里a和b交换了但外部的x和y没变 } int main() { int x 10, y 20; swap_by_value(x, y); std::cout x “, “ y; // 输出10, 20 }引用传递形参是实参的别名引用。在函数内修改形参直接影响外部的实参。在参数类型后加表示引用。void swap_by_reference(int a, int b) { // a和b是引用 int temp a; a b; b temp; } int main() { int x 10, y 20; swap_by_reference(x, y); std::cout x “, “ y; // 输出20, 10 }常量引用const T如果函数不需要修改实参只是为了避免拷贝大型对象如std::string,std::vector应使用常量引用。这既保证了效率又防止了意外修改。void print_string(const std::string str) { // 好避免拷贝 std::cout str std::endl; // str[0] ‘A‘; // 错误不能通过常量引用修改 }指针传递传递实参的地址。函数通过指针间接访问和修改实参。这是C语言的主要方式在C中对于需要修改实参或传递“可选”参数指针可以为nullptr时仍会使用。void swap_by_pointer(int *a, int *b) { if (a b) { // 安全检查 int temp *a; // 解引用 *a *b; *b temp; } } int main() { int x 10, y 20; swap_by_pointer(x, y); // 传递地址 std::cout x “, “ y; // 输出20, 10 }选择指南内置小型类型int, double, char等如果函数不需要修改它们值传递最简单高效。如果需要修改使用引用传递更现代、安全或指针传递。大型对象自定义类、容器如果函数不需要修改对象使用const T常量引用。如果需要修改对象使用T非常量引用。除非有特殊理由如兼容C接口或表示可选参数否则优先使用引用而非指针。数组数组作为函数参数时实际传递的是指针。通常需要同时传递数组大小。9.3 函数重载C允许在同一作用域内定义多个同名函数只要它们的参数列表参数类型、数量或顺序不同。编译器根据调用时提供的实参来决定调用哪个版本。int max(int a, int b) { return (a b) ? a : b; } double max(double a, double b) { return (a b) ? a : b; } // 错误仅返回类型不同不能重载 // double max(int a, int b) { ... } int main() { std::cout max(5, 3) std::endl; // 调用 int 版本 std::cout max(2.5, 3.7) std::endl; // 调用 double 版本 }函数重载是C支持多态的一种形式编译时多态它让函数名可以更自然地表达操作如print(int),print(double),print(const std::string)。10. 头文件与多文件编程当项目变大时不能把所有代码都写在一个.cpp文件里。需要将声明和定义分离使用头文件.h或.hpp来共享接口。10.1 头文件的作用与编写头文件通常包含函数声明原型类、结构体、枚举的定义全局常量的声明使用extern内联函数的定义模板的定义因为模板编译机制特殊一个简单的头文件示例math_utils.h// 防止头文件被多次包含的预处理指令Include Guards #ifndef MATH_UTILS_H #define MATH_UTILS_H // 函数声明 int add(int a, int b); double add(double a, double b); const double PI 3.1415926; // 常量定义内部链接每个包含它的源文件都有一份副本 // 内联函数定义 inline int square(int x) { return x * x; } #endif // MATH_UTILS_H#ifndef如果未定义、#define、#endif这一套是传统的防止头文件被重复包含的方法。重复包含会导致重复定义错误。现代C也可以使用#pragma once大多数编译器支持但非标准。10.2 源文件与编译链接对应的源文件math_utils.cpp// 包含对应的头文件确保声明和定义一致 #include “math_utils.h” // 函数定义 int add(int a, int b) { return a b; } double add(double a, double b) { return a b; } // PI 常量已在头文件中定义主文件main.cpp#include iostream #include “math_utils.h” // 包含自定义头文件 int main() { std::cout “Int sum: “ add(5, 3) std::endl; std::cout “Double sum: “ add(2.5, 3.7) std::endl; std::cout “PI: “ PI std::endl; std::cout “Square of 4: “ square(4) std::endl; return 0; }编译链接过程编译编译器分别编译main.cpp和math_utils.cpp生成目标文件.o或.obj。在编译main.cpp时它看到了add和square的声明来自头文件知道它们存在即使不知道具体实现。链接链接器将多个目标文件以及标准库等链接在一起。它负责解析main.obj中对add函数的“未解决引用”找到其在math_utils.obj中的定义并将它们关联起来最终生成可执行文件。常见错误重复定义一个函数/全局变量在多个源文件中都有定义。解决确保全局变量只在一个源文件中定义并在头文件中用extern声明。函数定义也只在一个源文件中。未解决的外部符号链接时找不到某个函数或变量的定义。检查是否漏掉了实现.cpp文件或者编译时漏掉了某个源文件。头文件循环包含A.h 包含了 B.hB.h 又包含了 A.h。通过良好的设计和使用前向声明来避免。11. 调试基础与常见问题排查再资深的程序员也离不开调试。掌握基本的调试技能是快速定位问题的关键。11.1 使用调试器GDB/LLDB 或 IDE 调试器以GDB为例最基本命令gdb ./your_program启动GDB调试你的程序。break main或b main在main函数开头设置断点。run或r运行程序直到断点。next或n执行下一行代码不进入函数内部。step或s执行下一行代码会进入函数内部。print variable或p variable打印变量的值。backtrace或bt显示函数调用栈在程序崩溃时非常有用。continue或c继续运行直到下一个断点或程序结束。quit或q退出GDB。在VS Code或Visual Studio等IDE中调试是图形化的设置断点、查看变量、单步执行都只需点击按钮体验友好得多。强烈建议初学者从使用IDE的调试功能开始。11.2 常见编译错误与运行时错误速查错误类型典型提示/现象可能原因与排查思路编译错误error: expected ‘;‘ before ‘}‘ token语法错误最常见是漏写分号;、括号{}、()不匹配。仔细检查错误行及上一行。error: ‘cout‘ was not declared in this scope忘记包含头文件#include iostream或忘记写std::命名空间using namespace std;可解决但不推荐在头文件用。error: redefinition of ‘xxx‘重复定义。检查是否在头文件中定义了变量/函数而未加static或inline或头文件被多次包含而无保护。链接错误undefined reference tofunc()‘函数只有声明在头文件中但没有定义在.cpp文件中实现。检查是否编译了所有源文件或函数名拼写错误。运行时错误段错误 (Segmentation fault)访问了非法内存。最常见原因1. 空指针解引用int* p nullptr; *p 5;。2. 数组越界访问。3. 访问已释放的内存悬空指针。排查使用调试器查看崩溃时的调用栈和变量值。程序无输出或卡住1. 死循环检查循环条件是否永远为真。2. 输入等待cin等待用户输入而你没提供。3. 无限递归递归函数没有正确的终止条件。输出乱码或奇怪值1. 未初始化的变量局部变量不会自动初始化其值是垃圾值。2. 类型不匹配的格式化输出如用%d打印double。3. 缓冲区溢出尤其是C风格字符串。11.3 调试心法二分法与打印日志二分法定位如果问题范围较大尝试注释掉一半代码看问题是否消失。通过不断缩小范围定位问题代码块。打印日志Debugging with printf在关键位置插入输出语句打印变量的值、函数调用信息等。这是最朴素但极其有效的方法。对于多线程程序打印日志可能影响时序需谨慎。理解错误信息编译器错误信息通常很具体虽然有时冗长。从第一条错误开始看因为后面的错误可能是由第一条引发的。学会从错误信息中提取关键行号和关键词。入门篇的上半部分我们搭建了环境剖析了第一个程序学习了数据类型、变量、运算符、控制流、函数、多文件组织以及调试基础。这些是构建任何C程序的砖瓦。下半部分我们将深入更核心的概念指针与内存管理这是C的精华与难点所在、面向对象编程类与对象、封装、继承、多态、标准库容器vector, string, map等的使用。你会发现掌握了这些你就能从“写小程序”迈向“设计小项目”了。编程路上理解原理和动手实践同样重要多写、多调、多思考遇到问题别怕那正是你成长的机会。
返回列表