【笔记】C语言的结构体

前言

C语言的结构体学习笔记

定义结构体并使用结构体变量

  • 结构体使用struct关键字定义,结构体末尾有;

  • 定义结构体,类似于OOP编程的创建类(当然,C语言是没有对象这个概念的,只不过是这里方便理解)

  • 结构体内部可以定义多个变量,类似于OOP编程中类的属性

  • 结构体变量的地址,实际上就是首元素的地址

  • 在C语言中一个结构体至少要有一个成员变量,在C++定义结构体时可以没有成员变量

先调用后初始化

初始化全部成员变量

A:结构体名
a:结构体变量名

1
2
3
4
5
6
7
8
9
10
11
12
struct A
{
char name[8];
int age;
};
int main()
{
struct A a;
scanf("%s", a.name);
scanf("%d", &a.age);
return 0;
}

只初始化部分变量

  • 没有被初始化的成员变量值为0
1
2
3
4
5
6
7
8
9
10
11
struct A
{
char name[8];
int age;
};
int main()
{
struct A a;
scanf("%s", a.name);
return 0;
}

调用时直接初始化

初始化全部成员变量

1
2
3
4
5
6
7
8
9
10
struct A
{
char name[8];
int age;
};
int main()
{
struct A a = { "张三", 18 };
return 0;
}

只初始化部分成员变量

不指定成员变量名
  • 不指定成员变量名时,会按照定义顺序进行初始化,且其他没有被初始化的成员变量值为0
1
2
3
4
5
int main()
{
struct A a = { "张三" };
return 0;
}
指定成员变量名
  • 如果指定成员变量名,就不考虑顺序
1
2
3
4
5
int main()
{
struct A a = { .age = 18 };
return 0;
}

结构体在内存中存放的方式

  • 结构体在内存中存储数据时,如果与最大成员变量不对齐,则会自动对齐,对齐时空出来的空间保持为空,且如果有其他小于空的空间的数据存放时,会自动放到空的空间(但是这个数据会右对齐)

  • 由于结构体的成员变量在内存中存储时是按顺序的,所以我们应该手动优化代码实现最省空间的存储

  • 如果结构体出现数组,则按照数组内的具体成员的类型作为对齐标准

以下是优化前的代码:

  1. 这一组成员变量中,最长的是4个字节的int类型a4,所以,应按照4字节为一个单位的存储
  2. 先存a1,占用1字节,再存a2,占用2字节,但是是右对齐,所以一共消耗了4字节空间
  3. 然后存a3,占用1字节,接下来想要存a4,但是不够4字节了,所以a3占用4字节
  4. 最后存a4,占用4字节
  5. 最终占用12字节
1
2
3
4
5
6
7
struct A
{
char a1;
short a2;
char a3;
int a4
};

以下是优化后的代码:

  1. 这一组成员变量中,最长的是4个字节的int类型a4,所以,应按照4字节为一个单位的存储
  2. 现存b1,占用1字节,再存b2,占用1字节,向右对齐
  3. 然后存b3,占用2字节,与b1、b2总共占用4字节
  4. 最后存b4,占用4字节
  5. 最终占用8字节
1
2
3
4
5
6
7
struct B
{
char b1;
char b2;
short b3;
int b4;
};

以比特为单位存储成员变量

  • 指定的bit数不能超过当前定义的数据类型的最大的内存占用空间

<num>:以几个bit存储数据

1
2
3
4
struct A
{
unsigned char a : <num>;
};

结构体数组

先调用后初始化

初始化全部成员变量

指定数组数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct A
{
char name[8];
int age;
};
int main()
{
struct A a[2];
int i;
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
scanf("%s", a[i].name);
scanf("%d", &a[i].age);
}
return 0;
}
不指定数组数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct A
{
char name[8];
int age;
};
int main()
{
struct A a[];
int i;
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
scanf("%s", a[i].name);
scanf("%d", &a[i].age);
}
return 0;
}

初始化部分成员变量

1
2
3
4
5
6
7
8
9
10
11
12
struct A
{
char name[8];
int age;
};
int main()
{
struct A a[2];
scanf("%s", a[0].name);
scanf("%d", &a[0].age);
return 0;
}

调用时直接初始化

初始化全部成员变量

指定数组数量
1
2
3
4
5
6
7
8
9
10
struct A
{
char name[8];
int age;
};
int main()
{
struct A a[2] = { { "张三", 18 }, { "李四", 20 } };
return 0;
}
不指定数组数量
1
2
3
4
5
6
7
8
9
10
struct A
{
char name[8];
int age;
};
int main()
{
struct A a[] = { { "张三", 18 }, { "李四", 20 } };
return 0;
}

只初始化部分成员变量

不指定成员变量赋值
1
2
3
4
5
6
7
8
9
10
struct A
{
char name[8];
int age;
};
int main()
{
struct A a[2] = { { "张三", 18 } };
return 0;
}
指定成员变量赋值
1
2
3
4
5
6
7
8
9
10
struct A
{
char name[8];
int age;
};
int main()
{
struct A a[2] = { { .age=18 }, { .name="李四" } };
return 0;
}

结构体嵌套

  • 在嵌套结构体时,结构体是作为一个整体存在的,所以对齐时只遵循子级结构体,而不是父级结构体
  • 例如以下的结构体占用了12字节内存
1
2
3
4
5
6
7
8
9
10
struct A
{
char a1;
};
struct B
{
struct A a;
char b1;
int b2;
};

结构体变量的复制

  • 结构体变量的赋值,其实就是内存的拷贝
1
2
struct A a = {};
struct B b = a;

操作符

  • 操作符实际上就是尖头函数->

定义一个结构体

1
2
3
4
5
struct P
{
char name[8];
int age;
};

没有使用操作符之前的结构体赋值

1
2
3
4
struct P p;

strcpy((*p).name, "张三");
(*p).age = 18;

使用操作符之后的结构体赋值

1
2
strcpy(p->name, "张三");
p->age = 18;

结构体作为函数参数

传递结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct A
{
char name[8];
int age;
};

void print_A(struct A a)
{
printf("name=%s, age=%d\n", a.name, a.age);
}

int main()
{
struct A a = {"张三", 18};
print_A(a);
return 0;
}

传递结构体脂针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct A
{
char name[8];
int age;
};

void set_A(struct A *a, char name[8], int age)
{
strcpy(a->name, name);
a->age = age;
}

int main()
{
struct A a = {"张三", 18};
set_A(&a, "李四", 20);
}

完成