C语言第十章字符串

2021-03-27 21:25

阅读:749

标签:网络   现在   字符   函数   不用   internet   动态   传递   scan   

 

上节回顾

 指针:特殊的数据类型

 指针变量:指向变量的指针

函数指针

 可以通过解引用来引用指针变量所指向的变量的值、函数指针指向的函数

 正确使用指针的两个原则:

1、一定要明确指向哪里---初始化

2、 指向的内存单元中的内容是什么 ---- 基类型

 

指针最重要的应用:

1、作为函数参数:指针变量作为形参,使被调函数可以访问修改主调函数中的变量

2、函数指针作为形参,可以编写通用的函数,实现不同的功能

 

字符串

本章内容

1、字符串常量

2、字符数组和字符指针

3、字符串处理函数

4、向函数传递字符串

5、从函数返回字符串指针

 

10.1字符串常量

技术图片

 

 

 ‘\0‘为转义字符,代表ascii码值为0的字符

 

 

10.2字符串的存储

 C语言没有提供专门的字符串数据类型,使用字符数组和字符指针来处理字符串。

字符数组

  每个元素都是字符类型的数组

  技术图片

 

 

 如果没有字符串结束标志的话,只能算是字符数组,可能会读出后面的乱码。需要明确的加上字符串结束标志 ‘\0‘

  技术图片

"a" --占用两个字节,省略了一个字符串结束符‘\0‘  

字符数组的初始化

  用字符常量的初始化列表对数组初始化

      char str[6] = {‘c‘, ‘h‘, ‘i‘, ‘n‘, ‘a‘, ‘\0‘};

  用字符串常量直接对数组初始化

    char str[6] = {"China"};

    char str[6] = "China";

    char str[  ] = "China";

10.3字符指针 

还可以用字符指针来指向一个字符串,如果让字符指针指向一个字符串常量

      char *pStr = "Hello China";

 技术图片

 

 字符串在内存中的存储位置,字符串保存在只读的常量存储区,

常量存储区和代码段都是只读的

技术图片

 

 

 

 两个误区:一是不能认为是把字符串存到*pStr;二是不能认为把字符串存到pStr; 

正确理解:定义了一个指针变量,用字符串的首地址对指针变量进行了初始化,将字符串的首地址赋值给了指针变量。

可以修改pStr的值,但是不能通过解引用方式对它指向的存储单元中的内容,为什么?

因为它指向的是字符串常量,存储在只读的常量存储区,不能改写字符串内容。

但是,如果将字符指针指向的不是一个字符串常量而是字符数组中的内容

 

那么,数组保存在哪里呢?

如果数组定义实在函数内,则保存在动态存储区。如果定义在了函数外部,或者定义为静态数组,字符串保存在静态存储区。

现在数组的内容可以修改,但是数组名不能修改,数组名指向数组的首地址。

 

技术图片

 *pStr = ‘W‘;----指针的解引用

等价于 str[0] = ‘W‘; 也等价于pStr[0] = ‘W‘;

 

怎么正确的使用和区分字符数组和字符指针?几个使用原则:

1、明确字符串指向的内容保存在哪里,常量存储区还是静态存储区

2、明确字符指针指向了哪里

 

 10.4字符串的访问和输入/输出

char str[10]; 按字符逐个输入/输出

1 for( i=0; str[i] != \0; i++){
2     putchar(str[i]);    
3 }
4 putchar(\n);

一般不用字符串长度控制,如i

 

第二种方法 

按字符串整体输入/输出

scanf("%s",str);为什么不加取地址运算符,因为str是一个数组名,已经代表了地址

printf("%s",str); s格式符会判断是否有字符串结束标志

gets(str); 输入一个字符串

puts(str); 输出一个字符串

 

scanf输入只能输入不带空格的字符串,因为它遇到空白字符就会输入结束了。包括空格、tab、回车等

但是gets是可以输入带空格的字符串的,它是已回车结束的。

 

scanf和gets使用中的细微差别!!

运行下面的程序。

技术图片

