C 語言疑難雜症

無聊在網上找了些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/n",sizeof(aaa1.i));
    printf("%d/n",sizeof(aaa1.j));
     printf("%d/n",sizeof(aaa1.b));
      printf("%d/n",sizeof(aaa1.a));
    printf("%d/n",sizeof(aaa1));
    
    getch();
}

運行結構是:

4

1

80

8

96

問題出在  char j; 這裏,單獨查看,可以看到 char類型佔用一個 byte,但是爲什麼放都結構體裏面的時候,它就佔用了 4 個byte 呢?難道結構體有對自己的元素類型佔用空間做了強制規定嗎?

在dev-c++ 裏面,調試了一下:

看到的結構體裏面,竟然沒有 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/n", __LINE__ );
    printf("該程序的文件名爲:%s/n", __FILE__ );
    printf("當前日期爲:%s/n", __DATE__ );
    printf("當前時間爲:%s/n", __TIME__ );
    return 0;

}

連接運算符“##”和字符串化運算符"#"有什麼作用?[仔細理解]

連接運算符“##”可以把兩個獨立的字符串連接成一個字符串。在C的宏中,經常用到“##”運算符,請看下例:
    #include<stdio.h>
    #define SORT(X)  sort_function ## X
    void main(vOid)

    void main(vOid)
    {
        char *array

        int  elements
element_size
        SORT(3) (array
elementselement_size)
    }

在該例中,宏SORT利用“##”運算符把字符串sort_function和經參數x傳遞過來的字符串連接起來,這意味着語句 SORT(3)(arrayelemntselement_size) 將被預處理程序轉換爲語句:
    sort_function3(array
elementselement_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---/n", result);
    result = FUNC(2)(a, b);
    printf("---%d---/n", result);
    getchar();
    return 0;

}

符串化運算符"#"運算符能將宏的參數轉換爲帶雙引號的字符串,請看下例:

    define DEBUG_VALUE(v)  printf(#v"is equal to %d. /n", 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./n",v )
#define DEBUG_VALUE_STR(v) printf(#v" is equal to %s./n",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'/n" , trail_str );
    printf( "and has a length of %d. /n" , strlen( trail_str ) );
    rtrim(trail_str);
    printf( "After calling rttim(), trail_ str is '%s'/n", trail_str );
    printf( "and has a length of %d. /n" , 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字符串的某一位設置爲值 '/0' 時,會出現異常..
            *( str + n + 1 ) = '/0'; 
            break ; 
        }
        else 
        {
            printf("%c, %d/n", 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()函數完成的,該函數調用了62的例子中的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'/n" , source_str);
    /* Use printf() to print only the last 13 characters of source _str. */
    printf("Last 13 characters:'%13.13s'/n", 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)-13source_str的地址相加,得出指向source_str中倒數第13個字符的地址的指針;即source_str+(strlen(source_str)-13)。這個指針就是printf()函數的第二個參數。
(4)
通過指定參數1313s”,迫使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()函數之間作選擇時,你只需考慮是否要初始化所分配的內存空間,而不用考慮函數是否能返回一個數組。

 

   

NULLNUL有什麼不同?

NULL是在<stddef.h>頭文件中專門爲空指針定義的一個宏。NULASCII字符集中第一個字符的名稱,它對應於一個零值。C語言中沒有NUL這樣的預定義宏。注意:在ASCII字符集中,數字0對應於十進制值80,不要把數字0'/0'(NUL)的值混同起來。

NULL可以被定義爲(void *)0,而NUL可以被定義爲'/0'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 !/n");
}
void my_exit2(void)
{
    printf("my_exit2() function !/n");
}
int main()
{
    atexit ( my_exit1 );
    atexit ( my_exit2 );
    printf("now, eixt this program.../n");
    exit(0);
}
輸出結果爲:

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

數組作爲函數的常數時,可以通過sizeof運算符得到函數數組的大小嗎?

不可以。當把數組作爲函數的參數時,你無法在程序運行時通過數組參數本身告訴函數該數組的大小,因爲函數的數組參數相當於指向該數組第一個元素的指針。這意味着把數組傳遞給函數的效率非常高,也意味着程序員必須通過某種機制告訴函數數組參數的大小。爲了告訴函數數組參數的大小,人們通常採用以下兩種方法:

第一種方法是將數組和表示數組大小的值一起傳遞給函數,例如memcpy()函數就是這樣做的:
    memcpy( dest
sourcelength );

第二種方法是引入某種規則來結束一個數組,例如在C語言中字符串總是以ASCII字符NUL('/0')結束,而一個指針數組總是以空指針結束。請看下述函數,它的參數是一個以空指針結束的字符指針數組,這個空指針告訴該函數什麼時候停止工作:
    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; 
在運行了上述這段代碼後,你就會發現ppa的打印結果是一個相同的值,即ppa指向同一個地址。但是,ppa指向的對象是不同的。

上述定義和以下定義是相同的,它們的含義都是“ap是一個含MAX個字符指針的數組”: char *ap[MAX].

以下這種定義並不能獲得一個指向整個數組的值的指針:char *(ap[MAX]).

 

 

 

 

 

 

怎樣判斷一個字符是數字、字母或其它類別的符號?

在頭文件ctype.h中定義了一批函數,它們可用來判斷一個字符屬於哪一類別。下面列出了這些函數:
---------------------------------------------------------------------------------------
  
函數         字符類別               返回非零值的字符
---------------------------------------------------------------------------------------
  isdigit()     十進制數               0--9
  isxdigit()   
十六進制數             0--9a—f,或
A--F
  isalnum()     
字母數字符號           0--9a--Z,或
A--Z
  isalpha()     
字母                   a—z
A--Z
  islower()    
小寫字母
               a—z
  isupper()     
大寫字母
               A--Z
  isspace()     
空白符                 空格符,水平製表符,垂直製表符,換行符,換頁符,或回車符

  isgraph()    
非空白字符             任何打印出來不是空白的字符(ASCII碼從217E)
  isprint()    
可打印字符             所有非空白字符,加上空格符

  ispunct()     
標點符                 除字母數字符號以外的所有非空白字符
  iscntrl()     
控制字符               除可打印字符外的所有字符(ASCII碼從001F,加上7F)

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

與前文提到過的使用標準庫函數的好處相似,調用上述這些宏而不是自己編寫測試字符類別的程序也有三點好處。首先,這些宏運算速度快,因爲它們的實現方式通常都是利用位屏蔽技術來檢查一個表,所以即使是進行一項相當複雜的檢查,也比真正去比較字符的值要快得多。其次,這些宏都是正確的。如果你自己編寫一個測試程序,你很容易犯邏輯上或輸入上的錯誤,例如引入了一個錯誤的字符(或漏掉了一個正確的字符)第三,這些宏是可移植的。信不信由你,並非所有的人都使用同樣的含PC擴充字符的ASCII字符集。也許今天你還不太在意,但是,當你發現你的下一臺計算機使用的是Unicode字符集而不是ASCII字符集,你就會慶幸自己原來沒有按照字符集中的字符值來編寫程序。

 

其他字符轉換函數:

isascii(測試字符是否爲ASCII 碼字符) 

int isascii(int c)

檢查參數c是否爲ASCII碼字符,也就是判斷c的範圍是否在0127之間。若參數cASCII碼字符,則返回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/n", str[i] ); 
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章