C语言程序设计学习笔记:P10-字符串


一、字符串

1.1 字符串

定义字符数组

char word[] = {
    
    ‘H’, ‘e’, ‘l’, ‘l’, ‘o’,!};

这个数组中若干字符组合在一起,并且它们组合起来是有意义的。但是这不是C语言的字符串,因为不能用字符串的方式做计算。


定义字符串

char word[] = {
    
    ‘H’, ‘e’, ‘l’, ‘l’, ‘o’,!,’\0};

C语言中字符串的性质如下

• 以0(整数0)结尾的一串字符
  • 0或’\0’是一样的,但是和’0’不同
• 0标志字符串的结束,但它不是字符串的一部分
  • 计算字符串长度的时候不包含这个0
• 字符串以数组的形式存在,以数组或指针的形式访问
  • 更多的是以指针的形式
• string.h 里有很多处理字符串的函数

定义字符串变量的方式

char *str = “Hello”;
char word[] = “Hello”;
char line[10] = “Hello”; //占据6个字节,因为后面还有个0

字符串常量:

• “Hello”
• ″Hello″ 会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0
• 两个相邻的字符串常量会被自动连接起来
  • 比如之前写的printf("请输入身高的英尺和英寸,"  "如输入\"5 7\"表示5英尺7英寸:");
  • 这两个字符串会被连接起来成为1个大的字符串。
• 行末的\表示下一行还是这个字符串常量
  • printf("请输入身高的英尺和英寸, \
        如输入\"5 7\"表示5英尺7英寸:");  

注:如果用\来连接两个字符串,\和后面字符之间的一些空格、Tab也会进入到新的字符串中。因此,一般第二行的字符串我们都贴到最左侧。
在这里插入图片描述


总结:

• C语言的字符串是以字符数组的形态存在的
  • 不能用运算符对字符串做运算
  • 通过数组的方式可以遍历字符串
• 唯一特殊的地方是字符串字面量可以用来初始化字符数组
• 以及标准库提供了一系列字符串函数

1.2 字符串变量

上面我们写了三种定义字符串的方式,既然字符串也是一个特殊的字符数组,那我们试着对其内容进行修改,看看会发生什么。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    
    
	char *s = "Hello World";
	s[0] = 'B';

	printf("Here!s[0]=%c\n", s[0]);

	return 0;
}

在上面程序中我们设置修改字符串的首个字母。运行,可以发现直接异常。
在这里插入图片描述


为什么会出异常呢?我们先来看看s的地址是多少,同时定义一个内容相同的字符串变量s2和一个int类型的变量i,看看这三者的地址有什么差异。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    
    
	int i = 0;
	char *s = "Hello World";
	char *s2 = "Hello World";

	printf("&i=%p\n", &i);
	printf("s =%p\n", s);
	printf("s2=%p\n", s2);	
	printf("Here!s[0]=%c\n", s[0]);

	return 0;
}

运行,可以发现s2与s的地址竟然相同,同时s与s2的地址明显比int类型变量i的地址小得多。
在这里插入图片描述


实际上,造成这种结果的原因是:i这个变量是个本地变量,它与s所指的那个字符串在内存中不在同一块区域,它们离得很远。s所指的那个字符串在内存中的代码段,且代码段中的内容是只读的,不可修改。
在这里插入图片描述
因此,对于字符串常量,总结如下:

char* s = "Hello, world!";
• s是一个指针,初始化为指向一个字符串常量
  • 由于这个常量所在的地方,所以实际上s是 const char* s ,但是由于历史的原因,编译器接受不带const的写法
  • 但是试图对s所指的字符串做写入会导致严重的后果
• 如果需要修改字符串,应该用数组:
char s[] = "Hello, world!";

我们来看下char* s = "Hello, world!"和char s[] = "Hello, world!"的区别和联系。我们使用数组定义一个内容相同的字符串变量s3,然后看看其地址且修改其首字母。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    
    
	int i = 0;
	char *s = "Hello World";
	char *s2 = "Hello World";
	char s3[] = "Hello Wordl";

	printf("&i=%p\n", &i);
	printf("s =%p\n", s);
	printf("s2=%p\n", s2);	
	printf("s3=%p\n", s3);
	s3[0] = 'B';
	printf("Here!s3[0]=%c\n", s3[0]);

	return 0;
}