将scanf换成gets 再试试。

 

此外,这两个函数对回车符的处理也不相同。运行下面的程序:

技术图片

gets将回车读走了,但是不作为字符串的一部分。输入hello world\n

第一次输出hello,被scanf读走,第二次输出world,是被gets读走

 

修改一下程序

技术图片

 

 

getchar 是能够将空格和回车作为有效字符读走的。

再修改下程序

技术图片

 

进一步的再修改下程序,看一下运行结果

技术图片

检查scanf的返回值,检查正确是否正确读入字符。

应该在下一次提示重新输入之前将缓冲区中的非法字符读走

技术图片

 

再进一步看下,不用getchar。

技术图片

 

用scanf(" ")读走了留在缓冲区中的回车,这两条语句连接起来用

技术图片

这里还可以将scanf("  ")换成getchar();

 

再修改下,把前面的scanf换成gets,这个时候就不用对读入的回车符进行处理了,后面的scanf前面就不需要加空格了

技术图片

 

例10.1 从键盘输入一个人名把它显示在屏幕上

 

技术图片

 

修改一下程序:

  技术图片

 

再进一步修改,将scanf 改成gets

技术图片

 

再进一步修改:
对于这个来说,指针变量的使用意义并不大。

技术图片

 

例10.3 从键盘输入一个带有空格的人名,然后在显示人名的前面显示"Hello",I said to

再进一步修改一下程序:

\" ----这是一个转义字符,就是双引号

fgets---同样也是可以读取带空格的, 13章文件中会讲到,这个函数更安全,可以控制输入长度,防止发生越界

技术图片

 

上节回顾:

scanf 和gets输入字符串的区别:

   1、 scanf不能输入空格,gets能够输入空格

 2、对回车的处理也不一样 ,gets可以将回车读走,scanf不能将回车读走

fgets :能够限制输入字符串的长度,比gets更加安全

 

两个输出函数,遇到字符串结束标志就结束。

printf    %s

puts()

 

字符指针,看下面的例子

技术图片

 

 

 

10.5字符串处理函数

5个常用的字符串处理函数

技术图片

 

 

 技术图片

 

 

 

求字符串长度--strlen(字符串)

不包含字符串结束标志,指的是改字符串实际的长度。

技术图片

 

 

 

 字符串输出的长度控制,使用strlen来控制,不过这种方法也不常用,更为常用的还是使用字符串结束标志

技术图片

 

 

 

字符串复制--strcpy(目的字符串,源字符串)

技术图片

 

 

 答案是不能,因为str1和str2都是地址值。

那么字符串怎么来赋值呢?要么就是编写一个函数,一个字符一个字符的赋值,要么就是使用字符串处理函数。

技术图片

 

 

 

字符串连接--strcat(目的字符串,源字符串);

把str1连接到str2的后面,那就意味着str2要足够大

技术图片

 

 

 

字符串比较--strcmp(字符串1,字符串2)

技术图片

 

 

 

 字符是根据ascii值来比较大小,那么字符串大小怎么比呢?

不能使用关系运算符来比较,因为代表的是地址。那么字符串比较函数是怎么比较大小的呢?

技术图片

 

 

 返回的是ascii值相减的结果

例10.4 按奥运会参赛国国名在字典中的顺序对其入场次序进行排序

二维字符数组可以存储多个字符串

char name[N][10] 表示可以存储N个实际长度最大为9的字符串。

 

技术图片

 

 

 

排序函数SortString() 

技术图片

 

 

 

本章补充内容:缓冲区溢出攻击

 

网络黑客常常针对系统和程序自身存在的漏洞,编写相应的攻击程序

  其中,最常见的就是对缓冲区溢出漏洞的攻击

  几乎占到了网络攻击次数的一半以上

世界上第一个缓冲区溢出攻击

  internet蠕虫,曾造成全球多台网络服务器瘫痪

何谓缓冲区溢出攻击?

  利用缓冲区溢出漏洞进行的攻击

 

