C Primer Plus 第11章 字符串和字符串函数 11.1字符串表示和字符串I/O

固然,最基本的您已经知道了:字符串(character string)是以空字符(\o)结尾的char数组。所以,您所学的数组和指针就能够用在字符串上。可是因为字符串的使用很是普遍,C提供了不少专为字符串设计的函数。本章将讨论字符串的特性、声明和初始化方法、如何在程序中输入输出字符串,以及字符串的操做。swift

程序清单11.1  string.c程序数组

//string.c --使用字符串与用户交互
#include <stdio.h>
#define MSG "You must have many talents. Tell me some. "
#define LIM 5
#define LINELEN 81   //最大字符串长度加1
int main(void)
{
    char name[LINELEN];
    char talents[LINELEN];
    int i;
    const char m1[40]="Limit yourself to one line's worth. ";  //初始化一个大小已肯定的char数组
    const char m2[]="If you can't think of anything,fake it. ";//让编译器计算数组大小
    const char *m3="\nEnough about me - what's your name? ";  //初始化一个指针
    const char *mytal[LIM]={"Adding numbers swiftly",
               "Multiplying accurately","Stashing date",
               "Following instructions to the letter",
               "Understanding the C language"}; //初始化一个字符串指针的数组
    printf("Hi!I'm clyde the computer. "" I have many talents.\n");

    printf("Let me tell you some of them.\n");
    puts("What were they? Ah,yes,here's a partial list. ");
    for (i=0;i<LIM;i++)
        puts(mytal[i]);  //打印计算机功能的列表
    puts(m3);
    gets(name);
    printf("Well,%s,%s\n",name,MSG);
    printf("%s\n%s\n",m1,m2);
    gets(talents);
    puts("Let's see if I've got that list: ");
    puts(talents);
    printf("Thanks for the information,%s.\n",name);
    return 0;

}

11.1.1  在程序中定义字符串函数

阅读程序清单11.1时您可能已经注意到,定义字符串的方法不少。基本的办法是使用字符串常量、char数组、char指针和字符串数组。程序应该确保有存储字符串的地方,这一点咱们稍后也会讨论到。spa

1、字符串常量设计

/*quotes.c 把字符串看做指针*/
#include <stdio.h>
int main(void)
{
    printf("%s, %p, %c\n","we","are",*"space farers");
    return 0;
}

%s格式将输出字符串we。%p格式产生一个地址,所以,若是“are”是一个地址,那么%p应该输出字符串中第一个字符的地址(ANSI以前可能用%u 或 %lu)。最后,*“space farers”应该产生所指向的地址中的值,即字符串“space farers”中的第一个字符。真的是这样吗?下面是输出结果:指针

we, 0x0040c010, scode

2、字符串数组及其初始化orm

定义一个字符串数组时,您必须让编译器知道它须要多大空间。一个办法就是指定一个足够大的数组来容纳字符串,下面的声明用指定字符串中字符数初始化一个数组m1:ip

const char m1[40] = "Limit yourself to one line's worth. " ;内存

const代表这个字符串不能够改变

注意标志结束的空字符。若是没有它,获得的就只是一个字符数组而不是一个字符串。

指定数组大小时,必定要确保数组元素数比字符串长度至少大1(多出来的一个元素用于容纳空字符)。

未被使用的元素均被自动初始化为0.这里的0是char形式的空字符,而不是数字字符0.

一般,让编译器决定数组大小 是很方便的。回忆一下,在进行初始化声明时若是活力了数组大小,则该大小由编译器决定。

const char m2 [ ] = "If you can't think of anything,fake it . ";

初始化字符数组是体现由编译器决定数组大小 的优势的又一个例子。这是由于字符串处理函数通常不须要知道数组的大小,由于它们可以简单的经过查找空字符来肯定字符串的结束。

请注意程序必须为数组name明确分配大小:

#defeine LINELEN 81

......

char name [LINELEN];

