C语言(Head First C)-2_2:存储器和指针

来源:互联网 发布:计算概率的软件 编辑:程序博客网 时间:2024/05/21 17:49

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

2 存储器和指针:指向何方?    

前文《C语言(Head First C)-2_1:存储器和指针》


scanf()与fgets():

 

 scanf()会导致缓冲区溢出:

 如果忘记限制scanf()读取字符串的长度,可能会输入远远超出程序空间的数据,多余的数据会写到计算机还没有分配好的存储器中;(运气好的话,不但能保存,而且不会有问题。)

 但缓冲区溢出很可能会导致程序出错,这种情况通常被称为段错误或abort trap,不管出现什么错误,程序都会崩溃;

 

 fgets():

 同样可以输入文本数据:接收char指针,并给出最大长度;

 如:

 char food[5];

 fgets(food,sizeof(food),stdin);//或将最大长度指定为5

 

 food:接收的指向缓冲区的指针;

 指定的长度是所接收字符串(包括\0)的最大长度;(这样就不用像scanf()那样把长度减1)

 stdin标示数据将来自键盘;

 

 (Code2_1)

/* * fgets */#include <stdio.h>int main() {        puts("请输入:");    char name[10];    fgets(name , sizeof(name) , stdin);    printf("name is :%s\n",name);    return 0;}

 log:

 请输入:

 uawehfawyhiefwahe

 name is :uawehfawy

 

 注意:这里配合使用的sizeof运算符,计算的是数组的大小;如果是指针的话,计算的仅仅是指针的大小;

 不要使用gets()!!!因为它没有任何限制;

 

 scanf()与fgets()对比:

 1)限制:

 scanf()需要在格式串中加入长度;

 fgets()强制限制用户输入字符串的长度;

 2)多字段:

 scanf()允许输入多个字段,而且允许输入结构化数据,可以指定两个字段之间以什么字符分割;

 fgets()只允许向缓冲区中输入一个字符串,而且只能是字符串,只能有一个缓冲区;

 3)字符串中的空格:

 scanf()使用%s读取字符串时,遇到空格就会停止,相输入多个单词可以调用多次scanf(),或是使用复杂的正则表达式技巧;

 fgets()总能读取整个字符串;

 (Code2_2)

/* * scanf */#include <stdio.h>int main() {        puts("请输入:");    char name[10];    scanf("%9s",name);    printf("name is :%s\n",name);    return 0;}

 log:

 请输入:

 gu wefha auewfh

 name is :gu

 

 字符串指针和字符串数组:

 接下来看一个例子:三猜一

 (Code2_3)

/* * 三猜一 */#include <stdio.h>int main() {        char * cards = "JQK";        char a_card = cards[2];    cards[2] = cards[1];    cards[1] = cards[0];    cards[0] = cards[2];    cards[2] = cards[1];    cards[1] = a_card;        puts(cards);        return 0;}

 log:Bus error: 10

 

 代码旨在交换字符串JQK中的三个字母;玩家把钱压好之后,运行程序;

 存储器故障!

 

 问题出在:字符串字面值不能更新:

 指向字符串字面值的指针变量不能用来修改字符串的内容;

 

 但如果是字符串字面值的数组就可以修改了;

 (Code2_4)

