组合类型-预处理-文件组织

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 定义结构体变量

  1. 指明所赋值的变量时,可以打乱顺序:

struct Person jack = {.name="jack",
.sex="男",
.age = 19,
.weight = 140,
.height = 180}; //定义初始化可以直接赋值

  1. 不指明所赋值的变量时,需要按结构体设计时候的成员顺序赋值:

struct Person rose = {"rose","女",19,180,140};

  1. 成员是数组时需要注意:
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字节)

img

2.2 定义变量

//定义联合体变量,与访问成员与结构体一致
union  Data   data;
data.b  = 0x1234567812345678;

img

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, "测试宏函数");
}

检查代码例子:
image

  1. 如果111111、222222、333333都出来了,意味着代码没有问题。
  2. 如果只有111111出来了,说明111111和222222之间的代码有问题。

4.2 内联函数

4.2.1 定义

在函数声明前面加inline。
调用内联函数的时候, 不会跳转, 而是把函数的代码搬运到到调用的位置执行。
一般应用于代码简短, 函数被调用的非常频繁。

4.2.2 与宏定义区别

  1. 内联函数是一个函数, 会有语法和类型检测, 宏函数不是真正的函数, 不会有类型检测。
  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就是宏)

posted @ 2025-05-12 15:10  cyt2730  阅读(66)  评论(0)    收藏  举报