不常见的数据类型---结构体,指针,全局数据

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

 

第一部分:结构体

结构体好比是一个团体,它将一些相关的数据放在一起,比如对于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>

 

 

 

转自:http://www.cnblogs.com/jerry19880126/archive/2012/12/21/2828573.html

### 回答1: `gd` 结构体是 `U-Boot` 中的全局数据结构。它包含了 `U-Boot` 运行期间所需要的一些全局变量指针,例如内存大小、环境变量指针等。 下面是一个 `U-Boot` 中的 `gd` 结构体的定义: ```c struct global_data { ulong flags; /* state flags for this record */ ulong baudrate; /* console baudrate */ ulong cpu_clk; /* CPU clock in Hz */ ulong bus_clk; /* Bus clock in Hz */ ulong mem_clk; /* Mem clock in Hz */ ulong arch_number; /* unique id for this board */ ulong ram_size; /* RAM size */ ulong flash_size; /* Flash size */ ulong flash_offset; /* start of FLASH */ int env_valid; /* true if env area is valid */ char env_buf[ENV_SIZE]; /* buffer for getenv() */ ulong env_addr; /* Address of Environment struct */ ulong env_sect_size; /* size of one complete sector */ ulong env_sectors; /* number of sectors available */ uchar env_flash[CFG_ENV_ADDR_REDUND] __aligned(ARCH_DMA_MINALIGN); /* Image environment in FLASH */ uchar env_redund[CFG_ENV_ADDR_REDUND] __aligned(ARCH_DMA_MINALIGN); /* Image redundant environment in FLASH*/ bd_t *bd; /* board description pointer */ }; ``` `gd` 结构体中的变量和指针的具体含义可以参考注释。 ### 回答2: Linux中的gd结构体是指全局数据结构(global data structure),用于存储截至到当前时间点的系统全局信息。gd结构体位于内存中的一个固定地址,它是Linux内核启动时创建的,包含了许多与系统运行相关的信息。gd结构体中包含了物理内存的起始地址和大小、设备树的地址、文件系统根目录、驱动程序的初始化函数列表等。 而U-boot中的gd结构体是指全局数据结构(global data structure),用于存储与启动过程相关的全局信息。U-boot是一种开源的嵌入式系统引导加载器,用于加载运行操作系统或其他应用程序。gd结构体存储了关于启动和引导过程的重要信息,如内存布局、启动设备、命令行参数、环境变量等。gd结构体是在U-boot启动时动态创建的,通过它可以在运行过程中获取和设置各种全局变量,以支持系统的引导和启动流程。 无论是在Linux还是U-boot中,gd结构体都扮演着重要的角色,它提供了一个全局数据存储空间,方便在系统运行过程中存储和访问各种全局变量。通过gd结构体,可以在系统的整个生命周期内记录和获取运行时的全局信息,为系统的正常运行和用户的操作提供了必要的数据支持。 ### 回答3: gd结构体是Linux内核中的一个重要数据结构,它在内核初始化期间被创建,用于管理全局数据。它定义在头文件"include/linux/gd.h"中。gd结构体包含了许多字段,下面是一些重要的字段及其功能: 1. flags:包含了一些标志位,用于表示一些系统状态信息,比如是启动时的标志。 2. env_addr:存储着环境变量的起始地址。 3. env_valid:标志着环境变量的有效性,为1时表示有效。 4. ram_base、ram_size:存储着内存的起始地址和大小。 5. arch:用于存储体系结构相关的信息。 6. baudrate:串口通信的波特率。 7. bootfile:引导该系统的启动文件名。 8. boot_device:标志着启动设备的类型和编号。 9. lcd_color_index:存储了LCD显示颜色索引。 除了上述字段外,gd结构体还包含了一些用于存储引导时加载的设备树、已加载内核的前一级地址以及其他一些系统配置信息的字段。 而在U-boot中,也有类似的gd结构体用于管理全局数据。U-boot的gd结构体提供了与Linux内核中的gd结构体类似的功能,并且额外包含一些U-boot特有的字段。这些字段用于记录U-boot启动的相关信息,比如启动设备类型和编号、启动设备的起始地址、U-boot的启动参数等。 总的来说,gd结构体在Linux和U-boot中是非常重要的数据结构,它们提供了全局数据的管理和存储,方便操作系统和引导程序进行相关的启动和配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值