文章目录
一、字符相关原理
1.字符串和字符数组的区别
字符数组是存储连续字符的内存空间。字符数组不一定能构成字符串
char str[3] = { 'a','b','c' };
printf("%s", str);
我们看到除了 abc 还有很多奇怪的字符。根源是%s 格式是用于输出字符串的,它需
要从一个地址开始(str 数组名代表的是一个地址)连续输出每一个字符的影像,直到
遇到一个结束标记才能停下来。这个结束标记就是 0 号的字符编码。通常这个 0 号字符
编码我们用字符型的 8 进制常量的方式表示即’\0’。很显然,我们定义的字符数组
str 里面没有这个\0 的标记,所以它会一直把内存中的每个非 0 的字节的内容以编码对
应字符影像方式输出直到遇到第一个\0 为止。因此解决办法是在字符数组加入一个 \0。
通常对字符数组赋值采用字符串常量进行初始值设置,写法如下:
char str[] = "abc";
双引号下的 abc 称为字符串常量,其自动在后面补充\0。
2.字符串常量的存储原理
字符串常量在定义时,会被创建在常量区,相当于是一个常量,无法用指针等进行二次修改
char str[] = "Hello";
char* p = "Hello";
str[0] = 'h';
p[0] = 'h';
如图,我们发现第八行,在利用指针 p 间接修改首字母时提示了报错,这个提示的意思是利用指针 p 无法修改指向的空间,因为此时没有这个权限。我们来分析一下为什么。
前面提到,字符串常量在开始时被创建在了常量区,而 str 和指针 p 被创建在了栈区,放置在常量区的数据只能读取不能修改。在源代码中字符串常量代表了其所在常量区的首地址。在创建 str 时,实际上是常量区的字符串常量复制给了栈区的 str 所以在 str 修改时不会报错,因为此时是修改的栈区的字符串,而指针定义时指向的则是常量区的字符串常量地址,因此无法修改。
再来看这段代码
char str[] = "Hello";
str = "world";
这是因为str是一个字符串地址常量,无法进行修改。
二、字符串处理函数
处理字符串时,C 语言提供了一些功能函数。它们来自 string.h 这个头文件。
这里我们介绍一些常用的函数功能
1.得到字符串长度
size_t strlen ( const char * str );
strlen 是得到字符串长度函数,他的参数是字符串的首地址,并从字符串首地址出发,直至找到第一个’\0’为止注意’\0’是不会被计入到字符串长度的。例如字符串常量”abcd”的字符数组长度是5,因为’\0’也需要占一个字节的空间。但字符串长度是4。
char str[] = "abcd";
printf("字符个数:%d 字符串长度:%d", sizeof(str), strlen(str));
如果不用 String.h 中的函数 strlen 我们该如何得到字符串长度呢,我们可以字节写一个函数,前面说过 strlen 的原理是从首地址出发,一直搜寻到’\0’,那么我们自己实现的话就是
int get_length(const char* str)
{
const char* p = str;
int count = 0;
while (*p != '\0') {
count++;
p++;
}
return count;
}
这是一种方法,我们还可以通过返回字符串尾地址和首地址的地址差来计算字符串长度,
int get_length(const char* str)
{
const char* p = str;
while (*p != '\0') {
p++;
}
return (p - str)/sizeof(char);
}
2.字符串的复制
char * strcpy ( char * destination, const char * source );
该函数的作用是将第二个参数指向的字符串复制到第一个参数指向的字符数组中,包括终止 null 字符(并在该点停止)。
char a[20];
strcpy( a,"world" );
printf("%s\n",a);
但是这个函数在在有些编译器会报错,认为是一个不安全的函数,因为如果字符数组容量不够装下被复制的数组,就容易发生溢出。为了解决这一问题,我们可以用下面的函数
strcpy_s
这个函数和上一个类似,只是需要在第一个参数后面加一个参数来声明目标字符数组的大小,比如:
char a[20];
strcpy_s( a,20,"hello");
printf("%s", a);
这里的20是表明字符数组 a 可以容纳20个字符。
但是上面的两个函数都不太完美,如果我们指向要复制目标字符串的前面一段的话要用到循环,非常麻烦,下面的函数就完美解决了这个问题:
char * strncpy ( char * destination, const char * source, size_t num );
这里的参数表示:第一个参数是要复制内容的目标数组,第二个参数是要复制的字符串,第三个参数是要从第二个参数复制的最大字符数。
举个例子
char a[20];
strncpy(a, "abcdef", 4);
printf("%s\n",a);
因为我们只复制了前四个字符,所以字符数组a没有默认加 ‘\0’
上面介绍了很多字符串的复制函数,我们了解了复制的原理后也可以自己来仿真一个复制函数,拿 strcpy 为例:
char* copy_str(char* des, char* str)
{
const char* p = str;
char* q = des;
while (*p!='\0')
{
q = p;
p++;
q++;
}
*q = '\0';
return des;
}
这样我们就实现了函数的复制仿真
3.字符串的拼接
char * strcat ( char * destination, const char * source );
将源字符串的副本追加到目标字符串。destination 中的终止 null 字符被 source 的第一个字符覆盖,并且在 destination 中由两者串联形成的新字符串的末尾包含一个 null 字符。
第一个参数 destination:指向目标数组的指针,该数组应包含一个 C 字符串,并且足够大以包含连接的结果字符串。
第二个参数 source:要追加的 C 字符串。
char a[50]="hello ";
char p[]="world ";
strcat(a, p);
printf("%s\n",a);
同字符串的复制一样,字符串拼接也有安全问题,所以自然也有
strcat_s
这个函数,当然用法也是和字符串复制函数类似,需要标明容纳空间
char a[50]="hello ";
char p[]="world ";
strcat_s(a, 50, p);
printf("%s\n",a);
同理,如果我只想和该字符串的前几个字符拼接,也要用到 strncat:
char * strncat ( char * destination, const char * source, size_t num );
将 source 的前 num 个字符附加到目标,以及终止 null 字符。如果 source 中 C 字符串的长度小于 num,则仅复制直到终止 null 字符的内容。
char a[50]="hello ";
char p[]="world ";
strncat(a,p,3);
printf("%s\n",a);
接下来,我们仿真一下 strcat 函数的功能
char* connect_str(char* dest, const char* src)
{
const char* p = src;
char* q = dest;
while (*q != '\0')
{
q++;
}
while (*p != '\0')
{
*q = *p;
p++;
q++;
}
*q = '\0';
return dest;
}
除此之外,我们还可以通过计算目标字符数组的尾地址,然后利用前面讲的复制函数,将第二个字符串复制到目标字符数组的尾部:
const char* p = src;
char* q = dest + strlen(dest);
strcpy(q, src);
4.字符串的比较
int strcmp ( const char * str1, const char * str2 );
将 C 字符串 str1 与 C 字符串 str2 进行比较每个字符串的第一个字符。如果它们彼此相等,则继续以下对,直到字符不同或达到终止 null 字符。
若返回值< 0,则说明第一个字符串对应的 ASCII 值小于第二个字符串
若返回值> 0,则说明第一个字符串对应的 ASCII 值大于第二个字符串
若返回值= 0,则说明第一个字符串对应的 ASCII 值等于第二个字符串
printf("%d\n",strcmp("apple", "apple"));
printf("%d\n",strcmp("Apple", "apple"));
printf("%d\n",strcmp("apple", "Apple"));
接下来我们来仿真下一这个函数:
char compare_str(const char* str1, const char* str2)
{
const char* p = str1;
const char* q = str2;
while (*p != '\0' && *q != '\0')
{
if (*p == *q)
{
p++;
q++;
}
else if (*p > *q)
{
return 1;
}
else if (*q > *p)
{
return -1;
}
}
return 0;
}
5.字符串的匹配
const char * strstr ( const char * str1, const char * str2 );
返回指向 str1 中 str2 第一次出现的指针,如果 str2 不是 str1 的一部分,则返回 null 指针。匹配过程不包括终止 null 字符,但到此为止
char* p1 = "how do you do";
char* p2 = "do";
char* p = strstr(p1, p2);
printf("找到的第一个字符:%c\n", *p);
printf("位置:%d\n", p - p1);
printf("余下的字符串:%s",p);
仿真:
char* str_str(const char* str1, const char* str2)
{
const char* p = str1;
const char* q;
const char* pp;
while (*p != '\0') {
pp = p;
q = str2;
while (*pp==*q)
{
q++;
pp++;
if (*q == '\0') {
return p;
}
}
p++;
}
return NULL;
}
6.字符和字符串的键盘输入
方法一 (字符型与字符串型)scanf 与 printf:
char c;
char a[300];
scanf("%s %c", a, &c);
printf("%s\n%c\n", a, c);
方法二(字符型):getchar 与 putch
char c;
c = getchar();
putchar(c);
方法三(字符型):_getch 与 _putch
c = _getch();
_putch(c);
这两种方式需要#include<conio.h>这个控制台输入输出的头文件。_getch()工作时,用户输入的字符不会被显示出来,输入完不需要按回车就会立即被读取到字符变量 ch 中。
方法四(字符型)_getche 与 _putch
c = _getche();
_putch(c);
和方式三的唯一区别就是在输入时,输入的字符能被显示出来。
方法五(字符串) gets函数与puts函数
char c;
char a[300];
gets(a, 300);
puts(a);