结构体使用方法大全(定义,内存大小,初始化,结构数组,结构指针(1)

int num;
char name[10];
char sex;
}stus[20];

struct Student students[3]={{0001, “Zhang San”,‘M’},
{0002, “Li Si”,‘W’},
{0003, “Zhao Liu”,‘M’}
};

struct Student
{
int num;
char name[10];
char sex;
}students[3]={ {0001, “Zhang San”,‘M’},
{0002, “Li Si”,‘W’},
{0003, “Zhao Liu”,‘M’}};

struct city{
char name[20];
double population;
double housing;
double ave;
};
city cities[2];
cities[0]={“A”,10000,11};
cities[1]={“B”,20000,11};

为什么不行啊? city cities[2]={{“A”,10000,11},{“B”,20000,11}}; 我知道这种赋值方法,但我就是想问问什么上面那种不行?别的数组是可以的啊

并不是aleda所说的原因,你可以试一下,即便不要结构体不要字符串

struct city
{
double population;
double housing;
double ave;
};
city cities[2];
cities[0]={22,10000,11};
cities[1]={22,20000,11};

这里与结构体匹配了,但是同样不可能通过
原因是C\C++语法是禁止结构体这样赋值的!!

你提到了初始化,的确结构体数组初始化是可以通过大括号这种形式进行初始化的,但是一定要注意

类似这样的语句,不是初始化,而是重新赋值!
cities[0]={“A”,10000,11};
cities[1]={“B”,20000,11};

结构体数组的初始化在city cities[2]这一句的时候就完成了(初始化了cities[0]和cities[1]这两个元素)

所以正确的初始化格式应该如下:

city cities[2] = {{“a”, 1,1,1}, {“b”, 1,1,1}}; // 还要注意一点,你问题处的成员少给了一个,结构体里有三个double,你只给了两

记住一点:

初始化永远是在定义的时候完成的,(或者如果是类的成员变量,其初始化是在构造函数的初始化列表中完成,如果你学的是C,可以忽略)

非定义处的只有重新赋值,没有初始化!

五、结构指针

前面讲过,&student1 表示结构体变量 student1 的首地址,即 student1 第一个项的地址。如果定义一个指针变量 p 指向这个地址的话,p 就可以指向结构体变量 student1 中的任意一个成员。

那么,这个指针变量定义成什么类型呢?只能定义成结构体类型,且指向什么结构体类型的结构体变量,就要定义成什么样的结构体类型。比如指向 struct STUDENT 类型的结构体变量,那么指针变量就一定要定义成 struct STUDENT* 类型。

下面将前面的程序用指针的方式修改一下:

include <stdio.h>

include <string.h>

struct AGE
{
int year;
int month;
int day;
};
struct STUDENT
{
char name[20]; //姓名
int num; //学号
struct AGE birthday; //生日
float score; //分数
};
int main(void)
{
struct STUDENT student1; /*用struct STUDENT结构体类型定义结构体变量student1*/
struct STUDENT *p = NULL; /*定义一个指向struct STUDENT结构体类型的指针变量p*/
p = &student1; /*p指向结构体变量student1的首地址, 即第一个成员的地址*/
strcpy((*p).name, “小明”); //(*p).name等价于student1.name
(*p).birthday.year = 1989;
(*p).birthday.month = 3;
(*p).birthday.day = 29;
(*p).num = 1207041;
(*p).score = 100;
printf(“name : %s\n”, (*p).name); //(*p).name不能写成p
printf(“birthday : %d-%d-%d\n”, (*p).birthday.year, (*p).birthday.month, (*p).birthday.day);
printf(“num : %d\n”, (*p).num);
printf(“score : %.1f\n”, (*p).score);
return 0;
}

输出结果是:
name : 小明
birthday : 1989-3-29
num : 1207041
score : 100.0

我们看到,用指针引用结构体变量成员的方式是:

(*指针变量名).成员名

注意,p 两边的括号不可省略,因为成员运算符“.”的优先级高于指针运算符“”,所以如果 *p 两边的括号省略的话,那么 *p.num 就等价于 *(p.num) 了。

从该程序也可以看出:因为指针变量 p 指向的是结构体变量 student1 第一个成员的地址,即字符数组 name 的首地址,*所以 p 和 (p).name 是等价的

但是,“等价”仅仅是说它们表示的是同一个内存单元的地址,但它们的类型是不同的。指针变量 p 是 struct STUDENT* 型的,而 (p).name 是 char 型的。所以在 strcpy 中不能将 (*p).name 改成 p。用 %s 进行输入或输出时,输入参数或输出参数也只能写成 (*p).name 而不能写成 p。

