第四章的主题是复合类型,前面主要介绍了数组、结构、共同体和枚举,后面介绍了指针。
本来挺奇怪,为什么把指针也当作复合数据类型呢?
我们知道,地址是某个内存单元(一般是字节)在内存空间中的编号,通过这个编号(地址)可以找到该空间(字节)。那么它应该是一个基础类型才对,实则不然。
只知道地址,然后去访问那个字节空间,没有问题,但是我们的存储对象,根据类型的不同,往往占用多个字节。比如int a变量,到底读取几个字节,然后解释它们,获得a变量的值?这就要求在定义指针时必须声明该指针指向的类型,是int、double、还是某个结构类型,甚至是它们的组合,如果int数组。指针还可以指向函数。
所以指针类型,必须加上它的修饰符,才构成一个类型:int* 、double*、struct xxx *都是不同。函数指针,比如:int (*f)(int, int),表明f是一个指向形参为两个int型,返回值也是int的函数。对f赋值时,也必须符合它的所有条件,除非加上强制类型转换,否则会报错。
因此,说指针是复合类型在逻辑上说得通。
既然指针对象存在某个地址值,那么该地址指向的内存空间,在什么位置,往往比较讲究了。
变量或对象,存储位置大体分这么几个地方:寄存器、栈帧、堆或静态。熟悉这几个概念就涉及计算机的内存空间模型。局部变量一般存在寄存器或栈帧位置,通过malloc或new动态分配的空间在堆上,而常量或字符串一般存储在静态区。它们又和对象的生命周期、访问权限有关联。
局部变量当声明的函数结束后,它的空间也被回收了。静态区的生命周期是和程序一样的,而堆上存储的对象的生命周期,是由程序员控制的。使用不当就造成“内存泄露”。
除了数组外,还介绍了vector,它是动态分配空间的,可以在运行时决定存储对象的长度。因此它不像定义数组,必须在编译器就决定数组长度,否则编译不通过。当vector对象结束生命周期时,会自动调用它的析构函数,释放它在堆上占用的空间。