HyJune的专栏

Linux From Scratch

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  10 随笔 :: 11 文章 :: 6 评论 :: 0 Trackbacks
from: http://blog.csdn.net/wuliming_sc/archive/2006/11/04/1365923.aspx

1. 什么是标准预定义宏?
2. 连接运算符“##”和字符串化运算符"#"有什么作用?
3. 怎样删去字符串尾部的空格?
4. 怎样删去字符串头部的空格?
5. 怎样打印字符串的一部分?
6. 用malloc()函数更好还是用calloc()函数更好?
7. NULL和NUL有什么不同?
8. 在程序退出main()函数之后,还有可能执行一部分代码吗?
9. 数组作为函数的常数时,可以通过sizeof运算符得到函数数组的大小吗?
10. array_name和&array_name有什么不同?
11. 怎样判断一个字符是数字、字母或其它类别的符号?

什么是标准预定义宏?

ANSIC标准定义了以下6种可供C语言使用的预定义宏:
----------------------------------------------------------------------------
   宏  名                     作    用
----------------------------------------------------------------------------
  __LINE__           在源代码中插入当前源代码行号
  __FILE__           在源代码中插入当前源代码文件名
  __DATE__           在源代码中插入当前编译日期〔注意和当前系统日期区别开来〕
  __TIME__           在源代码中插入当前编译时间〔注意和当前系统时间区别开来〕 
  __STDC__           当要求程序严格遵循ANSIC标准时该标识符被赋值为1。
----------------------------------------------------------------------------

标识符__LINE__和__FILE__通常用来调试程序;标识符__DATE__和__TIME__通常用来在编译后的程序中加入一个时间标志,以区分程序的不同版本;当要求程序严格遵循ANSIC标准时,标识符__STDC__就会被赋值为1;当用C++编译程序编译时,标识符__cplusplus就会被定义。
 1 #include <stdio.h>
 2 
 3 int main ()
 4 {
 5     printf("该输出行在源程序中的位置:%d\n", __LINE__ );
 6     printf("该程序的文件名为:%s\n", __FILE__ );
 7     printf("当前日期为:%s\n", __DATE__ );
 8     printf("当前时间为:%s\n", __TIME__ );
 9 
10     return 0;
11 }

连接运算符“##”和字符串化运算符"#"有什么作用?

连接运算符“##”可以把两个独立的字符串连接成一个字符串。在C的宏中,经常用到“##”运算符,请看下例:
 1 #include <stdio.h>
 2 
 3 int main ()
 4 {
 5     printf("该输出行在源程序中的位置:%d\n", __LINE__ );
 6     printf("该程序的文件名为:%s\n", __FILE__ );
 7     printf("当前日期为:%s\n", __DATE__ );
 8     printf("当前时间为:%s\n", __TIME__ );
 9 
10     return 0;
11 }

在该例中,宏SORT利用“##”运算符把字符串sort_function和经参数x传递过来的字符串连接起来,这意味着语句 SORT(3)(array,elemnts,element_size) 将被预处理程序转换为语句:
    sort_function3(array,elements,element_size);

从宏SORT的用法中你可以看出,如果在运行时才能确定要调用哪个函数,你可以利用“##”运算符动态地构造要调用的函数的名称。
 1 
 2 #include<stdio.h>
 3 
 4 #define FUNC(X)  func ## X
 5 
 6 int func1(int a, int b)
 7 {
 8     return 1;
 9 }
