当前位置: 首页 > news >正文

高密做网站哪家好黑帽seo培训

高密做网站哪家好,黑帽seo培训,wordpress 图片压缩,网站制作基本步骤字符串是C语言最重要最有用的数据类型之一。 说到字符串,一定和指针分不开关系。毕竟连字符串本身都是指针。字符串数组也可以用指针数组来创建。实际上,字符串的大多数操作都是指针完成的。 本文主要会讲很多C库定义的字符串函数 C字符串是以空字符结…

字符串是C语言最重要最有用的数据类型之一。

说到字符串,一定和指针分不开关系。毕竟连字符串本身都是指针。字符串数组也可以用指针数组来创建。实际上,字符串的大多数操作都是指针完成的。

本文主要会讲很多C库定义的字符串函数

C字符串是以空字符结尾的字符数组。

puts()

puts()函数也是stdio.h中的输入输出函数,但它只能显示字符串,并且自动在字符串末尾加换行符

下面的简单示例展示了三种定义字符串的方法

#include <stdio.h>
#define MSG "I am a symbolic string constant."//用字符串常量定义字符串
#define MAXLENGTH 81
int main()
{char words[MAXLENGTH] = "I am a string in an array.";//用char类型数组定义字符串,未被使用的数组元素会被初始化为空字符const char * pt1 = "Something is pointing at me.";//用指向char的指针定义字符串,const表明不会更改这个字符串puts("Here are some strings:");puts(MSG);puts(words);puts(pt1);words[8] = 'p';puts(words);return 0;
}

注意用char数组定义字符串时,不需要用标准的数组初始化方式:
而且这样初始化务必注意自己加个空字符,不然这就不是字符串,而是字符数组啦

用上面程序的双引号初始化方式,编译器会自动加空字符,但是两种方式都需要自己注意数组的大小至少比字符数多1,以容纳空字符,所以最好还是像以前的数组那样,方括号空着,让编译器自己确定数组大小。即char a[] = “apple pen”; 这样很合理,也很安全。

但是只有在初始化数组的时候才可以让编译器自动确定大小,如果只是先声明一个数组,后面才去填充内容,那就必须写数组的大小了。

char words[20] = {'I', 'a', 'm', 'a', 'b', 'e', 'e', '\0'};
Here are some strings:
I am a symbolic string constant.
I am a string in an array.
Something is pointing at me.
I am a spring in an array.

字符串字面量的串联(使用双引号)

两句等效

char words[MAXLENGTH] = "I ""am a string"" in an array.";
char words[MAXLENGTH] = "I am a string in an array.";

所以想在字符串内部使用双引号,则需要用转义符号,"

字符串常量属于静态存储类别

static storage class
即函数中的字符串常量只会被存储一次,他在程序的整个生命期内都存在,不管函数被调用几次。

字符串是指针!!!

字符串被视为指向该字符串存储位置的指针,类似于数组名是指向这个数组存储位置的指针

表示超级震惊,还有一点兴奋

#include <stdio.h>
int main()
{printf("%s, %p, %c\n", "We", "are", *"space farers");//我们是空间旅行者return 0;
}

我猜对了输出,由于“are”字符串是个指针,即他自己的首元素a的地址,所以%p输出字符a的内存位置;“space farers”是指针,指向首字符s的内存地址,所以对其解引用,得到了字符s,用%c输出。(我好奇用%s能不能输出整个字符串,尝试了,无法,只可以用%c输出首字符)

We, 0040b044, s

除了C99增加的变长数组以外,数组的大小必须是整型常量(包括整型常量的表达式)。
在这里插入图片描述

用指针表示法创建指针

这两句声明几乎一样的(不一样的地方后面说),pt1和ar都是字符串 的地址,且两种方式均由字符串决定数组的大小

const char * pt1 = "Something is pointing at me."; //指针法
const char ar[] = "Something is pointing at me.";//数组法

初始化字符数组以存储字符串 VS 初始化指针指向字符串的首字符

这两种创建字符串的方式的区别其实没那么重要,但是要看你用来干嘛了,如果你不想这个字符串字面量被修改,就用数组法;反之,用指针法。

下面较真地仔细分析一下咯

数组形式创建字符串实际会做什么

程序中用数组形式声明的字符串在编译(自动加上结尾空字符)和链接后,被存储在可执行文件(exe, 机器代码)中的数据段中。

当程序被载入到内存中时,字符串常量也就进入了内存,被存在静态存储区static memory中。

当程序开始运行时,才会为这个字符串数组分配内存,然后才把静态存储区中的字符串常量拷贝到数组中。所以此时这个字符串常量有2个副本,一个在静态内存中,一个在数组中(数组是在动态内存中)。

编译时就分配内存的是存在静态内存中,比如常量们;运行时才分配内存的存在动态内存中,比如数组

这时候,编译器才会把数组名ar识别为数组首元素ar[0]的地址&ar[0]。(问题:程序运行时编译器还会干活????看来是这样了)

重点:前面强调过,数组形式中的数组名ar是个**地址常量**!!!不可以对他递增递减,但可以有ar+1这样的运算。(因为==递增递减只能作用于可修改的左值,即变量==,不可用于常量)

指针形式创建字符串又会做什么

程序中用指针形式声明的字符串在编译(自动加上结尾空字符)和链接后,也被存储在可执行文件(exe, 机器代码)中的数据段中。

当程序被载入到内存中时,字符串常量进入内存,也被存在静态存储区static memory中。

至此,和数组形式没有区别。

