预处理与位运算

c语言编译步骤

  • 预处理 --- 编译 --- 汇编 --- 链接

什么是预处理

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

查看编译细节,使用下面命令:

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

预处理的功能

宏定义
  • 不带参数的宏定义

    语法格式:

#define 宏名 常量数据(#define 标识符 字符串)

                预处理:此时只做数据替换,不做类型检查

        注意:宏不会占用内存空间(还没有到编译环节,就会被替换成宏中的常量数据)

        宏展开:在预编译时将宏名替换成字符串的过程

  • 带参数的宏定义

    • 语法

对应格式进行输入运算
#define multi(a,b) a * b
int result = MULTI(7+2,3);	//7+2*3=13

宏定义作用域

  • #define出现在函数外,有效范围为定义命令之后到本源文件结束。通常,define命令写在文件开头

  • undef命令终止宏定义的作用域(终止后可以再次定义)

#define PI 3.14
#define DAY 10
void fun()
{
    float s = PI * 2;	//此时PI为3.14
}
#undef PI
#define	PI 3.1415926
void fun1()
{
    float s1 = PI * 2;	//此时PI为3.1415926
    int day = DAY;	//此时day=10
}

宏定义中引用已定义的宏名

#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. #include <xxxx.h>

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

    2. #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 ​​​​​​​

引入自定义头文件
#incude "algorithm.h"
    
int main()
{
    int arr[5] = {1,2,3,4,5};
    
    int res = sum(arr,5);
    
    printf("数组元素累加和为:%d\n", res);
    
    return 0;
}

编译命令:

gcc algorithm.c app.c -o app	//包含关系的c文件要一起编译
./app	//执行文件
条件编译
  • 概念

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

  • 预处理

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

  • 语法

    语法一:

#ifdef 标识		--判断标识是否定义
	..
#else
	..
#endif

        语法二:

#ifndef 标识
	..
#else
	..
#endif

        语法三:

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

案例:

#define M 1

int main(void)
{
    #ifdef M
    	int a = 1+2;
    #else
    	int a = 1*2;
    #endif
    
    printf("结果为:%d\n", a);	//3
    
    return 0;
}
输入一行字母字符,按要求条件编译,全转换为大写或者小写
#include <stdio.h>
    
#define M 0

void main()
{
    char str[20] = "I Love You";
    char c;
    
    int i = 0;
    
    while( (c=str[i]) != '\0')
    {
        i++;
        #if M
        	if(c >= 'A' && c <= 'Z')
            {
                c+=32;
            }
        #else
        	if(c >= 'a' && c <= 'z')
            {
                c-=32;
            }
        #endif
        printf("%c", c);
    }
    printf("\n");
}

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

语法:

#ifndef __XXXX_H
#define __XXXX_H
	...
#endif

案例:

  algorithm.h

#ifndef __ALGORITHM_H
#define __ALGORITHM_H
 
// 数组的累加和计算
extern int sum(const int *p,int len);

#endif

位运算

什么是位运算

针对数据的二进制位进行的相关操作,常用于嵌入式开发领域;

位运算运算符

符号说明符号说明
&按位与(有0为0)~按位取反(0改1,1改0)
|按位或(有1为1)<<按位左移
^按位异或(相同为0,不同为1)>>按位右移

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

- 作用

  `&`:

  1. 将指定位清零(指定位和0与);
  2. 取指定位的数据(指定位和1与);
  3. 保留指定位数据(指定位和1与);

  `|`:

  1. 置位指定位数据(按需求置0或1);

  `^`:

  1. 翻转指定位

<<

  1. 若移出去的高位都是0,a<<n,看作a*2^n;若高位包含1则不适用;

>>

  1. 若移动无符号操作数,高位补0,a>>n,看作a*2^(-n)

  2. 若移动有符号操作数,高位补0,则是逻辑右移;高位补符号位,则是算术右移;大部分遵循算术右移;

位运算赋值符

  • 运算符:&= |= >>= <<= ^=

举例:

a &= b ------ a = a & b;
a <<= 2 ----- a = a << 2;

不同长度数据进行位运算

  • 若不同长度数据(longshort),进行位运算(a & b),会将长度短的对标长度长的;

    • 若b为正数,则左侧补0;

    • 若b为负数,则左侧补1;

    • 若b为无符号整型,则左侧补0;

位运算符应用场景

注意:以下所有场景的n都是从右侧(低位)开始技术为0

  • 将数据从右侧(低位)指定的二进制位(n1,n2,n3...)清零

    • 公式

a &= ~(1<<(n1-1) | 1<<(n2-1) | 1<<(n3-1)...);

举例

char a = 1011 0110	//将1,3,5位清零
    
a &= ~(1<<(1-1) | 1<<(3-1) | 1<<(5-1));	//结果为1010 0010

获取某个数据指定位的数据

  • 公式

(a & (1 << n)) >> n

将数据的指定位设置为1

  • 公式

a |= (1 << n1 | 1 << n2 | 1 << n3 ...);

举例

a = 1010 1010	//将2,4,6设置为1
    
a |= (1 << 2 | 1 << 4 | 1 << 6);	//结果为1010 1010

将数据指定位翻转

  • 公式

a ^= (1 << n);

举例

a = 1010 1010;	//将第2位进行翻转

a ^= (1 << 2);	//结果为1010 1000

位段/位域

  • 概念

段域或位域:在结构体中,以位(bit)为单位的成员;本质是结构体成员,可以通过数字指明所占内存空间大小;

位段长度不能大于内存单元的长度

struct A
{
	char a:1;
	char b:8; //char占一个字节,最大分配8个bit位
	char c:9; //分配9个就是错误的,无法通过编译   
};

一个位段必须存放在一个内存单元中,不能跨两个单元

struct A
{
	char a:7;   //a在一个内存单元中,剩余1个bit位
	char c:3;   //c从下一个内存单元开始,因为上一个内存单元剩余1个bit, 放不下3个bit
	char d:3;   //d和c可以在同一个内存单元中,因为c只占了3个还剩余5个bit,能够放下d的3个bit不用跨内存单元
};

给位段成员赋值时,不能超出成员所占内存

struct S
{
	char a : 2;
	unsigned char b : 4;
	unsigned char c : 5;
};
struct S s = {1, 3};
s.a = 1; //a占两个bit位,并且是有符号数,它能被赋值为-2, -1, 0, 1,2, 3; 除此之外的都会报错
s.b = 10; //b占4个bit位,并且是无符号数,它的取值范围为:[0,15]; 如果取值是[-8,15]编译正常,只是-8在输出是值为15
s.c = 20; //c占5个bit位,并且是无符号数,它的取值范围为:[0,31];如果取值是[-16,31]编译正常,只是-16在输出是值为31

位段成员定义时不能是浮点数,只能是整型和字符型或者他们的无符号形式

struct A
{
	int a:2;
	unsigned char b:4;
	float c:4;  //错误,位段成员不能定义成float或者double
};

位段不能取地址

struct S
{
	char a : 2;
	unsigned char b : 4;
};
struct S s;
printf("%p\n", &s);   //s是结构体变量,可以取地址
printf("%p\n", &(s.a)); //s.a是结构体成员位段a,不能对它取地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值