标签 c 下的文章

scanf/fscanf 的%[]和%n使用方法

标准输入输出函数%[]和%n说明符的使用方法

scanffscanf,均从第一个非空格的可显示字符开始读起!
标准输入输出函数scanf具有相对较多的转换说明符,它常常作为入门级函数出现在各种教材中。但奇怪的是,[]n这两种都为c89/c99所规定的标准说明符却鲜少在大多数教材中出现。虽然[]n说明符的使用频率不及其它说明符,但两者在程序设计中的作用仍然不可小视,尤其是[]说明符。

众所周之,scanf以空白字符为定界符,但如果输入的字符串是以其它字符为定界符的,那怎么办?[]就是专门处理这个问题的转换说明符。[]转换说明符可以通过两种方式产生结果字符集,如果第一个[字符右边没有抑扬符^,那么处于[]之间的字符就是结果字符集,不在其中的可输入字符都作为定界符;如果左边[符号紧靠一个抑扬符^,那么意义相反,^]之间的字符是定界符,其余可输入字符是结果字符集。

在使用[]说明符之前,得先明白两个概念:一是扫描列表。扫描列表(scanlist)指的是包含在[和]两个字符之间除紧靠左边[字符的抑扬符之外的字符,例如:

scanf("%[abcd]", ptr);

abcd组成扫描列表。二是扫描字符集(scanset)。扫描字符集指的是结果字符集,例如上面的例子,结果字符集就是abcd。如果输入一个字符串“cbadkjf”,那么ptr得到的字符串是cbad,kjf三个字符都属于定界符,输入到k字符时输入字符串被截断,kjf三个字符被留在stdin里面。如果带有抑扬符,例如:

scanf("%[^abcd]", ptr);

扫描列表仍然是abcd,但扫描字符集是除abcd外的可输入字符。如果输入字符串“jksferakjjdf”,ptr得到的字符串是“jksfer”。如果想限制输入字符串的字符数量,可以象s说明符那样,在[]前面使用位域,例如:

scanf("%10[^abcd]", ptr);

这样结果字符串最多只能包含10个字符(除'0'字符外)。

[符号可以作为扫描列表中的一个成员,但]字符除紧贴最左边的[字符或抑扬符两种情况外,其余情况下都不会被看作扫描列表的成员。例如%[]abcd]或者%[^]abcd],上述两种情况下]字符属于扫描列表的成员,但如果是“%[ab]cd]”,中间的]字符不会被看作扫描列表的成员,而且输入输出的结果会是乱七八糟的。

对于减号-,只有在紧贴[字符或抑扬字符以及作为扫描列表最后一个成员时,-字符才会被视为扫描列表的成员。c标准把其余情况规定为编译器相关的。大多数编译器把这种情况的减号定义为连字符,例如:

scanf("%[a-zA-Z]", ptr);

那么扫描列表由大小写各26个字母组成。少数编译器仍旧把这种情况下的减号视为扫描列表成员。

fscanf(fd,"%*[^\n]\n");//%*是虚读,没有存,只是让指针跳过了这个变量!

%n说明符输出有效字符数量,%nscanfprintf中都可使用。与%n相对应的形参是一个int类型的指针,%n不影响scanfprintf的返回值。例如:

scanf("%d %d%n", &i, &j, &k);

如果输入434 6434,则k等于8,而scanf的返回值仍然为2。又如:

scanf("%c%n", &ch, &k);

输入sbcdefdg后,k等于1,而不是8,因为%c只取一个字符,%n输出的是有效字符数量。

%n用在printf函数里,表示输出的字符数量,例如:

printf("i=%d, j=%d/n%n", i, j, &k);

在i=343、j=123的情况下,k=12,同时%n不影响printf的返回值,其返回值仍然为12,而不是14。


这个用法是在参H264 jm82考代码上看到的,用来从解码器参数配置文件中读取配置参数,代码如下:

// read the decoder configuration file
if((fd=fopen(config_filename,"r")) == NULL)
{
snprintf(errortext, ET_SIZE, "Error: Control file %s not found/n",config_filename);
error(errortext, 300);
}

fscanf(fd,"%s",inp->infile);                // H.26L compressed input bitsream
fscanf(fd,"%*[^/n]");

fscanf(fd,"%s",inp->outfile);               // YUV 4:2:2 input format
fscanf(fd,"%*[^/n]");

fscanf(fd,"%s",inp->reffile);               // reference file
fscanf(fd,"%*[^/n]");

对应的配置文件内容如下:

test.264                 ........H.26L coded bitstream
test_dec.yuv             ........Output file, YUV 4:2:0 format
test_rec.yuv             ........Ref sequence (for SNR)

通过这种方式

inp->infile = "test.264"

inp->outfile = "test_dec.yuv"

inp->reffile = "test_rec.yuv"

而相应的配置文件中的一些注释则不会被读入,这是相当简便的用法,比起通过严格约定注释符并进行一个字符一个字符来解析,这种方式简单了许多!值得借鉴!


scanf

语法:

  #include <stdio.h>
  int scanf( const char *format, ... );

类似函数有

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);//指定输入流
int sscanf(const char *str, const char *format, ...);//指定缓存区

