深度解析Linux-C——结构体(初始化,结构体数组,结构体大小,位段操作,联合体,内存对齐,C的预处理,宏和带参宏,条件编译)

目录

结构体的三种初始化

结构体的两种引用

结构体数组

结构体大小

结构体实现位段操作

联合体

 内存对齐

C的预处理

 带参宏

 条件编译


结构体的三种初始化

定义如下结构体

struct  student 
{
   char name[100];  
   int age;  
   float  height;   
} ;

 1、定义变量时初始化

struct  student  xm = {"小明",18,175.3}

2、不按顺序的初始化

struct  student  xm ={.age=18, .height=175.3, .name="小明"};

3、在定义后初始化

struct  student  xm;
//xm.name = "小明"; // name是数组名是一个常量,常量不能赋值  
strcpy(xm.name,"小明");
xm.age = 18; 
xm.height=173.5;

 

结构体的两种引用

1、结构体变量是普通值时,使用变量名+  “   .   ”   访问

2、结构体变量是指针时,使用指针+   “   ->   ”   访问

结构体数组

定义:

struct student
{
    char name[1024]; // 姓名
    int id;          // 学号
    char clas[1024]; // 班级
};

初始化: 

struct student arry[50] = 
{
    {"小明", 1, "高一三班"},
    {"小东", 2, "高二三班"},
    {"小美", 3, "高三三班"},
};

 

结构体大小

结构体大小是根据 《最大的数据字节》对齐的原则进行分配的! 每次数据分配的空间都是《根据最大的字节数》进行对齐。

 参考:【C语言】结构体内存对齐_编译器默认对齐数-CSDN博客

结构体实现位段操作

位段操作允许程序员直接操作数据的特定位,而不是整个数据结构

从而在需要精确控制硬件或进行高效数据处理时非常有用。

 

带位段操作的结构体的大小

 

联合体

联合体的外在形式跟结构体非常类似,但它们有一个本质的区别:

结构体中的各个成员是各自独立的,而联合体中的各个成员却共用同一块内存,因此联合体也称为共用体。

整个联合体变量的尺寸,取决于联合体中尺寸最大的成员。

给联合体的某个成员赋值,会覆盖其他的成员,使它们失效。

联合体各成员之间形成一种“互斥”的逻辑,在某个时刻只有一个成员有效。

声明联合体:

union attr
{
    int x;
    char y;
    double z;  
};

定义联合体:

int main()
{
    // 定义联合体变量
    union attr n;
}

// 普通初始化:第一个成员有效(即只有100是有效的,其余成员会被覆盖)
union attr at = {100, 'k', 3.14};

// 指定成员初始化:最后一个成员有效(即只有3.14是有效的,其余成员会被覆盖)
union attr at = {
                .x  = 100,
                .y = 'k',
                .z = 3.14,
};

 联合体指针

union attr *p = &at;
p->x = 100;
p->y = 'k';
p->z = 3.14;  // 只有最后一个赋值的成员有效

printf("%d\n", p->x);
printf("%c\n", p->y);
printf("%lf\n", p->z);

 内存对齐

1.修改一个数据的对齐原则 

char c __attribute__((aligned(32))); // 将变量 c 的值、内存对齐值设置为32   

attribute语法:
attribute 机制是GNU特定语法,属于C语言标准语法的扩展。
attribute 前后都是双下划线,aligned两边是双圆括号。
attribute 语句,出现在变量定义语句中的分号前面,变量标识符后面。
attribute 机制支持多种属性设置,其中 aligned 用来设置变量的 m 值属性。
一个变量的内存对齐值只能提升,不能降低,且只能为正的2的n次幂

2.不适用任何的对齐原则 ,直接以真实大小存储 

 __attribute__((packed));

内存对齐,是为了CPU 更高效率取读取,内存的资源进行处理。 修改空间虽然可以节省内存,但是会降低CPU的处理效率。
 

C的预处理

头文件:#include

定义宏:#define

取消宏:#undef

条件编译:#if、#ifdef、#ifndef、#else、#elif、#endif

显示错误:#error

修改当前文件名和行号:#line

向编译器传送特定指令:#progma

一个逻辑行只能出现一条预处理指令,多个物理行需要用反斜杠连接成一个逻辑行

如:

#define LONG_MACRO_NAME \
this is a long macro definition \
that spans multiple lines

 将    LONG_MACRO_NAME   替换为   this is a long macro definition that spans multiple lines

 可以通过如下编译选项来指定来限定编译器只进行预处理操作:

gcc example.c -o example.i -E

 带参宏

//使用 pf 来替换 printf("%d",参数);
#define  pf(参数)  printf("%d",参数);

// 求两个数据的较大值
#define max(a, b) a > b ? a : b

// 求两个数据的较小值
#define min(a, b) a < b ? a : b

注意事项:

带参宏的特点:

直接文本替换,不做任何语法判断,更不做任何中间运算。

宏在编译的预处理阶段就被替换掉,运行中不存在宏。

宏将在所有出现它的地方展开,这一方面浪费了内存空间,另一方面有节约了切换时间。

 使用双井号粘贴字串

#define LAYER_INITCALL(num, layer)  __zinitcall_##layer##_##num

如果使用 LAYER_INITCALL(service, 1),那么预处理器会将其展开为 __zinitcall_service_1;如果使用 LAYER_INITCALL(feature, 2),则会展开为 __zinitcall_feature_2。

如果字串出现在最末尾,则最后的双井号必须去除,如果粘贴的字串并非出现在最末尾,则前后都必须加上双井号

另外,如果字串本身拼接为字符串,那么只需要使用一个井号即可

例如:

#define domainName(a, b) "www." #a "." #b ".com"
int main()
{
    printf("%s\n", domainName(baidu, aaa));
}



//输出www.baidu.aaa.com

 

 条件编译

#define A 0
#define B 1
#define C 2

#if A
    printf("真");
#endif

// 二路分支
#if A
    printf("A");
#elif B
    printf("B");
#endif

// 多路分支
#if A
    printf("A");
#elif B
    printf("B");
#elif C
    printf("C");
#endif

 ifndef和endif语句判断头文件中函数是否被重复定义

如对C的标准库进行定义时:

#ifndef stdio
#define stdio

void printf。。。

。。。scanf。。。

。。。int。。。

。。。float。。。


#endif

有时候多个头文件中包含了相同的函数声明或定义,或者一个程序中重复引用了某些头文件,这样做是为了防止其被重复引用
 

参考:
c语言结构体一篇懂_1.结构体变量的第一个成员在与结构体变量偏移量为0的地址处。-CSDN博客

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值