C Primer Plus 第11章 字符串和字符串函數 11.5 字符串函數

C庫提供了許多處理字符串的函數:ANSI C 用頭文件string.h給出這些函數的原型。下面是一些最有用和最經常使用的函數:strlen() 、strcat()、strncat() 、strcmp() 、strncmp() 、strcpy()、 strncpy()。此外,咱們也將研究一下頭文件stdio.h支持的sprintf()函數。程序員

11.5.1  strlen( )函數數組

咱們已經知道,用strlen()函數能夠獲得字符串的長度。下面的函數中用到了strlen()函數,這是一個能夠縮短字符串長度的函數:安全

/*test_fit.c-*/
void fit (char * string,unsigned int size)
{
    if(strlen(string)>size)
    *(string+size)='\0';
}

這個函數確實要改變字符串,所以在函數頭中聲明形式參量string時沒有使用const修飾符。app

在程序清單11.13的程序中測試一下fit( )函數。注意,代碼中用到C的字符串文本串聯功能。函數

程序清單11.13  test.c程序學習

/*test.c 試用縮短字符串的函數*/
#include <stdio.h>
#include <string.h>
void fit(char *,unsigned int );

int main(void)
{
    char msg[]="Hold on to your hats,hackers. ";

    puts(msg);
    fit(msg,7);
    puts(msg);
    puts("Let's look at some more of the string. ");
    puts(msg+8);
    return 0;
}

void fit(char *string,unsigned int size)
{
    if(strlen(string)>size)
        *(string+size)='\0';

}

輸出以下:測試

Hold on to your hats,hackers.
Hold on
Let's look at some more of the string.
to your hats,hackers.

fit( )函數在數組的第8個元素中放置了一個‘\0'字符來代替原來的空格字符。puts()函數輸出時停在第一個空字符處,忽略數組的其餘元素。然而,數組的其餘元素仍是存在的,以下面的函數調用的輸出結果所示:ui

puts(msg+8);編碼

ANSI C的sting.h頭文件中包含了C字符串函數系列的原型,所以這個示例程序要包含這個文件。設計

11.5.2  strcat( )函數

strcat( )(表明string concatenation)函數接受兩個字符串參數。它將第二個字符串的一份拷貝添加到第一個字符串的結尾,從而使第一個字符串成爲一個新的組合字符串,第二個字符串並無改變

strcat( )函數是char*類型。這個函數返回它的第一個參數的值,即其後添加了第二個字符串的那個字符串中第一個字符的地址。

程序清單11.4  str_cat.c 程序

/*str_cat.c 鏈接兩個字符串*/
#include <stdio.h>
#include <string.h>
#define SIZE 80
int main(void)
{
    char flower[SIZE];
    char addon[]="s smell like old shoes.";

    puts("What is your favorite flower?");
    gets(flower);
    strcat(flower,addon);
    puts(flower);
    puts(addon);

    return 0;
}

輸出以下:
What is your favorite flower?
Rose
Roses smell like old shoes.
s smell like old shoes.

11.5.3  strcat( )函數

strcat( )函數並不檢查第一個數組是否可以容納第二個字符串。若是沒有爲第一個數組分配足夠大的空間,多出來的字符溢出到相鄰單元時就會出問題。您也可使用strncat( )函數,這個函數須要另外一個參數來指明最多 容許添加的字符數目。例如,strncat(bugs,addon,13)函數把addon字符串中的內容添加到bugs上,直到加到13個字符或遇到空字符爲止,由兩者中先符合的那一個來終止添加過程。所以,把空字符計算在內,bugs數組應該足夠大,以存放原始字符串、增長的最多13個字符和結束的空字符。程序清單11.15使用這一知識來計算available變量值,這個值被用做最多容許添加的字符數。

程序清單11.15  join_chk.c程序

/*join_chk.c--鏈接兩個字符串,並檢查第一個字符串的大小*/
#include <stdio.h>
#include <string.h>
#define SIZE 30
#define BUGSIZE 13
int main(void)
{
    char flower[SIZE];
    char addon[]="s smell like old shoes.";
    char bug[BUGSIZE];
    int available;

    puts("what is your favorite flower?");
    gets(flower);
    if((strlen(addon)+strlen(flower)+1)<=SIZE)
        strcat(flower,addon);
    puts(flower);
    puts("what is your favorite bug?");
    gets(bug);
    available=BUGSIZE-strlen(bug)-1;
    strncat(bug,addon,available);
    puts(bug);

    return 0;
}

