组合类型-预处理-文件组织
1.结构体
1.1 结构体设计
格式:
struct 结构体名称{
成员;
};
例子:
struct Person{
char name[32]; //存储名字 8*4
char sex[2];//存储性别1*4
int age ;//存储年龄1*4
float height;//存储身高1*4
float weight;//存储体重1*4
char type;
};
大小计算:规则结构体按照字节对齐方式来分配空间(结构体最大成员类型来对齐,如果有指针按8/4字节对齐), 比如是4字节对齐每一次分配空间就分配4字节。
⭐⭐结构体是自定义数据类型, 本身结构体是不占用内存空间, 在定义对象后才分配内存空间
1.2 定义结构体变量
- 指明所赋值的变量时,可以打乱顺序:
struct Person jack = {.name="jack",
.sex="男",
.age = 19,
.weight = 140,
.height = 180}; //定义初始化可以直接赋值
- 不指明所赋值的变量时,需要按结构体设计时候的成员顺序赋值:
struct Person rose = {"rose","女",19,180,140};
- 成员是数组时需要注意:
struct Person tom ;
tom.name = "tom"; //这里的tom.name本身就是name, name是数组名称, 是常量不能被赋值
scanf("%s", tom.name); //这里是可以直接输入
1.3 结构体成员访问
1.3.1 对象访问
struct Person jack ; // 对象访问成员用(点.) jack.name, jack.age
1.3.2 指针访问
struct Person *ptr = &jack; // 指针访问成员用(箭头->) ptr->name, ptr->age
struct Person *ptr = (struct Person*)malloc(sizeof(struct Person)); //为结构体指针分配堆内存
free(ptr);
ptr->age = 20;
strcpy(ptr->name, "j ack");
练习
设计一个学生结构体(姓名, 年龄, 学号) 并且定义对象jack, rose, tom, 输入学生信息初始化, 最后输出所有学生信息。
#include <stdio.h>
#include <stdlib.h>
struct Student{
char name[32];
int age;
int number;
};
int main(void){
struct Student jack, rose, tom; //⭐这是一个好方法
struct Student *array[3] = {&jack, &rose, &tom}; //⭐好思路
for(int i=0; i<3; i++){
printf("请输入学号:");scanf("%d", &array[i]->number);
printf("请输入姓名:");scanf("%s", array[i]->name);
printf("请输入年龄:");scanf("%d", &array[i]->age);
}
printf("%d-%s-%d\n", jack.number, jack.name, jack.age);
printf("%d-%s-%d\n", rose.number, rose.name, rose.age);
printf("%d-%s-%d\n", tom.number, tom.name, tom.age);
}
1.4 结构体大小计算
#pragma pack(1) //指定按1字节对齐
//32位, 64位
struct Data{
char a; //1*8
int b; //0
char (*c)[32]; //1*8
char *p; //1*8
char name[12]; //2*8
};//__attribute__((packed)); //16 //不额外补齐__attribute__((packed));
#pragma pack(0) //恢复系统字节对齐
1.5 typedef 定义新类型
typedef struct Student {
char name[32];
int age;
int number;
} StuType, *StuPtr;
struct Student jack; 等价于 StuType jack;
struct Student *ptr; 等价于 StuPtr ptr;
1.6 结构体与函数、数组
typedef struct Data{
int linux;
int cpro;
} Data, *Dptr;
1.函数参数
若参数为指针,比较习惯用Data*,而不是Dptr
void show_data(struct Data* data)
{
printf("%d, %d\n", data->linux, data->cpro) ;
}
2.函数返回值
若返回值为指针,比较习惯用Data*,而不是Dptr
struct Data* create_data(int l, int c){
struct Data *dataptr = (struct Data*)malloc(sizeof(Data));
dataptr->linux = l;
dataptr->cpro = c;
return dataptr;
}
3.结构体数组
struct Data arrayData[40] ;
1.7 细节/扩展
1.7.1 memcpy
可以用于结构体数组拷贝
声明在 <string.h> 头文件
void *memcpy(void *dest, const void *src, size_t n);
参数说明:
- dest:目标地址,数据将被复制到这里。它是一个
void *类型,可以指向任何类型的数据。 - src:源地址,从这里复制数据。它也是一个
const void *类型,指向要复制的数据源。 - n:要复制的字节数。
n是一个size_t类型的整数,指定从src开始复制多少个字节。
返回值:
memcpy返回目标地址dest,也就是dest指针本身。
功能:
memcpy 会从源地址 src 开始,复制 n 个字节到目标地址 dest。复制的数据字节数是由 n 参数决定的。它不会进行重叠检测,所以如果 src 和 dest 区域重叠,结果是未定义的(这种情况可以用 memmove 来处理,它会安全地处理内存重叠)。
例子:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, world!";
char dest[20];
// 使用 memcpy 复制数据
memcpy(dest, src, strlen(src) + 1); // +1 包括 '\0' 结尾符
// 打印目标数组
printf("Dest: %s\n", dest);
return 0;
}
1.7.2 结构体位域⭐
struct Data{
unsigned char a:2; //a这个成员占用char里面0,1位(2位)
unsigned char b:1;
unsigned char :4; //跳过4个位
unsigned char c:1; //c占用1位
};
int main(void){
struct Data data;
data.b=0;
data.c=3;
}
例子:
#include <stdio.h>
struct Data{
unsigned char a:2; //a这个成员占用char里面0,1位(2位)
unsigned char b:1;
unsigned char :4; //跳过4个位
unsigned char c:1; //c占用1位
};
//用位域与联合实现对某一个数的对应位快速修改而不影响其他位
union UData{
unsigned char data;
struct BData{
unsigned char a0:1;
unsigned char a1:1;
unsigned char a2:1;
unsigned char a3:1;
unsigned char a4:1;
unsigned char a5:1;
unsigned char a6:1;
unsigned char a7:1;
}bitD;
};
int main(void){
struct Data data;
data.a=3;
data.c=2;
printf("%d\n", data.a);
union UData GPIO;
GPIO.data = 0x55; //10010101
GPIO.bitD.a6=0;
GPIO.bitD.a7=1;
printf("%x\n", GPIO.data);
}
1.7.3 避免头文件重复加载⭐⭐
//声明
#ifndef _DATA_H
#define _DATA_H //避免头文件重复加载
1.8 作业
1.8.1 设计一个简单的学生信息管理系统
定义一个结构体 Student,包含学生的姓名(char[50] 类型)、学号(int 类型)、年龄(int 类型)和成绩(float 类型)。
创建一个函数 addStudent,该函数用于添加新的学生信息到一个 学生数组StudentArray中。数组的最大容量为 100。
创建一个函数 displayStudents,用于显示数组中所有学生的信息。
在 main 函数中,调用 addStudent 函数添加至少 3 个学生的信息,然后调用 displayStudents 函数显示这些信息。
⭐⭐用到memcpy,用于拷贝结构体数组
struct Student{
char name[50];
int number;
int age;
float score;
}; //自定义数据类型 64
//stuArray是存储所有学生信息结构体数组, num当前存储的学生数量, stu新的学生信息
bool addStudent(struct Student* stuArray, int *num, struct Student *stu)
{
if(*num >= 100) return false;
int i=*num; (*num) += 1;
strcpy(stuArray[i].name, stu->name);
stuArray[i].number = stu->number;
stuArray[i].age = stu->age;
stuArray[i].score = stu->score;
//memcpy⭐
memcpy(&stuArray[i], stu, sizeof(struct Student));
}
void displayStudents(struct Student* stuArray, int num)
{
for(int i=0; i<num; i++)
{
printf("%d , %s , %d, %f\n", stuArray[i].number, stuArray[i].name,stuArray[i].age,stuArray[i].score);
}
}
int main(void){
struct Student stuArray[100]; //存储所有学生的信息结构体数组
int num = 0;//记录当前学生信息个数
for(int i=0; i<3; i++){
struct Student stu;
printf("学号:");scanf("%d", &stu.number);
printf("姓名:");scanf("%s", stu.name);
printf("年龄:");scanf("%d", &stu.age);
printf("成绩:");scanf("%f", &stu.score);
addStudent(stuArray, &num, &stu) ;
}
displayStudents(stuArray, num);
return 0;
}
1.8.2 图书信息管理
定义一个结构体 Book,包含图书的书名(char[100] 类型)、作者(char[50] 类型)、出版年份(int 类型)和价格(float 类型)。
创建一个包含 5 本图书信息的 Book 数组,并初始化这些信息。
编写一个函数 sortBooksByPrice,使用冒泡排序算法根据图书的价格对数组进行升序排序。
编写一个函数 displayBooks,用于显示数组中所有图书的信息。
在 main 函数中,调用 sortBooksByPrice 函数对图书数组进行排序,然后调用 displayBooks 函数显示排序后的图书信息。
⭐⭐结构体数组元素可以直接赋值用于交换数据,而不必要结构体的一个成员一个成员地复制。
char *msg[5]={"红楼梦,曹雪芹,1978,99"
,"三国演义,罗贯中,1978,199"
,"西游记,吴承恩,1978,89"
,"水浒传,施耐庵,1987,137"
,"本草纲目,李时珍,1876,299"};
struct Book{
char name[100];
char autr[50];
int year;
float price;
};
bool cmp(struct Book*a, struct Book *b)
{
return a->price>b->price;
}
void sortBooksByPrice(struct Book* bookArray, int size, bool(*cmp)(struct Book*, struct Book *) ){
//void sortBooksByPrice(struct Book* bookArray, int size){
for(int i=0; i<size-1; i++){
for(int j=0; j<size-1-i; j++)
{
if( cmp(&bookArray[j], &bookArray[j+1]) )
//if(bookArray[j].price >bookArray[j+1].price)
{
struct Book t = bookArray[j];//⭐⭐
bookArray[j] = bookArray[j+1];
bookArray[j+1] = t;
}
}
}
}
void displayBooks(struct Book* bookArray, int size){
for(int i=0; i<size; i++){
printf("%s-%s-%d-%f\n", bookArray[i].name,bookArray[i].autr,bookArray[i].year,bookArray[i].price);
}
}
int main(void){
struct Book bookArray[5];
for(int i=0; i<5; i++){
sscanf(msg[i],"%[^,],%[^,],%d,%f", bookArray[i].name,bookArray[i].autr, &bookArray[i].year,&bookArray[i].price);
}
displayBooks(bookArray, 5);
printf("--------------------------\n");
sortBooksByPrice(bookArray, 5, cmp);
displayBooks(bookArray, 5);
return 0;
}
2.联合体
联合体(共用体)自定义数据类型, 用关键字union来声明,联合体里面的所有成员共用一个成员的内存空间(最大那个成员的内存空间)。
2.1 结构
union 联合体名称{
成员(与结构体一样)
};
union Data{
int a;
long b;
}; //联合体大小是与b的大小一样(8字节)
2.2 定义变量
//定义联合体变量,与访问成员与结构体一致
union Data data;
data.b = 0x1234567812345678;
2.3 在字节序上的应用⭐⭐
union ByteOder{
unsigned short a;
unsigned char b;
};
union ByteOder data;
data.a = 0x1234;
if(data.b == 0x12){
printf("大端序\n");
}
3.枚举
枚举就是整形常量, 枚举作用是为了增强代码可读性
3.1 声明
enum 枚举名称{枚举项,枚举项,枚举项,枚举项} //默认从0开始依次递增
⭐⭐注意:若没有初始化赋值,则默认从0开始。
例子:
enum WeekDay{SUN=0,MON=1,TUE=2,WEN=3};
enum COLOR{red=0xff0000, green=0x00ff00, blue=0x0000ff}; //常量
3.2 定义变量
enum COLOR mycolor; //枚举变量只能赋值枚举项里面内容
例子:
enum COLOR{red=0xff0000, green=0x00ff00, blue=0x0000ff}; //常量
void show_color(enum COLOR c){
switch(c){
case red: printf("红色\n"); break;
case green: printf("绿色\n"); break;
case blue: printf("蓝色\n"); break;
}
}
int main(void){
unsigned int color = 0x0;
scanf("%x", &color);
show_color((enum COLOR)color);
switch(color){
case red: printf("红色\n"); break;
case green: printf("绿色\n"); break;
case blue: printf("蓝色\n"); break;
}
}
4.预处理
4.1 宏定义
4.1.1 定义
宏(macro)实际上就是一段特定的字串,在源码中用以替换为指定的表达式。
一般宏用⭐大写字符串表示。
#define SIZE 10
#define PI 3.14
4.1.2 作用
- 使得程序更具可读性:字串单词一般比纯数字更容易让人理解其含义。
- 使得程序修改更易行:修改宏定义,即修改了所有该宏替换的表达式。
- 提高程序的运行效率:程序的执行不再需要函数切换开销,而是就地展开。
4.1.3 例子
宏参数必须要用括号, 表达式也必须用括号⭐
#include <stdio.h>
//定义常量
#define SIZE 20
//定义带参数宏函数
#define AREA(r) ((r)*(r)*3.14) //宏参数必须要用括号, 表达式也必须用括号⭐
//#define AREA(r) (r*r*3.14)
double area(int r){
return r*r*3.14
}
//定义表达式
#define MID (5+6) //表达式也必须用括号
int main(void)
{
int a = SIZE;
printf("%d\n", SIZE)
int c = SIZE;
int r = 5;
double result = AREA(r+1);
int d = 5*MID
}
4.1.4 宏函数应用-DEBUG(x)
可以用来检查哪些代码有没有出问题。
#include <stdio.h>
#define DEBUG(x) printf("%s-%d-%s\n", __FILE__, __LINE__, x)//分别输出文件名、行号、x值
int main(void)
{
DEBUG("测试宏函数");
}
预处理后如下:
int main(void)
{
printf("%s-%d-%s\n", "main.c", 5, "测试宏函数");
}
检查代码例子:

