zoukankan      html  css  js  c++  java
  • 读书笔记_代码大全_第13章_不常见的数据类型

    不常见的数据类型

    前注:希望我的读书笔记能带你迅速走过25页的书籍,有不妥之处,欢迎指正。http://www.cnblogs.com/jerry19880126/

    本章主要介绍三种“不常见”的数据类型,分别是结构体,指针和全局数据,其实我觉得这三种数据类型还是很常见的,不太认同本书将之分类成“不常见”。

    第一部分:结构体

    结构体好比是一个团体,它将一些相关的数据放在一起,比如对于student而言,属性可能包括name,age,sex,height和weight等,因此可以这样声明一个学生的结构体:

    struct Student

    {

             string name;

             int age;

             char sex;

             int height;

             int weight;

    };

    定义一个学生的实体张三Student zhangSan,就可以这样引用张三的姓名zhangSan.name,年龄zhangSan.age。

    结构体的优点有四个:

    (1)     明确数据关系

    如上面这个例子,若不组团,而分开写,比如给张三同学赋初值:

    name = inputName;

    age = inputAge;

    这样貌似还是不很乱,但万一这时候还有李四同学怎么办?千万不要用name1,name2来区别,前面有读书笔记已经谈到,这种变量命名的方式是很糟糕的。那用nameZhangSan,ageZhangSan,nameLiSi,ageLiSi?这种命名方式会很麻烦,万一学生数很多,这样不是要有成千上万个变量了?那用数组name[100],age[100]来解决这个问题?数组的确是OK的,但有没有想过,这样的写法缺乏关联性?name[0]与age[0]是对同一个学生的描述(下标0表示这是第一个学生),但这两个变量名却没能反映它们都是学生的属性

    若采用上面的结构体声明,再定义Student student[100],然后用student[0].name来表示0号同学的名字,student[0].age来表示0号同学的年龄,这样能立刻感觉出name和age都是描述一个student的属性。因此:

    student[0].name = inputName;

    student[0].age = inputAge;

    能更清楚地反映属性的归属。

    (2)     可以简化对数据块的操作

    假设要复制学生张三,若不采用结构体,则需要这样操作:

    newName = nameZhangSan;

    newAge = ageZhangSan;

    这样要写很多句话,有多少属性就有多少个赋值语句,但如果用结构体:

    Student newStudent;

    newStudent = zhangSan;

    只要两句话就搞定了。

    (3)     可以简化参数列表

    你觉得是调用函数时用fun(nameZhangSan, ageZhangSan, sexZhangSan, heightZhangSan, weightZhangSan)比较好,还是用fun(zhangSan)来得简单呢?

    (4)     可以减少维护

    假定要对Student删除sex属性,你觉得是删掉每个对sex数组的引用比较好,还是直接删掉结构体中的sex项比较方便呢?

    第二部分:指针

    指针本质是数据的地址,画个示意图就知道了:

     

    以win32环境为例,方框表示内存块,内存块是以“字节”为单位的,方框里的数值表示的是内容,方框左侧的16进制表示的是方框所在的地址,由上到下地址逐渐增加。左侧表示的指针,指针在win32平台下都是4个字节(不管是int*,short*,double*还是char*,都是4个字节),这样指针的数值(存放的内容)是0x00001234(小端表示),而指针数值表示的是地址,所以它其实是指向首地址为0x00001234的内存块,也就是右侧。我们所熟悉的int*,short*等等,其实是解释应该怎样解读右侧的内存块。假定指针名为pointer,若指针类型是char*,则认为所指向的地址表示的是char数据,所以*pointer的值为0x0a,表示ASCII码为10的字符,也就是换行符;若指针类型为short*,则表示2字节的短整数0x610a,*pointer值为24842;若指针类型为float*,则表示4字节的浮点数0x6362610a,数值大约为4.176*10^21。综上,指针的数值(内存块的内容)表示的是目标内存块的首地址,指针总是4个字节,但怎样解释目标内存块是由指针类型决定的。从上面还可以看到指针其实也是有地址的,最左侧的数字就是指针的地址,用&pointer就可以获得0x003839了。

    《代码大全》上列举了指针的使用技巧,我只列出自己认为重要的:

    (1)     同时声明和定义指针

    int* pointer并不一个好习惯,因为这时候pointer的值到底是什么,我们并不知道,万一pointer指向了一段重要的数据区,一旦有*pointer = 1234,数据区的数据就会被污染(现代操作系统已经有这类问题的预防了,只要使用了*pointer,就会弹出带红叉的对话框阻止程序继续往下走,但仍需要小心)。解决这个问题的方法是int* pointer = &var,让指针从诞生起就有明确的指向。

    (2)     删除指针后将之设为空值

    当执行delete pointer后,pointer的值是多少?这时候是不确定的,有的时候它指向上次的目标内存块,有时候它指向系统的随机地址,此时的指针被称为dangling pointer(迷途指针或悬空指针)。这时候引用*pointer是非常危险的,比如*pointer = 1234,你的重要数据又不保了。

    (3)     粉碎垃圾数据

    当执行delete pointer后,其实你不是想解放指针,你真正想要做的是释放动态分配的一段内存。比如说:

    int *pointer = new int[2];

    pointer[0] = 3;

    pointer[1] = 9;

    delete [] pointer其实是想释放掉这2个int型的内存块,但事实上,在执行delete操作后,原来的那两个int型内存块的值可能还是3和9。delete不可能真的从操作系统中删掉内存(那些内存块一个也不会少)!比如你电脑D盘的总容量是30G,你删除一个文件后的总容量还会是30G,不会变少的。delete所能做只是删掉这些内存的引用,至于这些内存的内容,可能是上次操作遗留的值,也可能是操作系统随机出的垃圾值。这些遗留下来的值会干扰你调试程序,因为你无法确定指针指向的内存何时已经变成非法的了!解决方法是粉碎这些垃圾数据,比如memset(pointer, 0xcc, MemoryBlockSize(pointer)),然后再delete pointer。这样垃圾数据都被置0xcc了。《代码大全》上推荐在每一个待使用的内存块后多申请一个标记字段,称为dog tag(狗牌),这时只要检测狗牌是有效值还是0xcc,就知道还能否继续使用这段内存块了。

    (4)     在与指针分配相同的作用域中删除指针

    一句话,若在一个函数中new了内存块,则也在这个函数中delete掉它!不要把delete操作放在这个函数之外的地方。(因为new是在堆在分配内存,所以这个函数外的其他地方还是能够使用这块内存的,但这样做不安全,不要指望你的记性有多好,你不会总记得在最后释放掉它的)

    (5)     在使用指针前作检测

    包括两个检测,一是检测指针本身,二是检测指针所指向变量的取值范围。指针本身主要是看指针是否为空,我还记得360一面的面试官让我手写一个字符串查找的小程序,我还没写完他就打断我,说我忘了检测指针是否为空了,汗啊……这个条件一定要判断,不然会给面试官留下粗心的坏印象的。检测指针所指向变量的取值范围,其实就是检测变量是否是合法的,比如有的变量的最大值不可能超过360,但是*pointer后的值却超过了360,这就出现bug了。可以用断言,比如assert(*pointer <= 360)。

    (6)     链表操作中画图对付指针

    单向链表、双向链表、循环链表的操作包括插入、删除、查找结点等,这些操作空想会挺麻烦的,但若画个图,就简单多了。注意这里的操作代码有前后顺序之分,比如删除结点时什么时候该断开这个结点是有说法的,这里就不举例子了,很多数据结构书上都会介绍到的。

    下面谈谈C++中的指针与引用的区别,这是我舍友今年去微软面试的一道面试题,题面其实很直观,学过C++的同学也能答上个一两条,但若是能给出全面的答案,其实并不容易。本章给出了两个最重要区别,第一个区别是引用必须总是引用一个对象,不可能“悬空的”,比如int& a,这语法上就通不过的,int& a = b,将a引用b才是可以的(这时候a是b的别名,就好比某个同学身份证上的名字和乳名一样,其实是一个人的两种叫法);但指针可以“悬空”,比如int* p,指向“空”也可以,比如int* p = NULL。第二个区别是引用确定在声明的时候,即从它诞生之日起,就不可以改变了,int& a = b,之后再有a = c语法上就通不过了;但指针可以随时改变它的指向,比如int* p = &a,接下来p = &b也是合法的。

    下面给出比较全面的回答:

    i. 从现象看,指针在运行时可以改变其所指向的值,而引用一旦与某个对象绑定后就不能再改变;

    ii. 从内存分配上,程序为指针分配空间,而不为引用分配空间;

    iii. 从编译上看,指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值 。

    第三部分:全局数据

    本章的最后花了不少篇幅来介绍全局数据,其实也就是一句话,尽量少用全局变量。因为它不是线程安全的(即多个线程万一对这个变量同时操作,会出bug),另外,全局变量还破坏了代码的模块化,阻碍了代码的重用。但我也不得不承认,在写过的程序里使用过全局变量,因为它可以简化对极其常用的数据的使用,如果不用全局变量,可能需要传递多个函数参数。本书推荐将全局变量写在类中,可以作为类的静态成员来使用,当然不是把一堆不相关的全局变量放到一个不相关的类中,而是有规律的组织这些相关的变量。此外,用g_前缀来区分,并提供一份注释良好的清单,会减轻全局变量的副作用。

    <end>

  • 相关阅读:
    打开一个网页,以html代码保存于txt文件中
    用C查看系统任务管理器中运行的程序
    常见两种LINK错误
    怎么把下载的dll和def生成lib,以用于编程
    建立一个不能打开的文件(占坑)C语言高级API调用
    [转]软件版本命名格式
    回调函数编写和注重点
    ubuntu linux mysql 开发模式与连接编译
    创建一个进程和两个管道,实现与进程通信
    hdoj 1115 Lifting the Stone (求多边形重心)
  • 原文地址:https://www.cnblogs.com/jerry19880126/p/2828573.html
Copyright © 2011-2022 走看看