11.5.4  strcmp( )函數

假定您但願把用戶的一個輸入和一個已有的字符串進行比較,如程序清單11.16所示。

程序清單11.16  nogo.c程序

/*nogo.c--這個程序能知足要求嗎?*/
#include <stdio.h>
#define ANSWER "Grant"
int main(void)
{
    char try[40];

    puts("who is buried in Grant's tomb?");
    gets(try);
    while(try!=ANSWER)
    {
        puts("No,that's wrong.Try again. ");
        gets(try);
    }
    puts("That's right!");

    return 0;
}

ANSWER和try其實是指針,所以比較式try!=ANSWER並不檢查這兩個字符串是否同樣,而是檢查這兩個字符串的地址是否同樣。因爲ANSWER和try被存放在不一樣的位置,因此這兩個地址永遠不會同樣,用戶永遠被告知結果是"wrong"。

咱們須要一個比較字符串內容,而不是比較字符串地址的函數。您能夠自行設計一個,但並不須要這樣作。strcmp( )函數就能夠實現這個功能。這個函數對字符串的操做,就像關係運算符對數字的操做同樣。特別地,若是兩個字符串參數相同,它就返回0。改進後的程序清單 11.17.

程序清單11.17  compare.c 程序

/*compare.c--這個程序能夠知足要求*/
#include<stdio.h>
#include<string.h>
#define ANSWER "Grant"
#define MAX 40
int main(void)
{
    char try[MAX];

    puts("who is buried in Grant's tomb?");
    gets(try);
    while(strcmp(try,ANSWER)!=0)
    {
        puts("No,that's wrong.Try again.");
        gets(try);
    }
    puts("That's right!");
    return 0;
}

**說明:因爲任何非零值都爲真,所以,大多數程序員會把while語句簡單的寫爲while(strcmp(try,ANSWER))。

strcmp( )函數的一個優勢是它比較的是字符串,而不是數組。儘管數組try佔用40個內存單元,而字符串「Grant」只佔用6個內存單元(一個用來存放空字符),可是函數在比較時只看try的第一個空字符以前的部分。所以,strcmp()能夠用來比較存放在不一樣大小數組裏的字符串。

strcmp( )的返回值

若是字符串不相同,strcmp返回什麼值呢?

程序清單11.18  compback.c

/*compback.c  strcmp()的返回值*/
#include<stdio.h>
#include<string.h>
int main(void)
{
    printf("strcmp(\"A\",\"A\") is ");
    printf("%d\n",strcmp("A","A"));

    printf("strcmp(\"A\",\"B\") is ");
    printf("%d\n",strcmp("A","B"));

    printf("strcmp(\"B\",\"A\") is ");
    printf("%d\n",strcmp("B","A"));

    printf("strcmp(\"C\",\"A\") is ");
    printf("%d\n",strcmp("C","A"));

    printf("strcmp(\"Z\",\"a\") is ");
    printf("%d\n",strcmp("Z","a"));

    printf("strcmp(\"apples\",\"apple\") is ");
    printf("%d\n",strcmp("apples","apple"));

    return 0;
}

這些結果說明若是第一個字符串在字母表中的順序先於第二個字符串,則strcmp函數返回的是負數;相反,返回的就是正數。ANSI標準規定,若是第一個字符串在字母表中的順序先於第二個字符串,strcmp返回一個負數;若是兩個字符串相同,它返回0;若是第一個字符串在字母表中的順序落後於第二個字符串,它返回一個正數。而確切的數值是依賴於C實現的。

若是兩個字符串中初始的字符相同會怎麼樣呢?通常來講,strcmp函數一直日後查找,直到找到第一對不一致的字符。而後它就返回相應的值。apples和apple只有最後一個字符不一樣,匹配進行到apple的第6個字符,即空字符,因爲空字符在ASCII中排行第一,字符s在它的後面,所以,函數返回一個正數。