scanf()函数根据由format(格式)指定的格式从stdin(标准输入)读取,并保存数据到其它参数. 它和printf()有点类似. format(格式)字符串由控制字符,空白字符和非空白字符组成. 控制字符以一个%符号开始,如下:

控制字符说明
%c一个单一的字符
%d一个十进制整数
%i一个整数
%e, %f, %g一个浮点数
%o一个八进制数
%s一个字符串
%x一个十六进制数
%p一个指针
%n一个等于读取字符数量的整数
%u一个无符号整数
%[]一个字符集
%%一个精度符号
  1. scanf()读取匹配format(格式)字符串的输入. 当读取到一个控制字符, 它把值放置到下一个变量. 空白(tabs, 空格等等)会跳过. 非空白字符和输入匹配, 然后丢弃. 如果是一个在%符号和控制符间的数量, 那么只有指定数量的字符转换到变量中. 如果scanf()遇到一个字符集(用%[]控制字符表示), 那么在括号中的任意字符都会读取到变量中. scanf()的返回值是成功赋值的变量数量, 发生错误时返回EOF.
  2. scanf()函数的一般格式为:scanf("格式字符串",输入项首地址表)
  3. scanf的格式控制的一般形式为:%[*][宽度][F|N][h|l]类型字符
    []中的控制字符为可选项
  4. "*"表示该输入项读入后不赋予任何变量,即跳过该输入值。
  5. "宽度"表示输入读入字符的长度,对于整型表示截取相应宽度的数字赋给后面列表中的相应变量;对于字符型表示读入相应长度的字符后把第一个字符赋给相应的变量,其余的自动舍弃。例如scanf("%2d%3d",&a, &b);如果输入为12345则将12赋给a,将45赋给b;scanf("%2c%3c",&a, &b);如果输入为12345则将'1'赋给a,将'3'赋给b .
范例说明
%s整个输入作为一个串,并设置末尾的\0
%nsn为整数,读入的串最长不超过n,然后在末尾补'0'
%nf读入的浮点数最多有n位整数,位数多于n,会截断。
%n[a-z]读入最多n个字符,如果遇到非a-z的字符,停止
%1读入任意多的字符,直到遇到"="停止
%n[^=]读入"="号前的至多n 个字符
  1. F 、N、h、l分别表示远指针、近指针、短整和长整型。
  2. 对于输入字符串还有一些比较有用的控制。
    例如经常需要读入一行字符串,而这串字符里面可能有空格、制表符等空白字符,如果直接用%s是不可以的,于是有些人就想到用gets(),当然这也是一种选择,但是懂C的人基本上都知道gets()是一个很危险的函数,而且很难控制,特别是与scanf()交替使用时前者的劣势更是一览无余,所以gets()一般是不推荐用的,其实用%[^\n]就可以很好的解决这个问题了,^表示"非",即读入其后面的字符就结束读入。这样想读入一行字符串直接用scanf("%[^\n]%*c",str);就可以了,%*c的作用是读入\n,否则后面读入的将一直是/n

所有对%s起作用的控制都可以用%[],比如%[0-9]表示只读入'0'到'9'之间的字符,%[a-zA-Z]表示只读入字母,
'-'是范围连接符,当然也可以直接列出你需要读入的字符。
如果你只需要读"abc"里面的字符就可以用%[abc] (或者%[cab]、%[acb]、%[a-c]、%[c-a].....),
如果想读入某个范围之外的字符串就在前面加一个'^',如:%[^a-z]就表示读入小写字母之外的字符。
例如从键盘输入的"1235ab86"中读取1235、86给n,有如下方法:

#include <stdio.h>  
bool skip(){  
     scanf("%*[^0-9]");  
     return true;  
}  
void main()  
{  
      int n;  
      while(skip() && scanf("%d", &n)!=EOF)  
        printf("%d\n", n);  
}

输出为:

1235
86

转载自:http://blog.csdn.net/wesweeky/article/details/6439777


  1. =

c的标准IO

文件的概念

  • 所谓文件是指一组相关数据的有序集合,这个数据集有一个名称,叫做文件名。如源程序文件,目标文件,可执行文件,头文件等
  • 文件通常时驻留在外部介质(如磁盘等)上的,在使用时才调入内存中来
  • 从用户角度看,文件可分为普通文件和设备文件两种

    • 普通文件:普通文件是指驻留在磁盘或其他外部介质上的一个有序数据集,可以是源文件,目标文件,可执行程序等
    • 设备文件:设备文件是指与主机相联的各种外部设备,如显示器,打印机,键盘等。在操作系统中,把外部设备也看作是一个文件来管理,把它们的输入,输出等同于磁盘文件的读和写
  • 从文件编码的形式看,文件可分为文本文件和二进制文件两种

    • 文本文件

      • ASCII码格式存放,一个字节放一个字符。文本文件的每一个字节存放一个ASCII码,代表一个字符。这便于对字符的逐个处理,但占用存储空间较多,转换为二进制速度慢,但直观易记。
      • 如整数 1000 => 0011000100110000001100000011000000110000
    • 二进制文件

      • 以补码形式存放。二进制文件是把数据以二进制数的格式存放在文件中的,其占用存储空间极少,无需转换。
      • 数据按其内存中的存储形式原样存放。一个字节不对应一个字符,故不能直接输出其字符样式。
      • 如:整数 1000 => 0010011100010000

文件系统的分类

目前c语言所使用的磁盘文件系统分为缓冲文件系统和非缓冲文件系统

  • 缓冲文件系统

    • 系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。从磁盘向内存读入数据时,则一次从磁盘文件将一些数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送给变量。向磁盘文件输出数据时,先将数据送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去
    • 用缓冲区可以一次读入一批数据,或输出一批数据,而不是执行一次输入或输出函数就去访问一次磁盘,这样做的目的是减少磁盘的实际读写次数,提高读写的效率。
  • 非缓冲的文件系统

    • 不是有系统自动设置缓冲区,而由用户自己根据需要设置
    • 用户在程序中为每个文件设定缓冲区
  • 在传统的UNIX文件系统下,用缓冲文件文件系统来处理文本文件,用非缓冲文件系统处理二进制文件
  • 1983年ANSI C标准决定不采用非缓冲文件系统,而只采用缓冲文件系统。即用缓冲文件系统处理文本文件,也用它来处理二进制文件。也就是将缓冲文件系统扩充为可以处理二进制文件。
  • 一般把缓冲文件系统的输入输出称为标准输入输出(标准IO),非缓冲文件系统的输入输出称为系统输入输出(系统IO)。
  • 在c语言中没有输入输出语句,对文件的读写都是用库函数来实现的。ANSI C规定了标准输入输出函数,用它们对文件进行读写,需要使用头文件stdio.h

文件流的概念

  • 在c语言对文件的操作最终转化为对缓存中的数据进行操作,流实际上就是内存中的一种具有自动顺序操作的特殊数据,流如同流动的河水,有它的源和目的地。
  • 流根据方向可分为输入流和输出流

    • 输入流

      • 从文件读取数据的流
    • 输出流

      • 将数据输出到文件的流
  • 流根据数据内容可分为文本流和二进制流

    • 文本流

      • 二进制流就是流动着的字符序列
    • 二进制流

      • 二进制流就是流动着的二进制序列
  • 三个标准流

    • 标准输入流stdin(用数字0表示):针对标准输入,键盘
    • 标准输出流stdout(用数字1表示):针对标准输出,屏幕
    • 标准错误流stderr(用数字2表示):针对标准输出,屏幕
  • 上述所有的流都统称为文件流

文件类型结构体FILE和文件指针

  • 文件类型结构体FILE

    • 缓冲文件系统为每个正使用的文件在内存开辟文件信息区
    • 文件信息用系统定义的名为FILE的结构体描述
    • FILE定义在stdio.h
  • 文件指针的定义
    FILE *指针变量名

    • 文件指针实际上是一个文件类型结构体指针
    • 通过文件指针即可找到存放某个文件信息的文件类型结构体变量,然后按结构体变量提供的信息找到该文件,实施对文件的操作
    • 习惯上也笼统地把文件指针称为指向一个文件的指针或流指针
    • 当文件打开时,系统会自动创建文件类型结构体变量,并把指向它的指针返回回来,程序通过这个指针获得文件信息并访问文件
    • 文件关闭后,文件指针指向的结构体变量会被释放

文件打开

FILE *fopen(char *filename,char *mode);

  • 功能:按指定方式打开文件
  • 参数

    • filename:为打开的文件路径(相对路径或绝对路径)
    • mode:使用文件的方式
  • 返回:正常打开,返回文件指针;打开失败,返回NULL
  • 标准输入,标准输出和标准错误是由系统打开的,可直接使用
  • 示例
FILE *fp;
fp =fopen("test.txt","w");

使用文件的方式

文件使用方式含义
rt只读打开一个文本文件,只允许读数据
wt只写打开或建立一个文本文件,只允许写数据
at追加打开一个文本文件,并在末尾写数据
rb只读打开一个二进制文件,只允许写数据
wb只写打开或建立一个二进制文件,只允许写数据
ab追加打开一个二进制文件,并在末尾写数据
rt+读写打开一个文本文件,允许读和写
wt+读写打开或建立一个文本文件,允许读和写
at+读写打开一个文本文件,允许读或在末尾追加数据
rb+读写打开一个二进制文件,允许读和写
wb+读写打开或建立一个二进制文件,允许读和写
ab+读写打开一个二进制文件,允许读或在末尾追加数据

符号说明

  • r(read):读
  • w(write):写
  • a(append):追加
  • t(text):文本文件,可以省略不写
  • b(binary):二进制文件
  • +:读和写

文件使用的处理方式

mode处理方式当文件不存在当文件存在写文件读文件
r读取出错打开文件不能
w写入建立新文件覆盖原有文件不能
a追加建立新文件在原有文件后追加不能
r+读取/写入出错打开文件
w+读取/写入建立新文件覆盖原有文件
a+读取/写入建立新文件在原有文件后追加

如果是二进制文件,在使用时只要在模式后添加字符b即可,如rb,rb+分别表示读取二进制文件和以读取/写入方式打开二进制文件


文件关闭

int fclose(FILE *fp);

  • 功能:关闭fp指向的文件,释放文件类型结构体和文件指针。
  • 参数:fp打开文件时返回的文件指针
  • 返回:成功返回0,失败返回-1
  • 注意点:不关闭文件可能会丢失数据

    • 向文件写数据时,是先将数据输出到缓冲区,待缓冲区充满后才正式输出给文件。如果当数据未充满缓冲区而程序结束允许,就会将缓冲区的数据丢失
    • fclose先把缓冲区的数据输出到磁盘文件(刷新缓存),然后才会释放文件类型结构体和文件指针

测试文件读写位置

int ftell(FILE *fp);

  • 功能:测试当前文件的读写位置
  • 返回:测试成功返回文件位置指针所在的位置(当前读写位置距离文件开头的字节数),失败则返回-1

常用的标准IO函数

getchar

int getchar();

  • 功能:从标准输入读取一个字符
  • 返回:成功返回读取的字符,否则返回EOF

putchar

int putchar();

  • 功能:将一字符ch写入到标准输出

fgetc

int fgetc(FILE *fp);

  • 功能:从fp指向的文件中读取一个字符
  • 返回:成功返回读取的字符,否则返回EOF(-1)
  • 可针对标准输入操作

fputc

int fputc(int ch,FILE *fp);

  • 功能:把一字符ch写入fp指向的文件中
  • 返回:成功返回写入的字符ch,失败返回EOF
  • 可针对标准输入输出操作

ungetc

int ungetc(int c,FILE *fp);

  • 功能:撤销一个字符

fgets

char *fgets(char *str,int siez,FILE *fp);

  • 功能:从fp指向的文件中至多读size - 1个字符,放到str指向的字符数组中,如果在读入size - 1个字符结束前遇到换行符或EOF,读入即结束,字符串读入后在最后加一个\0
  • 返回:成功返回str字符串指针,失败返回NULL
  • 可针对标准输入操作

fputs

int fputs(char *str,FILE *fp);

  • 功能:把str指向的字符串或字符数组写入fp指向的文件中
  • 返回:成功返回0,出错返回EOF
  • 可针对标准输出操作

fscanf和fprintf

int fscanf(FILE *fp,const char *format,...);
int fprintf(FILE *fp,const char *format,...);

  • 功能:按format格式对fp指向的文件进行IO操作
  • 返回:成功返回IO字节数,失败或到文件末尾返回EOF
  • 可针对标准输入和输出操作
  • 示例
#include <stdio.h>
#include <string.h>
int main(int argc,char *argv[])
{
        if(argc < 2)
  6         {
                fprintf(stdout,"parameter error\n");
                return 1;
        }   
        FILE *fp1 = fopen("/etc/passwd","r");
        FILE *fp2 = fopen(argv[1],"r");
        char str[1024] = {'\0'},name[20] = {'\0'},passwd[20] = {'\0'},description[20] = {'\0'},home[20] = {'\0'},bash[20] = {'\0'}; 
        int uid = 0,gid = 0;
        // 这里读取之后,整行的内容都在name中
        int flag = fscanf(fp1,"%[^:]:%[^:]:%d:%d:%[^:]:%[^:]:%[^\n]\n",name,passwd,&uid,&gid,description,home,bash); 
        while((flag = fscanf(fp1,"%[^:]:%[^:]:%d:%d:%[^:]:%[^:]:%[^\n]\n",name,passwd,&uid,&gid,description,home,bash))!= EOF)
        {   
                printf("%d\n",flag);                fprintf(stdout,"name=%s;passwd=%s;uid=%d;gid=%d;description=%s;home=%s;bash=%s\n",name,passwd,uid,gid,description,home,bash);
                // description位置可能会为空,为空的话,文件指针的位置不会移动到下一行,这个需要处理下
                if(0 == flag)
                {   
                        fscanf(fp1,"%*[^\n]\n");
                        // 清空description的数据
                        memset(description,'\0',sizeof(description));
                        //break;
                }           
        }           
        return 0;   
}

sscanf

int sscanf(char const *str,char const *format,...);
int sprintf(char const *str,char const *format,...);
  • 功能:按format格式对str指向的字符数组进行IO操作
  • 返回:成功返回I/O字节数,失败返回EOF
  • 实例
#include <stdio.h>
int main(void)
{
        char str1[20] = "666 jin";
        int id = 0;
        char name[10] = {'\0'};
        // 这里不字符串666转换成了数字
        sscanf(str1,"%d %s",&id,name);
        printf("id = %d,name = %s\n",id,name);
        // 不数字666转换成字符串666
        char number[5] = {'\0'};
        sprintf(number,"%d",id);
        printf("number = %s\n",number);
        return 0;
}

fread和fwrite

