前言:最近通过MIT OCW的6.087 Practial Programming in C来复习C语言,对照了下6.087的课程设置与C Programming Language的章节结构,感觉两者的顺序差不多,但6.087为了使学习曲线更平滑,将一些比较难的内容分为两个或多个章节,并将难点放到后面,从而使学习过程更为轻松。这里是6.087的链接:http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-087-practical-programming-in-c-january-iap-2010/
Lec1 Introduction. Writing, compiling, and debugging C programs. Hello world.
C features:
• Few keywords
• Structures, unions – compound data types
• Pointers – memory, arrays
• External standard library – I/O, other facilities
• Compiles to native code
• Macro preprocessor
习惯了面向对象中类型的万能和强大,回到C中的类型不禁产生一种反蹼归真的感觉。一切皆是数,整型自不必说,字符也是数,字符串就是一串数,浮点数的内部表示应该也是用整型数字。除此之外,最重要的是万能的指针,也是数,而且可以进行指针运算。似乎可以从C看到图灵机的经典模型。
C语言提供了基本的控制指令:
与Java、C++等面向对象的语言相比,C语言中的类型比较弱,早期这些类型都是可以混合进行运算。我认为C中的类型是由编译器来控制,因此编译后类型信息就没什么作用了。而Java、C#这些运行在虚拟机中的语言,在运行时其类型信息仍保留在中间码中,可以发挥一定的作用,如反射机制。
Using GDB
Some useful commands:
• break linenumber – create breakpoint at specified line
• break file:linenumber – create breakpoint at line in file
• run – run program
• c – continue execution
• next – execute next line
• step – execute next line or step into function
• quit – quit gdb
• print expression – print current value of the specified expression
• help command – in-program help
GDB是用于调试C程序的,上面是GDB常用的命令,入门时记住这些命令就足够了。对于内存相关的调试,可以使用Valgrind。
.C文件的结构
/* Begin with comments about file contents */
Insert #include statements and preprocessor definitions
Function prototypes and variable declarations
Define main() function
{
Function body
}
Define other function
{
Function body
}
上面是一个标准C程序的结构,首先将所需的其它文件尤其是库文件包含进来,并进行宏定义。将进行预处理操作的宏放到文件的最前面是很自然的事情,将include命令放到最前面,应该是为了使编译器在开始编译程序时就获得相关库或文件的信息,放到前面也是很合理的。我觉得include放到最前面,接下来是宏定义这种方式比较好,因为include命令独立性最好,放到前面就不用理他了,如果将include放到宏与程序之间,会增大程序员需要控制的代码行数。
变量声明
• Must declare variables before use
• Variable declaration:
int n;
float phi;
• int - integer data type
• float - floating-point data type
• Many other types (more next lecture. . . )
变量在声明后系统并不会在内存中给其分配空间,我认为声明只有在编译期间才有作用,编译器内部有一个有关各个变量的表,记录变量名称和类型。这样在编译时就可以进行类型检查,从而提前发现错误。
变量初始化
• Uninitialized, variable assumes a default value
• Variables initialized via assignment operator:
n = 3;
• Can also initialize at declaration:
float phi = 1.6180339887;
• Can declare/initialize multiple variables at once:
int a, b, c = 0, d = 4;
我认为,当有操作符作用于自动变量时,自动变量才会进栈,如果不对栈中的变量显示赋值,那么这个变量的值就是分配给它的那段内存的值,一般会清0。
操作符的执行序
• Order of operations:
Operator Evaluation direction
+,- (sign) right-to-left
*,/,% left-to-right
+,- left-to-right
=,+=,-=,*=,/=,%= right-to-left
• Use parentheses to override order of evaluation
操作符的执行顺序一般都是从左至右,只有单目运算符、三目运算符和赋值运算符的运算顺序是从右至左。这种执行序的不同会对编译器的编写造成一定麻烦,但所有的二目运算符都是从左到右,其它的都是从右到左,这么理解的话倒也不是很难实现。
Fucntion prototypes
• Functions also must be declared before use
• Declaration called function prototype
• Function prototypes:
int factorial ( int ); or int factorial ( int n);
• Prototypes for many common functions in header files for
C Standard Library
• General form:
return_type function_name(arg1,arg2,...);
• Arguments: local variables, values passed from caller
• Return value: single value returned to caller when function exits
• void – signifies no return value/arguments
int rand(void);
函数在使用前也必须声明,一个完整的函数声明应该包括返回类型、函数名和参数列表,函数签名应该包括返回类型和参数列表中参数的个数,参数类表中参数的类型和顺序是否包含在函数签名中我不清楚。我认为C这种弱类型的语言不应该将类型作为函数签名的一部分,Java和C#这些强类型的语言倒是可以,但没有考证过。
C中void和NULL有很大的区别。void仅在声明时使用,内存中并没有void这么一个变量,而NULL则是一个用户定义的常量,可以作为返回值返回。
The Main() Function
• main(): entry point for C program
• Simplest version: no inputs, outputs 0 when successful, and nonzero to signal some error
int main(void);
• Two-argument form of main(): access command-line arguments
int main(int argc, char ∗∗argv);
• More on the char **argv notation later this week. . .
万事开头难,定义一个起点是困难的。main函数是C语言中的程序入口,是程序的起点。argc至少为1,因为函数名自身也是一个参数,即*argv。
Function Definitions
Function declaration
{
declare variables;
program statements;
}
• Must match prototype (if there is one)
• variable names don’t have to match
• no semicolon at end
• Curly braces define a block – region of code
• Variables declared in a block exist only in that block
• Variable declarations before any other statements
“Functions break large computing tasks into smaller ones, and enable people to build on waht others have done instead of starting over from scratch” --C Programming Language, 2nd edition.
上面是K&R中对函数的说明,函数的主要作用是封装和重用。有时候感觉C的风格很简洁,如果把函数看作返回类型的一个普通变量的话,C程序就是一堆数在进行计算。因为字符实际上也是数字,所有的变量本质上都是数字,所以其核心就是运算符对变量的计算,我认为。
函数中定义的变量是局部变量,只在函数内部使用。
More about strings
• Strings stored as character array
• Null-terminated (last character in array is ’\0’ null)
• Not written explicitly in string literals • Preprocessor macros begin with # character
#include <stdio.h>
• Special characters specified using \ (escape character):
• \\ – backslash, \’ – apostrophe, \” – quotation mark
• \b, \t, \r, \n – backspace, tab, carriage return, linefeed
• \ooo, \xhh – octal and hexadecimal ASCII character
codes, e.g. \x41 – ’A’, \060 – ’0’
C中并没有字符串这种类型,我想这是为了简洁,毕竟字符串本质上就是多个字符而已。C中的基本类型都可以方便的用一个数字表示,而字符串做不到这一点,我想这也是为什么C中不将字符串设为基本类型的一个重要原因。
Preprocessor Macros
• Preprocessor macros begin with # character
#include <stdio.h>
• #define can take arguments and be treated like a function
#define add3(x,y,z) (( x)+(y)+(z))
• parentheses ensure order of operations
• compiler performs inline replacement; not suitable for recursion
Conditional preprocessor macros
• #if , #ifdef, #ifndef, #else, # elif , #endif
conditional preprocessor macros, can control which lines are compiled
• evaluated before code itself is compiled, so conditions must be preprocessor defines or literals
• the gcc option -Dname=value sets a preprocessor define that can be used
• Used in header files to ensure declarations happen only once
• #pragma
preprocessor directive
• #error, #warning
trigger a custom compiler error/warning
• #undef msg
remove the definition of msg at compile time
C中宏的工作原理其实比较简单,尤其是使用最多的define,就是做个替换。但C中宏似乎也是容易出问题的地方,因为它是在编译器编译之前运行的,因此编译时很难发现宏的错误。我觉得在写程序时要少用宏,但因为很多源代码(如linux)中大量使用宏,因此对宏还是要有些了解,至少要读懂。
Summary
Topics covered:
• How to edit, compile, and debug C programs
• C programming fundamentals:
• comments
• preprocessor macros, including #include
• the main() function
• declaring and initializing variables, scope
• using puts() – calling a function and passing an argument
• returning from a function