运行,可以看出s3是个本地变量,它的值可以修改。
在这里插入图片描述

用指针和数组构建字符串的区别和用处:

• char *str = “Hello”;
• char word[] = “Hello”;
• 数组:这个字符串在这里
  • 作为本地变量空间自动被回收
• 指针:这个字符串不知道在哪里
  • 处理参数
  • 动态分配空间

char*是字符串?

• 字符串可以表达为char*的形式
• char*不一定是字符串
  • 本意是指向字符的指针,可能指向的是字符的数组(就像int*一样)
  • 只有它所指的字符数组有结尾的0,才能说它所指的是字符串

1.3 字符串输入输出

相较于C语言后面的语言,它对字符串处理的能力还是不足。比如说现在有两个字符串要做赋值

char *t = “title”;
char *s;
s = t;
并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的任何操作就是对t做的

在这里插入图片描述


对于C语言的基础类型,我们有办法做输入输出。对于字符串,printf和scanf也有特殊的手段对其做输入和输出,那就是%s。

char string[8];
scanf(%s”, string);
printf(%s”, string);

我们来写代码来看看printf和scanf对字符串做操作的具体细节。

#include <stdio.h>

int main(void)
{
    
    
	char string[8];
	scanf("%s", string);
	printf("%s##\n", string);
	return 0;
}

运行,输入Hello World,可以发现只读到了Hello。
在这里插入图片描述


我再来测试一下,看看连续输入两个字符串会是什么结果。

#include <stdio.h>

int main(void)
{
    
    
	char word[8];
	char word2[8];
	scanf_s("%s", word);
	scanf_s("%s", word2);
	printf("%s##%s##\n", word, word2);
	return 0;
}

我们输入Hello World和Hello 回车 World。可以发现分别被读入word和word2。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void)
{
    
    
	char word[8];
	char word2[8];
	scanf("%s", word);
	scanf("%s", word2);
	printf("%s##%s##\n", word, word2);
	return 0;
}

运行,分别测试两种输入方式,可以看出成功读入。
在这里插入图片描述
因此,scanf读取字符串总结如下:

• scanf读入一个单词(到空格、tab或回车为止)
• scanf是不安全的,因为不知道要读入的内容的长度
  • 比如我这里的word是8个字节,而scanf并不知道

如果我们输入超过8个字节,如我输入12345678 回车 12345678,直接报错,数组越界。
在这里插入图片描述因此,安全的方式是:在%和s之间插入一个数字,代表输入多少个字符,如%7s。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void)
{
    
    
	char word[8];
	char word2[8];
	scanf("%7s", word);
	scanf("%7s", word2);
	printf("%s##%s##\n", word, word2);
	return 0;
}

我分别输入123 回车 12345678。可以看出,123不到7个被直接读入,12345678只被读取了前7个。
在这里插入图片描述
如果我直接输入一个超过7位的数,如12345678,可以看出1234567给了第一个字符串,8自动给了第二个字符串。
在这里插入图片描述

因此,对于scanf安全地输入字符串总结如下:

• char string[8];
• scanf(“%7s”, string);
• 在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一

常见的错误:

• char *string;
• scanf(“%s”, string);
• 以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了
  • 由于没有对string初始化为0,所以不一定每次运行都出错

空字符串

• char buffer[100]=””;
  • 这是一个空的字符串,buffer[0] == ‘\0’
• char buffer[] = “”;
  • 这个数组的长度只有1,即buffer[0]才有内容,是个'\0'。

1.4 字符串数组

如果想写一个字符串数组表达多个字符串,该怎么写呢?我们试着来写几种看看是不是字符串数组。

1、char **a
   a是一个指针,指向另一个指针,那个指针指向一个字符(串)
2、char a[][]
   a是一个二维数组,第二个维度的大小不知道,不能编译
