C语言程序设计笔记(浙大翁恺版) 第十周:字符串

按照中国大学MOOC上浙江大学翁恺老师主讲的版本所作,B站上也有资源。原课程链接如下:

https://www.icourse163.org/course/ZJU-9001

由于是大三抽空回头整理的,所以可能前五章会记的内容比较简略。此外,作为选学内容的A0:ACLLib的基本图形函数和链表两章也没有做。西电的考试是机试,理论上学到结构体就能够应付考试了,但为了以后的学习考虑建议全学。

 

其他各章节的链接如下:

C语言程序设计笔记(浙大翁恺版) 第一周:程序设计与C语言

C语言程序设计笔记(浙大翁恺版) 第二周:计算

C语言程序设计笔记(浙大翁恺版) 第三周:判断

C语言程序设计笔记(浙大翁恺版) 第四周:循环

C语言程序设计笔记(浙大翁恺版) 第五周:循环控制

C语言程序设计笔记(浙大翁恺版) 第六周:数据类型

C语言程序设计笔记(浙大翁恺版) 第七章:函数

C语言程序设计笔记(浙大翁恺版) 第八周:数组

C语言程序设计笔记(浙大翁恺版) 第九周:指针

C语言程序设计笔记(浙大翁恺版) 第十周:字符串

C语言程序设计笔记(浙大翁恺版) 第十一周:结构类型

C语言程序设计笔记(浙大翁恺版) 第十二周:程序结构

C语言程序设计笔记(浙大翁恺版) 第十三周:文件

其他各科笔记汇总

 

字符串

字符串

字符串

字符数组

char word[] = {'H','e','l','l','o','!'};这种不是C语言的字符串,因为不能用字符串的方式做计算

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qse79qox-1659886795835)(C语言程序设计.assets/image-20220730091250228.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

本地变量iss2在内存中相同的区域。ss2指向位于同一个地址很小的地方的相同字符串

编译器会找地方存"Hello World"字符串,但是因为这个字符串在编译时已经有值,编译器会将它放在只能读不能写的只读代码段。并且如果如果程序里有多处相同,那些指针会指向同一个地方

如果试图写s[0] = 'B',操作系统有保护机制让程序崩溃。编译没问题,但运行时会出现"Bus error"错误(在Windows上可能是"segmentation fault")

在这里插入图片描述
 

 

在这里插入图片描述

char* s = "Hello, World!";s是一个指针,初始化为指向一个字符串。由于"Hello, World!"这个字符串常量所在的地方,所以实际上sconst char* s,但是由于历史的原因,编译器接受const的写法,但是试图对s所指的字符串做写入会导致严重的后果

 

如果需要修改字符串,应该用数组:char s[] = "Hello, world!";

既然在这有一个字符串常量,实际上程序里还是会存一份"Hello, world!",编译器会在这插入一段代码将放在只读代码段的"Hello, world!"字符数组内容拷贝到ss数组是本地变量可以修改

 

 

指针还是数组?

如果要构造一个字符串 —> 数组

如果要处理一个字符串 —> 指针

 

数组:这个字符串在这里,作为本地变量空间自动会被回收

指针:这个字符串不知道在哪里。通常用它来处理参数、动态分配空间

 

 

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)]

%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)]

"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)]

 

字符串函数

单字符输入输出

putchargetchar

 

 

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)]

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有一个很大的缓冲区,输入回车后会往内填充输入的内容。getcharscanf都从该缓冲区读内容,而用户输入是让shell去填充缓冲区。无论是getchar还是scanf遇到缓冲区结尾标志(下图用"\0"表示,实际内部可能用的其他形式)就会继续等用户输入内容

如果输入CTRL-D,shell会向缓冲区填充特殊标志(下图用"-1"表示)或者采用别的方式让程序再读时就会读到EOF,不同的shell和操作系统甚至可能不同的编译器有不同的具体实现方式。而如果输入CTRL-C,shell会直接关闭程序
在这里插入图片描述

 

 

 

 

所有C语言的发行版本都理应带有以下C标准库函数:

  • strlen
  • strcmp
  • strcpy
  • strcat
  • strchr
  • strstr

这些函数的原型在头文件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来表达s1s2是否相等,这样是比较两个数组变量地址是否相同

 

int strcmp(const char *s1, const char *s2);

比较两个字符串,返回:

  • 0s1==s2
  • 1s1>s2
  • -1s1<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的字符串拷贝到dstrestrict表明srcdst不重叠(C99)。这是为了适应多核计算机,多核计算机可能会把拷贝工作分成好几段,让每一个核做其中一段,此时如果dstsrc重叠,这些分开的一段段拷贝互相之间就会冲突

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RTb4C2z-1659886795862)(C语言程序设计.assets/image-20220731004406647.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必须具有足够的空间

 

 

安全问题

strcpystrcat都可能出现安全问题,因为目的地可能没有足够的空间

 

安全版本

用参数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); 寻找过程中忽略大小写
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值