ARTICLE DETAIL

资讯详情

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

C语言入门学习记录:从环境搭建到经典算法实战

C语言入门学习记录:从环境搭建到经典算法实战 从一个期末考试周讲起。当时我用 VC 6.0 写第一段 C 代码#include stdio.h编译通过后弹出一个黑色窗口光标在Press any key to continue上闪我在宿舍群里打了三个字跑通了。那个瞬间过去很多年但我现在依然觉得对零基础的人来说“把环境配好、把第一段代码跑起来”带来的正反馈比背十遍语法都管用。这篇“C语言学习记录1”与其叫教程不如叫一份第一周的学习存档。我会把初次接触 C 语言时最常遇到的几个大块——环境搭建、数据类型、输入输出、控制流、数组和几个经典编程题5×5鞍点问题、九九乘法表、冒泡排序、日期计算这类——按我实际踩坑的顺序整理出来。适合刚上完第一节 C 语言课、或者准备自考但不知道从哪下手的同学参考如果你已经能熟练写for循环前面两章可以快速跳过。1. 环境准备是第一个坎1.1 别在编辑器上纠结太久打开任意一个搜索引擎输入“c语言”加“编译器”跳出来的结果十有八九是“vscode配置c语言环境”和“虚拟机(ubuntu)配置c语言环境”。我一开始也照做了结果在 vscode 的tasks.json里折腾了两个小时最后发现真正要理解的就三件事需要有一个编译器。Windows 上最省事的是 MinGW-w64把它装好gcc命令能直接跑通Linux 上只需要sudo apt install gcc装完gcc --version能看到输出就说明环境好了。编辑器不是编译器。VSCode 只是个写代码的壳它自己不会编译代码所有的“一键运行”插件本质上都是在帮你调用gcc。命令行永远是最可靠的后路。有的同学插件装好了但编译报错的时候只看红字不知道去哪找错误详情。直接打开终端在源码目录执行gcc main.c -o main编译错误会一项项列出来几百行文件也能快速定位。给新手的建议如果只是写平时作业和练习不要在插件配置上花太多时间。一个命令行 任意编辑器VSCode、Notepad、甚至系统自带的文本编辑器都足够。等学到链表、多文件工程、需要调试器断点的时候再回头研究 VSCode 的调试配置不迟。1.2 第一段代码的完整运行过程我先写一个包含头文件的最简程序#include stdio.h int main(void) { printf(hello, c language\n); return 0; }保存成hello.c然后在终端里执行gcc hello.c -o hello ./helloWindows 下最后一步执行的是hello.exe。我看到很多人在这个环节卡住总以为是代码问题实际上是路径没切对。终端里要先cd到文件所在目录或者直接在代码编辑器里打开所有文件夹后再启动终端这样终端默认就在项目目录下。提示int main(void)里那个void表示“不接受任何参数”这是符合 C11 标准的写法。老教材里经常写main()编译也能过但从第一天开始就把标准的写法养成习惯后面不会吃亏。2. 数据类型的根基比想象中重要2.1 变量定义分类的两种视角这一章是“C语言数据变量定义分类定义”这个热词查出来最细的东西。其实分两个维度就能理清第一按存储位置分。全局变量放在静态存储区程序启动时分配结束才释放局部变量放在栈上函数调用结束就没了还有用malloc手动申请的变量放在堆上需要自己free。这段很枯燥但学到指针和内存管理时必须回来翻这一页。第二按类型分。C 语言的基础类型用一张表就能说清楚类型关键字占用大小64位常见环境常用格式化占位符字符型char1 字节%c、%d短整型short2 字节%hd整型int4 字节%d长整型long8 字节%ld单精度浮点float4 字节%f双精度浮点double8 字节%lf无符号unsigned与对应类型相同%u不知道你发现没有long在 32 位系统上是 4 字节在 64 位系统上是 8 字节。这就是为什么程序别写死变量大小最好用sizeof()去计算。2.2 使用 stdio.h 和 limits.h 感知边界我完成过的第一个“小成就感”练习就是用limits.h打印各种类型的数据范围#include stdio.h #include limits.h int main(void) { printf(int max %d\n, INT_MAX); printf(int min %d\n, INT_MIN); printf(unsigned int max %u\n, UINT_MAX); printf(char max %d\n, CHAR_MAX); return 0; }运行后看到int max 2147483647我第一次意识到“整数在计算机里不是无限的”。后来学到补码才知道这些数字的来历int 是 4 字节共 32 位最高位做符号位剩下的 31 位表示数值所以最大值是2^31 - 1。这个题目看起来简单但价值很大。它把“数据类型”“字节数”“取值范围”三件事第一次绑定在一起后面理解整型溢出、理解unsigned和signed的转换规则时全靠这一层基础垫底。2.3 scanf 的输入坑热词里有“c语言学习之scanf”和“c语言怎么换行输入”说明这是学习曲线上的一个大陡坡。我第一次写int a, b; scanf(%d%d, a, b);在终端输入3 5两个数字之间用一个空格隔开程序正常拿到a3, b5。但如果我要“换行输入”3 5其实也完全没问题因为%d这个占位符会自动跳过输入流里的空白字符空格、换行、制表符。真正让新手崩溃的是混用%c和%dint age; char sex; scanf(%d, age); scanf(%c, sex);输入20并回车后sex很可能被赋成了换行符\n。原因在于第一个scanf读完20后就停在了输入流的“换行符”位置第二个scanf使用%c时它不会跳过空白字符于是直接读到了那个回车。解决方案是在%c前面加一个空格写成 %c让它消费掉残留的换行符。这类坑不自己踩一次、查一次很容易在后续的综合练习题中白白浪费一晚上。3. 控制流与经典入门题3.1 while 和 do-while 到底差在哪热词里有“c语言while和do-while区别”这是初学者特容易混淆的一组语句。看一个期末试卷里特别爱出的例子#include stdio.h int main(void) { int i 0; while (i 0) { printf(while loop\n); i--; } int j 0; do { printf(do-while loop\n); j--; } while (j 0); return 0; }输出结果是do-while loop打印了一次而while什么都没打印。区别就一句话while 先判断再执行可能一次都不跑do-while 先执行一遍再去判断至少会跑一次。这个特性用在哪最典型的是“用户输入后重新判断”的场景。比如写一个登录逻辑循环条件判断密码是否正确但无论如何都要先让用户输入一次密码这时候 do-while 就比 while 干净很多。3.2 九九乘法表的对仗输出初学循环时九九乘法表是必修课。我的第一版是两层 for 循环#include stdio.h int main(void) { for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%2d , j, i, i * j); } printf(\n); } return 0; }输出结果1*1 1 1*2 2 2*2 4 1*3 3 2*3 6 3*3 9 ...要注意几个细节内层循环为什么是j i而不是j 9如果写成j 9会输出一张完整的 9×9 方形表格虽然也不算错但就不是“下三角”的九九乘法表了。另外%2d是为了让乘积右对齐否则个位数和两位数混在一起输出会很难看。这道题练的是对循环边界的控制不夸张地说能把九九乘法表边界推明白后面的冒泡排序就不太会写成死循环。3.3 冒泡排序第一次优化冒泡排序的经典写法很多人都能默写但“优化”才是真正拉开差距的位置。原始版本#include stdio.h void bubble_sort(int arr[], int n) { for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; } } } }既然叫冒泡排序就一定要知道为什么外层循环的边界是n - 1内层循环为什么要减掉i。每轮结束后最大的数会沉到数组末尾所以下一轮就不用再去比较已经就位的部分。理论上最多需要n - 1轮就能把 n 个数排完。如果某轮里没有发生任何交换说明数组已经有序可以提前结束。优化版加入一个标志变量void bubble_sort(int arr[], int n) { for (int i 0; i n - 1; i) { int swapped 0; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped 1; } } if (swapped 0) { break; } } }对一个已经排好序的数组原始冒泡排序仍要跑完所有轮次优化版只跑一轮就能退出时间复杂度从固定的 O(n²) 变成了“最好情况 O(n)”。这个优化点笔试和面试经常考值得记下来。4. 数组、指针与经典案例分析4.1 用处非常大的数组初始化分类C 语言数组初始化有几种常见的写法int a[5] {1, 2, 3, 4, 5}; // 全部初始化 int b[5] {1, 2}; // 其余自动补 0 int c[5] {0}; // 全部置 0 int d[] {1, 2, 3, 4}; // 由编译器确定大小为 4初学者最容易犯的错是认为int b[5] {1, 2}之后b[2]的值是“随机值”或“垃圾值”。实际上C 标准规定对数组进行了部分初始化剩余元素会被自动初始化为 0。这个知识点在一些老旧教材里写得不够清楚导致很多人莫名其妙地“算出正确结果”但完全说不清楚为什么。如果想要构造一个值特别大的数组就不要用{0}再逐个赋值直接用循环赋值就好。数组的索引从 0 开始这是另一个高频翻车点int a[5]的合法下标是 0 到 4这 5 个元素就是a[0]、a[1]、a[2]、a[3]、a[4]。访问a[5]属于越界C 语言不会主动提醒你它只会“悄悄”越界可能读到别的变量的值也可能直接让程序崩溃。这就是 C 语言对新手最不友好的地方之一。4.2 5×5 鞍点问题的完整解法热词里出现了“使用stdio.h和limits.h用c语言解决计算5*5鞍点问题”这是一道身边的同学大面积求助过的题。鞍点的定义是在 5×5 的二维数组中某个元素既是它所在行的最大值又是它所在列的最小值整个矩阵最多只有一个鞍点也可能一个都没有。解题思路分三步先找到每一行的最大值及其所在的列。检查这个最大值是不是它所在列的最小值。是则输出鞍点的值和位置不是则继续检查下一行。参考代码#include stdio.h #define ROW 5 #define COL 5 int main(void) { int a[ROW][COL]; int i, j; for (i 0; i ROW; i) { for (j 0; j COL; j) { scanf(%d, a[i][j]); } } int found 0; for (i 0; i ROW; i) { int max_val a[i][0]; int max_col 0; for (j 1; j COL; j) { if (a[i][j] max_val) { max_val a[i][j]; max_col j; } } int is_min 1; for (int k 0; k ROW; k) { if (a[k][max_col] max_val) { is_min 0; break; } } if (is_min) { printf(鞍点: a[%d][%d] %d\n, i, max_col, max_val); found 1; break; } } if (!found) { printf(不存在鞍点\n); } return 0; }这道题的核心难点不是“怎么找最大值”和“怎么找最小值”而是你得先意识到“行的最大值”和“列的最小值”必须交叉验证到同一个元素上。很多同学第一反应是分别生成行最大值数组和列最小值数组再去找两组数组的重叠项那样也能做但内存占用和代码复杂度都会高一些。上面的单轮扫描法是效率最高的写法一次遍历行一次遍历列O(n²) 的时间完成。注意如果矩阵里出现并列最大值取第一个即可只要逻辑统一结果不受影响。4.3 日期计算输入年月日输出是该年的第几天热词里有一道题“输入一个日期的年、月、日计算并输出这天是该年的第几天”。看起来简单但至少有三种解法查到的资料里提到两种优化方法第一种用数组存每月的天数#include stdio.h int main(void) { int year, month, day; scanf(%d%d%d, year, month, day); int days[12] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int total 0; for (int i 0; i month - 1; i) { total days[i]; } total day; if ((year % 4 0 year % 100 ! 0) || (year % 400 0)) { if (month 2) { total; } } printf(%d\n, total); return 0; }第二种方法是数组下标用“月份”直接索引把数组长度扩展到 13让days[1]就表示 1 月的天数这样代码读起来更直观但多浪费一个元素。这个题目真正容易错的地方是闰年判断。闰年的完整规则是能被 4 整除但不能被 100 整除或者能被 400 整除。只写year % 4 0会在 1900 年这种平年上误判。绝大多数入门题的测试数据都未必能查到这个边界但评测系统一旦包含就会直接扣分。这种边界条件意识从第一道题就要开始培养后面学limits.h和整型溢出时才不会头大。4.4 字符串逆序的两种写法热词里还有“字符串逆序c语言pta”PTA 是很多学校用来做 C 语言作业的在线评测平台题目要求输入一个字符串然后逆序输出。写法一原地逆序。用两个下标一个从头走一个从尾走交换字符直到中间相遇。#include stdio.h #include string.h int main(void) { char s[100]; gets(s); // 严谨一点应该用 fgets, 下文会说 int left 0; int right strlen(s) - 1; while (left right) { char tmp s[left]; s[left] s[right]; s[right] tmp; left; right--; } printf(%s\n, s); return 0; }写法二直接反向遍历输出不修改原字符串#include stdio.h #include string.h int main(void) { char s[100]; fgets(s, sizeof(s), stdin); int len strlen(s); if (len 0 s[len - 1] \n) { s[len - 1] \0; len--; } for (int i len - 1; i 0; i--) { printf(%c, s[i]); } printf(\n); return 0; }强烈建议新手直接学第二种写法原因有二第一不会破坏原始数据第二能顺便避开gets函数的安全问题。gets在 C11 标准里已经被移除因为它在读取时不检查缓冲区长度很容易缓冲区溢出。用fgets(s, sizeof(s), stdin)会安全很多但要注意它会把换行符也读进来需要手动去掉否则逆序输出的开头会多一个空行。字符串在 C 语言里没有“原生类型”它以字符数组和\0结尾来模拟。这个设计在今天看起来有点原始但理解了它再看任何一门现代语言的字符串底层实现都会觉得轻飘飘的。5. 常见错误与避坑经验5.1 新人编译错误速查把我和周围人第一周遇到的编译错误汇总成一张表每一行都是“差一点砸电脑”级别的痛错误提示见过整流原因处理方式undefined reference tomain没有 main 函数或文件名写错检查是否拼成了 mianexpected ; before }上一条语句少了分号在报错行往前找补分号scanf was not declared头文件漏写加上#include stdio.hstray \243 in program中文输入法打了中文符号切换英文输入法重打一遍variable or field xxx declared void把 void 放到了变量声明里检查函数声明语法真正要养成的习惯是编译报错后先看第一行错误信息不要看下面几十行的瀑布。很多时候后面的一百个错误只是第一个错误的连锁反应。我在第一次写冒泡排序时少打了一个右括号编译器给出一百多行错误我慌了半小时最后发现只是多了个左括号。从那以后拿到了报错信息我第一件事是检查括号和分号。5.2 从第一周开始就应该建立的三条习惯第一条变量先初始化再使用。C 语言不会帮你把局部变量默认置零未初始化的局部变量是一个随机值而这个随机值往往导致程序在“你的电脑上跑对了在评测系统上跑错了”的灵异现场。第二条不要用a b比较浮点数。由于浮点数在计算机内部是二进制近似表示0.1 0.2并不精确等于0.3。入门阶段先记住这个结论等到学float和double的精度问题时再回头消化。第三条把每一道错题都改成“独立可运行的小程序再测试”。不要直接在原有几百行作业里堆测试代码。我习惯把需要验证的算法单独复制到test.c用最少的输入数据跑一遍确认核心逻辑对了再合并回原项目。这个过程很土但排查效率极高。5.3 下一阶段的学习清单这份“记录1”写到这里对应的能力范围刚好覆盖了大多数学校的前半学期内容。之后要展开的方向包括指针理解指针就是“保存地址的变量”*p是“通过地址访问那个变量”。不要背概念去做一道“用指针交换两个数”的题。文件操作fopen、fscanf、fprintf、fclose先用文本文件做实验再碰二进制文件。结构体与链表这是“C语言课程设计”类项目的骨架网吧计费管理一类的小系统往往从这里开始。调试工具学会在命令行用gdb或者在 VSCode 里配置断点比单纯printf打桩高效得多。内存管理malloc和free的配对使用配合valgrind检查内存泄漏这几乎是职业程序员和业余爱好者的一个分水岭。我给所有正在学 C 语言的人一句实话这门语言最好的老师不是哪本教材、哪个网课而是你自己写的那些报错程序。每一次“为什么这里崩了”的追问都会把 C 语言的内存模型、变量生命周期、类型转换这些底层逻辑刻进脑子里。往后学任何语言遇到的坑你都能在大脑里快速检索出答案。拿我自己的体会来说最开始觉得 C 语言到处都是陷阱后来才明白恰恰是这些“陷阱”把计算机的工作原理摊开了放在你面前。别的语言帮你把细节藏起来C 语言选择把细节全部交给你。这既是它的门槛也是它能成为整个计算机软件行业地基的真正原因。继续往前翻页吧下一个章节的目标我已经写在编译器的报错信息里了。
返回列表