C语言程序设计(朱陈)-第8章 字符串

/2020年1月31日 周五1:33

第8章 字符串

让规范适应程序要比让程序适应规范容易。

It is easier to change the specification to fit the program than vice versa.

学习目标:

掌握字符串的定义和输入/输出方法

掌握用字符数组和字符指针处理字符串的方法

掌握字符串处理相关函数的方法

字符串是由0个或多个字符组成的序列,用于存储字母、数字、标点和其他符号组成的文本数据,如学号、姓名、书名等。

C语言中没有特别设置字符串类型,而是利用字符数组或字符指针来处理字符串。本章重点介绍字符串的定义与初始化、输入输出,以及常用的字符串处理函数。灵活使用字符和字符串在编程中非常重要。

8.1 字符串的定义与初始化

本节要点:

• 采用数组和指针定义及表示字符串的方法

• 字符串中初始化和赋值问题

字符串常量是用一对双引号括起来的若干字符,如“Hello”。

在内存中,系统会自动在其最后加上’\0’作为字符串的结束标志

而 C 语言基本类型中的 char 类型,仅能存储一个字符,

因此字符串的操作一般通过字符数组和字符指针来实现

 

8.1.1 用字符数组处理字符串

字符数组的定义格式与第6章其他类型的数组一样,

例如如下定义:charch[12];,表示定义了一个长度为12的字符数组,数组中的每一个元素都是字符。

需要注意的是,用字符数组存储字符串时,字符数组中必须以’\0’结尾,否则就是单纯的字符数组。

和其他类型的数组一样,字符数组可以有一维数组和多维数组,

常用一维数组存放一个字符串,二维数组存放多个字符串。

(1)逐字符初始化。

字符型数组在定义的同时,可以对其每个元素用字符进行初始化,例如:

char ch[10]= { 'H', 'i', ' ', 'w', 'o', 'r','l', 'd', '!', '\0'};

char ch[10]={'H','i',' ','w','o','r','l','d','!','\0'};

初始化时,如果元素个数小于数组的长度,则后面的元素自动初始化为空字符‘\0‘。如

char ch[12]={ 'H', 'i', ' ', 'w', 'o', 'r','l', 'd', '!'};

则该字符数组中的状态如图8.1所示。图8.1 字符数组部分赋值存储示意图

 

二维字符数组的定义和初始化也是同样的,如:

char ch[][6]={{ 'a', 'b'},{ 'c','d', 'e'},{ 'f','g', 'h', 'i'},{'j', 'k', 'l','m','n'}};

这里省略了一维下标,默认第一维为4,但是第二维不可省略,如图8.2所示。图8.2 二维数组存储示意图

 

(2)用字符串常量初始化。

字符数组还可以用字符串常量来初始化,由于有了双引号定界符,所以可以省略花括号,如以下定义:

char ch1[14]={"Programming!"}; 或 char ch1[14]="Programming!";

char ch2[ ]={"Programming!"}; 或 char ch2[]="Programming!";

字符串常量"Programming!"在内存中实际占用的空间是13个单元,因为末尾有结束标记符。

这样上述定义中字符数组ch1占用的空间是14个字节,最后还有一个’\0',

而字符数组ch2占用的空间是13个字节。从图8.3中可以看出两者的区别。

 

图8.3 字符串初始化后数组存储示意图

二维字符数组的初始化也可以用字符串常量,如:

char cColoar[4][7]={"white","red","orange","pink"};

二维字符数组cColor中字符存储如图

8.4所示,可见,cColor可用于存储4个长度最大为7 (包括’\0’在内)的字符串。

 

图8.4 二维字符数组存储示意图

 

8.1.2用字符指针处理字符串

首先来看字符指针与字符串的关系。如:

char *ps;

ps="Programming!";      / *字符指针变量,指向一个字符串常量* /

此时,字符指针ps指向字符串常量"Programming!"

——将字符串常量在内存中的首地址赋值给了指针变量ps,

这样,根据字符串的首地址即可找到整个字符串的内容,

所以可以用字符串的首地址来引用一个字符串,这样的指针称为串指针。其存储如图8.5所示。

图8.5 字符指针存储示意图

因为ps是指针变量,因此可以改变它的值,也就是改变它的指向,对上面的ps可以重新赋值如下:

ps="Hello world!"  / *字符指针变量,指向另一个字符串常量* /

这样,ps重新获得了"Hello world!"字符串在内存中的首地址,指向"Hello world!"了。

注意:

如果是字符数组,因为数组名是指针常量,不能被赋值,因此下面的使用方式不正确:

char ch1[14]={"Hello world!"};

ch1="Programming!";   / *错误,因为ch1是字符数组名,是指针常量* /

字符指针当然也可以用于处理字符数组中的字符串,而且数组和指针关系紧密,

特别在数组的各种处理中,指针更是提供了灵活多变的方法,如动态数组、数组在函数中的应用等。

关于这一部分内容请回顾7.2节内容。

因此,字符指针可以指向字符串,除了用数组下标访问,还可以用指针实现对字符串的操作。

【例8.1】输入一行文字(不带空格),统计其中字母、数字以及其他字符各有多少?

分析:从键盘输入字符串放在一个字符数组中,用一个字符指针遍历该数组,

对字符指针所指向的当前字符进行判断来统计其中字母、数字以及其他的字符的个数。

