当前位置:网站首页>C语言程序设计笔记(浙大翁恺版) 第十周:字符串
C语言程序设计笔记(浙大翁恺版) 第十周:字符串
2022-08-09 14:23:00 【CS_Lee_】
按照中国大学MOOC上浙江大学翁恺老师主讲的版本所作,B站上也有资源。原课程链接如下:
https://www.icourse163.org/course/ZJU-9001
由于是大三抽空回头整理的,所以可能前五章会记的内容比较简略。此外,作为选学内容的A0:ACLLib的基本图形函数和链表两章也没有做。西电的考试是机试,理论上学到结构体就能够应付考试了,但为了以后的学习考虑建议全学。
其他各章节的链接如下:
字符串
字符串
字符串
字符数组
char word[] = {'H','e','l','l','o','!'};这种不是C语言的字符串,因为不能用字符串的方式做计算
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qse79qox-1659886795835)(C语言程序设计.assets/image-20220730091250228.png)]](/img/f7/dd9b862cde7a8ec86a662142717929.png)
字符串
以0(整数0)结尾的一串字符。写成0或'\0'是一样的,但是和'0'不同
0标志字符串的结束,但它不是字符串的一部分。计算字符串长度的时候不包含这个0
字符串以数组的形式存在,以数组或指针的形式访问。更多的是以指针的形式
string.h里有很多处理字符串的函数
示例:
char word[] = {'H','e','l','l','o','!','\0'};
字符串变量
char *str = "Hello";char word[] = "Hello";char line[10] = "Hello";
字符串常量
"Hello"会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0
两个相邻的字符串常量会被自动连接起来成为一个大的字符串
示例:
printf("请分别输入身高的英尺和英寸,"
"如输入\"5 7\"表示5英尺7英寸:");
字符串
C语言的字符串是以字符数组的形态存在的,不能用运算符对字符串做运算,通过数组的方式可以遍历字符串
唯一特殊的地方是字符串字面量可以用来初始化字符数组,以及标准库提供了一系列字符串函数
字符串变量
示例:
#include <stdio.h>
int main(void)
{
int i = 0;
char *s = "Hello World";
// s[0] = 'B';
char *s2 = "Hello World";
char s3[] = "Hello World";
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;
}
&i=0xbff1fd6c
s =0xe1f82
s2=0xe1f82
s3=0xbff03d50
Here!s[0]=B
本地变量i,s,s2在内存中相同的区域。s和s2指向位于同一个地址很小的地方的相同字符串
编译器会找地方存"Hello World"字符串,但是因为这个字符串在编译时已经有值,编译器会将它放在只能读不能写的只读代码段。并且如果如果程序里有多处相同,那些指针会指向同一个地方
如果试图写s[0] = 'B',操作系统有保护机制让程序崩溃。编译没问题,但运行时会出现"Bus error"错误(在Windows上可能是"segmentation fault")


char* s = "Hello, World!";。s是一个指针,初始化为指向一个字符串。由于"Hello, World!"这个字符串常量所在的地方,所以实际上s是const char* s,但是由于历史的原因,编译器接受const的写法,但是试图对s所指的字符串做写入会导致严重的后果
如果需要修改字符串,应该用数组:char s[] = "Hello, world!";
既然在这有一个字符串常量,实际上程序里还是会存一份"Hello, world!",编译器会在这插入一段代码将放在只读代码段的"Hello, world!"字符数组内容拷贝到s,s数组是本地变量可以修改
指针还是数组?
如果要构造一个字符串 —> 数组
如果要处理一个字符串 —> 指针
数组:这个字符串在这里,作为本地变量空间自动会被回收
指针:这个字符串不知道在哪里。通常用它来处理参数、动态分配空间
char*是字符串?
字符串可以表达为char*的形式,char*不一定是字符串
char*本意是指向字符的指针,可能指向的是字符的数组(就像int*一样),只有它所指的字符数组有结尾的0,才能说它所指的是字符串
字符串输入输出
字符串赋值
char *t = 'title';
char *s;
s = t;
并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的任何操作就是对t做的
字符串输入输出
char string[8];
scanf("%s", string);
printf("%s", string);
scanf读入一个单词(到空格、tab或回车为止)
scanf是不安全的,因为不只读要读入的内容的内容的长度
示例:
#include <stdio.h>
int main(void)
{
char word[8];
scanf("%s", word);
printf("%s##\n", word);
return 0;
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ti5YB0TX-1659886795841)(C语言程序设计.assets/image-20220730144009440.png)]](/img/93/4c953987e0eaadc3ff68905932223e.png)
%s读到了第一个单词,并且没有包含空格

这涉及到这些变量在内存里面是如何排列和存放的,原因见课程讨论区
示例2:
#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;
}