3、char a[][10]
   a是一个二维数组,a[x]是一个char[10]
4、char *a[]
   a是一个一维数组,a[x]是一个char*

对于第三种和第四种可以用来当作字符串数组,但是第三种有一定局限性,即由于二维数组必须确定列数,导致所有的字符串必须小于10个字符。第三种与第四种方法的区别如下图所示:
在这里插入图片描述


字符串数组还有个有趣的应用的地方,那就是main函数的参数。

• int main(int argc, char const *argv[])
• argv[0]是命令本身,argc代表后面字符串数组argc有多少个字符串
  • 当使用Unix的符号链接时,反映符号链接的名字

我们写代码来看看这个argv字符串数组里面有什么东西。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(int argc, char const *argv[])
{
    
    
	int i;
	for (i = 0; i < argc; i++)
	{
    
    
		printf("%d:%s\n",i,argv[i]);
	}
	return 0;
}

运行,可以看出输出了可执行文件位置和名称。
在这里插入图片描述
如果我们在后面再接一些东西,也会打印出来。
在这里插入图片描述

二、字符串函数

2.1 单字符输入输出

putchar函数

• int putchar(int c);
• 向标准输出写一个字符
• 返回写了几个字符,EOF(-1)表示写失败

getchar函数

• int getchar(void);
• 从标准输入读入一个字符
• 返回类型是int是为了返回EOF(-1)
  • Windows—>Ctrl-Z
  • Unix—>Ctrl-D

我们写个程序来详细探究putchar和getchar的功能。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(int argc, char const *argv[])
{
    
    
	int ch;
	while ((ch=getchar()) != EOF)
	{
    
    
		putchar(ch);
	}
	printf("EOF\n"); 
	return 0;
}

我们运行,并输入一些东西来测试,发现都原封不动地打印了出来。直到我们输入了Ctrl+C,getchar停止读取。当我们按下Ctrl+Z,并继续按下回车,也会产生相同的效果。(我这里和视频中的Dev C++不同,视频里面按下Ctrl+C程序直接结束,不会打印EOF。视频里面输入Ctrl+Z效果和我这里相同)
在这里插入图片描述
在这里插入图片描述


思考:getchar()只读1个字符,但是我这里输入了很多东西它都没有回答我,直到我按下回车。同时,按下Ctrl+C程序直接结束了,按下Ctrl+D,getchar就停止读取了,这是为什么呢?

这是因为前面我们讲过,我们的程序和显示器、键盘之间通过Shell相连。键盘输入的东西,Shell会形成一个行编辑,在这个缓冲区内存放了我们输入的所有东西。只要在缓冲区内遇到回车,便会将前面的东西打印出来(getchar一个一个取出来打印,scanf将多个字符看成一个大的东西输出打印)。当我们输入Ctrl+D,就会在缓冲区内加上一个类似于EOF的东西,getchar读到它后就不再读取。如果我们输入Ctrl+C(Windows是Ctrl+Z),直接杀死我们的程序。
在这里插入图片描述


2.2 字符串函数strlen

C语言提供了很多函数来帮助我们处理字符串,这些函数的原型都在string.h中,因此使用这些函数需要#include <string.h>。我们首先来看strlen函数。

• size_t strlen(const char *s);
• 返回s的字符串长度(不包括结尾的0)

我们写一些代码来看看strlen的用法。我们定义了一个字符串,然后分别使用strlen和sizeof求其长度与大小。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    
    
	char line[] = "Hello";
	printf("strlen=%lu\n", strlen(line));
	printf("sizeof=%lu\n", sizeof(line));

	return 0;
}

可以看出,字符串长度为5,大小为6(含有最后的’\0’)。
在这里插入图片描述


我们试着自己来写这个strlen函数看看。我们可以从字符串首个字符开始遍历,直到遇到’\0’。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

size_t mylen(const char* s)
{
    
    
	int idx = 0;
	while (s[idx] != '\0') {
    
    
		idx++;
	}
	return idx;
}

