2011-06-17 53 views
1

我一直在試圖從書"The Practice of Programming"的章節接口中理解C語言中修訂後的csvgetline函數。我已經強調了對我來說沒有意義的代碼。無法理解從K&P書中的csvgetline.c函數中解釋的char **編程實踐

#include "csv.h" 

enum { NOMEM = -2 };   /* out of memory signal */ 
static char* line = NULL; /* input chars */ 
static char* sline = NULL; /* line copy used by split */ 
static int maxline = 0;  /* size of line[] and sline[] */ 
static char** field = NULL; /* field pointers */   // This is the problem! 
static int maxfield = 0;  /* size of field[] */ 
static int nfield = 0;  /* number of fields in field[] */ 

static char fieldsep[] = ","; /* field separator chars */ 
static char* advquoted(char *); 
static int split(void); 

/* endofline: check for and consume \r, \n, \r\n, or EOF */ 
static int endofline(FILE *fin, int c) 
{ … } 

/* reset: set variables back to starting values */ 
static void reset(void) 
{ … } 

/* csvgetline: get one line, grow as needed */ 
/* sample input: "LU",86.25,"11/4/1998","2:19PM",+4.0625 */ 
char* csvgetline(FILE *fin) 
{ 
    int i, c; 
    char *newl, *news; 

    if (line == NULL) {  /* allocate on first call */ 
    maxline = maxfield = 1; 
    line = (char *) malloc(maxline); 
    sline = (char *) malloc(maxline); 
    field = (char **) malloc(maxfield*sizeof(field)); // This is the problem! 

    if (line == NULL || sline == NULL || field == NULL) { 
     reset(); 

     return NULL; /* out of memory */ 
    } 
    } 

    for (i=0; (c=getc(fin))!=EOF && !endofline(fin,c); i++) { 
    if (i >= maxline-1) { /* grow line */ 
     maxline *= 2; /* double current size */ 
     newl = (char *) realloc(line, maxline); 
     if (newl == NULL) { 
     reset(); 
     return NULL; 
     } 

     line = newl; 
     news = (char *) realloc(sline, maxline); 
     if (news == NULL) { 
     reset(); 
     return NULL; 
     } 

     sline = news; 
    } 

    line[i] = c; 
    } 

    line[i] = '\0'; 

    if (split() == NOMEM) { 
    reset(); 
    return NULL;  /* out of memory */ 
    } 

    return (c == EOF && i == 0) ? NULL : line; 
} 


/* split: split line into fields */ 
static int split(void) 
{ 
    char *p, **newf; 
    char *sepp; /* pointer to temporary separator character */ 
    int sepc; /* temporary separator character */ 
    nfield = 0; 
    if (line[0] == '\0') 
    return 0; 

    strcpy(sline, line); 
    p = sline; 

    do { 
    if (nfield >= maxfield) { 
     maxfield *= 2;  /* double current size */ 
     newf = (char **) realloc(field, maxfield * sizeof(field[0])); 
     if (newf == NULL) 
     return NOMEM; 
     field = newf; 
    } 

    if (*p == '"') 
     sepp = advquoted(++p); /* skip initial quote */ 
    else 
     sepp = p + strcspn(p, fieldsep); 

    sepc = sepp[0]; 

    printf("%d", sepp[0]); // Debug 

    sepp[0] = '\0';  /* terminate field */ 
    field[nfield++] = p; 
    p = sepp + 1; 
    } while (sepc == ','); 

    return nfield; 
} 

/* advquoted: quoted field; return pointer to next separator */ 
static char *advquoted(char *p) 
{ … } 

/* csvfield: return pointer to n-th field */ 
char* csvfield(int n) 
{ 
    if (n < 0 || n >= nfield) 
    return NULL; 
    return field[n]; // This is the problem! 
} 

/* csvnfield: return number of fields */ 
int csvnfield(void) 
{ 
    return nfield; 
} 

/* csvtest main: test CSV library */ 
int main(void) 
{ 
    int i; 
    char *line; 

    while ((line = csvgetline(stdin)) != NULL) { 
    printf("line = `%s'\n", line); 
    for (i = 0; i < csvnfield(); i++) 
     printf("field[%d] = `%s'\n", i, csvfield(i)); // This line is a problem 
    } 
    return 0; 
} 

