C10节:预处理,宏定义,结构体,条件编译,联合与枚举

预处理指令是C语言支持的一种特殊的指令
预处理指令都以#做开头,不以;做结尾
预处理指令在编译的第一个阶段被处理
预处理指令的作用是修改程序源代码
gcc命令可以使用-E选项单独处理所有预处理指令

#define是一个预处理指令
这个预处理指令用于实现宏定义    
宏定义可以给数字起个名字
    
定义宏的时候不要使用=(赋值操作符)
宏名称中不应该有空格
宏和变量完全不同

/*
    宏定义练习
*/
#include <stdio.h>
#define   PI              3.14f
#define   CIRCLE(r)       2 * PI * r
#define   AREA(r)         PI * r * r
int main() {
    int radius = 0;
    //float pi = 3.14f;
    printf("请输入一个半径:");
    scanf("%d", &radius);
    printf("周长是%g\n", CIRCLE(radius));
    printf("面积是%g\n", AREA(radius));
    return 0;
}

练习:
    1.使用宏改写圈叉棋游戏中显示棋盘的代码

/*
    圈叉棋的练习
*/
#include <stdio.h>
#define    FIRST_PLAYER         'O'
#define    SECOND_PLAYER        'X'
#define    EMPTY                '.'
#define    SIZE                 3
int main() {
    int map[SIZE][SIZE] = {1, 2, 1, 0, 1, 2, 0, 2, 1};
    int row = 0, col = 0;
    for (row = 0;row <= SIZE - 1;row++) {
        for (col = 0;col <= SIZE - 1;col++) {
            if (!map[row][col]) {
                printf("%c", EMPTY);
            }
            else if (map[row][col] == 1) {
                printf("%c", FIRST_PLAYER);
            }
            else {
                printf("%c", SECOND_PLAYER);
            }
        }
        printf("\n");
    }
    return 0;
}

可以使用-D选项在编译时定义宏

宏还可以用来给一个计算公式起名字
宏也可以带参数
宏的参数没有类型(宏的参数不一定是个数)

/*
    带参数宏和函数的区别练习
*/
#include <stdio.h>
#define   NEG(num)             0 - num
#define   INC(num)             num++
int neg(int num) {
    return 0 - num;
}
void inc(int num) {
    num++;
}
int main() {
    int value = 0;
    printf("NEG(-5)是%d\n", NEG(-5));
    printf("neg(-5)是%d\n", neg(-5));
    printf("value是%d\n", value);
    INC(value);
    printf("value是%d\n", value);
    inc(value);
    printf("value是%d\n", value);
    return 0;
}

练习:
    1.编写计算圆面积的宏
    
宏不需要使用返回值变量
如果一个宏要计算出一个结果数字则只能使用三木运算符编写
宏的参数没有形参和实参之分
    
练习:
    1.编写一个宏计算两个数字的最大数
    2.编写一个宏把一个十六进制数位转换成十进制数字

/*
    宏练习
*/
#include <stdio.h>
#define MAX(n,n1)     n > n1 ? n : n1
#define HEX(n)   n >= '0' && n <= '9' ? \
    n - '0' : (n >= 'a' && n <= 'z' ? \
     n - 'a' + 10 : n - 'A' + 10)
int main() {
    printf("MAX(3, 7)是%d\n", MAX(3, 7));
    printf("MAX(13, 7)是%d\n", MAX(13, 7));
    printf("HEX('7')是%d\n", HEX('7'));
    printf("HEX('c')是%d\n", HEX('c'));
    printf("HEX('E')是%d\n", HEX('E'));
    return 0;
}

/*
    宏练习
*/
#include <stdio.h>
#define NEG(n)        (0 - (n))
int main() {
    printf("10 - NEG(5)是%d\n", 10 - NEG(5));
    printf("NEG(2 - 3)是%d\n", NEG(2 - 3));
    return 0;
}


    
宏的代码中要在每个参数外边以及整个计算表达式外边都加上小括号。
    
练习:
    1。编写计算两个数字乘积的宏

/*
    乘积宏练习
*/
#include <stdio.h>
#define MUL(x,y)     ((x) * (y))
int main() {
    printf("MUL(2, 6)是%d\n", MUL(2, 6));
    printf("MUL(2 + 3, 8 - 2)是%d\n", MUL(2 + 3, 8 - 2));
    printf("28 / MUL(2, 7)是%d\n", 28 / MUL(2, 7));
    return 0;
}


    
宏操作符#和##
#操作符可以把宏的参数变成字符串字面值
##操作符可以把参数中某个标示符和其他内容合并得到新标示符    

/*
    宏操作符练习
*/
#include <stdio.h>
#define STR(n)              #n
#define GLOBAL(n)           g_##n
int main() {
    int GLOBAL(value) = 0;
    printf("STR(2 + 3)是%s\n", STR(2 + 3));
    return 0;
}


    
预处理指令可以实现条件编译的效果
#ifdef/#ifndef...#else...#endif结构可以基于某个宏是否被定义过从两个分支中选择一个进行编译。

#if...#elif(n次)...#else(可有可无)...#endif结构可以基于任何逻辑表达式把程序分成多个分支,从中选择一个分支编译。

/*
    条件编译练习
*/
#include <stdio.h>
//#define   ONE
int main() {
#if  defined(ONE)
    printf("1\n");
#elif defined(TWO)
    printf("2\n");
#elif defined(THREE)
    printf("3\n");
#else
    printf("4\n");
#endif
#if 1
    printf("测试\n");
#endif
    return 0;
}

