目录
结构类型的定义
struct 结构类型名
{
//结构成员的定义
数据类型标识符 成员名
//....
};
//匿名结构体
struct
{
char c;
int i;
double d;
}s1,s2;
//后续不能再用此结构体创建变量,有且只有s1,s2。
1 首行“struct 结构类型名”是结构定义头,常用于结构类型的声明
2 左右花括号“{}”括起来的部分是结构定义体,可以在其中声明结构成员,或者说在结构定义体中声明的变量,都是该结构的成员。
3 结构定义一定以分号“;”结束
4 结构类型名通常以首字母大写,有意义的单词组成,若由多个单词组成,则每个单词首字母大写,如Date,StudentIfor。
例;
struct Rational
{
int numerator;//分子
int denomintor;//分母
};
//学生结构类型student
struct Date
{
int year;
int month;
int day;
};
struct Student
{
char name[80]; //姓名
unsigned int sid; //学号
struct Date birthday; //出生日期
char specialty[80]; //专业名称
char classname[80]; //班级名称
double score[5]; //五门课成绩
};
在Student结构定义中,成员birthday的类型是Date结构类型,就要求Date的定义要在Student的定义之前,否则就会一起编译错误。
强调:在结构定义体中的成员只是声明的性质,并没有对他们分配内存存储,因而不能在结构体中初始化结构成员。只有定义结构体变量后,这些成员才随着结构变量的生成而存在,才可以初始化、赋值。
结构变量的定义及初始化
在用struct定义一个结构体后,就诞生了一种新的数据类型,如前面定义的Rational、Date、Student都是自定义的结构类型,它们的名字就是数据类型标识符。这些标识符如同int、char、float、double一样,具有相同的地位,都表示一种数据类型。
结构变量的定义和int变量的定义的方式是一样的:
结构类型标识符 结构变量名
在c语言中,定义结构变量时,结构类型标识符的前面还需加上struct关键字。
结构变量在定义的同时可以对它们进行初始化:
结构类型标识符 结构变量={初始化列表};
上述形式中,初始化值列表必须按照结构成员的顺序对应排列,且值的数据类型需要与成员的数据类型一致。
结构成员访问
为了让结构变量的成员有确定的值,可以利用以下两种方式:第一,在定义该结构变量的时候对它初始化,初始化结构变量时是把该变量的成员作为一个整体进行的:第二,单独访问结构变量的某个成员,然后对它赋值。对结构变量的成员赋值,需要首先访问变量的某个成员。
对结构变量的成员的访问,主要涉及两个运算符:圆点运算符(.)和箭头运算符(->)。
当左边是结构变量时,用圆点运算符;当左边是结构指针时,用箭头运算符
例:
#include<stdio.h>
struct Rational
{
int numerator;
int denominator;
};
int main()
{
struct Rational a = { 2,3 };
struct Rational b;
b.numerator = 3;
b.denominator = 4;
printf("b: \t%d/%d\n", b.numerator, b.denominator);
struct Rational* p;
p = &a;
p->numerator = 5;
p->denominator = 6;
printf("a:\t%d/%d\n", p->numerator, p->denominator);
return 0;
}
箭头运算符也可以 化为圆点运算符。如上述第16、17行,结构指针p对成员的访问也可以化成如下形式:
(*p).numerator
结构体内存对⻬:
⾸先得掌握结构体的对⻬规则:
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。
vs默认为8 (该成员自身大小)
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
struct s1
{
char c1;
char c2;
int n;
};
struct s2
{
char c1;
int n;
char c2;
};
int main()
{
struct s1 a;
struct s2 A;
printf("%d \n",sizeof(a));//8
printf("%d \n", sizeof(A));//12
return 0;
}
为什么存在内存对齐
1平台原因
不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存
访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果
我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否
则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
修改默认对奇数
#pragma pack(1)
struct s
{
char c1;
int a;
char c2;
};//结构体大小6
#pragma pack()//恢复默认对齐数
对齐数一般设置为2的次方数
结构体传参
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;
}
print1 和 print2 函数哪个好些?
答案是:⾸选print2函数。
原因:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。
结构体实现位段
1、什么是位段
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是int、unsigned int 或signed int,在C99中位段成员的类型也可以选择其他类型。
2.位段的成员名后边有一个冒号和一个数字。
struct s
{
int _a;
int _b;
int _c;
int _d;
};
struct a
{
int _a:2; //a占2个比特位
int _b:4; //b占4个比特位
int _c:8; //c占8个比特位
int _d:16; //d占16个比特位
};
位段中的为指的是二进制的位
位段a所占内存大小为4个字节
位段的内存分配
1.位段的成员可以是int,unsigned int, signed int 或者是 char 等类型
2.位段的空间上是按照需要以4个字节(int )或者1个字节( char )的方式来开辟的。
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
//一个例子
struct s
{
char a:3;
char b:4;
char c:5;
char d:4;
};
int main()
{
struct S s={0};
s.a = 10;
s.b= 12;
s.c = 3;
s.d = 4;
return 0;
}
//空间是如何开辟的?