结构体的概念
前面的教程中我们讲解了变量和数组(array),变量是单独的一个一个的定义,数组是一组具有相同类型的变量的集合。但在实际的工作和生活中,我们往往还需要一组类型不同的数据,例如超女基本信息,姓名为字符串,身高和年龄为整数,身材和颜值为字符串,因为各种信息的数据类型不同,不能用一个数组来存放。
如果某对象的信息有100个属性,并且每个属性的数据类型不同,就要定义100个变量,对100个变量初始化,把100个变量作为函数的参数传递,太麻烦了。
在C语言中,使用结构体(struct)来存放一组不同类型的数据。结构体的定义形式为:
结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(member)。以超女为例:
结构体成员的定义方式与变量和数组的定义方式相同。
注意大括号}后面的分号;不能少,这是一条完整的语句。
结构体是一种程序员自己定义的数据类型,可以包含多个其他类型的数据,可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
结构体变量
结构体是一种程序员自定义的数据类型,是模板,可以用它来定义变量。例如:
struct st_girl queen, princess, waiting, workers;
定义了四个结构体变量,queen王后、princess王妃,waiting宫女和workers杂役。
占用内存的情况
理论上讲结构体的各个成员在内存中是连续存储的,和数组非常类似,但是,结果体的占用内存的总大小不一定等于全部成员变量占用内存大小的和。在编译器的具体实现中,为了提高内存寻址的效率,各个成员之间可能会存在缝隙。
用sizeof可以得到结构体占用内容在总大小。
sizeof(struct st_girl); 或 sizeof(queen); 都可以。
示例(book90.c)
运行结果
从上面的示例可以看出,struct st_girl全部成员变量占用的内存是50+4+4+30+30=118,但是结构体占用的内存是120。
注意,C语言提供了结构体成员内存对齐的方法,可以使结构体成员变量之间的内存没有空隙,我暂时不介绍。
结构体的变量名
结构体变量名不是结构体变量的地址,结构体变量名就是变量名,就象int ii一样,只是不能直接输出,直接输出没有意义。取地址要用&,不用钻牛角尖,不直接输出就行了。
struct st_girl stgirl;
printf("%d\n",stgirl); // 没有意义。
printf("%p\n",stgirl); // 没有意义,结构体变量名不是结构体变量的地址。
printf("%p\n",&stgirl); // 这才是结构体的地址。
结构体初始化
采用memset函数初始化结构体,全部成员变量的值清零。
memset(&queen,0,sizeof(struct st_girl));
或
memset(&queen,0,sizeof(queen));
成员的访问和使用
为了访问结构的成员,我们使用圆点.运算符。
结构体成员变量的使用与其它变量的使用相同。
示例(book92.c)
运行结果
结构体数组
结构体可以被定义成数组变量,本质上与其它类型的数组变量没有区别。
struct st_girl princess[6];
memset(princess,0,sizeof(princess));
strcpy(princess[0].name,"杨玉环");
princess[0].age=18;
strcpy(princess[1].name,"西施");
princess[1].age=28;
……
在实际开发中,我们很少用结构体数组,C++标准库的vector容器可完全取代结构体数组。
结构体指针
结构体是一种自定义的数据类型,结构体变量也是内存变量,也有内存地址,当然也就有指针。
在指针章节中我们已经学习过,采用不同数据类型的指针指向不同数据类型的变量的地址,这一规则也适用于结构体。如下:
struct st_girl queen;
struct st_girl *pst=& queen;
通过结构体指针可以使用结构体成员,一般形式为:
(*pointer).memberName
或者:
pointer->memberName
第一种写法中,圆点.的优先级高于*,(*pointer)两边的括号不能少。如果去掉括号写作*pointer.memberName,那么就等效于*(pointer.memberName),这样意义就完全不对了。
第二种写法中,->是一个新的运算符,习惯称它为“箭头”,有了它,可以通过结构体指针直接使用结构体成员;这也是->在C语言中的唯一用途。
上面的两种写法是等效的,我们通常采用后面的写法,这样更加直观。
示例(book93.c)
运行结果
结构体的复制
在C语言中,结构体的成员如果是基本数据类型(int、char、double)可以用=号赋值,如果是字符串,字符串不是基本数据类型,可以用strcpy函数赋值,如果要把结构体变量的值赋给另一个结构体变量,有两种方法,一种是把结构体变量成员的值逐个赋值给另一个结构体变量的成员,还有一种方法是内存拷贝,C语言提供了memcpy(memory copy的简写)实现内存拷贝功能。
函数声明:
void *memcpy(void *dest, const void *src, size_t n);
参数说明:
src 源内存变量的起始地址。
dest 目的内存变量的起始地址。
n 需要复制内容的字节数。
函数返回指向dest的指针,函数的返回值意义不大,程序员一般不关心这个返回值。
示例(book94.c)
运行结果
大家可能想起了strcpy函数,与memcpy有相似之处,实际上这两个函数从功能和实现原理上完本不同,甚至不应该放在一起比较。
1)复制的内容不同,strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2)用途不同,通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
3)复制的方法不同,strcpy不需要指定长度,它遇到被复制字符的串结尾符0才结束,memcpy则是根据其第3个参数决定复制的长度。
结构体作为函数的参数
结构体是多个变量集合,作为函数参数时就可以传递整个集合,也就是所有成员。如果结构体成员较多,函数参数的初始化和赋值的开销会很大,影响程序的运行效率。所以最好的办法就是传递结构体变量的地址。
示例(book95.c)
枚举和共同体
C语言还有两种数据结构:枚举和共同体,这两种数据结构的应用太少了,少到我已经记不起它们的定义,二十年来,我从未使用过,也不介绍了。
介绍两个库函数
1、memset函数
memset 函数是内存空间赋值函数,用来给某一块内存空间进行赋值的。
包含在<string.h>头文件中。
函数的声明如下:
void *memset(void *s, int v, size_t n);
s为内存空间的地址,一般是数组名或结构体的地址。
v为要填充的值。
n为要填充的字节数。
程序员不关心memset的返回值。
在实际开发中,程序员用memset函数对数组或结构体清零,在之前的章节中,我们已经用过很多次了。
2、bzero函数
bzero函数是内存空间清零。
包含在<string.h>头文件中。
函数的声明如下:
void bzero(void *s, size_t n);
s为内存空间的地址,一般是数组名或结构体的地址。
n为要清零的字节数。
如果要对数组或结构体清零,用memset和bzero都可以,没什么差别,看程序员的习惯。
版权声明
C语言技术网原创文章,转载请说明文章的来源、作者和原文的链接。
来源:C语言技术网(www.freecplus.net)
作者:码农有道
如果这篇文章对您有帮助,请点赞支持,或在您的博客中转发我的文章,谢谢!!!
如果文章有错别字,或者内容有误,或其他的建议或意见,请您留言指正,非常感谢!!!