int main(int argc, char const *argv[])
{
    
    
	char line[] = "Hello";
	printf("strlen=%lu\n", mylen(line));
	printf("sizeof=%lu\n", sizeof(line));

	return 0;
}

运行,使用自己写的strlen函数来查看字符串的长度,结果正确。
在这里插入图片描述

2.3 字符串函数strcmp

strcmp用来比较两个字符串,最后的结果有三种:相对、大于、小于。

• int strcmp(const char *s1, const char *s2);
• 比较两个字符串,返回:
  • 0:s1==s2
  • >0:s1>s2
  • <0:s1<s2

那么如何来定义两个字符串谁大谁小呢?我们先来测试下直接使用==来比较两个字符串和使用strcmp来比较两个字符串。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    
    
	char s1[] = "abc";
	char s2[] = "abc";
	printf("%d\n", s1==s2);
	printf("%d\n", strcmp(s1,s2));

	return 0;
}

数组1==数组2这种写法,结果恒为0(即不相等)。注意:两个字符串相等,strcmp返回0.
在这里插入图片描述


我们再来测试下不相等的情况。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    
    
	char s1[] = "abc";
	char s2[] = "bbc";
	printf("%d\n", strcmp(s1,s2));

	char s3[] = "abc";
	char s4[] = "Abc";
	printf("%d\n", strcmp(s3, s4));
	printf("%d\n", 'a' - 'A');
	return 0;
}

运行,可以发现’a’比’b’小,所以返回-1。'a’比’A’大,所以返回1。(在视频中Dev C++中strcmp返回不相等字符对应的差值,如代码中strcmp(s3,s4)会返回32。这是由于我们的编译器不同所导致)
在这里插入图片描述


我们再看一种特殊情况,两个字符串前面一行,其中一个字符串只比另外一个字符串多一个空格,看看会发生什么。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    
    
	char s1[] = "abc";
	char s2[] = "abc ";
	printf("%d\n", strcmp(s1,s2));
	printf("%d\n", '\0' - ' ');
	return 0;
}

可以看出,带空格的更大。这是由于s1[3]-s2[3]= ‘\0’ - ’ ’ = 0 - 32 = -32。
在这里插入图片描述


我们自己写一个strcmp看看,其中我们使用了两种方式:数组和指针的方式。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

//使用数组的方式
int mycmp1(const char* s1, const char* s2)
{
    
    
	int idx = 0;
	while (s1[idx] == s2[idx] && s1[idx] != '\0') {
    
    
		idx++;
	}
	return s1[idx] - s2[idx];
}

//使用指针的方式
int mycmp2(const char* s1, const char* s2)
{
    
    
	while (*s1 == *s2 && *s1 != '\0') {
    
    
		s1++;
		s2++;
	}
	return *s1 - *s2;
}

int main(int argc, char const *argv[])
{
    
    
	char s1[] = "abc";
	char s2[] = "Abc ";
	printf("%d\n", mycmp1(s1,s2));
	printf("%d\n", mycmp2(s1,s2));
	return 0;
}

测试,可以发现两种方法结果都正确。这也是我们常用的处理字符串的两种方法:把其当作数组、把其当作指针。
在这里插入图片描述

2.4 字符串函数strcpy

字符串拷贝函数strcpy定义如下:

• char * strcpy(char *restrict dst, const char *restrict src);
• 把src的字符串拷贝到dst
  • restrict表明src和dst不重叠(C99)
• 返回dst
  • 为了能链起代码来

其中,restict表明src和dst不重叠,重叠的情况是什么样的,如下图所示:
在这里插入图片描述
为什么不能重叠呢。因为字符串拷贝操作经常使用,现在的计算机都是多核多线程,这些拷贝操作通常由多个核一起完成,如果出现重叠就会导致错误。

此外,函数返回就是dst,这是因为我们希望strcpy的结果能够再参与其它运算。我们拿strcpy经常做的一件事就是复制一个字符串。

//注意要+1,因为有个结尾的'\0'
char *dst = (char*)malloc(strlen(src)+1);
strcpy(dst, src);

我们自己来写一个strcpy函数。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