- 如果111111、222222、333333都出来了,意味着代码没有问题。
- 如果只有111111出来了,说明111111和222222之间的代码有问题。
4.2 内联函数
4.2.1 定义
在函数声明前面加inline。
调用内联函数的时候, 不会跳转, 而是把函数的代码搬运到到调用的位置执行。
一般应用于代码简短, 函数被调用的非常频繁。
4.2.2 与宏定义区别
- 内联函数是一个函数, 会有语法和类型检测, 宏函数不是真正的函数, 不会有类型检测。
- 代码替换的时间不一样,宏(预处理), 内联函数在编译后
4.2.3 宏定义的符号粘贴
4.2.3.1 变量属于非字符串
在书写非字符串的字串时(没有双引号),使用两边双井号来粘贴字串,并且要注意如果字串出现在最末尾,则最后的双井号必须去除
#define LAYER_INITCALL(layer, num) __zinitcall_##layer##_##num## //(后面的##要去掉)
如下:
#define LAYER_INITCALL(layer, num) __zinitcall_##layer##_##num
使用:
LAYER_INITCALL(service, 1); //得到字串===> __zinitcall_service_1
4.2.3.2 变量属于字符串
如果字串本身拼接为字符串,那么只需要使用一个井号即可,比如:
#define domainName(a, b) "www." #a "." #b ".com"
应用:
domainName(yueqian, cn) //======> "www.yueqian.cn.com"
domainName(baidu, du) //=========> "www.baidu.du.com"
4.3 条件编译
有条件的编译,通过控制某些宏的值,来决定编译哪段代码
4.3.1 有值宏定义
针对#if
#define A 0
#define B 1
#define C 2
#if A
... // 如果 MACRO 为真,那么该段代码将被编译,否则被丢弃
#endif
#if B
...//这里的语句会被编译
#else
...//这里的代码被丢弃
#endif
4.3.2 无值宏定义
#define ARM
#define __cplusplus
应用在条件编译里面的#ifdef,# ifndef。
判断宏 MACRO 是否已被定义,据此决定其所包含的代码段是否要编译。
#ifdef ARM
...//arm平台特有代码
#else
...//x86平台特有代码
#endif
#ifndef ARM
...//X86平台特有代码
#else
...//arm平台特有代码
#endif
例子:
#include <stdio.h>
//#define ARM //定义宏
//#define x86_64
int main(void)
{
#ifdef ARM
printf("gec6818开发板启动\n");
#endif
#ifdef x86_64
printf("x86电脑启动\n");
#endif
}
⭐在编译程序的时候定义宏
gcc -o main main.c -DARM (-D表示定义宏,后面的ARM就是宏)
本文来自博客园,作者:cyt2730,转载请注明原文链接:https://www.cnblogs.com/cytwjyy/p/18872675

浙公网安备 33010602011771号