/*li08_01.c:字符统计示例*/
#include <stdio.h>
int main()
{
    int character=0,digit=0,other=0;
    /*设置统计变量并初始化为0*/
    char *p="Hello!",s[20];
    printf("%s\n",p);
    p=s;        /*字符指针p指向字符数组s*/
    printf("input string:\n");
    scanf("%s",s);        /*从键盘输入字符串放在数组s中*/
    while(*p!='\0')        /*判断字符串是否结束*/
    {
        if(('A'<=*p&&&*p<='Z')||('a'<=*p&&&*p<='z')){
            ++character;
        }
        else if((*p<='9')&&(*p>='0')){
            ++digit;
        }
        else{
            ++other;
        }
        p++;        /*指针移动,指向字符串的下一个字符*/
    }
    printf("character:%d\n digit:%d\n other:%d\n",character,digit,other);
    return 0;
}

运行此程序,屏幕上显示为:

Hello!

input string:

用户从键盘输入为:

用户从键盘输入为:

我的学号是B1010101<回车>

程序输出结果为:

character:1digit:7other: 10

说明

① 程序定义的字符指针 p,初始化指向字符串常量"Hello!",

然后通过代码第9行赋值语句 p=s,改变指针变量的值指向字符数组 s,

这样就可以用循环体中的语句p++;移动p指针,从而以*p形式访问字符数组s的各个元素并加以判断。

② 输入中有中文字符,由于一个中文字符占连续两个字节,相当于2个char类型的值,因此对5个中文字符统计出的其他字符数是10个。

 

8.2字符串的常用操作

本节要点:

字符串处理函数的定义和使用方法

字符串处理技巧

字符串存储在字符数组中,所以数组的操作方法都适用于字符数组——

可以用字符数组的下标或者指向字符串的字符指针访问和处理字符数组的各个元素。

8.2.1 字符串的输入和输出

输入和输出字符串主要有两种方式,

如下所示。有如下字符数组的定义:

char str_a[10],str_b[10];

char i;

(1)方法1:用格式控制字符%s整体输入/输出字符串。

例如:

scanf("%s%s",str_a,str_b); /*str_a,str_b是数组名,代表字符串的首地址,前面不再加&符*/

printf("%s\t%s\n",str_a,str_b); /*输出字符串*/

若输入:How are you?<回车>则输出:How are

说明

① scanf可以同时接收几个字符串,每个字符串对应一个格式控制符%s。

② printf 可以同时输出几个字符串,但是输出字符串后不会自动换行,如果希望换行,需要输出转义字符\n。

③ 用%s的形式输入字符串时,字符串中不能出现空格符、回车和制表符(TAB),因为它们都是 scanf 默认的输入分隔符,

如本例读入后相当于:char str_a[10]= "How", str_b[10]="are",所以才会输出How are而非How are you?。

如果需要输入带空格的串,就需要方法2来解决了。

注意字符数组与字符串不是同一概念,只是在C语言中,借助于字符数组来处理字符串,

如果字符数组中没有’\0’元素则不能认为是字符串。

【例8.2】字符数组的输出。

分析:可以用字符初始化字符数组,也可以用字符串初始化字符数组。

需要注意的是:字符数组在存储字符串时,尾部一定要有’\0'。

/*li08_02.c:字符数组示例*/
#include <stdio.h>
int main()
{
    char ch1[]={'H','e','l','l','o',' ','w','o','r','l','d','!'};
    char ch2[]={"Hello world!"};
    printf("%s",ch1);
    printf("\n");
    printf("%s",ch2);
    printf("\n");
    return 0;
}

此例中看似用一个字符数组存放了一个字符串"Hello world!"。当通过单步跟踪调试程序时,在调试窗口中会看到如图8.6(a)所示的变量值,串尾出现了“烫”字符,为什么呢?

 

说明为了准确表达一个字符串,C语言规定了一个字符串结束标记符,以空字符’\0’来代表。而程序中代码第5行charch1[]={'H','e','l','l','o',' ','w','o','r','l','d','!'};,字符数组ch1中没有以’\0’结尾,因此ch1仅是一个字符数组,存储的是字符,而不是字符串。因而不能用格式控制字符%s整体输出。当代码第6行char ch2[]={"Hello world!"};时,同样的数组存放的是"Hello world!"。第13个字符元素中填充的是’\0',即字符串结束标记符。再进行单步跟踪就不会出现图8.6(a)所示的情况,而显示如图8.6(b)所示,没有乱码出现。换句话说,在一个字符数组中,当遇到空字符’\0’时,就表示字符串结束,由它前面的字符组成字符串。

注意对字符数组初始化时,用单个字符逐一初始化数组元素和用字符串整体初始化是有差别的。如,char ch2[]={"Helloworld!"};与char ch1[]={'H','e','l','l','o','','w','o','r','l','d','!'};是不等价的。前者在内存中占用13个字节,最后以’\0’结束;而后者只占用12个字节,没有以’\0’结束。

(2)方法2:用系统提供的gets和puts函数完成字符串的输入/输出。

函数原型:

int puts(const char *ps); /*在显示器上输出字符串ps,串结束符被转换为换行符,成功输出字符串,并返回输出的字符数*/

char *gets(char *ps);     /*从键盘输入一个字符串,按回车键结束*/

例如:

gets(str_a); / *str_a是一个已经定义的字符数组* /

puts(str_a); / *输出字符串* /

若输入:How are you?<回车>则输出:How are you?

说明gets和puts函数是C语言提供的标准函数,使用时要包含头文件stdio.h。puts函数遇到’\0’结束,并将’\0’转换为’\n',也就是能自动进行换行的处理。而用 gets 函数读取字符串时,字符串连同换行符(即回车符)会依次读入指针形式参数ps指向的字符数组,并将换行符\n转为串结束符\0。

 

