C语言要点总结
1 基本数据类型
1.1 整型(假设占2个字节)
- 有符号基本整型 [signed] int
- 无符号整型 unsigned int
- 有符号短整型 [signed] short [int]
- 无符号短整型 unsigned short [int]
- 有符号长整型 [signed] long [int]
- 无符号长整型 unsigned long [int]
有符号整型变量可以存放正负数,无符号整型变量只能存放正数。
负数在内存中以补码的形式保存,即负数的绝对值的二进制数取反再加1。如-10,它在内存中应该是1111111111110110。二进制数对应的十进制负数也可以通过取反加1来获得。
整型内存溢出:32767+1将会是-32768而不是32768
整型常量后面加一个字母u或U,被认为是unsigned int型。如12345u在内存中会以unsigned int方式存储,-12345u则先将-12345转换成其补码53191,然后按unsigned int 方式存储。
整型常量后面加一个字母l或L,被认为是long int型。
1.2 浮点型
- 单精度 float
- 双精度 double
- 长双精度 long double
浮点型常量的两种表示形式:
- 十进制小数形式,如1.23
- 指数形式,如1.23e2(规范化指数形式),它等于1.23X102
在内存中会把浮点型数据分成小数部分和指数部分,分别存放。小数部分占的位(bit)数愈多,数的有效数字愈多,精度也愈高。指数部分占的位数愈多,则能表示的数值范围愈大。
1.3 字符型
字符型变量可以看作是一个只有一个字节的整型,除此之外,字符型与整型可以说是完全等同。因此字符型也会有signed char(有符号字符型)与unsigned char(无符号字符型)。
C规定以字符'\0'作为字符串结束标志。
1.4 算术运算
在进行运算时,不同类型的数据要先转换成同一类型,然后进行运算。转换法则如下:
Notice:两个整数相除的结果为整数,如5/3的结果值为1,舍去小数部分。但如果除数或被除数中有一个为负值,则舍入的方向是不固定的,但一般是“向零取整”。
1.5 赋值
- 将浮点型数据(包括单、双精度)赋给整型变量时,舍弃浮点数的小数部分
- 将一个double型数据赋给float变量时,截取float所能存储的有效数字。
- 将字符型数据赋给整型变量时,如果是无符号字符型,整型变量高位补0;如果是有符号字符型,最高位为0时,整型变量高位补0,最高位为1时,整型变量高位补1
- 将long赋给整型变量时,将long型数据低位原封不动地保存到整型变量。
- 将unsigned int 赋给signed int或signed int赋给unsigned int,会原封不动地送到signed int。但要注意溢出:unsigned int a=65535;int b=a;printf("%d",b);这时会输出-1。
1.6 逗号表达式
逗号表达式的作用是把多个表达式“串联”起来,逗号表达式的值为最后一个表达式的值。如:
int a=(3+5,5+7);那么a的值为12;
Notice:逗号运算符是所有运算符中级别最低的。所以
int a;
a=3+5,5+7;//会先算赋值表达式a=3+5。再算5+7,整个语句是一个逗号表达式,表达式的值为12。逗号运算符优先级最低
1.7 赋值运算符(=)
“=”在C语言中是一个运算符,a=b就是一个赋值表达式,该表达式的值为a的值。如:
if((a=b)>0)t=a;//赋值运算符优先级仅高于逗号运算符
1.8 C语言中的函数库
printf和scanf是C语言的标准输入输出函数,它们并不是C语言的关键字,它们存在于系统标准库中。这样做可以使C语言编译系统简单,因为没有输入输出语句就可以避免在编译阶段处理与硬件有关的问题。实际上,在windows与linux系统中,所用的printf与scanf函数(或者说标准函数库)实现是不一样的。
1.9 关系表达式与逻辑表达式
关系表达式的值不是1就是0。
Notice:在C语言中没有false和true常量,只要是非0值就是真,0值就是假。
关系运算符、算术运算符、逻辑运算符的优先级
2 C语言输入输出函数
2.1 单字符输入输出
- putchar(char c);——单字符输出
- getchar();——单字符输入
2.2 格式输入输出
- printf(格式控制,输出数据列表);——格式输出
- scanf(格式控制,地址列表&);——格式输入
格式字符 | 说明 |
d,i | 以带符号的十进制形式输出整数(正数不输出符号) |
o | 以八进制无符号形式输出整数(不输出前导符0) |
x,X | 以十六进制无符号形式输出整数(不输出前导符0x),用x则输出十六进制数的a~f时以小写形式输出。用X时,则以大写字母输出 |
u | 以无符号十进制形式输出整数 |
c | 以字符形式输出,只输出一个字符 |
s | 输出字符串 |
f | 以小数形式输出单,双精度数,隐含输出6位小数 |
e,E | 以指数形式输出实数,用e时指数以"e"表示(如1.2e+02),用E时指数以“E”表示(如1.2E+02) |
g,G | 选用%f或%e格式中输出宽度较短的一种格式,不输出无意义的0。用G时,若以指数形式输出,则指数以大写表示 |
字符 | 说明 |
l | 用于长整型整数,可加在格式符d、o、x、u前面 |
m(正整数) | 数据最小宽度 |
n(正整数) | 对实数,表示输出n位小数;对字符串,表示截取的字符个数 |
- | 输出的数字或字符在域内向左靠 |
字符 | 说明 |
l | 用于输入长整型数据(可用%ld,%lo,%lx,%lu)以及double型数据(用%lf或%le) |
h | 用于输入短整型数据(可用%hd,%ho,%hx) |
m | 指定输入数据所占宽度(即输入m宽度后,赋值给变量) |
* | 表示本输入项在读入后不赋给相应的变量。如scanf("%2d%*3d",&a); |
Notice:
- 要输出'%',使用两个%。如:printf("%f%%",1.0/3);
- 如要输入字符时,要注意空格等都算是一个字符。如要输入scanf("%d%c%f",&a,&b,&c);则应在键盘敲入1234a1230.26
3 数组
3.1 一维数组
一维数组定义:
int a[10];//与java不一样,java把数组也看成是一种类型,如int[] a=new int[10];
一维数组的初始化:
int a[5]={0,1,2,3,4};//给全部元素赋初始化值
int a[5]={0,1,2};//给一部分元素赋初始化值,后面的元素补0
int a[5]={0};//所有元素初始化为0,如果不初始化,元素的值是不确定的
int a[]={0,1,2,3,4};//如果对全部元素赋初始值时,可以不指定数组长度.
3.2 二维数组
二维数组定义:
int a[2][3];//二行三列
可以把二维数组看成是一个特殊的一维数组,它的元素又是一个一维数组.
二维数组初始化:
int a[2][3]={{1,2,3},{4,5,6}};//初始化全部元素.
int a[2][3]={1,2,3,4,5,6};//初始化全部元素
int a[3][4]={{1},{},{5,6}};//只对部分元素赋初值
int a[][3]={1,2,3,4,5,6};//如果对全部元素赋初值,可以省略第一维的长度
Notice:数组名的值实际上是在内存中存放该数组的起始地址
3.3 字符数组与字符串的关系
可以直接通过字符串向字符数组赋值:
char s[]="China";
而实际上,在内存中,字符串是通过字符数组来存储的,而字符串常量的值实际上是其在内存中的字符数组的起始地址.
3.4 字符串处理函数
- puts(str):在终端输出字符串str
- gets(str):在终端输入字符串到str,返回str的起始地址
- strcat(str1,str2):连接str1与str2,并保存到str1中,返回str1的起始地址
- strcpy(str1,str2):将str2完整地复制到str1中,str1的长度大于str2的长度.str1原来的字符不变
- strncpy(str1,str2,n):将str2前n个字符复制到str1中.
- strcmp(str1,str2):比较str1同str2,如果str1>str2,则返回一个正整数;如果str1==str2,则返回0;如果str1<str2,则返回负整数
- strlen(str):返回str的有效长度,不包括'\0'
- strlwr(str):lowercase str
- strupr(str):upper str
4 函数
4.1 函数返回类型
函数返回类型默认是int型.
如:
max(int x,int y){
return (x>y?x:y);
}
4.2 函数的调用中实参的求值顺序
函数的调用中实参的求值顺序会因编译系统而不一样。一般是从右到左的求值顺序。
int i=3;pirntf("%d %d\n",i,i++);//结果为:4,3
4.3 数组名作为函数参数
float average(float array[]);//array就是数组首地址
float average(float array[][2]);//高维长度必须要指定
4.4 局部变量与全局变量
在函数内、程序块内声明的变量为局部变量,它的作用范围为该函数或程序块内。
在函数外定义(且不带static)的变量为全局变量。全局变量能作用的范围为所有源文件。全局变量名的首字母一般为大写
如果函数或全局变量定义为static,那么它们能作用的范围为所在的源文件,否则,它们能作用的范围为所有源文件。
如果在调用函数或引用全局变量之前没有作出相应的函数或全局变量定义或它们定义在其它源文件中,需要在调用或引用前使用extern声明(函数声明可以省略extern)。
4.5 变量的存储类别
每个变量和函数有两个属性,数据类型和数据在内存中的存储方式(存储类别)。存储类别可以分为两大类:静态存储类和动态存储类。具体包含4种:自动的(auto),静态的(static),寄存器的(register),外部的(extern)。
4.5.1 auto
函数内的局部变量,如果不专门声明为static,默认为auto的。它存储在动态存储区,只在函数调用时动态分配存储空间,在函数调用结束时释放存储空间
int f(int a){ auto int b;//与int b等同 }
4.5.2 static
若将函数内的局部变量声明为static,则该变量存储在静态存储区。它在整个程序运行过程中占用内存空间,但它的作用范围与auto相同(在函数内),不能被其他函数引用。
4.5.3 register
当一个变量需要被经常引用时,可以声明为register变量。一般编译系统都可以自动地确定哪些变量作为register变量,因此在代码中一般不需要明确指定。
4.5.4 extern
默认的外部变量定义、函数定义都是extern的,意思是它们能作用的范围为所有源文件。
extern的另一个作用是声明外部变量、函数,告诉编译系统该外部变量或函数定义在本文件后面或其他文件中。
5 预处理命令
预处理命令不是语句,因此后面不需要跟分号。
5.1 宏定义
宏定义是指在预处理阶段,把某些标识符用字符串替换。
5.1.1 不带参数宏定义
#defiine PI 3.1415926 //定义PI为3.1415926,在预处理时会用3.1415926替换当前源文件中所有的PI(双引号内除外)。标识符一般用全大写字母表达。
5.1.2 带参数宏定义
#define S(a,b) (a)*(b) int f(x,y){ int sum=S(x+1,y+2);//经预处理后即:int sum=(x+1)*(y+2); }
5.2 include预处理
#include <文件名>或#include "文件名"
include的作用是在预处理阶段把指定的文件完整地嵌入到当前源文件中。
include支持嵌套文件,如:文件c1包含文件c2,而文件c2又包含c3
用<>时,系统到存放C库函数头文件的目录中寻找要包含的文件,这是标准方式。用“”时,系统先在用户当前目录中寻找要包含的文件,若找不到,再按标准方式查找。一般来说,如果为调用库函数,应用<>,如果要包含用户编写的文件,则用""
5.3 条件编译
#ifdef DEBUG content #else content #endif //或 #ifndef DEBUG content #else content #endif //或 #if DEBUG==1 content #else content #endif
6 指针
指针就是指变量在内存中的地址。指针的加减运算,实质上是地址的移动,移动的距离视指针指向的变量而定。比如,一个指向整型(如整型占2个字节)变量的指针,则它加1则是指针向后移动2个字节。
&:取址址运算符,用于获取变量的地址。
*:指针运算符,取指针所指向的内容。
6.1 指针变量的定义
int * p;//p是指向整型变量的指针
int * p[10];//p是一个指针数组,数组中的每个元素都指针
int (* p)[10];//p是一个指向一维数组的指针
紧记:一维数组名是一个指向该数组首元素的地址常量。如:int a[10]; 则a是a[0]的地址。
同理,二维数组名是一个指向其首个一维数组的地址常量。如:int a[][10];则a指向a[0]的地址(此处a[0]是一个一维数组),**a则指向a[0][0]的地址。
所以得出一个结论,如果p是指向一个n维数组的指针,那么*p则是指向该n维数组内首个n-1维数组的指针。
6.2 函数参数的值传递与址传递
如果参数是通过值传递的方式,那么在函数运行过程中,会在内存中创建一片新的区域来保存参数的值,它不会改变实参的值。如基本类型变量,结构体变量等
如果参数是通过址传递的方式,那么函数运行时是直接对实参操作。如所有的指针变量,数组名。
如果想通过函数调用改变N个变量的值,就要使用址传递的方式了。
Notice:[]实际上是变址运算符
6.3 指针运算
*p++:相当于*(p++)。因为*与++优先级别相同,而且*与++都是右结合性
6.4 字符指针
在C语言中,字符串是以字符数组的形式存在。一个字符串常量实质上是字符数组首字符的地址。因此可以像下面这样对字符指针赋值。
char * p="China!";
Notice:在对数组定义的时候,会给该数组分配内存空间,但对指针变量定义是,该指针变量所指向的地址是不确定的。因此在定义指针变量时一般需要进行初始化:int * p=NULL;
6.5 指向函数的指针
函数名代表该函数的入口地址。
函数指针的定义:
int (*p)(int,int);//p是指向一个带两个整型参数,返回值是整型的函数地址。
通过函数指针来调用函数:
int c=(*p)(1,2);
定义一个返回数组指针的函数:
int (*toArray())[4]{...}
函数指针存在的意义:
1.函数指针作为形参
2.会视条件的不同而调用不同的函数。
函数指针示例:
#include<stdio.h> int main(int argc,char ** argv){ extern int getNum(int (*)(int,int),int,int); extern int getMax(int,int); extern int getMin(int,int); //printf("%d\n",argc); if(argc!=2){ fprintf(stderr,"error:need a number parameter!"); return 1; } char * p=*(argv+1); int result=0; printf("%s\n",p); if(strcmp(p,"0")){ result=getNum(getMax,3,5); }else{ result=getNum(getMin,3,5); } printf("result=%d\n",result); } int getNum(int (*p)(int,int),int a,int b){ return (*p)(a,b); } int getMax(int a,int b){ return a>b?a:b; } int getMin(int a,int b){ return a>b?b:a; }
6.6 指针数组与指向指针的指针
指向指针的指针,即该指针是指向指针的地址。
指针的指针的定义:
char **p;
指针的指针在main方法中的应用:
void main(int argc,char **argv);
void main(int argc,char * argv[]);
Realization:指针的指针一般在指向数组元素时才会显得有意义!
6.7 void指针类型
void指针用于定义一个指针变量,但不指定它是指向哪一种类型数据的。
在使用void指针时,一般都需要用强制类型转换,如:
char * p1;
void * p2;
p1=(char *) p2;
7 结构体
结构体给了C语言用户一个自定义数据类型的机会。一个结构体所占的内存空间为所有成员变量之和。
结构体类型的定义:
struct student{ int num; char name[20]; char sex; int age; float score; char addr[3]; };//注意不能省略分号
结构体变量的定义:
struct student s1={1,"Tim",'M',20,20.3,"China"};//定义及初始化。
结构体成员的引用:
1.使用"."形式:s1.num或(*p).num //p为指向结构体变量的指针
2.使用"->"形式: p->num //p为指向结构体变量的指针
Notice:可通过结构体来实现链表的数据结构。
8 共用体
共用体意味着共用体内的成员变量共用同一块内存区域,而且同一时间只能引用或赋值给其中一个成员变量,不能对共同体变量赋值。一个共用体变量所占的内存长度等于最长的成员的长度。
共用体类型的定义:
union data{ int i; float f; };
共同体变量的定义:
union data a;//a为共同体类型data的变量。
9 枚举类型
如果一个变量只有几种可能的值,则可以定义为枚举类型。
枚举类型的定义:
enum weekday{sun,mon,tue,wed,thu,fri,sat};//编译时,按定义的顺序给它们赋值0,1,2...如sun=0,mon=1
枚举类型变量的定义:
enum weekday workday,week_end;//枚举类型变量只能取类型定义中定义的值。 workday=(enum weekday)2;//需要强制类型转换。 if(workday>sun)... //可以用比较运算符
10 typedef
typedef的作用是给数据类型定义别名,它有利于程序的通用与移植,如:
typedef int INTEGER;//注意有分号
INTEGER a,b;//现在a,b就是整型变量
声明一个新的类型名的方法是:
1.先按定义变量的方法写出定义体(如:int i;)
2.将变量名换成新类型名(例如:将i换成 INTEGER)。
3.在最前面加typedef(例如: typedef int INTEGER).
4.然后使用新的类型名去定义变量。
如要声明一个新的结构体类型名:
1.定义一个结构体变量
struct{
int num;
char name[20];
}s1;
2. 把s1替换成STUDENT
struct{
int num;
char name[20];
}STUDENT;
3.在最前面加typedef
typedef struct{
int num;
char name[20];
}STUDENT;
4.使用新声明的类型名
STUDENT s={1,"Tim"};
typedef与#define的区别:#define是在预处理中执行简单的文件替换,而typedef是在编译阶段处理。
11 位运算
11.1 按位与(&)
- 清零:11111111 & 00000000=00000000;
- 保留原值:10101010 & 11111111=10101010;
11.2 按位或(|)
- 置1:10101010 | 11111111=11111111;
- 保留原值:10101010 | 00000000=10101010;
11.3 异或去处(^)——同为0,异为1
- 位翻转:10101010 ^ 11111111 = 01010101;
- 保留原值:10101010 ^ 00000000 = 10101010;
- 交换两个值,不用临时变量:
int a=3,b=4;
a=a^b;
b=b^a;
a=a^b;
Notice:位运算相同时,支持交换律。
11.4 左移(<<)
左移n位,相当于原值乘以2n 。注意左移时溢出的情况。左移时最低位补0
11.5 右移(>>)
右移n位,相当于原值除以2n 。右移时无符号数高位补0,有符号正数高位补0,有符号负数高位补1
11.6 位段
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员为“位段”或称“位域”。如:
struct packed_data{
unsigned a:2;
unsigned b:6;
unsigined c:4;
unsigned d:4;
int i;
} data;
12 文件处理
12.1 C文件概述
所谓“文件”一般指存储在外部介质上数据的集合。操作系统是以文件为单位对数据进行管理的,也就是说,如果想找存在外部介质上的数据,必须先按文件名找到所指定的文件,然后再从该文件中读取数据。
根据数据的组织形式,可分为ASCII文件和二进制文件。ASCII文件又称文本文件,它的每一个字节存放一个ASCII代码,代表一个字符。二进制文件是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。
一个C文件是一个字节流或二进制流。它把数据看作是一连串的字符(字节),而不考虑记录的界限。输入输出的数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制。
C中有两种对文件的处理方法:一种叫“缓冲文件系统”,一种叫“非缓冲文件系统”。
所谓缓冲文件系统是指系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。如果从磁盘向内存读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到送到程序数据区(给程序变量)。
所谓“非缓冲文件系统”是指系统不自动开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区。
在UNIX系统下,用缓冲文件系统来处理文本文件,用非缓冲文件系统处理二进制文件。
ANSI C 标准不采用非缓冲文件系统,而只采用缓冲文件系统,即既用缓冲文件系统处理文本文件,也用它来处理二进制文件,也就是将缓冲文件系统扩充为可以处理二进制文件。
12.2 文件类型指针
文件类型,实际上它是一个结构体类型。
struct _IO_FILE { int _flags; char* _IO_read_ptr; char* _IO_read_end; char* _IO_read_base; char* _IO_write_base; char* _IO_write_ptr; char* _IO_write_end; char* _IO_buf_base; char* _IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; int _flags2; __off_t _old_offset; unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; # 321 "/usr/include/libio.h" 3 4 __off64_t _offset; # 330 "/usr/include/libio.h" 3 4 void *__pad1; void *__pad2; void *__pad3; void *__pad4; size_t __pad5; int _mode; char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]; }; typedef struct _IO_FILE FILE;
一般使用的是文件类型指针变量:
FILE * fp;
12.3 文件的打开与关闭
12.3.1 文件的打开
FILE * fp=fopen(文件名,文件使用方式);
如果打开文件时有任何错误,则返回NULL指针。
文件使用方式有以下这些:
- r:以只读方式打开一个文本文件,文件不存在返回NULL。
- w:以只写方式打开一个文本文件,不管文件是否存在,都会重新写该文件
- a:向文本文件尾添加数据,文件不存在返回NULL
- rb:以只读方式打开一个二进制文件,文件不存在返回NULL。
- wb:以只写方式打开一个二进制文件,不管文件是否存在,都会重新写该文件
- ab:向二进制文件尾添加数据,文件不存在返回NULL
- r+,w+,a+,rb+,wb+,ab+:与上面几点相同,但是可读写的
12.3.2 文件的关闭
fclose(文件指针)
操作成功返回0,否则返回EOF(-1),可以用ferror函数来测试
在程序终止之前应关闭所有文件,如果不关闭有可能会丢失数据。因为在向文件写数据时,是先将数据输出到缓冲区,待缓冲区充满后才正式输出给文件。如果当数据未充满缓冲区而程序结束运行,就会将缓冲区中的数据丢失。用fclose函数关闭文件,可以避免这个问题,它先把缓冲区中的数据输出到缓冲文件,然后才释放文件指针变量。
12.4 文件的读写
12.4.1 系统的三个标准文件
在程序开始运行时,系统自动打开3个标准文件:标准输入、标准输出、标准出错输出,它们对应的指针变量为:stdin、stdout、stderr。程序如果对这三个标准文件进行读写,就表示对终端进行输入输出操作。
12.4.2 文件读写函数
char fputc(char ch,FILE * fp);//向文件写一个字符。一般用于文本文件
char fgetc(char ch,FILE * fp);//从文件读一个字符。一般用于文本文件
char * fgets(char * str,int n,FILE * fp);//从文件读取长度为n的字符串,读取出来的数据保存到str数组,返回str数组的首地址。一般用于文本文件
int fputs(char * str,FILE * fp);//向文件写入字符串str,操作成功返回0,失败则返回EOF。一般用于文本文件
fprintf(FILE * fp,格式字符串,输出表列);//printf函数相当于fprintf(stdout,格式字符串,输出表列)。一般用于文本文件
fscanf(FILE * fp,格式字符串,输入表列);//scanf函数相当于fscanf(stdin,格式字符串,输入表列)。一般用于文本文件
int fread(void * buffer,int size,int count,FILE * fp);//读取count个大小为size的数据块,数据存放在buffer指针指向的内存中。返回实际读取的数据块数量。一般用于二进制文件
int fwrite(void * buffer,int size,int count,FILE * fp);//写入count个大小为size的数据块,数据来源于buffer。一般用于二进制文件。返回实际写入的数据块数量。一般用于二进制文件
Notice:对于文本文件的读写操作可以通过EOF(-1)来判断文件是否结束。因为单个字符是不可能保存"-1"的。而对于二进制文件则不能通过EOF来判断了,因为-1在二进制中是可以获取的。要判断文件是否结束要用函数feof(FILE * fp)。
12.5 文件定位
void rewind(FILE * fp);//位置指针返回文件的开头
void fseek(FILE * fp,long offset,int position);//位置指针定位到相对于position的offset的位置。
position可取值:
起始点 | 名字 | 用数字代表 |
文件开始 | SEEK_SET | 0 |
文件当前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
long ftell(FILE * fp);//获取相对于文件开头位置的位移量。出错返回-1L。
12.5 出错检测
所有的输入输出函数出错时,会记录到文件错误标志中。可以通过使用ferror函数来检查是否出错。
int ferror(FILE * fp);//没错返回0。
void clearerr(FILE * fp);//使文件错误标志置为0。
Notice:只要出现错误标志,就一直保留,直到对同一文件调用clearerr或rewind或任何一个输入输出函数。
13 宏中"#"和"##"的用法
13.1 一般用法
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.如:
#include <stdio.h> #define STR(a) #a #define E(a,b) (int)a##e##b void main(){ printf(STR(abcdef\n));//输出"abcdef\n" printf("%d""\n",E(2,3));//输出2000 }
宏是这样展开的:
STR(abcdef\n)——>"abcdef\n"
E(2,3)——>(int)2e3
13.2 当宏参数是另一个宏的时候
#include <stdio.h> #define NUM 2 #define STR(a) #a #define E(a,b) (int)a##e##b void main(){ printf(STR(NUM));//输出NUM,并不会展开NUM printf("%d""\n",E(NUM,NUM));//会编译出错 }
根据上面的代码可以得出结论:如果宏定义中使用了'#'或'##',那么该宏的参数不会被展开。 解决上面问题很简单,就是使用一个中间宏,中间宏的作用就是先让宏参数展开,再展开带'#'或'##'的宏。如:
#include <stdio.h> #define NUM 2 #define _STR(a) #a #define STR(a) _STR(a) #define _E(a,b) (int)a##e##b #define E(a,b) _E(a,b) void main(){ printf(STR(NUM)"\n");//使用中间宏后,可以成功展开NUM printf("%d""\n",E(NUM,NUM));//使用中间宏后,可以成功展开NUM // printf("%d\n",__LINE__); }
上例中宏展开的顺序为:
STR(NUM)——>_STR(2)——>"2"
E(NUM,NUM)——>_E(2,2)——>(int)2e2
Notice:宏__LINE__的值为所在行号