因为直到程序运行时才能读取name的内容,因此除非您说明,编译器没法预先知道须要为它预留多大空间。声明一个数组时,数组 的大小必须为整形常量,而不能是运行时获得的变量值。

和任何数组同样,字符数组名也是数组首元素的地址。所以下面的式子对于数组m1成立:

m1 == &m1[0] , *m1 == 'L' , and *(m1+1) == m1[1] == 'i'

的确,可使用指针符号创建字符串。例如,程序清单11.1中使用了下面的声明:

const char *m3 = "\nEnough about me - what's your name? " ;

这个声明和下面的声明的做用几乎相同:

char m3[ ] = "\nEnough about me - what's your name? " ;

上面两个声明m3是一个指向给定字符串的指针。在两种状况下,都是被引用的字符串自己决定了为字符串预留的存储空间的大小。尽管如此,这两个形式并不彻底相同。

3、数组和指针

那么,数组和指针形式的不一样之处是什么呢?

数组形式(m3 [ ])在计算机内存中被分配 一个有38个元素的数组,每一个元素都被初始化为相应的字符。一般,被引用的字符存储在可执行文件的数据段部分;当程序被加载到内存中时,字符串也被加载到内存中。被引用的字符串被称为位于静态存储区。可是在程序开始运行后才为数组分配存储空间。这时候,把被引用的字符串复制到数组中。此后,编译器会把数组 名m3看做是数组首元素&m3[0]的同义词。这里重要的一点是,在数组形式中m3是个地址常量,您不能更改m3,由于这意味着更改数组存储的位置(地址)。可使用运算符m3 + 1 来标识下一个元素,可是不容许使用++m3。增量运算符只能用在变量名前,而不能用在常量名前。

指针形式(*m3)也在静态存储区为字符串预留38个元素的空间。此外,一旦程序开始执行,还要为指针变量m3另外预留一个存储位置,以在该指针变量中存储字符串的地址。这个变量初始时指向字符串的第一个字符,可是它是能够变的。所以,能够对它使用增量运算符。例如,++m3将指向第二个字符E。

总之,数组初始化是从静态存储区把一个字符串复制给数组,而指针初始化只是复制字符串的地址。

这些区别重要与否,主要取决于作什么。

4、数组和指针的差异

咱们研究一下初始化一个存放字符串的数组和初始化一个指向字符串的指针这二者的不一样(指向字符串实际上是指向字符串的第一个字符)。例如,考虑下面的两个声明:

char heart [ ] = "I love Tillie!" ;

char *head = "I love Millie!" ;

主要差异在于,数组名heart是个常量,而指针heart是个变量。实际使用中又有什么不一样呢?

首先,二者均可以使用数组符号

for(i=0;i<6;i++)
    putchar(heart[i]);
putchar('\n');
for(i=0;i<6;i++)
    putchar(head[i]);
putchar('\n');

如下是输出:

I love
I love

其次二者均可以使用指针加法

for(i=0;i<6;i++)
    putchar(*(heart+i));
putchar('\n');
for(i=0;i<6;i++)
    putchar(*(head+i));
putchar('\n');

输出不变

可是,只有指针可使用增量运算符

while(*(head)!='\o')
    putchar(*(head++));

产生以下输出: I love Millie!

假定但愿head与heart相同,能够这样作:

head = heart ; /*如今head指向数组heart*/

可是不能这样作:

heart = head ;  /*非法语句*/

赋值语句的左边必须是一个变量或者更通常的说是一个左值(lvalue),好比*p_int。顺便提一下,head = heart;不会使Millie字符串消失,它只是改变了head中存储的地址。可是,除非已在别处保存了“I love Millie!"的地址,不然当head指向另外一个地址时就没有办法访问这个字符串了。

能够改变heart中的信息,方法是访问单个的数组元素:

heart[7] = 'M' ;  或者  *(heart + 7) = 'M' ;

