本节来讨论c语言主要的数据结构,包括数组、结构体、共用体、枚举。首先从数组开始。
1、数组
数组为同一类型的基本数据的集合。我们知道,基本数据类型都占据一定的内存空间,基本数据类型的定义,实际上就是内存地址的分配过程,同样的,数组作为基本数据类型的集合,在创建时也必须进行内存的分配。以下都为正确的数组创建方法:
int num1[4]={0,1,2,3};
int num2[4]={0,1};
int num3[4]={[2]=2,[3]=3};
int num4[]={0,1,2,3};
int n=3;int num5[n];
int num6[0];
错误的示例:
int num5[]; // 未明确范围
int num6={0,1,2,3}; // 格式不对
int num7[];num7={0,1,2,3}; // 整体赋值必须在定义时赋值
int n=3;int num8[n]={0,1,2,3}; // 错误
int num9[-1]; // 没有负数个
特殊的数组:
a、字符数组(字符串)
在c语言中通过字符数组的方式表示字符串,字符串的结尾不是字符数组的结尾,而是字符数组中出现的第一个‘\0’。
char str[]={'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0'};
printf("%s\n", str);
也可以采用下面的方式创建(这两者不等价):
char *str="Hello World!";
printf("%s\n", str);
b、多维数组
有的时候采用单一的数组也可能满足不了需求,这时候就需要用到多维数组,他的定义和普通的数组完全一样,只不过他的每一个元素本身都是一个数组。我们来看一下多维数组的存储方式:
int a[2][2]={{1,2},{1,2}};
printf("a[0][0]: %p \n", &a[0][0]);
printf("a[0][1]: %p \n", &a[0][1]);
printf("a[1][0]: %p \n", &a[1][0]);
printf("a[1][1]: %p \n", &a[1][1]);
输出结果:
a[0][0]: 0x7fff5fbff8c0
a[0][1]: 0x7fff5fbff8c4
a[1][0]: 0x7fff5fbff8c8
a[1][1]: 0x7fff5fbff8cc
从上述结果中可以看出,多维数组和普通数组的存储方式完全一样,也是按次序摆放,只是调用的方式略有不同。
2、结构体
在数组中,所用元素的类型都必须是相同的,为了满足更个性化的需要,就出现了结构体,他是一种类似数组的结构,只不过其内部存储的变量不是单一的数据类型,可以由多种数据类型组成,甚至可以是某个函数。为了表示结构体的这种任意的特性,结构体需要开发者自己编写结构格式,典型结构体的格式定义如下(要放在主函数的前面):
struct student {
int number; //学号
int age; //年龄
double height; //身高
char name[8]; //姓名
}; //注意分号
该操作只是定义了一个名称为student的框架,并没有实际分配内存。
为了分配内存,我们需要对student结构体进行实例化:
struct student stu; // 获得student结构体实例stu
// 地址及大小输出
printf("stu 地址:%p 大小: %lu Byte \n", &stu, sizeof(stu));
printf("stu.number 地址:%p 大小: %lu Byte \n", &stu.number, sizeof(stu.number));
printf("stu.age 地址:%p 大小: %lu Byte \n", &stu.age, sizeof(stu.age));
printf("stu.height 地址:%p 大小: %lu Byte \n", &stu.height, sizeof(stu.height));
printf("stu.name 地址:%p 大小: %lu Byte \n", &stu.name, sizeof(stu.name));
在上面的方法中,我们通过点语法访问结构体实例的内部变量,并获取各变量的地址,输出结果:
stu 地址:0x7fff5fbff8c0 大小: 24 Byte
stu.number 地址:0x7fff5fbff8c0 大小: 4 Byte
stu.age 地址:0x7fff5fbff8c4 大小: 4 Byte
stu.height 地址:0x7fff5fbff8c8 大小: 8 Byte
stu.name 地址:0x7fff5fbff8d0 大小: 8 Byte
可以看出,在结构体实例中,内存的分配是按照内部变量的次序依次排列的,结构体的地址为第一个变量的地址,这和前面的结论一致。
3、共用体
共用体和结构体的创建方法基本相同,所不同的是他的内部变量公用相同的存储单元。为什么要这样做呢?是因为并不是所有的硬件都有足够的内存供我们使用,在有些场合下,一个“结构体”的内部变量同时只用到一个,这时候使用共用体将大大节省内存开支。此外,在通信领域,有时候在接受数据时无法确定数据的类型,先把数据存储下来,然后再根据类型对应读取,举例如下:
创建共用体框架(同样要放在主函数前面):
union reciveData {
int number; // 数字
char character; // 字符
};
实例化及验证:
union reciveData data; // 实例化
// 地址及大小输出
printf("data 地址:%p 大小: %lu Byte \n", &data, sizeof(data));
printf("data.number 地址:%p 大小: %lu Byte \n", &data.number, sizeof(data.number));
printf("data.character 地址:%p 大小: %lu Byte \n", &data.character, sizeof(data.character));
输出结果:
data 地址:0x7fff5fbff8d8 大小: 4 Byte
data.number 地址:0x7fff5fbff8d8 大小: 4 Byte
data.character 地址:0x7fff5fbff8d8 大小: 1 Byte
由输出结果可以看出,共用体实例的存储空间为共用体实例内部最大元素的尺寸,各元素的地址相同,都等于共用体的地址。
4、枚举
枚举是一种特殊的数据结构,他将某些固定的数值表示成有意义的标识符,以提高程序的可读性。
枚举和上面的结构体和共用体类似,也需要在主函数前面声明其框架,例如:
enum {spring, summer, autumn, winter} season; //不含类型标签,定义单一的变量
enum color_led {red, green, blue}; //含类型标签,可定义多个变量
这两个枚举分别定义了四季和LED灯的三种状态,不同的是,第一个例子中没有“框架名称”,直接声明了单一的枚举,其标识符为season。第二个例子中有“框架名称“color_led”,我们可以通过此名称创建多个相似的框架。注意,这里并没有分配空间!如果你尝试通过&指令输出地址的话,会报错!
第一个例子的输出:
printf("spring = %d\n", spring);
printf("summer = %d\n", summer);
printf("autumn = %d\n", autumn);
printf("winter = %d\n", winter);
结果为:
spring = 0
summer = 1
autumn = 2
winter = 3
可见,这些标识符在真正运行的阶段还是以数字的形式进行流通,只是我们给他们换了一个叫发,方便了编程工作及思路的整理。顺便一提的是,我们可以在定义枚举的时候人为的改变这些默认值,方法如下:
enum {
spring,
summer = 2,
autumn,
winter
} season;
此时,summer之后的数据根据summer依次递加,前面的保持不变,输出如下:
spring = 0
summer = 2
autumn = 3
winter = 4
5、总结
合理运用以上几种复杂数据类型,将使我们的程序变得完善和规范,同时便于我们思路的理清。务必弄清楚这几种数据的使用。