上面的比較說明strcmp( )比較全部的字符,而不只僅是字母;所以咱們不該稱比較是按字母順序,而應該稱strcmp()是按機器編碼(collating sequence)順序進行比較的。這意味着字符的比較是根據它們的數字表示法,通常是ASCII值。在ASCII中,大寫字母先於小寫字母。所以,strcmp("Z","a")是負數。

一般咱們不會在乎返回的確切值,只想知道結果爲0仍是非0;或者咱們是把字符串按字母表順序排序,但願知道比較結果是正數、負數仍是0。

**說明:strcmp()函數用於比較字符串,而不是字符。所以,可使用諸如「apples"或"A"之類的參數;可是不能使用字符參數,如'A'。考慮到char類型是整數類型,所以可使用關係運算符來對字符進行比較。

程序清單11.19  quit_chk.c程序(判斷一個程序是否應該中止讀取輸入)

/*quit_chk.c --某程序的開始*/
#include<stdio.h>
#include<string.h>
#define SIZE 81
#define LIM 100
#define STOP "quit"
int main(void)
{
    char input[LIM][SIZE];
    int ct=0;

    printf("Enter up to %d lines(type quit to quit):\n",LIM);
    while(ct<LIM && gets(input[ct])!=NULL&&
          strcmp(input[ct],STOP)!=0)
          {
            ct++;
          }
    printf("%d strings entered!\n",ct);
    return 0;
}

當程序遇到一個EOF字符(此時gets()返回空)時,或者您輸入單詞quit時,或者達到LIM的上限時,程序退出對輸入的讀取。

順便提一下,有時候輸入一個空行來終止輸入更方便,也就是說,在一個新行中不輸入任何字符就按下Enter鍵。要這樣作,您能夠對while循環的控制語句作以下的修改:

while(ct<LIM && gets(input[ct])!=NUll && input[ct][0]!='\0')

此處,input[ct]是剛輸入的字符串,input[ct][0]是該字符串的第一個字符。若是用戶輸入一個空行,gets( )就把空字符放在第一個元素處,所以以下表達式是用來檢測空輸入行的:

input[ct][0] != '\0'

11.5.5  strncmp( )變種

strcmp( )函數比較字符串時,一直比較到找到不一樣的相應字符,搜索可能要進行到字符串結尾處。而strncmp( )函數比較字符串時,能夠比較到字符串不一樣處,也能夠比較徹底由第三個參數字控制的符數。程序清單11.20示例了這個函數的用法。

程序清單 11.20  starsrch.c 程序

/*starsrch.c --使用strncmp( )函數*/
#include <stdio.h>
#include <string.h>
#define LISTSIZE 5
int main(void)
{
    char * list[LISTSIZE]={
    "astronomy","astounding",
    "astrophysics","ostracize",
    "asterism"};
    int count=0;
    int i;

    for(i=0;i<LISTSIZE;i++)
        if(strncmp(list[i],"astro",5)==0)  //注意if語句的位置
    {
        printf("Found:%s\n",list[i]);
        count++;
    }
    printf("The list contained %d words beginning with astro.\n",count);
    return 0;
}

11.5.6  strcpy( )和strncpy( )函數

咱們已經提到過 ,若是pts1和pts2都是指向字符串的指針,則下面的表達式只複製字符串的地址而不是字符串自己:

pts2=pts1;

假定您確實但願複製字符串,那麼可使用strcpy( )函數。程序清單11.21要求用戶輸入以q開頭的單詞。程序把輸入一個臨時的數組裏,若是第一個字母是q,程序就使用strcpy( )函數把字符串從臨時數組裏複製到永久的目的地。strcpy() 函數在字符串運算中上做用造價於賦值運算符

程序清單11.21  copy1.c程序

/*copy1.c --strcpy()示例程序*/
#include <stdio.h>
#include <string.h>
#define SIZE 40
#define LIM 5

int main(void)
{
    char qwords[LIM][SIZE];
    char temp[SIZE];
    int i=0;

    printf("Enter %d words beginning with q:\n",LIM);
    while(i<LIM && gets(temp))
    {
        if(temp[0]!='q')
            printf("%s doesn't begin with q!\n",temp);
        else
        {
            strcpy(qwords[i],temp);
            i++;
        }
    }
    puts("Here are the word accepted: ");
    for(i = 0;i < LIM;i++)
        puts(qwords[i]);
    return 0;
}

