译者注:这是一份C指南,目的在于指导C初学者快速开始并了解C语言,其并不能代替市面上大多数的教材。这里推荐一本非常著名且优秀的C语言教材:
The C Programming Language –ANSI C
–By Brian W.C. Kernighan & M. Ritchies
大纲:
- 第一个程序
- 计算
- 循环
- 宏常量
- 条件语句
- 指针
- 数组
- 字符数组
- I/O功能
- 函数
- 命令行参数
- 图形接口
1. A First Program
1 |
|
2 | |
3 | void main(){ |
4 | printf("\nHello world\n"); |
5 | } |
将代码保存在 hello.c 中,然后输入下面的命令进行编译:
1 | gcc hello.c |
这将产生一个可执行文件 “a.out”
一个C程序包含 函数 与 变量,函数指明了程序需要完成的任务,”main” 函数建立了所有的程序逻辑,通常我们需要保证他是简洁的(通过调用其它的函数来运行子逻辑),所有的 C 程序都有且只有一个 “main” 函数。
printf是来自于 I/O 函数库 (defined in stdio.h), 最原始的C语言并没有内置的 I/O 库,这些都是后来发展中新增的并最终并入了 ANSI C 规范中。 The K & R 教科书列出了这些新增的标准库
2. Let’s Compute
下面的程序 ,”sine.c” ,计算了从 0 到 360 度的角度的 sine 函数值
1 |
|
2 |
|
3 | |
4 | void main(){ |
5 | int angle_degree; |
6 | double angle_radian,pi,value; |
7 | |
8 | printf("\nCompute a table of the sine function\n\n"); |
9 | |
10 | // atan 是反正切函数 |
11 | pi = 4.0*atan(1.0); |
12 | printf("Value of PI = %f \n\n",pi); |
13 | printf("angle Sine \n"); |
14 | angle_degree = 0; |
15 | |
16 | while(angle_degree<=360){ |
17 | angle_radian = pi*angle_degree/180.0; |
18 | value = sin(angle); |
19 | printf("%3d %f \n",angle_degree,value); |
20 | |
21 | angle_degree = angle_degree+10; /*increament the loop index */ |
22 | } |
23 | } |
这个函数同样包含了一些定义于 math 函数库的函数
变量名的命名是随意的(部分编译器规定了最大长度,典型的是32个字符), C 使用以下标准的变量类型:
1 | int |
2 | short |
3 | long |
4 | float |
5 | double |
6 | char |
// 有点奇怪,C里面没有 byte 和 boolean 么
printf(“format”,var) 格式问题:
1 | %.nd integer n位表示,不足用0补齐 |
2 | %m.nf |
3 | %ns string(optional n=number of columns) |
4 | %c character |
5 | \n \t to introduce new line or tab |
6 | \g ring the bell(‘beep') on the terminal |
Loops
大多数现实中的小恒徐会包含一些循环结构在程序里面。针对一些连续的数据或内存,执行一些相对固定的逻辑。在C程序中,实现循环有多种方式,最常见的就是 while 循环和 loop 循环。
1 | while(expression){ //expression为true则进入循环体 |
2 | ...block of statements to execute... |
3 | } |
4 | |
5 | for(expression_1;expression_2;expression_3){ //expression_2为true则进入循环体 |
6 | ...block of statements to execute... |
7 | } |
无限循环是被允许的,通常用break关键字跳出循环,例如:
1 | angle_degree = 0; |
2 | for(;;){ |
3 | ...blocks of statements... |
4 | |
5 | angle_degree = angle_degree+10; |
6 | if(angle_degree==360)break; |
7 | } |
//TODO:空表达式是真值?
Symbolic Constants
你可以通过 编译指令 #define 定义任何类型的常量
1 |
|
2 |
|
Conditionals
1 | if(conditional_1){ |
2 | ...block of statements executed if conditional_1 is true... |
3 | }else if(conditional_2){ |
4 | ...block of statements..... |
5 | }else{ |
6 | ... |
7 | } |
8 | |
9 | switch(expression){ |
10 | case const_expression_1: |
11 | { |
12 | ... |
13 | break; |
14 | } |
15 | case const_expression_2: |
16 | { |
17 | ... |
18 | break; |
19 | } |
20 | default: |
21 | { |
22 | ...block of statements... |
23 | } |
24 | } |
注意switch语句的执行流程,是从上到下,如果某个分支没有break,会继续执行下一条分支
1 | && and |
2 | || or |
3 | ! not |
Pointers
啊哈,重头戏指针:C语言允许程序员对内存直接进行操作,这个特性给予了语言无情的灵活性和力量,但是,这也是非常巨大的障碍,初学者必须克服以便于能正确高效地运用C语言。
程序中的所有变量都保存在内存之中
1 | float x; |
2 | x = 6.5; |
这段代码为float变量x请求4个字节的内存,并把6.5写入其中。有时候我们需要了解变量在内存中存储的位置,”&”操作符提供了这个功能,将 “&” 置于变量名var前面,就代表了var的地址:
1 | float x; |
2 | x = 6.5; |
3 | &x; |
C语言同时提供了一种类型的变量,来存储地址类型,这个变量就是指针变量。
1 | float* px; |
2 | px = &x; |
“*” 的理解有两个方面,我们且看下面的两种表现形式
1 | float* x; |
2 | float *x; |
float* 表示一个float型指针(地址类型)
而 float x 则表示 *x是一个float变量,在这种意义下表示的就是该x地址所指向的内容的值,很多书本上,将地址变量比喻成门牌号,我觉得是很贴切的,给地址变量前面加个 * 就表示了门牌号所指代的房间(内容)。实际上上面两种形式下,x 的含义也是不变的,都是一个地址变量。
可以叠加,例如 “float* p”,p则为描述地址的地址。
地址类型之间也可以强转,强转之后,指针的地址虽未改变,其内容(内存角度)也为改变,但其内容所代表的变量类型却改变了。看下面这个例子:
1 | char* pc; |
2 | float* px; |
3 | float x; |
4 | |
5 | x=6.5; |
6 | px = &x; |
7 | pc = (char*) px; //this is a cast |
pc和px指向同一地址吗,但 px+1 与 pc+1 则指向不同的内存地址
Arrays
通用的声明格式如下:
1 | type name[dim]; |
Struct
Array是一组相同类型数据的集合,只是有时候我们也需要不同类型数据的集合,从设计思想上,二者也有不同,Array是方便进行批处理,而Struct则是将相关联的数据联系起来,进行同步处理,Struct优点类似类的概念。
Struct的一般结构如下:
1 | struct 结构体名{ |
2 | 结构体所包含的变量或者数组或者结构体变量 |
3 | } |
结构体也是一种数据类型,它由程序员自己定义,可以包含多个类型的数据。像 int 、float、char 等是C语言本身提供的数据类型,不能进行再拆分。我们称之为基本数据类型,而结构体则属于复杂数据类型或者构造数据类型
结构体变量
1 | struct stu{ |
2 | char *name; |
3 | int schoolNum; |
4 | int age; |
5 | float score; |
6 | }stu0; //变量可以直接放在结构体定义的最后 |
7 | |
8 | struct stu stu1,stu2; |
理论上讲,结构体的各个成员再内存中是连续存储的,和数组非常相似。但是再编译器的具体实现中,各个成员之间可能存在缝隙,这是为了内存对齐,提高寻址效率。
结构体变量使用 . 来获取单个成员
1 | int age = stu1.age; |
2 | float score = stu1.score; |
除了可以对成员进行逐一赋值,也可以在定义时整体赋值,例如:
1 | stu stu1 = {"Tom","20092510",18,90f}; |
->操作符
->操作符和 . 操作符都是对结构体内的数据进行访问。不同的是, ->操作符是一个指向结构体的指针对该结构体中的数据进行访问
1 | stu *stu; |
2 | stu.name = "YUDAN"; |
3 | |
4 | char *str = stu->name; |
C语言中的变量与Java中的变量有什么不同
Java中的变量有很多种,对于基本类型的变量而言,二者是相同的,其实它们都是一个地址,指向存储的数据
对象变量当然也是一个地址(或者说引用),只是它作为一个地址指向的是一个存储在堆中的对象。而普通的基本类型变量又分为局部变量或者对象变量,二者的作用域不同,存储的区域自然也不同,后者是线程私有的,存储在线程的栈帧中,前者则存储在对象之中,作为对象的一部分。