【例8.3】利用二维字符数组读入、输出多个字符串。

分析:二维字符数组的每一行都是一个字符串,即对若干个字符串进行输入、输出操作。

/*li08_03.c:二维字符数组示例*/
#include<stdio.h>
int main()
{
    char a[5][7];        /*通过a管理5个字符串*/
    int i;
    for(i=0;i<5;i++)
    {
        gets(a[i]);        /*a[i]是指向字符串的指针*/
    }
    for(i=0;i<5;i++)
    {
        puts(a[i]);        /*a[i]是指向字符串的指针*/    
    }
    return 0;
}

运行此程序,用户从键盘输入:

File <回车>Edit<回车>View<回车>Run<回车>Tools<回车>

屏幕输出为:

File

Edit

View

Run

Tools

说明如图8.7所示,二维数组a可以看成由a[0]、a[1]、a[2]、a[3]、a[4]这5个指向一维字符数组的字符指针组成,

因此可以用于字符串的读写。

 

管理多个字符串还可以定义char *pa[5],即含有5个字符指针的指针数组,并赋值如下:

管理多个字符串还可以定义char *pa[5],即含有5个字符指针的指针数组,

并赋值如下:

for(i=0;i<5;i++) pa[i]=a[i];

这样,每一个pa[i]都指向了一个字符串的首地址。

注意如果仅定义char *pp;后就执行gets(pp);

是错误的,因为此时指针pp的指向还不确定,将读入的字符串试图存放到不确定的存储空间,

这是必须避免的操作。

 

8.2.2 字符串处理的常用函数

在编写程序时,经常会对字符串进行操作,诸如字符串大小写转换、求字符串的长度、复制字符串等,

这些操作都可以用字符串函数来解决。C语言标准库函数专门提供了这一系列的字符串处理函数,

包括在系统头文件string.h 中。在编写过程中合理使用这些函数可以有效地提高编程效率。

本节将对一些常用的字符串处理函数进行介绍。

(1)字符串长度的获取。

在使用字符串的时候,经常需动态获取字符串的长度,虽然可以通过循环来计算字符串的长度,

但这样的方法相当麻烦。可以直接调用strlen函数来获得字符串的长度。该函数的原型如下:

unsigned int strlen(const char *str);

功能:计算字符串的有效字符个数的函数,串结束符不计算在内,函数返回值是串长度。

该函数源代码如下:

unsigned int strlen(const char *str)
{
    int i=0;
    while(str[i])i++;        /*判断字符串结束标志,如果未结束,有效字符个数加1*/
    return i;
}

说明字符串有效字符个数不包括最后的串结束标志’\0',比实际的存储空间大小要小1。

 

(2)字符串复制。

在字符串操作中,字符串复制是比较常见的操作,对于字符数组,

无法用赋值号 “=”将一个字符串常量或其他表示字符串的字符数组或字符指针赋值给它,

此时需要通过字符串复制函数来完成此项任务。该函数的原型如下:

char* strcpy(char *destinationn,const char *source);

功能:字符串复制函数,将源字符串source复制到目标字符串destination中。

返回值是目标字符串指针destination。

该函数源代码如下:

char* strcpy(char *destination,const char *source) / *注意两个指针形参的区别* /
{
    int i=0;
    while(source[i]!='\0')        / *以被复制串的当前字符是否是串结尾标志为条件* /
    {
        destination[i]=source[i++];        / *复制对应下标的字符到目标串* /
    }
    destination[i]='\0';        / *此句很重要,在目标串结尾加上串结尾标志* /
    return (destination);        / *返回目标串首地址* /
}

说明注意两个形式参数的不同,形参source用const加以修饰,

表示所指向的被复制的字符串在函数中是不能被修改的。

循环执行后,源串除了串结束标志’\0’外,其余字符都赋值给目标串相应元素,

为了使目标串保持字符串的性质,需要添加第8行语句destination[i]='\0'; 为目标串加上结束标志。

两个字符串复制的过程可以参见图8.8。

图8.8 串复制strcpy示意图

注意在复制前一定要确认destination指向的字符串存储空间满足source字符串的空间大小要求,避免出现数组越界情况。

(3)字符串连接。

字符串连接函数实际上就是完成两个字符串相加的效果,即一个字符串连接在另一字符串的末尾,

构成一个新的字符串。该函数原型如下:

char* strcat(char *destination,const char *source);

功能:完成字符串连接,将源字符串source接到目标串destination的尾部,

返回值是连接后的字符串destination指针。

注意,使用该函数前应确认destination指向的空间要能容下衔接以后的整个字符串。

该函数源代码如下:

char* strcat(char *destination,const char *source) /*注意两个指针形参的区别*/
{
    int i=0;
    while(*(destination+i)!='\0')        / *等效于while(destination[i]!='\0')* /
    {
        i++;        / *循环结束时,i指向目标串结尾处* /
    }
    while(*source!='\0')         / *将第2个串的内容复制到第1个串i下标开始处* /
    {
        *(destination+i++)=*source++;
    }
    *(destination+i)='\0';         / *在目标串最后加上结束标志* /
    return(destination);         / *返回目标串地址* /
}

说明

第7行语句*(destination+i++)相当于:destination[i++]=*(source++);,

将source串中字符赋值给destination串中相应位置,然后各自指向下一个字符。具体过程如图8.9所示。

具体过程如图8.9所示。

图8.9 串连接strcat示意图

(4)字符串比较。