請注意只有當輸入的單詞經過了q判斷,計數值i纔會增長。還要注意程序使用了一個基於字符的判斷:

if(temp[0]  !=  'q')

這至關於,temp數組的第一個字符是否不爲q?還可使用一個基於字符串的判斷:

if(strncmp(temp[0],"q",1) != 0)

這至關於,字符串temp和字符串「q"的第一個元素是否不一樣。

注意,第二個參數temp指向的字符串被複制到第一個參數qword[i]指向的數組中。複製的那份字符串被稱爲目標(target)字符串,最初的字符串被稱爲源(source)字符串。若是注意到它和賦值語句的順序同樣,目標字符串在左邊,就容易記住參數的順序。

確保目標數組對複製源字符串來講有足夠大的空間就是您的責任了。

char *str;
strcpy(str,"The C of Tranquility");  /*存在一個問題*/

函數將把字符串「The..."複製到str指定的地址中,可是str沒有初始化, 所以這個字符串可能被複制到任何地方!

總之,strcpy( )接受兩個字符串指針參數。指向最初字符串的第二個指針能夠是一個已聲明的指針、數組名或字符串常量。指向複製字符串的第一個指針應指向空間大到足夠容納該字符串的數據對象,好比一個數組。記住,聲明一個數組將爲數據分配存儲空間,而聲明一個指針只爲一個地址分配存儲空間。

1、strcpy( )的高級屬性

stycpy( )函數還有另外兩個有用的屬性。首先,它是char *類型,它返回的是第一個參數的值,即一個字符串的地址;其次,第一個參數不須要指向數組的開始,這樣就能夠只複製數組的一部分。程序清單11.22舉例說明了這兩個屬性。

程序清單11.22  copy2.c 程序

/*copy2.c --strcpy( )示例程序*/
#include <stdio.h>
#include <string.h>
#define WORD "beast"
#define SIZE 40

int main(void)
{
    char *orig = WORD;
    char copy[SIZE] = "Be the best that you can be.";
    char *ps;

    puts(orig);
    puts(copy);
    ps=strcpy(copy+7,orig);
    puts(copy);
    puts(ps);

    return 0;
}

輸出以下
beast
Be the best that you can be.
Be the beast
beast

注意,strcpy( )從源字符串複製空字符。在這個例子中,空字符覆蓋了that中的第一個t,這樣新的字符串就以beast結尾。還要注意,ps指向copy的第8個元素(索引爲7),這是由於第一個參數是copy+7。所以,puts(ps)從這個地方開始輸出字符串。

2、較爲謹慎的選擇:strncpy( )

strcpy()和gets( )函數一樣有一個問題,那就是都不檢查目標字符串是否容納得下源字符串。複製字符串使用strncpy( )比較安全。它須要第三個參數來指明最大可複製的字符數。程序清單11.23用strncpy()代替了程序清單中的strcpy()。爲了說明源字符串太大會產生的問題,它使用了一個至關小的目標字符串。

程序清單11.23  copy3.c 程序

/*copy3.c --strncpy() 示例程序*/
#include <stdio.h>
#include <string.h>
#define SIZE 40
#define TARGSIZE 7
#define LIM 5
int main(void)
{
    char qword[LIM][TARGSIZE];
    char temp[SIZE];
    int i=0;

    printf("Enter %d words beginning with q: \n",LIM);
    while(i<LIM && gets(temp))
    {
        if(temp[0]!='q')
            printf("%s doesn't begin with q!\n",temp);
        else
        {
            strncpy(qword[i],temp,TARGSIZE-1);
            qword[i][TARGSIZE-1]='\0';
            i++;
        }
    }
    puts("Here are the words accepted:");
    for(i=0;i<LIM;i++)
        puts(qword[i]);
    return 0;
}

下面是一個運行示例
Enter 5 words beginning with q:
quack
quadratic
quisling
quota
quagga
Here are the words accepted:
quack
quadra
quisli
quota
quagga

