栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

11 字符串和字符串函数

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

11 字符串和字符串函数

1 表示字符串和字符串I/O

字符串是以空字符’’结尾的char类型数组。不过,由于字符串十分常用,所以C提供了许多专门用于处理字符串的函数。

程序11.1 演示了在程序中表示字符串的几种方式:

程序11.1 strings1.c程序

// strings1.c
#include 
#define MSG "I am a symbolic string constant."
#define MAXLENGTH 81

int main(void){
 	char words[MAXLENGTH] = "I am a string in an array.";
 	const char * pt1 = "Something is pointing at me.";
 	puts("Here are some strings:");
 	puts(MSG);
 	puts(words);
 	puts(pt1);
 	words[8] = 'p';
 	puts(words);
 	return 0;
} 

和pirntf函数一样,puts函数也属于stdio.h系列的输入/输出函数。但是,与printf不同的是,puts函数只显示字符串,而且自动在显示的字符串末尾加上换行符。下面是该程序的输出:

Here are some strings:
I am an old-fashioned symbolic string constant.
I am a string in an array.
Something is pointing at me.
I am a spring in an array. 

在后面的内容中,我们首先分析一下该程序中定义字符串的几种方法,然后再讲解把字符串读入程序涉及的一些操作,最后学习如何输出字符串。

1.1 在程序中定义字符串 1.1.1 字符串字面量(字符串常量)




1.1.2 字符串数组和初始化





1.1.3 数组和指针

待补充

2 字符串输入

如果想把一个字符串读入程序,首先必须预留储存该字符串的空间,然后用输入函数获取该字符串。

2.1 分配空间

char *name;
scanf("%s",name);


2.2 不安全的gets函数

在读取字符串时,scanf()和转换说明符%s只能读取一个单词。可是在程序中经常要读取一整行输入,而不仅仅是一个单词。许多年前,get函数就用于处理这种情况。get函数简单易用,它读取整行输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在这些字符的末尾添加一个空字符使其成为一个C字符串。它经常和puts函数配对使用,该函数用于显示字符串,并在末尾添加换行符。程序11.6演示了这两个函数的用法:

程序11.6 getsputs.c

#include 
#define STLEN 81

int main(void){
 	char words[STLEN];
 	
 	puts("Enter a string, please.");
 	gets(words);
 	printf("Your string twice:n");
 	printf("%sn", words);
 	puts(words);
 	puts("Done.");
 	
 	return 0;
}

下面是该程序在某些编译器(至少是旧式编译器)中的运行示例:

Enter a string, please.
I want to learn about string theory!
Your string twice:
I want to learn about string theory!
I want to learn about string theory!
Done. 

整行输入(除了换行符) 都被储存在words中,puts(words)和printf(“%sn”,words)的效果相同。

下面是该程序在另一个编译器中的输出示例:

Enter a string, please.
warning: this program uses gets(), which is unsafe.
Oh, no!
Your string twice:
Oh, no!
Oh, no!
Done. 

编译器在输出中插入了一行警告信息。每次运行这个程序,都会显示这行消息。但是,并非所有的编译器都会这样做。其他编译器可能在编译过程中给出警告,但不会引起我们的注意。

上面这种使用方式的问题处在gets唯一的参数是words,它无法检查数组是否装得下输出行。数组名最终会被转换成该数组首元素的地址,因此,gets函数只知道数组的开始处,并不知道数组中有多少个元素。

Enter a string, please.
warning: this program uses gets(), which is unsafe.
I think I'll be just fine.
Your string twice:
I think I'll be just fine.
I think I'll be just fine.
Done.
Segmentation fault: 11 

在UNIX系统中,“Segmentation fault”说明程序试图访问未分配的内存。

gets函数的不安全行为造成了安全隐患,过去,有些人通过系统编程,利用gets插入和运行一些破坏系统安全的代码。

不久,C编程社区的许多人都建议在编程时摒弃gtes。制定C99标准的委员会也把这些建议放入了标准,承认了gets的问题并建议不要再使用它。尽管如此,在标准中保留gets也合情合理,因为现有程序中含有大量使用该函数的代码。而且,只要使用得当,它的确是一个很方便的函数。

但是好景不长,C11标准委员会采取了更强硬的态度,直接从标准中废除了gets函数。但在实际应用中,编译器为了兼容以前的代码,大部分都继续支持gets函数。

2.3 gets的替代品

