C语言总结——关键字和预处理

来源:互联网 发布:做视频的软件 编辑:程序博客网 时间:2024/06/15 16:43

一.关键字

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函数与普通函数有什么区别?

在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用,其他文件中的函数调用该函数就会报错。

const

1. 关键字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操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。    
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。
7.当操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小,Linux中值为4。
8.sizeof与其他操作符的关系。sizeof的优先级为2级,比/、%等3级运算符优先级高,它可以与其他操作符一起组成表达式。
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"#nvoid main(){    printf("%s\n",PASTE(15));}
宏定义中的#运算符告诉预处理程序,把源代码中任何传递给该宏的参数转换成一个字符串。所以输出应该是adhfkj15。
4.##运算符
##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。看下面的例子:
#define NUM(a,b,c) a##b##c#define STR(a,b,c) a##b##cvoid 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代码放入预编译器符号表中,高效;它是个真正的函数,调用时有严格的参数检测
0 0
原创粉丝点击