c语法高阶—(联合体,枚举,位域,编译器,宏定义,条件编译,条件编译,头文件)
目录
一 联合体(重要)
特性
总结
二 枚举(重要)
特性
总结:
三 位域(了解)
定义
特性
使用场景
优缺点分析表
位域的特点和使用方法
总结:
四 编译器(linux)(重要)
常用编译选项
分阶段编译流程
1. 预处理(此时假设要处理的文件为hello.c)
2. 编译
3. 汇编
4. 链接
基本使用流程
1. 编写源代码
2. 完整编译(一步到位)
五 宏定义加强(重要)
六 条件编译(重要)
七 头文件(重要)
项目结构
编辑
1. 创建头文件(utils.h)
2. 实现文件(utils.c)
3. 主程序(main.c)
编译与使用
八,typedef关键字
一 联合体(重要)
特性
特性 联合体(union) 结构体(struct) 定义方式 union 名称 { 类型 成员; ... };
struct 名称 { 类型 成员; ... };
内存分配 所有成员共享同一内存地址 每个成员有独立内存地址 存储空间 大小为最大成员的尺寸 大小为所有成员尺寸之和(含填充) 访问方式 同一时刻只能有效访问一个成员 可同时访问所有成员 类型安全 低(需自行管理当前有效类型) 高(成员类型明确) 典型应用 类型转换、协议解析、硬件寄存器访问 数据聚合、对象建模 赋值 当给联合一个成员赋值名,其他成员的值则失效
不会失效
总结
C 共用体 | 菜鸟教程
之上链接足可以了解,同一时间,联合体只能有效访问一个成员。
#include <stdio.h>/*
联合体与结构体非常相似,但是他们本质上完全不一样
结构体每个成员都有独立的内存
联合体的成员共用一个内存空间,因此联合体也被称为共用体语法:
union 标签{}联合体可以定义多个成员,同一时间,只有一个内存,只有一个成员的值有效1.尺寸由成员最大决定2.成员地址一样,一个内存空间3.为一个成员赋值,其他成员值失效
*/union wahaha
{int a;char c;long age;char w;
};
int main(int argc, char const *argv[])
{union wahaha www;printf("%lu\n", sizeof(www.age));return 0;
}
二 枚举(重要)
特性
特性/场景 说明 定义方式 enum 名称 { 常量1, 常量2... };
常量管理 集中管理相关常量,避免魔法数字 可读性 使用有意义的名称代替原始值 类型安全 C中本质是 int
,可隐式转换;C++中为独立类型内存占用 通常使用 int
存储(4字节),编译器可能优化适用场景 状态码、有限选项集合、模式标志、错误代码 优势 代码自文档化、易维护、避免重复值冲突 缺点 作用域污染(C中枚举常量全局可见)、无法表示复杂数据类型
总结:
#include <stdio.h>//方案1 用宏定义
// #define MON 1
// #define TUE 2
// #define WED 3
// #define THU 4
// #define FRI 5
// #define SAT 6
// #define SUN 7/*枚举C语言中的一种基本数据类型,用于定义一组具有离散值的常量,它可以让数据更简洁,更易读。
枚举类型通常用于为程序中的一组相关的常量取名字,以便以程序的可读性和维护性;语法:enum 标签{常量1,常量2...};c语言中的枚举中的元素数据类型都必须是整型 int(有符号、无符号均可)
*//*
1.枚举中的常量第一个不赋值 默认为0
2.后续不赋值默认在前一个基础+1*/
enum DAY{MON=1,TUE=2,WED=3,THU=4,FRI=5,SAT=6,SUN
};int main(int argc, char const *argv[])
{enum DAY d1=SUN;printf("%d\n",d1);d1=MON;printf("%d\n",d1);d1=TUE;printf("%d\n",d1);//枚举的元素遍历(里面的值必须都是连续的),否则不能执行下面的遍历for (d1 = MON; d1<= SUN; d1++){printf("%d\n",d1);}return 0;
}
三 位域(了解)
定义
特性 描述 限制条件 定义方式 在结构体中使用 类型 成员名 : 位数;
声明位数需≤类型长度(int通常≤32) 内存分配 按需分配位空间,可能跨字节存储 编译器决定具体布局 取值范围 有符号类型保留1位符号位 无符号类型可多用1位 访问方式 使用成员运算符 .
访问无法取地址(&操作非法) 填充规则 相邻位域类型相同时可能合并存储 不同类型通常换存储单元 典型应用 硬件寄存器、协议字段、标志位集合 内存敏感场景 特性
维度 位域 常规变量 存储粒度 位级(1-32位) 字节级(8的倍数) 内存效率 高(紧凑存储) 低(可能浪费空间) 访问速度 较慢(需位操作) 较快(直接访问) 可移植性 低(编译器实现差异) 高(标准明确) 调试可见性 差(二进制形式) 好(直接查看值) 适用场景 硬件寄存器、协议字段 常规数据处理 使用场景
应用场景 示例说明 优势体现 硬件寄存器 外设控制寄存器位映射 精确控制硬件位 网络协议 TCP头中的标志位(SYN/ACK等) 紧凑解析协议字段 嵌入式系统 设备状态标志集合 节省有限内存资源 压缩存储 存储大量布尔值 减少内存占用(8倍于bool数组) 数据包格式 自定义二进制协议 精确控制每个位的含义 优缺点分析表
优点 缺点 1. 极致内存利用率 1. 可移植性差 2. 直观操作硬件寄存器 2. 访问速度较慢 3. 提升标志位代码可读性 3. 调试困难 4. 减少位操作代码复杂度 4. 不可取地址 5. 自动位掩码生成 5. 类型限制(通常整型) 位域的特点和使用方法
- 定义位域时,可以指定成员的位域宽度,即成员所占用的位数。
- 位域的宽度不能超过其数据类型的大小,因为位域必须适应所使用的整数类型。
- 位域的数据类型可以是
int
、unsigned int
、signed int
等整数类型,也可以是枚举类型。- 位域可以单独使用,也可以与其他成员一起组成结构体。
- 位域的访问是通过点运算符(
.
)来实现的,与普通的结构体成员访问方式相同。总结:
1.节约内存,其他还算简单。
问题一:
my.b = 10; // 有符号的
printf("b=%d\n", my.b); //输出为-6
解答:
#include <stdio.h>/*
C语言的位域(bit-field)是一种特殊的结构体成员,允许我们按位对成员进行定义,指定其占用的位数;
指定占用位数的数据类型只能是int/unsigned int;位域与共用体,其共同目的是减少内存的使用,提高运行的效率,这在板子开发中,有极其重要的作用
*/struct bf
{// 指定的位数不能超过C语言规定的位数大小int a : 4; // 指定其占用4位int b : 4; // 指定其占用4位unsigned c : 2; // 指定其占用2位// 空域,占着位数,但是不能赋值,没给名字int : 4; // 指定其占用4位// 从下一个单元开始存储新的成员数据unsigned d : 4;
};int main(int argc, char const *argv[])
{// 声明该位域的 变量struct bf my;// 1.为a赋值,a的位数为18,即 00 0000 0000 0000 0000my.a = 1;printf("a=%d\n", my.a);// 2.为b赋值,b的位数为4,即0000my.b = 5; // 有符号的printf("b=%d\n", my.b);// 3.无符号,为c赋值,c的位数为0,即00my.c = 3; // 无符号的printf("c=%d\n", my.c);// 4.无符号,为c赋值,c的位数为4,即0000my.d = 13; // 无符号的printf("d=%d\n", my.d);// 4.无符号,为c赋值,c的位数为4,即0000my.b = 10; // 有符号的printf("b=%d\n", my.b);// 4.无符号,为c赋值,c的位数为4,即0000my.d = 10; // 无符号的printf("d=%d\n", my.d);// 求其字节数,按照最大的进补原则printf("%lu\n", sizeof(my)); // 4return 0;
}
四 编译器(linux)(重要)
常用编译选项
选项 作用描述 示例 -o <file>
指定输出文件名 gcc -o demo demo.c
-Wall
开启所有警告信息 gcc -Wall test.c
-g
包含调试信息(GDB使用) gcc -g debug.c
-O<level>
优化级别(0-3,s) gcc -O2 optimize.c
-I<dir>
指定头文件搜索目录 gcc -I./include src.c
-L<dir>
指定库文件搜索目录 gcc -L./lib main.c -lmylib
-l<library>
链接指定库 gcc main.c -lm
(链接数学库)-D<macro>
定义预处理宏 gcc -DDEBUG debug.c
分阶段编译流程
1. 预处理(此时假设要处理的文件为hello.c)
gcc -E hello.c -o hello.i
作用:
展开头文件(
#include
)宏替换(
#define
)删除注释
添加行号标识
2. 编译
gcc -S hello.i -o hello.s
生成:汇编代码文件(
hello.s
)
3. 汇编
gcc -c hello.s -o hello.o
生成:目标文件(
hello.o
,二进制机器码)
4. 链接
gcc hello.o -o hello
作用:链接库文件和其他目标文件生成可执行文件
基本使用流程
1. 编写源代码
// hello.c
#include <stdio.h>
int main() {printf("Hello, Linux C!\n");return 0;
}
2. 完整编译(一步到位)
gcc hello.c -o hello # 编译生成可执行文件hello
./hello # 运行程序
输出:
Hello, Linux C!
五 宏定义加强(重要)
宏(macro)其实就是一个特定的字符串,用来直接替换,编译的时候直接替换为对应的宏的内容(字符串替换);
宏的作用:
1.使得程序的可读性有所提高,使用一个有意义的单词来表示一个无意义数字(某个值)
2.方便对代码进行迭代更新,如果代码中有多处使用到该值,则只需要修改一处即可
3.提高程序的执行效率,可以使用宏来实现一个比较简单的操作,用来替代函数的调用(宏表达式)
#include <stdio.h>
/*
宏,就是一个特定的字符串
*/// 1.无参,就是定义常量
#define a 1// 无值宏
#define NO_VALUE// 3.有参
// 宏只能写在一行(逻辑行),多行红使用转义符\来链接
// 本质是字符串替换,有时候会出现逻辑错误,需要使用()提升优先级。
#define MAX(a, b) a > b ? a : bint main(int argc, char const *argv[])
{// 1. 使用无参数宏,就是一个预处理的模板,其实就是定义常量printf("%d\n", a);// 2.无值宏int c = 1;int d = 2;int e = (c, d == 100 ? 100 : 200 ? 1000: 2000);printf("%d\n", e);return 0;
}
六 条件编译(重要)
指令 描述 #define 定义宏 #include 包含一个源代码文件 #undef 取消已定义的宏 #ifdef 如果宏已经定义,则返回真 #ifndef 如果宏没有定义,则返回真 #if 如果给定条件为真,则编译下面代码 #else #if 的替代方案 #elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 #endif 结束一个 #if……#else 条件编译块 #error 当遇到标准错误时,输出错误消息 #pragma 使用标准化方法,向编译器发布特殊的命令到编译器中
#include <stdio.h>/*
条件编译:根据某一个条件来决定,是否需要编译,不同的操作系统,编译不同*/// 1.无值宏
#define HERON ;// 有值宏 条件编译
// 使用有值红田间只能是数字bool值,不可以是字符串
#define M1 0
#define M2 0
#define M3 0int main(int argc, char const *argv[])
{/*无值宏的使用*/
#ifdef HERONprintf("已经定义无值宏\n");
#endif#ifndef HERONprintf("无定义无值宏\n");
#endif/*有值宏条件编译1*/
#if M1printf("有值0000000\n");
#elif M2printf("有值aaaaaaaa\n");
#elif M3printf("有值9999999\n");
#elseprintf("都不成立\n");
#endifreturn 0;
}
七 头文件(重要)
特性 描述 文件扩展名 .h
主要作用 声明函数原型、宏定义、类型定义等公共内容 包含方式 #include <系统头文件>
或#include "自定义头文件"
编译阶段 预处理阶段展开( gcc -E
可查看)最佳实践 1. 使用头文件守卫
2. 声明与实现分离
3. 避免包含实现代码
项目结构
1. 创建头文件(utils.h)
#ifndef UTILS_H // 头文件守卫开始
#define UTILS_H// 函数声明
int add(int a, int b);
void print_result(int result);// 宏定义
#define MAX(a, b) ((a) > (b) ? (a) : (b))// 结构体声明
struct Point {int x;int y;
};#endif // UTILS_H // 头文件守卫结束
2. 实现文件(utils.c)
#include <stdio.h>
#include "utils.h" // 包含自定义头文件int add(int a, int b) {return a + b;
}void print_result(int result) {printf("结果:%d\n", result);
}
3. 主程序(main.c)
#include <stdio.h> // 系统头文件
#include "utils.h" // 自定义头文件int main() {struct Point p = {10, 20};int sum = add(p.x, p.y);printf("最大值:%d\n", MAX(p.x, p.y));print_result(sum);return 0;
}
编译与使用
看不到问我
gcc ./file/*.c -o ./指定文件目录/自定义文件名 -I ./头文件的文件目录名
八,typedef关键字
c语言提供了typedef关键字,为类型取一个新的名字
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:
#include <stdio.h>
/*
c语言提供了typedef关键字,为类型取一个新的名字
*/// 1.1
// 关键字
typedef int my_int;// 1.2 给枚举/结构体/共用体/位域 取别名
// 共用体 联合体取别名
typedef union gift
{int a; // 4char c; // 1long age; // 8char name[32]; // 32
} gg;int main(int argc, char const *argv[])
{// 1.my_int i = 1;printf("%d\n", i);// 2.gg g1;int test = g1.a = 1;printf("%d\n", test);return 0;
}