过去通常使用fgets来代替gets,fgets函数稍微复杂些,在处理输入方面与gets略有不同。C11标准新增的gets_s()函数也可以代替gets。该函数与gets函数更接近,而且可以替换现有代码中的gets。但是,它是stdio.h输入/输出函数系列的可选拓展,所以支持C11的编译器也不一定支持它。

2.3.1 fgets函数(和fputs)




程序11.7 fgets1.c

#include 
#define STLEN 14

int main(void){
 	char words[STLEN];
 	
 	puts("Enter a string, please.");
 	fgets(words, STLEN, stdin);
 	printf("Your string twice (puts(), then fputs()):n");
 	puts(words);
 	fputs(words, stdout);
 	puts("Enter another string, please.");
 	fgets(words, STLEN, stdin);
 	printf("Your string twice (puts(), then fputs()):n");
 	puts(words);
 	fputs(words, stdout);
 	puts("Done.");
 	return 0;
} 

程序的输出示例如下:

Enter a string, please.
apple pie
Your string twice (puts(), then fputs()):
apple pie
 
apple pie
Enter another string, please.
strawberry shortcake
Your string twice (puts(), then fputs()):
strawberry sh
strawberry shDone. 
5 字符串函数

5.1 strlen函数


程序11.17 test_fit.c

#include 
#include  

void fit(char *, unsigned int);

int main(void){
 	char mesg[] = "Things should be as simple as possible,"
 " but not simpler.";
 	puts(mesg);
 	fit(mesg,38);
 	puts(mesg);
 	puts("Let's look at some more of the string.");
 	puts(mesg + 39);
 	return 0;
}

void fit(char *string, unsigned int size){
 	if (strlen(string) > size)
 		string[size] = '';
} 

程序输出如下:

Things should be as simple as possible, but not simpler.
Things should be as simple as possible
Let's look at some more of the string.
 but not simpler. 

图11.4以一个更简单的例子演示了上面的过程。在11.17中,fit函数把第39个元素的逗号替换成’’字符。puts函数在这个空字符停止了输出,并忽略了其他字符,但是实际上这些字符还在缓冲区中。在图11.4的例子中,调用puts(mesg+8)可以将剩余字符打印出来。因为mesg+8是mesg[8]的地址,该地址上存储了t。所以puts显示该字符并继续输出直到遇到原来字符串中的空字符。

5.2 strcat函数


程序 11.18 str_cat.c

#include 
#include  
#define SIZE 80

char * s_gets(char * st, int n);

int main(void){
 	char flower[SIZE];
 	char addon[] = "s smell like old shoes.";
 	puts("What is your favorite flower?");
 	if (s_gets(flower, SIZE)){
 		strcat(flower, addon);
 		puts(flower);
 		puts(addon);
 	}
 	else
 		puts("End of file encountered!");
 	puts("bye");
 	return 0;
}

char * s_gets(char * st, int n){
 	char * ret_val;
 	int i = 0;
 	ret_val = fgets(st, n, stdin);
 	if (ret_val){
		while (st[i] != 'n' && st[i] != '')
 			i++;
 		if (st[i] == 'n')
 			st[i] = '';
 		else // must have words[i] == ''
 			while (getchar() != 'n')
 				continue;
 	}
 	return ret_val;
} 

该程序的输出示例如下:

What is your favorite flower?
wonderflower
wonderflowers smell like old shoes.
s smell like old shoes.
bye 

从以上输出可以看出,flower改变了,而addon保持不变。

5.5 strcpy和strncpy函数

如果pts1和pts2都是指向字符串的指针,那么下面语句拷贝的是字符串的地址而不是字符串本身:

pts2=pts1;

如果希望拷贝整个字符串,要使用strcpy函数。程序11.25要求用户输入以q开头的单词。该程序把输入拷贝至一个临时数组中,如果第1个字母是1,程序调用strcpy把整个字符串从临时数组拷贝至目标数组中。strcpy函数相当于字符串赋值运算符。

程序11.25 copy1.c

#include 
#include  // declares strcpy()
#define SIZE 40
#define LIM 5

char * s_gets(char * st, int n);

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 && s_gets(temp, SIZE)){
 		if (temp[0] != 'q')
 			printf("%s doesn't begin with q!n", temp);
 		else{
 			strcpy(qwords[i], temp);
 			i++;
 		}
 	}
 	puts("Here are the words accepted:");
 	for (i = 0; i < LIM; i++)
 	puts(qwords[i]);
 	return 0;
}
 