同样,虽然 &student1 和 student1.name 表示的是同一个内存单元的地址,但它们的类型是不同的。&student1 是 struct STUDENT* 型的,而 student1.name 是 char* 型的,所以在对 p 进行初始化时,“p=&student1;”不能写成“p=student1.name”。因为 p 是 struct STUDENT* 型的,所以不能将 char* 型的 student1.name 赋给 p。

此外为了使用的方便和直观,用指针引用结构体变量成员的方式:(*指针变量名).成员名

可以直接用:指针变量名->成员名来代替,它们是等价的。“->”是“指向结构体成员运算符”,它的优先级同结构体成员运算符“.”一样高。p->num 的含义是:指针变量 p 所指向的结构体变量中的 num 成员。p->num 最终代表的就是 num 这个成员中的内容。

下面再将程序用“->”修改一下:

include <stdio.h>

include <string.h>

struct AGE
{
int year;
int month;
int day;
};
struct STUDENT
{
char name[20]; //姓名
int num; //学号
struct AGE birthday; /*用struct AGE结构体类型定义结构体变量birthday, 生日*/
float score; //分数
};
int main(void)
{
struct STUDENT student1; /*用struct STUDENT结构体类型定义结构体变量student1*/
struct STUDENT *p = NULL; /*定义struct STUDENT结构体类型的指针变量p*/
p = &student1; /*p指向结构体变量student1的首地址, 即第一项的地址*/
strcpy(p->name, “小明”);
p->birthday.year = 1989;
p->birthday.month = 3;
p->birthday.day = 29;
p->num = 1207041;
p->score = 100;
printf(“name : %s\n”, p->name); //p->name不能写成p
printf(“birthday : %d-%d-%d\n”, p->birthday.year, p->birthday.month, p->birthday.day);
printf(“num : %d\n”, p->num);
printf(“score : %.1f\n”, p->score);
return 0;
}

输出结果是:
name : 小明
birthday : 1989-3-29
num : 1207041
score : 100.0

但是要注意的是,只有“指针变量名”后面才能加“->”,千万不要在成员名如 birthday 后面加“->”。
综上所述,以下 3 种形式是等价的:

  • 结构体变量.成员名。
  • (*指针变量).成员名。
  • 指针变量->成员名。

其中第 3 种方式很重要,通常都是使用这种方式,另外两种方式用得不多。后面讲链表的时候用的也都是第 3 种方式。

指向结构体数组的指针

在前面讲数值型数组的时候可以将数组名赋给一个指针变量,从而使该指针变量指向数组的首地址,然后用指针访问数组的元素。结构体数组也是数组,所以同样可以这么做。

我们知道,结构体数组的每一个元素都是一个结构体变量。如果定义一个结构体指针变量并把结构体数组的数组名赋给这个指针变量的话,就意味着将结构体数组的第一个元素,即第一个结构体变量的地址,也即第一个结构变量中的第一个成员的地址赋给了这个指针变量。比如:

include <stdio.h>

struct STU
{
char name[20];
int age;
char sex;
char num[20];
};
int main(void)
{
struct STU stu[5] = {{“小红”, 22, ‘F’, “Z1207031”}, {“小明”, 21, ‘M’, “Z1207035”}, {“小七”, 23, ‘F’, “Z1207022”}};
struct STU *p = stu;
return 0;
}

此时指针变量 p 就指向了结构体数组的第一个元素,即指向 stu[0]。我们知道,当一个指针指向一个数组后,指针就可以通过移动的方式指向数组的其他元素。

这个原则对结构体数组和结构体指针同样适用,所以 p+1 就指向 stu[1] 的首地址;p+2 就指向 stu[2] 的首地址……所以只要利用 for 循环,指针就能一个个地指向结构体数组元素。

同样需要注意的是,要将一个结构体数组名赋给一个结构体指针变量,那么它们的结构体类型必须相同。

下面编写一个程序:

include <stdio.h>

struct STU
{
char name[20];
int age;
char sex;
char num[20];
};
int main(void)
{
struct STU stu[3] = {{“小红”, 22, ‘F’, “Z1207031”}, {“小明”, 21, ‘M’, “Z1207035”}, {“小七”, 23, ‘F’, “Z1207022”}};
struct STU *p = stu;
for (; p<stu+3; ++p)
{
printf(“name:%s; age:%d; sex:%c; num:%s\n”, p->name, p->age, p->sex, p->num);
}
return 0;
}

