第十章 字符串
字符串常量是由一对双引号括起来的一个字符串序列
字符串实际就是由若干个有效数字构成且以字符'\0'作为结束的一个字符序列
C语言没有提供字符串数据类型,因此字符串的存取要用字符型数组来实现
字符型数组是由字符构成的数组,仅当其最后一个元素是'\0'时才表示字符串
字符串结束标志'\0'也占一个字节的内存,但它不计入字符串的实际长度,只计入数组的长度
正确写法:
char str[6] = {'H','e','l','l','o','\0'};
char str[] = {'H','e','l','l','o','\0'};
char str[] = {"hello"};
char str[] = "hello";
编译系统会根据字符串中字符的个数来确定数组的大小
数组的大小为字符串中实际字符的个数加1
多个字符串需要存放在二维字符数组中
数组第一维的长度代表要存储的字符串的个数,可以省略
但是第二维的长度不能省略
字符指针:指向字符型数据的指针变量
只要将字符串的首地址赋值给字符指针,即可让字符指针指向一个字符串
字符串常量本身代表的就是存放它的常量存储区的首地址,是一个地址常量
char *ptr = "Hello";
等价于
char *ptr;
ptr = "Hello"; /* 将保存在常量存储区中的"Hello"的首地址赋值给ptr,不能理解为将字符串赋值给ptr */
常量存储区中的字符串是只读的
数组中的字符串是可以修改的
char str[10] = "Hello";
char *ptr = str;
等价于
char str[10] = "Hello";
char *ptr;
ptr = str; /* 等价于ptr = &str[0] */
*ptr = 'W'; /* 等价于ptr[0] = 'W'; 相当于str[0] = 'W'; */
正确使用字符指针,必须明确字符串被保存到了哪里以及字符指针指向了哪里
测试
#include <stdio.h>
int main()
{
char str[10] = "Hello";
char *ptr;
ptr = str;
printf("%p\n",ptr);
printf("%c\n",ptr[0]);
printf("%s\n",str);
*ptr = 'W';
printf("%p\n",ptr);
printf("%c\n",ptr[0]);
printf("%s\n",str);
}
//运行结果
0060FF02
H
Hello
0060FF02
W
Wello
如何访问字符串中的单个字符:
- 使用下标:str[i]
- 使用指针:若字符指针ptr指向了字符数组str的首地址,*(ptr+i)相当于str[i]
- 可以通过ptr++操作,移动指针ptr,使ptr指向字符串中的某个字符
- 对于数组名str,不可以通过str++操作使其指向字符串中的某个字符,因为数组名是一个地址常量,其值是不能被改变的
字符串的输入/输出:
for(i = 0; i < 10; i++)//输入
{
scanf("%c", &str[i]);
}
for(i = 0; str[i]! = '\0'; i++)//输出
{
printf("%c",str[i]);
}
scanf("%s", str);//输入
printf("%s", str);//输出
//L10-1
#include <stdio.h>
#define N 12
int main()
{
char name[N];
printf("Enter your name:");
scanf("%s", name);
printf("Hello %s!\n",name);
scanf("%s", name); /* 读取输入缓冲区中余下的上次未被读走的字符 */
printf("Hello %s!\n",name);
return 0;
}
//运行结果
Enter your name:dingdingdangdang
Hello dingdingdangdang! Enter your name:dingding dangdang
Hello dingding!
Hello dangdang!
用%d输入数字或者%s输入字符串时,忽略空格、回车或制表符等空白字符(被作为数据的分隔符)
读到这些字符时,系统会认为读入结束,因此用函数scanf()按s格式符不能输入带空格的字符串
gets()
gets()以回车作为字符串的终止符,同时将回车符从输入缓冲区读走,但不作为字符串的一部分,而scanf()不读走回车符,回车符仍留在输入缓冲区中
//L10-2
#include <stdio.h>
#define N 12
int main()
{
char name[N];
printf("Enter your name:");
gets(name);
printf("Hello %s!\n",name);
return 0;
}
//运行结果
Enter your name:dingding dangdang
Hello dingding dangdang!
puts()
函数puts()用于从括号内的参数给出的地址开始,依次输出存储单元中的字符
当遇到第一个'\0'时输出结束,并且自动输出一个换行符
函数puts()输出字符串简介方便,唯一不足是不能像函数printf()一样在输出行中增加一些其他的字符信息并控制输出的格式
gets()和puts()都是C语言的标准输入/输出函数
//L10-2
#include <stdio.h>
#define N 12
int main()
{
char name[N];
printf("Enter your name:");
fgets(name, sizeof(name), stdin); /* 限制输入字符串长度不超过数组大小 */
printf("Hello %s!\n",name);
return 0;
}
//运行结果
Enter your name:dingding dangdang
Hello dingding da!
函数gets()不能限制输入字符串的长度,很容易引起缓冲区溢出
函数scanf()也同样存在这个问题
fgets(name, sizeof(name), stdin);
可以从标准输入stdin中读入一行长度为sizeof(name)的字符串送到以name为首地址的存储区中
由于该语句限制了输入字符串的长度不能超过数组的大小,所以用户输入的多余的字符都被舍弃了
//L10-3
#include <stdio.h>
#define N 12
int main()
{
char name[N];
char str[] = "\"Hello\", I said to";//\"是一个转义字符,代表双引号
printf("Enter your name:");
fgets(name, sizeof(name), stdin);
printf("%s %s.\n", str, name);
return 0;
}
//运行结果
Enter your name:dingding dangdang
"Hello", I said to dingding da.
字符串处理函数:(需在程序开头将头文件<string.h>包含到源文件中来)
strlen(str) | 求字符串长度 |
strcpy(str1,str2) | 将字符串str2复制到字符数组str1中 |
strcmp(str1,str2) | 比较字符串str1和str2的大小,对两个字符串从左至右按字符的ASCII码值大小逐个字符相比较,直到出现不同的字符或遇到'\0'为止 |
strcat(str1,str2) | 字符串连接,将字符串str2添加到字符数组str1中的字符串的末尾,字符数组str1中的字符串结束符被字符串str2的第一个字符覆盖,连接后的字符串存放在字符数组str1中,函数调用后返回字符数组str1的首地址 |
strncpy(str1,str2,n) | “n族” |
strncmp(str1,str2,n) | “n族” |
strncat(str1,str2,n) | “n族” |
//L10-4
#include <stdio.h>
#include <string.h>
#define MAX_LEN 10 /* 字符串最大长度 */
#define N 150 /* 字符串个数 */
void SortString(char str[][MAX_LEN], int n);
int main()
{
int i, n;
char name[N][MAX_LEN]; /* 定义二维字符数组 */
printf("How many countries?");
scanf("%d",&n);
getchar(); /* 读走输入缓冲区中的回车符 */
/* 前面的scanf()在读取输入时会在缓冲区中留下一个字符'\n'
所以如果不在此加一个getchar()把这个回车符取走的话
gets()就不会等待从键盘键入字符,而是会直接取走这个“无用的”回车符,从而导致读取有误 */
printf("Input their names:\n");
for (i=0; i<n; i++)
{
gets(name[i]); /* 输入n个字符串 */
}
SortString(name, n); /* 字符串按字典顺序排序 */
printf("Sorted results:\n");
for (i=0; i<n; i++)
{
puts(name[i]); /* 输出排序后的n个字符串 */
}
return 0;
}
/* 函数功能:交换法实现字符串按字典顺序排序 */
void SortString(char str[][MAX_LEN], int n)
{
int i, j;
char temp[MAX_LEN];
for (i=0; i<n-1; i++)
{
for (j = i+1; j<n; j++)
{
if (strcmp(str[j], str[i]) < 0)
{
strcpy(temp,str[i]);
//对单个字符进行赋值操作可以使用赋值运算符,但是赋值运算符不能用于字符串的赋值操作
//字符串赋值只能使用函数strcpy()
strcpy(str[i],str[j]);
strcpy(str[j],temp);
}
}
}
}
//运行结果
How many countries?5
Input their names:
America
England
Australia
Sweden
Finland
Sorted results:
America
Australia
England
Finland
Sweden
因为字符数组和字符指针都可以存取C字符串
因此向函数传递字符串时,既可以使用字符数组作函数参数,也可以使用字符指针作函数参数
//L10-5
#include <stdio.h>
#define N 80
void MyStrcpy(char dstStr[], char srcStr[]);
int main()
{
char a[N], b[N];
printf("Input a string:");
gets(a); /* 输入字符串 */
MyStrcpy(b, a); /* 将字符数组a中的字符串拷贝到b中 */
printf("The copy is:");
puts(b); /* 输出复制后的字符串 */
return 0;
}
/* 函数功能:用字符数组作为函数参数实现字符串拷贝 */
void MyStrcpy(char dstStr[], char srcStr[])
{
int i = 0; /* 数组下标初始化为0 */
while (srcStr[i] != '\0') /* 若当前取出的字符不是字符串结束标志 */
{
dstStr[i] = srcStr[i];/* 复制字符 */
i++; /* 移动下标 */
}
dstStr[i] = '\0'; /* 在字符串dstStr的末尾添加字符串结束标志 */
}
//运行结果
Input a string:Hello China
The copy is:Hello China
或者
/* 函数功能:用字符指针作为函数参数,实现字符串拷贝 */
void MyStrcpy(char *dstStr, char *srcStr)
{
while (*srcStr != '\0') /* 若当前srcStr所指字符不是字符串结束标志 */
{
*dstStr = *srcStr; /* 复制字符 */
srcStr++; /* 使srcStr指向下一个字符 */
dstStr++; /* 使dstStr指向下一个存储单元 */
}
*dstStr = '\0'; /* 在字符串dstStr的末尾添加字符串结束标志 */
}
//L10-6
#include <stdio.h>
unsigned int MyStrlen(const char str[]);
int main()
{
char a[80];
printf("Input a string:");
gets(a);
printf("The length of the string is: %u\n", MyStrlen(a));
return 0;
}
/* 函数功能:用字符型数组作函数参数,计算字符串的长度 */
unsigned int MyStrlen(const char str[])
{
int i ;
unsigned int len = 0; /* 计数器置0 */
for (i=0; str[i]!='\0'; i++)
{
len++; /* 利用循环统计不包括'\0'在内的字符个数 */
}
return len; /* 返回实际字符个数 */
}
//运行结果
Input a string:Hello China
The length of the string is: 11
为防止实参在被调函数中被意外修改,可以在相应的形参前面加上类型限定符const
这样如果在函数体内试图修改形参的值,就会产生编译错误
函数
char *strcpy(char *str1,const char *str2);
char *strcat(char *str1,const char *str2);
返回的都是字符指针str1的值,即存储字符串str1的内存空间的首地址
这样设计的目的是为了增加使用时的灵活性
函数之间的握手(信息交换)是通过函数参数和返回值来实现的
从函数返回字符串指针:
返回指针的函数在定义时需要在函数名的前面加一个 * 号
char *f();定义了一个函数f,该函数的返回值是一个字符指针
char (*f)();定义了一个函数指针f,该指针指向的函数没有形参,返回值是字符型
//L10-7
#include <stdio.h>
#define N 80
char *MyStrcat(char *dstStr, char *srcStr);
int main()
{
char first[2*N]; /* 这个数组应该足够大,以便存放连接后的字符串 */
char second[N];
printf("Input the first string:");
gets(first);
printf("Input the second string:");
gets(second);
printf("The result is: %s\n", MyStrcat(first, second));
return 0;
}
/* 函数功能:将字符串srcStr连接到字符串dstStr的后面 */
char *MyStrcat(char *dstStr, char *srcStr)
{
char *pStr = dstStr; /* 保存字符串dstStr的首地址 */
/* 将指针移到字符串dstStr的末尾 */
while (*dstStr != '\0')
{
dstStr++;
}
/* 将字符串srcStr复制到字符串dstStr的后面 */
for(; *srcStr!='\0'; dstStr++, srcStr++)
{
*dstStr = *srcStr;
}
*dstStr = '\0'; /* 在连接后的字符串的末尾添加字符串结束标志 */
return pStr; /* 返回连接后的字符串dstStr的首地址 */
}
//运行结果
Input the first string:Hello
Input the second string:China
The result is: HelloChina
const类型限定符:
如果只希望数据传到被调函数内部,而不希望它们在函数内被修改
此时,为了防止数据被意外修改,让函数的功能更加明确
可以使用const对参数进行限定
(1)const放在类型关键字的前面
int a,b;
const int *p = &a;
p是一个指针变量,可以指向一个整型常量(Integer Constant)
*p是一个常量,而p不是
*p是只读的,不可以在程序中被修改
指针变量p的值是可以修改的
对p所指向的变量a进行赋值也是合法的
(2)const放在类型关键字的后面和*变量名的前面
int const *p = &a;
p是一个指针变量,可以指向一个常量整数(Constant Integer)
*p是一个常量,而p不是
与第一种情况是等价的
(3)const放在类型关键字*的后面,变量名的前面
int *const p = &a;
p是一个指针变量,可以指向一个整型数据(Integer)
p是一个常量,而*p不是
p是只读的,不可以在程序中被修改,即不可以让它指向其他变量
但是它所指向的变量的值是可以修改的
(4)一个const放在类型关键字的前面,另一个const放在类型关键字和*变量名之间
const int *const p = &a;
p是一个指针变量,可以指向一个整型常量(Integer Constant)
p和*p都是常量,都是只读的
字符处理函数:
int isdigit(int c) | 如果是数字返回真 |
int isalpha(int c) | 如果是字母返回真 |
int isalnum(int c) | 如果是数字或字母返回真 |
int islower(int c) | 如果是小写字母返回真 |
int issupper(int c) | 如果是大写字母返回真 |
int tolower(int c) | 如果是大写字母则转换为小写字母返回 |
int toupper(int c) | 如果是小写字母则转换为大写字母返回 |
int isspace(int c) | 如果是空白字符(换行符\n、空格符 、换页符\f、回车符\r、水平制表符\t、垂直制表符\v)返回真 |
int iscntrl(int c) | 如果是控制字符(水平制表符\t、垂直制表符\v、换页符\f、响铃报警符\a、退格符\b、回车符\r、换行符\n)返回真 |
int isprint(int c) | 如果是包含空格在内的可打印字符($、#、(、)、[、]、{、}、;、:、%)返回真 |
int isgraph(int c) | 如果是除空格以外的可打印字符返回真 |
//L10-8
#include <stdio.h>
#define N 80
int main()
{
char str[N];
int i, letter = 0, digit = 0, space = 0, others = 0;
printf("Input a string:");
gets(str);
for (i=0; str[i]!='\0'; i++)
{
if (str[i]>='a' && str[i]<='z' || str[i]>='A' && str[i]<='Z')
letter ++; /* 统计英文字符 */
else if (str[i] >= '0' && str[i] <= '9')
digit ++; /* 统计数字字符 */
else if (str[i] == ' ')
space ++; /* 统计空格 */
else
others ++; /* 统计其他字符 */
}
printf("English character: %d\n", letter);
printf("digit character: %d\n", digit);
printf("space: %d\n", space);
printf("other character: %d\n", others);
return 0;
}
//L10-8
#include <stdio.h>
#include <ctype.h>
#define N 80
int main()
{
char str[N];
int i, letter = 0, digit = 0, space = 0, others = 0;
printf("Input a string:");
gets(str);
for (i=0; str[i]!='\0'; i++)
{
if (isalpha(str[i]))
letter ++; /* 统计英文字符 */
else if (isdigit(str[i]))
digit ++; /* 统计数字字符 */
else if (isspace(str[i]))
space ++; /* 统计空格(其实还包含了其他空白字符) */
else
others ++; /* 统计其他字符 */
}
printf("English character: %d\n", letter);
printf("digit character: %d\n", digit);
printf("space: %d\n", space);
printf("other character: %d\n", others);
return 0;
}
//运行结果
Input a string:abcd 12345 (*)
English character: 4
digit character: 5
space: 2
other character: 3
//L10-9
#include <stdio.h>
#include <ctype.h>
#define N 80
int main()
{
char name[N];
int i;
printf("Input a name:");
gets(name); /* 输入名和姓 */
i = 0;
while(!isalpha(name[i])) /* 跳过所有空格,直到遇字母为止 */
{
i++;
}
name[i] = toupper(name[i]); /* 将名的首字母变为大写 */
while (!isspace(name[i])) /* 跳过所有字母,直到遇空格为止 */
{
i++;
}
while (!isalpha(name[i])) /* 跳过所有空格,直到遇字母为止 */
{
i++;
}
name[i] = toupper(name[i]); /* 将姓的首字母变为大写 */
printf("Formatted Name:%s\n", name);
return 0;
}
//运行结果
Input a name:john smith
Formatted Name:John Smith
数值字符串向数值的转换:
C语言提供的字符串转换函数可以将数字字符串转换为整型或浮点数型的数值
使用这些函数,必须在程序开头包含头文件<stblib.h>
double atof(const chat *nPtr) | 将nPtr指向的字符串转换为双精度浮点数 |
int atoi(const char *nPtr) | 将nPtr指向的字符串转换为整型数 |
long atol(const char *nPtr) | 将nPtr指向的字符串转换为长整型数 |
//L10-10
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[] = {" 123.5"};
int intNum;
long longNum;
double doubleNum;
intNum = atoi(str); /* 字符串转换为整型数 */
longNum = atol(str); /* 字符串转换为长整型数 */
doubleNum = atof(str); /* 字符串转换为双精度实型数 */
printf("intNum = %d\n", intNum);
printf("longNum = %ld\n", longNum);
printf("doubleNum = %f\n", doubleNum);
return 0;
}
//运行结果
intNum = 123
longNum = 123
doubleNum = 123.500000