int fread(void *buffer,int num_bytes,int count,FILE *fp);
int fwrite(void *buffer,int num_bytes,int count,FILE *fp);
  • 功能:读写数据块,一般用于二进制文件的输入输出
  • 返回:成功返回读写的元素个数,失败或到文件末尾返回EOF
  • 参数:

    • buffer:一个指向要进行输入输出数据存储区的通用指针
    • num_bytes:每个要读写的元素的字节数
    • count:要读写的元素个数
    • fp:要读写的文件指针
  • fread和fwrite函数的使用注意点

    • fprintffscanf函数对磁盘读写,使用方便,容易理解,但由于在输入时要将ASCII码转换成二进制形式,在输出时又要将二进制形式转换成字符,花费时间较多。
    • 因此,在内存与磁盘频繁交换数据的情况下,最好不要用fprintffscanf函数,而用freadfwrite函数
  • 实例
#include <stdio.h>
struct User
{
        int id;
        char name[20];
};
int main(int argc,char *argv[])
{
        if(argc < 2)
        {
                printf("parameter error\n");
                return 1;
        }
        FILE *fp1 = fopen("/etc/passwd","r");
        FILE *fp2 = fopen(argv[1],"wb");
        if(fp1 == NULL || fp2 == NULL)
        {       
                printf("parameter error\n");
                return 1;
        }
        struct User user;
        while(EOF != fscanf(fp1,"%[^:]:%*[^:]:%d:%*[^\n]\n",user.name,&user.id))
        {
                if(EOF == fwrite(&user,sizeof(struct User),1,fp2))
                {
                        printf("fail to write file\n");
                        return 1;
                }
                //printf("uid = %i;name = %s\n",user.id,user.name);
        }
        fclose(fp1);
        fclose(fp2);
        fp2 = fopen(argv[1],"rb");
        /* 利用返回值判断是否到达文件尾
        while(0 != fread(&user,sizeof(struct User),1,fp2))
        {
                printf("uid = %i;name = %s\n",user.id,user.name);
        }
        */
        fread(&user,sizeof(struct User),1,fp2);
        // 使用feof判断是否是文件结尾
        while(!feof(fp2))
        {
                printf("uid = %i;name = %s\n",user.id,user.name);
                fread(&user,sizeof(struct User),1,fp2);
        }

        return 0;
}

fseek函数

int fseek(FILE *fp,long offset,int whence);

  • 功能:使fp所指文件的位置指针重置到指定位置(从whence位置移动offset个字节)
  • 返回:成功返回0,失败返回-1
  • offset:位移量,表示移动的字节数
  • whence:

    • SEEK_SET:文件首 0 offset非负
    • SEEK_CUR:文件当前读写位置 1 offset可正可负
    • SEEK_END:文件尾 2 offset可正可负

rewind函数

void rewind(FILE *fp);

  • 功能:使文件位置指针重新返回文件首

remove函数

int remove(const char *filename);

  • 功能:删除指定的文件
  • 返回:成功返回0,失败返回-1

fflush函数

void fflush(FILE *fp);

  • 功能:刷新缓冲区。如果打开文件进行读操作,该函数将清空文件的输入输出缓冲区,如果打开文件进行写操作时,该函数将文件的输出缓冲区内容写入文件中

c预处理操作符,预定义宏和其他指令

预处理操作符

c语言中有两个预处理操作符###,他们可以在#define中使用
操作符#通常称为字符串化的操作符,它把其后的串变成用双引号包围的串
连接操作符##可以把两个独立的字符串连接成一个字符串
示例

#define PRINT(FORMAT,VALUE) printf("the value of"#VALUE"is"FORMAT"\n",VALUE)
#define ADD_TO_SUM(sum_number,value) sum##sum_number+=value

案例
源码

#define PRINT(FORMAT,VALUE) printf("the value of "#VALUE" is "FORMAT"\n",VALUE)
#define ADD_TO_SUM(sum_number,value) sum##sum_number+=value
int main(void)
{
        PRINT("%d",888);
        int sum222 = 222;
        ADD_TO_SUM(222,666);
        PRINT("%d",sum222);
        return 0;
}

