C 语言疑难杂症 [转:http://blog.chinaunix.net/uid-20688544-id-1894880.html]

无聊在网上找了些C语言的东东练一下手,竟然发现其实还有好多细节之前,没注意到,该好好复习一下先。
解决掉的问题先不发出来,把疑问的先做个笔记,过几天解决了就回来修改补上。
 

#include <stdio.h>
struct{
        int i; 
        char j;
        double a;
        int b[20];
}aaa1; 
main()
{
    aaa1.i = 1;
    aaa1.j = 'a';
    aaa1.a = 0;
    printf("%d ",sizeof(aaa1.i));
    printf("%d ",sizeof(aaa1.j));
     printf("%d ",sizeof(aaa1.b));
      printf("%d ",sizeof(aaa1.a));
    printf("%d ",sizeof(aaa1));
    
    getch();

运行结构是:

4

1

80

8

96

问题出在  char j; 这里,单独查看,可以看到 char类型占用一个 byte,但是为什么放都结构体里面的时候,它就占用了 4 个byte 呢?难道结构体有对自己的元素类型占用空间做了强制规定吗?

在dev-c++ 里面,调试了一下:

500)this.width=500;" border="0" src="http://blogimg.chinaunix.net/blog/upfile2/080426204138.jpg">

看到的结构体里面,竟然没有 j 这个元素,取而代之的是 i=97'a' 这又怎么解释呢?

—。—#

//-----------------------------------------

找到这篇文章,哈哈,我想红色字就是我要的答案了:

intarray[3]={35,56,37}; 
int*pa=array;

  通过指针pa访问数组array的三个单元的方法是:

*pa;//访问了第0号单元 
*(pa+1);//访问了第1号单元 
*(pa+2);//访问了第2号单元

  从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。

  所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。

  所以,例中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。

//-----------------------------------------

 

当全局变量跟局部变量有冲突时,局部变量会把全局变量隐藏掉。据说可以用加 :: 的方式来强制访问全局变量,但是我加了这个符号根本就编译不过去,应该是C++ 才有的吧?

 

#include<stdio.h>
int main()
{
    int i=3;
    int j;
    j = (++i) + (++i) + (++i);
    printf("i=%d,j=%d", i ,j);

    getch();
}

 

超简单的代码,可是,运行后 i=6,j=16

怎么回事呢??

 
解答:
1、一个变量在一条语句中出现一次以上,不宜使用自加运算。  
2、自加运算是编译器相关的,不同的编译器得到的结果不同。
3、这里面存在一个符号优化的问题,对于j=(++i)+(++i)+(++i):是先算前两项(++i)+(++i),再和最后一个(++i)相加...

一些常被问到的知识点:

什么是标准预定义宏?

ANSIC标准定义了以下6种可供C语言使用的预定义宏:
----------------------------------------------------------------------------
   
宏  名                     作    用

----------------------------------------------------------------------------
  __LINE__           在源代码中插入当前源代码行号
  __FILE__           在源代码中插入当前源代码文件名
  __DATE__           在源代码中插入当前编译日期〔注意和当前系统日期区别开来〕
  __TIME__           在源代码中插入当前编译时间〔注意和当前系统时间区别开来〕   
  __STDC__           当要求程序严格遵循ANSIC标准时该标识符被赋值为1。

----------------------------------------------------------------------------

标识符__LINE__和__FILE__通常用来调试程序;标识符__DATE__和__TIME__通常用来在编译后的程序中加入一个时间标志,以区分程序的不同版本;当要求程序严格遵循ANSIC标准时,标识符__STDC__就会被赋值为1;当用C++编译程序编译时,标识符__cplusplus就会被定义。

 

#include <stdio.h>
int main ()
{
    printf("该输出行在源程序中的位置:%d ", __LINE__ );
    printf("该程序的文件名为:%s ", __FILE__ );
    printf("当前日期为:%s ", __DATE__ );
    printf("当前时间为:%s ", __TIME__ );
    return 0;

}

连接运算符“##”和字符串化运算符"#"有什么作用?[仔细理解]

连接运算符“##”可以把两个独立的字符串连接成一个字符串。在C的宏中,经常用到“##”运算符,请看下例:
    #include
    #define SORT(X)  sort_function ## X
    void main(vOid)
    void main(vOid)
    {
        char *array
        int  elementselement_size
        SORT(3) (arrayelementselement_size)
    }

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

从宏SORT的用法中你可以看出,如果在运行时才能确定要调用哪个函数,你可以利用“##”运算符动态地构造要调用的函数的名称。

#include<stdio.h>
#define FUNC(X) func ## X
int func1(int a, int b)
{
    return 1;
}
int func2(int a, int b)
{
    return 2;


int main()
{
    int a, b, result;
    result = FUNC(1)(a, b);
    printf("---%d--- ", result);
    result = FUNC(2)(a, b);
    printf("---%d--- ", result);
    getchar();
    return 0;

}

符串化运算符"#"运算符能将宏的参数转换为带双引号的字符串,请看下例:

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

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

    int x20;

    DEBUG_VALUE(x);

上述语句将在屏幕上打印"x is equal to 20"。这个例子说明,宏所使用的“#”运算符是一种非常方便的调试工具。

 

#include <stdio.h>
#define DEBUG_VALUE_INT(v) printf(#v" is equal to %d. ",v )
#define DEBUG_VALUE_STR(v) printf(#v" is equal to %s. ",v )
int main(int)
{
      int x = 0;
      char str[] = "asdfqweqwfdvasdf";
      DEBUG_VALUE_INT(x);
      DEBUG_VALUE_STR(str);
      getchar();
      return 0;

}

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

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

 

#include <stdio.h>
# include <string.h>
char * rtrim( char * );
int main()
{
//char * trail_str = "0123456789 "; 把字符串定义成这种形式时,运行程序的时候会出现异常
char trail_str[21] = "0123456789 ";
    printf( "Before calling rtrim(), trail_str is '%s' " , trail_str );
    printf( "and has a length of %d. " , strlen( trail_str ) );
    rtrim(trail_str);
    printf( "After calling rttim(), trail_ str is '%s' ", trail_str );
    printf( "and has a length of %d. " , strlen( trail_str ) ) ;
    getchar();
    return 0;
}
/* The rtrim() function removes trailing spaces from a string. */
char * rtrim( char * str )
{
    int n = strlen(str) - 1; 
    while( n > 0 ) 
    {
        if( *( str + n ) != ' ' ) 
        {
            //在windows下,将str字符串的某一位设置为值 '' 时,会出现异常..
            *( str + n + 1 ) = ''; 
            break ; 
        }
        else 
        {
            printf("%c, %d ", str[n], str[n] ); 
            n--;
        }
    }
    return str; 
}

 

在上例中,rtrim()是用户编写的一个函数,它可以删去字符串尾部的空格。函数rtrim()从字符串中位于null字符前的那个字符开始往回检查每个字符,当遇到第一个不是空格的字符时,就将该字符后面的字符替换为null字符。因为在C语言中null字符是字符串的结束标志,所以函数rtrim()的作用实际上就是删去字符串尾部的所有空格。

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

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

#include <stdio.h> 
#include <string.h>
char *ltrim(char * ); 
char *rtrim(char * );
void main (void)
{



}
char * ltrim(char * str)
{
    strrev(str); /* Call strrev to reverse the string. */
    rtrim(str); /* Call rtrim to remvoe the "trailing" spaces. */
    strrev(str); /* Restore the string's original order. */
    return str ; /* Return a pointer to the string. */
}
char* rtrim(char* str)
{

}

在上例中,删去字符串头部空格的工作是由用户编写的ltrim()函数完成的,该函数调用了6.2的例子中的rtrim()函数和标准C库函数strrev()。ltrim()函数首先调用strrev()函数将字符串颠倒一次,然后调用rtrim()函数删去字符串尾部的空格,最后调用strrev()函数将字符串再颠倒一次,其结果实际上就是删去原字符串头部的空格。

  

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

使用printf()函数打印字符串的任意部分,请看下例:

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    char * source_str = "THIS IS THE SOURCE STRING" ;
    /* Use printfO to print the first 11 characters of source_str. */
    printf("First 11 characters: ' %11.11s' " , source_str);
    /* Use printf() to print only the last 13 characters of source _str. */
    printf("Last 13 characters:'%13.13s' ", source_str+(strlen(source_str)-13));

}
    输出结果为: 
    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 numElementssize_t sizeOfElement);
