作为非计算机专业的学生,如果只想考试及格以及过二级,如何学习C语言

在学校上学的时候,曾经就这个问题专门给学弟们做过讲座,并且总结成了一篇文章。为了造福后辈,帮助后辈争取早日脱单,这里把以前写的内容修改一下发表出来,毕竟C语言课通常大一开,而且大一是提升妹子好感度,与其约会,使其娇羞的关键时期。

说实话,现代人都比较功利——既然是非计算机专业的学生,那么C语言基本上是个用不着的东西。可是,它还是门必修课,那最起码不能挂科,对吧?

内容很长,最好找个时间慢慢读。

FIRST

稍微了解一下C语言的历史,你就应该知道那个东西绝不是给孩子们入门用的——一开始它的应用是Unix操作系统,尽管创造Unix操作系统最初的目的是为了玩个游戏

从这个角度讲,对于非计算机专业的学生来说,Pascal甚至Basic都比C语言靠谱。因为C语言对于他们而言过于灵活,暴露了很多让初学者迷惑的东西。而Pascal和Basic都很简单,而且Visual Basic还很好玩。

当然,虽然VB又简单又好玩,但是我们的老师成功地把VB讲成了一个比C语言还困难的语言,而且不是因为内容深,而是讲得不好。由此可见,编程语言学不好可能是门没入对

很多人喜欢看清华大学出版社的谭浩强的书。但是请注意,虽然出版社名字带着“清华大学”四个字,但是这家出版社的书都是垃圾。清华大学出版社的书不仅无法把东西讲明白,而且还误导人(谭浩强书中甚至有根本跑不通的示例程序),拉低了清华大学学术水平的下限。因此大家不要买清华出版社的书,已经买的建议考虑扔掉或者送给关系不好的人。

如果有闲功夫的话,可以拜读一下C语言之父Dennis Ritchie(dmr)的《C程序设计语言》。这本入门书比大多数国产C语言书薄,但是是一部经典。毕竟是C语言亲爹写的。

如何学习

回答“只要多努力……”、“只要多练习……”当然没有意义,所以下面写一些个人经验。

注意,本文有一个前提——“非计算机专业”。因此我假设学完C语言基本上是为了通过考试和二级。

新思维

大学每一科目都有自己的思维方式,C语言也一样。

C语言作为一门“语言”,当然有自己的语法,只不过是人类和机器约定好的、互相都能看得懂的语法。人类可以一目十行,但是对于机器而言,它只能一行一行地读、一句一句地读。

如果把每一条语句当作一件事儿,那么计算机当然是有条不紊地做事儿。写代码的时候,你当然也要一件事儿一件事儿地做。

因为我们学的是简单的单文件的C语言,所以实际上代码结构基本都是这样的:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    // 输入
    scanf("%d", &n);
    ...

    // 处理
    ...

    // 输出
    printf("%d\n", a);

    return 0;
}

注意上面有“输入”、“处理”和“输出”三个阶段。做题的时候(编写练习程序和做题类似),我们需要按照题目要求逐步完善这三个阶段的内容,或者说按照题目要求填坑。至于能不能填上,就看你积累了多少“段子”——这和多做数学题积攒解题思路是差不多的。

填坑的时候有一个技巧:如果遇到困难,尽量先把题目的语言翻译成“像C语言的东西”。例如,“判断n是否为XX数,如果是则输出YES和全部的xxx,否则输出‘NO’”。那么首先应该能够想到,“判断”是if,因此:

if (n是XX数)
{
    printf("YES\n");
    // 输出全部的xxx
    printf(全部xxx);
}
else
{
    printf("NO\n");
}

那什么是“XX数”呢?看题目其他条件,例如“一个数恰好等于因子之和”,那么if语句应该是

if (n == n的因子之和)
...

“因子”之和怎么求呢?按照以往的练习,因子是用for语句找的,所以在if之前最起码得有个for语句:

