1、结构体(struct)
结构体类型声明、定义结构体类型变量以及结构体变量的使用
#include <stdio.h>
#include <string.h>
// struct 是一个C语言的一个关键字,用来声明一个结构体类型
// 声明了一个名字叫 student 的结构体类型
// 有两个成员,分别是整型的 id(学号) 字符数组 name(名字)
// struct 只是声明了这样一种数据类型,和普通的数据类型比如int、char
// 一样本身是没有分配空间的,只有在定义变量的时候才会为变量分配空间
struct student
{
int id;
char name[20];
}; //用struct 声明类型的时候要有 ;号,和函数相区分
#if 0
// 变量定义2:直接在结构类型声明的时候定义变量
struct teacher
{
int id;
char name[20];
}t1,t2; // 定义了两个老师的变量 变量名叫 t1、t2
// 变量定义3:结构类型可以不要名字,也就是说不能在其他地方使用了
// 只能在类型声明的时候定义变量
struct
{
int a;
int b;
}a, b; // 定义结构体变量a和b
//
int main()
{
// 结构体的变量定义
// 定义了一个student结构体变量 变量名叫 stu
// 用结构体定义变量的时候,类型要写完整,struct 关键字不能丢
// student stu2; struct 不能丢
struct student stu;
return 0;
}
#endif
// 结构体变量的初始化和成员使用
int main1()
{
// 定义结构体变量并且初始化
struct student stu = {1234, "xiaoming"};
struct student stu2 = {.name="xiaohong", .id=1234};
// 结构的成员变量的使用,结构变量使用成员变量,需要点 . 指明使用的是哪一个成员
printf ("id = %d, name = %s\n", stu.id, stu.name);
}
int main()
{
struct student stu;
// 结构体变量使用
stu.id = 1224;
strcpy(stu.name, "hello");
printf ("id = %d, name = %s\n", stu.id, stu.name);
}
结构体的内存对齐模式
数据项只能存储在地址是数据项大小的整数倍的内存位置上,例如 int 类型占用4个字节,地址只能在0,4,8等位置上。
每 个操作系统都有自己的默认内存对齐系数,如果是新版本的操作系统,默认对齐系数一般都是8,因为操作系统定义的最大类型存储单元就是8个字节,例如 long long,不存在超过8个字节的类型(例如int是4,char是1,long在32位编译时是4,64位编译时是 8)。当操作系统的默认对齐系数与内存对齐的理论产生冲突时,以操作系统的对齐系数为基准。例如:假设操作系统的默认对齐系数是4,那么对与long long这个类型的变量就不满足第一节所说的,也就是说long long这种结构,可以存储在被4整除的位置上,也可以存储在被8整除的位置上。可以通过#pragma pack()语句修改操作系统的默认对齐系数,编写程序的时候不建议修改默认对齐系数。
防止出现二次访问,提高效率但是损耗了空间。
结构体的位域:位字段
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。
位域的具体存储规则
当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。
当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会。
无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。
结构体数组
结构体嵌套
结构体指针
#include <stdio.h>
#include <string.h>
struct student
{
int id;
char name[20];
};
int main1()
{
// 定义了一个结构体变量
// 结构体变量名和数组名进行区分
// 数组名代表数组首个元素的地址
// 结构体变量是一个变量不是地址
struct student stu;
// 定义了一个结构体变量指针
struct student *p = &stu;
// 通过指针使用结构体变量:需要新的运算符->
// ->只是用在结构体指针变量引用成员变量的时候
p->id = 10; // stu.id = 10;
strcpy(p->name, "abcd");
printf ("id = %d, name = %s\n", p->id, p->name);
return 0;
}
void printA(struct student a)
{
a.id = 5678;
printf ("id = %d, name = %s\n", a.id, a.name);
}
void printB(struct student *a)
{
a->id = 5678;
printf ("id = %d, name = %s\n", a->id, a->name);
}
int main()
{
struct student a = {1234, "hello"};
struct student b;
b = a;// 结构体变量之间可以直接赋值
// 结构体变量之间可以直接赋值导致结构变量可以直接作为函数参数进行传递
printB(&b);
printf ("id = %d, name = %s\n", b.id, b.name);
return 0;
}
2、共用体(union)
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
共用体的所有成员起始地址的是一样的。
判断大小端存储方式
#include <stdio.h>
// 小端返回真(1),大端返回假(0)
int isLittleEndian()
{
union
{
char ch;
int a;
}a;
a.a = 1;
return (a.ch == 1);
}
int main()
{
if (isLittleEndian())
printf ("小端\n");
else
printf ("大端\n");
return 0;
}
3、枚举(enum)
#include <stdio.h>
// enum 是C语言的一个关键字,用来声明枚举类型
enum week{ Mon = 10, Tues, Wed, Thurs = 1000, Fri, Sat, Sun };
int main()
{
// 定义一个枚举变量
// 枚举变量day 的取值范围是 week定义的这些常量中的一个
// 没有day.Mon 的形式
enum week day;
day = Mon;
// 枚举定义出来的是一个常量,值不能被改变
// Mon = 8;
printf ("%d\n", Sat);
printf ("%d\n", sizeof(day));
return 0;
}
4、extern
extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义。
5、static
在局部静态变量前面加上关键字static,该局部变量便成了静态局部变量。静态局部变量有以下特点:
(1)该变量在全局数据区分配内存
(2)如果不显示初始化,那么将被隐式初始化为0
(3)它始终驻留在全局数据区,直到程序运行结束
(4)其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。
在全局变量前面加上关键字static,该全局变量变成了全局静态变量。
全局静态变量有以下特点:
(1)在全局数据区内分配内存
(2)如果没有初始化,其默认值为0
(3)该变量在本文件内从定义开始到文件结束可见,即只能在本文件内使用
在函数的返回类型加上static关键字,函数即被定义成静态函数。
静态函数有以下特点:
(1) 静态函数只能在本源文件中使用
(2) 在文件作用域中声明的inline函数默认为static
说明:静态函数只是一个普通的全局函数,只不过受static限制,他只能在所在文件内使用,不能在其他文件内使用。
6、const
const int * p; // p可变,p指向的对象不可变
int const * p; // p可变,p指向的对象不可变
int * const p; // p不可变,p指向的对象可变
const int * const p; // 指针p和p指向的对象都不可变
这里给出一个记忆和理解的方法:
先忽略类型名(编译器解析的时候也是忽略类型名),我们看const离哪个近,“近水楼台先得月”,离谁近就修饰谁。
const (int) *p //const 修饰*p,p是指针,*p是指针指向的对象,不可变。
(int) const * p; //const 修饰*p,p是指针,*p是指针指向的对象,不可变。
( int) * const p;//const 修饰p,p不可变,p指向的对象可变
const ( int)* const p; // 前一个const修饰*p,后一个const修饰p,指针p和p指向的对象都不可变