如果调用成功,函数malloc()和calloc()都将返回所分配的内存空间的首地址。
malloc()函数和calloc()函数的主要区别是前者不能初始化所分配的内存空间,而后者能
。如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之,如果这部分内存空间曾经被分配、释放和重新分配,则其中可能遗留各种各样的数据。也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常运行,但经过一段时间后(内存空间已被重新分配)可能会出现问题。

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

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

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

 

   

NULL和NUL有什么不同?

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

NULL可以被定义为(void *)0,而NUL可以被定义为''NULLNUL都可以被简单地定义为0,这时它们是等价的,可以互换使用,但这是一种不可取的方式。为了使程序读起来更清晰,维护起来更容易,你在程序中应该明确地将NULL定义为指针类型,而将NUL定义为字符类型。

 

 

  

在程序退出main()函数之后,还有可能执行一部分代码吗?

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

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

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

 

#include<stdlib.h>
#include<stdio.h>
void my_exit1(void)
{
    printf("my_exit1() function ! ");

void my_exit2(void)
{
    printf("my_exit2() function ! ");

int main()
{
    atexit ( my_exit1 );
    atexit ( my_exit2 );
    printf("now, eixt this program... ");
    exit(0);
}
输出结果为:

now, eixt this program...
my_exit2() function !
my_exit1() function !

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

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

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

第二种方法是引入某种规则来结束一个数组,例如在C语言中字符串总是以ASCII字符NUL('')结束,而一个指针数组总是以空指针结束。请看下述函数,它的参数是一个以空指针结束的字符指针数组,这个空指针告诉该函数什么时候停止工作:
    void printMany( char *strings[] )    
    {
        int i = 0;   
        while( strings[i] != NULL )
        {
            puts(strings[i]);
            ++i;
        }
    }

正象9.5中所说的那样,C程序员经常用指针来代替数组下标,因此大多数C程序员通常会将上述函数编写得更隐蔽一些:
    void printMany( char *strings[] )
    {
        while( *strings )
        {
            puts(*strings++)
        }
    }

尽管你不能改变一个数组名的值,但是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。此为宏定义,非真正函数。

#include<stdio.h>
#include <ctype.h>
int main()
{
    char str[]=123c@#FDsP[e?;
    int i;
    for( i = 0; str[i] != 0; i++ )
    {
        if( isalnum( str[i] ) ) 
            printf("%c is an alphanumeric character ", str[i] ); 
    }
}

 

原文地址:https://www.cnblogs.com/woshare/p/4886302.html