float *g() , (*h)();
()的优先级大于 * , 因此 *g() 为 *( g() ) ,该函数 g() 的返回类型为指向浮点数的指针 float * 。
而h是一个函数指针,返回值类型为 float。
以数组下标进行运算,实际上都是通过指针进行的。
#define NULL 0
当常数0被转换为指针使用时,这个指针绝对不能被解除引用(dereference),换言之,当我们将0赋值给一个指针变量时,绝对不能使用该指针所指向的内存中的存储内容。
无符号数无溢出
if((unsigned)a) + (unsigned)b > INT_MAX)
main函数与其他任何函数一样,若未显式声明返回类型,则默认为整型。
int a;
如果出现在所有函数体之外,则称为外部对象a的定义。a是一个外部整型变量,同时为a分配了存储空间,默认为0;
int a = 7;
在定义a的同时也为a明确指定了初始值。
extern int a; //这是一种引用,而非定义
a是一个外部整型变量,extern说明a的存储空间是在程序的其他地方分配的,只是一个外部对象的显式引用。
如果一个程序包含了 extern int a ,那么这个程序就必须在别的某个地方包括: int a; 那么这两个语句既可以在同一个源文件中,也可以位于程序的不同源文件之中。
两个具有相同名称的外部对象,实际上代表同一个对象,如在两个不同的源文件都包括了定义:int a;
则或表示程序错误(禁止外部变量重复定义),或在两个源文件中共享a的同一个实例。
static 修饰符能解决命名冲突问题:
static int a;
static 具有文件作用域,将a的作用域限制在当前源文件中,对其他源文件,a并不可见。
static同样可以修饰函数,建议:若一个函数仅被同一个源文件中的其他函数调用,我们应当声明该函数为static。
任何C函数都有一个形参列表,列表中的每个参数都是一个变量,改变量在函数的调用过程中被初始化。
任何C函数都有自己的返回值:void/其他
如果一个函数在定义域之前被调用,那么它的返回值默认为 整型
- 如果一个函数在被定义或声明之前被调用,那么他的返回值类型默认为整型。
- 在多文件中,若函数的定义和调用位于不同的文件中,则需要在调用前声明。
外部变量的定义和声明必须保持一致。
比如有两个文件,其中f2.c中定义了一个 long类型的变量n,但是在f1.c文件中却声明为int类型:
- f1.c: extern int n;
- f2.c:long n;
如下所示为ref.h文件中定义的宏:
#ifndef _REF_H
#define _REF_H
#define F(x) (x+1)
#define G (x) (x+1)
#endif
在main函数中使用:
int a = F(2);
int b = G(3);
错误原因在于G和 (x)之间多了一个空格,导致编译器将G作为一个宏,而不是G(x).
注意:
#define ABS1(a) ((a) > 0 ? (a) : -(a))
#define ABS2(a) (a > 0 ? a : -a)
#define MAX(a,b) ((a) > (b) ? (a) : (b))
如上代码片段中的两个宏定义所示,在宏定义ABS2(a)中,每个参数不用括号保护,当a=x-y的情况下,直接使用字符替换就会发生错误:
int x = 4;
int y = 6;
int z1 = ABS1(x-y);
int z2 = ABS2(x-y);
根据字符串简单替换的规则:
z1 = ABS1(x-y) = ((x-y) > 0 ? (x-y) : -(x-y)) = -2 > 0 ? -2 : 2 = 2
z2 = ABS2(x-y) = (x-y > 0 ? x-y : -x-y ) = -2 > 0 ? -2 : -4-6 = -10
所以使用括号来保护表达式,是很有必要的。
此外,还要确保宏中的参数不会出现二次变化(没有副作用),如以下代码片段:
int i = 0, arr = [2,4,6,8];
int a = MAX(arr[i++],1);
按照一般思维:i是先使用后自加,所以arr[i++]为2,MAX(2,1)结果等于2,即a等于2。
但是按照宏的简单字符串替换原则:
int a = MAX(arr[i++],1) = arr[i++] > 1 ? arr[i++] : 1 ;
当执行完 arr[i++] > 1 操作后, 还会执行 ? 后面的 arr[i++],此时i等于1,导致a的结果等于4;
最后还需注意的是 宏的嵌套,可能会产生非常庞大的表达式,始终要记住宏是简单的字符替换,而不是函数,C语言中函数可以使用递归(利用了栈的原理)来达到精简代码的目的,如以下代码:
MAX(a,MAX(b,MAX(c,d)));
将其展开后,程序将非常臃肿,不利于后期调试。
总而言之,宏是一种 以空间换时间 的一中策略,在这一点上,类似内联函数。
类型定义可以使用 typedef:
// 宏定义和typedef 一致
#define FOOTYPE struct foo
typedef struct foo FOOTYPE
// T1和T2所表示的类型有可能会不一致
#define T2 struct foo *
typedef strcut foo * T1
//比如
T1 a , b; // struct foo * a,b; a是指针类型,b是结构体类型
T2 a , b; // strcut foo * a, strcut foo *b; a和b都是指针类型
比如:
if( 'c' == a)
{
// 推荐这种写法,当少写一个等于号=时,('c' = a)编译器会立即报错
}
if( a == 'c')
{
// 当少写一个等于号=时,(a = 'c')编译器不会报错,增加了调试难度
}
如上述程序所示,一个鲁棒性高,稳定性高的程序,一定要注意一个原则:
编译时报错 > 运行时报错
也就是说,程序中的bug要早发现早解决,程序运行过程中出现的bug其查找难度倍增。