char * s_gets(char * st, int n){
	char * ret_val;
 	int i = 0;
 	ret_val = fgets(st, n, stdin);
 	if (ret_val){
 		while (st[i] != 'n' && st[i] != '')
 			i++;
 		if (st[i] == 'n')
 			st[i] = '';
 		else // must have words[i] == ''
 			while (getchar() != 'n')
 				continue;
 	}
 	return ret_val;
} 

下面是该程序的运行示例:

Enter 5 words beginning with q:
quackery
quasar
quilt
quotient
no more
no more doesn't begin with q!
quiz
Here are the words accepted:
quackery
quasar
quilt
quotient
quiz 

注意,只有在输入以q开头的单词后才会递增计数器i。

strcpy函数将第二个参数(temp)指向的字符串拷贝至第一个参数(qword[i])指向的数组中。拷贝出来的字符串被称为目标字符串,最初的字符串被称为源字符串,即第一个参数是目标字符串,第二个参数是源字符串。

如果以整型赋值的方式给字符串赋值,那么会报错:

char target[20];
int x;
x = 50; 
strcpy(target, "Hi ho!"); 
target = "So long";  

程序员有责任明确目标数组有足够的空间容纳源字符串的副本,下面的代码存在问题:

char * str;
strcpy(str, "The C of Tranquility"); // a problem

strcy函数把"The C of Tranquility"拷贝到str指向的地址上,但是str未被初始化,所以该字符串有可能被拷贝到任意的地方。

总之,strcpy接受两个字符串指针作为参数,可以把指向源字符串的第二个指针声明为指针、数组名或字符串常量;而指向源字符串副本的的第一个指针应指向一个数据对象(如,数组),且该对象有足够的空间存储源字符串的副本。记住,声明数组将分配储存数据的空间,而声明指针只分配储存一个地址的空间。

5.5.1 strcpy的其他属性

strcpy函数还有两个有用的属性。第一,strcpy函数的返回类型是char *,该函数返回的是第一个参数的值,即一个字符的地址。第二,第一个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分。程序12.26演示了该函数的这两个属性:

程序12.26 copy2.c

#include 
#include  // declares strcpy()
#define WORDS "beast"
#define SIZE 40

int main(void){
 	const char * orig = WORDS;
 	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函数把源字符串中的空字符也拷贝在内。在该例中,空字符覆盖了copy数组中that的第一个t,如图11.5所示。注意,由于第一个参数是copy+7,所以ps指向copy中的第八个元素(下标为7)。因此puts(ps)从该处开始打印字符串。

5.5.2 更谨慎的选择:strncpy


程序 11.27 copy3.c

#include 
#include  
#define SIZE 40
#define TARGSIZE 7
#define LIM 5

char * s_gets(char * st, int n);

int main(void){
 	char qwords[LIM][TARGSIZE];
 	char temp[SIZE];
 	int i = 0;
 	printf("Enter %d words beginning with q:n", LIM);
 	while (i < LIM && s_gets(temp, SIZE)){
 		if (temp[0] != 'q')
 			printf("%s doesn't begin with q!n", temp);
 		else{
 			strncpy(qwords[i], temp, TARGSIZE - 1);
 			qwords[i][TARGSIZE - 1] = '';
 			i++;
 		}
 	}
 	puts("Here are the words accepted:");
 	for (i = 0; i < LIM; i++)
 	puts(qwords[i]);
 	return 0;
}
 
char * s_gets(char * st, int n){
 	char * ret_val;
 	int i = 0;
 	ret_val = fgets(st, n, stdin);
 	if (ret_val){
 		while (st[i] != 'n' && st[i] != '')
 			i++;
 		if (st[i] == 'n')
 			st[i] = '';
 		else // must have words[i] == ''
	 		while (getchar() != 'n')
 				continue;
 	}
 	return ret_val;
}

下面是该程序的运行示例:

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中。因此,如果source中的字符数小于n,则拷贝整个字符串,包括空字符。但是,strncpy拷贝字符串的长度不会超过n,如果拷贝到第n个字符时还未拷贝完整个源字符串,就不会拷贝空字符。所以,拷贝的副本中不一定有空字符。鉴于此,该程序把n设置为比目标数组大小小1,然后把数组最后一个元素设置为空字符。

这样做确保储存的是一个字符串。如果目标空间能容纳源字符串的副本,那么从源字符串拷贝的空字符串便是该副本的结尾;如果目标空间装不下副本,则把副本最后一个元素设置为空字符。

课后习题与答案 3


5


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/991077.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号