一、常用字符串库函数
(1)gets和fgets函数
- gets(char *s); //参数是一个字符数组 //作用就像scanf一样,并且不会被空格断开
- gets认为回车代表输入完成,空格只是字符串中的一部分而已
- gets和scanf一样有缓冲区溢出的危险,使用gets和scanf都要注意缓冲区溢出的问题
- fgets是安全的,因为它改进了gets内存溢出的问题,不会因为用户恶意的输入过长的字符串导致溢出(当输入个数过多时,取前面几个,比如10个长度的字符串,会取输入的前9个字符)
- fgets函数是为读取文件而设计的,所以读取键盘时灭有gets那么方便
-
fgets有三个参数,第一个参数是char的数组,第二个参数是标明这个数组的大小,第三个参数输入来源如果是从键盘输入可以固定写为stdin。fgets(char *s, size_t len, stdin)
-
fgets会认为用户输入的回车也是字符串的一部分内容。回车同时也是结束输入的标志。
#include <stdio.h>
int main()
{
char a[10] = {0};
fgets(a, sizeof(a), stdin);
printf("(%s)\n", a);
return 0;
}
运行结果
[root@localhost test]# gcc main.c -o main
[root@localhost test]# ./main
hello
(hello
)
[root@localhost test]# ./main
hello worldxxx
(hello wor)
[root@localhost test]#
(2)puts和fputs函数
- puts打印字符串,与printf不同,puts会在最后自动加一个'\n',并且puts不支持各种转义字符,比如%d,%s都不支持,puts只能简单的直接输出一个字符串,而不能输出char, int,double等其他类型。
- puts的底层函数是putchar,函数原型为【int puts(const char *s);】
- fputs是puts的文件操作版本。第一个参数是char的数组,第二个参数是输出端,如果只是在屏幕输出的话可以固定为stdout。
- fgets不会自动输出一个\n。
#include <stdio.h>
int main()
{
char s[] = "hello world";
puts(s);
fputs(s, stdout);
return 0;
}
(3)strlen函数
- 返回字符串长度,返回不包含字符串结尾'\0'的长度,实际上返回的是这个字符串实际占用的字节数。注意一个汉字在gbk模式下占用2字节,在utf-8模式下占用3字节。
-
原型:size_t strlen(const char *_Str);
int main()
{
int len;
char s[100] = "hello world";
len = strlen(s);
printf("%d\n", len); // 11
char s2[100] = "hello好好学习";
len = strlen(s2);
printf("%d\n", len); // gbk编码时:13 utf8编码时:17
return 0;
}
(4)strcat和strncat函数
size_t strcat(char *_Str1, const char *_Str2)
将参数Str2追加到Str1后面
用strcat的时候要注意,第二个字符串过长时,第一个字符串会自动扩展空间
#include <stdio.h>
#include <string.h>
int main()
{
char a[10] = "abxxc";
char b[10] = "de";
strcat(a, b);
printf("%s\n", a); //abxxcde
//注意当a的空间不够时,会自动扩展长度
char c[10] = "fghi";
strcat(a, c);
printf("%s\n", a); //abxxcdefghi
return 0;
}
size_t strncat(char *_Str1, const char *_Str2, size_t len)
字符串有限追加,在strcat的基础上增加了一个参数,这个参数来表明最多可以追加str2的前几个char
#include <stdio.h>
#include <string.h>
int main()
{
char a[10] = "abxxc";
char b[10] = "defhgi";
strncat(a, b, 5);
printf("%s\n", a); // abxxcdefhg
return 0;
}
(5)strcmp和strncmp函数
int strcmp(const char *str1 , const char *str2)
该函数主要是用来实现字符串对比,该函数执行为将字符串str1和str2进行对比,不能将两个字符串变量名直接比较,因为相当于比较两个地址值。
- 如果str1>str2则返回一个正数
- 如果str1<str2则返回一个负数
-
如果str1=str2则返回0
从第一个字符开始比较,若相同则判断下一个字符,若不等则返回结果,str1该位置字符大,则返回正数,若小返回负数;若str1和str2长度相等且每个字符都相等,则返回0;若str1和str2长度不相等,且较短的那个字符串是较长字符串的最左边一部分,则较长的那个字符串大,即此时若str1较长,则返回正数,若str2较长,则返回负数。
int strcmp(const char *_Str1, const char *_Str2, size_t n);
比较两个字符串前n个字符是否相等,相等返回0,不等返回非0(一般是1);
#include <stdio.h>
#include <string.h>
int main()
{
char time1[] = "09:30:00";
char time2[] = "09:31";
char time3[] = "09:30:001";
int ret;
ret = strcmp(time1, time2);
printf("%d\n", ret); // -1 // 09:30:00 < 09:31
ret = strcmp(time1, time3);
printf("%d\n", ret); // -49 // 09:30:00 < 09:30:00x
ret = strncmp(time1, time3, 8);
printf("%d\n", ret); // 0
return 0;
}
(6)strcpy和strncpy函数
字符串拷贝:注意strcpy是直接将str2覆盖掉str1,而strncpy是将_Str2将n个字符覆盖掉_Str1前n个字符,如果_Str1本来的字符长度大于n,则覆盖后n后面的字符不变
//将参数_Str2拷贝到_Str1中
char *strcpy(char *_Str1, const char *_Str2);
//将参数_Str2拷贝到_Str1中, 拷贝前n个
char *strncpy(char *_Str1, const char *_Str2, size_t n);
示例
#include <stdio.h>
#include <string.h>
int main()
{
char time1[] = "09:30:00";
char time2[] = "09:31";
char time3[] = "09:30:001";
strcpy(time1, time2);
printf("%s\n", time1); // 09:31
strncpy(time3, time2, 5);
printf("%s\n", time3); // 09:31:001
return 0;
}
(7)sprintf和sscanf函数
- 和printf函数功能类似,printf将格式化结果输出到屏幕,sprintf将格式化结果输出到字符串
-
sprintf和printf用法类似,唯一区别在于增加了第一个参数,第一个参数是char数组
-
所有printf的转移符对于sprintf是一样
#include <stdio.h>
#include <string.h>
int main()
{
char a[100];
sprintf(a, "%s\n", "(hello world)");
printf("%s", a); //(hello world)
sprintf(a, "( %d%s)\n", 3, "yourname");
printf("%s", a); //( 3yourname)
sprintf(a, "(%s)\n", "xxx");
printf("%s", a); //(xxx)
return 0;
}
- sscanf类似于scanf函数,scanf从键盘读取输入,sscanf从指定格式化字符串读取输入
- sscanf多了第一个参数,char的数组,sscanf会从这个数组中读取相关的内容。用sscanf传入字符串到指定数组后,那个数组的每一个字符都是字符串的每一个字符,当数组长度比字符串长时,数组后面的元素为0
- 注意以下示例代码中的&符号容易忘
#include <stdio.h>
#include <string.h>
int main()
{
char a[100] = "hello56+k+72name";
int i, j;
sscanf(a, "hello%d+k+%dname", &i, &j); //从字符串里面取数
printf("i = %d, j = %d\n", i, j); //i = 56, j = 72
return 0;
}
(8)strchr和strstr函数
- 【char * strchr(char * _Str, int _Ch);】 在参数_Str中查找参数 _Ch指定字符,找到返回字符 _Ch在_Str中的所在地址,没有找到返回 NULL
- 【char * strstr(const char *_Str, const char *_SubStr);】 在参数_Str中查找参数_SubStr指定子串,找到返回子串_SubStr在_Str中的所在首地址,没有找到返回NULL
示例代码
#include <stdio.h>
#include <string.h>
int main()
{
char a[100] = "hello world";
char *s; //定义了char类型的指针变量
s = strchr(a, 'l'); //返回l所在的地址
printf("%s\n", s); //llo world
s = strchr(a, 'w'); //返回w所在的地址
printf("%s\n", s); //world
s = strchr(a, 'a'); //返回a所在的地址
if (s == NULL)
printf("s is NULL, not find\n");
else
printf("%s, \n", s);
s = strstr(a, "wo"); //返回w子串的首地址
printf("%s\n", s); //world
return 0;
}
(9)strtok 字符串分割函数
字符在第一次调用strtok()时必须给予参数s字符串,往后的调用则将参数s设置为NULL,每次调用成功则返回指向被分割片段的首地址
示例代码
#include <stdio.h>
#include <string.h>
int main()
{
char a[100] = "abc_3434_xxxxx_world";
char *s; //定义一个char的指针变量
// 1.一个一个取
s = strtok(a, "_"); //注意是"_"不是'_' //"_"就是分割符
printf("%s\n", s); // abc
s = strtok(NULL, "_"); //如何取出第二个
printf("%s\n", s); // 3434
s = strtok(NULL, "_"); //取出第三个
printf("%s\n", s); // xxxxx
s = strtok(NULL, "_"); //取出第四个
printf("%s\n", s); // world
s = strtok(NULL, "_"); //取出第五个
if (s == NULL){printf("s is NULL\n", s);}
// 2.一次性取出所有的
char b[100] = "abc_3434_xxxxx_world";
s = strtok(b, "_");
while (s != NULL)
{
printf("%s\n", s);
s = strtok(NULL, "_");
}
return 0;
}
运行结果
[root@localhost test]# gcc main.c -o main
[root@localhost test]# ./main
abc
3434
xxxxx
world
s is NULL
abc
3434
xxxxx
world
[root@localhost test]#
(10)atoi和atof和atol
- atoi的功能就是把一个char的数组转化为一个int,需要头文件stdlib.h。参数传递一个指针或者地址
- atof,把一个小数形式的字符串转化为一个浮点数float
- atol,将一个字符串转化为long类型
- 在c语言里面提供了把字符串转化为整数的函数,但并没有提供把整数转化为字符串的函数
- c语言并没有把一个int转化为字符串的函数,所以不要尝试使用itoa这种函数,可以使用sprintf将一个int,或者其他类型转化为一个字符串。
- atoi是标准的库函数,itoa不是c语言标准的库函数。
- itoa可以在vs编译,但在其它地方就不知道了
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char a[100] = "123";
int a1 = atoi(a);
printf("%d\n", a1); //123
char b[100] = "123.1";
float b1 = atof(b);
printf("%f\n", b1); //123.099998
long a2 = atoi(a);
printf("%d\n", a2); //123
//用sprintf将int转化为字符串
char str1[] = "abc";
int num = 45;
sprintf(a, "%d", num);
printf("%s\n", a); // 45
strcat(str1, a);
printf("%s\n", str1); //abc45
return 0;
}
(11)memset、memcpy和memmove
①memset 内存值设置
需要包含头文件#include <string.h>
void *memset(void *s, int c, size_t n);
memset的功能主要是设置一块内存区域的值重新为多少,第一个参数是内存首地址,第二个参数是要设置的值,第三个参数设置多少内存(单位字节)为那个值。
第一个参数一般是指定要置空内存的首地址,第二个参数一般写0,第三个参数是这块内存的 大,这种设置表示将这块内存的值全部重新设置为0。
#include <stdio.h>
#include <string.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 将数组内容全部改为0
// 1.传统方法:遍历每个成员,将其值改为0
// 2.用memset函数
memset(a, 0, sizeof(a));
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
②memcpy 内存值拷贝
void *memcpy(void *dest, const void *src, size_t n); //内存拷贝
第一个参数是目标内存首地址,第二个参数是源内存首地址,第三个参数是拷贝字节数
使用memcpy时,一定要确保内存没有重叠区域
同类型拷贝
#include <stdio.h>
#include <string.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int b[10] = {0};
//内存拷贝,将a的前n个字节拷贝到b的前n个字节
// 1.传统方法:遍历,然后赋值
// 2.用memcpy函数
memcpy(b, a, 8); //拷贝前8个字节,这里也就是2个int
for (int i = 0; i < 10; i++)
{
printf("b[%d] = %d\n", i, b[i]);
}
/*
b[0] = 1
b[1] = 2
b[2] = 0
b[3] = 0
b[4] = 0
b[5] = 0
b[6] = 0
b[7] = 0
b[8] = 0
b[9] = 0
*/
return 0;
}
不同类型拷贝
#include <stdio.h>
#include <string.h>
int main()
{
//将short拷贝到int,结果会是怎样
short a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int b[10] = {0};
printf("%p, %p\n", &a[0], &a[1]); // 0x7fff7bf088b0, 0x7fff7bf088b2
// 可以看到a[0]的地址比a[1]的地址小
printf("%d\n", sizeof(a)); //20
memcpy(b, a, sizeof(a)); //拷贝前20个字节
/*
由于两个short等于一个int
因此b中的值是a中两个short在内存中拼接后的值
以b[0]为例,它由a[0]和a[1]的补码拼接而成
a[0]: 1 → short补码=原码 00000000 00000001
a[1]: 2 → short补码=原码 00000000 00000010
小端对齐,即低地址对应低位数,因此1和2的补码拼接起来
00000000 00000010 00000000 00000001
->对应16进制: 0 0 0 2 0 0 0 1
打印显示时不展示前面的0
同理b[1]由a[2]和a[3]的补码拼接而成
a[2]: 3 → short补码=原码 00000000 00000011
a[3]: 4 → short补码=原码 00000000 00000100
小端对齐,低地址对应低位数,拼接
00000000 00000100 00000000 00000011
对应16进制 0 0 0 4 0 0 0 3
*/
for (int i = 0; i < 10; i++)
{
// 按16进制输出
printf("b[%d] = %x\n", i, b[i]);
}
/*
b[0] = 20001
b[1] = 40003
b[2] = 60005
b[3] = 80007
b[4] = a0009
b[5] = 0
b[6] = 0
b[7] = 0
b[8] = 0
b[9] = 0
*/
return 0;
}
内存重叠时拷贝要注意
#include <stdio.h>
#include <string.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
memcpy(&a[3], &a[0], 20); //拷贝5个int //注意存在内存重叠
for (int i = 0; i < 10; i++)
{
printf("a[%d] = %d\n", i, a[i]);
}
/*
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 1
a[4] = 2
a[5] = 3
a[6] = 4
a[7] = 5
a[8] = 9
*/
return 0;
}
③memmove 内存移动
void *memmove(void *dest, const void *src, size_t n); //内存移动,参数与memcpy一致
作用几乎与memcpy,都会进行拷贝。只是在存在重叠区域时的处理有所不同:
- `memcpy()` 假设源内存区域和目标内存区域之间没有重叠。如果这两个区域确实存在重叠,`memcpy()`的行为是未定义的,这意味着它可能会拷贝错误的数据或者破坏源数据。因此,使用`memcpy()`时,程序员需要确保源和目标内存区域不重叠
- `memmove()` 考虑到源内存区域和目标内存区域之间可能存在的重叠。如果存在重叠,`memmove()`会采取适当的措施来确保数据被正确拷贝。这通常涉及到先拷贝数据到一个临时位置,然后再从临时位置拷贝到目标位置,从而避免数据被意外覆盖。
#include <stdio.h>
#include <string.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int b[10] = {0};
// 内存移动
//
memmove(b, a, sizeof(a));
for (int i = 0; i < 10; i++)
{
printf("a[%d] = %d, b[%d] = %d\n", i, a[i], i, b[i]);
}
/*
a[0] = 1, b[0] = 1
a[1] = 2, b[1] = 2
a[2] = 3, b[2] = 3
a[3] = 4, b[3] = 4
a[4] = 5, b[4] = 5
a[5] = 6, b[5] = 6
a[6] = 7, b[6] = 7
a[7] = 8, b[7] = 8
a[8] = 9, b[8] = 9
a[9] = 10, b[9] = 10
*/
return 0;
}
二、字符串内容解析
(1)解析单个运算表达式
有一个字符数组,整数是任意的,中间可能是* + - /
比如char a[100] = "43-56="
写一个程序,将结算的结果追加到字符串a的后面
程序执行完成后a的值是"43-56=-13"
#include <stdio.h>
#include <string.h>
int main()
{
char a[10] = "43-56=";
int i, j;
char s;
// for (i = 0; i < sizeof(a); i++)
// {
// printf("a[%d] = %c\n", i, a[i]);
// }
//用%c取char
sscanf(a, "%d%c%d=", &i, &s, &j);
printf("%d%c%d\n", i, s, j);
//用switch来进行运算符号的判定
//因为用if语句会出问题,比如if (s == "-")这里的s是ASCII码值
int result = 0;
// if (s == "-")
// {
// printf("hellow world");
// }
switch (s)
{
case '+':
result = i + j;
break;
case '-':
result = i - j;
break;
case '*':
result = i * j;
break;
case '/':
result = i / j;
break;
default:
result = 0;
}
sprintf(a,"%d%c%d=%d", i, s, j, result);
printf("%s\n", a);
return 0;
}
(2)解析多个运算表达式
char a[100] = "12+5=;45-2=;34*2=;56/3=;
到底有多少分号是不确定的
写个程序,执行后=号后面自动添加计算结果
“12+5=17;45-2=43;34*2=68;56/3=18”
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char a[100] = "12+5=;45-2=;34*2=;56/3=";
// 1.用strtok将;部分拆开
char *s;
int i, j;
char operation;
int result;
char temp[sizeof(a)];
char a2[sizeof(a)] = ""; //初始为空字符串
s = strtok(a, ";");
while (s != NULL)
{
// 2.用sscanf获取每个数字和运算符
printf("%s\n", s);
sscanf(s, "%d%c%d=", &i, &operation, &j);
// 3.用switch计算结果
switch (operation)
{
case '+':
result = i + j;
break;
case '-':
result = i - j;
break;
case '*':
result = i * j;
break;
case '/':
result = i / j;
break;
default:
result = 0;
}
s = strtok(NULL, ";");
// 4.用sprintf得到指定格式的字符串
// printf("%d%c%d=%d\n", i, operation, j, result);
if (s != NULL)
{
sprintf(temp, "%d%c%d=%d;", i, operation, j, result);
}
else
{
sprintf(temp, "%d%c%d=%d", i, operation, j, result);
}
// 5.用strcat拼接字符串
strcat(a2, temp);
}
strcpy(a, a2);
printf("%s\n", a); // 12+5=17;45-2=43;34*2=68;56/3=18
return 0;
}