1. 程式人生 > >【C/C++】string操作方法彙總如下

【C/C++】string操作方法彙總如下

本文提供【C/C++】string操作方法彙總如下:

原文地址:http://sodino.com/2015/02/04/c-string-operate/

判斷內容是否相同
字串複製
字串拼接
字串拼接單個char
字串類別檢查
字串子串擷取 (substring)
字串界位符切割 (strtok strtok_r)
判斷內容是否相同
#include <string.h>

int strcmp(const char s1, const char s2); Thread-Safe
int strncmp(const char s1, const char s2, size_t n); Thread-Safe
對s1與s2進行的比較。大小寫敏感。
return ==0, s1 == s2;
return >0, s1 > s2;
return <0, s1 < s2;

int memcmp(const void s1, const void s2, size_t n); Thread-safe
注意,這裡的引數是void而不是普通的char *,所以memcmp可以對更廣泛的物件進行對比,如字元陣列,整型,類,結構體等。比較的內容為第三個引數所限定的長度範圍內。

int strcoll(const char s1, const char s2);
比較字串s1和s2。功能和strcmp類似,用法也一樣.
特別注意:strcoll()會依環境變數LC_COLLATE所指定的文字排列次序來比較s1和s2字串。
strcmp是根據ASCII來比較2個字串的.若LC_COLLATE為”POSIX”或”C”,則strcoll()與strcmp()作用完全相同。

#include <strings.h>

int strcasecmp(const char s1, const char s2);
int strncasecmp(const char s1, const char s2, size_t n);
對s1與s2進行大小寫無關的比較。
return ==0, s1 == s2;
return >0, s1 > s2;
return <0, s1 < s2;

int bcmp(const void s1, const void s2, size_t n) Thread-Safe
已廢棄的方法,不推薦使用。推薦使用memcmp代替。

字串複製
#include <string.h>

char strcpy(char dest, const char src); Thread-Safe
char strncpy(char dest, const char src, size_t n); Thread-Safe
將src指向的字串(全部或指定的n個字元)複製到dest中,其中dest與src指向的記憶體空間不能重疊。要保證dest有足夠的空間能夠容納src中的內容,否則may make the impossible possible.
對於strncpy()只負責拷貝指定長度的字元,拷貝完並不會在字元的結尾加上字串終止符。所以見下面的示例程式碼中會打印出”c.st strcpy()”.對於n其長度最大值就為dest_length -1,因為最後一個位置還要放一個字串終止符。
1
2
3
4
5
6
char * strA = "Test strcpy()"; // 對於下面的strDest中長度最小要13 + 1=14。
char strDest[100]; // 宣告一個數組,空間對於將要複製的字串來說要足夠大。
char *strcpy = strcpy(strDest, strA); // 執行復制。
printf("%s \n", strcpy); // 打印出:Test strcpy()
strcpy = strncpy(strDest, "c.code", 2); // 執行指定長度的字串內容。
printf("%s \n", strcpy); // 打印出:c.st strcpy()
strncpy()如果src比較小,而指定的n比較大時,則strncpy會把多出來的長度用/0填充,這就引出一個效率上不高的問題:如下面的程式碼中strncpy會填寫99個char,而不止是”ab”本身。

1
2
char strDest[100];
strncpy(strDest, "ab", 99);
void memcpy(void dest, const void *src, size_t n);
用法同strncpy(),但注意到這是純記憶體拷貝操作,可以對更廣泛的物件進行對比,如字元陣列,整型,類,結構體等。

size_t strlcpy(char restrict dst, const char restrict src, size_t size);
strlcpy()是為替代strncpy()而實現的更簡易、更安全、更不容易出錯的字串拷貝方式。
使用時僅需如下例子在size引數中傳入sizeof(dst)即可不用考慮執行結果中是否包含字串終止符的問題(自動給加上了)。
strlcpy()僅僅會從src中拷貝最多size -1個字元,並在dest最後新增上字串終止符。
strlcpy()的返回值是src的長度,所以一旦結果為len > sizeof(buf),則表示拷貝的結果把src截斷了。

