C 语言中的结构体能够使我们实现类型的自定义,满足定义一些特殊变量的需求。
结构体一般定义为:
struct structname
{
datatype member1;
datatype member2;
datatype member3;
};
#include
struct
{
int num;
char name[10];
int math;
int chinese;
int english;
}stu1;
struct
{
int num;
char name[10];
int math;
int chinese;
int english;
}stu2,stu3;
int main()
{
struct
{
int num;
char name[10];
int math;
int chinese;
int english;
}stu4;
return 0;
}
#include
struct Student
{
int num;
char name[10];
int math;
int chinese;
int english;
}stu1;
struct Worker
{
int num;
char name[10];
char department[20];
};
int main()
{
struct Student stu2;
struct Worker worker1;
return 0;
}
#include
typedef struct Student
{
int num;
char name[10];
int math;
int chinese;
int english;
}STU;
int main()
{
STU stu;
return 0;
}
结构体也属于构造类型,可以参考同属于构造类型的数组的变量初始化方法,即:
#include
#include
struct
{
int num;
char name[10];
int math;
int chinese;
int english;
}stu1 = {10,"123",20,30,40},stu2 = {10,"123",20,30,40};
struct Student
{
int num;
char name[10];
int math;
int chinese;
int english;
};
int main()
{
struct Student stu3 = {10,"123",20,30,40},stu4;
stu4.num = 10;
strcpy(stu4.name,"123");
stu4.math = 20;
stu4.chinese = 30;
stu4.english = 40;
printf("stu1.num = %d\n",stu1.num);
printf("stu1.name = %s\n",stu1.name);
printf("stu1.math = %d\n",stu1.math);
printf("stu1.chinese = %d\n",stu1.chinese);
printf("stu1.english = %d\n",stu1.english);
printf("stu2.num = %d\n",stu2.num);
printf("stu2.name = %s\n",stu2.name);
printf("stu2.math = %d\n",stu2.math);
printf("stu2.chinese = %d\n",stu2.chinese);
printf("stu2.english = %d\n",stu2.english);
printf("stu3.num = %d\n",stu3.num);
printf("stu3.name = %s\n",stu3.name);
printf("stu3.math = %d\n",stu3.math);
printf("stu3.chinese = %d\n",stu3.chinese);
printf("stu3.english = %d\n",stu3.english);
printf("stu4.num = %d\n",stu4.num);
printf("stu4.name = %s\n",stu4.name);
printf("stu4.math = %d\n",stu4.math);
printf("stu4.chinese = %d\n",stu4.chinese);
printf("stu4.english = %d\n",stu4.english);
return 0;
}
结果为:
stu1.num = 10
stu1.name = 123
stu1.math = 20
stu1.chinese = 30
stu1.english = 40
stu2.num = 10
stu2.name = 123
stu2.math = 20
stu2.chinese = 30
stu2.english = 40
stu3.num = 10
stu3.name = 123
stu3.math = 20
stu3.chinese = 30
stu3.english = 40
stu4.num = 10
stu4.name = 123
stu4.math = 20
stu4.chinese = 30
stu4.english = 40
上边的程序中有一段话可能需要解释一下:
stu4.num = 10; strcpy(stu4.name,"123"); stu4.math = 20; stu4.chinese = 30; stu4.english = 40;
其中 str4.name 是用 strcpy 函数进行赋值的,为什么不能也用 stu4.name = “123”; 这样的形式呢?
- stu4.name 对应成员 name 的首地址,该地址在变量 stu4 定义的时候已经确定了,所以是个常量
- “123” 实际上对应的是存储在只读数据段的首地址,实际也是常量
- 用 stu4.name = “123”; 进行赋值自然就会报错
- 用 *stu4.name = “123”; 也会报错,因为你不知道字符串常量 “123” 所在的地址对应的字符串是什么东西
- 我们需要将 “123” 对应的字符串重新复制到 stu4.name 对应的地址中,因此才会使用 strcpy 函数
. 成员运算符
在初始化的时候,我们使用 stu.xxx 的形式来指向结构体变量的成员,中间的那个点也是一个运算符,叫做成员运算符。
使用 . 运算符能够访问到结构体变量的成员,我们可以试试手动输入进行赋值。
#include
struct Student { int num; char name[10]; int math; int chinese; int english; }; int main() { struct Student stu; printf("Please input number:"); scanf("%d",&stu.num); printf("Please input name:"); scanf("%s",stu.name); printf("Please input math:"); scanf("%d",&stu.math); printf("Please input chinese:"); scanf("%d",&stu.chinese); printf("Please input english:"); scanf("%d",&stu.english); printf("stu.num = %d\n",stu.num); printf("stu.name = %s\n",stu.name); printf("stu.math = %d\n",stu.math); printf("stu.chinese = %d\n",stu.chinese); printf("stu.english = %d\n",stu.english); return 0; } -> 间接成员运算符
从之前的程序可以看出,当直接定义结构体变量时,可以用 . 成员运算符直接进行访问。
那么间接成员运算符就主要是针对结构体指针来说的,可以使用间接成员运算符访问结构体指针指向的变量成员。
#include
typedef struct Student { int num; char name[10]; int math; int chinese; int english; }STU; int main() { STU stu1; STU *ps = &stu1; printf("Please input number:"); scanf("%d",&ps->num); printf("Please input name:"); scanf("%s",ps->name); printf("Please input math:"); scanf("%d",&ps->math); printf("Please input chinese:"); scanf("%d",&ps->chinese); printf("Please input english:"); scanf("%d",&ps->english); printf("ps->num = %d\n",ps->num); printf("ps->name = %s\n",ps->name); printf("ps->math = %d\n",ps->math); printf("ps->chinese = %d\n",ps->chinese); printf("ps->english = %d\n",ps->english); printf("(*ps).num = %d\n",(*ps).num); printf("(*ps).name = %s\n",(*ps).name); printf("(*ps).math = %d\n",(*ps).math); printf("(*ps).chinese = %d\n",(*ps).chinese); printf("(*ps).english = %d\n",(*ps).english); return 0; }
- 运行之后可以发现,两次的输出结果是一样的
- 也就是说不管是 . 还是 ->,都是有效的
- 两个运算符作用也是等价的
有时也不直接从栈上开辟内存,而是从堆上,程序本身没有什么大的区别。
#include
#include typedef struct Student { int num; char name[10]; int math; int chinese; int english; }STU; int main() { STU *ps = (STU *)malloc(sizeof(STU)); printf("Please input number:"); scanf("%d",&ps->num); printf("Please input name:"); scanf("%s",ps->name); printf("Please input math:"); scanf("%d",&ps->math); printf("Please input chinese:"); scanf("%d",&ps->chinese); printf("Please input english:"); scanf("%d",&ps->english); printf("ps->num = %d\n",ps->num); printf("ps->name = %s\n",ps->name); printf("ps->math = %d\n",ps->math); printf("ps->chinese = %d\n",ps->chinese); printf("ps->english = %d\n",ps->english); printf("(*ps).num = %d\n",(*ps).num); printf("(*ps).name = %s\n",(*ps).name); printf("(*ps).math = %d\n",(*ps).math); printf("(*ps).chinese = %d\n",(*ps).chinese); printf("(*ps).english = %d\n",(*ps).english); return 0; }
之前我们说过数组中的元素地址是线性的,但其实结构体变量中的成员地址也是线性的。看下边一段程序:
#include
#include
typedef struct Student
{
int num;
char name[10];
int math;
int chinese;
int english;
}STU;
int main()
{
STU stu = {10,"123",20,30,40};
printf("&stu.num = %p,stu.num = %d\n",&stu.num,stu.num);
printf("&stu.name = %p,stu.name = %s\n",stu.name,stu.name);
printf("&stu.math = %p,stu.math = %d\n",&stu.math,stu.math);
printf("&stu.chinese = %p,stu.chinese = %d\n",&stu.chinese,stu.chinese);
printf("&stu.english = %p,stu.english = %d\n",&stu.english,stu.english);
return 0;
}
结果为:
&stu.num = 0060FE94,stu.num = 10
&stu.name = 0060FE98,stu.name = 123
&stu.math = 0060FEA4,stu.math = 20
&stu.chinese = 0060FEA8,stu.chinese = 30
&stu.english = 0060FEAC,stu.english = 40
从上面结果来看:
有时候我们还会要进行变量赋值,如果进行成员间赋值是很麻烦的。看下面的程序:
#include
#include
typedef struct Student
{
int num;
char name[10];
int math;
int chinese;
int english;
}STU;
int main()
{
STU stu1 = {10,"123",20,30,40},stu2;
stu2 = stu1;
printf("&stu1.num = %p,stu1.num = %d\n",&stu1.num,stu1.num);
printf("&stu1.name = %p,stu1.name = %s\n",stu1.name,stu1.name);
printf("&stu1.math = %p,stu1.math = %d\n",&stu1.math,stu1.math);
printf("&stu1.chinese = %p,stu1.chinese = %d\n",&stu1.chinese,stu1.chinese);
printf("&stu1.english = %p,stu1.english = %d\n",&stu1.english,stu1.english);
printf("&stu2.num = %p,stu2.num = %d\n",&stu2.num,stu2.num);
printf("&stu2.name = %p,stu2.name = %s\n",stu2.name,stu2.name);
printf("&stu2.math = %p,stu2.math = %d\n",&stu2.math,stu2.math);
printf("&stu2.chinese = %p,stu2.chinese = %d\n",&stu2.chinese,stu2.chinese);
printf("&stu2.english = %p,stu2.english = %d\n",&stu2.english,stu2.english);
return 0;
}
结果为:
&stu1.num = 0060FE94,stu1.num = 10
&stu1.name = 0060FE98,stu1.name = 123
&stu1.math = 0060FEA4,stu1.math = 20
&stu1.chinese = 0060FEA8,stu1.chinese = 30
&stu1.english = 0060FEAC,stu1.english = 40
&stu2.num = 0060FE78,stu2.num = 10
&stu2.name = 0060FE7C,stu2.name = 123
&stu2.math = 0060FE88,stu2.math = 20
&stu2.chinese = 0060FE8C,stu2.chinese = 30
&stu2.english = 0060FE90,stu2.english = 40
从上面的结果可以看出:
既然知道了结构体类型的定义和初始化方式,之前我们也知道了二维数组的定义和初始化方式,那么结构体数组就不算是太大的问题了。
#include
typedef struct Student
{
int num;
char name[10];
int score;
}STU;
int main()
{
STU stu[3] = {{101,"Tom",70},{102,"Jim",80},{103,"Lily",90}};
// STU stu[3] = {101,"Tom",70,102,"Jim",80,103,"Lily",90};
for (unsigned int i = 0;i < sizeof(stu)/sizeof(stu[0]);i++)
{
printf("stu.num = %d\n",stu[i].num);
printf("stu.name = %s\n",stu[i].name);
printf("stu.score = %d\n",stu[i].score);
}
return 0;
}
结果是:
stu.num = 101
stu.name = Tom
stu.score = 70
stu.num = 102
stu.name = Jim
stu.score = 80
stu.num = 103
stu.name = Lily
stu.score = 90
从程序段可以看出:
有时候,当某一项属性涉及到多个子项的时候,也会采用结构体类型嵌套的形式,可以增加程序的可读性。
#include
//struct Score
//{
// int math;
// int chinese;
// int english;
//};
typedef struct Student
{
int num;
char name[10];
struct
{
int math;
int chinese;
int english;
}score;
}STU;
int main()
{
STU stu = {101,"Tom",{10,20,30}};
// STU stu = {101,"Tom",10,20,30};
printf("stu.num = %d\n",stu.num);
printf("stu.name = %s\n",stu.name);
printf("stu.score.math = %d\n",stu.score.math);
printf("stu.score.chinese = %d\n",stu.score.chinese);
printf("stu.score.english = %d\n",stu.score.english);
return 0;
}
结果为:
stu.num = 101
stu.name = Tom
stu.score.math = 10
stu.score.chinese = 20
stu.score.english = 30
从上边的结果可以看出:
#include
#include
typedef struct complex
{
float re;
float im;
}COM;
COM com_plus(COM com1, COM com2)
{
COM res;
res.re = com1.re + com2.re;
res.im = com1.im + com2.im;
return res;
}
int main()
{
COM com1 = { 1.0f, 2.0f };
COM com2 = { 3.0f, 4.0f };
COM res = com_plus(com1, com2);
printf("res:re = %f ,im = %f\n", res.re, res.im);
return 0;
}
结果为:
res:re = 4.000000 ,im = 6.000000
#include
#include
typedef struct complex
{
float re;
float im;
}COM;
COM com_plus(COM *pcom1, COM *pcom2)
{
COM res;
res.re = pcom1->re + pcom2->re;
res.im = pcom1->im + pcom2->im;
return res;
}
int main()
{
COM com1 = { 1.0f, 2.0f };
COM com2 = { 3.0f, 4.0f };
COM res = com_plus(&com1, &com2);
printf("res:re = %f ,im = %f\n", res.re, res.im);
return 0;
}
结果是:
res:re = 4.000000 ,im = 6.000000
从上边的结果可以看出:
结构体类型就好像 int,float 这样的关键字一样,是不占用内存的,但是结构体类型的变量确实要占用内存的,并且还有点特殊。看下边一段程序:
#include
#include
typedef struct Student
{
short num;
char sex;
int score;
}STU;
int main()
{
STU stu = {10,'m',80};
printf("&stu.num = %p,stu.num = %d\n",&stu.num,stu.num);
printf("&stu.sex = %p,stu.sex = %c\n",&stu.sex,stu.sex);
printf("&stu.score = %p,stu.score = %d\n",&stu.score,stu.score);
return 0;
}
结果是:
&stu.num = 0060FEA8,stu.num = 10
&stu.sex = 0060FEAA,stu.sex = m
&stu.score = 0060FEAC,stu.score = 80
当时我们只是说明了结构体类型的变量的内存空间也是线性排列的,并没有涉及到实际的内存地址。但对于上面的结果,我们能够看出该变量首地址对应 0060FEA8,下一个成员地址是 0060FEAA,再下一个地址是 0060FEAC。为什么不是 0060FEAA 和 0060FEAB 呢?我们就从这里引入我们的问题。
对于上面的程序,由于变量的内存空间是线性的,那么对于该变量各成员的位置有上图中几种方案,由结果我们知道选择的是方案 B。
我们知道计算机地址总线是固定的,一般为 32 或者 64,这里以 32 为例。也就是说计算机一次能够读取四个字节的地址,然后我们看看上面三个方案的不同:
从上面的结果结果我们知道,不同的方案数据读取的次数就不同,同时计算机所花费的时间就会不同。一个成员变量需要多个机器周期去读的现象,就叫做内存不对齐。也就是说利用内存对齐,虽然会浪费些空间而节省计算时间。这对于计算机来说是划算的。
方案 B 和方案 C 的好像空间和时间都是一样的,那为什么会使用方案 B 呢?那是因为内存对齐是有一定规则的。
具体方法:
这里把上边的程序修改后重新运行一下:
#include
#include
typedef struct Student
{
short num;
char sex;
int score;
char access;
// short grade;
}STU;
int main()
{
STU stu = {10,'m',80,'y'};
printf("The STU length is %d\n",sizeof(STU));
printf("&stu.num = %p,stu.num = %d\n",&stu.num,stu.num);
printf("&stu.sex = %p,stu.sex = %c\n",&stu.sex,stu.sex);
printf("&stu.score = %p,stu.score = %d\n",&stu.score,stu.score);
printf("&stu.access = %p,stu.access = %d\n",&stu.access,stu.access);
// printf("&stu.grade = %p,stu.grade = %d\n",&stu.grade,stu.grade);
return 0;
}
结果为:
The STU length is 12
&stu.num = 0060FEA4,stu.num = 10
&stu.sex = 0060FEA6,stu.sex = m
&stu.score = 0060FEA8,stu.score = 80
&stu.access = 0060FEAC,stu.access = 121
从上面的结果可以看出:
看下边一段代码:
#include
#include
typedef struct Student
{
short num;
char *name;
int score;
}STU;
int main()
{
STU stu;
stu.num = 10;
strcpy(stu.name,"123");
stu.score = 80;
printf("&stu.num = %p,stu.num = %d\n",&stu.num,stu.num);
printf("&stu.name = %p,stu.name = %p,*stu.name = %c\n",&stu.name,stu.name,*stu.name);
printf("&stu.score = %p,stu.score = %d\n",&stu.score,stu.score);
return 0;
}
上面的代码看似没有问题,出于节约内存的考量,我们将 char name[10] 换成了 char *name,但是结果并不是我们想象的那样,那么问题出现在哪里呢?
将上边的程序改写为:
#include
typedef struct Student
{
short num;
char *name;
int score;
}STU;
int main()
{
STU stu;
stu.num = 10;
stu.name = "123";
stu.score = 80;
printf("&\"123\" = %p\n","123");
printf("&stu.num = %p,stu.num = %d\n",&stu.num,stu.num);
printf("&stu.name = %p,stu.name = %p,stu.name = %s\n",&stu.name,stu.name,stu.name);
printf("&stu.score = %p,stu.score = %d\n",&stu.score,stu.score);
return 0;
}
结果为:
&"123" = 00404064
&stu.num = 0060FEA4,stu.num = 10
&stu.name = 0060FEA8,stu.name = 00404064,stu.name = 123
&stu.score = 0060FEAC,stu.score = 80
总结为一点就是:什么样的成员就执行什么样的操作
上面提到的问题还可以用另外一种方法解决:
#include
#include
#include
typedef struct Student
{
short num;
char *name;
int score;
}STU;
int main()
{
STU *stu = (STU *)malloc(sizeof(STU));
if (NULL == stu)
return -1;
stu->num = 10;
stu->name = malloc(10);
if (NULL == stu->name)
return -1;
strcpy(stu->name,"123");
stu->score = 80;
printf("&\"123\" = %p\n","123");
printf("&stu->num = %p,stu->num = %d\n",&stu->num,stu->num);
printf("&stu->name = %p,stu->name = %p,stu->name = %s\n",&stu->name,stu->name,stu->name);
printf("&stu->score = %p,stu->score = %d\n",&stu->score,stu->score);
free(stu->name);
stu->name = NULL;
free(stu);
stu = NULL;
return 0;
}
结果为:
&"123" = 00404064
&stu->num = 00C616F0,stu->num = 10
&stu->name = 00C616F4,stu->name = 00C61708,stu->name = 123
&stu->score = 00C616F8,stu->score = 80
上面的方法也能够解决之前提到的问题。但是在使用上述的方法时,一定要注意释放内存,并且是由内而外的释放内存。