网易公开课“Programming Paradigms” 笔记



      网易公开课中提供的“Programming Paradigms”由杰弗瑞·凯恩(Jerry Cain)讲授,共27集,中英文字幕。Jerry 还是facebook公司的engineer。
      从序号看,这些属于课程CS107。在Stanford官网上可以看到CS107是该校编程系列课程之一。从时间上看,这些教学视频至少是2011年之前录的。虽然时间已经过去几年,但其中讲述的编程方法与思想是能跨越时间的,仍有重要学习价值。这不是一门编程入门课,需要学习者对C/C++、汇编、计算机原理和体系结构等有所了解。使用的是32位Unix/linux操作系统,而不是windows,大多数情况下影响不大。
       这个课程在教学方式上最大的特点是图画、代码和讲解三者结合的非常好。Jerry一边写代码片段一边画图一边讲解,把各种数据、操作等在内存中的示意图呈现在学生面前,可以使人更加直观地了解编程和程序。讲解中他仅用一支粉笔在不停地写、画,全程很少用到PPT。
       因为笔者曾做过两年老师,知道上课时在黑板上板书的感受,不得不说Jerry把Stranford教室中的分块多层活动黑板用得淋漓尽致,往往是两、三块黑板一起用,一块板上写代码,另一块板则是画内存示意图,有时候还要再用一块板来写汇编代码。对听课者来说是一目了然,互不干涉。这种授课效果,是PPT软件所不容易实现的。



       Lecture 01。介绍整个课程的主要内容,本课将涉及多种编程语言和方法,包括C、Assembly、C++、Concurrent Programming、Scheme和Python等。
       Lecture 02。以C/C++中常用的几种数据类型为例,讲解处理器层面的内存操作,分别介绍了int、char、float、double、long、short的宽度、取值范围和内存表示,重点介绍了浮点数表示法(1个符号位,8个幂指数位,21个小数位),(-1)^s1.xxxx^(exp-127)。最后通过对一个整型数(int i = 37;)进行浮点数强制类型转换(float f = *(float *)&i;)的例子,详细演示了浮点数在内存中不同于整型数的位模式。
       Lecture 03~04。Lecture03结合指针、强制类型转换、地址引用,用*(char *)&的形式,对其他数据类型以及结构(struct { })进行一些出乎意料的更具技巧性的操作。介绍了数组越界访问的问题,指针的算术操作与数组操作的对应关系。在lecture 03的结尾,用指针写了一个交换整型数值的函数viod swap(int *ip1, int *ip2)。lecture04在这一函数的基础上,继续写了泛型版本的swap()、顺序查找函数lsearch()。实现泛型的关键是viod*、指针的算术运算、元素边界的计算、内存复制等技巧,这里充分显示了C的灵活性。
      Lecture 05~06。Lecture05继续完善lserch()函数,使它能更好地支持泛型。这里引出了形如int(*StrCmp)(void * elem1, void* elem2)的函数指针,实现后进一步改善,以支持泛型。在以上3节课中,Jerry 介绍了诞生于1970年代的C支持现代编程技术中的泛型概念的方法,为C语言初学者踏上更高的阶梯打下了重要基础。本课后半部分,讲解Stack结构的实现,涉及局部变量生存期、从堆里分配内存的malloc()函数、用于测试的assert()宏等。由于C没有“类”(class)的概念,但可以用typedef struct {} 来模拟。
       Lecture 06。继续实现Stack的函数,用realloc()重新分配、free()释放内存,还实现了Stack的泛型版。内存边界的手工计算是关键。C借助指针和强制类型转换,显得很强大;但权力越大责任越大,C必须小心处理内存分配和释放,指针边界检查。
       Lecture 07。测试上节课实现的Stack。因为之前的实现,不能很好地处理带有指针成员的结构,再次对Stack进行升级。首先是在Stack结构中增加一个新域:void (*freefn)(void*,void*),接着升级相关函数,还增加一个StringFree()函数,这里讲到*(char **)的用法。接下来用纯C实现C++ STL 库中的一个函数:void rotate(void *front, void *middle, void *end)。讲了memcpy()与memmove()的区别。介绍了qsort(),建议使用man命令查询相关函数的说明。介绍了malloc()、realloc()和free()三个函数的实现原理,Jerry在这里画了一张内存大图,简单地介绍了堆段(heap segment)、栈段(stack segment)的位置、作用,以及操作系统中堆管理器是如何动态管理内存空间。
       Lecture 08。更加深入地讲解内存管理。介绍了在对动态分配的内存,静态数组做free()时,容易出现的错误。介绍了操作系统对内存空闲列表的管理。用一张图描述C程序的变量、函数调用,在内存中的大致过程。



       Lecture 09。从这节课开始引入汇编语言,按照Jerry的话说,这是专门为CS107设计的汇编语言。本课用汇编代码模拟C/C++代码,包括赋值、循环、操作码(op code)编码、结构等。
       Lecture 10。从汇编语言角度,理解C语言程序在内存中的活动记录,怎么调用内部函数、如何处理参数,如何返回等。实例是求解斐波那契数列的函数。
       Lecture 11。汇编语言翻译一段调整swap交换整型数的代码。在调用无参函数时,如void foo(), 在stack中首先压入4个字节返回地址。在这个返回地址之后是函数入口,用汇编语言可以表示为:CALL 。接着调整指向stack指针,预留出存储局部变量所需的空间,按代码中的声明顺序,压入函数中的局部变量。这里需要注意的是,对于带参数的函数,如void swap(int *ap, int *bp), 在调用时,首先要按照参数逆序,在stack中压入参数,然后再压入返回地址,再接着压入函数内的局部变量(如果有的话)。
       接下来先讲了C与C++的差异。从底层实现看,C与C++中的类和结构体在内存中差异并不太大,差别仅是类和结构体的默认修饰符,一个是private,一个是public。接着讲解一个C++的类在内存中的存储实际情况。需要注意的是,C++编译器在将C++代码转成汇编代码的过程中,会做一些隐含的解释工作,如,对this指针的处理。从汇编代码角度看,C++类中的方法(函数)的第一个参数,是一个隐含的this指针。这使得从形式上看,C++代码转汇编代码比C代码转汇编代码,显得稍微隐晦一些。这里还有同学提问,C++类的方法定义中使用static修饰符的问题(Jerry说,它在某些时候确实有用,但很少使用,因为它影响类的继承特性)。随后解释了编译和链接的原理。
      Lecture 12。复习宏定义define指令等预处理命令、介绍了assert语句。用宏写代替大量相似的重复代码,利用预处理器。这里要注意的是,使用宏的缺点在于预处理阶段并不做类型检查,所以比较容易出错。这里讲了assert宏,一个很奇怪的东西出现了:(void)0。它的意思是“空”!Jerry说,编译器不会为它生成汇编代码。最后介绍了代码的编译、链接过程。
      Lecture 13。以一个C文件为例,逐个注释掉包含头文件的语句,在编译、链接过程可能会出现的警告、失败。这里重要的是原因分析。gcc链接时不检查实参个数。C语言程序可以容易通过编译,而C++则不是。C++中的函数重载实现原理,在于相同函数的名称标记带上不同参数。程序崩溃时出现seg fault原因是对NULL指针解引用。出现总线错误,是因为编译器假设除short 和char类型外,其他类型在内存中都是4对齐的,硬件不允许int类型存储在奇地址开始的地方。最后介绍了缓冲区溢出的两个例子。
      Lecture 14。两个相邻函数共用相同内存布局(是一种错误,但有被利用的可能)。printf()的函数原型:int printf(const char *control, ...) 。这种函数原型可以用来解释为什么stack按照逆序(参数从右到左)压入,这样更利用编译器的实现。接着讲了进程切换,让两个程序看起来同时运行的原理。线程使用的场景(对现实世界的建模)。
      Lecture 15 ~18。介绍多线程。这一部分的内容明显难度加大,而且课程内容比较多。学生们有很多提问。以航班售票为例介绍多线程编程。多线程编程中对全局共享变量的访问问题。介绍信号量机制,并提出了原子操作和临界区域念。看到这里自己才知道过去在操作系统相关课程里看到信号量、临界区等概念的用处。
      Lecture 19~23。介绍scheme 语言。    
      Lecture 24 ~26。介绍python 语言。
      Lecture 27课,Jerry邀请他的facebook同事——Sasha Rush,介绍Haskell语言。
      另,Lecture 22~27 这部分网上有笔记,可查询参考。

你可能感兴趣的:(Programming,Paradigm,笔记)