for (i=1; i<n; i++)
{
    if (i是因子)
    {
        sum=sum+i;    // 不要忘记前面 sum=0
    }
}

if (n == sum)
...

“是因子”实际上就是整除,即“n%i==0”。

假如题目还需要输出所有因子,那么我们不妨在找因子的时候把类似代码再敲一遍。因为我们是应付考试,不是参加ACM比赛,倒不需要太追求性能。

最后检查一下输入输出格式,把细节完善(见后面的“一一对应”),然后测试、调试,一切OK就可以提交了。如果考试是笔试,实在遇到困难,也可以用类似方法骗分。

先问搜索引擎

问问题是一门艺术。在错误的时间、错误的地点找错误的人问了错误的问题,那是要失败的。

学习编程会遇到很多问题,例如不会搭建开发环境、忘记某个函数的名字或参数、遇到错误,这时候不建议马上去问别人。不妨先自己尝试解决,如果解决不了,再到搜索引擎上搜索。

如果不会使用搜索引擎,可以看下图的操作说明(图片来自网络):

百度使用说明

因为大多数语言和框架(在开发项目时,从零开始是不理智的)是老外写的,对于某些问题,如果你用英文搜索,可能会更快得到结果。这时候你可能就要想办法找谷歌了(备注:必应也行,至少免翻墙)。

在技术方面的中文搜索中,百度和谷歌区别不太大。百度经常有误导人的推广,但是在技术话题上这个问题并不明显。

在英文搜素方面,百度几乎不可用。谷歌最好(直接与StackOverflow整合),如果在网络方面遇到困难,也可以考虑必应。当然,因为很多外国网站使用谷歌服务器来加速(CDN),所以点进链接后可能会卡半天不出内容。遭遇这种情况的话,你还是需要先把网络问题解决一下。

因为全世界就连那个没网(全国网站地址是10开头的IP)的北朝鲜都在使用C语言,因此搜C语言本身的东西不会遇到太大困难。

照猫画虎

不要问别人“xxx怎么写”——照着书上的代码改,或者到搜索引擎上找代码,照着他们的代码改。

学习一门编程语言,很多时候找个老师不如找一段示例代码,照着敲一遍,编译运行,观察现象,然后对代码稍作修改,编译运行,观察产生了什么变化……

需要注意的是,有些东西涉及算法(应该都是简单算法,不涉及数据结构),例如简单素数判断(素数测试有很多种)。那么你需要找到一些现成的代码,搞清楚处理过程中哪些变量是输入、哪些是结果,了解大致的思路,然后自己写一遍。这种东西务必亲自上机写一遍,哪怕没有完全理解——光靠背是不现实的。

很多算法的套路是固定的,因此建议学过函数之后,自己编程的时候把它们都扔到函数里,例如:

int is_prime(int number)
{
    int i;
    for (i=2; i<number; i++)
    {
        if (number % i == 0)
        {
            return 0;
        }
    }
    return 1;
}

在做作业的时候直接is_prime(a)判断(别忘了把函数实现拷到作业里头),既方便又避免出错。

一一对应

编程是个比较严谨的东西。初学者在写代码的时候,为了避免出错,要注意“一一对应”:

  • 解答题目的时候,你的代码是否和题目条件、输入输出格式要求对应?
  • 该写分号的地方是否写分号了?
  • 不该写分号的地方是不是没写?(例如for (...;...;...)的后面不应该写,如果故意要写,最好用个注释说明一下)
  • printf、scanf中的“%d”(或别的什么)是否和后面的变量一一对应?数量、类型都对应了吗?
    int a;
    float b;
                 第二个%d对应后面的b
                |------|
                v      v
    printf("%d %f", a, b);
            ^       ^
            |-------|
             第一个%d对应后面的a
    • 假如后面不是int类型,你前面是否还在用“%d”?
  • scanf后面有没有“&”?
  • int main()、if、while、for等的圆括号是否匹配?后面是否跟了“{”和“}”并且数量匹配?
    • 虽然if、while、for后面代码只有一句时可以省略花括号,但是强烈建议你不要偷这个懒。
  • 在使用变量的时候,前面是否已经定义了?(例如要scanf n了,前面是否有int n?按照题目要求是不是应该为int?)
  • 在使用“++”、“--”、“+=”等符号的时候,前面是否已经初始化了(说白了就是有没有让它先等于0)?
    • 初始化的值和初始化的位置是否正确?比方说有的应该放到for前头,有的应该放到for后头……
  • 数组大小是否不比题目要求的小?
    • 为了安全,建议开大点。例如题目说最大100,你就开110甚至是11000。反正贪污和浪费内存不是极大的犯罪(by 毛泽西)。
  • 头文件都写全了吗?
    • 为了安全,可以把你听说过的标准库的头文件都写上。实际上ACM比赛经常有人把能想到的头文件全都写上,结果#include写得比后面的具体实现还长。

