IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C Primer Plus 第11章_字符串和字符串函数_代码和练习题 -> 正文阅读

[C++知识库]C Primer Plus 第11章_字符串和字符串函数_代码和练习题

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

字符串是以空字符(\0)结尾的char类型数据。
strings1.c 演示在程序中表示字符串的几种方式

#include <stdio.h>
#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函数只显示字符串,而且在显示的字符串末尾加上换行符 */
	puts(pt1);
	words[8] = 'p';
	puts(words);

	printf("%s, %p, %c\n", "We", "are", *"space farers");

	return 0;
}

在这里插入图片描述

在程序中定义字符串

(1)字符串字面量(字符串常量)
用双引号括起来的内容称为字符串字面量。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中。
字符串常量属于静态存储类别(static storage class),这说明在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次。
用双引号括起来的内容被释为指向该字符串储存位置的指针。
(2)字符串数组和初始化

const char m1[40] = "Limit yourself to one line's worth.";
const char m2[] = "Limit yourself to one line's worth."; /* 建议在指针初始化为字符串字面量时使用const限定符 */

让编译器计算数组的大小只能在初始化数组时。
const char * pt1= “Limit yourself to one line’s worth.”;

注意:数组和指针的区别
数组名m2是常量,指针名pt1是变量。如果打算修改字符串,就不要用指针指向字符串字面量。
初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。

#include <stdio.h>
#define MSG "I'm special"

int main(void)
{
	char ar[] = MSG;
	const char* pt = MSG;
	printf("address of \"I'm special\":%p \n", "I'm special");
	printf("			address ar:%p\n", ar);
	printf("			address pt:%p\n", pt);
	printf("		address of MSG:%p\n", MSG);
	printf("address of \"I'm special\":%p \n", "I'm special");

	return 0;
}

在这里插入图片描述
(4)字符串数组
arrchar.c–指向字符串的指针数据和char类型数组的数组


#include <stdio.h>
#define SLEN 40
#define LIM 5

int main(void)
{
	const char* mytalents[LIM] = {
		"Adding number swiftly",
		"Multiplying accurately", "Stashing data",
		"Following instrcutions to the letter",
		"Understanding the C language"
	};
	char yourtalents[LIM][SLEN] = {
		"Walking in a straight line",
		"Sleeping", "Watching televisions",
		"Mailing letters", "Reading email"
	};
	int i;

	puts("Let's compare talents.");
	printf("%-36s  %-25s\n", "My talents", "Your talents");
	for (i = 0; i < LIM; i++) {
		printf("%-36s  %-25s\n", mytalents[i], yourtalents[i]);
	}
	printf("\nsizeof mytalents: %zd, sizeof yourtalents: %zd\n",
		sizeof(mytalents), sizeof(yourtalents));

	return 0;
}

在这里插入图片描述
综上所述,如果要用数组表示一系列待显示的字符串,请使用指针数组,因为它比二维字符数组的效率高。但是,指针数组也有自身的缺点。mytalents 中的指针指向的字符串字面量不能更改;而yourtalentsde 中的内容可以更改。所以,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针。

11.2 字符串输入

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

gets函数

C11标准委员会采取了更强硬的态度,直接从标准中废除了gets()函数。
问题出在 gets()唯一的参数是 words,它无法检查数组是否装得下输入行。上一章介绍过,数组名会被转换成该数组首元素的地址,因此,gets()函数只知道数组的开始处,并不知道数组中有多少个元素。
“Segmentation fault”(分段错误)似乎不是个好提示,的确如此。在UNIX系统中,这条消息说明该程序试图访问未分配的内存。

#include <stdio.h>
#include <string.h>
#define STLEN 81
int main(void)
{
	char words[STLEN];

	puts("Enter a string, please.");
	gets(words);
	printf("Your string twice:\n");
	printf("%s\n", words);
	puts(words);
	puts("Done.");

	return 0;
}

fgets()和fputs()函数用法