当程序开始运行时,编译器会为指针变量pt分配一个内存地址,把pt存在那里,pt最初指向字符串首字符,但是他是可以递增递减的,于是可以指向字符串的任意字符。

需要注意的是:字符串字面量在C中被默认视为const数据,是受保护的。所以==指向字符串的指针一定要声明为const指针!!(墙裂推荐)==这样可以保证不能通过这个指针去修改字符串,但是指针本身的值可以修改。在保护字符串字面量这一点上,数组形式就相形见绌了,因为字符串字面量拷贝给数组后可以通过修改数组去修改拷贝来的字符串,但是你把数组也声明为const数组的话,就可以避免通过修改数组去修改数组中的字符串啦。但是不管怎样,数组表示法由于操作的是副本,所以绝对不会修改原来的原件,这一点很安全。

总结:

  • 数组初始化法会把静态内存的字符串字面量拷贝给数组;而指针初始化只是把静态内存中字符串字面量的地址拷贝给指针而已
    数组获得的只是副本,指针拿到的是原件,然而这不是说指针厉害,而是说明指针危险!如果不想修改字符串的值,就不要用指针指向它啦
  • 数组名是常量,指针名是变量,只有指针名可以递增。但是数组元素是变量,是可以修改的左值哦

示例

#include <stdio.h>
#define MSG "I'm special"
int main()
{char ar[] = MSG;const char *pt = MSG;printf("address of \"I'm special\": %p\n", "I'm special");printf("address ar: %p\n", ar);printf("address pt: %p\n", pt);printf("address of MSG: %p\n", MSG);printf("address of \"I'm special\": %p\n", "I'm special");return 0;
}

这个结果还挺惊讶的,我以为直接写"I’m special"会创建一个新的字符串字面量呢。原来同一个程序中,同一个字符串字面量,不管你直接写它,还是写它的符号名,都只有一个它。(有点说了等于没说的感觉,毕竟MSG本身就只是复制替换)
书上的说明是:不同编译器的处理不同,有的编译器对于多次出现同一个字符串字面量会存在同一个位置;有的编译器存在多个位置。

只有ar的地址不一样,说明指针法确实是被赋给了字符串的地址,而数组则自己在一个新地址,它里面的内容只是一个拷贝副本。

第一句输出代码很棒,字符串就是指针,高级

address of "I'm special": 0040b044
address ar: 0061ff20
address pt: 0040b044
address of MSG: 0040b044
address of "I'm special": 0040b044

创建字符串数组的两种方法

1. 指向字符串的 指针数组

一般情况创建字符串数组,请使用这种方式,指针数组。它的效率比char数组的数组高,(主要是说内存使用率高)。

但指针数组的缺点:它指向的这些字符串字面量的内容是不可以修改的。(所以一定用const指针哦)
所以如果你想修改字符串的内容,或者需要为字符串输入预留空间的话,就只能用第二种方式了。

2. char数组(字符串) 的 数组

示例

#include <stdio.h>
#define SLEN 40
#define LIM 5
int main()
{//[]优先级高于*,所以mytalents先和[]结合,所以一个const指针数组const char *mytalents[LIM] = {"Adding numbers swiftly","Multiplying accurately","Stashing data","Following instructions to the letter","Understanding the C language"};//相当于二维数组,子数组是字符串(字符串是char数组)char yourtalents[LIM][SLEN] = {"Walking in a straight line","Sleeping", "Watching television","Mailing letters", "Reading email"};int i;puts("Let's compare talents.");printf("%-36s  %-25s\n", "My Talents", "Your Talents");for(i=0;i<LIM;i++)printf("%-36s  %-25s\n", mytalents[i], yourtalents[i]);printf("\nsize of mytalents:%zd, sizeof yourtalents: %zd\n", sizeof(mytalents), sizeof(yourtalents));return 0;
}

可以看到,mytalents只占了20字节,一共5个字符串,所以一个字符串占了4字节,刚好就是一个地址(我的32位软件),所以mytalents实际上是一个指针数组,存了5个指针,分别指向每一个字符串。

yourtalents占200字节,每个字符串40字节,即每个字符串(字符数组)的长度是40,可以存储40个字符(包括空字符)。

二者相同和不同

相同之处:

  • mytalents[1][2]指mytalents的第2个指针指向的字符串的第3个字符;yourtalents[1][2]指yourtalents的第2个字符串中的第3个字符。是一样的。
  • 二者的初始化方式也一样。

不同之处:

  • 声明不同。看代码
  • mytalents中的指针指向的5个字符串存储在静态内存中,而yourtalents的字符串数组中的只是5个原始字符串的副本,存在动态内存中,
  • 数组法的内存使用率更低,因为yourtalents的每个子数组的大小必须一样且必须大于最长的字符串。而指针法则有点像是不贵规则长度的数组(只是比喻长度的区别哈,二者存储位置都不一样)
  • 指针法的每个字符串并不需要存储在连续的内存中,数组就必须。
Let's compare talents.
My Talents                            Your Talents
Adding numbers swiftly                Walking in a straight line
Multiplying accurately                Sleeping
Stashing data                         Watching television
Following instructions to the letter  Mailing letters
Understanding the C language          Reading emailsize of mytalents:20, sizeof yourtalents: 200

拷贝指针(效率更高) VS 拷贝字符串

