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)。

相关文章
相关标签/搜索