多试验

别怕试验。多写一些小片段,看看那些代码是怎样运行的。

举个例子——for循环到底执行了多少次:

for (i=12; i<15; i++)
{
    printf("A");
}
printf("i=%d\n", i);

for (i=12; i<=15; i++)
{
    printf("B");
}
printf("i=%d\n", i);

scanf和gets的冲突:scanf和get紧挨着可能在输入数据时遇到问题,这时候需要一个小技巧……

scanf("%d\n", &n);
fgets(str, 100, stdin);   // 其实就是 gets(str) 的推荐写法
puts(str);                // 如果那个scanf里没有“\n”,gets的时候……

再举个例子(指针与一维数组):

#include <stdio.h>

int main(int argc, char *argv[])
{
    int a[]={6, 8, 10, 12, 14, 16, 18, 20, 22, 24};
    int *p = a;

    printf("%d\n", a);
    printf("%d\n", a[0]);
    printf("%d\n", &a[0]);

    printf("%d\n", p);
    printf("%d\n", p+1);
    printf("%d\n", *p);
    printf("%d\n", *(p+1));
    printf("%d\n", *p+1);

    return 0;
}

因为只是为了看个结果,所以,只要自己能把结果弄明白,怎么舒服怎么来。

当然有个重要前提——你手头得有个电脑。如果没有电脑,或者“大一不让带”,可以买个几百或一两千的Win10平板,比手机便宜,而且便于隐藏和携带,既可防导员查寝,又可带教室去开小差。

调试

经常有人问“为什么我的代码结果不对”,然后贴上一段代码。这是非常令人讨厌的行为。

为了不被人鄙视,大家应该学一些调试的技巧。

作为非专业人士,与其学习那些复杂又难用的调试工具,不如学习又简单又实用的 printf。

printf有两大作用:定位错误、查看中间值。

假如程序崩溃了,可以这样调试:

int a[10];
int i, n;
printf("准备读n\n");
scanf("%d", n);
printf("准备读a\n");
for (i=0; i<n; i++)
{
    scanf("%d", &a[i]);
}
printf("开始处理\n");

假如崩溃的时候屏幕上面只有“准备读n”而没有“准备读a”,说明中间夹着的scanf有问题;假如崩溃的时候有“准备读a”而没有“开始处理”,说明中间夹着的for有问题,这样你可以在for语句内部插入一些printf("a\n"),从而精确地找到错误的出处(例如,崩溃的时候看到10个a,说明数组开得不够大)。

如果计算结果不正确,可以这样调试(筛法求素数):

int i, num, isprime;
isprime = 1;
for (num=2; num<10; num++)
{
    for (i=2; i<num; i++)
    {
        if (num % i == 0)
        {
            printf("DEBUG: %d不是素数(╯°Д°)╯︵ ┻━┻\n", num);
            isprime = 0;
            break;
        }
    }

    printf("DEBUG: %d 是素数吗?%d\n", num, isprime);

    if (isprime)
    {
        printf("%d ", num);
    }
}

