C语言基础——⑩②预处理和位运算

C语言程序的编译过程

  1. 预处理
  2. 编译
  3. 汇编
  4. 链接

一、什么是预处理

        预处理就是在源文件(如.c文件)编译之前,所进行的一部分预备操作,这部分操作是由预处理程序自动来完成;当源文件在编译时,编译器会自动调用预处理程序来完成对预处理指令的解析,预处理指令解析完成才能进入下一步的编译过程。

        我们为了能够方便的看到这个编译细节,我们可以使用下面命令:

gcc 源文件 -E -o 程序名[.后缀]

二、预处理的功能

1、宏定义

  • 不带参数的宏定义

语法:
#define 宏名 常量数据;
预处理:此时的预处理只做数据替换,不做类型检查

注意:我们定义的宏不会占用内存空间,还没有到编译环节,就已经被替换成了我们宏中的常量数据

宏展开:在预编译时宏名替换成字符串的过程称为“宏展开”。

举例:
#include <stdio.h>
#define PI 3.1415926
void main()
{
    float l,s,r,v;
    printf("input radius:");
    scanf("%f",&r);
    l=2.0*PI*r;
    s=PI*r*r;
    v=4.0/3*PI*r*r*r;
    printf("l=%10.4f\ns=%10.4f\nv=%10.4f\n",l,s,v);
}

  • 带参数的宏定义

语法:
#define 宏名(参数列表) 参数表达式
面试题:
#define multi(a,b) a*b

举例:带参数的宏定义

 /**
* 宏定义——带参数
*/
#include <stdio.h>

#define MULTI(a,b) a * b

int main()
{
    int result = MULTI(7+2,3);
    printf("%d\n",result);// 7+(2*3) = 13

    return 0;
}

  • 带参数的宏和参数的区别:

举例:在宏定义中引用已定义的宏

#include <stdio.h>
#define R 3.0
#define PI 3.1415926
#define L 2*PI*R
#define S PI*R*R
void  main()
{
 printf("L=%f\nS=%f\n",L,S);
}

  • 宏定义的作用域

  1. #define 命令出现在程序中函数的外围,宏名的有效范围为定义命令之后到本源文件结束。通常, #define 命令写在文件开头,函数之间,作为文件一部分,在此文件范围内有效;
  2. 可以用 #undef 命令终止宏定义的作用域。

举例:宏定义的作用域

1、用 #undef 结束宏定义

2、 #undef 后再次定义(结论:不能重复定义宏

3.宏定义的作用域
/**
 * 宏定义的作用域
 */
#define PI 3.14
#define DAY 20
void fun()
{
   float r = 4;
   float s = PI * r * r;
   int day = DAY;
}
#undef PI  // 结束PI的作用范围

#define PI 3.1415926
void fun1()
{
   float r1 = 4;
   float s1 = PI * r1 * r1;
   int day = DAY;
}
void main()
{
    fun();
    fun1();
}

三、文件包含

1、概念

        所谓“文件包含”处理是指一个源文件可以将另外一个源文件的全部内容包含进来。这个适用于多文件开发。

2、预处理

      此时的预处理,是将文件中的内容替换,文件包含指令。  

3、包含方式

  •  #include <xxxx.h>

        系统会到标准库头文件目录(Linux下 /usr/include )查找包含的文件;

  •  #include "xxxx.h"

        在当前工程路径下查找,如果未找到,仍然会到标准库头文件目录查找。

举例:双文件

algorithm.h——自定义头文件,专门用于存放被外部访问的函数的声明

/**
 * 自定义头文件,专门用于存放被外部访问的函数的声明
 */
// 数组的累加和计算
extern int sum(const int *p,int len);

algorithm.c——实现数组元素的累加计算

/**
 * 实现数组元素的累加计算
 */
int sum(const int *p,int len)
{
    int sum = 0;
    register int i = 0;
    for(;i < len; i++)
   {
        sum += *(p+i);
   }
    return sum;
}

app.c

// #include <stdio.h>
// 引入自定义的头文件
#include "algorithm.h"
// 如果有n多个外部函数,难道都要一个个的使用extern进行声明?
// 引入外部函数声明
// extern int sum(const int*,int);
int main()
{
    int arr[5] = {12,33,14,55,34};
    
    int res = sum(arr,5);
    printf("数组累和结果是:%d\n",res);
    return 0;
}

编译命令:

gcc algorithm.c app.c -o app  // 又包含关系的c文件要一起编译

四、条件编译

1、概念

        根据设定的条件选择编译的语句代码。

2、预处理

        将满足条件的语句进行保留,不满足条件的语句进行删除,交给下一步编译

3、语法

1)语法一:

#ifdef 标识    --> 判断标识符定义与否
    ..
#else
    ..