两个%s读到的都不带空格和回车,空格和回车作为分隔符区分两个单词
示例3:
#include <stdio.h>
void f(void)
{
char word[8];
char word2[8];
scanf("%s", word);
scanf("%s", word2);
printf("%s##%s##\n", word, word2);
}
int main(void)
{
f();
return 0;
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oSyZQUVx-1659886795848)(C语言程序设计.assets/image-20220730150409578.png)]](/img/f3/6a82ed64d924ce2f1b49c19e60f076.png)
"Abort trap"说明数组已经越界,造成严重后果导致程序崩溃
安全的输入
char string[8];
scanf("%7s", string);
在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一
示例:
下一次scanf是从哪里开始?
#include <stdio.h>
void f(void)
{
char word[8];
char word2[8];
scanf("%7s", word);
scanf("%7s", word2);
printf("%s##%s##\n", word, word2);
}
int main(void)
{
f();
return 0;
}

常见错误
char *string
scanf("%s", string);
以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了。由于没有对无默认初始值的本地变量string初始化为0,所以不一定每次运行都出错
空字符串
char buffer[100] = ""; 是一个空的字符串,buffer[0] == '\0'
char buffer[] = ""; 这个数组的长度只有1
字符串数组
字符串数组
char **a不是字符串数组。a是一个指针,指向另一个指针,那个指针指向一个字符(串)
char a[][]作为二维数组变量,第二维必须要有确切的大小,否则编译不能通过
示例:
#include <stdio.h>
int main(void)
{
// a[0] --> char [10]
char a[][10] = {
"Hello",
"World"
};
return 0;
}
如果出现超长的字符串,就会出问题

还有另一种写法是将char a[][10]改为char *a[],则不存在超长问题。此时a[0]是一个指针,指向某个字符串

程序参数
int main(int argc, char const *argv[])
整数argc告知后面的字符串数组argv到底有几个字符串
示例:
#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;
}

main函数可以读到在命令行执行程序时在程序名字后输入的内容
而第一个argv[0]是执行时输入的命令。当使用Unix的符号链接时,反映符号链接的名字
我们需要在程序中知道究竟是通过什么方式运行程序的,是直接运行还是通过某个链接等等。在Unix下将a.out叫做my,查看说my是一个符号链接,链接指向a.out,如果执行my就会告知执行的是my而不是a.out。这里想要深入了解建议搜一下busybox
在Windows下虽然会较少在命令行输入程序名字运行程序,但是也可以在快捷方式里指定给这个可执行程序的参数
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mK1MNjt-1659886795854)(C语言程序设计.assets/image-20220730171545438.png)]](/img/8a/a097228a76d8496326abbbfdacb674.png)
字符串函数
单字符输入输出
用putchar和getchar
putchar
int putchar(int c);
向标准输出写一个字符,返回写了几个字符,宏EOF(-1)表示写失败
输入不是char类型而是int类型,但是实际上它的int只能够接受一个字符
getchar
int getchar(void);
从标准输入读入一个字符,返回该字符。返回类型是int是为了返回EOF(-1)表示标准输入结束
示例:
#include <stdio.h>
int main(int argc, char const *argv[])
{
int ch;
while ( (ch = getchar()) !=EOF ) {
putchar(ch);
}
printf("EOF\n");
return 0;
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B21YTz5J-1659886795856)(C语言程序设计.assets/image-20220730202312758.png)]](/img/6c/32aa3203d0c68afabaf67bf9f74f2c.png)
getchar要等到输入回车才回应
输入CTRL-C程序结束,但是"EOF\n"并没有被打印出来,说明这只是强制让程序结束,并没有正确地输入EOF告知其输入结束
输入CTRL-D才能让程序读到EOF状态。Windows和Unix对于EOF的定义不同,但只是具体的键不一样,剩下的事情是一样的,如果在Windows下需要输入的是CTRL-Z
你的程序和用户的键盘、显示器之间还有另外一个叫shell的程序
shell负责运行你的程序,键盘输入的内容先交给shell,shell处理后再交给程序。你的程序要printf显示的内容交给shell,shell处理后再交给显示器
shell的一项基本工作就是为键盘上的所有输入内容做行编辑,你在键盘上输入的内容在输入回车之前都停留在shell,没有被交给你的程序
shell有一个很大的缓冲区,输入回车后会往内填充输入的内容。getchar和scanf都从该缓冲区读内容,而用户输入是让shell去填充缓冲区。无论是getchar还是scanf遇到缓冲区结尾标志(下图用"\0"表示,实际内部可能用的其他形式)就会继续等用户输入内容
如果输入CTRL-D,shell会向缓冲区填充特殊标志(下图用"-1"表示)或者采用别的方式让程序再读时就会读到EOF,不同的shell和操作系统甚至可能不同的编译器有不同的具体实现方式。而如果输入CTRL-C,shell会直接关闭程序
所有C语言的发行版本都理应带有以下C标准库函数:
strlenstrcmpstrcpystrcatstrchrstrstr
这些函数的原型在头文件string.h
字符串函数strlen
strlen
size_t strlen(const char *s);
返回s的字符串长度(不包括结尾的0)
示例:
#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", strlen(line));
printf("strlen=%lu\n", mylen(line));
printf("sizeof=%lu\n", sizeof(line));
return 0;
}
strlen=5
sizeof=6
字符串函数strcmp
不能用s1==s2来表达s1和s2是否相等,这样是比较两个数组变量地址是否相同
int strcmp(const char *s1, const char *s2);
比较两个字符串,返回:
0:s1==s21:s1>s2-1:s1<s2
示例:
#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", 'a'-'A');
return 0;
}
32
32
如果将"Abc"改为"abc ",由于'\0'-' '=-32,得strcmp(s1,s2)的结果为-32