数组的元素是变量(除非声明时带有关键字const),可是数组名不是变量。

让咱们回到对指针初始化的讨论:

char * word = "frame" ;

能够用指针改变这个字符串吗?

word[1] = 'l' ;

您的编译器可能会容许上厕所状况,但按照当前的C标准,编译器不该该容许这样作。这种语句可能会致使内存访问错误。缘由在于编译器可能选择内存中的同一个单个的拷贝,来表示全部相同的字符串文字。例如,下面的语句都指向字符串“Klingon"的同一个单独的内存位置。

char * p1 = "Klingon" ;
p1[0] = 'F' ;//ok?
printf("Klingon");
printf(": Beware the %ss!\n","Klingon");

这就是说,编译器能够用栅的地址来替代每一个“Klingon"实例。若是编译器使用这种单个拷贝法而且容许把p1[0]改成‘F‘的话,那将影响到全部对这个字符串的使用。因而,打印字符串文字“Klingon"的语句将为显示为“Flingon"。

所以,建议的作法是初始化一个指向字符串文字的指针时使用const修饰符:

const char * p1 = "Klingon" ;  //推荐作法

用一个字符串文字来初始化一个非const数组,则不会致使此类问题,由于数组从最初的字符串获得了一个拷贝。

5、字符串数组

字符串的初始化遵循数组初始化的规则。花括号里那部分的形式以下:

{{...},{...},...,{...}} ;

省略号表明咱们懒得键入的内容。关键之处是第一对双引号对应着一对花括号,用于初始化第一个字符串指针。第二对双引号初始化第二个指针,等等。相邻字符串要用逗号隔开。

另外一个方法就是创建一个二维数组:

char mytal_2[LIM] [LINLIM] ;

在这里mytal_2是一个5个元素的数组,每个元素自己又是一个81个char的数组。在这种状况下,字符串自己也被存储在数组里。二者差异之一就是第二种方法选择创建了一个全部行的升序都相同的矩形(rectangular)数组。也就是说,每个字符串都用81个元素来存放。而指针数组创建的是一个不规则的数组,每一行的长度由初始化字符串决定:

char * mytal [LIM] ;

这个不规则数组不浪费任何存储空间。

另一个区别就是mytal和mytal_2的类型不一样:mytal是一个指向char的指针的数组,而mytal_2是一个char数组的数组。一句话说,mytal存放5个地址,而mytal_2存放5个完整的字符数组。

11.1.2  指针和字符串

绝大多数的C字符串操做事实上使用的都是指针。例如,考虑一下程序清单11.3所示的用于起到指示做用的程序。

程序清单 11.3 p_and_s.c程序

/* p_and_s.c --指针和字符串 */
#include <stdio.h>
int main (void)
{
    char * mesg = "Don't be a fool! ";
    char * copy;

    copy = mesg;
    printf("%s\n",copy);
    printf("mesg = %s; &mesg = %p; value = %p\n",
           mesg,&mesg,mesg);
    printf("copy = %s; &copy = %p; value = %p\n",
           copy,&copy,copy);
    return 0;
}

输出结果以下:

Don't be a fool!
mesg = Don't be a fool! ; &mesg = 0022FF4C; value = 00403024
copy = Don't be a fool! ; &copy = 0022FF48; value = 00403024

首先,mesg和copy以字符串形式输出(%s)。这里并无发生奇怪的事。

每一行的下一项是指定指针的地址。mesg和copy这两个指针分别存放在位置0022FF4C和0022FF48。

注意最后一项,即value。它是指定指针的值。指针的值是该指针中存放的地址,能够看到mesg指向00403024,copy也是如此。所以,字符串自己没有被复制。语句copy = mesg ;所作的事情就是产生指向同一个字符串的第二个指针。

为何如此谨慎行事?为何不干脆复制整个字符串?好了,问一下本身哪种方式更有效率?复制一个地址仍是复制50个单个的元素?一般只有地址才是程序执行所须要的。