一、字符串的几个特点
字符串:字符串或串是由数字、字母、下划线组成的一串字符。一般以0或者’\0’结尾,数字0和’\0’等价。 它是编程语言中表示文本的数据类型。
字符串的几个特点:
1.字符数组输出的时候,从开始位置直到找到0或’\0’结束; 如下:
char str1[] = {'h','e','l','\0','l','o'};
printf("%s\n",str1);
输出结果为:hel
2.字符数组在部分初始化后,后面的元素自动赋值为’\0’;
char str2[100] = {'h','e','l','l','o'};
printf("%s\n",str2);
输出为:hello,后面默认为0;
3.如果以字符串初始化忘了写’\0’,那么编译器默认会在字符串尾部添加’\0’;
char str2[10] = {'h','e','l','l','o'};
printf("%s\n",str2);
输出为:hello,默认在最后补齐:’\0’。
4.sizeof计算数组大小,包含’\0’字符;strlen计算时,不包含’\0’字符,碰到\0就结束了;
实例1:sizeof(str4))为100;strlen(str4))为5。
char str4[100] = "hello";
printf("sizeof(str4) = %d\n",sizeof(str4));//100
printf("strlen(str4) = %d\n",strlen(str4));//5
实例2:sizeof(str5))为12;strlen(str5))为5。
char str5[] = "hello\0world";
printf("%s\n",str5);
printf("sizeof(str5) = %d\n",sizeof(str5));//12
printf("strlen(str5) = %d\n",strlen(str5));//5
实例3:sizeof(str6))为12;strlen(str6))为11。
char str6[] = "hello\012world";
printf("%s\n",str6);
printf("sizeof(str6) = %d\n",sizeof(str6));//12
printf("strlen(str6) = %d\n",strlen(str6));//11
输出结果为:
为什么输出会这样呢?
“hello\012world”:hello为5个字符,world为5个字符,但是此时碰到了\0为转义字符,为8进制,\012表示为8进制下12这么一个数字;因此sizeof输出为:hello(5)+\012(1)+world(5)+\n(1)=12;strlen输出为hello(5)+\012(1)+world(5)=11;
那么为什么输出hello再输出world呢?
因为/012为八进制,将它转换为10进制下即为10,10再对应ASCII码中的10刚好为换行,所有输出会换行。
二、字符串的拷贝和反转
字符串拷贝:
方式1:利用下标方式进行拷贝
//字符串拷贝
//参数1:目标字符串,参数2源字符串
//需求:将源字符串拷贝到目标字符串中
void CopyString1(char* dest,char* source)//第一种,利用下标方式进行拷贝
{
int len = strlen(source);
int i = 0;
for (i; i<len; i++)
{
dest[i] = source[i];
}
dest[len] = '\0';
}
void test1()
{
char *str = "hello wolrd";
char buf[1024];
CopyString1(buf,str);
printf("%s\n",buf);
}
int main()
{
test1();
return 0;
}
拷贝成功:
方式2:利用字符串指针进行拷贝
void CopyString2(char* dest,char* source)//第二种,利用字符串指针进行拷贝
{
while (*source != '\n')
{
*dest = *source;
*dest++;
*source++;
}
*dest = '\n';//拷贝完让它结束
}
拷贝成功:
方式3:利用字符串指针进行拷贝,同方法二,但写法最简单
void CopyString3(char* dest,char* source)//第三种,写法最简单
{
while (*dest++ = *source++){}
}
拷贝成功:
字符串反转:
方式1:利用下标方式进行反转
//字符串反转
void ReverseString(char *str)//方法一:利用下标进行反转
{
int len = strlen(str);
int start = 0;//定义开始下标
int end = len-1;//定义末尾下标
while (start < end)
{
char temp = str[start];
str[start] = str[end];
str[end] = temp;
start++;
end--;
}
}
void test()
{
char str[] = "abcdefg";
ReverseString(str);
printf("%s\n",str);
}
int main()
{
test();
return 0;
}
反转成功:
方式2:利用指针进行反转
void ReverseString2(char* str)//方法二:利用指针进行反转
{
int len = strlen(str);
char* start = str;//定义开始下标
char* end = str + (len-1);//定义末尾下标
while(start < end)
{
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
反转成功:
三、sprintf格式化字符串
sprintf:可以将想要的结果输出到指定的字符串中,也可作为缓冲区,而printf只能输出到命令行上;sprintf()根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中去,直到出现字符串结束符’\0’为止。
函数原型:int sprintf(char* str,const char* format,...)
参数:
str:字符串首地址。
format:字符串格式,用法和printf()一样
...:拼接的参数内容,可选参数个数不定,根据实际情况使用
返回值:
成功:实际格式化的字符个数
失败:-1
实际功能1:格式化字符串(即将指定内容输入到字符串中)
//1.格式化字符串
void test()
{
char buf[1024];
memset(buf,0,1024);
sprintf(buf,"今天是%d年%d月%d日",2020,2,15);
printf("%s\n",buf);
}
输出结果:
实际功能2:拼接字符串
void test()
{
char buf[1024];
char str1[] = "hello";
char str2[] = "world";
int len = sprintf(buf,"%s%s",str1,str2);//sprintf返回的是字符串的长度
printf("buf = %s\nlen = %d\n",buf,len);
}
输出结果:
实际功能3:数字转为字符串
//3.数字转为字符串
void test()
{
char buf[1024];
int num = 100;
sprintf(buf,"%d",num);//sprintf返回的是字符串的长度
printf("buf = %s\n",buf);
}
输出结果:转换成为了一个字符串100
实际功能4:设置宽度,右对齐与左对齐(用的少)
//4.设置宽度,右对齐与左对齐
void test()
{
char buf[1024];
int num = 100;
sprintf(buf,"%8d",num);//右对齐
printf("buf1 = %s\n",buf);
sprintf(buf,"%-8d",num);//左对齐
printf("buf2 = %sa\n",buf);
}
输出结果:算上100总共占8个空位
实际功能5:转换为16进制小写或8进制(用的少)
//5.转换为16进制小写或8进制
void test()
{
char buf[1024];
int num = 100;
sprintf(buf,"0X%x",num);//转换为16进制小写
printf("buf1 = %s\n",buf);
sprintf(buf,"%o",num);//转换为8进制
printf("buf2 = %s\n",buf);
}
输出结果:
四、calloc和realloc的使用
calloc的使用
calloc: 在内存动态存储区中分配rmemb块长度为size字节的连续区域。calloc自动将分配的内存置0。
函数原型:void* calloc(size_t nmemb,size_t size);
参数:
nmemb:所需内存单元数量
size:每个内存单元的大小(单位:字节)
返回值:
成功:分配空间的起始地址
失败:NULL
calloc使用时:需要两个参数,并且会自动将分配的内存置0;但同样是在堆区分配的内存,还需要手动释放
我们先使用malloc在堆区分配一块内存,不初始化它的值为0
void test()
{
int* p = (int*)malloc(sizeof(int)*10);
int i = 0;
for (i; i<10; i++)
{
printf("%d\n",p[i]);
}
}
我们发现,使用malloc如果不初始化的话,会出现随机值
此时,我们使用calloc在堆上分配内存;
void test()
{
int*p = (int*)calloc(10,sizeof(int));
int i = 0;
for (i; i<10; i++)
{
printf("%d\n",p[i]);
}
if (p != NULL)
{
free(p);
p = NULL;
}
}
分配好后,会自动将内存置0;同样也需要手动释放内存;
realloc的使用
realloc: 重新分配用malloc或者calloc函数在堆中分配内存空间的大小; realloc不会自动清理增加的内存,需要手动清理,如果指定的地址后面没有空间,那么realloc会重新分配新的连续内存把旧内存的值拷贝到新内存,同时释放旧内存。
函数原型:void* *realloc(ptr,size_t size);
参数:
ptr:为之前用malloc或者calloc分配的内存地址,如果此参数等于NULL,那么和realloc与malloc功能一致。
size:为重新分配内存的大小(单位:字节)
返回值:
成功:新分配的堆内存地址
失败:NULL
realloc机制:
1.如果原来的空间后面有足够大的空闲空间,那么直接在原来的后面继续开辟,返回原有的首地址。
2.如果原来的空间后续没有足够大的空闲空间,那么系统会直接分配一个空间,这个空间就是需要的内存空间。并将原有空间下的数据拷贝到新空间下,并且会将原有空间释放返回新空间的首地址,但是后续开辟的空间并未进行初始化。。
我们来验证一下这两种机制:
验证1:如果原来的空间后面有足够大的空闲空间,那么直接在原来的后面继续开辟,返回原有的首地址。
void test()
{
int* p = (int*)malloc(sizeof(int)*10);
printf("%d\n",p);//打印p的地址
for (int i = 0; i<10; i++)
{
p[i] = i;//0-9
}
p = (int*)realloc(p,sizeof(int)*11);//情况1:有可能够用
for (int i = 0; i<10; i++)
{
printf("%d\n",p[i]);
}
printf("%d\n",p);//再次打印p的地址
}
打印结果:原有地址与分配完地址相同,机制1成立;
验证2:如果原来的空间后续没有足够大的空闲空间,那么系统会直接分配一个空间,这个空间就是需要的内存空间。并将原有空间下的数据拷贝到新空间下,并且会将原有空间释放返回新空间的首地址,但是后续开辟的空间并未进行初始化。。
将此行改为20,后续空间就可能不够大了
void test()
{
int* p = (int*)malloc(sizeof(int)*10);
printf("%d\n",p);//打印p的地址
for (int i = 0; i<10; i++)
{
p[i] = i;//0-9
}
p = (int*)realloc(p,sizeof(int)*20);//情况2:后续空间可能不够大了,但是后续开辟的空间并未初始化
for (int i = 0; i<20; i++)
{
printf("%d\n",p[i]);
}
printf("%d\n",p);//再次打印p的地址
if (p != NULL)
{
free(p);
p = NULL;
}
}
输出结果:前后地址不一样了,机制2也成立。但是后续开辟的空间并未进行初始化。
五、ssacnf的使用
sscanf: 从一个字符串中读进于指定格式相符的数据。利用它可以从字符串中取出整数、浮点数和字符串;从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
sscanf和scanf的区别: scanf是以键盘作为输入源,sscanf是以字符串作为输入源。
函数原型:int sscanf(const char* str,const char* format,...);
参数:
str:指定的字符串首地址
format:字符串格式,用法和scanf()一样
返回值:
成功:返回参数数目
失败:-1
用法1:% * s或% * d,忽略数据。
忽略数字:如图
void test()
{
char* str = "12345abcde";
char* buf[1024] = {0};
sscanf(str,"%*d%s",buf);//忽略数字,%*表示忽略
printf("%s\n",buf);
}
输出结果:
忽略字符串方式1:中间加空格(但一般不允许改动字符串)
char* str = "abcde 12345";//忽略数字方式1:中间加空格
忽略字符串方式2:中间加\t(但一般不允许改动字符串)
char* str = "abcde\t12345";//忽略数字方式2:制表符忽略
忽略字符串方式3:%*[a-z]%s方法
void test()
{
char* str = "abcde12345";
char* buf[1024] = {0};
sscanf(str,"%*[a-z]%s",buf);//忽略数字,%*表示忽略
printf("%s\n",buf);
}
成功将字符串[a-z]忽略:
用法2:%d[width]s 读取指定宽度的数据
void test()
{
char* str = "abcde12345";
char* buf[1024] = {0};
sscanf(str,"%6s",buf);//读取6个宽度
printf("%s\n",buf);
}
如图:读取了6个宽度的数据
用法3:%[a-z]匹配a-z中任意字符(尽可能多的匹配)
void test()
{
char* str = "12345abcde";
char* buf[1024] = {0};
sscanf(str,"%*d%[a-c]",buf);//匹配a-c的数据
printf("%s\n",buf);
}
成功匹配a-c的数据:
用法4:%[aBz]匹配a、B、c中的一员,贪婪性:只要有一个匹配失败,后面都不会匹配
void test()
{
char* str = "aabcde12345";
char* buf[1024] = {0};
sscanf(str,"%[aBc]",buf);//只要有一个匹配失败,后面都不会匹配
printf("%s\n",buf);
}
输出如图:只要有一个匹配失败,后面都不会匹配
用法5:%[^a]匹配非a的任意字符,贪婪性
void test()
{
char* str = "abcde12345";
char* buf[1024] = {0};
sscanf(str,"%[^c]",buf);//只要有一个匹配失败,后面都不会匹配
printf("%s\n",buf);
}
输出如图:匹配非a的字符,只要有一个匹配失败,后面都不会匹配
用法6:%[^a-z]匹配非a-z的任意字符
void test()
{
char* str = "abcde12345";
char* buf[1024] = {0};
sscanf(str,"%[^0-9]",buf);
printf("%s\n",buf);
}
输出如图:
案例1: 利用sscanf截取数字。 利用sscanf将一个本机ip地址分别截取到4个num中。
void test()
{
char* ip = "127.0.0.1";
int num1 = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
sscanf(ip,"%d.%d.%d.%d",&num1,&num2,&num3,&num4);
printf("num1 = %d\n",num1);
printf("num2 = %d\n",num2);
printf("num3 = %d\n",num3);
printf("num4 = %d\n",num4);
}
截取成功:
案例2: 提取字符串中有效的名称。
void test()
{
char* str = "abcde#hello@12345";
char* buf[1024] = {0};
sscanf(str,"%*[^#]#%[^@]",buf);
printf("%s\n",buf);
}
提取成功:可以有多种方式提取
案例3:给定字符串:helloworld@itcast.cn实现helloworld与itcast.cn的输出:
void test()
{
char* str = "helloworld@itcast.cn";
char* buf1[1024] = {0};
char* buf2[1024] = {0};
sscanf(str,"%[a-z]%*[@]%s",buf1,buf2);
printf("buf1 = %s\nbuf2 = %s\n",buf1,buf2);
}
成功分离: