学习字符串时先要对字符串常量和字符数组有个明确的概念
字符数组
- 用来存放字符的数组
char str1[] = {'a','b','c','d','e'};
char str2[] = "abcdefg";
char buf1[] = {'a','b','c','d','e'};//默认元素个数
char buf2[4] = {'a','b','c','d'};//指定元素个数
char buf3[4] = {'a','b'};//指定4个元素,其余两个自动补0
用字符串来初始化数组
char buf1[] = {'a','b','c','\0'};
char buf2[] = {"abcdefg"};
char buf3[] = "abcdefg";
char buf4[10] = "abcd";
- 在c语言中使用字符数组来模拟字符串
- c语言中字符串都是以‘\0’结束的字符数组
- 字符串可以在栈、堆或只读存储区分配空间
- strlen()求字符串长度,字符串长度不包括‘\0’
- sizeof(buf3)字符串类型的大小,包括‘\0’,如上buf3 sizeof(buf3) = 8
字符串常量
- c语言中没有字符串这个类型,但是存在字符串常量,以‘\0’来结尾
char *str = "hello world";
字符串常量和指针的关系
由上面可以看出,字符串常量保存的是一个地址,是字符串常量首字符的地址,而不是这个字符串本身。因此在c语言中要想访问一个字符串通常声明一个char *类型的指针并初始化一个字符串常量。
如何访问字符串中的每一个字符,以为字符串都是以‘\0’结尾的。因此可以循环遍历,如果不是‘\0’,则地址下移。
while (*str)\\ *str != '\0'
{
printf("%c\n",*str);
str++;
}
字符串相关一级指针内存模型
char buf[10]= "abcde";
char buf2[] = "aaabb";
char *p1 = "hello world";
char *p2 = malloc(20); strcpy(p2, "abcd");
操作字符串的两种方法
- 数组下标
- 指针
- 字符数组名,代表字符数组首元素的地址,不代表整个数据的地址
char buf[] = "abcdefgh";
//数组下标
int i = 0;
int count = strlen(buf);
for ( i = 0; i < count; i++)
{
printf("buf[%d] = %c\n",i,buf[i]);
}
//指针
for ( i = 0; i < count; i++)
{
printf("buf[%d] = %c\n",i,*(buf+i));
}
int index = 0;
while (buf[index])
{
printf("buf[%d] = %c\n",index,*(buf+index));
index++;
}
字符串做函数参数
void Copy_Str1(char *from,char *to)
{
for(;*from != '\0';from++,to++)
{
*to = *from;
}
*to = '\0';
}
//上下对比
int Copy_Str2(const char *from,char *to)
{
if(from == NULL || to == NULL)
{
printf("throw error\n");
return -1;
}
while ((*to++ = *from++) != '\0')
{
;
}
return 0;
}
对比上面两个方法,都是拷贝字符的方法,但是推荐使用第二种Copy_Str2(),首先被拷贝者来说只进行简单的读取操作,因此使用const关键字来提高数据的安全性。对拷贝者来说是要对其进行读写操作,所以不需要加const。添加判断机制防止空指针异常,使用while语法可以减少代码行数,提高了编译效率。
常见字符串处理函数
gets()
-
从标准输入读入字符,并保存到s指定的内存空间,直到出现换行符或读到文件结尾为止。
#include <stdio.h>
//s:字符串首地址
char *gets(char *s);
//返回值:
// 成功:读入的字符串
// 失败:NULL
char str[100];
printf("请输入str: ");
gets(str);
printf("str = %s\n", str);
gets(str)与scanf(“%s”,str)的区别:
- gets(str)允许输入的字符串含有空格
- scanf(“%s”,str)不允许含有空格
注意:由于scanf()和gets()无法知道字符串s大小,必须遇到换行符或读到文件结尾为止才接收输入,因此容易导致字符数组越界(缓冲区溢出)的情况。
fgets()
- 从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。
#include <stdio.h>
//s:字符串
//size:指定最大读取字符串的长度(size - 1)
//stream:文件指针,如果读键盘输入的字符串,固定写为stdin
char *fgets(char *s, int size, FILE *stream);
//返回值:
// 成功:成功读取的字符串
// 读到文件尾或出错: NULL
char str[100];
printf("请输入str: ");
fgets(str, sizeof(str), stdin);
printf("str = \"%s\"\n", str);
fgets()在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车也做为字符串的一部分。通过scanf和gets输入一个字符串的时候,不包含结尾的“\n”,但通过fgets结尾多了“\n”。fgets()函数是安全的,不存在缓冲区溢出的问题。
puts()
- 标准设备输出s字符串,在输出完成后自动输出一个'\n'。
#include <stdio.h>
//s:字符串首地址
int puts(const char *s);
//返回值:
// 成功:非负数
// 失败:-1
int main()
{
puts("hello world");
printf("Hello World");
return 0;
}
fputs()
- 将str所指定的字符串写入到stream指定的文件中, 字符串结束符 '\0' 不写入文件。
#include <stdio.h>
//str:字符串
//stream:文件指针,如果把字符串输出到屏幕,固定写为stdout
int fputs(const char * str, FILE * stream);
//返回值:
// 成功:0
// 失败:-1
int main()
{
printf("hello world\n");
puts("hello world");
fputs("hello world", stdout);
return 0;
}
fputs()是puts()的文件操作版本,但fputs()不会自动输出一个'\n'
strcpy()
- 把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去
#include <string.h>
//dest 目的字符串首地址
//src 源字符首地址
char *strcpy(char *dest, const char *src);
//返回值:
// 成功:返回dest字符串的首地址
// 失败:NULL
char src[] = "abcdef";
char dest[10] = "12345";//dest的内存空间要足够大,否则会造成缓冲溢出
strcpy(dest,src);
printf("%s\n",dest);
strcpy前 strcpy后
注意:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。
strncpy()
-
把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'。
#include <string.h>
//dest:目的字符串首地址
//src: 源字符首地址
//n: 指定需要拷贝字符串个数
char *strncpy(char *dest, const char *src, size_t n);
//返回值:
// 成功:返回dest字符串的首地址
// 失败:NULL
char src[] = "abcdefd";
char dest[10] = "12345";
strncpy(dest,src,4);
dest[4] = '\0';//把src中的前四个字符拷贝给了dest,没有以'\0'结尾,手动添加。
printf("%s\n",dest);
strcpy前 strcpy后
memcpy()
-
拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。
#include <string.h>
//dest:目的内存首地址
//src:源内存首地址,注意:dest和src所指的内存空间不可重叠
//n:需要拷贝的字节数
void *memcpy(void *dest, const void *src, size_t n);
//返回值:dest的首地址
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int b[10];
memcpy(b, a, sizeof(a));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d, ", *(b +i));
}
strset()
- 将字符串str中所有的字符都设置成为指定的字符。
#include <string.h>
//_Str 目标字符串
//_Val 虽然参数为int,但必须是unsigned char , 范围为0~255
char *__cdecl strset(char *_Str,int _Val)
//返回值:
// 成功:返回被替换后的str首地址
// 失败:NULL
char str[10] = "12345";
char symbol = 'b';
strset(str,symbol);
printf("%s\n",str);
strnset()
- 将一个字符串中的前n个字符都设为指定字符。
#include <string.h>
//_Str 目标字符串
//_Val 虽然参数为int,但必须是unsigned char , 范围为0~255
//_MaxCount 最大字符数范围
char *__cdecl strnset(char *_Str,int _Val,size_t _MaxCount)
//返回值:
// 成功:返回被替换后的str首地址
// 失败:NULL
char str[30] = "123asd45sdasd";
char symbol = 'k';
strnset(str,symbol,10);
printf("%s\n",str);
memset()
-
将s的内存区域的前n个字节以参数c填入
#include <string.h>
//s:需要操作内存s的首地址
//c:填充的字符,c虽然参数为int,但必须是unsigned char , 范围为0~255
//n:指定需要设置的大小
void *memset(void *s, int c, size_t n);
//返回值:s的首地址
char a[10];
memset(a, 0, sizeof(a));
strcpy(a,"abcd");
int i = 0;
for (i = 0; i < strlen(a); i++)
{
printf("%c\n", a[i]);
}
strpbrk()
- 比较字符串str1和str2中是否有相同的字符,如果有,则返回该字符在str1中的位置的指针。
#include <string.h>
//_Str 待比较的字符串
//_Control 指定被搜索的字符串
char *__cdecl strpbrk(const char *_Str,const char *_Control);
//返回值 返回指针,搜索到的字符在_Str中的索引位置的指针。
char *str1 = "hello world!";
char *str2 = "world";
char *str3 = strpbrk(str1,str2);
printf("%s\n",str3);
strtok()
-
分解字符串 str 为一组字符串,delim 为分隔符。
#include <string.h>
//str 要被分解成一组小字符串的字符串。
//delim 按照那种字符进行分割
char *strtok(char *str, const char *delim)
//返回值 该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。
char str[] = "this is strtok function";
char *s = " ";
char *result = strtok(str, s);//(位置1)
printf("%s\n",str);//特别要注意分割处理后原字符串 str 会变,变成第一个子字符串
while (result)
{
printf("%s\n",result);
result = strtok(NULL,s);//(位置2)
}
注意:strtok(NULL,s)中的NULL怎么理解。这里的strtok涉及到了两个指针pointer_a和pointer_b,pointer_a是用来指向返回的字符串的指针;pointer_b是指向匹配字符的位置。因此上面代码(位置1)处执行完后pointer_a指向了“this”的首部元素位置,pointer_b指向了“this is”中间的空格位置,代表pointer_b之前的位置已经进行过查找匹配了。这样,在while循环中的(位置2)执行时,只要把strtok()的第一个参数设置为NULL,就可以直接从pointer_b位置开始进行查找匹配了。因此这里传入NULL是告诉编译器,之前的位置都匹配过了,接下来从pointer_b开始继续向下匹配。
第一次执行完strtok时,源字符串会被分割处理,变成第一个子字符串。
还有很多字符串处理函数,这里就不一一列举了,其实用的的时候可以百度一下。
const理解
在指针中学过了,指针常量和常量指针。这里在详细了解一下,const关键字是定义一个常量意味着这个变量函数只读,
const char *a;
char * const b;
const char * const c;
- a 是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改)
-
b 是常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)
-
c 是一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)
合理的使用const可以有效提高代码质量,减少bug,对函数参数使用const可以清楚的分清参数的输入和输出特性