字符串比较函数,比较字符串str1和str2,返回一个整型数来确认str1小于(或等于或大于)str2。

比较两个字符串的大小,实际上是逐个比较对应下标的字符大小,即比较两个字符对应的ASCII值大小,

以串中第一对不相等的两个字符的大小决定整串的大小关系,其中,字符结尾标记’\0'(也就是0)

小于任何一个字符(1~255)。所以两个字符串相等,一定是串长相等且对应位置的字符一一对应相等。

该函数原型如下:

int strcmp(const char *str1,const char *str2);

功能:比较两个字符串大小,若全部相等,则函数返回值为0;

如果遇到对应的字符不等,则停止比较,依据对应不等字符的ASCII值进行比较,

如果串str1中的ASCII值大于串str2中的对应字符值,则返回1;否则,返回−1。

该函数源代码如下:

int strcmp(const char *s1,const char *s2)  / *两个指针形参完全一样,不允许修改实参值* /
{
    int i=0;
    while(s1[i]!='\0'&&s2[i]!='0')     / *两个字符串都没有结束作为循环控制条件* /
    {
    if(s1[i]!=s2[i])    / *比较对应下标的字符,如果不相等则结束循环* /
    {
        break;
    }
    i++;     / *如果对应下标字符相等,则下标加1,继续比较* /
    }
    if(s1[i]>s2[i])    / *循环结束时,比较对应下标的字符* /
    {
        return(1);    / *如果第1个串对应字符大,则返回1* /
    }
    else if(s1[i]<s2[i])  / *如果第1个串对应字符小,则返回-1* /
    {
        return(-1);
    }
    else
    {
    return(0); / *相等返0* /
    }
}

注意表达式s1>s2在语法上也是正确的,但是此时并不是用于比较两个字符串的大小,

因为s1与s2在编译过程中解释为两个字符串的首地址,即s1>s2比较的是两个字符串起始地址的大小,

没有实际意义。因此,比较字符串的大小一定要调用strcmp函数。

 

(5)字符串大小写转换。

在编程中,通常会遇到大小写字母混合使用,当需要转换时,

可以使用 strupr 函数和 strlwr函数一次性完成,方便快捷。

1 char* strupr(char *str);

功能:将字符串str中的小写字母改为大写字母,其余字符不变,返回修改后的字符串str指针。

该函数源代码如下:

char* strupr(char *str)
{
    char *p=str;
    while(*p)
    {
        if(*p>='a'&&*p<='z')
        {
            *p-=32;
        }
        p++;
    }
    return str;
}

 

2 char* strlwr(char *str);

功能:将字符串str中的大写字母改为小写字母,其余字符不变,返回修改后的字符串str指针。

该函数源代码如下:

char* strlwr(char *str)
{
    char *p=str;
    while(*p)
    {
        if(*p>='A'&&*p<=='Z')
        {
            *p+=32;
        }
        p++;
    }
    return str;
}

下面通过示例进一步熟悉字符串处理函数的使用方法。

【例8.4】字符串处理函数的应用示例。

/*li08_04.c:字符串处理函数示例*/
#include<stdio.h>
#include<string.h>

int main()
{
    char str[20]="Programming";
    char cstr[20];
    char tmp[20];
    int i;
    printf("Input a string:\n");
    gets(cstr);
    if(strcmp(str,cstr)>0)         / *字符串比较,小的字符串放在str中* /
    {
        strcpy(tmp,str);
        strcpy(str,cstr);
        strcpy(cstr,tmp);
    }
    strcat(cstr,"**");         / *在cstr后加上字符** * /
    i=strlen(cstr);
    if(i+strlen(str)<20)
    {
        strcat(cstr,str);        / *将str连接到cstr后* /
        puts(cstr);
    }
    else
    {
        printf("Strcat can't be executed!\n");
    }
    strupr(cstr);
    puts(cstr);
    return 0;
}

运行此程序,屏幕上显示为:

Input a string:

用户从键盘输入为:C<回车>

程序输出结果为:

C** Programming

C** PROGRAMMING

再次运行程序,屏幕上显示为:

Input a string:

用户从键盘输入为:Just do it<回车>

程序输出结果为:

Strcat can't be executed!

JUST DO IT**

说明

说明字符串处理函数中,参数都是指向字符串的字符指针,指针操作时要时刻关注指针的指向。

另外,因为字符串都保存在字符数组中,要注意数组越界的问题。

本程序在做字符串连接操作时,对连接后的字符串长度做了相应的判断,这是非常必要的。

 

8.3应用举例

本节要点:

• 如何进行回文的判断

• 如何统计单词出现次数

• 如何进行密码验证

• 如何实现字符串的排序

1.回文的判断

所谓回文,就是去掉空格之后的字符串是中心对称的。

【例8.5】从键盘输入任意一个字符串,判断该字符串是否为回文。

分析:判断一个字符串是否是回文的算法思想如下。

① 表示下标的变量i和j分别“指向”字符串的首尾元素。

② 如果i小于j,则重复步骤

③,否则执行步骤④。

③ 如果i指向的是空格符,则i值加1,直到指向非空格符为止;如果j指向的是空格符,则j值减1,直到指向非空格符为止。然后比较i和j指向的字符,如果不同,则返回0,表明不对称,如果相同,则i值加1,j值减1,然后返回步骤②。

④ 返回1,表明字符串对称。

根据以上算法思想,定义一个判断回文的函数,主函数中读入一个字符串,调用该函数,根据判断结果输出相应的提示信息。

/*li08_05.c:回文判断示例*/
#include<stdlib.h>
#include<string.h>
#define MAX 80
int Palindrome(const char *str); /*判断回文的函数原型*/

int main()
{
    char str[MAX],ch;
    do        /*该循环用于控制是否需要多次判断串是否为回文*/
    {
        printf("Input a string:\n");
        gets(str);
        if(Palindrome(str))        /*调用函数判断是否为回文,输出不同的结论*/
        {
            printf("It is apalindrom.\n");
        }
        else
        {
            printf("It is not a palindrom.\n");
        }
        printf("continue?(Y/N)\n");        /*询问是否要继续判断回文*/
        ch=getchar();        
        getchar();        /*跳过刚刚输入的回车符*/
        }while(ch!=';N'&&ch!='n'); /*如果既不是N也不是n表示需要继续判断*/
        return 0;
}

int Palindrome(const char *str)        /*const 用于保护实参*/
{
    int i=0;j=strlen(str)-1;        /*对应于算法步骤1*/
    while(i<j)        /*对应算法步骤2*/    
    {
        while(str[i]==32)
        {
            i++;
        }
        while(str[j]==32)
        {
            j--;
        }
        if(str[j]==str[i])
        {
            i++;
            j--;
        }
        else
        {
            return(0);        / *对应字符不同,则返回0,表示不是回文* /
        }
        return (1); / *对应于算法步骤④,循环停止,i>=j,所有的str[j]==str[i]* /
}   

运行此程序,屏幕上显示的提示为:

Input a string:

用户从键盘输入为:asdsa<回车>

屏幕上显示的结果及提示为:

It is a palindrom.

continue?(Y/N)

用户从键盘输入为:y<回车>

屏幕上显示的提示为:

Input a string:

用户从键盘输入为:

as dfd sa<回车>

屏幕上显示的结果及提示为:

It is a palindrom.continue?(Y/N)用户从键盘输入为:y<回车>屏幕上显示的提示为:Input a string:用户从键盘输入为:abcde<回车>屏幕上显示的结果及提示为:It is not a palindrom.continue?(Y/N)用户从键盘输入为:n<回车>

说明

程序中,利用下标i、j分别从字符串的首尾向中间移动,判断对应字符元素是否相等。其中,用str[j]==32判断字符元素是否为空格,还可以写成str[j]== ' '。

 

2.统计单词出现次数

在一段文字中去统计给定单词出现的次数是字符串中一种较为常见的操作,Word软件中的“查找”功能与此类似,只不过“查找”功能是直接将光标定位到待查找元素在正文中的位置处。

【例8.6】找出给定单词在一段文字中出现的次数,假定原文中的任意分隔符均不会连续出现。

分析:因为原文不会出现连续的分隔符,可以认为一旦出现分隔符就表示是前后两个单词的分界点。因此,算法利用这点对原文进行分割,分离出一个个单词,然后进行大小的比较就可以了。

/*li08_06.c:单词统计示例*/
#include<stdio.h>
#include<string.h>

int search(const char *ps,const char *pf)
{
    int count=0,i=0;
    char dest[20];
    while(*ps)
    {
        i=0;
        while((*ps>='a'&&*ps<='z')||(*ps>='A'&&*ps<='Z'))
        {
            dest[i++]=*ps++;
        }
        dest[i]='\0';
        ps++;
        if(strcmp(dest,pf)==0)
        {
            count++;
        }
    }

    return count;
}

int main()
{
    char source[200];
    char key[15];
    puts("Input the source sentence:");
    gets(source);
    puts("Input the key word:");
    gets(key);
    printf("there are %d key words in this sentence.\n",search(source,key));
    return 0;
}

运行此程序,屏幕上显示为:

Input the source sentence:

用户从键盘输入为:

If you are a fan of cartoons,don't miss the Chinesefilm I am a Wolf.<回车>

屏幕上继续显示为:Input the key word:

用户从键盘继续输入为:a<回车>程序输出结果为:There are 2 key word in this sentence.

说明本例旨在熟悉字符串的输入和输出,并运用字符串比较函数来检查关键字,注意在查找过程中指针变量的变化。

此外,特别要注意的是每次读出一个单词,应该在后面添加字符串结束符号。

3.密码问题

在实际开发中,经常会遇到要设置用户名及用户密码的问题。

这是个简单而又实用的问题。

【例8.7】要求用户输入密码,以“#”作为结束标志。

按一定规则进行解密后,若与预先设定的密码相同,则显示“pass”,否则发出警告。

分析:密码的判断可以用函数完成。当然两个字符串的比较可以用strcmp函数,

但是因为需要对密码进行解密的操作,为了能访问字符串的各个字符元素,

需要用循环语句直接进行字符的比较。主函数除了让用户输入密码,

还应根据函数的返回值给出相应的输出。

/*li08_07.c:密码输入示例*/
#include<stdio.h>
char passwd[]="NJUPT";        /*设置的密码*/
/*函数功能:  判断密码与预设密码是否一致
  函数入口参数:字符指针,指向用户输入的密码
  函数返回值: 整型,表示密码正确与否,1正确,0错误
*/

int check(char *ps)
{
    int i=0;
    int flag=1;        /*设置标志位*/
    for(;*ps!='\0'&&flag;ps++)        /*字符串结束标志的应用*/
    {
        if(*ps>='a'&&*ps<='z')
        {
            *ps=*ps-32;        /*解密规则*/
        }
        if(*ps!=passwd[i])        /*只要有一个字符不吻合,flag=0,终止循环*/
        {
            flag=0;
        }
        else
        {
            i++;
        }
    }
    return flag;
}

int main()
{
    char str[10];
    int i=0;
    printf("Input your password:\n");
    while((str[i]=getchar())!='#')        / *逐字符读入,'#’作为结束标志* /
    {
        i++;
    }
    str[i]='\0';        / *增加字符串结束标志* /
    if(check(str))
    {
        printf("Pass!\n");
    }
    else
    {
        printf("Error!\n\a\a\a");        /*发出警报声*/
    }
    return 0;
}










运行此程序,屏幕上显示为:

Input your password:

用户从键盘输入为:njupt #<回车>

程序输出结果为:

Pass!

再次运行程序,屏幕上显示为:

Input your password:

用户从键盘输入为:well#<回车>

程序输出结果为:

Error!

说明

本例中,密码检验规则的实现在check函数中,如果规则有变,只需要修改函数,

其他部分不用改动。check函数中使用的字符串结束的判断方法,是常用的方法。

main函数中,用循环实现对字符数组的逐个读入,当读入的字符为’#’时退出循环。

当然也可以用gets(str)实现字符串的整体输入。

 

4.字符串的排序

打开一本英文字典,里面的单词是按照英文字母顺序来排列的,如果我们自己设计一个单词本,

也希望以字母的顺序排列,以方便查询,这就涉及到多个字符串的排序问题。多个字符串排序,

前面介绍的冒泡排序、选择排序都可以用,只不过参加排序的是字符串。

而每一个字符串是用一维数组或一级指针来管理的,这样,

就使得字符串的排序比一批整数的排序复杂。

【例8.8】多个字符串的排序。将主函数中给定的多个字符串按由小到大的顺序排序,输出排序后的结果。

分析:

本例实际要完成的是排序操作,只是排序的对象变成了字符串。

下面的实现采用选择法排序,排序思想不再赘述。

定义二维字符数组管理多个字符串,每一个串是用一个一维的字符数组来实现的。

这时,待排序的字符串这样定义:

char string[][10]={"FORTRAN","PASCAL","BASIC","C"};

排序函数需要进行一定次数的交换字符串的操作。

/*li08_08:字符串排序示例*/
#include<stdio.h>
#include<string.h>
/ *函数功能:  对多个字符串排序
 函数入口参数: 列长度为10的行指针,
 用来接受实参传递过来的二维字符数组
 函数返回值: 无
* /

void sort(char (*str)[10],int n)
{
    char temp[20];
    int i,j,k;
    for(i=0;i<n-1;i++)
    {
        k=i;
        for(j=i+1;i<n;j++)
        {
            if(strcmp(str[k],str[j])>0)    / *比较字符串大小* /
            {
                k=j;        / *得到本趟最小字符串的下标* /
            }
        }
        if(k!=i)        / *交换字符串内容,保证本趟最小串到位* /
        {
            strcpy(temp,str[i]);
            strcpy(str[i],str[k]);
            strcpy(str[k],temp);
        }
    }
}

int main()
{
    char string[][10]={"FORTRAN","PASCAL","BASIC","C"}; /*二维字符数组存储4个字符串*/
    int i,nNum=4;
    sort(string,nNum);
    for(i=0;i<nNum;i++)
    {
        printf("%s\n",string[i]); / *string[i]表示二维字符数组中第i个字符串的首地址* /
    }
    return 0;
}

说明

该方法中定义的是二维字符数组,数组名是指针常量,不可以改变其指向,

但可以改变存储的内容。在排序过程中,用strcpy交换存储空间的内容。

排序前后的存储示意图如图8.10所示。

多个字符串还可以通过定义一维字符指针数组实现,请查阅相关参考书。

对此题,有兴趣的读者可以尝试用一维字符指针数组完成排序。

 

8.4带参的main函数

本节要点:

• main函数中两个形参的含义

• 命令行执行方式传实参给main中的形式参数

 

之前程序的main函数均不带形式参数,

事实上,main函数也可以有形式参数,其参数个数固定是两个,各有意义。

main函数是程序运行的入口函数,因此其调用方式将涉及命令行环境下运行程序,

用命令行传递实参给main函数的形式参数。首先,简单介绍一下命令行。

(1)命令行。

假设有一个非常简单的C语言源程序:simple.c,代码如下:

#include<stdio.h>
int main()
{
    printf("One world one dream!\n");
    return 0;
}

在VS 2010下经过编译、链接后生成simple.exe可执行文件。有以下两种方式运行该文件。

① 编译环境中运行:这是通常采用的方法。

即在 VS 2010环境下直接选择二级菜单项“开始执行(不调试)”执行simple.exe,得到输出。

② 命令行中运行:

回到操作系统的仿DOS界面下,进入simple.exe所在的文件夹,然后在命令提示符下输入:

simple<回车>同样可以执行simple.exe运行程序,这就是命令行。

当然,这条命令中只有命令名,即simple,而没有实际参数。

 

有些操作系统,如 UNIX、MS-DOS 允许用户在命令行中以带参的形式启动程序,

程序按照一定的方式处理这些参数,这就是命令行参数。

带有参数的命令行的形式如下:命令名 实参1 实参2 … 实参n如对上述程序,

在命令提示符下输入:simple world<回车>

那么,world 就是通过命令行传入的第一个实参,但是程序运行结果没有变化,

因为该程序的主函数没有形参,因此无法接受从命令行传入的参数。

 

(2)带参的main( )函数。

在C语言程序中,主函数可以有两个参数,用于接受命令行参数,

带参数的 main的函数原型为:

int main(int argc,char **argv);

或者

int main(int argc,char *argv[]);

这里,第1个形参argc用来接收命令行参数(包括命令本身)个数

第2个形参argv接收以字符串常量形式存放的命令行参数(命令本身作为第1个实参传给argv[0])。

例如上面提到的调用:simple world<回车>这时,

形参argc的值为2,argv[0]的值为“simple”,argv[1]的值为“world”。

 

【例8.9】编写程序,将所有的命令行参数(不包括命令本身)在屏幕的同一行输出。

/*li08_09.c:main函数命令行参数示例*/
#include<stdio.h>
int main(int argc,char **argv)
{
    int i;
    for(i=1;i<argc;i++)        / *下标0对应的是命令行字符串本身,根据题意不输出* /
    {
        printf("%8s",argv[i]);
    }
    printf("\n");
    return 0;
}

说明

该程序经过编译、链接后生成了complex.exe文件,将它先复制到E盘根目录下,

然后在DOS提示符后输入命令行,得到输出结果。

E:\>complex One world one dream<回车> / *这是输入的命令行* /

One  world  one dream  / *这是输出结果* /

输入以上命令行后,main函数的形参中的值如图8.11所示。

意使用命令行的程序不能在VS 2010环境下直接执行,必须回到命令行状态,输入命令行才可以。

 

8.5 综合应用实例——单词本管理

本节要点:

• 菜单实现方法

• 函数之间的关系和参数传递

实际应用中离不开字符串的操作,字符串的存储依赖于数组,

第6章中数组的操作方法都可以用在字符数组中,而第7章用指针访问数组的方法也适用于字符数组。

本章最后给出一个综合示例,对若干个字符串进行多种操作,让读者可以进一步理解字符串的操作。

 

【例8.10】编写程序完成单词本的管理,

包括在单词本中新增单词、删除单词、查询单词和显示所有单词的功能。

分析:本案例要求实现单词新增、删除、查询和显示4个功能,可以定义4个函数完成相应的功能。

主函数中通过菜单设置调用不同的函数完成不同的功能。功能模块如图8.12所示。

多个单词存放在二维字符数组中,为了在函数之间共享单词,用行指针变量作为形参进行传址操作。

/*li0810.c:单词本管理示例*/
#include<stdio.h>
#include<string.h>
#define SIZE 100    / *最多可存储的单词数目* /

int addword(char p[][20],int n);
int findword(char p[][20],int n,char *f);
int delword(char p[][20],int n,char *f);
void display(char p[][20],int n);
void menu();

int main()
{
    char myword[100][20];
    char word[20];
    char choice;
    int count=0;
    
    int pos=-1;
    
    do
    {
        menu();
        printf("Please input your choice:");
        scanf("%c",&choice);
        getchar();     / *去掉多余的回车字符* /
        switch(choice)
        {
            case '1':
                count=addword(myword,count);
            break;
            case '2':
                printf("Please input what you are find:");
                gets(word);
                pos=findword(myword,count,word);       / *查找单词在单词本中的位置* /
                if(pos!=-1)
                {
                    printf("It's the %d word\n",pos+1);
                }
                else
                {
                    printf("it's not in myword list!\n");
                }
            break;
            case '3':
                printf("Please input what you want to delete:");
                gets(word);
                count=delword(myword,count,word);
            break;
            case '0':
                choice=0;
            break;
            default:
                printf("Error input,please input your choice again!\n");
            
        }
    }while(choice);
    return 0;
}

 / *函数功能:  菜单显示
   函数入口参数:无
   函数返回值: 无
* /

void menu()
{
    printf("        --------1.增加单词--------\n");
    printf("        --------2.查询单词--------\n");
    printf("        --------3.删除单词--------\n");
    printf("        --------4.显示单词--------\n");
    printf("        --------0.退出系统--------\n");
}
 / *函数功能:  从键盘上输入单词并统计单词个数
   函数入口参数:两个形式参数分别是行指针变量和单词个数变量
   函数返回值: 整型,读入的单词个数
  * /

int addword(char p[][20],int n)
{
    int i,j;
    int pos=-1;
    char flag='y';
    char tmp[20];
    while(flag=='y'||flag=='Y')
    {
        if(n==SIZE)
        {
            printf("Word list is full\n"); / *单词表已满,不能再增加* /
            break;
        }
        else
        {
            printf("Input your word:");
            gets(tmp);
            pos=findword(p,n,tmp);    / *判断待增加的单词是否已经存在* /
            if(pos!=-1)
            {
                printf("the word is exit!\n");
                break;
            }
            else
            {
                if(n)    / *如果单词本中已有单词,需要按字典顺序插入单词* /
                {
                    for(i=0;i<n&&strcmp(tmp,p[i])>0;i++);/ *查找待插入的位置i,循环停止时的i就是* /
                    for(j=n;j>i;j--)    / *用递减循环移位,使i下标元素可被覆盖* /
                    {
                        strcpy(p[j],p[j-1]);
                    }
                    strcpy(p[i],tmp);    / *数组的i下标元素值为插入新增单词* /
                    n++;
                }
                else        *插入第1个单词* /
                {
                    strcpu(p[0],tmp);
                    n=1;
                }
            }
            printf("Another word?(y/n):");
            scanf("%c",&flag);
            getchar();         / *去掉多余的回车字符* /
        }
    }
    return n;
}

 / *函数功能:  从多个单词里寻找某一个单词是否存在以及对应位置
   函数入口参数:3个形式参数分别是行指针变量、单词个数变量、待查找单词的字符串
   函数返回值: 整型,如果找到,返回找到的单词的下标,如果找不到返回-1
  * /
int findword(char p[][20],int n,char *f)
{
    int i;
    int pos=-1;
    for(i=0;i<n;i++)
    {
        if(!strcmp(p[i],f))
        {
            pos=i;
            break;
        }
    }
    return pos;
}

 / *函数功能:  从多个单词的词库中删除某一个指定的单词
   函数入口参数:3个形式参数分别是行指针变量、单词个数变量、待删除单词的字符串
   函数返回值: 整型,返回删除之后的单词个数
  * /

int delword(char p[][20],int n,char *f)
{
    int i;
    int pos=-1;
    pos=findword(p,n,f);
    if(pos==-1)
    {
        printf("It's not in myword list!\n");
    }
    else{
        for(!=pos;i<n-1;i++)
        {
            strcpy(p[i],p[i+1]);
        }
        n=n-1;
    }
    return n;
}

void display(char p[][20],int n)
{
    int i;
    if(n)
    {
        for(i=0;i<n;i++)
        {
            puts(p[i]);
        }
    }
    else
    {
        printf("There is no word in myword list!\n");
    }
}

                


运行该程序,得到菜单提示:-------- 1. 增加单词 --------------- 2. 查询单词 --------------- 3. 删除单词 --------------- 4. 显示单词 --------------- 0. 退  出 -------Please input your choice: 1 <回车> / *首次运行增加单词* /Input your word:good<回车> / *输入单词good* /Another word?(y/n):y<回车> / *选择继续输入* /Input your word: bad<回车> / *输入单词bad* /Another word?(y/n):n<回车>-------- 1. 增加单词 --------------- 2. 查询单词 --------------- 3. 删除单词 --------------- 4. 显示单词 --------------- 0. 退  出 -------Please input your choice: 4 <回车> / *选择显示所有单词的功能* /badgood-------- 1. 增加单词 --------------- 2. 查询单词 ---------------- 3. 删除单词 ---------------- 4. 显示单词 ---------------- 0. 退  出 -------Please input your choice: 2<回车>  /*选择查询功能* /Please input what you are find:bad<回车>It's the 1 word-------- 1. 增加单词 --------------- 2. 查询单词 --------------- 3. 删除单词 --------------- 4. 显示单词 --------------- 0. 退  出 -------Please input your choice: 1<回车> /*选择增加单词功能,并按序插入* /Input your word:great<回车>Another word?(y/n):n<回车>-------- 1. 增加单词 --------------- 2. 查询单词 --------------- 3. 删除单词 --------------- 4. 显示单词 --------------- 0. 退  出 -------Please input your choice: 4 <回车>  / *再一次选择显示所有单词的功能* /badgoodgreat-------- 1. 增加单词 --------------- 2. 查询单词 --------------- 3. 删除单词 --------------- 4. 显示单词 --------------- 0. 退  出 -------Please input your choice:3 <回车> /*选择删除单词的功能* /Please input what you want to delete:good <回车>-------- 1. 增加单词 --------------- 2. 查询单词 --------------- 3. 删除单词 --------------- 4. 显示单词 --------------- 0. 退  出 -------Please input your choice: 4 <回车>  / *再一次选择显示所有单词的功能* /badgreat-------- 1. 增加单词 --------------- 2. 查询单词 --------------- 3. 删除单词 --------------- 4. 显示单词 --------------- 0. 退  出 -------Please input your choice: 0<回车> / *退出程序* /

 

说明

该程序有多个功能模块,分别由相应的函数调用完成,main函数的主要作用就是进行各个函数的调用和组合。这里面有一个需要保持一致的变量——单词本中单词的数量,因此只要进行增、删操作都要修改变量的值。本程序编写过程中要注意如下一些细节问题。

① addword函数首次添加一个单词作为数组的第一个元素,后面再增加单词时要注意检查拟增加的单词是否已存在,如果已存在则不加入以避免重复。另外,增加单词后为保证原来的顺序性,首先要用循环确定插入的位置,注意字符串比较函数strcmp的运用。② 为保证程序的完备性,必须做相应的判断,如addword函数中对单词数量的判断,避免数组越界。又如,delword函数中要判断需要删除的单词是否在单词本中,不在的话无法进行删除操作,要给出相应的提示。

③ 注意程序中多次用到getchar()函数,这是为了减少多余字符的输入。因为程序运行过程中需要从键盘输入数据,如菜单的选项等,每次输入都会以回车符作为输入的结束,而字符输入函数如gets()、scanf("%c")也会把回车当成正常的字符读入,这样可能无法得到需要的数据。因此每次输入后将这个不必要的回车符用getchar();“吃掉”,免得对后续操作造成影响。

④ 本例中字符串的插入、查询、删除、遍历和第6章数组的操作方法是一样的,但要注意,字符串处理时必须用<string.h>中的字符串处理函数,如strcpy、strcmp等函数。为了保证程序的正确性,应该对程序的各个可能分支进行测试,但限于篇幅,本例仅给出程序运行的部分结果。

 

8.6 本章小结本章主要讲解了C语言中字符串的相关知识。C语言中一般通过字符数组和字符指针进行字符串的操作。本章介绍了对字符串进行输入、输出、访问等操作的方法,以及常用的字符串处理函数。并列举了回文判断、统计单词出现次数、密码验证、字符串排序这4个字符串应用实例。最后,本章给出了一个综合范例——单词本管理,希望读者能够进一步理解字符串的操作,理解字符数组、字符指针在函数中的应用方法。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值