#include <stdio.h>
int main()
{//mesg, copy都是指向char的指针const char *mesg = "Don't be a fool!";const char *copy;copy = mesg;//这样只是创建了一个新的指针,也让他指向字符串,原来的指着还在,字符串也在printf("%s\n", copy);//&mesg是mesg指针被存储的位置,mesg是字符串被存储的位置(指针存储的地址)printf("mesg = %s, &mesg = %p, value = %p\n", mesg, &mesg, mesg);printf("copy = %s, &copy = %p, value = %p\n", copy, &copy, copy);return 0;
}

可以看出,只有&copy 和 &mesg不一样,所以字符串并没有被拷贝,只是让新指针也指向字符串,明显这样做比拷贝整个字符串的效率高很多,字符串越长对比优势越明显,所以指针是提供了一种捷径,一种更简单偷懒的方法

Don't be a fool!
mesg = Don't be a fool!, &mesg = 0061ff2c, value = 0040b044
copy = Don't be a fool!, &copy = 0061ff28, value = 0040b044

从键盘输入字符串

不要觉得这一节很简单,,没啥好说的,我们已经输入过很多次字符串了,但是我们不知内部原理,一丁点都不知道。

这一节主要两点,一是输入字符串之前给它分配内存,二是输入字符串的函数,比gets, fgets, scanf。

必须在程序中给字符串分配足够的内存才可以读入

计算机不会在读取字符串的时候顺便计算其长度再分配空间的,不过以后可以自己试试写一个这样的函数。

之前刚学指针时说过不要在指针未初始化时就解引用它,否则会导致不可预知的结果,因为指针可能指向任何地方, 那个地方存储的值也就不可预知。
但这里说的是,指向char的指针,未初始化时不可以用作字符串输入函数的参数

通过上面的示例我们已经知道,%s实际上是对指向char字符的指针指向的内容的转换说明,它对应的参数是指针,之前我们用字符串作为他对应的参数,但现在我们知道,字符串就是指针。

在这里插入图片描述

在这里插入图片描述

最常用的 gets函数

scanf搭配%s只可以读取一个单词

在这里插入图片描述
scanf搭配%s只可以读取一个单词,因为scanf要跳过空白,换行符,制表符,它主要的工作并不是专门读取字符串的

#include <stdio.h>
int main()
{char a[10];//scanf("%s", &a);//警告%s要和char*类型匹配,即指向char的指针,但是&a的类型是char(*)[10],即指向一个长度为10的char数组的指针scanf("%s", a);//a是char*类型,即指向char的指针printf("%s", a);return 0;
}

虽然有警告,但scanf("%s", &a);和scanf("%s", a);的输出结果一样,都只能读取一个单词

we are friends
we

在这里插入图片描述

重要,三遍
%s要和char类型匹配,即指向char的指针
%s要和char
类型匹配,即指向char的指针
%s要和char*类型匹配,即指向char的指针

此外,如果指定字段宽度,则scanf要么读取字段宽度个字符(就算没遇到空白字符),要么遇到空白字符就停下了

在这里插入图片描述

#include <stdio.h>
int main()
{char name1[11], name2[11];int count;printf("Please enter 2 names.\n");count = scanf("%5s %10s", name1, name2);printf("I read the %d names %s and %s", count, name1, name2);return 0;
}

由于第一个名字字段只有5,所以littl读取到就结束了,读取第二个名字时emary还在缓冲区,于是就被读到了,后面又是空白字符,所以就结束读取,导致错误。

可以看到丢弃多余的字符是很有必要的,否则滞留在缓冲区会影响后续输入。

Please enter 2 names.
littlemary greenapple
I read the 2 names littl and emary

最常用却不安全的gets函数(已被C11标准废除,不要使用它了!!!)

gets函数会带来安全隐患。

为了读取一行输入, 于是gets诞生,用于读取一整行输入(遇到换行符就停止)

gets遇到换行符会丢弃换行符,然后在前面的所有字符末尾加个空字符得到字符串

puts是它的好搭档,它显示字符串,并自动在末尾加上换行符

#include <stdio.h>
#define STLEN 81
int main()
{char words[STLEN];puts("Enter a string, please.");gets(words);//典型用法,参数是指向char的指针printf("Your string twice: \n");printf("%s\n", words);//%s对应指向char的指针哦puts(words);//参数是数组名,或说成是指向char的指针puts("Done.");//参数是字符串字面量return 0;
}

看到的最大好处是不用手动输入换行符了,哈哈哈
另外,可以看到gets不是只读了一个单词,而是换行符输入之前的所有字符
gets和puts的参数可以是字符串字面量,也可以是指向char的指针

Enter a string, please.
I have an apple.
Your string twice:
I have an apple.
I have an apple.
Done.

我们的编译器没有给警告,但是书上说
在这里插入图片描述在这里插入图片描述
由于数组名words会被编译器识别为数组的首元素地址,所以只传入words做参数, gets函数不知道words数组的长度。(为啥不把数组长度作为第二个参数传过去呢????后面说的fgets就这么做了)

如果输入的字符串太长了,超过了数组长度,就会导致缓冲区溢出(buffer overflow), 如果多余的字符占用了未被使用的内存则暂时安全;如果擦写掉了程序中的其他数据,就会导致程序异常终止甚至其他不可预知的情况。

在这里插入图片描述

我也试了,可是我的编译器还是啥警告都没给。

在这里插入图片描述

所以黑客就是捕捉类似于这样的漏洞然后痛下黑手的??
在这里插入图片描述

不要用它,大概企业的编程标准是不允许用它的

两个替代品 fgets gets_s

在这里插入图片描述在这里插入图片描述

