C语言相关知识
太多的缺省可见性
定义C函数时,在缺省情况下函数的名字是全局可见的。可以在函数的名字前加个冗余的extern关键字。这个函数对于链接到他所在的目标文件的任何东西都是可见的。
如果想限制对这个函数的访问,就必须加个static关键字。
function apple() //在任何地方均可见
extern function pear() //在任何地方均可见
static function turnip() //在这个文件之外不可见
优先级问题
- .的优先级高于*
*p.f
表示的是对p取f偏移,作为指针,然后进行解除引用操作:*(p.f)
->操作符用于取消掉这个问题 - []高于*
int *ap[]
表示的是ap是个元素为int指针的数组:int *(a[])
- 函数()高于*
int *fp()
表示的是fp是个函数,返回int* - ==和!=高于位操作符
(val & mask != 0)
表示的是val & (mask != 0)
- ==和!=高于赋值符
c = getchar() != EOF
表示的意思是c = (getchar() != EOF)
- 算术运算高于移位运算符
msb << 4 + lsb
表示msb << (4 + lsb)
typedef int x[10]和#define x int[10]的区别
- typedef是一种彻底的“封装”类型,而#define则是宏文本替换,就仅仅只是文本替换罢了。
- 可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做。
#define peach int
unsigned peach i; //这样是可以的
typedef int banana;
unsigned banana i; //这样是不行的
- 在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证。如下所示:
#define int_ptr int *
int_ptr chalk, cheese;
经过宏扩展,第二行变为:
int * chalk, cheese;
这使得chalk和cheese成为不同的类型,chalk是一个指向int的指针,而cheese则是一个int。相反,下面的代码中:
typedef char * char_ptr;
char_ptr Bentley, Rolls_Royce;
Bentley和Rolls_Royce的类型相同,都是指向char的指针。
使字符串的比较看上去更自然
因为strcmp方法比较的时候,相等的话是返回0,比较反人类,可以参考下面宏定义的方式来优化:
#define STRCMP(a, R, b) (strcmp(a, b) R 0)
if(STRCMP(s, ==, "volatile"))
数组和指针的区别
- 定义指针的时候,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化,如下所示:
char *p = "bread";
- 初始化指针时所创建的字符串常量被定义为只读。如果试图通过指针修改这个字符串的值,程序就会出现未定义的行为。与指针相反,由字符串常量初始化的数组是可以修改的。
char a[] = "gooseberry";
strncpy(a, "black", 5);
数据段和堆(heap)
堆区域用于动态分配的存储,也就是通过malloc(内存分配)函数获得的内存,并通过指针访问。堆中的所有东西都是匿名的——不能按名字直接访问,只能通过指针间接访问。
从堆中获取内存的唯一办法就是通过调用malloc(以及同类的calloc、realloc等)库函数。
malloc和free,从堆中获得内存以及把内存返回给堆。
brk和sbrk,调整数据段的大小至一个绝对值。
calloc和malloc类似,但它在返回指针之前先把分配好的内存的内容都清空为零。calloc的意思是“分配清零后的内存”
realloc函数改变一个指针所指向的内存块的大小,既可以将其扩大,也可以把它缩小,它经常把内存拷贝到别的地方然后将指向新地址的指针返回给你。这在动态增长表的大小时很有用。
避免内存泄漏
每次当调用malloc分配内存时,注意在以后要调用相应的free来释放它。
一种简单的方法就是在可能的时候使用alloca()来分配动态内存。当离开调用alloca的函数时,它所分配的内存会被自动释放。
什么时候数组和指针相同
- extern,如
extern char a[];
,不能改写成指针的形式。 - 定义,如
char a[10];
,不能改写成指针的形式。 - 函数的参数,如
func(char a[]);
,你可以随自己喜欢,选择数组形式或者是指针形式。 - 如
c = a[i];
,你可以随自己喜欢,选择数组形式或者是指针形式。
数组和指针的规则
- “表达式中的数组名”就是指针
//假如我们声明:
int a[10], *p, i = 2;
//就可以通过以下任何一种方式来访问a[i]
p = a;
p[i];
p = a;
*(p + i);
p = a + i;
*p;
事实上,可以采用的方法更多。对数组的引用如a[i]
在编译时总是被编译器改写成*(a + i)
的形式。
编译器自动把下标值的步长调整到数组元素的大小。如果整型数的长度是4个字节,那么a[i+1]
和a[i]
在内存中的距离就是4。
2. C语言把数组下标作为指针的偏移量
- “作为函数参数的数组名”等同于指针
在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。
编译器只向函数传递数组的地址,而不是整个数组的拷贝。
很有意思的是,没有办法把数组本身传递给一个函数,因为它总是被自动转换为指向数组的指针。
在函数内部使用指针,所能进行的对数组的操作几乎跟传递原本的数组没有差别,只不过,如果想用sizeof(实参)来获取数组的长度,得到的结果是不正确的。
注意,有一种操作只能在指针里进行,而无法在数组中进行,那就是修改它的值。
数组名是不可修改的左值,他的值是不能改变的。
状态机
四要素:现态,动作,条件,次态
- 现态:状态机当前所处的状态
- 动作:当前所处状态所要执行的动作
- 条件:当前状态要转成下一个状态所要满足的条件
- 次态:当前状态的下一个状态