示例2:
int mycmp(const char* s1, const char* s2)
{
int idx = 0;
while ( s1[idx] == s2[idx] && s1[idx] != '\0' ) {
// if ( s1[idx] != s2[idx] ) {
// break;
// } else if ( s1[idx] == '\0' ) {
// break;
// }
idx ++;
}
return s1[idx] = s2[idx];
}
int mycmp(const char* s1, const char* s2)
{
while ( *s1 == *s2 && *s1 != '\0' ) {
s1++;
s2++;
}
return *s1 - *s2;
}
字符串函数strcpy
char * strcpy(char *restrict dst, const char *restrict src);
把src的字符串拷贝到dst,restrict表明src和dst不重叠(C99)。这是为了适应多核计算机,多核计算机可能会把拷贝工作分成好几段,让每一个核做其中一段,此时如果dst和src重叠,这些分开的一段段拷贝互相之间就会冲突
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RTb4C2z-1659886795862)(C语言程序设计.assets/image-20220731004406647.png)]](/img/51/798ea2e30071cc81c62a66472cda88.png)
返回dst,为了能链起代码来
复制一个字符串
char *dst = (char*)malloc(strlen(src)+1);
strcpy(dst, src);
示例:
char* strcpy(char* dst, const char* src)
{
int idx = 0;
while ( src[idx] ) {
dst[idx] = src[idx];
idx++;
}
dst[idx] = '\0';
return dst;
}
char* strcpy(char* dst, const char* src)
{
char* ret = dst;
while ( *dst++ = *src++ );
*dst = '\0';
return rest;
}
*dst++ = *src++赋值运算表达式结果就是*src
字符串函数strcat
char * strcat(char *restrict s1, const char *restrict s2);
把s2拷贝到s1的后面,接成一个长的字符串,返回s1
s1必须具有足够的空间
安全问题
strcpy和strcat都可能出现安全问题,因为目的地可能没有足够的空间
安全版本
用参数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);
字符串搜索函数
字符串中找字符
从左开始或从右开始在s中找c第一次出现的位置。返回NULL表示没有找到,否则返回指向要找字符的指针
char * strchr(const char *s, int c);char * strrchr(const char *s, int c);
示例:
#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;
}
lo
示例2:
将找到的字符及其后面的部分复制到另一字符串
#include <stdio.h>
#include <string.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;
}
llo
示例3:
将找到的字符前面的部分复制到另一字符串
#include <stdio.h>
#include <string.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);
*p = c;
printf("%s\n", t);
free(t);
return 0;
}
he
字符串中找字符串
char * strstr(const char *s1, const char *s2);char * strcasestr(const char *s1, const char *s2);寻找过程中忽略大小写
边栏推荐
猜你喜欢
随机推荐
Simulink simulation pid control servo system
SMI 与 Gateway API 的 GAMMA 倡议意味着什么?
The rising star DPU is revolutionizing the data center!
*3-2 CCF 2014-09-2 drawing
apt-get install 和pip install 的区别
*3-1 CCF 2014-09-1 Adjacent pairs
冰冰学习笔记:new与delete
After reading the "Redis In-depth Notes" compiled by Tencent bosses in 90 days, I worshipped on the spot.
Assembly language learning (2)
apt-cache 命令
Redis 面试题
【DevOps】jekinsBuild step ‘Execute shell‘ marked build as failure
Unity Obi插件修改到支持URP
* 5-2 CCF 2014-12-3 call auction
IK学习笔记(2)——TwoBones IK
apt-cache command
Assembly language learning (5)
[manjaro]更新后内核文件加载失败
*4-2 CCF 2014-12-2 zigzag scan
探索进军元宇宙的代表性企业