输出结果是:
name:小红; age:22; sex:F; num:Z1207031
name:小明; age:21; sex:M; num:Z1207035
name:小七; age:23; sex:F; num:Z1207022

此外同前面“普通数组和指针的关系”一样,当指针变量 p 指向 stu[0] 时,p[0] 就等价于 stu[0];p[1] 就等价于 stu[1];p[2] 就等价于 stu[2]……所以 stu[0].num 就可以写成 p[0].num,其他同理。下面将上面的程序用 p[i] 的方式修改一下:

include <stdio.h>

struct STU
{
char name[20];
int age;
char sex;
char num[20];
};
int main(void)
{
struct STU stu[3] = {{“小红”, 22, ‘F’, “Z1207031”}, {“小明”, 21, ‘M’, “Z1207035”}, {“小七”, 23, ‘F’, “Z1207022”}};
struct STU *p = stu;
int i = 0;
for (; i<3; ++i)
{
printf(“name:%s; age:%d; sex:%c; num:%s\n”, p[i].name, p[i].age, p[i].sex, p[i].num);
}
return 0;
}

输出结果是:
name:小红; age:22; sex:F; num:Z1207031
name:小明; age:21; sex:M; num:Z1207035
name:小七; age:23; sex:F; num:Z1207022

六、位域

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

  • 位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:

struct 位域结构名
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度
例如:

struct bs
{
int a:8;
int b:2;
int c:6;
};

位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:

说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:

struct bs
{
int a:8;
int b:2;
int c:6;
}data;

一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:

struct bs
{
unsigned a:4
unsigned :0 /*空域*/
unsigned b:4 /*从下一单元开始存放*/
unsigned c:4
};

在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。

位域的长度不能大于一个int的长度,也就是说不能超过32位。
位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:

struct k
{
int a:1
int :2 /*该2位不能使用*/
int b:3
int c:2
};

从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。

位域的使用

位域的使用和结构成员的使用相同,其一般形式为: 位域变量名·位域名 位域允许用各种格式输出。

上例程序中定义了位域结构bs,三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。
程序的9、10、11三行分别给三个位域赋值。( 应注意赋值不能超过该位域的允许范围)程序第12行以整型量格式输出三个域的内容。第13行把位域变量bit的地址送给指针变量pbit。第14行用指针 方式给位域a重新赋值,赋为0。第15行使用了复合的位运算符"&=“, 该行相当于: pbit->b=pbit->b&3位域b中原有值为7,与3作按位与运算的结果为3(111&011=011,十进制值为 3)。同样,程序第16行中使用了复合位运算”|=", 相当于: pbit->c=pbit->c|1其结果为15。程序第17行用指针方式输出了这三个域的值。
为了节省空间,可以把几个数据压缩到少数的几个类型空间上,比如需要表示二个3位二进制的数,一个2位二进制的数,则可以用一个8位的字符表示之。

这个结构体所占空间为一个字节,8位。节省了空间。

struct
{
char a : 3;
char b : 3;
char c : 2;
};

这个结构体所占空间为一个字节,8位。节省了空间。