运行结果只有2和3。通过观察调试输出,发现程序认为5不是素数。按理说,如果不是素数,屏幕会输出一个掀桌子的表情。然而程序认为5不是素数,但是还没有掀桌子表情,说明问题出在isprime变量上面——isprime=1的位置不对。

当然,交作业的时候别忘了把这种输出删掉。

测试

不要把测试和调试搞混。用一句话概括它们的区别:测试是为了观察“程序结果是否正确”,而调试是为了“找到程序的错误”。

很多初学者经常不做测试,或者不做充分的测试就提交,然后遇到问题就反复改,反复提交。这样也是不好的。

Online Judge网站就是测试的一种:系统预置了一系列数据和答案,按照题目要求解题、提交,系统会自动编译和运行代码,检查程序是否能在规定时间内处理预定输入并得到正确结果。在比赛中,错误提交是要影响成绩的,因此选手必须要学会自己测试,争取一次提交就通过。

程序编译通过之后,当然要用数据检验一下结果。如果把样例输入敲进去结果都不对,那程序肯定不行。

光通过样例也是不行的。为了保证通过,你应该自行构造多个输入,观察输出是否正确。应从以下几个角度构造数据:

  1. 守规矩的计算
  2. 不守规矩的计算:如果题目没做保证,那么应假设有非法数据。假如题目没说a一定大于0,那你有没有想到a可以等于0或-1?
  3. 多种情况:假如程序有多种结果(Yes/No),你是否都测试到了?
  4. 边界条件:假如最多100个人,那么你的程序在恰好输入100个人的情况下能否正常工作?
    • 没有人或只有1个人呢?
    • 稍微提一下,假如数组有100个元素(int a[100]),访问a[100]是非法的,但是在你自己电脑上不一定报错。这是一个隐藏的错误。为了避免这种错误,可以把数组开大一些,例如200甚至10000。

除此之外,初学者常常忽略的问题有空格(一个空格、两个空格、多个空格、行末空格)、回车(例如最后一行的回车)、字母大小写和符号全半角,还有不明显的格式差别(例如题目要求用逗号分隔你却用了空格)。因为测试是全文逐字比较的,所以不要犯这种低级错误。

养成好习惯

建议大家照做,因为这样可以避免很多不必要的错误:

  • 如果你不知道全角符号,。、()和半角符号,.\()有什么区别,那么建议你写代码的时候把输入法关掉,免得出现莫名其妙的错误。
  • 不同功能(定义变量、输入、数据处理、输出)之间留点空行,也就是按功能分块,就像前面提到的那样。
  • 接上条,如果代码太长,那么最好多用一些函数,把各功能分离出来。写代码的时候一个功能一个功能地写。没有人喜欢又长又臭的代码。
  • 缩进,一定要保持代码的缩进。没有缩进的代码难以理解,而且很有可能丢花括号,从而造成不必要的麻烦。
  • 使用“黑科技”(不解释就难以理解)的时候要加注释。
  • if、for等语句后面可以不加花括号对吧?别懒,把花括号加上,免得犯这样的错误:
if (a > b)
    c=a;
    printf("%d",c);
else
    c=b;
    printf("%d",c);

如何通过二级

这是也很多人关心的问题。虽然事实上计算机二级比英语六级容易通过(对于985、211的学生来说,只要英语三级也就是高考成绩不太烂,英语四级裸奔也能过……),但是很多人还是没通过。这不是他们不刻苦或者学得不好,而是因为他们不知道如何对付一个考证级别的考试。对,和英语四六级有着本质的区别。

首先用一句话概括通过计算机三级和四级的武林秘籍:去购买高等教育出版社的、封面上带个大大的黄色字母K的官方题库(不是教材),从头到尾把题刷一遍,该背的试题背下来,如果还没通过那你肯定是没好好背。

基本内容

