一.关键字
static
1. static局部变量和普通局部变量有什么区别?
在函数体中,一个被声明为静态的变量在这一函数被调用过程中维持其值不变, 即:static局部变量位于静态存储区,只被初始化一次,下一次依据上一次结果值。
而普通局部变量位于栈区,函数调用完成后该内存区就被释放,下次调用又要重新初始化。
2. static全局变量与普通的全局变量有异同?
在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所有函数访问,但不能被模块外其它函数访问,它是一个本地的全局变量。 例如:在testa.c中定义了一个全局静态变量static int par=1;, 在testb.c中引用该变量extern int par;,编译就会报错。static限制了变量的作用域。
如果在testa.c中定义了一个全局变量int par=1;, 在testb.c中引用该变量extern int par;,一切都是OK的。
全局变量和static修饰的全局变量都位于静态存储区,都只初始化一次。
3. static函数与普通函数有什么区别?
在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用,其他文件中的函数调用该函数就会报错。
const1. 关键字const的作用,告诉别人是只读的常量。
2. 通过给优化器一些附加的信息,便于进行类型检查,使用关键字const也许能产生更紧凑的代码。
3. 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改,这样可以减少bug的出现。
void f(const int i){ i=10;//error! }
4.const
同宏定义一样,可以做到不变则已,一变都变。但是const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
const int a=110;
const修饰的全局变量在.rodata只读数据段(const变量在定义时必须初始化,所以没有所谓的未初始化const变量),只读数据段在和.text同一个Segment.
const int a;
int const a; //前两个的作用是一样,a是一个常整型数。
const int *a; //a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以
int * const a; //a是一个指向整型数的常指针 (也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)
int const * a const; //a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)
volatile
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1. 并行设备的硬件寄存器(如:状态寄存器)
2. 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3. 多线程应用中被几个任务共享的变量
补充:
1.一个参数既可以是const还可以是volatile,一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2.一个指针可以是volatile ,尽管这并不很常见。一个例子是,当一个中服务子程序修该一个指向一个buffer的指针时。
sizeof
sizeof不是函数,是C语言中的关键字。
1.例:int par;
sizeof使用形式: sizeof(type),是数据类型必须用括号括住: sizeof(int)
如果不是数据类型,如果是变量可以不用括号括住,如sizeof (par),sizeof par等都是正确形式。
sizeof(max) --若此时变量max定义为int max();
sizeof(char_v) --若此时char_v定义为char char_v[MAX]且MAX未知,
sizeof(void) 以上都是不正确形式。
2.当操作数是指针时,sizeof依赖于编译器。例:在32位的Ubuntu上测试
void UpperCase( char str[] ) // 将 str 中的小写字母转换成大写字母
{
for( size_t i=0; i<sizeof(str)/sizeof(str[0]); ++i )//这里的使用sizeof得到的数组大小只有4,数组传参会退化为指针
if( 'a'<=str[i] && str[i]<='z' )
str[i] -= ('a'-'A' );
}
3.当操作数具有数组类型时,其结果是数组的总字节数。
char a[5]; int b[5];
sizeof(a) = 5;sizeof(b) = 20;
4.当操作数是具体的字符串或者数值时,会根据具体的类型进行相应转化。
sizeof(8) = 4; //自动转化为int类型
sizeof(8.8) = 8; //自动转化为double类型,注意,不是float类型
sizeof("ab") = 3 //自动转化为数组类型,
5.当操作数是联合类型时,sizeof是其最大字节成员的字节数。
union u{ //对union来说
char c;
double d;
}u;
sizeof(u) = max(sizeof(c),sizeof(d)) = sizeof(1,8) = 8;
6.当操作数是结构类型时,sizeof是其成员类型的总字节数,包括补充字节在内。
struct a{ //对struct来说
char b;
double x;
}a;
Linux上, sizeof(a) = 12;
而一般sizeof(char) + sizeof(double) = 9;
这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。但如果全对齐的话,sizeof(a) = 16, 这是因为b被放到偏移量为0的地址,占1个字节;在存放x时,double类型长度为8,需要放到能被8整除的偏移量上,这时候需要补7个空字节,
达到8个,这时候偏移量为8,放上x后长度为16。在此例中,所有的结构成员都要放在被4整除的地址(Linux的存放方式),这里补3个字节,所以为12。
int i = 10; i*sizeof(int);
typeof
typeof关键字是C语言中的一个新扩展,typeof的参数可以是两种形式:表达式或类型,typeof()。这类似于sizeof关键字接受的操作数(与sizeof不同的是,位字段允许作为typeof实参,并被解释为相应的整数类型)。从语义上看,typeof 关键字将用做类型名(typedef名称)并指定类型。
1.使用typeof的声明
下面是两个等效声明,用于声明int类型的变量a。
typeof(int) a; /* Specifies variable a which is of the type int */
typeof('b') a; /* The same. typeof argument is an expression consisting of character constant which has the type int */
以下示例用于声明指针和数组。为了进行对比,还给出了不带typeof的等效声明。
typeof(int *) p1, p2; /* Declares two int pointers p1, p2 */
int *p1, *p2;
typeof(int) * p3, p4;/* Declares int pointer p3 and int p4 */
int * p3, p4;
typeof(int [10]) a1, a2;/* Declares two arrays of integers */
int a1[10], a2[10];
如果将typeof用于表达式,则该表达式不会执行。只会得到该表达式的类型。以下示例声明了int类型的var变量,因为表达式foo()是int类型的。由于表达式不会被执行,所以不会调用foo函数。
extern int foo();
typeof(foo()) var;
2.使用typeof的声明限制
请注意,typeof构造中的类型名不能包含存储类说明符,如extern或static。不过允许包含类型限定符,如
const或volatile。例如,下列代码是无效的,因为它在typeof构造中声明了extern,
typeof(extern int) a;
下列代码使用外部链接来声明标识符b是有效的,表示一个int类型的对象。下一个声明也是有效的,它声明了一个使用const限定符的char类型指针,表示指针p不能被修改。
extern typeof(int) b;
typeof(char * const) p = "a";
3.在宏声明中使用typeof
typeof构造的主要应用是用在宏定义中。可以使用typeof关键字来引用宏参数的类型。
二.预处理
预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
指令 用途
# 空指令,无任何效果
#include 包含一个源代码文件
#define 定义宏
#undef 取消已定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码,相当于#elseif
#endif 结束一个#if……#else条件编译块
#error 停止编译并显示错误信息
1.用来预防多重包含同一头文件,一般用在头文件中
#ifndef __TEST_H__
#define __TEST_H__
头文件的正文内容
#endif
#ifndef指示检测预__TEST_H__处理器变量是否未定义,如果未定义,那么后面所有的指示全被处理直到出现#endif
2.一种或者两种情况的条件编译
#define TEST //自己决定是否需要定义该宏
#ifdef TEST
程序段1
#else
程序段2
#endif
或者
#ifdef 0
程序段1
#else
程序段2
#endif
它的作用是:当TEST已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。 其中#else部分也可以没有,即:
#ifdef TEST
程序段1
#endif
用该条件编译也可用来做注释
#ifdef 0
程序段1
#endif
3.多个条件的条件编译
#define CHOICE 2
#if 1 == CHOICE
程序段1
#elif 2 == CHOICE
程序段2
#elif 3 == CHOICE
程序段3
#endif
#define DeBug1
#ifdef DeBug1
程序段1
#elif defined DeBug2
程序段2
#elif defined DeBug3
程序段3
#endif
4.#运算符
出现在宏定义中的#运算符把跟在其后的参数转换成一个字符串。有时把这种用法的#称为字符串化运算符。例如:
#define PASTE(n) "adhfkj"#n
void main()
{
printf("%s\n",PASTE(15));
}
宏定义中的#运算符告诉预处理程序,把源代码中任何传递给该宏的参数转换成一个字符串。所以输出应该是adhfkj15。
4.##运算符
##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。看下面的例子:
#define NUM(a,b,c) a##b##c
#define STR(a,b,c) a##b##c
void main()
{
printf("%d\n",NUM(1,2,3));
printf("%s\n",STR("aa","bb","cc"));
}
最后程序的输出为:
123
aabbcc
5.#error用于生成一个编译错误消息,并停止编译并停止编译
用法 #error message
注:message不需要用双引号包围
#error编译指示字用于自定义程序员特有的编译错误消息
类似的,#warning用于生成编译警告,但不会停止编译
#include <stdio.h>
//方便在编译的时候打印出正在编译的什么 工程中会编译很久,方便马上停止
#if defined(ANDROID20)
#pragma message("Compile Android SDK 2.0...")
#define VERSION "Android 2.0"
#elif defined(ANDROID23)
#pragma message("Compile Android SDK 2.3...")//以附注的形式打印出来
#define VERSION "Android 2.3"
#elif defined(ANDROID40)
#pragma message("Compile Android SDK 4.0...")
#define VERSION "Android 4.0"
#else
#error Compile Version is not provided!
#endif
//gcc -DANDROID23 test.c -o test 命令窗口
int main()
{
printf("%s\n", VERSION);//宏替换
return 0;
}
6.#line用于强制指定新的行号和编译文件名,,并对源程序 并对源程序的代码重新编号
#line number filename
注:filename可省略
#line编译指示字的本质是重定义__LINE__和__FILE__
编译器是否遵循标准C规范 __STDC__
#include <stdio.h>
#line 1 "Hello.c"//从此行开始编号为7,重命名为Hello.c
#define CONST_NAME1 "CONST_NAME1"
#define CONST_NAME2 "CONST_NAME2"
void f()
{
return;
}
int main()
{
printf("%s\n", CONST_NAME1);
printf("%s\n", CONST_NAME2);
printf("%d\n", __LINE__);
printf("%s\n", __FILE__);
f();
return 0;
}
17:01:01 编译时的时间 __TIME__
Jan 31 2012 编译时的日期 __DATE__
25 当前行号 __LINE__
file1.c 被编译的文件名 __FILE__
7.# pragma是编译器指示字,,用于指示编译器完成一些特定的动作
# pragma所定义的很多指示字是编译器和操作系统特有的
# pragma在不同的编译器间是不可移植的
预处理器将忽略它不认识的# pragma指令
两个不同的编译器可能以两种不同的方式解释同一条# pragma指令
一般用法:#pragma parameter
注:不同的parameter参数语法和意义各不相同
#pragma pack//用法#pragma pack(2)
CPU对内存的读取不是连续的,而是分成块读取的,块的大小只 块的大小只能是1、2、4、8、16字节
当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣
某些硬件平台只能从规定的地址处取某些特定类型的数据,否则抛出硬件异常
8.#define,定义宏
用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1). #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2). 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3). 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4). 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要
写一个“标准”宏,这个宏输入两个参数并返回较小的一个。
#define Min(X, Y) ((X)>(Y)?(Y):(X)) //宏中小心地把参数用括号括起来
#define f (x) ((x)-1)//直接报错,X未定义 宏函数之间不能有空格
不用中间变量,实现两变量的交换
#define swap(x,y) \
x=x+y;\
y=x-y;\
x=x-y //注意:结尾没有分号
#define swap(a,b) \
a = a ^ b;\
b = a ^ b;\
a = a ^ b
inline 和define 对比,inline代码放入预编译器符号表中,高效;它是个真正的函数,调用时有严格的参数检测