我不能找出什麼是char**字段我相信這是一個指向指向字符串/字符數組/字段的數組。 因此field[n]應該包含指向字符串的指針,即字符串的地址,而不是字符串本身。但是,從printf()聲明中訪問它時,函數csvfield(i)返回值而不是指向第n個字段的指針,這似乎不是這種情況。

我瀏覽過很多試圖瞭解char **的網站,但信息非常非常有限。一個網站說char *是一個指向字符數組的指針,但是char**是指向char的指針。另一個網站談到char **類型是一個標量類型。但我仍然對這char **模糊不清。

請問您能解釋一下嗎?

+1

一本很棒的書,可以閱讀! –

+0

所以......你問爲什麼'printf(「%s \ n」,x);'打印字符串,當'x'是'char *'時?相當肯定這是在書中更早的涵蓋:) –

回答

0

你說得對,char**可以看作是指向字符串/字符數組/字段的指針數組。你是怎麼做的printfcsvfield(i)?如果你使用格式「...%s...」確定它會輸出字符串值;如果您需要指針地址,請使用格式「...%p...」。

+0

因此,而不是上面的printf語句,我使用printf(「field [%d] ='%s'\ n」,i,* field [n])會不會給我同樣的答案? – razi

+0

​​將獲得字段[n]的第一個字符值,並且不能使用%s來打印,而是使用%c。不測試,自己試試。 – cxa

+0

問題中的代碼顯示'printf(「field [%d] ='%s'\ n」,i,csvfield(i));'確實使用'%s'。回答razi的問題,'欺騙'並使用'printf(「field [%d] ='%s'\ n」,i,field [i]);'會得到相同的結果(但是'n'不是在'i'被定義的上下文中定義),並且使用'* field [i]'將嘗試打印單個字符作爲字符串,這將不會很好地工作(但是'printf(「field [%d] [0 ] ='%c'\ n「,i,* field [i]);'或'printf(」field [%d] [0] ='%c'\ n「,i,field [i] [0 ]);'會正常工作)。 –

1

本書中的代碼轉錄中存在一個小問題,然後對char **field;有必要的理解是合理的。

在本書的副本中,您難以理解的第一行與您引用的內容不同。你的報價是第一位的;什麼書上說排第二:

field = (char **) malloc(maxfield*sizeof(field)); 
field = (char **) malloc(maxfield*sizeof(field[0])); 

我放心了,因爲官方的版本是正確的,但首先會偶然有些工作。不同之處在於它爲char **maxfield個副本分配了一定的空間量,而需要的空間則是多個char *值。現在碰巧,sizeof(char **) == sizeof(char *),但最好清楚要問什麼。爲Xyz *類型的指針ptr,這種習慣用法分配N值的數組是:

Xyz *ptr = (Xyz *)malloc(N * sizeof(*ptr)); 

其中*ptrXyz型的,當然,也是相當於ptr[0],書中所用的表示法。現在

,有關char **field ...分配什麼可以用這樣的圖理解:

+-------+  +----------+ 
| field |---->| field[0] | 
+-------+  +----------+ 
       | field[1] | 
       +----------+ 
       | field[2] | 
       +----------+ 
       | field[3] | 
       +----------+ 
        ... 

當它最初被分配,每個field[0]field[1] ...沒有初始值和點行不通的。在代碼中,作爲字段被解析,空間被分配用於每個字段如下:

char **   char *   char 
+-------+  +----------+  +----------------+ 
| field |---->| field[0] |---->| String value 1 | 
+-------+  +----------+  +----------------+ +----------+ 
       | field[1] |-------------------------->| String 2 | 
       +----------+  +--------------+  +----------+ 
       | field[2] |---->| Third String | 
       +----------+  +--------------+ 
       | field[3] | 
       +----------+ 
        ... 

數組的元素是連續的;這些字符串不是連續的。當然,陣列的每個元素都是char *

現在,在csvfield(int n)之內,函數返回NULL或field[n]。從圖中可以看出,值field[n]char *。所以代碼是正確的,並且它返回一個char *作爲函數簽名狀態。 (即使在嚴格的警告水平下,代碼也會乾淨地編譯。)