fgets fputs示例

注意一下,我发现声明变量后面会留一行空行,提升可读性,我觉得这是一个很好的习惯,我要坚持。
#include <stdio.h>
#define STLEN 14
int main()
{char words[STLEN];puts("Enter a string please.");fgets(words, STLEN, stdin);puts(words);fputs(words, stdout);//不会自动加换行符puts("Enter another string, please.");fgets(words, STLEN, stdin);printf("Your string twice:(puts(), then fputs())\n");puts(words);fputs(words, stdout);//fputs函数的返回值是指向char的指针,和它的第一个参数相同则程序正确,如果读到文件结尾,它将返回一个空指针puts("Done!");return 0;
}

由于输入的字符串太长,所以fgets只得到了前面一部分字符串,所以puts和fputs都只输出了被存储的字符串,但是puts还会另外输出一个换行符。

剩下的没被存入words数组的字符就被第二句fgets吸收了,根本没给我输入的机会,这就是因为C采用的是缓冲输入, 输入的字符在缓冲区里。

Enter a string please.
I like to do exercise.
I like to do
I like to do Enter another string, please.
Your string twice:(puts(), then fputs())
exercise.exercise.
Done!

在这里插入图片描述

示例 空指针

空指针, null pointer, 是一个特殊的指针, 即地址,它不会指向任何有效的数据,但不是说它没值哈,空指针里面也是存储了一个地址的,只是这个地址没有存程序里的任何有效数据。

#include <stdio.h>
#define STLEN 10
int main()
{char words[STLEN];puts("Enter strings (empty line to quit):");//因为输入空行就终止,空行的第一个字符就是换行符,所以判断条件中要用words[0]!='\n'//如果前面用的s_gets()函数则要用words[0]!='\0'因为s_gets会把换行符替换为空字符while(fgets(words, STLEN, stdin)!= NULL && words[0]!='\n')fputs(words, stdout);puts("Done!");return 0;
}

这个程序非常有意思,我还以为我哪里弄错了,怎么输入很长的字符串也能正确输出,后来才发现其中奥秘

当输入I have a pen. I have an apple.时,fgets首先得到I have a \0, while条件满足,打印出来(没有换行);然后fgets得到输入缓冲字符流里的pen. I ha\0, while条件还是满足,打印出来(没换行);然后fgets再继续获取输入字符流里的字符ve a pen.\0·····直到最后一次,读入了\n\0,所以最后一次打印出来会换行。

对于输入字符串bbbbbbbbbbbb^Z,fgets先输出了9个b, 然后程序进入等待输入的状态,按下换行符,fgets获取剩下的bbb^Z时,我不知道发生了什么·········至今不懂

总之调试发现,键入的CTRL+Z以及他前面的字符不会被fgets直接读过去,而需要再输入一个换行符才读过去,而且读取成了空格字符,space,\032,所以输出的也是空格字符,现象是这样,至于原因,暂时不得而知

后面添加:
\032不是空格,前缀0表示八进制,换算为十进制则是26,ASCII编码26就是CTRL+Z字符,他是个转义字符,所以有反斜杠!!!!转义字符不是打印字符,所以打印出来乱码
在这里插入图片描述

Enter strings (empty line to quit):
I have a pen. I have an apple.
I have a pen. I have an apple.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbb^Z
bbbbbbbbb
bbbDone!

文件结束标志是CTRL+Z+ENTER!

#include <stdio.h>
#define STLEN 10
int main()
{char words[STLEN];char *a;a = fgets(words, STLEN, stdin);printf("%p\n", a);return 0;
}

注意文件结束标志不是CTRL+Z。而是CTRL+Z+换行符!所以下面输出中空了一行,才输出了返回的指针,但并不是空指针,为啥呢???

我调试发现,输入CTRL+Z+换行符后,程序还是等待输入的状态,需要再输入一个换行符,才进入while循环,并且被存储的字符是\032,查ascii码,是空格!!!space。。。我输入CTRL+Z+换行符竟被编译器识别为空格,所以返回的指针不是空指针,因为编译器不知道这是文件结束。

aaa^Z0061ff22

必须保证CTRL+Z+ENTER之前没有字符,即必须在行首输入才会被识别为EOF,(20200302在写C++程序偶然发现的,,····)

直接输入CTRL+Z+ENTER,返回空字符,输出八个0

^Z
00000000

fgets示例 去掉fgets函数读取的换行符(读取整行并把换行符替换为空字符)

用空字符代替
在这里插入图片描述

#include <stdio.h>
#define STLEN 10
int main()
{char words[STLEN];int i = 0;puts("Enter a string:");fgets(words, STLEN, stdin);puts(words);//去掉换行符,由于C采用行缓冲,遇到换行符就结束输入,并把缓冲字符流发给程序,所以一次输入的缓冲区最多只有一个换行符while(words[i] != '\n')i++;words[i] = '\0';puts(words);return 0;
}

可以看到第一次打印时打印了换行符,第二次没有

Enter a string:
aaaa
aaaaaaaa

在这里插入图片描述在这里插入图片描述

如果目标数组装不下一行,可丢弃剩余字符(包括换行符)

之前用过的
在这里插入图片描述

fgets示例 只打印数组长度的字符(即只读取一部分输入,丢弃剩余部分)

无法用键盘输入空字符!!
这个程序很巧妙。我认真调试了四五次才搞清楚到底怎么回事,很棒!!