多文件编写的基本要求
1.每个.c源文件有一个对应的.h头文件,其中包含.c文件中所有函数的预声明
2.每个.c源文件应该使用#include预处理指令包含对应的.h头文件
3.如果某个.c源文件使用了某个.h头文件中声明的函数则也应该使用#include包含那个头文件

//add.c

#include "10add.h"
static int value = 0;
void add(int num1, int num2) {
    value = num1 + num2;
}

//add.h
#ifndef __10ADD_H__
#define __10ADD_H__
void add(int , int);
#endif  //__10ADD_H__

/*
    多文件练习
*/
#include <stdio.h>
#include "10add.h"
extern int value;
int main() {
    add(2, 3);
    printf("add(2, 3)是%d\n", value);
    return 0;
}


    
每个头文件中都应该使用条件编译避免多次被编译
头文件中的宏名称应该从文件名变换得来
    
extern关键字可以在某个源文件中把另外一个源文件中已经声明的全局变量再次声明一遍。
这样声明后这个文件中的语句才可以使用这个全局变量

静态全局变量不可以被其他文件中的语句所使用

结构体可以把多种不同类型的信息合并成一个整体
一个结构体是一种数据类型
    
结构体声明语法如下
struct person {
    int id;
    char name[20];
    float salary;
};
大括号内部的变量声明仅仅描述结构体内部的结构,并不会真正生成变量。
    
结构体可以作为数据类型直接声明变量,例如
struct person p;
其中p是结构体变量名

可以通过如下语法使用结构体变量中的各部分
p.id,p.name,p.salary

可以在声明结构体的时候立刻声明结构体变量(这个时候可以省略结构体名称)    
    
结构体声明可以单独放在头文件中,这样所有语句就都可以使用它了。
    
C语言中结构体内部不可以定义函数

/*
    结构体练习
*/
#include <stdio.h>
#include <string.h>
struct person {
   int id;
   char name[20];
   float salary;
};
int main() {
    /*struct person {
        int id;
        char name[20];
        float salary;
    };*/
    struct person p;
    /*struct {
        int id;
        char name[20];
        float salary;
    } p;*/
    p.id = 1;
    strcpy(p.name, "abc");
    p.salary = 10.45f;
    return 0;
}

结构体也可以采用数组初始化的语法进行初始化
    
typedef关键字可以用来给现有数据类型起别名
给数据类型起别名是一定要使用typedef关键字而不应该使用#define预处理指令

/*
    typedef练习
*/
#include <stdio.h>
struct month {
    char name[10];
    int days;
};
//typedef struct month month;
#define mon struct month
typedef int * PINT;
//#define PINT     int *
int main() {
    //mon m = {"Jan", 31};
    PINT   p_num = NULL, p_num1 = NULL;
    return 0;
}


同类型的结构体变量之间可以互相赋值,结果和整数类型变量互相赋值一样

结构体变量做参数时应该声明成指针类型,这样可以节省时间和空间。

一个变量的地址一定是它的大小的整数倍
double类型的地址是4的整数倍
这个规则叫做数据对齐

结构体中各部分之间是有空隙的

结构体内部各部分的顺序不同则结果大小不同

结构体变量的大小一定是其中占地最大的变量的整数倍
double数据按照4个字节大小计算
这叫做数据补齐。
数据补齐可能造成结构体最后有浪费的字节

/*
    结构体练习
*/
#include <stdio.h>
struct point {
    int x;
    int y;
};
struct rect {
    struct point top_left;
    struct point bottom_right;
};
int main() {
    struct rect r;
    int area = 0;
    printf("请输入左上角的坐标:");
    scanf("%d %d", &r.top_left.x, &r.top_left.y);
    printf("请输入右下角的坐标:");
    scanf("%d %d", &r.bottom_right.x, &r.bottom_right.y);
    area = (r.bottom_right.x - r.top_left.x) * (r.bottom_right.y - r.top_left.y);
    area = area < 0 ? 0 - area : area;
    printf("面积是%d\n", area);
    return 0;
}


/*
    结构体练习
*/
#include <stdio.h>
typedef struct {
    int id;
    char name[20];
    float salary;
} person;
void read(person *p_person) {
    printf("请输入id:");
    scanf("%d", &p_person->id);
    printf("请输入姓名:");
    scanf("%s", p_person->name);
    printf("请输入工资:");
    scanf("%g", &p_person->salary);
}
void show(const person *p_person) {
    printf("id是%d\n", p_person->id);
    printf("name是%s\n", p_person->name);
    printf("salary是%g\n", p_person->salary);
}
int main() {
    person p1 = {1, "abc", 4.78f};
    person *p_person = &p1;
    read(p_person);
    show(p_person);
    return 0;
}


联合可以用不同的方式操作同样的内存空间
对联合中任何一部分做的修改都会影响另一部分
联合变量所占空间大小是其中最大的部分所占的空间大小

/*
    枚举练习
*/
/*#define    SPR                0
#define    SUM                1
#define    AUT                2
#define    WIN                3*/
#include <stdio.h>
int main() {
    typedef enum {SPR, SUM = 6, AUT, WIN} season;
    enum {RED, WHITE, BLACK};
    season s = WIN;
    printf("AUT是%d\n", AUT);
    return 0;
}

枚举类型可以用来批量的把名称转换成整数
枚举和宏定义有区别(宏定义关心对应的数字而枚举不关心对应的数字)

/*
    联合练习
*/
#include <stdio.h>
/*union u1 {
    int value;
    char ch[4];
};*/
typedef union {
    int value;
    char ch[4];
} u1;
int main() {
    u1 u;
    u.value = 0;
    u.ch[0] = 'a';
    printf("整数数值是%d\n", u.value);
    printf("sizeof(u1)是%d\n", sizeof(u1));
    return 0;
}


    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值