main(){
struct bs
{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf(“%d,%d,%d\n”,bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf(“%d,%d,%d\n”,pbit->a,pbit->b,pbit->c);
}

这个结构体所占空间为一个字节,8位。节省了空间。

位域可以和union结合起来一起用会更加方便

union hhh
{
char x;//注意位与外面变量占的字节大小要相同比较好。
struct
{
char a : 3;
char b : 3;
char c : 2;
}bitvar;
};

七、结构体和联合体的嵌套

嵌套可以有这些情况:结构体中嵌套结构体、结构体中嵌套联合体、联合体中嵌套联合体、联合体中嵌套结构体。

下面就一一对上面的嵌套形式进行说明,并讲解这些嵌套形式的使用方法。

结构体中嵌套结构体,有点像c++中类的继承,应该是使用的比较常见的。

C语言中的嵌套结构体表示在一个结构体中可以使用另一个结构作为成员。在C语言中定义结构体嵌套有两种方法:

  1. 通过独立的结构
  2. 通过嵌入式结构

独立结构

我们可以创建2个结构体,但在主结构中应该使用依赖其它结构体作为成员。我们来看看嵌套结构体的代码。

struct Date
{
int dd;
int mm;
int yyyy;
};
struct Employee
{
int id;
char name[20];
struct Date doj; // 嵌套一个结构体:Date,用于存储日期
}emp1;

如上所见,doj(加入日期)是Date类型的变量。 这里doj用作为Employee结构体中的成员。通过这样的方式,我们可以在其他结构体中使用Date结构。

嵌入式结构体

我们可以在结构体内定义结构体,它比第一种方式需要更少的代码。但它不能用于其它结构中。

struct Employee
{
int id;
char name[20];
struct Date
{
int dd;
int mm;
int yyyy;
}doj;
}emp1;

访问嵌套结构体

我们可以通过Outer_Structure.Nested_Structure.member访问嵌套结构的成员,如下所示:

e1.doj.dd
e1.doj.mm
e1.doj.yyyy
C

嵌套结构体示例

我们来看看C语言中嵌套结构体的一个简单例子。创建一个源代码文件:nested-structure.c,其代码如下

#include <stdio.h>
#include <string.h>
struct Employee
{
int id;
char name[20];
struct Date
{
int dd;
int mm;
int yyyy;
}doj;
}e1;

int main()
{
//storing employee information
e1.id = 1001;
strcpy(e1.name, “Maxsu”);//copying string into char array
e1.doj.dd = 12;
e1.doj.mm = 11;
e1.doj.yyyy = 2018;

//printing first employee information
printf(“employee id : %d\n”, e1.id);
printf(“employee name : %s\n”, e1.name);
printf(“employee date of joining (dd/mm/yyyy) : %d/%d/%d\n”, e1.doj.dd, e1.doj.mm, e1.doj.yyyy);
return 0;
}

执行上面示例代码,得到以下结果 -

employee id : 1001
employee name : Maxsu
employee date of joining (dd/mm/yyyy) : 12/11/2018

结构体中嵌套联合体,也就是在结构体中加入了一个联合体,也是比较常见的。

示例代码:

enum DATA_PKG_TYPE
{
DATA_PKG1 = 1,
DATA_PKG2,
DATA_PKG3
};

struct data_pkg1
{
// …
};

struct data_pkg2
{
// …
};

struct data_pkg3
{
// …
};

struct data_pkg
{
enum DATA_PKG_TYPE data_pkg_type;
union
{
struct data_pkg1 data_pkg1_info;
struct data_pkg2 data_pkg2_info;
struct data_pkg3 data_pkg3_info;
}data_pkg_info;
};

这里把struct data_pkg1、struct data_pkg2、struct data_pkg3三个结构体放到了struct data_pkg这个结构体里进行管理,把data_pkg_type与union里的三个结构体建立一一对应关系,我们需要用哪一结构体数据就通过data_pkg_type来进行选中。

在进行数据组包的时候,先给data_pkg_type进行赋值,确定数据包的类型,再给对应的union里的结构体进行赋值;在进行数据解析的时候,通过data_pkg_type来选择解析哪一组数据。

联合体中嵌套联合体

待补充。

联合体中嵌套结构体

typedef union
{
unsigned int u;
struct
{
unsigned char a :1;
unsigned char b :1;
unsigned char c :6;
unsigned char d :1;
} ST;
}UN;

  1. 注意联合体的定义,就是组成联合体的变量共用一个空间。这个 例子中变量u和ST共用一个空间
  2. 现在用的pc机大多为小段结构,我的结果13也是在小段机测试的,如果架构改变,结果可能不同
  3. 基于小段结构,数据的低字节保存在内存的低地址中,ST占用9位(Bit),与变量u(32Bit)共用低位的9位,根据小段结构,变量a的地址应该最低,往后依次是b,c,d
  4. un.u = 0; 执行这一步,变量所对应的空间二进制全部为0,即00000000 00000000 00000000 00000000
  5. un.ST.a = 1;执行这一步,变量最后一位变化,即00000000 00000000 00000000 00000001
  6. un.ST.b = 2;执行这一步,由于1位空间无法存储2,所以赋值被截断,原值不变
  7. un.ST.c = 3;执行这一步,变量第3-8位发生变化,变量值变为00000000 00000000 00000000 00001101

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-zMCUkg21-1713072267667)]
[外链图片转存中…(img-bhh9Rnpk-1713072267668)]
[外链图片转存中…(img-FbFjEnG3-1713072267669)]
[外链图片转存中…(img-OF4Rc9wP-1713072267669)]
[外链图片转存中…(img-g7A3YCAY-1713072267670)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-HKhSbNnq-1713072267671)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 12
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值