结构体的介绍-----C语言

前言:

结构体是C语言中的一种构造类型,它是一系列具有相同类型或者不同类型的数据所构成的数据集合。在生活中,每一个物品它都会有多种属性,例如某一本书,他会有作者、书名、定价,若想将这些属性都存储在一起,结构体就可以完成同时存入多个不同类型的变量。

1.结构体如何定义

结构体的定义形式是:

                              struct+结构体名称

                              {

                                     成员1;

                                     成员2;

                                     ·······;

                              };

接下来我们来定义一个结构体,里面包含学生的各种信息(姓名、学号、成绩)。

struct Stu
{
char name[20];
char XueHao[10];
int score;
};

注意:定义结构体类型本身并不占用内存空间,因为结构体类型定义只是告诉编译器如何表示数据,但没有让计算机为其分配空间。只有在创建结构体变量时,才会分配内存空间。

2.结构体变量如何创建以及初始化

1、如何创建结构体变量

1.在定义结构体的同时在创建结构体变量

struct Stu
{
char name[20];
char XueHao[10];
int score;
}s1,s2;

此时的s1、s2就是我们创建的变量。

2.在需要使用结构体是再创建结构体变量

struct Stu
{
char name[20];
char XueHao[10];
int score;
};

int main()
{
struct Stu s1,s2;
return 0;
}

这里的s1、s2同样也是结构体变量。

2、如何初始化结构体变量

下面我们来看一下结构体变量是如何初始化的:

struct Stu
{
char name[20];
char XueHao[10];
int score;
};

int main()
{
struct Stu s1 = {"Lisi","12306",90};
return 0;
}

 上面的这种初始化方式是必须按照结构体里变量顺序进行赋值,这里的"Lisi","12306",90就按照结构体里的变量顺序依次放进了 name,XueHao,score中。

struct Stu
{
char name[20];
char XueHao[10];
int score;
};

int main()
{
struct Stu s1 = {.score = 90,.XueHao = "12306",.name = "Lisi"};
return 0;
}
struct Stu
{
char name[20];
char XueHao[10];
int score;
};

int main()
{
struct Stu s1;
s1.score = 90;
s1.XueHao = "12306";
s1.name = "Lisi";
return 0;
}

所采用的的 " . "叫做结构体成员直接访问操作符,通过 " . "  +  结构体成员变量,可以在创建结构体变量同时对其进行赋值;也可以在创建好结构体变量之后,在需要使用时,通过  结构体变量+" . "+结构体成员变量  来找到成员变量进行赋值或者修改。

struct Stu
{
char name[20];
char XueHao[10];
int score;
};

int main()
{
struct Stu s;
struct Stu* ps = &s; //因为s是 struct Stu 类型,
ps->score = 90;      //所以它的地址就需要struct Stu* 类型的变量来存储
ps->XueHao = "12306";
ps->name = "Lisi";
return 0;
}

这里的ps就是一个结构体变量的指针, " -> "是结构体间接访问操作符,通过   结构体变量指针 + " -> " + 结构体成员名   就可以间接访问到我们的成员变量从而进行赋值与修改。  

3、结构体的重命名

但是在真正使用结构体时, 我们通常会在声明结构体时同时对其进行重命名,以达到使用更方便的效果,此时我们需要用到C语言中的 typedef 关键字来对其进行重命名。

typedef struct Stu
{
char name[20];
char XueHao[10];
int score;
}Stu;

int main()
{
Stu s;
return 0;
}

这里我们就将struct Stu 重命名为 Stu ,这样让我们在使用的时候会更加的简洁。 


3.结构体的特殊声明

在定义结构体当中,有一种特殊的定义方法,如下:

struct
{
成员1;
成员2;
.....;
}s1,s2;

这种命名方式与上面所讲到的相比,它没有结构体名,因此也叫做匿名结构体。

特点:它只能在定义的同时后面创建变量。

struct
{
int a;
char b;
}s;

struct
{
int a;
char b;
}*ps; // ps叫做匿名结构体指针

int main()
{
ps = &s;
return 0;
}

 倘若定义了两个匿名结构体,里面的成员完全相同,但在编译器看来,它们是不同的类型,所以在main 函数中,ps = &s 是不合法的,ps不能存放s的地址。


4.结构体数组

结构体数组,顾名思义也就是用来存放多个相同类型的结构体变量的数组,接下来我们来看一下它是如何创建的以及如何初始化。 

struct Stu
{
	char name[20];
	char XueHao[10];
	int score;
};

int main()
{
	struct Stu sarr[3] = { {"L","1",90} ,{"B","2",86},{"A","3",96}};
	return 0;
}

