C程序设计语言
控制流
控制流和逗号运算符
逗号运算符“,”是C语言中优先级最低的运算符,在for循环中经常用到,可以同时处理两个循环控制变量。被逗号分隔的一对表达式回从左到右求值,右边的为最终结果。
例子:
void reverse(char s[]){
int c, i, j;
for(i = 0, j = strlen(s) - 1; i < j; i++, j--){
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
或者,元素交换看为单步操作,保证**从左向右的操作顺序**。
for(i = 0, j = strlen(s) - 1; i < j; i++, j--){
c = s[i], s[i] = s[j], s[j] = c;
}
Goto and labels
- 用于跳出多重循环,终止程序在某些深层嵌套的结构的处理过程。
for (...)
for(...){
....
if(disaster)
goto error;
}
error;
#处理报错
label的命名规则和变量相同, 后面跟一个;
另外的例子:
for(..)
for(...)
if(....)
goto found;
...
found:
.....
# 改写成没有goto的代码形式,所有goto都可以被改写,尽可能少出现goto语句
found = 0
for(....)
for(....;..&!found;...)
if(.....)
found = 1;
if(found)
....
else
....
程序结构
外部变量
c语言程序可以看为由一系列外部对象构成,这些外部对象可能是变量或函数。因为外部变量在全局可以访问,这就为函数的数据交换提供了一种可以替代函数参数与返回值的方式。使用外部变量比使用一个很长的参数表更加方便有效。因此,如果两个函数不互相调用,但必须共享某些数据,这时最方便的是将共享数据定义为外部变量。
如果在外部变量定义之前使用该变量,或者外部变量的定义和变量的使用不在同一个源文件中,那么久需要强制使用关键字extern。
讲外部变量的定义和声明区分很重要,外部变量的声明用于说明变量的属性,而定义在除此之外还会为它分配存储空间。如果将以下语句放在函数外部:
int sp;
double val[MAXVAL]
那么将会定义外部变量val和sp,并且为他们分配存储单元,同时作为源文件其余部分对于这两个变量的声明。而下面两行语句:
extern int sp;
extern double val[];
为源文件的其余部分声明这两个变量的类型,但是不建立变量和分配存储空间。
Notation:在一个源程序的所有源文件中,一个外部变量只能在某个文件定义一次,而其他文件可以通过extern声明来访问它(定义外部变量的源文件也可以包含对外部变量的extern声明)。在外部变量的定义中必须指定数组的长度,而extern声明则不必要。
外部变量的初始化只能出现在定义中。
例如文件1使用的变量sp定义并初始化在文件2中,需要通过以下方式绑定:
在file1.c中
extern int sp;
void function1() {...}
double function2(){...}
在file2.c中
int sp = 0;
file1的extern声明需要出现在函数外部,文件前面,适用于所有源文件中的所有函数。
静态变量
用static声明限定外部变量与函数,可以讲其后声明的对象的作用域限定为被编译源文件的剩余部分。通过static限定外部对象,可以达到隐藏外部对象的目的。
static int butp = 0;
static char buf[BUFSIZE];
其他函数就不能访问这两个变量,这两个变量名也不会和同一程序的其他文件中的相同的名字冲突。
外部的static声明通常多用于变量,也可用于声明函数。如果把函数声明为static类型,则函数名除了对函数名声明的该文件可见外,其他文件都无法访问。
static也可以用于在函数内部声明变量。如同自动变量一样,是某个函数的自动变量,但是不管函数是否被调用,它都一直存在,不随着函数的调用和返回而存在和消失。
寄存器变量
register声明告诉编译器,它所声明的变量使用频率很高,将其放在寄存器中,使程序的执行速度更快。
register int x;
f(register unsigned m, register long n){
register int i;
....
}
register变量只适用于自动变量以及函数的形式参数。
实际上,底层硬件会对寄存器变量的使用存在一定限制,过量的声明会自动被编译器忽略。寄存器变量的地址是不可以被访问的。
程序块结构
C语言在函数中是不可以定义函数的,但是在函数中可以用程序块结构的形式定义变量。每次进入程序块时,在程序块中被声明以及初始化的自动变量将被初始化。静态变量只在第一次进入时初始化一次。自动变量和形式参数可以隐藏同名的外部变量和参数。
初始化总结
在不进行显式的初始化时,所有外部变量和静态变量的初始值将被初始化为0,而自动变量和寄存器变量却没有定义(脏数据)。
对于外部变量和静态变量,初始化值需要是常量,且只能初始化一次(概念上是在程序开始之前进行初始化)。而自动变量在每次进入函数或程序块时被初始化,他们的初始化表达式可以不是常量表达式,表达式中可以包括任意被定义的包括函数调用。
{ int high = n-1;
}
数组初始化 char pattern[] = “okay”; 等同于 char pattern[] = {‘o’,‘k’,‘a’,‘y’,‘\0’};
头文件相关内容(预处理器,宏替换,条件包含)
使用#include “filename” 和 #include <filename>的行将被文件名指定的内容替换。源文件开头通常会有多个include指令,或者从头文件中访问这些库。引号表示从源文件目录下面查找。
宏替换
#define 名字 替换文本
也可以吧一个较长的宏定义分为若干行,需要在待续的行末尾加一个反斜杠\,宏定义也可以使用之前定义的宏定义。替换只对记号进行,对在引号中的字符串不起作用。例如:
#define YES 1199 不会替换printf(“YES”)或YESman。
宏定义可以带参数,这样可以对不同的宏调用不同的替换文本。
#define max(A, B) ((A) > (B) ? (A) : (B))
使用宏max很像函数调用,但宏调用直接将文本插入到代码中。形式参数AB的每次出现将被替换成对应的实际参数。
x = max(q+p, r+s);
#将被替换为
x = ((q+p) > (r+s) ? (q+p) : (r+s));
它对于所有数据类型的参数处理都是一致的,则可以将宏定义用于任何数据类型,因此就不需要针对不同数据定义不同的函数。但是与普通函数调用不同的是,普通的函数调用的时候会产生栈帧(stack frame),而带参数的宏则不会,只是简单的替换,相当于内联函数,不会导致函数调用产生的额外开销,因此在实现比较小的功能的时候使用宏比较合适。
条件包含
使用条件语句对预处理本身进行控制,这种条件语句的值是在预处理执行的过程中进行计算。
#if语句是对其中的常量整型表达式进行求值(不能包括sizeof,类型转换运算符或enum常量),若表达式的值不等于0,则包含其后的各行,知道遇到#endif、#elif、#else为止。在#if语句中可以使用defined(名字),该表达式的值遵循规则:名字已经被定义时,值为1,否则为0。
#if !defined(HDR)
#define HDR
/*hdr.h*/
#endif
=========================================================
/*可以用#ifndef和#ifdef改写*/
#ifndef(HDR)
#define HDR
/*hdr.h*/
#endif
上述代码可以确保hdr.h不被重复包含。
也可以用这种特性来测试不同版本的代码。
#if SYSTEM == A
#define HDR "A.h"
#elif SYSTEM == B
#define HDR "B.h"
#else
#define HDR "default.h"
#endif
#include HDR