知之愈明,则行之愈笃;行之愈笃,则知之益明。——朱熹(宋)
1、结构体定义问题
struct student
{
int age;
int height;
char name[100];
};
这一段,就是定义结构体类型,也就是相当于是,别的类型一样,就比如int,float之类,但是此时只是类型,还没有变量,只有定义了变量才能使结构体类型有存在。也只有创建变量之后,结构体类型才是在内存中创建了空间,在空间中存放age,height,name。
要想怎么创建变量,有两种方法分别是
代码1
struct student
{
int age;
int height;
char name[100];
}n1,n2;
但是这段代码,算的上是匿名的结构体,不能够使用多次,也就是说,在结构体变量命名的时候也就确定了,只能是n1和n2,这两个,也不会有别的变量能够使用了。并且此时的n1和n2都是全局变量。
代码2
struct student
{
int age;
int height;
char name[100];
};
int main()
{
struct student n3, n4;
return 0;
}
这都是可以的。
==再次说明:==在代码1中,struct student中student是定义的结构体的类型名,而其中的n1,n2是定义的结构体类型变量,不是名称,如果想要让stu为结构体类型名称时,必须在结构体定义时添加typedef关键字
struct student
{
int age;
int height;
char name[100];
}*ps;
struct student
{
int age;
int height;
char name[100];
}*s;
int main()
{
ps=&s;
}
那这里,关于ps和s的能不能成功的相等呢?就算是两个的结构体里面的成员是一模一样的呢?
结果其实是不可以的,关于编译器来说,就算是一模一样的内容,那也是不一样的结构体
2、结构体访问成员的操作符
关于结构体访问成员的操作符,在定义的时候,就是可以用到两个,这两个也是在初始化结构体变量的时候起到重大作用的。(因为结构体里面的存放的不止一个数据,那么说明肯定不能只定义一下,所以要像数组一样用大括号进行初始化)
int main()
{
struct student n3 = {20,120,"wangwu"};
return 0;
}
那其实这段是没有什么操作符使用的,而且初始化的时候还必须要按照顺序,那么要是想不按照顺序,那么还可以怎么初始化呢?
int main()
{
struct student n4 = { .height = 244,.age = 12,.name = "wangwu" };
return 0;
}
这样的初始化,就用到了,一个重要的操作符==.,并且还能根据自己想要的顺序进行初始化。那么其实关于这个操作符,还有一个->==,关于这个操作符来说,这个就是相当于在打印的时候使用的
int main()
{
struct student n4 = { .height = 244,.age = 12,.name = "wangwu" };
printf("%d",n4->height);//printf("%d",n4.height)
return 0;
}
就是大概这么使用的,当然也还可以用之前的操作符。
当然还有一个操作符也是可以用到的。
int main()
{
struct student n4 = { 10,230,"zhangsan" };
struct student* pst = &n4;
printf("%d\n", (*(pst)).height);
printf("%d\n", pst->height);
return 0;
}
那就是解引用操作符。关于打印的那两句话,效果是一样的,而且在第一段的打印的时候,必须要是加上括号,不然的话.的优先级是高于解引用的。
就比如下面这段题目。
#include < stdio.h >
struct S
{
int a;
int b;
};
int main( )
{
struct S a, *p=&a;
a.a = 99;
printf( "%d\n", __________);
return 0;
}
如果想打印关于结构体中成员a的数据,在printf中,我们应该写什么呢?
其实这样的话答案是有三种的
*1、a.a
2、p->a
3、(p).a
那么就还有一种典型的错误,那就是 *p.a,这是完全不对的。
3、结构体的所占内存空间
当然要是想要修改对齐数的话也是可以的,就要使用
#pragma pack(x)
其中x就是修改后的对齐数。
那么为什么要用到对其呢?为什么就是这样呢?
- 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定
类型的数据,否则抛出硬件异常。 - 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要
作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地
址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以
⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两
个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
大抵上就是这样子的。
4、和之前的联系
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
那么关于结构体传参的时候到底是使用什么更好呢?是传址,还是传值?
其实,问这问题的时候,就是要看传值和传址的根本本质是什么了。其实传址就是把地址给过去,通过首地址,来一个个的访问。传值可就不一样了,还会创建形参,临时拷贝,那么拷贝的量就是相当的大了,这样就会消耗大量的时间和空间,会导致代码的不好,所以,还是传址是更好的选择。
5、结构体实现位段
struct A
{
int a:2;
int b:4;
int c:10;
}
这是位段的代码形式。
虽然位段是可以节省空间的,但是关于位段,有很多不确定的因素使得位段是不能跨平台使用的,在注重可移植的程序应该避免使用位段。
1、当开辟了内存的时候,内存中的每个比特位从左向右,还是从右到左都是不确定的。
2、当之前剩余的空间不足的时候,下一个成员使用的时候,剩余空间是否使用是不确定的。
5、1位段的应用
5、2位段使用的注意
由于位段,使得每一个元素其实并不是独立占用一个字节,每一个字节可能都不是一个变量的初始地址,所以不能用scanf直接给位段的成员输入值,只能先输入放在一个变量中,然后在赋值给位段的成员。
struct A
{
int a : 2;
int b : 10;
int c : 23;
};
int main()
{
int b = 0;
scanf("%d", &b);
struct A sa = { 0 };
sa.b = b;
//scanf("%d",&sa.b);这是错误的。
return 0;
}