虽然二级过了三四级成绩才能算数,但是,之所以先介绍三四级,是因为三四级比二级好过——三四级基本上可以纯背,但二级还是要会点东西的。下面就介绍介绍二级C语言:

  • 选择题40分,大题(改错、填空、编程)60分。
  • 上机考试,先做选择题,选择题全部做完才能做大题。做选择的时候电脑屏幕是锁死的,做大题的时候才能使用编程软件。
  • 题目是从题库里随机抽的,所以不要尝试照旁边的人抄。当然,运气好的话,你会碰到和官方教材中差不多的题。撞大题的概率更高。
  • 选择题中至少有20~30分来自“二级公共语言基础”,这个“公共语言基础”大致分成两类,一类是“算法与数据结构”,需要你去理解一些东西;另一类不管它是啥,反正是纯粹要背的东西。剩下的选择题和C语言本身有关。
  • C语言课的东西够用了,关键是你对考试出题的基本法的了解程度。
  • 编程软件是钦点的Visual C++ 6.0,虽然它是盗版的,虽然它很可能无法在你自己的电脑上正常运行。
  • 二级考完只有三种状态:没通过(<60分)、合格(6089分)、优秀(90100分)。因此,如果你不打算拿优秀证,考60和考89没有任何区别。(在这一点上,英语四六级就不一样——证书上面把成绩写出来了)
  • 因为计算机二三四级证书都很水,因此,求职时如果应聘条件没有明确要求,不要指望拿这些证书给自己贴金。虽然二级可能是很多岗位的基本条件之一。
  • 最后,如果你想考三四级,一定要看清楚对应科目对二级的要求——是要求通过编程语言类的二级还是数据库语言类的二级。如果你二级报的是Office那还是别考三四级了。

选择题

如果你觉得大题把握很大,而且没有拿优秀证的打算,那么“二级公共语言基础”的东西就可以少背一点了。反之要多背一些。

“算法和数据结构”是一个不太容易懂的东西——如果学得很好,没准就可以去打ACM比赛了。既然是个只需要拿60分的考证,那么不妨把官方教材拿出来,看看他们喜欢怎样出题,把相关考点记住即可。

有关C语言本身的选择题,实在不懂也不必太较真,因为它可能没有意义:例如经典的a+=a-=a*a;a=a++;都是未定义行为——连C语言标准对这些语句的结果都没做任何保证,所以肯定没有正确答案。

大题

大题是一定要会的。如果你没有精心准备,选择题分数可能会惨不忍睹,因此大题要尽量把分数都弄到手。

为了把分数搞到手,我们要从两方面准备,一方面是正确解答题目,另一方面是正确地解答题目。

正确解答题目

大题分为改错、填空、编程。

改错就很简单了。因为改错的模式都比较固定,只要多看看官方的题目就应该清楚他们会怎样考了。

填空也一样。一般是让你填一些“单词”,或者是些简单的表达式。填空时候要多留意已经定义和未使用的变量。

其实吧,改错和填空题很多时候就是推理题——不用太清楚程序干嘛,只要保证所有变量都被用过了,该对应的地方都对应了,OK。

编程题要注意:数组、指针和字符串对于初学者来说比较难理解,对吧?不幸的是,出题人也爱考这些东西。因此,还是要看官方教材,看看他们喜欢怎样出题,并且建议你在做这些题的时候亲自上机练习一下。

正确地解答题目

两个要点:一是不犯低级错误,二是要会用Visual C++ 6.0编辑代码。

不犯低级错误,举个例子,题目提示只修改/******* FOUND *******/下一行的代码,那么你不要改其他地方的代码,也不要把这个注释本身修改或删除。此外尽量不要碰原有代码的其他部分,例如空格和缩进。

Visual C++ 6.0是一个古老的软件了(1998年发布),在现代操作系统(Windows 8/8.1/10)中会出现兼容性问题,甚至无法启动。因此,平时在自己电脑上练习的时候可以使用其他编程软件,然后考前去一趟机房(学校机房应该安装了Visual C++ 6.0),学会如何打开和保存代码(考试软件会帮你打开代码)、如何找到int main()、如何编译运行。知道这些就足够了。

会点上面三个按钮就够了