/* * 三猜一 */#include <stdio.h>int main() {        char cards[] = "JQK";        char a_card = cards[2];    cards[2] = cards[1];    cards[1] = cards[0];    cards[0] = cards[2];    cards[2] = cards[1];    cards[1] = a_card;        puts(cards);        return 0;}

 log:QKJ

 

 这是由C语言使用存储器的方式决定的;

 存储器中的char * cards = "JQK";究竟发生了什么?

 1)计算机加载字符串字面值:

 当计算机把程序载入存储器时,会把所有常数值(字符串"JQK")放到常量存储区,这部分存储器是只读的;

 2)程序在栈上创建cards变量:

 栈是存储器中计算机用来保存局部变量的部分,局部变量也是位于函数内的变量,cards变量就在这个地方;

 3)cards变量设为"JQK"的地址:

 cards变量将会保存字符串字面值"JQK"的地址;为了防止修改,字符串字面值通常保存在只读存储器中;

 4)计算机试图修改字符串:

 程序试图修改cards变量指向的字符串中的内容时就会失败,因为字符串是只读的;

 

 所以,问题出在像"JQK"这样的字符串字面值保存在只读存储器中,他们是常量;

 

 解决方案:在存储器的非只读区域创建字符串副本,进行修改;

 

 在看可行的方式:char cards[] = "JQK"; cards不只是指针,cards现在是数组;

 那么什么时候是数组(cards[])什么时候是指针呢(*cards)?

 这取决于在什么地方看到它,如果是普通的变量声明,cards就是一个数组,而且需要立即赋值(因为声明时没有给出数组的大小);

 如果cards以函数参数的形式声明,那么cards就是一个指针;(我们前面有过类似的例子,sizeof的那个,还记得不?)

 

 存储器中的char cards[] = "JQK";究竟发生了什么?

 1)计算机载入字符串字面值:(和刚才一样)

 2)程序在栈上新建了一个数组:

 声明了数组,程序会创建一个足够大的数组来保存字符串,这里4个足以;

 3)程序初始化数组:

 分配空间,并把字符串字面值"JQK"的内容复制到栈上;

 

 我们看到区别在于:

 原来的代码使用了指向只读字符串字面值的指针;现在,使用字符串字面值初始化了一个数组,从而得到了这些字母的副本(非只读区域),这样就可以随意修改了;

 

 为了避免这个错误,我们可以在把指针设成字符串字面值时,确保使用const关键字:const char * s = "flower";

 这样,如果编译器发现有代码视图修改字符串,就会提示;

 

 (Code2_5)

/* * */#include <stdio.h>int main() {        char cardsY[] = "JQK";    char * cards = cardsY;        printf("字符串数组(B):%s\n",cardsY);    printf("字符串指针(B):%s\n",cards);        cardsY[0] = '1';    cardsY[1] = '2';    cardsY[2] = '3';        printf("字符串数组(A):%s\n",cardsY);    printf("字符串指针(A):%s\n",cards);    return 0;}

 log:

 字符串数组(B):JQK

 字符串指针(B):JQK

 字符串数组(A):123

 字符串指针(A):123

 

 看看这个例子,是不是更清晰了;

 

 要点:

 -如果在变量声明中看到*,说明变量是指针;

 -字符串字面值保存在只读存储器上;

 -如果想修改字符串,需要在新的数组中创建副本;

 -可以将char指针,声明为const char *,以防代码用它修改字符串;

 

 const:

 加不加const修饰的字符串字面值都是只读的,const修饰符表示,一旦你试图用const修饰过的变量去修改数组,编译器会报错;

 

 我们还要理解一点,数组变量并不保存在存储器中:

 程序在编译期间,会把所有的数组变量的引用换成数组的地址,也就是说,在最后的可执行文件中,数组变量并不存在;

 

 scanf()其实表示“scan formatted”,用来扫描带格式的输入;

 

 把存储器保存在大脑里(各个存储器段):

 

 1)栈:

 存储器用来保存局部变量的部分;调用函数时,函数的所有局部变量都在栈上创建;看起来就像堆积而成的栈板;

 栈做事颠三倒四,他从存储器的顶端开始,向下增长;

 

 2)堆:

 堆用于动态存储:程序运行时创建的一些数据,然后使用很长一段时间;(后续会介绍用法)

 

 3)全局量:

 全局量位于函数之外,对所有函数可见;程序一开始运行时就会创建全局量,你可以修改他们;

 

 4)常量:

 常量也是在程序一开始创建的,但是他们保存在只读存储器中;

 常量是一些程序中要用到的不变量,你不会想修改这些值;

 

 5)代码:

 最后是代码段,多数操作系统都吧代码放在存储器的低位;它是只读的,他在存储器中用来加载机器代码的部分;

 

 高位地址->              |栈

                         ^堆

                         |全局量

 只读存储器->            ^常量

 低位地址+只读存储器->   |代码

 

 工具箱:

 1)scanf("%i",&x)可以让用户直接输入数字x;

 2)不同计算机int的大小不同;

 3)char指针变量x的声明:char * x;

 4)字符串字面值保存在只读存储器中;

 5)用字符串初始化数组会复制字符串中的内容;

 6)&x会返回x的地址;

 7)&x称为指向x的指针;

 8)局部变量保存在栈上;

 9)数组变量可以用作指针;

 10)用*a读取地址a中的内容;

 11)可以用fgets(buf,size,stdin)输入文本;