fgets()函数的第2个参数指明了读入字符的最大数量。如果该参数的值是n,那么fgets()将读入n-1个字符,或者读到遇到的第一个换行符为止。
如果fgets()读到一个换行符,会把它储存在字符串中。这点与gets()不同,gets()会丢弃换行符。
fgets()函数的第3 个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin(标准输入)作为参数,该标识符定义在stdio.h中

#include <stdio.h>
#include <string.h>
#define STLEN 14

int main(void)
{
	char words[STLEN];

	puts("Enter a string, please.");
	fgets(words, STLEN, stdin);			/* apple ie\n\0 被储存在数组中 */
	printf("Your string twice(puts(), then fputs()):\n");
	puts(words);
	fputs(words, stdout);		/* fputs()不在字符串末尾添加换行符 */

	puts("Enter another string, please.");
	fgets(words, STLEN, stdin);		/* fgets()只读入了13个字符,并把strawberry sh\0 储存在数组中。 */
	printf("Your string twice(puts(), then fputs()):\n");
	puts(words);		/* puts()函数会在待输出字符串末尾添加一个换行符 */
	fputs(words, stdout);
	puts("Done.");

	return 0;
}

在这里插入图片描述
fgets()函数返回指向 char的指针。如果一切进行顺利,该函数返回的地址与传入的第 1 个参数相同。但是,如果函数读到文件结尾,它将返回一个特殊的指针:空指针(null pointer)。

下面程序演示了一个简单的循环,读入并显示用户输入的内容,直到fgets()读到文件结尾或空行(即,首字符是换行符)。

#include <stdio.h>
#include <string.h>
#define STLEN 10

int main(void)
{
	char words[STLEN];

	puts("Enter strings (empty line to quit):");
	while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n') {
		fputs(words, stdout);
	}
	puts("Done.");
	
	return 0;
}

第一次读 By the wa\0
第二次读 y, the ge\0
最后一次读 tion\n\0在这里插入图片描述
fgets()储存换行符有好处也有坏处。坏处是你可能并不想把换行符储存在字符串中,这样的换行符会带来一些麻烦。好处是对于储存的字符串而言,检查末尾是否有换行符可以判断是否读取了一整行。如果不是一整行,要妥善处理一行中剩下的字符。
首先,如何处理掉换行符?一个方法是在已储存的字符串中查找换行符,并将其替换成空字符:

while(words[i] != '\n') {		// 假设\n在words中
	i++;
}
words[i] = '\0';

其次,如果仍有字符串留在输入行怎么办?一个可行的办法是,如果目标数组装不下一整行输入,就丢弃那些多出的字符:

while(getchar() != '\n') {		// 读取但不储存输入,包括\n
	continue;
}

程序清单11.9在程序清单11.8的基础上添加了一部分测试代码。该程序读取输入行,删除储存在字符串中的换行符,如果没有换行符,则丢弃数组装不下的字符。


#include <stdio.h>
#include <string.h>
#define STLEN 10