上图中的sarr就是一个结构体数组变量,数组的每一个元素都是struct Stu 结构体类型,所以每一个结构体变量的初始化都应该使用一个 " { } ",除此之外,下面的这种初始化方法作用也类似。

struct Stu
{
	char name[20];
	char XueHao[10];
	int score;
};

int main()
{
	struct Stu sarr[3];
    sarr[0].name = "L";
    sarr[0].XueHao = "1";
    sarr[0].score = 90;
	return 0;
}


5.结构体的内存对齐


   1.对齐规则

      1.结构体的第一个成员要对齐到和结构体变量起始位置(结构体变量的地址)偏移量为0的  地址处,也就是放在结构体变量的起始位置。
      2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
         对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值,

         在-vs 中默认对齐数为 8(下面的都是采用vs编译器所得到的结果)
         而在-Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
      3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍
      4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数  倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数              倍。

接下来我们来看一个实例:

struct Stu
{
	char a;
	int b;
	double c;
};

int main()
{
    struct Stu s;
	int ret = sizeof(s);
	printf("%d", ret);  //ret的值表示s所占内存的字节数
	return 0;
}                       

一个char类型的变量大小是1字节,一个整形变量的大小是4字节,一个double类型的变量大小是8字节,倘若不存在内存对齐,那么上面所算出的结果应该是13,但实际上我们得到的结果是16,这正是由于内存对齐的作用,接下来我们来看一下在内存中到底是如何去存储的:

有了上面的基础,下面让我们再来看两个例子:

struct Stu
{
    int a;  
    char b;
	char c;
};

int main()
{
    struct Stu s;
	int ret = sizeof(s);
	printf("%d", ret); 
	return 0;
} 

相应的,我们来看一下a、b、c三个变量在内存当中的实际储存情况:

 

我们若将成员的顺序调整以下,又会不会算出的结构体大小还是相同的结果呢?下面我们来看一下将上述的int类型的变量与char类型的变量调换了位置,算出的结果会是多少呢?

struct Stu
{
    char a;
    int b;  
	char c;
};

int main()
{
    struct Stu s;
	int ret = sizeof(s);
	printf("%d", ret); 
	return 0;
} 

接下来我们来看一下为什么会得到这个结果:

所以在创建结构体时,尽量将占用空间内存小的变量集中放在一起,这样可以避免浪费更多的空间。 

接下来我们再来看一下结构体嵌套时的情况:

struct S
{
	char A;
	int B;
};

struct Stu
{
	int a;
	struct S s1;
	char b;
	char c;
};

int main()
{
	struct Stu s;
	int ret = sizeof(s);
	printf("%d", ret);
	return 0;
}

 

这里得到的结果为什么又是16呢,我们来看一下内存示意图就一目了然:

                                                struct S s1变量的内存示意图

                                                 struct Stu s变量的内存示意图


   2.为什么存在内存对齐

 1.平台原因(移植原因):
   不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取  某些特定类型的数据否则抛出硬件异常。
2.性能原因:
   数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

总体来说:结构体的内存对齐是拿空间来换取时间的做法。


   3.如何修改默认的对齐数(pragma pack(对齐数))

#pragma pack(1)  //若我们将对齐数改为1 ,那么结构体中就不会存在内存浪费;                             
                 //创建的各个变量总大小是多少,结构体的大小就是多少;
struct S                                
{
	char A;
	int B;
};

int main()
{
	struct S s;
	int ret = sizeof(s);
	printf("%d", ret);
	return 0;
}


6.结构体如何传参

1、结构体变量作为函数参数

struct Stu
{
	int a;
	char b;
};

void print_info(struct Stu  x)
{
	printf("%d %c", x.a, x.b);
}

int main()
{
	struct Stu s = {4,'A'};
	print_info(s);
	return 0;
}

 若采用上面的这种传参方式,函数的形参X是实参s的一份拷贝,相当与重新创建了一个struct Stu 类型的变量,里面存有跟结构体变量s相同的信息,这种传参方式的效率不高,并且会额外开辟一块空间,但是形象直观。

2、结构体指针作为函数参数

struct Stu
{
	int a;
	char b;
};

void print_info(struct Stu* p)
{
	printf("%d %c", p->a, p->b);
}

int main()
{
	struct Stu s = {4,'A'};
	print_info(&s);
	return 0;
}

上面的这种方式是传入了结构体变量s的地址,函数的形参部分用结构体指针p来接受,此时通过p指针访问的直接是s里面的内容,空间与时间开销都很小,效率较高。 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值