预处理后的代码

int main(void)
{
 printf("the value of ""888"" is ""%d""\n",888);
 int sum222 = 222;
 sum222+=666;
 printf("the value of ""sum222"" is ""%d""\n",sum222);
 return 0;
}

预定义宏

  • __FILE__进行编译的文件名
  • __LINE__文件当前行的行号
  • __DATE__文件被编译的日期(格式为"Mmm dd yyyy")
  • __TIME__文件被编译的时间(格式为"hh:mm:ss")
  • __func__当前所在函数名

其他预定义指令

  • #error
  • #line
  • #pragma

示例

#include <stdio.h>
/*
// 下面这句会中断程序的编译
// 与 __LINE__ 配合使用,设置了这个之后,下面的 __LINE__ 会输出1003 
#error "this is a test!!"*/
#line 1000
// 在编译时会输出下面的信息
#pragma message("this is a pragma message")
int main(void)
{
        printf("__FILE__ = %s\n",__FILE__);
        printf("__LINE__ = %d\n",__LINE__);
        printf("__DATE__ = %s\n",__DATE__);
        printf("__TIME__ = %s\n",__TIME__);
        printf("__func__ = %s\n",__func__);
}

c文件包含

文件包含时c预处理程序的另一个重要功能,被包含的文件名字必须要用双引号或一对尖括号括起来

文件包含的语法

#include <文件名>
#include "文件名"
  • 功能:一个源文件可将另一个源文件的内容全部包含进来,从而把指定的文件和当前的源程序文件连城一个源文件。
  • 处理过程:在预处理时,用被包含文件的内容替换该包含指令,再对包含后的文件作一个源文件编译
  • 一般而言,若调用标准库函数用#include <文件名>,若要包含用户自己编写的文件用#include "文件名"
  • 一个include指令只能指定一个被包含文件,允许潜逃包含。被包含的文件可以是源文件(.c)或者头文件(.h)

文件包含的搜索模式

#include <文件名>
若指定文件目录(如gcc -I选项指定的目录)则从此目录中找,否则按标准方式查找。
标准方式:从系统标准文件所在中寻找要包含的文件
#include "文件名"
先从存放c源文件的目录中查找,然后若用户指定目录(如gcc -I选项指定的目录),再从此目录中寻找要包含的文件,若找不到再按标准方式查找

文件包含的作用

  • 一个大的程序可以分为多个模块,由多个程序猿分别编程。有些公用的符号常量,结构体声明或宏定义等可单独组成一个文件,在其它文件的开头用包含指令包含该文件即可使用。
  • 可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少出错

多重包含

  • 同一个文件被多次包含称为多重包含
  • 多重包含可能会出现重复定义的编译错误
  • 为了防止多重包含可使用条件编译,注意条件编译只适用于一个文件中
#ifndef __HEADERNAME_H__
#define __HEADERNAME_H__ 1
#include "headername.h"
#endif

c条件编译

  • 一般情况下,源程序中所有的行都进行编译,但是有时希望对其中一部分内容在满足一定条件下才进行编译,也就是对一部分内容指定编译的条件,这就是条件编译。
  • 条件编译可以指定代码的一部分是被正常编译还是被完全忽略
  • 条件编译有利于提升程序的可移植性,增加程序的灵活性

条件编译语法一

#ifdef 标识符(宏名) //或#if defined(标识符)
      程序段1
#else
      程序段2
#endif

当所指定的宏已经被#define指令定义过,则在程序编译阶段编译程序段一,否则编译程序段二,#else可以没有

条件编译语法二

#ifdef 标识符(宏名) //或#if !defined(标识符)
      程序段1
#else
      程序段2
#endif

当宏未被#define指令定义过则编译程序段1,否则编译程序段2

条件编译语法三

#if 常量表达式
      程序段1
#else
      程序段2
#endif

或者

#if(常量表达式1)
      程序段1
#elif(常量表达式2)
      程序段2
#else
      程序段3
#endif

当指定的常量表达式值为真(非0)时就编译程序段1,否则编译程序段2