#endif

2)语法二:

#ifndef 标识     --> 判断标识符定义与否
    ..
#else
    ..
#endif

3)语法三:

#if 表达式     --> 根据表达式返回的结果:0-不成立输出else,1-成立输出if
    ..
#else
    ..
#endif

举例:预处理-条件编译——判断标识符定义与否

/**
* 预处理-条件编译
*/
#define MINUS 1  //判断

int main(void)
{
    #ifdef MINUS  // 判断标准是表示有没有定义,不是0或-1
        int a = 7 - 4;
    #else
        int a = 7 * 4;
    #endif

    printf("计算结果为:%d\n",a);

    return 0;

}

举例:根据表达式的返回值,判断输出。

预处理-条件编译:输入一行字母字符,根据需要设置条件编译,使之能将字母全改为大写输出,或全改为小写字母输出。

 /**
 * 预处理-条件编译:输入一行字母字符,根据需要设置条件编译,使之能将字母全改为大写输出,或全改为小写字母输
出。
 */
#include <stdio.h>
// 定义一个标识 
#define LETTER 0
void main()
{
    // 测试用的字母字符数组
    char str[20] = "C Language";
    char c;
    int i;
    i = 0;
    while((c=str[i])!='\0')
   {
        i++;
        #if LETTER
            if(c >='a' && c <='z')
           {
                c -= 32;
           }
        #else
            if(c >='A' && c <='Z')
           {
                c += 32;
           }
        #endif
        printf("%c",c);
   }

    printf("\n");
}
1)#define LETTER 1;
2)#define LETTER 0;

4、避免头文件重复包含的方法

语法:

#ifndef __XXXX_H
#define __XXXX_H
    ...
#endif

举例:自定义头文件,专门用于存放被外部访问的函数的声明

 /**
 * 自定义头文件,专门用于存放被外部访问的函数的声明
 */
#ifndef __ALGORITHM_H
#define __ALGORITHM_H
// 数组的累加和计算
extern int sum(const int *p,int len);
#endif

五、位运算

1、什么是位运算

针对数据的二级制位进行的相关操作,位运算在嵌入式开发领域有着非常重要的应用。

2、位运算常用的运算符

符号说明符号说明
&按位与~按位取反
|按位或<<按位左移
^按位异或>>按位右移

注意:参与位运算的运算量只能是整型或者字符型,不能是实型。

3、位运算符的运算规则

&:按位与:

  • 运算规则:对于左右操作数,只有相应二进制位数据都为1时,结果数据对应位数据为1。
  • 举例:
    // 代表二进制位
    1 & 1 = 1
    1 & 0 = 0
    0 & 1 = 0
    0 & 0 = 0
  • 作用:
  1. 获取某二进制位数据;
  2. 将指定二进制位数据清零
  3. 保留指定位。

|:按位或:

  • 运算规则:对于左右操作数,只要相应二进制位数据有一个为1,结果数据对应位数据为1。
  • 举例:
    // 代表二进制位
    1 | 1 = 1
    1 | 0 = 1
    0 | 1 = 1
    0 | 0 = 0
    
  • 作用:
  1. 置位某二进制位数据;

^:按位异或:

  • 运算规则:对于左右操作数,只要相应二进制位数据相同,结果数据对应位数据为0,不同为1。(相同为0,不同为1
  • 举例:
    // 代表二进制位
    1 ^ 1 = 0
    1 ^ 0 = 1
    0 ^ 1 = 1
    0 ^ 0 = 0
  • 作用:
  1. 翻转;
  2. 值交换
  • 面试题:

~:按位取反:

  • 运算规则:原操作数据将相应二进制位数据:0变1,1变0

按位左移:

  • 运算规则:原操作数所有的二进制位数向左移动指定位;移出的数据舍弃,右边用0补全

注意:

1. 如果移出的高位都是0,我们可以这样理解:a ,可以看做:a * 2^n
2. 如果移出的高位包含1,我们是不能使用以上计算方式的。

按位右移:

  • 运算规则:原操作数所有的二进制位数据向右移动指定位,移出的数据舍弃
    • 如果操作数是无符号数,左边用0补全:
    • 如果操作数是有符号数,左边用什么去补全,取决于计算机系统:
      • 逻辑右移:用0补全;
      • 算数右移:用1补全。

大部分情况下,系统是遵循“算数右移”的。

4、位运算赋值符

  • 运算符:&=, |=, >>=,<<=,^=
  • 举例:
    a &= b 相当于 a = a & b
    a <<=2 相当于 a = a << 2

5、不同长度数据进行位运算

        如果两个数据长度不同(例如long型和short型),进行位运算时(如a & b,而a为long型,b为short型), 系统会将二者按右端对齐。

6、位运算符的应用场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值