字串轉換、字元測試


若要將字串轉換為數字,則可以使用 atofatoiatolatoll 等函式,這些函式都包括在 stdlib.h 中:

double    atof( const char* str );
int       atoi( const char *str );
long      atol( const char *str );
long long atoll( const char *str );

atofatoiatolatoll 等函式 會搜尋字串中可以轉換的部份,直到遇到無法轉換的字元,字串開頭可以使用正負號,例如 "+100""-100"atof 可以接受科學記號,例如 "12.3e-5""123E+4",這幾個函式若沒有可轉換的字元則傳回 0,若是轉換結果超出了傳回型態的範圍,傳回值沒有定義,也就是難以檢查錯誤。

C99 有一系列轉換字串的函式,使用起來比較麻煩一些:

long               strtol( const char *restrict str, char **restrict str_end, int base );
long long          strtoll( const char *restrict str, char **restrict str_end, int base );

unsigned long      strtoul( const char *restrict str, char **restrict str_end,int base );
unsigned long long strtoull( const char *restrict str, char **restrict str_end,
                             int base );
float              strtof( const char *restrict str, char **restrict str_end );
double             strtod( const char *restrict str, char **restrict str_end );
long double        strtold( const char *restrict str, char **restrict str_end );

這幾個函式的第一個參數都接受來源字串;第二個參數在函式執行過後,會用來儲存字串中第一個無法剖析為數字的字元位址,如果設定為 NULL,會忽略這個參數;第三個參數用來指定基底,如果設定為 0,從字串中自動偵測基底;函式若沒有可轉換的字串,會傳回 0。

因此最簡單的轉換情況就是當成 atof 的替代品:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    printf("\"1010\"\t二進位:\t%ld\n", strtol("1010", NULL, 2));
    printf("\"12\"\t八進位:\t%ld\n", strtol("12", NULL, 8));
    printf("\"A\"\t十六進位:\t%ld\n", strtol("A", NULL, 16));
    printf("\"012\"\t自動基底:\t%ld\n", strtol("012", NULL, 0));
    printf("\"0xA\"\t自動基底:\t%ld\n", strtol("0xA", NULL, 0));
    printf("\"junk\"\t自動基底:\t%ld\n", strtol("junk", NULL, 0));

  return 0;
}

執行結果如下:

"1010"  二進位:        10
"12"    八進位:        10
"A"     十六進位:      10
"012"   自動基底:      10
"0xA"   自動基底:      10
"junk"  自動基底:      0

若是轉換結果超出了傳回型態的範圍,會將定義在 errno.h 的 errno 設為 ERANGE,並傳回各自傳回型態的最大可容許數值(最大值或最小值),因此,可藉由檢查 errno 來看看轉換是否有誤:

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

int main(void) {
    long i = strtol("99999999999999999999999999999999999999999999999999", NULL, 10);

    if(errno == ERANGE) {
        printf("超出轉換函式範圍");
        errno = 0;
    }
    else {
        printf("%d", i);
    }

    return 0;
}

由於第二個參數在函式執行過後,會用來指向字串中第一個無法剖析為數字的字元,因此若想連續剖析一組數字,數字以某一標點符號區隔,可以如下,這需要認識更多指標的觀念,你可以在後續學習過指標之後,再回頭看看這個範例:

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

int main(void) {
    const char *p = "10,200,3000,-400000";
    char *end;
    for (long i = strtol(p, &end, 10); p != end; i = strtol(p, &end, 10)) {
        printf("\"%.*s\":", (int)(end - p), p);
        p = end + 1;  // 新的字串起點
        if (errno == ERANGE){
            printf("轉換超出範圍");
            errno = 0;
        }

        printf("%ld\n", i);
    }
}

執行結果如下:

"10":10
"200":200
"3000":3000
"-400000":-400000

若要測試字元為數字、字母、大寫、小寫等等,可以使用 ctype.h 中的 isxxxx() 函式,例如:

isalnum(int c):是否為字母或數字
isalpha(int c):是否為字母
iscntrl(int c):是否為控制字元
isdigit(int c):是否為數字
islower(int c):是否為小寫字母
isprint(int c):是否為列印字元
ispunct(int c):是否為標點符號
isspace(int c):是否為空白
isupper(int c):是否為大寫字母
isxdigit(int c):是否為16進位數字
...

這些函式事實上是巨集,可以查看 ctype.h 得知更多的 isxxxx 函式,ctype.h 中也包括了像是可以進行字母大小寫轉換的 tolowertoupper 等函式。