程序逻辑:获取的输入,挨个检查字符,如果先遇到换行符,就把他替换为空字符,如果先遇到空字符,就把空字符后面的输入字符全部丢弃,丢弃就是要读取但不存储或使用,一般用getchar逐个读取。所以程序遇到比数组长度小的字符输入,就会先遇到换行符,原样输出输入的所有字符;
如果遇到比数组长度长的字符流,就会先遇到空字符(比如数组长度为4,输入asdfg,那么数组中存储的是asd和编译器添加的空字符,逐个遍历数组元素时到数组末尾就会遇到空字符,于是后面的fg就被getchar读取了,然后换行符被getchar读取,程序再次进入等待输入的状态(缓冲区没有字符啦))。

#include <stdio.h>
#define STLEN 10
int main()
{char words[STLEN];int i;char ch;puts("Enter strings (empty line to quit):");while(fgets(words, STLEN, stdin)!= NULL && words[0]!='\n'){i=0;while(words[i]!='\n' && words[i]!= '\0')i++;//如果先遇到换行符,就替换为空字符if(words[i]=='\n')words[i]='\0';else if(words[i]=='\0')//如果先遇到空字符,就丢弃后面的字符//所以这个程序就只会输出数组长度数目的字符!!!由于words[9]是空字符,后面的丢了while((ch=getchar())!='\n')//丢弃后面的字符continue;//这里会把多于数组长度的字符读取了,所以输入缓冲字符流已经没字符啦!!!puts(words);}puts("Done!");return 0;
}

我在这个示例里犯了一个错误,即我测试程序功能时,试图用键盘输入空字符,如下,我写了\0,这是很naive, ridiculous的笑话一般的错误····
调试时我才看到,\0会被读取为两个字符,\和0·····天真可爱的我

想了想,我们是不可能从键盘输入空字符的,这个程序也根本不需要我们这么做,人家这里就是专门要利用编译器自动加在数组末尾的那个空字符!!!

但是我仍然不知道怎么让fgets得到空指针返回值????

Enter strings (empty line to quit):
asdfg
asdfg
gydfhu
gydfhu
asfdfhfdjdfdfd\0
asfdfhfdjDone!

空指针和空字符

空指针是指针,是地址
空字符是字符
没啥好混淆的
在这里插入图片描述

gets_s函数

在这里插入图片描述

在这里插入图片描述

结论:fgets最佳,用它!

在这里插入图片描述

利用fgets写一个s_gets函数去替代gets,兼获fgets的优点

这个函数实际用的是fgets,但是又克服了fgets读取换行符的缺点,还会自动把多余的输入去掉,用它来代替gets和fgets是很好的。

但是s_gets在自动删除多于的字符时,也是不通知程序不告知用户的,这一点不太好,但总比不删除,然后引发程序崩溃等意外好。

char * s_gets(char * st, int n)
{char * ret_val;int i;ret_val = fgets(st, n, stdin);if(ret_val != NULL)//等效于if(ret_val),即fgets没有遇到文件结尾啥的,正常读取了一个字符串{//逐个检查输入字符i=0;while(st[i]!='\n' && st[i]!='\0')i++;if(st[i]=='\n')st[i] = '\0';elsewhile(getchar() != '\n')continue;}return ret_val;
}

字符串输出

三个标准库函数打印字符串,puts, fputs, printf

puts

只有一个参数:字符串的地址
遇到空字符就停止打印
会自动加换行符

#include <stdio.h>
#define DEF "I'm a #defined string."
int main()
{char str1[80] = "An array was initialized to me.";const char *str2 = "A pointer was initialized to me.";//下面所有的参数都是地址puts("I'm an argument to puts().");puts(DEF);puts(str1);puts(str2);puts(&str1[5]);//前5个字符不会被打印puts(str2+4);//前4个不被打印return 0;
}

双引号括起来的就是字符串常量,被视为字符串自己的地址

I'm an argument to puts().
I'm a #defined string.
An array was initialized to me.
A pointer was initialized to me.
ray was initialized to me.
inter was initialized to me.

不要给puts传入指向没有空字符结尾的char数组的指针

如果puts没有遇到空字符,即你给他一个指针,但是指向的是一个没有空字符结尾的char数组

#include <stdio.h>
#define DEF "I'm a #defined string."
int main()
{char side_a[] = "Side A";char dont[] = {'W', 'O', 'W', '!'};char side_b[] = "Side B";puts(dont);return 0;
}
WOW!Side A

如果去掉side a ,side b
因为内存中空字符挺多,所以还好,没一直持续输出

WOW!a

在这里插入图片描述

相比于gets带来的实打实的安全漏洞,puts还算不错了,所以可以继续使用,没沦落到被C标准废除

示例 gets puts配对 读一行打印一行

#include <stdio.h>
int main()
{char line[81];while(gets(line))//gets没有遇到文件结尾就不会返回空指针,则为真puts(line);//会自动打印换行符return 0;
}

由于不知道怎么让gets返回空指针,我没法展示程序结束

adfhdjfjfn
adfhdjfjfn
fhjakfddfjdfjfnadndnf
fhjakfddfjdfjfnadndnf

fputs

在这里插入图片描述

示例 fgets fputs配对 读一行打印一行

#include <stdio.h>
int main()
{char line[81];while(fgets(line, 81, stdin))//fgets会自动在末尾添加换行符fputs(line, stdout);return 0;
}
fhfdujzh
fhfdujzh
hfjdhfhfdnfdf
hfjdhfhfdnfdf

可以看到,puts会自动打印换行符,fgets会自己加换行符,所以不可以把他俩配对使用,否则就有两个换行符。

gets puts, fgets fputs要配对使用。又因为gets不安全,所以就用后面这对吧

printf

printf函数也是把字符串的地址作为参数,但不会自动加换行符

printf和scanf一样,不是专门用于字符串的,他们都更加多才多艺,长处是格式化各种类型数据的输出
在这里插入图片描述

利用getchar 和 putchar自己定义字符串输入输出函数

示例1 puts1

putchar(*st++);//++优先级高于∗*,所以递增的是st,而不是它指向的值

#include <stdio.h>
void puts1(char *);
int main()
{char line[81];fgets(line, 81, stdin);puts1(line);puts1(line);return 0;
}void puts1(const char * st)
{while(*st!='\0')putchar(*st++);//++优先级高于*,所以递增的是st,而不是它指向的值
}

我写的puts1并没有输出换行符,换行符是fgets加的

It's late.
It's late.
It's late.
It's never too late.
It's never too late.
It's never too late.

也可以用数组表示法写这个函数,但需要多一个变量,所以指针好啊

void puts1(char st[])
{int i=0;while(st[i]!='\0')putchar(st[i++]);
}

更高级的写法

void puts1(char *st)
{while(*st)//字符串结尾一定是空字符,空字符的值是0putchar(*st++);
}

在这里插入图片描述

示例 puts2

打印字符串,且计算被打印字符的个数

#include <stdio.h>
unsigned int puts2(char *);
int main()
{char line[81];unsigned int n;fgets(line, 81, stdin);n = puts2(line);printf("n=%u", n);return 0;
}unsigned int puts2(char *st)
{unsigned int n = 0;while(*st)//字符串结尾一定是空字符,空字符的值是0{putchar(*st++);n++;}return n;
}

n=19,因为有换行符

I like C language.
I like C language.
n=19

字符串函数

在这里插入图片描述
这些函数在C++里面还是在用

库函数的原型都在几个标准的头文件里

strlen

#include <stdio.h>
#include <string.h>
void fit(char *, unsigned int);
int main()
{char mesg[] = "Things should be as simple as possible,"" but not simpler.";puts(mesg);fit(mesg, 38);puts(mesg);puts("Let's look at more of the string.");puts(mesg + 39);return 0;
}void fit(char * st, unsigned int size)
{	//这个size指纯字符,不包括空字符if(strlen(st)>size)*(st+size) = '\0';
}

第39个字符,一个逗号,被替换为空字符

Things should be as simple as possible, but not simpler.
Things should be as simple as possible
Let's look at more of the string.but not simpler.

strcat

在这里插入图片描述
函数的类型就是返回值的类型。strcat返回第一个参数。

#include <stdio.h>
#include <string.h>
#define SIZE 80
char * s_gets(char *, int n);
int main()
{char flower[SIZE];char addon[] = "s smell like old shoes.";puts("What is your favorite flower?");if(s_gets(flower, SIZE)){strcat(flower, addon);puts(flower);puts(addon);}elseputs("End of file encountered!");puts("Bye!");return 0;
}char *s_gets(char *st, int n)
{char *ret_val;int i=0;ret_val = fgets(st, n, stdin);if(ret_val){while(*(st+i)!='\n' && st[i]!='\0')i++;if(st[i]=='\n')st[i] = '\0';elsewhile(getchar()!='\n')continue;}return ret_val;
}
What is your favorite flower?
lily
lilys smell like old shoes.
s smell like old shoes.
Bye!

strncat

之前还以为他是拼接多个字符串呢
确实没必要设计一个拼接多个字符串的函数,直接循环strcat就可以实现了

在这里插入图片描述

#include <stdio.h>
#include <string.h>
#define SIZE 30
#define BUGSIZE 13
char * s_gets(char *, int n);
int main()
{char flower[SIZE];char bug[BUGSIZE];int available;char addon[] = "s smell like old shoes.";puts("What is your favorite flower?");s_gets(flower, SIZE);if((strlen(flower) + strlen(addon) + 1) <= SIZE)strcat(flower, addon);puts(flower);puts(addon);puts("What is your favorite bug?");s_gets(bug, BUGSIZE);available = BUGSIZE - strlen(bug) - 1;strncat(bug, addon, available);puts(bug);puts("Bye!");return 0;
}char *s_gets(char *st, int n)
{char *ret_val;int i=0;ret_val = fgets(st, n, stdin);if(ret_val){while(*(st+i)!='\n' && st[i]!='\0')i++;if(st[i]=='\n')st[i] = '\0';elsewhile(getchar()!='\n')continue;}return ret_val;
}
What is your favorite flower?
rose
roses smell like old shoes.
s smell like old shoes.
What is your favorite bug?
earthworm
earthworms s
Bye!

在这里插入图片描述
牛逼的观点,这境界,只有牛逼程序员才能达到

strcmp (比较内容)

返回值是int类型, 若两字符串内容相同,则返回0,否则返回1或-1或其他值(编译器不同则处理略微不同),后面有示例程序

它比较的是字符串,不是数组哈,所以直接输入不同大小的数组也没事,他会自动根据空字符识别出字符串

比较字符串是否相同不能用关系运算符(如!=和==)

因为字符串实际上是指针,即会被识别为存储自己的地址,用不等于实际上是在比较两个地址是否一样,那除了和自己之外不可能一样的,输入是用户输入的,必定另外开辟内存,所以这个程序永远也不会判定输入是正确的

so,strcmp 比较2个字符串的内容
!=比较2个字符串的地址

但是char类型可以用关系运算符如!=来比较哦!!因为char实际上是整型。

#include <stdio.h>
#include <string.h>
#define ANSWER "Grant"
#define SIZE 40
char *s_gets(char *st, int n);
int main()
{char try[SIZE];puts("Who is buried in Grant's tomb?");s_gets(try, SIZE);while(try!=ANSWER){puts("No, that's wrong. Try again.");s_gets(try, SIZE);}puts("That's right!");return 0;}char *s_gets(char *st, int n)
{char * ret_val;int i = 0;ret_val = fgets(st, n, stdin);if(ret_val){while(*(st+i)!='\n' && *(st+i)!='\0')i++;if(st[i]=='\n')st[i] = '\0';elsewhile(getchar()!='\n')continue;}return ret_val;
}
Who is buried in Grant's tomb?
mary
No, that's wrong. Try again.
grant
No, that's wrong. Try again.
Grant
No, that's wrong. Try again.

在这里插入图片描述
改为strcmp

while(strcmp(try, ANSWER))//等效于while(strcmp(try, ANSWER)!=0),但是前者更简洁,因为非0都真
Who is buried in Grant's tomb?
Grant
That's right!

解决大小写问题(用户友好)

把答案预定义为大写,然后把用户的输入全部转换为大写

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define ANSWER "GRANT"
#define SIZE 40
char *s_gets(char *st, int n);
int main()
{char try[SIZE];int a, i;puts("Who is buried in Grant's tomb?");s_gets(try, SIZE);//把用户输入统一转换为大写,注意不要用sizeof,因为try数组的size是40for(i=0;i<strlen(try);i++)*(try+i) = toupper(try[i]);while((a=strcmp(try, ANSWER))){puts("No, that's wrong. Try again.");s_gets(try, SIZE);}puts("That's right!");return 0;}char *s_gets(char *st, int n)
{char * ret_val;int i = 0;ret_val = fgets(st, n, stdin);if(ret_val){while(*(st+i)!='\n' && *(st+i)!='\0')i++;if(st[i]=='\n')st[i] = '\0';elsewhile(getchar()!='\n')continue;}return ret_val;
}
Who is buried in Grant's tomb?
grant
That's right!

strcmp的返回值

在这里插入图片描述

#include <stdio.h>
#include <string.h>
int main()
{printf("strcmp(\"A\", \"B\") is %d\n", strcmp("A", "B"));printf("strcmp(\"B\", \"A\") is %d\n", strcmp("B", "A"));printf("strcmp(\"A\", \"A\") is %d\n", strcmp("A", "A"));printf("strcmp(\"A\", \"a\") is %d\n", strcmp("A", "a"));printf("strcmp(\"apple\", \"apples\") is %d\n", strcmp("apple", "apples"));printf("strcmp(\"apples\", \"apple\") is %d\n", strcmp("apples", "apple"));return 0;
}

A和A相同,返回0
A和B的两个比较结果返回值分别为1和-1,所以字母的顺序(在ascii码表中的顺序)会影响返回值
在这里插入图片描述
同一个字母的大小写的返回值不是0哦,ascii码表中,a在A后面,所以返回负数

注意这里用双引号括一个字母,不是字符,而是字符串(字符A和空字符组成)

在这里插入图片描述
这说明strcmp会比较所有字符,甚至包括空字符,不是只比较首字符

strcmp("A", "B") is -1
strcmp("B", "A") is 1
strcmp("A", "A") is 0
strcmp("A", "a") is -1
strcmp("apple", "apples") is -1
strcmp("apples", "apple") is 1

示例 利用返回值结束输入

#include <stdio.h>
#include <string.h>
#define SIZE 80
#define LIM 10
#define STOP "quit"
char *s_gets(char *st, int n);
int main()
{char input[LIM][SIZE];int ct=0;printf("Enter up to %d lines (type quit to quit):\n", LIM);while(ct<LIM && s_gets(input[ct], SIZE)!= NULL && strcmp(input[ct], STOP)!=0)ct++;printf("%d strings entered.\n", ct);return 0;
}
char *s_gets(char *st, int n)
{char *ret_val;int i=0;ret_val = fgets(st, n, stdin);if(ret_val){while(*(st+i)!='\n' && st[i]!='\0')i++;if(st[i]=='\n')st[i] = '\0';elsewhile(getchar()!='\n')continue;}return ret_val;
}
Enter up to 10 lines (type quit to quit):
aaaaaaa
ccccccccccccccc
ffffffffffff
quit
3 strings entered.

在这里插入图片描述

strncmp

在这里插入图片描述

#include <stdio.h>
#include <string.h>
#define LISTSIZE 6
int main()
{const char *list[LISTSIZE] = {"astronomy", "astounding", "astrophysics", "ostracize", "asterism", "astrophobia"};int count = 0;int i;for(i=0;i<LISTSIZE; i++)if(strncmp(list[i], "astro", 5)==0){printf("found: %s\n", list[i]);count++;}printf("The list contained %d words beginning"" with astro.\n", count);return 0;
}
found: astronomy
found: astrophysics
found: astrophobia
The list contained 3 words beginning with astro.

strcpy (字符串赋值运算符)

既然可以拷贝地址,为啥还要拷贝字符串本身到数组中呢?
在这里插入图片描述在这里插入图片描述

#include <stdio.h>
#include <string.h>
#define SIZE 40
int main()
{int x;x=5;char temp[SIZE]="long time no see.";//正确!!初始化可以这样char temp[SIZE];//temp = "long time no see.";//错误!!不可以直接把数组赋给另一个数组return 0;
}

在这里插入图片描述

这个问题中的临时数组像一个缓冲,以保证复制给目标数组的单词一定是以q打头的

#include <stdio.h>
#include <string.h>
#define SIZE 40
#define LIM 5
char *s_gets(char *st, int n);
int main()
{char temp[SIZE];char target[LIM][SIZE];int i=1, ct=0;printf("Enter %d words beginning with q:\n", LIM);while(s_gets(temp, SIZE)!= NULL && i<LIM){if(temp[0]=='q'){strcpy(target[i], temp);ct++;}i++;}printf("There are %d words beginning with q.\n", ct);return 0;
}char *s_gets(char *st, int n)
{char *ret_val;int i=0;ret_val = fgets(st, n, stdin);if(ret_val){while(*(st+i)!='\n' && st[i]!='\0')i++;if(st[i]=='\n')st[i] = '\0';elsewhile(getchar()!='\n')continue;}return ret_val;
}
Enter 5 words beginning with q:
quote
query
pear
apple
hot
There are 2 words beginning with q.

改良程序(让程序获取5个q开头的字符才退出):

这个程序让我注意到了以前没重视起来的地方,即短路运算符组成的关系表达式中,要把最紧要的条件放在最前面,这样一旦不满足就不看后面了,有时候涉及输入的话放错顺序会导致需要多输入一个换行符才行,不信可以把下面程序中while(i<LIM && s_gets(temp, SIZE)!= NULL)改为while(s_gets(temp, SIZE)!= NULL && i<LIM),看看第五个q开头单词输入后的区别

if(temp[0]!=‘q’)// 等效于if(strncmp(temp, “q”, 1))

#include <stdio.h>
#include <string.h>
#define SIZE 40
#define LIM 5
char *s_gets(char *st, int n);
int main()
{char temp[SIZE];char target[LIM][SIZE];int i=0;printf("Enter %d words beginning with q:\n", LIM);while(i<LIM && s_gets(temp, SIZE)!= NULL)//一定要把i<LIM放在前面,这样第五个q开头的单词输入后就直接退出了,用短路运算符时一定把首要判断条件放在前面,顺序是需要考虑一下的哦{if(temp[0]!='q')// 等效于if(strncmp(temp, "q", 1)){puts("This word is not beginning with q!");}else{if(i!=4)puts("Got one! Keep entering:");strcpy(target[i], temp);i++;}}printf("End of entering. The input words beginning with q:\n");for(i=0;i<LIM;i++)puts(target[i]);return 0;
}char *s_gets(char *st, int n)
{char *ret_val;int i=0;ret_val = fgets(st, n, stdin);if(ret_val){while(*(st+i)!='\n' && st[i]!='\0')i++;if(st[i]=='\n')st[i] = '\0';elsewhile(getchar()!='\n')continue;}return ret_val;
}
Enter 5 words beginning with q:
as
This word is not beginning with q!
qw
Got one! Keep entering:
qe
Got one! Keep entering:
qr
Got one! Keep entering:
qt
Got one! Keep entering:
qy
End of entering. The input words beginning with q:
qw
qe
qr
qt
qy

在这里插入图片描述在这里插入图片描述

http://www.lbrq.cn/news/2614717.html

相关文章:

  • 哪一个做网站模版好用的外链吧
  • 做电影网站考什么软件企业网络营销业务
  • b2b网站大全 黄页大全百度关键词快排
  • 电影网站建站关键词排名推广怎么做
  • 全国疫情分布图aso优化吧
  • 用ps如何做网站首页四川seo选哪家
  • 在线网站你们会回来感谢我的上海关键词优化排名哪家好
  • 网站建设方案需要哪些步骤电商网站开发平台有哪些
  • 网站适合用angular做吗企业推广方案
  • 手机做ppt苹果手机下载网站营销策略有哪些有效手段
  • 上海外贸网站制作公司南宁seo服务优化
  • 网站开发流程详细介绍软件定制开发
  • 金顺广州外贸网站建设青岛网站排名提升
  • 日本3040岁精华液排行榜上海网站seo诊断
  • 湖北企业模板建站开发湖南网站推广
  • 建设银行网站源码关键词优化工具互点
  • 类似建设b站网站韩国vs加纳分析比分
  • 武汉移动网站制作洗发水营销推广软文800字
  • 找网站建设公司如何自己开个网站平台
  • 网站建设佰首选金手指六自有品牌如何推广
  • 宁波建网站如何收费在百度上怎么打广告
  • 金顺广州外贸网站建设提升排名
  • 网站建设分金手指专业十百度推广和优化哪个好
  • 建筑施工企业中是应急救援领导北京百度推广优化排名
  • 做资讯类网站百度查一下
  • 王建设医生网站优化什么意思
  • 九江网站设计公司表白网页制作免费网站制作
  • 做网站有效果吗短信营销平台
  • 做网站单线程CPU和多线程cpu公司查询
  • 宜昌网站设计公司网站收录服务
  • Python Pandas.lreshape函数解析与实战教程
  • GPU 优化-用 tensor core实现5G Massive MIMO 64x64
  • 海康威视相机,MVS连接成功,但无图像怎么办?
  • libpq库使用
  • LSTM + 自注意力机制:精准预测天气变化的创新方案
  • 【计算机网络 | 第2篇】计算机网络概述(下)