目录
复杂数据类型
单一的数据类型无论面对底层开发还是应用开发都是不够用的,我们常常需要处理更为复杂的数据类型,例如一个学生拥有姓名、性别、年龄等内容,这些数据类型不同,也不使用索引来存取。对于高级语言来说复杂数据类型可以用类描述,对于面向过程语言,C提供了结构体、共用体这样的复杂数据类型来描述,和数组一样他们描述一段用于储存数据的内存空间。
结构体
结构体这种数据类型有很多称呼,有的称关联数组,有的称为构造型数组,在结构体中可以定义成员名称并设置值,有的书本称为值对,把成员名称为“键”,成员值称为“键值”,一个结构体的定义格式如下:
struct 类型名称
{
属性1;
属性2;
…
};
注意结构体最后有一个分号,定义了结构体后就可以使用结构体类型定义变量了,例如:
#include<stdio.h>
struct Student
{
char name[20];
int age;
int sex;
};
int main(int argc, char* argv[])
{
struct Student s1={
"xiaoming",5,0};
printf("s1:%s,%d,%d",s1.name, s1.age, s1.sex);
return 0;
}
定义结构体和用结构体声明变量时都必须书写关键字struct,struct Student是一个整体。结构体类型可以在函数外声明也可以在函数内声明,在函数内声明的结构体类型外部不能识别,因此没有通用性,大部分情况下我们都会在文件顶部声明结构体。如果想节省代码,可以在定义时同时声明变量,例如:
struct Student
{
char name[20];
int age;
int sex;
} s1,s2;
甚至可以没有名称,例如:
struct
{
char name[20];
int age;
int sex;
} s1,s2;
这种匿名方式不能在其它地方定义变量,但可以使用这种方式定义别名,关于别名我们在后面的章节中讲解。
初始化结构体
不能在定义结构体的内部为成员指定初始值,因为我们现在是在定义数据类型而不是声明变量,初始值只能在声明变量时指定,比如这个例子中的:
struct Student s1={“xiaoming”,5,0};
对于结构体和联合体,也可以在初始化时通过属性指定值,例如:
(struct Point){.x=3, .y=5};
显示指定属性好处是可以按任意顺序赋值,这种方式在联合体中特别有用,因为需要告诉编译器该值指派給哪个成员类型。当然我们也可以选择定义变量后再通过s1.name=”xiaoming”初始化,显然这种方式比较麻烦,适合修改成员内容。
结构体指针和成员的引用
在讲结构体指针之前我们必须明确一个概念,那就是C标准对结构体的设定,初学者很容易认为结构体就是高级语言中的类模板,或者跟数组一样是一个数据集合,也容易认为结构体变量名是第一个成员的地址或者是数据集合入口地址,这样就犯了原始性的概念错误,产生这样的错觉一是因为结构体拥有成员,很容易将它看作数据集合或类模板,二是被关联数组、构造型数组这样的名称迷惑了,实际上C标准并不将结构体看作为数据集合,更不会引入面向对象的概念将它当作类模板,而是将结构体定义为一个原始的复杂数据类型,在很多方面处理结构体的方式和基本数据类型一样,如下:
-
结构体名称仅代表数据类型
假如我们用struct Student声明一个结构体变量s1,s1的类型为struct Student,这个类型如同int,float,仅用于解释s1所占的内存区域的数据类型 -
除非使用const,否则用结构体类型声明的是变量而不是常量,这个变量可以被赋值,且赋值时也是按值传递的,我们用下面的代码来验证一下:
#include<stdio.h>
#include<string.h>
struct Student
{
char name[20];
int age;
int sex;
};
int main(int argc, char* argv[])
{
struct Student s1={
"xiaoming",5,0};
struct Student s2=s1;
strcpy(s2.name,"xiaohong");
s2.age=4;
s2.sex=1;
printf("s1:%s,%d,%d\n",s1.name, s1.age, s1.sex);
printf("s1:%s,%d,%d",s2.name, s2.age, s2.sex);
return 0;
}
从结果可以看出,修改s2后s1并未发生变化,同样将结构体变量作为函数参数时也是按值传递的,修改函数参数并不会对原来的变量内容产生影响,如果觉得克隆结构体花费的代价较大,可以将&s1传入函数。
- 结构体名称和变量名都不表示地址
有人会想,基本数据类型变量名表示某内存的值,数组名表示首元素的地址,那么结构体名和结构体变量名表示什么呢?和基本数据类型一样,结构体名和int,float一样是数据类型,变量名则是某段内存的值,它们都不表示地址。要说区别,那就是基本数据类型变量和数组名都可以通过printf()输出,而结构体变量却不能输出,因为printf()不支持结构体输出,如果用printf(“%d”, s1)输出结构体变量名会得到什么结果呢?如果使用"%d",printf()会将结构体所在内存的前几个字节当作整数输出,而且还会给出类型不匹配的警告,如果用"%p"会将结构体前几个字节当作内存地址输出,所以说用printf()输出结构体本身是没有意义的。在C语言中,结构体变量只能用于赋值、取址,用sizeof()求大小,或者通过它访问其成员,尝试把结构体变量转化为整数、字符串、地址等操作均会失败,而结构体名如同int, float一样只能用于类型判断或用sizeof()计算大小。
了解结构体的概念后我们来看看结构体指针,当我们声明一个结构体指针时,指针类型为结构体类型加上*号,其值是用&号对结构体变量取址,例如:
struct Student *p=&s1;
这和基本数据类型的声明方式是一样的,对于存放结构体的内存来说,理论上各成员在内存中应该是连续存放的,但在编译器的具体实现中成员之间可能存在缝隙,因此结构体内部对编写者来说并不透明,所占空间大小不一定是各个成员大小的和,它的实际大小应该用sizeof()求出,也因为这个原因我们不能通过指针位移来获取成员值。但即便成员之间有间隙,结构体所在内存的起始位置仍然存放第一个成员的内容,因此结构体变量的地址和第一个成员的地址相同,但是类型不一样。在上面这个例子中,第一个成员为字符串,对于字符串来说,数组名的地址、数组名和第一个字符的地址是相同的,不同的是数据类型,因此对于上面的例子,&s1, p, &p.name, p.name以及&p.name[0]的地址都是相同的,它们都是这段内存数据的起始地址,用一段代码进行测试:
#

本文详细介绍了C语言中的复杂数据类型,如结构体、初始化、结构体指针、结构体作为函数参数、结构体数组、链表、共用体、枚举以及嵌套匿名。结构体和共用体在内存使用上有所不同,结构体成员可以是各种类型,并通过指针和成员引用进行访问。枚举提供了一种限制变量取值范围的方法,提高代码可读性。伸缩型数组成员是C99引入的新特性,允许结构体中包含动态大小的数组。这些数据类型在底层开发和数据组织中起到重要作用。
最低0.47元/天 解锁文章
1067

被折叠的 条评论
为什么被折叠?



