现在几乎所有理工科的学生都要学习 C 语言编程,有不少同掌握感觉C语言非常难基础知识。下面我将从我一个人的学习心得出发,总结在 C 语言学习的过程中,如何才能少走些弯路。
第一需要说一点,要想学好 C 语言,最好的方法是:上机!上机!上机!要紧的事说三遍,离开编程实践,是没方法学好一门语言的。像所有工具一样,最高效学习方法就是多用。上机多了,对编程的感觉自然而然会上来。
不管你的期末考试的形式是哪种,就算就对你说,考试试题就出复习材料中的原题,也千万不要试图去背答案。背答案永远是最差的解决方案。常言道授人以鱼不如授人以渔,掌握一种解决问题的办法,掌握一门编程语言,对所有理工科的学生来讲,都是一种在日后未必什么时间就可以用到的技能。
下面进入正题。这里我不喜欢根据教程的顺序解说,学一门语言要融汇贯通,要站在一个更高的视角反观正在学习的内容。
先说基本的程序结构,只有涉及到控制流(分支、循环等)和函数调用时,才有程序结构这一定义。
不要根据 ”解决这个问题我需要用什么语法“ 来想问题,语法或者代码本身永远是最后最后才考虑的问题。我带过一个学生,我带他做题时,我都会先问题他:这个问题有思路吗?结果他上来就说:用 for 对吗?这说明他的思维没上高度,眼光只盯着语法来想问题。
最好的思维方法时,先想很大致的思路,这个思路是用自然语言描述的,不是用具体的编程语言描述的。比如,假如有一个问题,叫你求 a 和 b 的最大公约数。有一点数论背景的人都会想到,用辗转相除法(广义欧几里德除法),算 a mod b,余数计为 q,则有 = ,继续算 b mod q,一环一环算下去,当整除时,除数就是最大公约数。上面这个就是我所谓的 “思路”,不涉及具体的编码。先有思路,再转换为编程语言的具体代码,如此一来思路明确,二来不容易出错。依据这个思路,比较容易写成代码:
while {
int q = a % b;
a = b;
b = q;
}
再说说我的另一个看法:语言的细节是通过编程尝试学习的。譬如,printf 语句中,格式字符串中假如有 %s, %15s, %-15s, %15.5s, %.5s, %-15.5s,分别的意思是?死记硬背一定已经背晕了,但假如你写出如此的一个程序:
main {
char *str = "1234567890abcdefg";
printf;
printf;
printf;
printf;
printf;
printf;
return 0;
}
运行一下,比较容易就能得到你想要的答案。而且,这与人的记忆方法有关,你如此试过之后,这个结果比较容易形成你的长期记忆,无需像背书一样地来背什么负号表示左对齐,.表示精度,.前面的表示宽度等等。
又如,我上面两个程序都用了语言固有些一些约定:上面那个,把一个整数作为判断条件,涉及到 C 语言中 bool 值的表示办法:非 0 表示真,0 表示假。下面那个,函数返回值种类默觉得 int。这类也都可以通过编程实践试出来,无需死记硬背。
又如,for 表示一个死循环,而 while 会致使编译错误,也就是说 for 语句的循环判断条件可以省略,while 语句不可以省,要想用 while 语句架构死循环,需要写成 while 。
还有,你能否一眼就看出来 for; 跟 for 有什么区别?对,差了一个分号。没足够多的编译实践,没无数次的由于一个分号致使编译错误,你是没办法具备如此一双慧眼的。这也就是为何编程上机这么要紧。
要学精 C 语言,要知道一些系统底层的常识。说实话,我也觉得,对于毫无编程经验的人来讲,基础知识第一门语言学 C 的确有的偏难了,由于 C 语言太过底层了。但这也未尝不是一件好事——假如把 C 语言能学精,那样将来学习任何一门别的语言都会感觉比较容易。要学好 C 语言,你需要了解,在程序运行时,进程的内存布局分为什么块:有代码区、静态数据区、栈区、堆区等等,当你了解这类后,你自然而然就会了解,为何说可以把 static 变量看作是全局变量,为何 malloc 得到的东西假如不 free 就会永远存在,为何大家的局部变量也叫自动变量,何为 “传值不传参”。知道一些体系结构的常识,你才能了解 register 关键词表示什么意思含义。知道一个 hello, world 程序从源码到 .exe 文件历程了什么变化过程,了解什么叫编译,什么叫链接,你才能了解 extern 关键词的意义。等等。其实单为了学习一门语言,要看三四本书当然有点过了,但简单地通过搜索引擎或者通过请教老师,把底层的一些常识搞懂,才能真的说你非常懂 C 语言。
“指针是 C 语言的灵魂。”
这是我的《计算机导论》老师反复跟大家强调的一句话。直到用 C 语言一年多之后,我才深刻地领会到了这句话的意思。没指针,C 语言就是渣;有了指针,C 语言就是一门万能的语言。
初学 C 语言,到了指针这里,假如对 int **ppi, int ***pppi, int [], int *ap[] 如此的声明都一点都不晕,才能说指针学得达到基础知识的水平了。一个能减轻你的大脑负担的方法是,见到指针之后,把指针变量本身画出来,把它指的内容画出来,用一个箭头指过去,如此就明确明了了。我说的 “画出来”,指的是内存,如此在操作指针时,你就了解,你操作的到底是什么东西,为何它变了,为何它没变。上机实践够多之后,这类东西就都熟知了,就能不过脑子直接写出正确的代码了。
给几个大家老师反复强调的口诀:
指针就是地址。
数组名就是指针。
指针就是数组,数组就是指针。
函数名就是指针。
把这类东西都理解透,你就能比较容易地理解,假如有如此的声明 int a[100], *pa = a;
那样,a[i], pa[i], *, * 都是同一个意思。
要理解函数指针,第一步,熟练用
浅析递归
新手遇见递归都会晕,甚至我的同学中,有些已经写了两年代码了,见到递归还是晕。其实大可不必害怕递归这个东西,用已有些思维方法,可以很容易的理解它。
有些读者可能不了解我在怎么说,递归就是一个函数调用它自己。譬如下面这段求 Fibonacci 数列的程序:
fibonacci {
if
return 1;
return fibonacci + fibonacci;
}
就这个例子大家可以看到递归的要点:
1、递归需要有终止条件。这非常不错理解,如本例中的 if 语句,假如没,那样会无限递归下去,最后程序会由于栈溢出而崩溃。
2、函数调用自己。
要理解递归,只须根据高中数学的思维想问题就能,递归等于递推公式,或者可以理解成数学总结法。给一个初始条件,但凡后面的都依据前面的能推出来。大家写程序时,仅需把初始条件给好,把递推规则写好,剩下的,“交给上帝来做”。
看一看递归的运行栈,历史状况都在运行栈里,函数一层一层调用下去,再一层一层返上来,一点一点走。可以自己把运行栈画出来,一清二楚。
递归可以简化大家的编码,但带来的函数调用的开销是非常大的,尤其是当递归层数过多时,大概由于递归太深致使运行栈崩溃。大家完全可以不需要递归来写递归程序,这个时候要大家自己维护一个栈,记录中间的状况。这个编程困难程度对新手来讲有点大,仅需理解即可。
至于对基本语法已经很了解的同学,假如想深入学习,建议学一下数据结构。这可以强化你对递归和指针的理解,自己手工达成一些基本的数据结构,可以强化 C 语言的编程能力。
推荐的数组结构书目:
《数据结构与算法剖析(C语言描述)》,机械工业出版社。
推荐的 C 语言教程:《C程序设计语言(第2版)》,机械工业出版社。
这本书是 C 语言的设计者写的,现在几乎全世界所有些 C 语言教程都以这本书为蓝本,大师的文字读起来就是一种享受,语言精练、易懂,内容全方位。