会点上面三个按钮就够了。

拓展学习

对于非计算机专业的学生来说,尽管C语言很难学,尽管C语言看起来“不实用”,但是很多编程语言的套路都是类似的,因此学完C语言有助于其他“实用”语言的学习。有些“实用”语言甚至直接向C语言致敬,例如PHP。

以下是一些当下很流行的、学起来可以“实用”的编程语言,如果有兴趣的话可以学一学。

  • HTML+CSS+JavaScript:做网页的一套语言,虽然是三个,但是实际上哪个都不需要学得太深——一方面需要什么东西可以自己去查,另一方面现成框架太多了,很多时候用不着从零开始。我个人建议不要用Dreamweaver,即使用也要用最新的Dreamweaver CC,否则容易落伍。
  • PHP:想做个实用网站吗?学吧!就是它大量致敬了C语言的语法!
    • 由于PHP学习门槛很低,因此很容易写出有大量安全漏洞的网站。做项目时要多加小心。
  • Python:Python是个小巧而又强大的脚本语言。建议有兴趣的一定要学。
    • 如果想写一个随写随用的程序,当然要找它;
    • 如果想做一个实用的网站,同样可以找它——可以学习基于Python语言的Django框架;
    • 如果想做一个带窗口对话框的程序,当然也可以找它——可以学习基于Python语言和Qt的PyQt框架。对于这种情况,建议同时学习PyQt和Qt C++。
    • Python有两种版本,一个是2.7,一个是3.6。它们两个有些差别,但是实际上不影响学习。遇到困难时到搜索引擎上搜一下就行了。
  • Visual C#:C++和C#都带个“C”,那它们当然是从C语言衍生过来的……学C语言写不出一个带窗口对话框的程序对吧,没关系,我们可以用一个好学的Visual C#,随手画个窗口出来!
  • Qt C++:同样是用来制作带窗口对话框程序的,只不过这个是C++语言。
  • Java:会写Java至少能混口饭吃,而且现在很多业务系统和Android应用都是拿Java写的。
  • Objective-C:iOS应用是拿这个语言写的(备注:现在已经换成Swift,所以不用学Objective-C了),只不过进App Store的门槛有点高。
  • MATLAB:数值计算、数值模拟必备。一定先把高等数学和线性代数学好然后再学这个……

我不推荐Pascal和Basic,前者用的人数不多,后者……VB6也是1998年的东西,在现在的系统中已经不能正常运行了,而且学新版VB不如学C#。

另外还有两个硬件(小心,玩硬件烧钱):

  • Arduino:Arduino是一个卡片大小的开发板(如果不知道开发板是啥——就是可以让你拿来写程序的电路板),编程的时候基本上就是C或C++。与电路相关的东西都是封装好的,可以直接调用函数,所以不会一开始就被一些细枝末节困扰。虽然玩51单片机(备注:51单片机通常是STC产的,其官网和牛皮癣小广告有一拼)的人更多,但是我觉得从Arduino入门会容易很多。深入之后也可以考虑玩STM32之类的单片机。
  • 树莓派:树莓派是一个卡片大小的单板电脑。虽然设计者的定位是儿童电脑,不过实际上被极客当成高级玩具了。最新的树莓派3可以跑Linux和Windows 10(物联网版)系统,对物联网感兴趣的可以玩玩。

我个人的学习建议:

  1. 尽量找外文书的中译本,原因很简单,翻译一本书需要付出大量时间精力,因此能够被拿来翻译的书基本上都是好书。因为写书很容易,因此国内很容易出烂书。外国的月亮也不圆——外国也一样。
  2. 把书粗读一遍,大致了解一下基本语法。很多语言的基本结构都差不多,只不过单词不一样罢了。
  3. 细读,看看(特别是翻译本那种)作者的思想和思路。
  4. 照着教材做个小练习。
  5. 不用强求把所有东西都记住。现在网络那么发达,甚至手机流量都不需要省着用,有问题可以直接去网上查。