1
2
3
4
5
char *src, *p, buf[BUFSIZ];
int len = strlcpy(buf, src, sizeof(buf));
if (len > sizeof(buf)){
        printf("%s \n", "string src is truncated.");

字串拼接
#include <string.h>

char strcat(char dest, const char src); Thread-Safe
char strncat(char dest, const char src, size_t n); Thread-Safe
string concatenate
字串拼接同上面的複製,都要求dest有足夠的空間,且dest和src所指向的空間不能重疊。
dest的空間長度最小值為 strlen(dest) + n + 1。其中n表示strlen(src)或指定要拼接的長度值n,最後一個1表示字串終止符。
1
2
3
4
5
6
7
// 宣告一個數組,空間對於將要複製和拼接的字串來說要足夠大。
// 6是最小值, 6 = strlen(strA) + 2(strcat) + 1(\0)
char strDest[6];
char * strA = "abc";
strcpy(strDest, strA);
strncat(strDest, "123", 2);
printf("%s \n", strDest); // 打印出:abc12
size_t strlcat(char restrict dst, const char restrict src, size_t size);
同strlcpy(),是保證進行字串拼接時以字串終止符結尾。第三個引數size應傳入dst的長度。
在如下程式碼中,很明顯dest的空間是不足以拼接”123456789”的,所以最後的拼接結果是“12345”+1個字串終止符。
1
2
3
4
5
6
7
char strDest[6];
char * strA = "123456789";
int result = strlcat(strDest, strA, sizeof(strDest));
printf("%s\n", strDest); // 打印出“12345”
if (result){
    printf("%s be truncated.\n", strA);
}
字串拼接單個char
c中沒有現成的函式可以拼接單個char,這裡需要用到指標進行賦值,如下:

1
2
3
4
5
6
7
8
9
10
11
12
char dest[5];
char *str = "abc";
// 對dest賦值
strcpy(dest, str);
char * pDest;
pDest = dest + strlen(dest);
// 拼接單個字串
*pDest++ = '1';
*pDest++ = 0; // 字串終止符
// 打印出:abc1 5 4 
printf("%s %lu %lu \n", dest, sizeof(dest), strlen(dest));
字串長度
之所以把字串長度放在字串拼接之後,是為了更方便講述理解。
#include <string.h>

size_t strlen(const char *s);
strlen()直接計算引數s中字元的個數,直至碰到字串終止符\0時停止計算。所以在字串拼接單個char中,strlen(dest)返回結果是4。
但如果把程式碼稍作修改成如下,則strlen(dest)返回的結果可就不一定了。
1
2
3
4
5
6
7
8
9
10
11
12
char dest[5];
char *str = "abc";
// 對dest賦值
strcpy(dest, str);
char * pDest;
pDest = dest + strlen(dest);
// 拼接單個字串
*pDest++ = '1';
*pDest++ = '2'; // 改動點:不以字串終止符結尾
// 打印出:abc12\300\370\277_\377 5 11 內容是不固定的
printf("%s %lu %lu \n", dest, sizeof(dest), strlen(dest));
可以看到,strlen()返回的長度已經超出了dest可容納的最大值。所以該函式返回的結果存在一定的溢位風險。
如何解決該問題?可以使用下面的函式。

size_t strnlen(const char *s, size_t maxlen);
strnlen()的第2個引數指定s的最大值,正常情況下和strlen()能力是一致的。區別在於strnlen()返回的最大值最多隻能是maxlen。所以如果返回值為引數maxlen時,即可以判斷出s是非正常的字串終止符結尾,需要特別處理。見下示例程式碼。
1
2
3
4
5
6
int n_len = strnlen(dest, sizeof(dest));
if (n_len >= sizeof(dest)){
    printf("dest is not NUL-TERMINALED \n");
} else {
    printf("dest.len=%d \n", n_len);
}
字元類別檢查
#include <ctype.h>

int isalnum(int c);
返回1表示字元c是 大小寫字母 或 數字;否則返回0。
int isalpha(int c);
返回1表示字元c是 大小寫字母 ;否則返回0。
相當於(isupper(c) || islower(c))。在某些locale設定下,有可能一些非字母字元也返回1。
int isascii(int c);
返回1表示字元c屬於 ASCII字符集 ;否則返回0。
int isblank(int c);
返回1表示字元c是 空格 或 Tab符\t;否則返回0。注意有些系統或編輯器下的Tab符是由多個空格組成的,這種情況下返回0。
int iscntrl(int c);
返回1表示字元c是控制制服(c:[0:31…]等等)
int isdigit(int c);
返回1表示字元c屬於0~9的數字;否則返回0。
int isgraph(int c);
返回1表示字元c屬於印刷可見的字元(不包含空格);否則返回0。大部分情況下返回結果和iscntrl(c)是相反的。
int islower(int c);
返回1表示是英文字母中的小寫字元;否則返回0。
int isprint(int c);
同isgraph(c),區別在於c是空格時也返回1。
int ispunct(int c);
返回1表示排除了空格及英文字母數字的可列印字符集;否則返回0。
int isspace(int c);
返回1時表示c屬於 空格、\f、換行符‘\n’、回車’\r’、水平Tab\t、垂直Tab\v;否則返回0。
int isupper(int c);
返回1時表示c羽毛球英文字母中的大寫字母。
int isxdigit(int c);
返回1時表示c屬於數字十六進位制表示形式中的字元(0~9、a~f、A~F);否則返回0。
字串子串擷取
c的類庫中目前沒有找到現成的字串子串擷取方法。
如果是想擷取指定字串從起始位置開始的前n個字元,可以使用strncpy或memcpy代替。
但如果是想字串擷取從第n個到第n+x個字元的子字串,得自己實現了。示例程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static
char* substring(const char* src, const int idxStart, const int idxEnd) {
    if (strlen(src) == 0) {
        printf("arg src.length is 0, return.");
        return (void*)0;
    }
    if (idxEnd > strlen(src)) {
        printf("idxEnd=%d > strlen(src)=%lu, return.", idxEnd, strlen(src));
        return (void*)0;
    }
    if (idxStart >= idxEnd) {
        printf("idxStart=%d >= idxEnd=%d, return.", idxStart, idxEnd);
        return (void*)0;
    }
    // 避免直接定義固定長度
    int length = idxEnd - idxStart + 1;
    const int sub_length = length;
    // 使用malloc,避免在函式體結束之後即回回收導致函式返回值無法被使用。
    // 需要使用free()釋放;
    char * pDest = (char*)malloc(sub_length);
    for (int i = idxStart;i < idxEnd;i ++) {
//        printf("src %c \n", *(src+i));
        *(pDest + (i-idxStart)) = *(src+i);
//        printf("dest %c \n", *(pDest + (i-idxStart)));
    }
    *(pDest+(idxEnd - idxStart)) = 0; // 新增上字串終止符
//    printf("pDest=%s %p\n", pDest, pDest);
    return pDest;
}
int main(int argc, const char * argv[]) {
    char * src = "abc123ABC";
    char *dest = substring(src, 3, 6);
    //打印出"123"
    printf("substring1=[%s] \n", dest);
    // 不要了,回收掉
    free(dest);
    // 不需要了之後對指標置空,防止野指標
    dest = NULL;
    printf("%s \n", dest);
}
字串界位符切割
#include <string.h>

char strtok(char str, const char *delim);
char strtok_r(char str, const char delim, char *saveptr);
以上兩個函式可以將指定的字串切割成0個或數個非空子串.
以上兩個函式在第一次呼叫時,第一個引數str傳入待切割的長字串;在下次迴圈呼叫之前需要把str置為NULL。
第2個引數delim指定切割關鍵字符集(所以可以包含多種情況)。
函式執行後的返回結果是切割後的子字串,如果返回為NULL,則表示已經沒有目標切割物件了,可以停止迴圈。
以下程式碼示例strtok()的使用,切割標準為-或:字元:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    char * token;
    char original[] = "ab-cd;ef-gh";
    char * src = original;
    char * delim = ";-";
    for(int i = 0;; i ++) {
        token = strtok(src, delim);
        if (token == NULL){
            break;
        }
        printf("%d:%s ", i, token);
        if (src != NULL) {
            src = NULL; // 對src置空
        }
    }
// 打印出:0:ab 1:cd 2:ef 3:gh
對src置空後,迴圈的呼叫仍可輸出切割的子字串結果,個人覺得應該是在strtok()方法體內使用了函式內靜態引數的原因。

strtok_r():strtok reentrant
strtok_r()是可重入版本的strtok()方法。第三個引數saveptr用於指向未進行切割處理的字串子串。是指向指標的指標。這也是可重入計算的關鍵。
如下示例程式碼中演示了strtok_r()的可重入計算,即在迴圈體內再巢狀一個迴圈用第二種界位符對字元進行二次切割。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
int main(int argc, const char * argv[]) {
    char *str1, *str2, *token, *subtoken;
    char *saveptr1, *saveptr2;
    int j;
    
    
    if (argc != 4) {
        fprintf(stderr, "Usage: %s string delim subdelim\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    const unsigned long argv1_len = strlen(argv[1]);
    char argv1[argv1_len];
    strcpy(argv1, argv[1]);
    printf("argv1=%s \n", argv1);
    
//  for (j = 1, str1 = argv[1]; ; j++, str1 = NULL) {
    // 這種寫法,避免Assigning to 'char *' from 'const char *' discards qualifiers
    for (j = 1, str1 = argv1; ; j++, str1 = NULL) {
        token = strtok_r(str1, argv[2], &saveptr1);
        if (token == NULL)
            break;
        printf("%d: %s  %s\n", j, token, saveptr1);// 當saveptr1為null時,則下個迴圈時strtok_r()返回結果亦為null
        
        for (str2 = token; ; str2 = NULL) {
            subtoken = strtok_r(str2, argv[3], &saveptr2);
            if (subtoken == NULL)
                break;
            printf(" --> %s \n", subtoken);
        }
    }
    
    exit(EXIT_SUCCESS);
}
在命令列工具下執行命令,對長字元進行兩次切割,先”-:”後”/“。
執行結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sodino:CString sodino$ ./a.out "a-b-cd:ef/hi/jk:///lm://no-p:q" "-:" "/"
argv1=a-b-cd:ef/hi/jk:///lm://no-p:q 
1: a  b-cd:ef/hi/jk:///lm://no-p:q
 --> a 
2: b  cd:ef/hi/jk:///lm://no-p:q
 --> b 
3: cd  ef/hi/jk:///lm://no-p:q
 --> cd 
4: ef/hi/jk  ///lm://no-p:q
 --> ef 
 --> hi 
 --> jk 
5: ///lm  //no-p:q
 --> lm 
6: //no  p:q
 --> no 
7: p  q
 --> p 
8: q  (null)
 --> q