C语言 结构体

1什么是结构体

结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员。结构的成员可以是标量、数组、指针,甚至是其他结构体。

2结构体的定义

(1)定义结构体的一般格式:

struct 结构名
{
类型 变量名;
类型 变量名;
...
} 结构变量;

例如定一个学生的结构体
struct student{
char no[20]; //学号
char name[20]; //姓名
char sex[5]; //性别
int age; //年龄
}stu[100];

像上面这里,我们就定义了表示100个学生的结构体。如果没有结构体,我们可能要用400行代码才能表示完100个学生的信息。这就是结构体的好处。
注意:结构体变量可以写,也可以不写,但是后面的分号不能省略
(2)结构体定义的特殊格式
匿名结构体(没有结构名),它只能使用一次,用过一次就不能再用了

struct 
{
类型 变量名;
类型 变量名;
...
} 结构变量;

看下面

代码1
struct {
char b;      
char name[20];        
int a;         
}x;

代码2
struct {
char b;      
char name[20];        
int a;         
}a[10],*p;

既然代码1和代码2的结构体成员一样,那我是不是可以把变量x的地址放到*p中去呢?就像这样 p=&x,可行吗?
实际上不可以。因为匿名结构体的成员虽然一样,但是在编译器看来,他们的类型是不一样的,所以编译器会报错(不兼容)。

3结构体变量的初始化

(1)方法:在对结构体变量初始化时,要对结构体成员一 一赋值,不能跳过前面成员变量,而直接给后面成员赋初值,但是可以只赋值前面几个,对与后面未赋值的变量,如果是数值型,则会自动赋值为0,对于字符型,会自动赋初值为NULL,即‘\0’
(2)定义时直接赋值

struct student{     
char name[20];      
char sex;  
int num;         
}x={"zhangsan",'m',1234};
或者
struct student{     
char name[20];  
char sex;      
int num;         
}struct student s={"zhangsan",'m'}

//需要注意的是,字符用’ ’ ,而字符串要用" "
(3)定义结构体后逐个赋值
按顺序

struct student{     
char name[20];  
char sex;      
int num;         
}stu;
int main()
{
stu.name="zhangsan";
stu.sex='m';
stu.num=1234;
return 0;
}

(4)定义之后任意赋值
可以不按顺序

struct student{     
char name[20];  
char sex;      
int num;         
}stu1;
int main()
{
.sex='m'; //或者stu1.sex='m'
.num=1234; //stu1.num=1234
.name="zhangsan";
return 0;
}

**知识:**利用成员运算符进行初始化,在结构体变量内部,在成员运算符“ . ”前面加或者不加结构体变量名都是可以的。

4结构体成员的访问

结构体成员访问操作符有 " . "和 “->”

(1) 用“ . ”访问

4.1一般的结构体成员输出

#include
struct student{
	char name[20];
	char sex;
	int num;
};
int main()
{
	struct student s = { "zhangsan", 'm', 123 };
	//比如现在要将结构体的成员打印出来
	printf("%s\n", s.name);
	printf("%c\n", s.sex);
	printf("%d\n", s.num);
	return 0;
}

C语言 结构体_第1张图片
4.2结构体中又有结构体
需要一级一级访问,直到最后一级的成员

struct in{
	int a=10;
	int b=20;
};
struct student{
	char name[20];
	char sex;
	int num;
	struct in s1;
};
int main()
{
	struct student s = { "zhangsan", 'm', 123 };
	//比如现在要将结构体的成员打印出来
	printf("%s\n", s.name);
	printf("%c\n", s.sex);
	printf("%d\n", s.num);
	printf("%d\n", s.s1.a);//变量s指向s1再指向a
	printf("%d\n", s.s1.b);//变量s指向s1再指向b
	return 0;
}

C语言 结构体_第2张图片

**

(2)用 -> 访问

**
成员变量为指针(不管指向什么,包括结构体)
把这部分内容放在下文(7结构体的传参)讲

5结构体的解引用