int main(void)
{
	char words[STLEN];
	int i;

	puts("Enter strings (empty line to quit):");
	while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n') {
		i = 0;
		while (words[i] != '\n' && words[i] != '\0') {
			i++;
		}
		if (words[i] == '\n') {
			words[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
		puts(words);
	}
	puts("Done.");
	
	return 0;
}

在这里插入图片描述

s_gets()函数

如果 fgets()返回 NULL,说明读到文件结尾或出现读取错误,s_gets()函数跳过了这个过程。
如果字符串中出现换行符,就用空字符替换它;
如果字符串中出现空字符,就丢弃该输入行的其余字符,然后返回与fgets()相同的值。

char* s_gets(char* st, int n)
{
	char* ret_val;
	int i = 0;

	ret_val = fgets(st, n, stdin);
	if (ret_val != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		}
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

也许读者想了解为什么要丢弃过长输入行中的余下字符。这是因为,输入行中多出来的字符会被留在缓冲区中,成为下一次读取语句的输入。例如,如果下一条读取语句要读取的是 double 类型的值,就可能导致程序崩溃。丢弃输入行余下的字符保证了读取语句与键盘输入同步。

scanf()函数

scanf()函数有两种方法确定输入结束。无论哪种方法,都从第1个非空白字符作为字符串的开始。如果使用%s转换说明,以下一个空白字符(空行、空格、制表符或换行符)作为字符串的结束(字符串不包括空白字符)。如果指定了字段宽度,如%10s,那么scanf()将读取10 个字符或读到第1个空白字符停止(先满足的条件即是结束输入的条件)。
scanf()函数返回一个整数值,该值等于scanf()成功读取的项数或EOF(读到文件结尾时返回EOF)。
演示了在scanf()函数中指定字段宽度的用法:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define STLEN 10

int main(void)
{
	char name1[11], name2[11];
	int count;

	printf("Please enter 2 names.\n");
	count = scanf("%5s %10s", name1, name2);
	printf("I read the %d names %s and %s.\n", count, name1, name2);
	
	return 0;
}

在这里插入图片描述
scanf()的典型用法是读取并转换混合数据类型为某种标准形式。例如,如果输入行包含一种工具名、库存量和单价,就可以使用scanf()。否则可能要自己拼凑一个函数处理一些输入检查。如果一次只输入一个单词,用scanf()也没问题。
scanf()和gets()类似,也存在一些潜在的缺点。如果输入行的内容过长,scanf()也会导致数据溢出。不过,在%s转换说明中使用字段宽度可防止溢出。

11.3 字符串输出

puts()函数

puts()函数很容易使用,只需把字符串的地址作为参数传递给它即可。
puts()在显示字符串时会自动在其末尾添加一个换行符。
puts()如何知道在何处停止?该函数在遇到空字符时就停止输出,所以必须确保有空字符。

fputs()函数

fputs()函数是puts()针对文件定制的版本。它们的区别如下。
fputs()函数的第 2 个参数指明要写入数据的文件。如果要打印在显示器上,可以用定义在stdio.h中的stdout(标准输出)作为该参数。
与puts()不同,fputs()不会在输出的末尾添加换行符。
注意,gets()丢弃输入中的换行符,但是puts()在输出中添加换行符。另一方面,fgets()保留输入中的换行符,fputs()不在输出中添加换行符。

printf()函数

与puts()不同的是,printf()不会自动在每个字符串末尾加上一个换行符。因此,必须在参数中指明应该在哪里使用换行符。

自定义输入\输出函数

假设你需要一个类似puts()但是不会自动添加换行符的函数。

void put1(const char* string)
{
	while (*string != '\0') {
		putchar(*string++); 		/* ++的优先级高于* */
	}
}

假设要设计一个类似puts()的函数,而且该函数还给出待打印字符的个数。

int put1(const char* string)
{
	int count = 0;
	int i = 0;

	while (string[i] != '\0') {
		putchar(string[i++]);
		count++;
	}
	putchar('\n');	// 不统计换行符
	return count;
}

程序清单11.16使用一个简单的驱动程序测试put1()和put2(),并演示了嵌套函数的调用。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

void put1(const char* string);
int put2(const char* string);

int main(void)
{
	put1("If i'd as much money");
	put1(" as I could spend,\n");
	printf("I count %d characters.\n", 
		put2("I never would cry old chairs to mend."));
	
	return 0;
}

void put1(const char* string)
{
	while (*string != '\0') {
		putchar(*string++);
	}
}


int put2(const char* string)
{
	int count = 0;

	while (*string != '\0') {
		putchar(*string++);
		count++;
	}
	putchar('\n');
	return count;
}

在这里插入图片描述

字符串函数

strlen()函数

strlen()函数用于统计字符串的长度。下面的函数可以缩短字符串的长度,其中用到了strlen():

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

程序清单11.17中的程序测试了fit()函数。注意代码中使用了C字符串常量的串联特性。
/* test_fit.c – 使用缩短字符串长度的函数 */

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

void fit(char* string, unsigned int size);

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] = '\0';
	}
}

在这里插入图片描述

strcat()函数

strcat()(用于拼接字符串)函数接受两个字符串作为参数。该函数把第2个字符串的备份附加在第1个字符串末尾,并把拼接后形成的新字符串作为第1个字符串,第2个字符串不变。strcat()函数的类型是char (即,指向char
的指针)。strcat()函数返回第1个参数,即拼接第2个字符串后的第1个字符串的地址。
/
str_cat.c – 拼接两个字符串 */

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#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 encounted!");
	}
	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 != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		 }
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