易引起缓冲区溢出攻击、不安全的函数

   gets()、scanf()、strcpy()等不限制字符串长度,不对数组越界进行检查和限制,导致有用的堆栈数据被覆盖,给黑客攻击以可乘之机

对缓冲区溢出漏洞进行攻击的后果。

  程序运行失败,系统崩溃

  精确设置一些数据,让程序执行非授权的指令,进行各种非法的操作

 

怎么防止和检测缓冲区溢出呢?

 

缓冲区溢出攻击实例

技术图片

 

通过缓冲区溢出将密码改成了me,当然了一般系统不一定这么容易就破解,主要是让大家理解缓冲区溢出的危害。

 

 技术图片

 

 

字符串的安全输入方法----修改下程序:

但是这种方法也有弊端,就是要字符串的长度变了后,就要修改数字8,比较麻烦。

技术图片

 

 

 

再次修改程序:

fgets限制输入字符串的长度,更加灵活

技术图片

 

 

 

前面三个不安全,但是下面的n族的相对安全,限制了处理的字符串长度。

技术图片

 

10.6 向函数传递字符串

向函数传递字符串时

  既可用字符数组作函数参数

  也可用字符指针作函数参数

两种方法都属于传地址调用,传递字符串的首地址相对于传递整个字符串的内容来说,效率更高。

 

下面来看几个例子。尝试自己编写字符串处理函数。

1、编写字符串复制函数(用字符数组)

技术图片

 

 

2、字符串拷贝---使用字符指针

技术图片

 

 

 

 例、10.6计算实际字符个数

分别用字符数组和字符指针来编写程序。

技术图片

两种方式不同之处:

字符数组作为形参:通过下标来控制循环

字符指针作为形参:直接用字符指针的自增运算来遍历

 

 

 

例10.7编写字符串连接函数实现strcat()的功能,返回连接后的字符串首地址

技术图片

 

 看一下连接函数的功能:

首先要将目的字符串的指针移动到末尾‘\0‘位置

技术图片

 

 接下来要完成字符串复制的操作

技术图片

 总结一下就是两个步骤:一是将前一个字符串指针移动到末尾,二是将源字符串从第一个字符串从目的字符串的末尾开始拷贝

但是仍然并不能实现返回连接后的字符串首地址的功能,因此需要一个指针变量记录下目的字符串原来的首地址(循环开始之前)。

 

技术图片

通过上面的例子可以知道,字符指针既可以作为函数的形参,也可以作为函数的返回值

 

本章小结

如果一个字符指针指向了一个字符串常量,字符串常量存储在常量存储区,是只读的,因此不能修改字符串内容

但如果指向的是一个字符数组,并且这个字符数组是在函数内部定义的,那么这个字符数组是保存在动态存储区的。但如果改字符数组是静态的,那么保存在静态存储区,但无论是动态还是静态,保存的字符串内容和指向该字符数组的指针都是可以修改的(修改字符指针是修改的字符数组的地址)。所以一定要明确两条(1和2):

1、明确字符串被保存到了哪里

2、明确字符指针指向了哪里

  指向字符串常量的字符指针

  指向字符数组的字符指针

3、向函数传递字符串

  向函数传递字符数组

  向函数传递字符指针

字符数组和字符指针都能够达到向函数传递字符串的目的,但是传递的不是字符串的内容,而是字符串的首地址,采用的是传地址调用的方法,被调函数通过字符串的地址来间接访问字符串的内容,还可以从函数返回字符串的地址,只要将返回值类型定义为字符指针类型。

4、字符串处理函数

拷贝、连接、比较、长度计算等等,不常用的还有很多,可以查手册

记住,数组名赋值给另一个数组名方式不能完成字符串拷贝(赋值的是地址)

并且,字符串比较的时候不能直接用关系运算符,但是字符的比较大小可以使用关系运算符。

C语言第十章字符串

标签:网络   现在   字符   函数   不用   internet   动态   传递   scan   

原文地址:https://www.cnblogs.com/west20180522/p/13644760.html


评论


亲,登录后才可以留言!