结构体的自引用(self reference),就是在结构体内部,包含指向(需要用指针)自身类型结构体的指针。
5.1错误的方式:

 struct tag_1{
  struct tag_1 A; 
  int value;
  };

这种声明是错误的,因为这种声明实际上是一个无限循环,成员struct tag_1 A 是一个结构体,struct tag_1 A 的内部还会有成员是结构体,依次下去,无线循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的。
5.2正确的方式(使用指针):

struct tag_1{
  struct tag_1 *A; /* 指针 */
  int value;
  };

由于指针的长度是确定的(在32位机器上指针长度为4),所以编译器能够确定该结构体的长度。

6.typedef

typedef是类型重命名,将结构体重新命名为一个新的名字

typedef struct tag{
  int num; 
  char name;
  }node;

就相当于将结构体重新命名为node
下面如果我们要创建两个变量n1和n2,可不可以像下面这样写呢?

typedef struct tag{
  int num; 
  char name;
  }node;
int main()
{
  struct tag n1;
        node n2;
 return 0;
}

这样写也是对的,node n2就相当于 struct tag n2,因为typedef将strcut tag 重命名成了node。此时n1和n2的类型是一样的。

typedef的好处就是可以将复杂的结构体重命名为一个简单的名字,方便我们创建新的变量。

7结构体的传参

一是传递结构体变量,这是值传递;二是传递结构体指针,这是地址传递
比如我们现在需要 设计一个函数,用来打印结构体成员
方法1传值

#include
struct stu{
	char name[20];
	char sex;
	int num;
};
void print(struct stu p) 
{   //用来打印结构体成员的函数
	printf("%s\n", p.name); 
	printf("%c\n", p.sex);   
	printf("%d\n", p.num);   
}
int main()
{
	struct stu s1 = { "zhangsan", 'm', 1234 };
	print(s1);  //把变量s1的值传过去
	return 0;
}

C语言 结构体_第3张图片

方法2传址

struct stu{
	char name[20];
	char sex;
	int num;
};
void print2(struct stu* p2)  //用一个指针p2,来接收结构体s2的地址
{   //用来打印结构体成员的函数
	printf("%s\n", p2->name);  //p2指向name
	printf("%c\n", p2->sex);   //p2指向sex
	printf("%d\n", p2->num);   //p2指向num
}
int main()
{
	struct stu s2 = { "zhangsan", 'm', 1234 };
	print2(&s2);  //把s2的地址传过去
	return 0;

C语言 结构体_第4张图片

8结构体内存对齐

比如我们现在要计算结构体的大小

struct s1{
char c1;
int a;
char c2;
}s1;
struct s2{
char c1;
char c2;
int i;
}s2;
int main()
{
printf("%d",sizeof(s1));
printf("%d",sizeof(s2));
return 0;
}

在这里插入图片描述
为什么两个成员一样的结构体,大小却不一样?这是因为两个结构体的内存对齐不一样导致的
7.1内存对齐规则
(1)第一个成员在结构体变量偏移量为0 的地址处。
(2)其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数该成员大小中的较小值。vs中默认值是8 Linux默认值为0(vs:64位编译系统下是8个字节,32位编译系统下是4个字节)。
(3)结构体总大小为最大对齐数的整数倍。(每个成员变量都有自己的对齐数)
(4)如果嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。

我们再来看看s1和s2的内存对齐
C语言 结构体_第5张图片

C语言 结构体_第6张图片

补充说明,int a和int i的对齐数都是4,所以偏移量从他们的整数倍开始
7.2为什么存在内存对齐呢?
平台原因(移植原因):
不是所有的硬件平台都能访问任意地址的任意数据的;某些平台只能在某些地址处取某些特定类型的数据
性能原因
数据结构尤其是栈应该尽可能在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次访问内存;而对齐的内存访问仅需要一次访问。
缺点:
无可厚非:这必然会存在效率问题,这是一种以空间换时间的做法,虽然浪费了一定的空间,但是却可以少花时间,所以这种做法是值得的
C语言 结构体_第7张图片

你可能感兴趣的:(c语言)