函數調用strncpy(target ,source, n)從source把n個字符(或空字符以前的字符,由兩者中最早知足的那個決定什麼時候終止)複製到target。所以,若是源字符串的字符數比n小,整個字符串都被複制過來,包括空字符。函數複製的字符數毫不會超過n,所以若是源字符串還沒結束就達到了限制,就不會添加空字符。結果,最終的字符串可能有也可能沒有空字符。出於這個緣由,程序設置的n比目標數組的大小要少1,這樣就能夠把空字符放到數組的最後一個元素裏。

strncpy(qwords[i],temp,TARGSIZE-1);
qwords[i][TARGSIZE-1]='\0';

這就確保您已經存儲了一個字符串。若是源字符串確實能夠容納得下,和它一塊兒複製的空字符就標誌着字符串的真正結束。若是源字符串在目標數組中容納不下,這個最後的空字符就標誌着字符串的結束。

11.5.7  sprintf()函數

sprintf()函數是在stdio.h而不是在string.h中聲明的。它的做用和printf()同樣,可是它寫到字符串裏而不是寫到輸出顯示所以,它提供了把ds幾個元素組合成一個字符串的一種途徑。sprintf()的第一個參數是目標字符串的地址,其他的參數和printf()同樣:一個轉換說明字符串,接着是要寫的項目列表。

程序清單11.24  format.c

/*format.c 格式化一個字符串*/
#include<stdio.h>
#define MAX 20
int main(void)
{
    char first [MAX];
    char last [MAX];
    char formal[2*MAX+10];
    double prize;

    puts("Enter your first name: ");
    gets(first);
    puts("Enter your last name: ");
    gets(last);
    puts("Enter your prize money: ");
    scanf("%lf",&prize);
    sprintf(formal,"%s,%-19s: $%6.2f\n",last,first,prize);
    puts(formal);

    return 0;
}
下面是一個運行示例
Enter your first name:
Teddy
Enter your last name:
Behr
Enter your prize money:
2000
Behr,Teddy              : $2000.00

sprintf( )命令獲取輸入,並把輸入格式化爲標準形式後存放在字符串formal中。

11.5.8  其餘字符串函數

  • char *strchr(const char *s, int c )

該函數返回一個指向字符串s中存放字符c的第一個位置的指針(標誌結束的空字符也是字符串的一部分,所以也能夠搜索到它)。若是沒找到該字符,函數就返回空指針。

  • char *strpbrk(const char *s1,const char *s2)

該函數返回一個指針,指向字符串s1中存放s2字符串中的任何字符的第一個位置。若是沒有找到任何字符,函數就返回空指針。

  • char *strrchr(const char *s,int c )

該函數返回一個指針,指向字符串s中字符c最後一次出現的地方(標誌結束的空字符也是字符串的一部分,所以也能夠搜索到它)。若是沒有找到該字符函數就返回空指針。

  • char *strstr(const  char *s1,const char *s2)

該函數返回一個指針,指向s1字符串中第一次出現s2字符串的地方。若是在s1中沒有找到s2字符串,函數就返回空指針。

  • size_t strlen(const char *s);

該函數返回s字符串中的字符個數,其中不包括標誌結束的空字符

注意:這些原型使用const指出哪一個字符串是函數不能改動的。

第5章「運算符、表達式和語句」中已經討論過,size_t類型是sizeof運算符返回的任何類型。C規定sizeof運算符返回一個整數類型,可是沒有指定是哪一種整數類型。所以size_t在一系統上能夠是unsigned int類型;在另外一個系統上又能夠是unsigned long 類型。string.h文件爲您的特定系統定義了size_t,或者您能夠參考其餘有該定義的頭文件。

讓咱們看一下這引發函數其中的一個簡單應用。前面已經學習過fgets()函數。在讀取一行輸入時,這個函數把換行符存儲到目標字符串中。可使用strchr()函數來用一個空字符代替這個換行符。首先,使用strchr()找到換行符(若是有的話)。若是找到了,函數就返回這個換行符的地址,因而就能夠在該地址中放一個空字符:

char line[80];
char * find;

fgets(line,80,stdin);
find=strchr(line,'\n');
if(find)               //若是該地址不爲null,
    *find='\0';        //就把一個空字符放在這裏

若是strchr()沒有找到換行符,說明fgets()在行未結束時就達到了大小限制。您能夠給if加一個else來處理這種狀況。

接下來,咱們看一下處理字符串的完整程序(11.6)。

相關文章
相關標籤/搜索