10 
11 int func2(int a, int b)
12 {
13     return 2;
14 
15 
16 int main()
17 {
18     int  a, b, result;
19     result = FUNC(1)(a, b);
20     printf("---%d---\n", result);
21 
22     result = FUNC(2)(a, b);
23     printf("---%d---\n", result);
24 
25     getchar();
26 
27     return 0;
28 }
字符串化运算符"#"运算符能将宏的参数转换为带双引号的字符串,请看下例:

    define DEBUG_VALUE(v)  printf(#v"is equal to %d. \n", v)

    你可以在程序中用 DEBUG_VALUE 宏检查变量的值,请看下例:

    int x=20;

    DEBUG_VALUE(x);

上述语句将在屏幕上打印"x is equal to 20"。这个例子说明,宏所使用的“#”运算符是一种非常方便的调试工具。
 1 #include <stdio.h>
 2 
 3 #define DEBUG_VALUE_INT(v)  printf(#v" is equal to %d.\n",v )
 4 
 5 #define DEBUG_VALUE_STR(v)  printf(#v" is equal to %s.\n",v )
 6 
 7 int main(int)
 8 {
 9       int x = 0;
10       char str[] = "asdfqweqwfdvasdf";
11 
12       DEBUG_VALUE_INT(x);
13 
14       DEBUG_VALUE_STR(str);
15 
16       getchar();
17 
18       return 0;
19 }
20 

怎样删去字符串尾部的空格?

C语言没有提供可删去字符串尾部空格的标准库函数,但是,编写这样的一个函数是很方便的。请看下例:
 1 #include <stdio.h>
 2 # include <string.h>
 3 
 4 char * rtrim( char * );
 5 
 6 int main()
 7 {
 8 
 9 //char * trail_str = "0123456789          "; 把字符串定义成这种形式时,运行程序的时候会出现异常
10 
11 char trail_str[21= "0123456789          ";
12 
13     printf( "Before calling rtrim(), trail_str is '%s'\n" , trail_str );
14     printf( "and has a length of %d. \n" , strlen( trail_str ) );
15 
16     rtrim(trail_str);
17 
18     printf( "After calling rttim(), trail_ str is '%s'\n", trail_str );
19     printf( "and has a length of %d. \n" , strlen( trail_str ) ) ;
20 
21     getchar();
22 
23     return 0;
24 }

 1 /* The rtrim() function removes trailing spaces from a string. */
 2 
 3 char * rtrim( char * str )
 4 {
 5     int n = strlen(str) - 1;
 6 
 7     while( n > 0 )
 8     {
 9         if*( str + n ) != ' ' )
10         {
11             //在windows下,将str字符串的某一位设置为值 '\0' 时,会出现异常..
12             *( str + n + 1 ) = '\0';
13             break ;
14         } else {
15             printf("%c, %d\n", str[n], str[n] );
16             n--;
17         }
18     }
19 
20     return str; 
21 }
在上例中,rtrim()是用户编写的一个函数,它可以删去字符串尾部的空格。函数rtrim()从字符串中位于null字符前的那个字符开始往回检查每个字符,当遇到第一个不是空格的字符时,就将该字符后面的字符替换为null字符。因为在C语言中null字符是字符串的结束标志,所以函数rtrim()的作用实际上就是删去字符串尾部的所有空格。

怎样删去字符串头部的空格?

C语言没有提供可删去字符串头部空格的标准库函数,但是,编写这样的一个函数是很方便的。请看下例:

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 char *ltrim(char * );
 5 char *rtrim(char * );
 6 
 7 void main (void)
 8 {
 9 
10 }
11 
12 char * ltrim(char * str)
13 {
14 
15     strrev(str);    /* Call strrev to reverse the string. */
16     rtrim(str);     /* Call rtrim to remvoe the "trailing" spaces. */
17 
18     strrev(str);    /* Restore the string's original order. */
19 
20     return str ;    /* Return a pointer to the string. */
21 }
22 
23 char* rtrim(char* str)
24 {
25 
26 }
在上例中,删去字符串头部空格的工作是由用户编写的ltrim()函数完成的,该函数调用了6.2的例子中的rtrim()函数和标准C库函数strrev()。ltrim()函数首先调用strrev()函数将字符串颠倒一次,然后调用rtrim()函数删去字符串尾部的空格,最后调用strrev()函数将字符串再颠倒一次,其结果实际上就是删去原字符串头部的空格。

怎样打印字符串的一部分?

使用printf()函数打印字符串的任意部分,请看下例:
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 
 5 int main()
 6 
 7 {
 8 
 9     char * source_str = "THIS IS THE SOURCE STRING" ;
10 
11     /* Use printfO to print the first 11 characters of source_str. */
12     printf("First 11 characters: ' %11.11s'\n" , source_str);
13 
14     /* Use printf() to print only the last 13 characters of source _str. */
15     printf("Last 13 characters:'%13.13s'\n", source_str+(strlen(source_str)-13));
16 }
17 

    输出结果为:  
    First 11 characters: 'THIS IS THE'  
    Last 13 characters:'SOURCE STRING'

在上例中,第一次调用printf()函数时,通过指定参数"%11.11s",迫使printf()函数只打印11个字符的长度,因为源字符串的长度大于11个字符,所以在打印时源字符串将被截掉一部分,只有头11个字符被打印出来。第二次调用printf()函数时,它将源字符串的最后13个字符打印出来,其实现过程为:
(1)用strlen()函数计算出source_str字符串的长度,即strlen(source_str)。
(2)将source_str的长度减去13(13是将要打印的字符数),得出source_str中剩余字符数,且pstrlen(source_str)-13。
(3)将strlen(source_str)-13和source_str的地址相加,得出指向source_str中倒数第13个字符的地址的指针;即source_str+(strlen(source_str)-13)。这个指针就是printf()函数的第二个参数。
(4)通过指定参数“%13.13s”,迫使printf()函数只打印13个字符的长度,其结果实际上就是打印源字符串的最后13个字符。

用malloc()函数更好还是用calloc()函数更好?

函数malloc()和calloc()都可以用来分配动态内存空间,但两者稍有区别。malloc()函数有一个参数,即要分配的内存空间的大小:  
    void *malloc(size_t size);  
calloc()函数有两个参数,分别为元素的数目和每个元素的大小,两个参数的乘积就是要分配的空间的大小:  
    void *calloc(size_t numElements,size_t sizeOfElement);
如果调用成功,函数malloc()和calloc()都将返回所分配的内存空间的首地址。
malloc()函数和calloc()函数的主要区别是前者不能初始化所分配的内存空间,而后者能。如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之,如果这部分内存空间曾经被分配、释放和重新分配,则其中可能遗留各种各样的数据。也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常运行,但经过一段时间后(内存空间已被重新分配)可能会出现问题。

calloc()函数会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为零;如果你是为指针类型的元素分配内存,那么这些元素通常(但无法保证)会被初始化为空指针;如果你是为实数类型的元素分配内存,那么这些元素可能(只在某些计算机中)会被初始化为浮点型的零。

malloc()函数和calloc()函数的另一点区别是calloc()函数会返回一个由某种对象组成的数组,但malloc()函数只返回一个对象。为了明确是为一个数组分配内存空间,有些程序员会选用calloc()函数。但是,除了是否初始化所分配的内存空间这一点之外,绝大多数程序员认为以下两种函数调用方式没有区别:
    calloc( numElements,sizeOfElement );
    malloc( numElements * sizeOfElement );

需要解释的一点是,理论上(按照ANSIC标准)指针的算术运算只能在一个指定的数组中进行,但是在实践中,即使C编译程序或翻译器遵循这种规定,许多C程序还是冲破了这种限制。因此,尽管malloc()函数并不能返回一个数组,它所分配的内存空间仍然能供一个数组使用(对realloc()函数来说同样如此,尽管它也不能返回一个数组)。总之,当你在calloc()函数和malloc()函数之间作选择时,你只需考虑是否要初始化所分配的内存空间,而不用考虑函数是否能返回一个数组。

NULL和NUL有什么不同?

NULL是在<stddef.h>头文件中专门为空指针定义的一个宏。NUL是ASCII字符集中第一个字符的名称,它对应于一个零值。C语言中没有NUL这样的预定义宏。注意:在ASCII字符集中,数字0对应于十进制值80,不要把数字0和'\0'(NUL)的值混同起来。

NULL可以被定义为(void *)0,而NUL可以被定义为'\0'。NULL和NUL都可以被简单地定义为0,这时它们是等价的,可以互换使用,但这是一种不可取的方式。为了使程序读起来更清晰,维护起来更容易,你在程序中应该明确地将NULL定义为指针类型,而将NUL定义为字符类型。
 
在程序退出main()函数之后,还有可能执行一部分代码吗?

可以,但这要借助C库函数atexit()。利用atexit()函数可以在程序终止前完成一些“清理”工作——如果将指向一组函数的指针传递给atexit()函数,那么在程序退出main()函数后(此时程序还未终止)就能自动调用这组函数。在使用atexit()函数时你要注意这样两点:

第一: 由atexit()函数指定的要在程序终止前执行的函数要用关键字void说明,并且不能带参数;

第二: 由atexit()函数指定的函数在入栈时的顺序和调用atexit()函数的顺序相反,即它们在执行时遵循后进先出(LIFO)的原则。

 1 #include<stdlib.h>
 2 #include<stdio.h>
 3 
 4 void my_exit1(void)
 5 {
 6     printf("my_exit1() function !\n");
 7 }              
 8 
 9 void my_exit2(void)
10 {
11     printf("my_exit2() function !\n");
12 }
13 
14 
15 int  main()
16 {
18     atexit ( my_exit1 );
19     atexit ( my_exit2 );
20     printf("now, eixt this program\n");
21     exit(0);
22 }
输出结果为:
now, eixt this program...
my_exit2() function !
my_exit1() function !

数组作为函数的常数时,可以通过sizeof运算符得到函数数组的大小吗?

不可以。当把数组作为函数的参数时,你无法在程序运行时通过数组参数本身告诉函数该数组的大小,因为函数的数组参数相当于指向该数组第一个元素的指针。这意味着把数组传递给函数的效率非常高,也意味着程序员必须通过某种机制告诉函数数组参数的大小。为了告诉函数数组参数的大小,人们通常采用以下两种方法:

第一种方法是将数组和表示数组大小的值一起传递给函数,例如memcpy()函数就是这样做的:
    memcpy( dest,source,length );

第二种方法是引入某种规则来结束一个数组,例如在C语言中字符串总是以ASCII字符NUL('\0')结束,而一个指针数组总是以空指针结束。请看下述函数,它的参数是一个以空指针结束的字符指针数组,这个空指针告诉该函数什么时候停止工作:
1 void printMany( char *strings[] )  
2     {
3         int i = 0
4         while( strings[i] != NULL )
5         {
6             puts(strings[i]);
7             ++i;
8         }
9     }
正象9.5中所说的那样,C程序员经常用指针来代替数组下标,因此大多数C程序员通常会将上述函数编写得更隐蔽一些:
1 void printMany( char *strings[] )
2     {
3         while*strings )
4         {
5             puts(*strings++);
6         }
7     }
尽管你不能改变一个数组名的值,但是strings是一个数组参数,相当于一个指针,因此可以对它进行自增运算,并且可以在调用puts()函数时对strings进行自增运算 [注意辨别这种情况:对“int array[10] 数组的array不能进行自加运算”]。

array_name和&array_name有什么不同?

前者是指向数组中第一个元素的指针,后者是指向整个数组的指针。注意: 笔者建议读者读到这里时暂时放下本书,写一下指向一个含MAX个元素的字符数组的指针变量的说明。希望你不要敷衍了事,因为只有这样你才能真正了解C语言表示复杂指针的句法的奥秘。下文将介绍如何获得指向整个数组的指针。

数组是一种类型,它有三个要素,即基本类型(数组元素的类型),大小(当数组被说明为不完整类型时除外),数组的值(整个数组的值)。你可以用一个指针指向整个数组的值:
    char a[MAX];    /*array of MAX characters*/
    char *p = a;   
    char *pa = &a;
在运行了上述这段代码后,你就会发现p和pa的打印结果是一个相同的值,即p和pa指向同一个地址。但是,p和pa指向的对象是不同的。

上述定义和以下定义是相同的,它们的含义都是“ap是一个含MAX个字符指针的数组”: char *ap[MAX].

以下这种定义并不能获得一个指向整个数组的值的指针:char *(ap[MAX]).

 
怎样判断一个字符是数字、字母或其它类别的符号?

在头文件ctype.h中定义了一批函数,它们可用来判断一个字符属于哪一类别。下面列出了这些函数:
---------------------------------------------------------------------------------------
   函数         字符类别               返回非零值的字符
---------------------------------------------------------------------------------------
  isdigit()     十进制数               0--9
  isxdigit()    十六进制数             0--9,a—f,或A--F
  isalnum()     字母数字符号           0--9,a--Z,或A--Z
  isalpha()     字母                   a—z或A--Z
  islower()     小写字母               a—z
  isupper()     大写字母               A--Z
  isspace()     空白符                 空格符,水平制表符,垂直制表符,换行符,换页符,或回车符
  isgraph()     非空白字符             任何打印出来不是空白的字符(ASCII码从21到7E)
  isprint()     可打印字符             所有非空白字符,加上空格符
  ispunct()     标点符                 除字母数字符号以外的所有非空白字符
  iscntrl()     控制字符               除可打印字符外的所有字符(ASCII码从00到1F,加上7F)
----------------------------------------------------------------------------------------

与前文提到过的使用标准库函数的好处相似,调用上述这些宏而不是自己编写测试字符类别的程序也有三点好处。首先,这些宏运算速度快,因为它们的实现方式通常都是利用位屏蔽技术来检查一个表,所以即使是进行一项相当复杂的检查,也比真正去比较字符的值要快得多。其次,这些宏都是正确的。如果你自己编写一个测试程序,你很容易犯逻辑上或输入上的错误,例如引入了一个错误的字符(或漏掉了一个正确的字符)。第三,这些宏是可移植的。信不信由你,并非所有的人都使用同样的含PC扩充字符的ASCII字符集。也许今天你还不太在意,但是,当你发现你的下一台计算机使用的是Unicode字符集而不是ASCII字符集,你就会庆幸自己原来没有按照字符集中的字符值来编写程序。


其他字符转换函数:

isascii(测试字符是否为ASCII 码字符)

int isascii(int c);

检查参数c是否为ASCII码字符,也就是判断c的范围是否在0到127之间。若参数c为ASCII码字符,则返回TRUE,否则返回NULL(0)。

toascii(将整型数转换成合法的ASCII 码字符)

int toascii(int c);

toascii()会将参数c转换成7位的unsigned char值,第八位则会被清除,此字符即会被转成ASCII码字符。将转换成功的ASCII码字符值返回。

tolower(将大写字母转换成小写字母)

int tolower(int c);

若参数c为大写字母则将该对应的小写字母返回。返回转换后的小写字母,若不须转换则将参数c值返回。

toupper(将小写字母转换成大写字母)

int toupper(int c);

若参数c为小写字母则将该对映的大写字母返回。返回转换后的大写字母,若不须转换则将参数c值返回。
 
以isalmun为例,说明这些函数的用法[剩余的其他函数跟它类似]:
int isalnum ( int c )

检查参数c是否为英文字母或阿拉伯数字,若参数c为字母或数字,则返回TRUE,否则返回NULL。此为宏定义,非真正函数。
 1 #include<stdio.h>
 2 #include <ctype.h>
 3 
 4 int main()
 5 {
 6     char str[]="123c@#FDsP[e?";
 7     int i;
 8     for( i = 0; str[i] != 0; i++ )
 9     {
10 
11         if( isalnum( str[i] ) )
12             printf("%c is an alphanumeric character\n", str[i] );
13     }
14 
15 }

posted on 2008-05-22 15:34 martin0501 阅读(520) 评论(0)  编辑 收藏 引用 所属分类: 1. C/C++系列

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理