//使用数组的方式
char* mycpy1(char* dst, const char* src)
{
    
    
	int idx = 0;
	while (src[idx]) {
    
    
		dst[idx] = src[idx];
		idx++;
	}
	dst[idx] = '\0';
	return dst;
}

//使用指针的方式
char* mycpy2(char* dst, const char* src)
{
    
    
	char* ret = dst;
	while (*dst++ = *src++) 
	  ;//while循环就是个空循环
	*dst = '\0';
	return ret;
}

int main(int argc, char const *argv[])
{
    
    
	char s1[] = "abc";
	char s2[] = "def";
	char s3[] = "ghi";
	char s4[] = "jkl";
	mycpy1(s1,s2);
	int i = 0;
	while (s1[i] != '\0') {
    
    
		printf("%c ",s1[i]);
		i++;
	}
	printf("\n");

	mycpy2(s3,s4);
	i=0;
	while (s3[i] != '\0') {
    
    
		printf("%c ", s3[i]);
		i++;
	}
	printf("\n");
	return 0;
}

我们进行测试,可以看到s1和s3成功被覆盖了。
在这里插入图片描述

2.5 字符串函数strcat

strcat函数主要做字符串拼接,定义如下:

• char * strcat(char *restrict s1, const char *restrict s2);
• 把s2拷贝到s1的后面,接成一个长的字符串
• 返回s1
• s1必须具有足够的空间

其实就是从dst[strlen(dst)]开始将src的内容拷贝过去,如下图所示。
在这里插入图片描述


安全问题

• strcpy和strcat都可能出现安全问题
• 如果目的地没有足够的空间?
• 建议:尽可能不用它们,使用安全版本

安全版本
安全版本的参数表中多了个n,且函数名称也多了个n。这个n就代表最多可以拷贝过去多少个字符,如果多了就掐掉。对于strcmp的安全版本,我们只是用来判断前n个是否相等。

char * strncpy(char *restrict dst, const char *restrict src, size_t n);
char * strncat(char *restrict s1, const char *restrict s2, size_t n);
int strncmp(const char *s1, const char *s2, size_t n);

2.6 字符串搜索函数

字符串中找字符

• char * strchr(const char *s, int c);
  • 从左往右搜索,返回c第一次出现的位置
• char * strrchr(const char *s, int c);
  • 从右往左搜索,返回c第一次出现的位置
• 返回NULL表示没有找到

我们写段代码来测试下这些函数功能。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    
    
	char s[] = "hello";
	char *p = strchr(s, 'l');
	printf("%s\n", p);
	return 0;
}

运行,可以发现成功将l从左往右第一次出现的地方及后面的字符以字符串形式打印出来了。
在这里插入图片描述


现在我有个问题,如何寻找第2次出现的地方呢?我们只需从第一次找到’l’的地方后面一位开始继续寻找’l’出现的地方。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    
    
	char s[] = "hello";
	char *p = strchr(s, 'l');
    p = strchr(p+1, 'l');
	printf("%s\n", p);
	return 0;
}

运行,结果正确。
在这里插入图片描述


现在我想把’l’后面的东西复制到另外一个地方

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    
    
	char s[] = "hello";
	char *p = strchr(s, 'l');
	char *t = (char*)malloc(strlen(p)+1);
	strcpy(t,p);
	printf("%s\n", t);
	free(t);
	return 0;
}

运行,结果正确。
在这里插入图片描述


如果我想把’l’左边的东西放到另外个地方,怎么办呢?

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    
    
	char s[] = "hello";
	char *p = strchr(s, 'l');
	char c = *p;
	*p = '\0';
	char *t = (char*)malloc(strlen(s)+1);
	strcpy(t,s);
	printf("%s\n", t);
	free(t);
	return 0;
}

运行,结果正确。
在这里插入图片描述


字符串中找字符串

char * strstr(const char *s1, const char *s2);
char * strcasestr(const char *s1, const char *s2); //寻找过程中忽略大小写

猜你喜欢

转载自blog.csdn.net/InnerPeaceHQ/article/details/121614276
今日推荐