文档章节

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

idreamo
 idreamo
发布于 2016/08/17 06:29
字数 3211
阅读 25
收藏 0
点赞 1
评论 0

当然,最基本的您已经知道了:字符串(character string)是以空字符(\o)结尾的char数组。因此,您所学的数组和指针就可以用在字符串上。但是由于字符串的使用非常广泛,C提供了很多专为字符串设计的函数。本章将讨论字符串的特性、声明和初始化方法、如何在程序中输入输出字符串,以及字符串的操作。

程序清单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指针和字符串数组。程序应该确保有存储字符串的地方,这一点我们稍后也会讨论到。

一、字符串常量

/*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, s

二、字符串数组及其初始化

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

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是一个指向给定字符串的指针。在两种情况下,都是被引用的字符串本身决定了为字符串预留的存储空间的大小。尽管如此,这两个形式并不完全相同。

三、数组和指针

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

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

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

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

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

四、数组和指针的差别

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

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数组,则不会导致此类问题,因为数组从最初的字符串得到了一个拷贝。

五、字符串数组

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

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

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

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

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个单个的元素?通常只有地址才是程序执行所需要的。

© 著作权归作者所有

共有 人打赏支持
idreamo
粉丝 12
博文 139
码字总数 224743
作品 0
青岛
产品经理
C Primer Plus 第11章 字符串和字符串函数 11.2 字符串输入

11.2.1 创建存储空间 要做的第一件事是建立一个空间以存放读入的字符串。 最简单的办法就是在声明中明确指出数组的大小: char name[81] ; 现在的name是一个已经分配81字节存储块的地址。另一...

idreamo ⋅ 2016/08/19 ⋅ 0

C Primer Plus 第11章 11.7 ctype.h字符函数和字符串

第7章“C控制语句 分支和跳转”介绍了ctype.h系列字符相关的函数。这些函数不能被 应用于整个字符串,但是可以被应用于字符串中的个别字符。程序清单11.26定义了一个函数,它把toupper( )函数...

idreamo ⋅ 2016/08/27 ⋅ 0

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

C库提供了许多处理字符串的函数:ANSI C 用头文件string.h给出这些函数的原型。下面是一些最有用和最常用的函数:strlen() 、strcat()、strncat() 、strcmp() 、strncmp() 、strcpy()、 strn...

idreamo ⋅ 2016/08/25 ⋅ 0

C Primer Plus 第11章 字符串和字符串函数 11.3 字符串输出

11.3.1 puts( )函数 puts( )函数的使用很简单,只需要给出字符串参数的地址。程序清单11.8列出了输出字符串的多种方式。 程序清单11.8 put_out.c程序 输出如下: 注意,每个字符串都单行显示...

idreamo ⋅ 2016/08/19 ⋅ 0

【书评:Oracle查询优化改写】第五至十三章

【书评:Oracle查询优化改写】第五至十三章 一.1 BLOG文档结构图 一.2 前言部分 一.2.1 导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩...

技术小胖子 ⋅ 2017/11/15 ⋅ 0

C Primer Plus 第11章 11.9 把字符串转换为数字

数字即能以字符串形式也能 以数字形式存储。以字符串形式存储数字就是存储数字字符。例如,数字213即能以数字'2'、‘1’、‘3’、‘0’的形式存储在一个字符串数组中。以数字形式存储213意味...

idreamo ⋅ 2016/08/29 ⋅ 0

C Primer Plus 第11章 字符串和字符串函数 11.4 自定义字符串I/O函数

不一定要使用标准C库的函数进行输入和输出。如果不具备或者不喜欢它们,您可以自行定义,在getchar() putchar()的基础上建立自己的函数。 假定您希望有一个类似puts()但并不自动添加换行符的...

idreamo ⋅ 2016/08/20 ⋅ 0

C Primer Plus 第11章 11.6 字符串例子:字符串排序

我们来解决一个把字符串按字母表顺序排序的问题。准备花名册、建立索引以及很多其他情况下都会用到字符串排序。这个程序的一个主要工具就是strcmp( ),因为可以使用这个函数来决定两个字符串...

idreamo ⋅ 2016/08/27 ⋅ 0

C Primer Plus 第11章 11.8 命令行参数

现代的图形界面出现之前是命令行界面。Dos和Unix就是例子。命令行(command line)是在一个命令行环境下,用户输入的用于运行程序的行。假定有一个程序在名为fuss 的文件中,那么在UNIX下运行该...

idreamo ⋅ 2016/08/27 ⋅ 0

关东升的《从零开始学Swift》第2版已经出版

关东升的《从零开始学Swift》第2版已经出版 大家好: 苹果2015WWDC大会发布了Swift2.0,它较之前的版本Swift1.x有很大的变化,所以我即将出版《从零开始学Swift》 《从零开始学Swift》将在《...

tony关东升 ⋅ 2016/02/24 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

使用 vue-cli 搭建项目

vue-cli 是一个官方发布 vue.js 项目脚手架,使用 vue-cli 可以快速创建 vue 项目,GitHub地址是:https://github.com/vuejs/vue-cli 一、 安装 node.js 首先需要安装node环境,可以直接到中...

初学者的优化 ⋅ 16分钟前 ⋅ 0

设计模式 之 享元模式

设计模式 之 享元模式 定义 使用共享技术来有效地支持大量细粒度对象的复用 关键点:防止类多次创建,造成内存溢出; 使用享元模式来将内部状态与外部状态进行分离,在循环创建对象的环境下,...

GMarshal ⋅ 31分钟前 ⋅ 0

SpringBoot集成Druid的最简单的小示例

参考网页 https://blog.csdn.net/king_is_everyone/article/details/53098350 建立maven工程 Pom文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM......

karma123 ⋅ 今天 ⋅ 0

Java虚拟机基本结构的简单记忆

Java堆:一般是放置实例化的对象的地方,堆分新生代和老年代空间,不断未被回收的对象越老,被放入老年代空间。分配最大堆空间:-Xmx 分配初始堆空间:-Xms,分配新生代空间:-Xmn,新生代的大小一...

算法之名 ⋅ 今天 ⋅ 0

OSChina 周日乱弹 —— 这么好的姑娘都不要了啊

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @TigaPile :分享曾惜的单曲《讲真的》 《讲真的》- 曾惜 手机党少年们想听歌,请使劲儿戳(这里) @首席搬砖工程师 :怎样约女孩子出来吃饭,...

小小编辑 ⋅ 今天 ⋅ 8

Jenkins实践3 之脚本

#!/bin/sh# export PROJ_PATH=项目路径# export TOMCAT_PATH=tomcat路径killTomcat(){pid=`ps -ef | grep tomcat | grep java|awk '{print $2}'`echo "tom...

晨猫 ⋅ 今天 ⋅ 0

Spring Bean的生命周期

前言 Spring Bean 的生命周期在整个 Spring 中占有很重要的位置,掌握这些可以加深对 Spring 的理解。 首先看下生命周期图: 再谈生命周期之前有一点需要先明确: Spring 只帮我们管理单例模...

素雷 ⋅ 今天 ⋅ 0

zblog2.3版本的asp系统是否可以超越卢松松博客的流量[图]

最近访问zblog官网,发现zlbog-asp2.3版本已经进入测试阶段了,虽然正式版还没有发布,想必也不久了。那么作为aps纵横江湖十多年的今天,blog2.2版本应该已经成熟了,为什么还要发布这个2.3...

原创小博客 ⋅ 今天 ⋅ 0

聊聊spring cloud的HystrixCircuitBreakerConfiguration

序 本文主要研究一下spring cloud的HystrixCircuitBreakerConfiguration HystrixCircuitBreakerConfiguration spring-cloud-netflix-core-2.0.0.RELEASE-sources.jar!/org/springframework/......

go4it ⋅ 今天 ⋅ 0

二分查找

二分查找,也称折半查找、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于...

人觉非常君 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部