C关键字
预处理
宏函数
- 避免了函数的入栈、出栈、跳转等开销,可以提高程序运行效率
- 多次调用,会是代码变的庞大,以空间换时间
- 避免替换错误需要添加括号
#define AAD(x, y) (x + y)
#define MAX(x, y) ( (x > y) ? (x) : (y) )
宏的有效范围
- 从宏定义的位置开始到文件结束或者取消宏定义
- 不做作用域检测
- 不做类型检测
void test()
{
#define NUM 10
}
int main()
{
printf("%d\n", SUM);
}
撤销宏
#undef SUM
内置宏
printf("代码在 %d 行\n", __LINE__);
printf("代码编译的时间%s %s\n", __DATE__, __TIME__);
printf("文件名 %s\n", __FILE__);
printf("函数名%s\n", __FUNCTION__);
条件编译ifdef
#ifdef 标识符
程序段1
#else
程序段2
#endif
它的功能是:如果标识符已被#define命令定义过,则对程序段1进行编译;
否则对程序段2进行编译。如果没有程序段2(它为空),本格式中的#else可以没有,#endif不可或缺
#include <stdio.h>
int main()
{
#ifdef _DEBUG
printf ("正在使用DEBUG模式编译代码。。。\n");
#else
printf ("正在使用Release模式编译代码。。。。\n");
#endif
return 0;
}
编译的时候增加宏:gcc -D_DEBUG
#if 常量表达式
程序段1
#else
程序段2
#endif
它的功能是:如果常量表达式的值为真(非0),则对程序段1进行编译;
否则对程序段2进行编译。因此可以使程序在不同条件下编译,完成不同的功能。
#include <stdio.h>
// window _WIN32
// Linux __linux__
int main()
{
#if (_WIN32)
{
printf("this is window system\n");
}
#elif (__linux__)
{
printf ("this is linux system\n");
}
#else
{
printf ("unkown system\n");
}
#endif
#if 0
code
#endif
return 0;
}
#与##
- #用于在与编译期间将宏参数转化为字符串
#include <stdio.h>
#define SQR(x) printf ("The square of "#x" is %d \n", ((x)*(x)))
int main()
{
SQR(8);
return 0;
}
- ##用于在预编译期间粘连两个符号
#include <stdio.h>
#define CREATEFUN(name1, name2)\
void name1##name2()\
{\
printf ("%s called\n", __FUNCTION__);\
}
CREATEFUN(my, Function);
int main()
{
myFunction();
return 0;
}
关键字
Static
Static修饰局部变量
在局部静态变量前面加上关键字static,该局部变量便成了静态局部变量。静态局部变量有以下特点:
(1)该变量在全局数据区分配内存
(2)如果不显示初始化,那么将被隐式初始化为0
(3)它始终驻留在全局数据区,直到程序运行结束
(4)其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束
Static修饰全局变量
在全局变量前面加上关键字static,该全局变量变成了全局静态变量。
全局静态变量有以下特点:
(1)在全局数据区内分配内存
(2)如果没有初始化,其默认值为0
(3)该变量在本文件内从定义开始到文件结束可见,即只能在本文件内使用
Static修饰函数
在函数的返回类型加上static关键字,函数即被定义成静态函数。
静态函数有以下特点:
(1) 静态函数只能在本源文件中使用
(2) 在文件作用域中声明的inline函数默认为static
说明:静态函数只是一个普通的全局函数,只不过受static限制,他只能在所在文件内使用,不能在其他文件内使用。
extern
extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义。
const
const只是不能通过修饰的变量改变空间的值,不代表空间的值不能改变
const修饰一般变量
一般变量是指简单类型的只读变量。这种只读变量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。例如:
int const i = 2;
const int i = 2;
const修饰数组
定义或说明一个只读数组
int const a[5] = {1,2,3,4,5};
const int a[5] = {1,2,3,4,5};
const修饰指针
const int * p; // p可变,p指向的对象不可变
int const * p; // p可变,p指向的对象不可变
int * const p; // p不可变,p指向的对象可变
const int * const p; // 指针p和p指向的对象都不可变
这里给出一个记忆和理解的方法:
先忽略类型名(编译器解析的时候也是忽略类型名),我们看const离哪个近,离谁近就修饰谁。
const (int) *p //const 修饰*p,p是指针,*p是指针指向的对象,不可变。
(int) const * p; //const 修饰*p,p是指针,*p是指针指向的对象,不可变。
( int) * const p;//const 修饰p,p不可变,p指向的对象可变
const ( int)* const p; // 前一个const修饰*p,后一个const修饰p,指针p和p指向的对象都不可变
const修饰函数参数
const修饰也可以修饰函数的参数,当不希望这个参数值在函数体内被意外改变时使用,例如:
`void Fun(const int *p);`
告诉编译器*p在函数体中不能改变,从而防止了使用者的一些无意的或错误的修改。
const修饰函数返回值
const修饰符也可以修饰函数的返回值,返回值不可被改变。例如:
`const int Fun(void);`
typedef
使用关键字 typedef 可以为类型起一个新的别名,语法格式为:
typedef oldName newName;
oldName 是类型原来的名字,newName 是类型新的名字。
需要强调的是,typedef 是赋予现有类型一个新的名字,而不是创建新的类型。为了“见名知意”,请尽量使用含义明确的标识符,并且尽量大写。
typedef给结构体类型定义别名
typedef struct stu
{
char name[20];
int age;
char sex;
} STU;
STU 是 struct stu 的别名,可以用 STU 定义结构体变量:
STU body1,body2;
它等价于:
struct stu body1, body2;
typedef给指针类型定义别名
typedef int (*PTR_TO_ARR)[4];
表示 PTR_TO_ARR 是类型int * [4]的别名,它是一个二维数组指针类型。接着可以使用 PTR_TO_ARR 定义二维数组指针:
PTR_TO_ARR p1, p2;
按照类似的写法,还可以为函数指针类型定义别名:
typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;
typedef与#define区别
typedef 在表现上有时候类似于 #define,但它和宏替换之间存在一个关键性的区别。
正确思考这个问题的方法就是把 typedef 看成一种彻底的“封装”类型,声明之后不能再往里面增加别的东西。
1) 可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。如下所示:
#define INTERGE int
unsigned INTERGE n; //没问题
typedef int INTERGE;
unsigned INTERGE n; //错误,不能在 INTERGE 前面添加 unsigned
2) 在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型,而 #define 则无法保证。例如:
#define PTR_INT int *
PTR_INT p1, p2;
经过宏替换以后,第二行变为:
int *p1, p2;
这使得 p1、p2 成为不同的类型:p1 是指向 int 类型的指针,p2 是 int 类型。
相反,在下面的代码中:
typedef int * PTR_INT
PTR_INT p1, p2;
p1、p2 类型相同,它们都是指向 int 类型的指针。