在这里插入图片描述

strncat()函数

strcat()函数无法检查第1个数组是否能容纳第2个字符串。如果分配给第1个数组的空间不够大,多出来的字符溢出到相邻存储单元时就会出问题。
/* join_chk.c – 拼接两个字符串,检查第1个数组的大小 */

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define SIZE 30
#define BUGSIZE 13
char* s_gets(char* st, int n);

int main(void)
{
	char flower[SIZE];
	char addon[] = "s smell like old shoes.";
	char bug[BUGSIZE];
	int available;

	puts("what is your favorite flower?");
	s_gets(flower, SIZE);
	if ((strlen(addon) + strlen(flower) + 1) <= SIZE) {
		strcat(flower, addon);
	}
	puts(flower);
	puts("what is your favorite bug?");
	s_gets(bug, BUGSIZE);
	available = BUGSIZE - strlen(bug) - 1;
	strncat(bug, addon, available);
	puts(bug);

	return 0;
}

char* s_gets(char* st, int n)
{
	char* ret_val;
	int i = 0;

	ret_val = fgets(st, n, stdin);
	if (ret_val != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		 }
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

在这里插入图片描述

strcmp()函数

strcmp()函数比较的是字符串,不是整个数组。
ASCII标准规定,在字母表中,如果第1个字符串在第2个字符串前面,strcmp()返回一个负数;如果两个字符串相同,strcmp()返回0;如果第1个字符串在第2个字符串后面,strcmp()返回正数。然而,返回的具体值取决于实现。
如果两个字符串开始的几个字符都相同会怎样?一般而言,strcmp()会依次比较每个字符,直到发现第 1 对不同的字符为止。然后,返回相应的值。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define ANSWER "Grant"
#define SIZE 40
char* s_gets(char* st, int n);

int main(void)
{
	char try[SIZE];

	puts("Who is buried in Grant's tomb?");
	s_gets(try, SIZE);
	while (strcmp(try, ANSWER)) {
		puts("No, that's wrong. Try again.");
		s_gets(try, SIZE);
	}
	puts("That's right!");
	
	return 0;
}

char* s_gets(char* st, int n)
{
	char* ret_val;
	int i = 0;

	ret_val = fgets(st, n, stdin);
	if (ret_val != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		 }
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

用strcmp()函数检查程序是否要停止读取输入。
该程序在读到EOF字符(这种情况下s_gets()返回NULL)、用户输入quit或输入项达到LIM时退出。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define STOP "quit"
#define SIZE 80
#define LIM 10
char* s_gets(char* st, int n);

int main(void)
{
	char input[LIM][SIZE];
	int ct = 0;

	/* ct < LIM && s_gets(input[ct], SIZE) != NULL && input[ct][0] != '\0' */
	printf("Enter up to %d lines (type line to quit):\n", LIM);
	while (ct < LIM && s_gets(input[ct], SIZE) != NULL &&
		strcmp(input[ct], STOP) != 0) {
		ct++;
	}
	printf("%d strings entered\n", ct);

	return 0;
}

char* s_gets(char* st, int n)
{
	char* ret_val;
	int i = 0;

	ret_val = fgets(st, n, stdin);
	if (ret_val != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		 }
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

strncmp()函数

strcmp()函数比较字符串中的字符,直到发现不同的字符为止,这一过程可能会持续到字符串的末尾。而strncmp()函数在比较两个字符串时,可以比较到字符不同的地方,也可以只比较第3个参数指定的字符数。例如,要查找以"astro"开头的字符串,可以限定函数只查找这5 个字符。程序清单11.24 演示了该函数的用法。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define STOP "quit"
#define LISTSIZE 6
char* s_gets(char* st, int n);

int main(void)
{
	const char* list[LISTSIZE] = {
		"astronomy", "astounding",
		"astrophsics", "ostracize",
		"asterism", "astrophobia"
	};
	int count = 0;
	int i;

	for (i = 0; i < LISTSIZE; i++) {
		if (strncmp(list[i], "astro", 5) == 0) {
			printf("Found : %s\n", list[i]);
			count++;
		}
	}
	printf("The list contained %d words beginning"
		" with astro.\n", count);

	return 0;
}

char* s_gets(char* st, int n)
{
	char* ret_val;
	int i = 0;

	ret_val = fgets(st, n, stdin);
	if (ret_val != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		 }
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

strcpy()和strncpy()函数

strcpy()参数的顺序,即第1个是目标字符串,第2个是源字符串。
程序员有责任确保目标数组有足够的空间容纳源字符串的副本。
strcpy()接受两个字符串指针作为参数,可以把指向源字符串的第2个指针声明为指针、数组名或字符串常量;而指向源字符串副本的第1个指针应指向一个数据对象(如,数组),且该对象有足够的空间储存源字符串的副本。记住,声明数组将分配储存数据的空间,而声明指针只分配储存一个地址的空间。

/* 要求用户输入以q开头的单词。该程序把输入拷贝至一个临时数组中,如果第1 个字母是q,程序调用strcpy()把整个字符串从临时数组拷贝至目标数组中。strcpy()函数相当于字符串赋值运算符。*/
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define LIM 5
#define SIZE 40
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) != NULL) {
		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 != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		 }
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

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

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define WORDS "beast"
#define SIZE 40
char* s_gets(char* st, int n);

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;
}

char* s_gets(char* st, int n)
{
	char* ret_val;
	int i = 0;

	ret_val = fgets(st, n, stdin);
	if (ret_val != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		 }
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

在这里插入图片描述strncpy(target, source, n)把source中的n个字符或空字符之前的字符(先满足哪个条件就拷贝到何处)拷贝至target中。因此,如果source中的字符数小于n,则拷贝整个字符串,包括空字符。但是,strncpy()拷贝字符串的长度不会超过n,如果拷贝到第n个字符时还未拷贝完整个源字符串,就不会拷贝空字符。所以,拷贝的副本中不一定有空字符。鉴于此,该程序把 n 设置为比目标数组大小少1(TARGSIZE-1),然后把数组最后一个元素设置为空字符:

strncpy(qwords[i], temp, TARGSIZE - 1);
qwords[i][TARGSIZE - 1] = '\0';

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

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define LIM 5
#define SIZE 40
#define TARGSIZE 7
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) != NULL) {
		if (temp[0] != 'q') {
			printf("%s doesn't begin with q!\n", temp);
		}
		else {
			strncpy(qwords[i], temp, TARGSIZE - 1);
			qwords[i][TARGSIZE - 1] = '\0';
			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 != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		}
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

在这里插入图片描述

sprintf()函数

该函数和printf()类似,但是它是把数据写入字符串,而不是打印在显示器上。sprintf()的第1个参数是目标字符串的地
址。其余参数和printf()相同,即格式字符串和待写入项的列表。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define MAX 20
char* s_gets(char* st, int n);

int main(void)
{
	char first[MAX];
	char last[MAX];
	char formal[2 * MAX + 10];
	double prize;

	puts("Enter your first name:");
	s_gets(first, MAX);
	puts("Enter your last name:");
	s_gets(last, MAX);
	puts("Enter your prize money:");
	scanf("%lf", &prize);
	sprintf(formal, "%s, %-19s: $%6.2f\n", last, first, prize);
	puts(formal);

	return 0;
}

char* s_gets(char* st, int n)
{
	char* ret_val;
	int i = 0;

	ret_val = fgets(st, n, stdin);
	if (ret_val != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		}
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

在这里插入图片描述

字符串示例:字符串排序

该程序的巧妙之处在于排序的是指向字符串的指针,而不是字符串本身。
我们采用选择排序算法(selection sort algorithm)来排序指针。具体做法是,利用for循环依次把每个元素与首元素比较。如果待比较的元素在当前首元素的前面,则交换两者。循环结束时,首元素包含的指针指向机器排序序列最靠前的字符串。然后外层for循环重复这一过程,这次从input的第2个元素开始。当内层循环执行完毕时,ptrst中的第2个元素指向排在第2的字符串。这一过程持续到所有元素都已排序完毕。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define SIZE 81
#define LIM 20
char* s_gets(char* st, int n);
void stsrt(char* strings[], int num);

int main(void)
{
	char input[LIM][SIZE];
	char* ptstr[LIM];
	int ct = 0;
	int k;

	printf("Input up to %d lines, and I will sort them.\n", LIM);
	printf("To stop, press the Enter key at a line's start.\n");
	while (ct < LIM && s_gets(input[ct], SIZE) != NULL &&
		input[ct][0] != '\0') {
		ptstr[ct] = input[ct];
		ct++;
	}

	stsrt(ptstr, ct);

	puts("\nHere's the sorted list:\n");
	for (k = 0; k < ct; k++) {
		puts(ptstr[k]);
	}

	return 0;
}

void stsrt(char* strings[], int n)
{
	int i, j;
	char* temp;

	for (i = 0; i < n - 1; i++) {
		for (j = i + 1; j < n; j++) {
			if (strcmp(strings[i], strings[j]) > 0) {
				temp = strings[i];
				strings[i] = strings[j];
				strings[j] = temp;
			}
		}
	}
}

char* s_gets(char* st, int n)
{
	char* ret_val;
	int i = 0;

	ret_val = fgets(st, n, stdin);
	if (ret_val != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		}
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

ctype.h字符函数和字符串

第7章中介绍了ctype.h系列与字符相关的函数。虽然这些函数不能处理整个字符串,但是可以处理字符串中的字符。例如,程序清单11.30中定义的ToUpper()函数,利用toupper()函数处理字符串中的每个字符,把整个字符串转换成大写;定义的 PunctCount()函数,利用 ispunct()统计字符串中的标点符号个数。另外,该程序使用strchr()处理fgets()读入字符串的换行符(如果有的话)。
/* mod_str.c – 修改字符串 */

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LIMIT 81
char* s_gets(char* st, int n);
void ToUpper(char* str);
int PunctCount(const char* str);

int main(void)
{
	char line[LIMIT];
	char* find;

	puts("Please enter a line:");
	fgets(line, LIMIT, stdin);
	find = strchr(line, '\n');
	if (find) {
		*find = '\0';
	}
	ToUpper(line);
	puts(line);
	printf("That line has %d punctuation characters.\n", PunctCount(line));

	return 0;
}

void ToUpper(char* str)
{
	while (*str) {
		*str = toupper(*str);
		str++;
	}
}

int PunctCount(const char* str) 
{
	int ct = 0; 

	while (*str) {
		if (ispunct(*str)) {
			ct++;
		}
		str++;
	}
	return ct;
}

把字符串转换为数字

数字既能以字符串形式储存,也能以数值形式储存。把数字储存为字符串就是储存数字字符。例如,数字213以’2’、‘1’、‘3’、'\0’的形式被储存在字符串数组中。以数值形式储存213,储存的是int类型的值。
C要求用数值形式进行数值运算(如,加法和比较)。但是在屏幕上显示数字则要求字符串形式,因为屏幕显示的是字符。printf()和 sprintf()函数,通过%d 和其他转换说明,把数字从数值形式转换为字符串形式,scanf()可以把输入字符串转换为数值形式。C 还有一些函数专门用于把字符串形式转换成数值形式。
假设你编写的程序需要使用数值命令形参,但是命令形参数被读取为字符串。因此,要使用数值必须先把字符串转换为数字。如果需要整数,可以使用atoi()函数(用于把字母数字转换成整数),该函数接受一个字符串作为参数,返回相应的整数值。程序清单11.32中的程序示例演示了该函数的用法。
/* hello.c – 把命令行参数转换为数字 */

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main(int argc, char *argv[])
{
	int i, times;

	if (argc < 2 || (times = atoi(argv[1]) < 1)) {
		printf("Usage: %s positive-number\n", argv[0]);
	}
	else {
		for (i = 0; i < times; i++) {
			puts("Hello, good looking!");
		}
	}

	return 0;
}

如果字符串仅以整数开头,atio()函数也能处理,它只把开头的整数转换为字符。例如, atoi(“42regular”)将返回整数42。atof()函数把字符串转换成 double 类型的值, atol()函数把字符串转换成long类型的值。atof()和atol()的工作原理和atoi()类似,因此它们分别返回double类型和long类型。
ANSI C还提供一套更智能的函数:strtol()把字符串转换成long类型的值,strtoul()把字符串转换成unsigned long类型的值,strtod()把字符串转换成double类型的值。这些函数的智能之处在于识别和报告字符串中的首字符是
否是数字。而且,strtol()和strtoul()还可以指定数字的进制。
下面的程序示例中涉及strtol()函数,其原型如下:
long strtol(const char * restrict nptr, char ** restrict endptr, int base);
这里,nptr是指向待转换字符串的指针,endptr是一个指针的地址,该指针被设置为标识输入数字结束字符的地址,base表示以什么进制写入数字。程序清单11.33演示了该函数的用法。
/* strcnvt.c – 使用 strtol() */

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define LIM 30
char* s_gets(char* st, int n);

int main(int argc, char *argv[])
{
	char number[LIM];
	char* end;
	long value;

	puts("Enter a number (empty line to quit):");
	while (s_gets(number, LIM) && number[0] != '\0') {
		value = strtol(number, &end, 10); // 十进制
		printf("base 10 input, base 10 output: %ld, stopped at %s (%d)\n",
			value, end, *end);
		value = strtol(number, &end, 16); // 十六进制
		printf("base 16 input, base 10 output: %ld, stopped at %s (%d)\n",
			value, end, *end);
		puts("Next number:");
	}
	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 != NULL) {
		while (st[i] != '\n' && st[i] != '\0') {
			i++;
		}
		if (st[i] == '\n') {
			st[i] = '\0';
		}
		else {
			while (getchar() != '\n') {
				continue;
			}
		}
	}
	return ret_val;
}

复习题1.下面字符串的声明有什么问题?

int main(void)
{
char name[] = {‘F’, ‘e’, ‘s’, ‘s’ };

}
2.下面的程序会打印什么?
#include <stdio.h>
int main(void)
{
char note[] = “See you at the snack bar.”;
char *ptr;
ptr = note;
puts(ptr);
puts(++ptr);
note[7] = ‘\0’;
puts(note);
847
puts(++ptr);
return 0;
}
3.下面的程序会打印什么?
#include <stdio.h>
#include <string.h>
int main(void)
{
char food [] = “Yummy”;
char *ptr;
ptr = food + strlen(food);
while (–ptr >= food)
puts(ptr);
return 0;
}
4.下面的程序会打印什么?
#include <stdio.h>
#include <string.h>
int main(void)
848
{
char goldwyn[40] = "art of it all ";
char samuel[40] = “I read p”;
const char * quote = “the way through.”;
strcat(goldwyn, quote);
strcat(samuel, goldwyn);
puts(samuel);
return 0;
}
5.下面的练习涉及字符串、循环、指针和递增指针。首先,假设定义了下面的函数:
#include <stdio.h>
char *pr(char *str)
{
char *pc;
pc = str;
while (pc)
putchar(pc++);
do {
849
putchar(
–pc);
} while (pc - str);
return (pc);
}
考虑下面的函数调用:
x = pr(“Ho Ho Ho!”);
a.将打印什么?
b.x是什么类型?
c.x的值是什么?
d.表达式
–pc是什么意思?与–pc有何不同?
e.如果用
–pc替换–*pc,会打印什么?
f.两个while循环用来测试什么?
g.如果pr()函数的参数是空字符串,会怎样?
h.必须在主调函数中做什么,才能让pr()函数正常运行?
6.假设有如下声明:

char sign = '$';

sign占用多少字节的内存?' ′ 占 用 多 少 字 节 的 内 存 ? " '占用多少字节的内存?" ""占用多少字节的内存?
7.下面的程序会打印出什么?

8.下面的程序会打印出什么?

9.本章定义的s_gets()函数,用指针表示法代替数组表示法便可减少一个变量i。请改写该函数。

10.strlen()函数接受一个指向字符串的指针作为参数,并返回该字符串的长度。请编写一个这样的函数。

11.本章定义的s_gets()函数,可以用strchr()函数代替其中的while循环来查找换行符。请改写该函数。

12.设计一个函数,接受一个指向字符串的指针,返回指向该字符串第1个空格字符的指针,或如果未找到空格字符,则返回空指针。

13.重写程序清单11.21,使用ctype.h头文件中的函数,以便无论用户选择大写还是小写,该程序都能正确识别答案。

编程练习

1.设计并测试一个函数,从输入中获取下n个字符(包括空白、制表符、换行符),把结果储存在一个数组里,它的地址被传递作为一个参数。
2.修改并编程练习1的函数,在n个字符后停止,或在读到第1个空白、制表符或换行符时停止,哪个先遇到哪个停止。不能只使用scanf()。
3.设计并测试一个函数,从一行输入中把一个单词读入一个数组中,并丢弃输入行中的其余字符。该函数应该跳过第1个非空白字符前面的所有空白。将一个单词定义为没有空白、制表符或换行符的字符序列。
4.设计并测试一个函数,它类似编程练习3的描述,只不过它接受第2个参数指明可读取的最大字符数。
5.设计并测试一个函数,搜索第1个函数形参指定的字符串,在其中查找第2个函数形参指定的字符首次出现的位置。如果成功,该函数返指向该字符的指针,如果在字符串中未找到指定字符,则返回空指针(该函数的功能与 strchr()函数相同)。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。
6.编写一个名为is_within()的函数,接受一个字符和一个指向字符串的指针作为两个函数形参。如果指定字符在字符串中,该函数返回一个非零值(即为真)。否则,返回0(即为假)。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。
7.strncpy(s1, s2, n)函数把s2中的n个字符拷贝至s1中,截断s2,或者有必要的话在末尾添加空字符。如果s2的长度是n或多于n,目标字符串不能以空字符结尾。该函数返回s1。自己编写一个这样的函数,名为mystrncpy()。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。

8.编写一个名为string_in()的函数,接受两个指向字符串的指针作为参数。如果第2个字符串中包含第1个字符串,该函数将返回第1个字符串开始的地址。例如,string_in(“hats”, “at”)将返回hats中a的地址。否则,该函数返回空指针。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。

9.编写一个函数,把字符串中的内容用其反序字符串代替。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。

10.编写一个函数接受一个字符串作为参数,并删除字符串中的空格。在一个程序中测试该函数,使用循环读取输入行,直到用户输入一行空行。该程序应该应用该函数只每个输入的字符串,并显示处理后的字符串。

11.编写一个函数,读入10个字符串或者读到EOF时停止。该程序为用户提供一个有5个选项的菜单:打印源字符串列表、以ASCII中的顺序打印字符串、按长度递增顺序打印字符串、按字符串中第1个单词的长度打印字符串、退出。菜单可以循环显示,除非用户选择退出选项。当然,该程序要能真正完成菜单中各选项的功能。

12.编写一个程序,读取输入,直至读到 EOF,报告读入的单词数、大写字母数、小写字母数、标点符号数和数字字符数。使用ctype.h头文件中的函数。

13.编写一个程序,反序显示命令行参数的单词。例如,命令行参数是see you later,该程序应打印later you see。

14.编写一个通过命令行运行的程序计算幂。第1个命令行参数是double类型的数,作为幂的底数,第2个参数是整数,作为幂的指数。

15.使用字符分类函数实现atoi()函数。如果输入的字符串不是纯数字,该函数返回0。

16.编写一个程序读取输入,直至读到文件结尾,然后把字符串打印出来。该程序识别和实现下面的命令行参数:
-p     按原样打印
-u     把输入全部转换成大写
-l     把输入全部转换成小写
如果没有命令行参数,则让程序像是使用了-p参数那样运行。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-06-29 18:48:02  更:2022-06-29 18:48:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年3日历 -2024/3/29 7:41:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码