ARTICLE DETAIL

资讯详情

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

C语言输入输出实战:用唐诗录入程序掌握printf、scanf与文件操作

C语言输入输出实战:用唐诗录入程序掌握printf、scanf与文件操作 记得我刚开始学C语言那会儿整天对着黑乎乎的终端窗口敲printf打印出来的永远是Hello World。直到有一次课程设计老师让我们做一个带点文化气息的小项目我突发奇想既然输出函数这么灵活能不能让程序把唐诗也打印得有模有样后来我又试着让用户输入诗句、诗人、朝代程序再把这些信息整理输出一个简单的唐诗输入输出程序就这么诞生了。这个看似简单的项目其实把C语言里最核心的输入输出知识点全串起来了。格式化输出、字符输入、字符串处理、缓冲区问题、中文编码甚至文件操作全都能在输入一首唐诗、输出一首唐诗这个场景里找到落点。尤其对于正在刷翁恺C语言练习题、备战计算机二级、或者刚入门C语言基础的朋友来说把输入输出这块吃透后面学指针、学结构体、学文件操作都会顺畅很多。这篇博文我就把自己当时做这个项目的完整思路、踩过的坑、还有代码一步步演进的过程整理出来。不管你是刚看完C语言入门教程的小白还是想找点练习题巩固基础的初学者这篇都能给你一些可以直接抄作业的代码和思路。1. 项目需求拆解为什么用唐诗来讲输入输出1.1 输入输出是C语言的门面C语言程序运行起来和人打交道的方式就是输入输出。你从键盘敲进去什么程序往屏幕上打印什么这一切的背后都是标准库函数在干活。很多初学者对printf和scanf的理解停留在背格式的层面知道%d是整数、%c是字符、%s是字符串但真到了实际项目里面对复杂一点的场景就不知道怎么灵活组合了。我的看法是输入输出这个话题看起来基础实际上藏着一堆值得深挖的细节。比如scanf在处理字符输入时的缓冲区残留问题比如printf的宽度控制和左对齐右对齐比如gets函数为什么在C11标准里被直接删掉了这些知识点单独拎出来讲都很枯燥但如果把它们放在录入一首唐诗并格式化输出这个具体任务里每一个细节都有了明确的使用场景。学了马上能用用了马上能看到效果这才是初学者最需要的学习路径。1.2 唐诗场景能覆盖哪些核心知识点选唐诗作为载体不是为了花里胡哨而是因为它天然具备几个适合练手的特点。第一唐诗是字符串。每首诗有标题、有作者、有正文正文又分好几行这正好对应C语言里的字符数组和字符串处理。你要是只打印Hello World根本接触不到多维字符数组、字符串拷贝、字符串比较这些操作。唐诗结构稍微复杂一点逼着你去思考怎么存储、怎么组织数据。第二唐诗是中文。这就牵扯到编码问题。Windows下的控制台默认GBK编码Linux终端默认UTF-8同样的代码在两边跑出来的效果可能完全不一样。这个坑如果你没踩过以后做任何涉及中文输入输出的项目都会懵。第三唐诗有格式美。绝句四句、律诗八句每句字数整齐打印的时候可以做对齐、做装饰这就用到了printf的格式控制功能。比如居中输出、加边框线、按列对齐这些都是很实用的终端界面美化技巧。除了这些还能顺带练习文件读写。把唐诗保存到文件里、从文件里读取唐诗这是输入输出的进阶玩法也正好覆盖了c语言文件读写操作代码这个热搜词指向的知识点。一个项目把这些全串起来学一遍抵得上闷头刷几十道练习题。2. 先把C语言输入输出的底层机制理清楚2.1 格式化输出printf不只是打印字符串那么简单printf大概是每个C语言程序员第一个学会的函数但你真的把它用明白了吗我见过不少初学者写printf就只会printf(hello)或者printf(%d, a)遇到稍微复杂的需求就不知道怎么处理了。printf的核心机制是格式化字符串加可变参数。第一个参数是格式控制字符串里面可以混排普通字符和格式说明符。普通字符原样输出格式说明符则告诉printf后续参数该怎么解释、怎么排版。比如printf(%s 的《%s》\n, poet, title);这里%s是字符串格式说明符第一个%s对应poet变量第二个%s对应title变量。逗号后面的参数按顺序和格式说明符一一匹配。如果参数个数和格式说明符数量对不上程序不会报错但输出结果就是垃圾值或者直接崩溃。这种错误在大型程序里极其隐蔽所以写printf的时候一定要养成习惯格式说明符数量和参数数量逐个对齐检查。格式说明符除了指定类型还能控制宽度、精度、对齐方式。%-10s表示左对齐、最小宽度10个字符%5d表示右对齐、最小宽度5个字符。初学者容易忽略这些修饰符但实际上在打印表格、对齐文本时非常好用。比如我想把唐诗打印成固定宽度的卡片样式就可以这样写printf(╔══════════════════════════════════╗\n); printf(║ %-8s %-20s ║\n, poet, title); printf(╚══════════════════════════════════╝\n);宽度控制配合边框字符就能做出来还不错的终端卡片效果。注意中文在终端里通常占两个英文字符的宽度实际对账的时候要想清楚不然对齐会歪。2.2 scanf的缓冲区机制为什么我的输入和预期不一样scanf的坑比printf多得多最大的坑就是缓冲区残留。标准输入是带缓冲的你从键盘输入的数据先进缓冲区scanf再从缓冲区里按格式读取。问题出在当scanf读取失败或者没有完全消费掉缓冲区里的内容时残留的字符会影响下一次读取。举个最常见的例子。你连续用两个scanf读整数再读字符int num; char ch; scanf(%d, num); scanf(%c, ch);第一次scanf(%d, num)读取整数后缓冲区里还留着一个换行符\n因为你输入完数字按了回车。第二次scanf(%c, ch)读到的是这个残留的换行符而不是你想要的字符。这就是初学者经常遇到我明明输入了字符程序为什么不等待的根源。解决办法有几种。最简单的就是让scanf把空白字符吃掉在格式字符串里加一个空格scanf( %c, ch);格式字符串开头的空格会跳过任意数量的空白字符空格、回车、制表符直到遇到非空白字符。这个方法我在唐诗录入程序里用了很多次确实好用但要注意只适用于%c这种本身不会跳过空白的格式。还有一个办法是用fflush(stdin)清空输入缓冲区不过这个方法在C标准里其实没有定义行为不同编译器表现不一致不推荐在需要可移植代码的场景使用。更稳妥的做法是用getchar()把残留的换行符读掉scanf(%d, num); getchar(); // 吃掉残留的换行符 scanf(%c, ch);2.3 字符输入输出函数getchar与putchar的底层逻辑getchar和putchar一次只处理一个字符看上去功能很弱但在某些场景下比scanf和printf灵活得多。比如你要逐字符处理输入、统计某个字符出现的次数、实现字符过滤用getchar就非常方便。getchar的返回值要特别注意它的类型是int而不是char。这是因为getchar不仅要返回读取的字符还要在读到文件末尾EOF时返回-1这个特殊值。char类型在有符号的编译器上是-128到127刚好能存下-1但在无符号char的平台上char类型就存不下EOF了。所以正确的写法是int c; while ((c getchar()) ! EOF) { putchar(c); }这里c必须声明为int否则在无符号char的平台上EOF的值会被截断成255永远不可能和EOF相等程序就会陷入死循环。这个知识点在教科书上经常被一笔带过但实际编码时踩一次坑就明白了。在唐诗输入输出程序里getchar可以用来实现一个按任意键继续的效果。比如查完一首诗之后提示用户按回车返回菜单用getchar读取用户按下的回车键正好还能顺带清掉缓冲区里的残留换行符。2.4 字符串输入gets被删除的原因与fgets的正确姿势gets这个函数老版本的C语言教材里经常出现作用是读取一行字符串。它的原型是char *gets(char *s)直接把键盘输入的一整行读进字符数组里问题是它完全不检查缓冲区长度。如果你输入的字符比数组容量多gets就会继续往数组后面的内存里写这是经典的缓冲区溢出漏洞。正因为如此C11标准直接把它删除了VS编译器甚至默认报错。正确的替代方案是fgets。fgets需要指定最大读取长度防止溢出char line[100]; fgets(line, sizeof(line), stdin);这句代码的意思是从标准输入读取最多sizeof(line)-1个字符或者读到换行符为止。读到的内容存进line数组末尾自动补\0。唯一的问题是fgets会把换行符也读进去如果你想去掉末尾的换行符需要手动处理line[strcspn(line, \n)] \0;strcspn函数返回第一个匹配到目标字符集的位置把那个位置替换成字符串结束符就能把换行符去掉。不懂这个写法也没关系可以换一种更笨但更好理解的方式int len strlen(line); if (line[len - 1] \n) { line[len - 1] \0; }这两种写法效果一样我推荐用strcspn因为这行代码在真实项目里非常常见读代码的时候遇到了要能看懂。3. 从零开始写唐诗录入程序三个版本的演进3.1 第一版用printf打印一首完整的唐诗做任何项目都别一上来就想做到终极形态先实现一个最简单能跑的版本再一步步加功能。我写唐诗输入输出程序的第一版就干了一件事打印《静夜思》。#include stdio.h int main() { printf(\n); printf(静夜思\n); printf(李白\n); printf(------------------------------------\n); printf(床前明月光\n); printf(疑是地上霜。\n); printf(举头望明月\n); printf(低头思故乡。\n); printf(\n); return 0; }这一版代码很简单编译运行就能看到效果。但就是这样一个简单的程序也能帮助理解几个基本概念字符串字面量的写法、转义字符\n的作用、main函数的返回值。如果连这一版都编译不过那问题多半出在开发环境上比如VS Code的C语言环境配置没弄好编译器没装或者路径没配对。为了让输出更美观可以在printf的括号里加边框字符。在Windows的控制台环境里特殊符号的支持比较有限但在Linux终端下可以用各种Unicode字符做装饰。如果目标是考试和练习输出格式不需要太花哨重点是代码本身的正确性。3.2 第二版用scanf和fgets实现唐诗信息的录入第一版的问题是程序写死了换成别的诗就得改代码重新编译。第二版的目标是让用户从键盘输入诗名、作者和诗句程序再把用户输入的内容原样输出。这就用到了scanf和fgets的组合。#include stdio.h int main() { char title[100]; char poet[50]; char line1[100]; char line2[100]; printf(请输入诗名); scanf(%s, title); printf(请输入作者); scanf(%s, poet); getchar(); // 把上一个scanf残留的换行符读掉 printf(请输入第一句); fgets(line1, sizeof(line1), stdin); line1[strcspn(line1, \n)] \0; printf(请输入第二句); fgets(line2, sizeof(line2), stdin); line2[strcspn(line2, \n)] \0; printf(\n你录入的唐诗信息如下\n); printf(诗名%s\n, title); printf(作者%s\n, poet); printf(内容%s\n, line1); printf( %s\n, line2); return 0; }这里有好几个容易出错的地方。第一scanf(%s, title)读的是不带空格的连续字符串所以诗名里千万不能带空格不然静夜思没问题但要是输入登鹳雀楼也没事可是春晓这种两个字的诗名也没事。麻烦的是一旦你想输入带空格的诗名就得多想了。不过s数组名本身已经是一个指向数组首元素的指针不需要再加。初学者经常犯的错就是在scanf里写scanf(%s, title)多此一举除了部分编译器不报错但行为未定义之外纯属画蛇添足。第二scanf(%s, poet)之后缓冲区里还有一个换行符。如果不做处理紧接着的fgets(line1, sizeof(line1), stdin)会把那个换行符当成一行读进去程序就直接跳到下一句了。所以那句getchar()必须放在scanf和fgets之间把残留的换行符吃掉。这个细节我在前面已经强调过实际写代码时真的特别容易漏。第三fgets会读取并保存换行符。如果不对tailline处理就打印输出的诗句后面会多一个空行。用strcspn(line1, \n)把换行符替换成字符串结束符输出才干净。3.3 第三版完整版唐诗录入与查询工具前两版只是热身这一版才有了一点程序的样子。我的终极版本实现了三个功能录入新诗、浏览全部诗库、按诗名查询。数据结构用数组加结构体存储方式用文件读写。这个版本放大了很多但每一块知识点都是前面内容的延伸。先定义结构体和全局数组#include stdio.h #include string.h #include stdlib.h #define MAX_POEMS 100 #define MAX_TITLE 50 #define MAX_POET 50 #define MAX_LINE 100 #define MAX_CONTENT 8 struct Poem { char title[MAX_TITLE]; char poet[MAX_POET]; char content[MAX_CONTENT][MAX_LINE]; int line_count; }; struct Poem library[MAX_POEMS]; int poem_count 0;这里content是二维字符数组每一行存储一句诗最多支持8句绝句和律诗都够了。line_count记录实际有多少句。因为数组是定长的所以在录入时要防止越界超过MAX_CONTENT的句子直接丢弃或者提示用户。录入功能的核心代码void add_poem() { if (poem_count MAX_POEMS) { printf(诗库已满无法继续录入。\n); return; } struct Poem *p library[poem_count]; printf(请输入诗名); scanf(%s, p-title); printf(请输入作者); scanf(%s, p-poet); getchar(); printf(请输入诗句每行一句空行结束\n); int n 0; while (n MAX_CONTENT) { char buffer[MAX_LINE]; fgets(buffer, sizeof(buffer), stdin); buffer[strcspn(buffer, \n)] \0; if (strlen(buffer) 0) { break; } strcpy(p-content[n], buffer); n; } p-line_count n; poem_count; printf(录入成功共录入%d句。\n, n); }这段代码涵盖了好几个字符串处理的常用函数。strcpy是字符串拷贝把buffer里的内容复制到结构体的content数组里strlen是获取字符串长度用来判断用户是否输入了空行strcspn是去除换行符。这些函数都可以在c语言库函数大全里查到但光看定义不如在项目里用一遍记得牢。查询功能的核心代码void search_poem() { char title[MAX_TITLE]; printf(请输入要查询的诗名); scanf(%s, title); int found 0; for (int i 0; i poem_count; i) { if (strcmp(library[i].title, title) 0) { printf(\n); printf(%s\n, library[i].title); printf(作者%s\n, library[i].poet); printf(------------------------------------\n); for (int j 0; j library[i].line_count; j) { printf(%s\n, library[i].content[j]); } printf(\n); found 1; break; } } if (!found) { printf(没有找到诗名为%s的唐诗。\n, title); } }注意这里用了strcmp来比较字符串不能用直接比较。因为比较的是两个字符串的首地址不是内容。两个内容相同的字符串如果存储位置不同比较的结果就是假。strcmp按字典序逐字符比较返回0表示相等这是C语言里比较字符串的标准做法。为了保存数据再加一个文件保存和读取的功能。写入文件的思路是把内存中的数据按格式序列化到文件里读取的时候再反向解析void save_to_file(const char *filename) { FILE *fp fopen(filename, w); if (fp NULL) { printf(文件打开失败。\n); return; } fprintf(fp, %d\n, poem_count); for (int i 0; i poem_count; i) { fprintf(fp, %s\n, library[i].title); fprintf(fp, %s\n, library[i].poet); fprintf(fp, %d\n, library[i].line_count); for (int j 0; j library[i].line_count; j) { fprintf(fp, %s\n, library[i].content[j]); } } fclose(fp); printf(诗库已保存到%s。\n, filename); }fopen的第二个参数w表示以写模式打开如果文件不存在就创建如果文件已存在就清空内容重新写。保存诗歌时第一步是先把诗的总数写进文件这样读取的时候就知道要循环多少次。每一首诗保存的字段依次是诗名、作者、句数、具体每一句。fclose必须调用否则缓冲区里的数据可能还没真正写入文件。读取文件的代码逻辑刚好反过来void load_from_file(const char *filename) { FILE *fp fopen(filename, r); if (fp NULL) { printf(文件不存在跳过加载。\n); return; } fscanf(fp, %d, poem_count); getchar(); // 吃掉整数后面的换行符 for (int i 0; i poem_count; i) { fgets(library[i].title, MAX_TITLE, stdin); // 这里有bug ... } fclose(fp); }这里写代码的时候要小心一个地方fgets可以指定从FILE*读取所以我上面写stdin是错的应该是fp。这也说明一个很容易犯的错fgets的第三个参数是文件流指针不一定是stdin。从文件读取字符串传的就是fp。void load_from_file(const char *filename) { FILE *fp fopen(filename, r); if (fp NULL) { printf(文件不存在跳过加载。\n); return; } fscanf(fp, %d, poem_count); fgetc(fp); // 吃掉整数后面的换行符 for (int i 0; i poem_count; i) { fgets(library[i].title, MAX_TITLE, fp); library[i].title[strcspn(library[i].title, \n)] \0; fgets(library[i].poet, MAX_POET, fp); library[i].poet[strcspn(library[i].poet, \n)] \0; fscanf(fp, %d, library[i].line_count); fgetc(fp); for (int j 0; j library[i].line_count; j) { fgets(library[i].content[j], MAX_LINE, fp); library[i].content[j][strcspn(library[i].content[j], \n)] \0; } } fclose(fp); printf(诗库加载完成共%d首诗。\n, poem_count); }注意这里fscanf(%d, poem_count)之后文件流指针停在整数后面的换行符上如果直接用fgets读取下一行读到的就是空行。所以中间加了一个fgetc(fp)把换行符吃掉。这个处理方式和前面scanf和fgets混用时的getchar是同一个道理只不过这次是从文件流读不是从标准输入读。最后写一个主菜单把这些功能串起来int main() { int choice; load_from_file(poems.txt); while (1) { printf(\n唐诗录入查询工具\n); printf(1. 录入新诗\n); printf(2. 浏览全部诗库\n); printf(3. 按诗名查询\n); printf(4. 保存到文件\n); printf(0. 退出\n); printf(请选择); scanf(%d, choice); getchar(); // 吃掉残留换行符 switch (choice) { case 1: add_poem(); break; case 2: list_all_poems(); break; case 3: search_poem(); break; case 4: save_to_file(poems.txt); break; case 0: save_to_file(poems.txt); printf(再见\n); return 0; default: printf(无效选项请重新选择。\n); break; } } return 0; }主菜单用while(1)死循环加switch分支是C语言实现命令行交互界面的最常见模式。退出选项return 0直接结束程序。每次scanf读完选择后同样用getchar()吃掉换行符避免影响后续fgets对诗名、诗句的读取。3.4 代码优化把输入输出封装成函数的好处做到这一步代码已经能跑了但我建议再做一轮重构把重复的输入处理逻辑封装成工具函数。比如读取一行字符串并自动去掉换行符这个操作在add_poem、load_from_file里反复出现完全值得抽出来void read_line(char *buffer, int size) { fgets(buffer, size, stdin); buffer[strcspn(buffer, \n)] \0; }这样在add_poem里的那一长串fgets加strcspn的写法就变成了while (n MAX_CONTENT) { char buffer[MAX_LINE]; read_line(buffer, sizeof(buffer)); if (strlen(buffer) 0) { break; } strcpy(p-content[n], buffer); n; }代码短了读起来清楚多了。另外一个可以优化的是scanf和getchar的组合也可以封装成一个通用的读整型和读字符串的工具函数方法一是真的能减少很多重复代码。封装之后主流程的代码大幅缩短维护起来也轻松很多。初学者可能觉得封装很麻烦反正代码能跑就行了。但等你写的程序到了几百行几千行到处是重复的fgets加strcspn改一个逻辑要改好几处的时候就会明白函数封装的重要性了。这个道理放之四海而皆准不只是C语言的输入输出任何编程语言都一样。4. 典型报错与实操排查记录4.1 中文乱码问题做唐诗程序第一个遇到的大概率是中文乱码。在Windows下控制台默认的代码页是936GBK源文件如果是UTF-8编码printf输出的中文字符串对不上控制台的编码就会显示成乱码。VS Code默认保存UTF-8和Windows控制台默认的GBK冲突这是最常见的乱码根源。我的解决方法是根据编译器分别处理。如果用Visual Studio可以把源文件另存为UTF-8 with BOM格式或者直接用系统默认的ANSI编码保存。如果用VS Code加MinGW这样的组合最简单的方法是在代码开头加上#include windows.h ... SetConsoleOutputCP(CP_UTF8);这行代码把控制台输出代码页切换到UTF-8配合VS Code默认的UTF-8源文件中文就能正常显示。不过在考试或者在线判题环境里别这么写因为那些平台通常在Linux下运行不需要也不支持Windows的API。在Linux环境下终端默认UTF-8源文件也用UTF-8保存基本不会乱码。如果是从Windows拷贝过来的GBK文件可以用iconv命令转码或者在VS Code里重新保存成UTF-8。一个更通用的建议是在项目一开始就统一编码规范。源文件用UTF-8控制台环境也切到UTF-8或者源文件用GBK控制台保持默认。两头不统一乱码是必然的。4.2 scanf输入不生效或者跳过输入这个问题的排查思路我已经在前面讲过就是缓冲区残留作怪。症状是程序没有等待输入直接往下跑了或者读到的内容不对。典型场景就是scanf读数字之后fgets读字符串或者连续两个scanf读字符。排查方法很简单在可疑的地方加一个getchar()或者fflush(stdin)试试看症状是否消失。如果消失了基本可以确定是缓冲区残留。不过fflush(stdin)在标准C里是未定义行为GCC可能没效果MSVC有效果所以尽量用getchar()或者格式串前面的空格来解决。调试这类问题我推荐一个工具技巧在关键读取操作前后打印标记比如printf([DEBUG] 开始读取\n);这样可以清楚地看到程序卡在哪一步、跳过了哪一步。这种打印标记的方法比单步调试在命令行小项目里更好用也更容易定位问题。4.3 数组越界与字符串截断数组越界这个问题C语言不像Java、Python那样会主动报错它属于未定义行为程序可能在越界时看起来正常也可能直接崩溃或者内存数据被悄悄破坏过了一会儿才暴露问题。在唐诗录入程序里越界的风险主要在两处。一处是用户输入的诗句长度超过MAX_LINE如果fgets指定了上限多的部分会被截断不会越界但内容不完整。另一处是诗句行数超过MAX_CONTENT如果add_poem里的循环条件没有限制n MAX_CONTENT用户输入十几句诗就会把结构体后面的内存写坏。排查数组越界的一个笨办法是在结构体末尾加一个哨兵变量循环写入后检查这个变量是否被破坏。更专业的工具是AddressSanitizer在GCC编译时加上-fsanitizeaddress参数运行程序时如果发生越界会输出详细的错误信息。这个工具我之前做嵌入式C语言项目时经常用强烈推荐大家学会。4.4 文件读写失败不报错fopen返回NULL表示文件打开失败但很多人不检查这个返回值程序运行起来没有报错数据却没有保存成功。比如保存到不存在的目录、磁盘满、权限不足这些情况fopen都会失败。不检查返回值这些问题全都静默忽略了。我的原则是涉及文件操作的函数fopen之后第一件事就是判断返回值FILE *fp fopen(poems.txt, w); if (fp NULL) { perror(文件打开失败); return; }perror会打印错误描述比如No such file or directory或者Permission denied对排查问题帮助很大。除了fopen要检查fgets返回NULL表示读取到了文件末尾fscanf返回EOF表示读取失败这些返回值在某些场景下也需要检查不能默认每次读写都成功。5. 这个项目还能怎么扩展5.1 用命令行重定向让C语言程序更灵活程序写完之后我发现一个有意思的玩法根本不用改代码直接用命令行重定向就能实现自动化输入输出。在Linux和Windows的cmd里都支持和重定向。比如我提前准备一个input.txt文件内容是一首诗的录入信息登鹳雀楼 王之涣 白日依山尽 黄河入海流。 欲穷千里目 更上一层楼。然后在终端运行./poem_app input.txt output.txt程序就会自动读取input.txt的内容作为输入把所有输出写到output.txt里。不需要手工敲键盘了这在实际测试和批处理场景下非常实用。这也是理解输入输出流这个概念的最好方式程序不关心输入来自键盘还是文件不关心输出去往屏幕还是文件对程序来说都是数据流。理解了这一点再去学shell管道、学日志重定向脑子里就会有一个清晰的图景。5.2 从静态数组到动态内存管理我这个版本的唐诗库用的静态数组最多存100首诗每首诗最多8句限制很明显。如果要做成一个真正能长期使用的工具应该改成动态内存分配。用malloc为每首诗分配内存用realloc扩展诗库容量用完了用free释放。这样就涉及c语言内存管理的标准操作。动态版本的add_poem要改成struct Poem *p (struct Poem *)malloc(sizeof(struct Poem)); if (p NULL) { printf(内存分配失败。\n); return; } // 录入数据到p指向的内存 library[poem_count] p; poem_count;malloc之后必须检查返回值NULL表示内存不足。用完之后必须配对free否则会内存泄漏。这里malloc、realloc、free三件套配上指针操作正好是C语言进阶路上绕不开的知识点。如果你准备考计算机二级C语言或者刷翁恺老师的C语言练习题把静态版本升级成动态版本是一次很好的综合练习。5.3 与其他语言对比C语言的输入输出为什么这么难搞做这个项目的时候我有一个很深的体会C语言的输入输出真的比Python、Java繁琐不少。Python里一句input()就搞定了字符串读取input()还能自动去掉换行符。Java虽然也不简单但BufferedReader封装得还算人性化。但C语言这种麻烦恰恰是它值得学的原因。因为C语言不帮你隐藏底层的细节缓冲区、字符数组、指针、内存管理全部暴露在外你被迫理解每一步到底发生了什么。等你用C语言把输入输出彻底搞明白再看Python的input、Java的Scanner会觉得一切都是那么顺理成章。反过来如果你一开始就学Python可能永远不需要理解缓冲区残留和字符串结束符这些概念。我个人的建议是入门阶段别怕C语言的繁琐耐着性子把每个坑踩一遍。踩坑的过程虽然痛苦但踩完一遍你对计算机底层的理解就会有质的飞跃。这也是为什么很多计算机专业的第一门语言选择C语言的原因——它不是最易用的却是最能让人理解计算机如何工作的。5.4 从控制台到可视化输入输出的更多形态程序写完可以在控制台跑但如果你想让唐诗的展示更美观可以尝试把输出重定向到HTML文件用浏览器展示。C语言里输出HTML其实没有想象中复杂只要printf打印的内容是带HTML标签的字符串就行。这也是输入输出概念的自然延伸程序输出的不一定是纯文本只要接收方能解析字符串内容可以是任意格式。更大的方向是把C语言程序嵌到嵌入式设备里用OLED屏幕显示唐诗。这个涉及到液晶屏驱动、字模编码存储属于嵌入式C语言的范畴。模拟一个场景来说如果你要在一个只支持点阵显示的小屏幕上输出一首五言绝句你需要把每个汉字转换成一个16x16的点阵数据再把点阵数据写到显存里。这个过程本质上信息输出的一种形式和printf输出到屏幕在抽象层面是一致的。最后再说一点我自己做这个项目时的体会如果你是从零开始学C语言我强烈建议你也找一个小项目不要太大但能覆盖输入输出、数组、字符串、控制流、函数这些基础知识点然后认真把它做完。我做这个唐诗输入输出程序的整个过程从最简单的printf打印到结构体、文件操作差不多花了一个周末的时间但这一个周末学到的东西比我看一个月教程都管用。在做完了基本功能之后我还听说有个比较有意思的扩展方向就是给程序增加随机推荐一首诗的功能用rand()函数从诗库里随机挑一首打印出来。这样每次运行程序都会看到不同的诗对朋友展示的时候效果好得多。类似的扩展方向还有很多比如按作者分类统计、按诗名字典序排序每一个都能练到不同的知识点。有些扩展可能你没有思路这也很正常可以先看看别人写的示例代码再自己动手改慢慢就有感觉了。说到底写代码就像写诗先模仿再创作最后才能写出属于自己的风格。
返回列表