C语言复习第五天

宏定义与使用分析

1、#define是预处理器处理的单元实体之一
2、#define定义的宏可以出现在程序的任意位置
3、#define定义之后的代码都可以使用这个宏

定义宏常量

1、#define定义的宏常量可以直接使用
2、#define定义的宏常量本质为字面量

下面定义的宏常量正确吗?

#define ERROR -1
#define PATH1 "D:\test\test.c"    
#define PATH2 D:\test\test.c	 
#define PATH3 D:\test\		      

int main()
{
   int err=ERROR;
   char *p1=PATH1;     //true
   char *p2=PATH2;	   //false
   char *p3=PATH3;	   //false
}
test.c

因为宏的本质是替换,预编译后的结果(gcc -E test.c -o test.i)如下:

# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.c"

int main()
{
    int err = -1;
    char* p1 = "D:\test\test.c";
    char* p2 = D:\test\test.c;
    char* p3 = D:\testtest.c;
}

宏定义表达式

1、#define表达式的使用类似函数调用
2、#define表达式可以比函数更强大
3、#define表达式比函数更容易出错

示例:

#include <stdio.h>

#define _SUM_(a, b) (a) + (b)
#define _MIN_(a, b) ((a) < (b) ? (a) : (b))
#define _DIM_(a) sizeof(a)/sizeof(*a)

int main()
{
    int a = 1;
    int b = 2;
    int c[4] = {0};

    int s1 = _SUM_(a, b);
    int s2 = _SUM_(a, b) * _SUM_(a, b);   //直接替换
    int m = _MIN_(a++, b);				  //a++替换a,导致最后m=3
    int d = _DIM_(c);					  //可以计算数组大小,函数是不可以的。
    									  //函数不能传递数组,只能传递首指针
    
     printf("s1 = %d\n", s1);
     printf("s2 = %d\n", s2);
     printf("m = %d\n", m);
     printf("a = %d\n", a);
     printf("d = %d\n", d);

    return 0;
}

程序运行结果
在这里插入图片描述
预编译的结果:

# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.c"
int main()
{
    int a = 1;
    int b = 2;
    int c[4] = {0};

    int s1 = (a) + (b);
    int s2 = (a) + (b) * (a) + (b);
    int m = ((a++) < (b) ? (a++) : (b));  //a在这里加了两次
    int d = sizeof(c)/sizeof(*c);

     printf("s1 = %d\n", s1);
     printf("s2 = %d\n", s2);
     printf("m = %d\n", m);
     printf("d = %d\n", d);

    return 0;
}

宏表达式与函数的对比

1、宏表达式被预处理器处理,编译器不知道宏表达式的存在
2、宏表达式用"实参"完全代替形参,不进行任何运算
3、宏表达式没有任何的"调用"开销
4、宏表达式中不能出现递归定义
#include <stdio.h>
#define _SUM_(n)(n>0?(_SUM_(n-1)+n):0)
int main()
{
  int s=_SUM_(10);
  printf("%d",s);
  return 0;  
}

编译结果:提示找不到_SUM_的宏定义。
在这里插入图片描述

宏定义的常量或表达式是否有作用域限制

示例:结果: #define定义之后的代码都可以使用这个宏,没有作用域的限制。

 #include <stdio.h>

void def()
{
    #define PI 3.1415926
    #define AREA(r) r * r * PI
}

double area(double r)
{
    return AREA(r);
}

int main()
{
    double r = area(5);

    printf("PI = %f\n", PI);
    printf("d = 5; a = %f\n", r);
    
    return 0;
}

在这里插入图片描述

强大的内置宏

含义示例
_ FILE_被编译的文件名file1.c
_ LINE_当前行号25
_ DATE_被编译的日期Jan 31 2012
_ TIME_编译时的时间17:01:01
_ STDC_编译器是否遵循标准C规范1

示例:

#include <stdio.h>
#include <malloc.h>
#define MALLOC(type,x)  (type*)malloc(sizeof(type)*x)
#define FREE(p)         (free(p),p=NULL)
#define LOG(s)   printf("[%s] {%s:%d} %s\n",__DATE__,__FILE__,__LINE__,s);
#define FOREACH(i,m) for(i=0;i<m;i++)
#define BEGIN {
#define END   }
int main()
{
    int x = 0;
    int* p = MALLOC(int, 5);
    LOG("Begin to run main code...");   
    FOREACH(x, 5)
    BEGIN
        p[x] = x;
    END    
    FOREACH(x, 5)
    BEGIN
        printf("%d\n", p[x]);
    END    
    FREE(p);    
    LOG("End");
  return 0;
}

运行结果:
在这里插入图片描述

宏小结

  1. 预处理器直接对宏进行文本替换
  2. 宏使用时的参数不会进行求值和运算
  3. 预处理器不会对宏定义的语法进行检查
  4. 宏定义时出现的语法错误只能被编译器检测
  5. 宏定义的效率高于函数调用
  6. 宏的使用会带来一定的副作用

条件编译使用分析

基本概念

  1. 条件编译的行为类似于C语言中的if……else……
  2. 条件编译是预编译提示指令,用于控制是否编译某段代码
#define C 1

int main()
{
  #if(C==1)
    printf("C==1\n");
  #else 
  	printf("C!=1\n");
  #endif
  return 0;
}

条件编译的本质

  1. 预编译器根据条件编译指令有选择的删除代码
  2. 编译器不知道代码分支的存在
  3. if…else…语句在运行期间进行分支判断
  4. 条件编译指令在预编译期进行分支判断
  5. 可以通过命令行定义宏

命令行定义宏:
gcc -Dmacro=value file.c
gcc -Dmacro file.c
示例:

int main()
{
  #ifdef C
    printf("C==1\n");
  #else 
  	printf("C!=1\n");
  #endif
  return 0;
}

在这里插入图片描述

条件编译可以解决头文件重复包含的编译错误

示例:
C头文件中加入:

#ifndef _HEADER_FILE_H
#define _HEADER_FILE_H

//soruce code

#endif

条件编译的意义

  1. 条件编译使得我们可以按照不同的条件编译不同的代码段,因而产生不同的目标代码
  2. #if…#else…#endif被预编译处理,而if…else…语句被编译器处理,必然被编译进目标代码
  3. 实际工程条件编译主要用于一下两种情况:
    1、不同产品线共用一份代码
    2、区分编译产品的调试版和发布版
    示例:
#include <stdio.h>
//#include "product.h"

#if DEBUG
    #define LOG(s) printf("[%s:%d] %s\n", __FILE__, __LINE__, s)
#else
    #define LOG(s) NULL
#endif

#if HIGH
void f()
{
    printf("This is the high level product!\n");
}
#else
void f()
{
}
#endif

int main()
{
    LOG("Enter main() ...");
    
    f();
    
    printf("1. Query Information.\n");
    printf("2. Record Information.\n");
    printf("3. Delete Information.\n");
    
    #if HIGH
    printf("4. High Level Query.\n");
    printf("5. Mannul Service.\n");
    printf("6. Exit.\n");
    #else
    printf("4. Exit.\n");
    #endif
    
    LOG("Exit main() ...");
    
    return 0;
}

在这里插入图片描述

#error和#line使用分析

#error的用法

  1. #error用于生成一个编译错误的消息
  2. 用法
    #error message
    tips message 不用双引号包围
    #error编译指示字用于自定义程序员特有的编译错误消息*(程序不会生成可执行文件)*
    类似的#warning用于生成编译警告*(程序会生成可执行文件)*
  3. #error是一种预编译指示字
  4. #error可用于提示编译条件是否满足

示例:编译一个C++程序:

#include <stdio.h>
#ifndef __cplusplus
    #error This file should be processed with C++ compiler.  //用来提示编译器是否支持                      
#endif                                                        //C++编译器
class CppClass
{
private:
    int m_value;
public:
    CppClass()
    {
        
    }
    ~CppClass()
    {
    }
};
int main()
{
    return 0;
}

用gcc编译的结果:报出我们自定义的错误
在这里插入图片描述

#error在条件编译中的运用

#include <stdio.h>

void f()                  
{
#if ( PRODUCT == 1 )
    printf("This is a low level product!\n");
#elif ( PRODUCT == 2 )
    printf("This is a middle level product!\n");
#elif ( PRODUCT == 3 )
    printf("This is a high level product!\n");
#else
	#error The macro PRODUCT is not define !  //#error提示必须宏定义一类产品
#endif
}

int main()
{
    f();
    
    printf("1. Query Information.\n");
    printf("2. Record Information.\n");
    printf("3. Delete Information.\n");

#if ( PRODUCT == 1 )
    printf("4. Exit.\n");
#elif ( PRODUCT == 2 )
    printf("4. High Level Query.\n");
    printf("5. Exit.\n");
#elif ( PRODUCT == 3 )
    printf("4. High Level Query.\n");
    printf("5. Mannul Service.\n");
    printf("6. Exit.\n");
#endif
    return 0;
}

两次编译的结果:
在这里插入图片描述

#line的用法

  1. #line用于强制指定新的行号和编译文件名,并对源程序的代码进行重新编号
  2. 用法
    #line number filename
    filename可省略

tips
#line编译指示字的本质是重定义_LINE_和_FILE_

示例:

#include <stdio.h>

// The code section is written by A.
// Begin
#line 1 "a.c"

// End

// The code section is written by B.
// Begin
#line 1 "b.c"

// End

// The code section is written by Delphi.
// Begin
#line 1 "delphi_tang.c"
int main()
{
    printf("%s : %d\n", __FILE__, __LINE__);
    
    printf("%s : %d\n", __FILE__, __LINE__);
    
    return 0;
}

// End

运行结果:
在这里插入图片描述

#pragma使用分析

  1. #pragma用于指示编译器完成一些特定的动作
  2. #pragma所定义的很多指示字是编译器特有的
  3. #pragma在不用的编译器间是不可移植的
    ——预处理器将忽略它不认识的#pragma指令
    ——不同的编译器可能以不同的方式解释同一条#pragma指令
    一般用法 :#pragma parameter
    tips 不同的parameter参数语法和意义各不相同

#pragma message

  1. message参数在大多数的编译器中都有相似的实现
  2. message参数在编译时输出消息到编译输出窗口中
  3. message 用于条件编译中可提示代码的版本信息
    tips与 #error 和#warning 不同,#pragma仅代表一条编译消息,不代表程序错误
    示例:
#include <stdio.h>
#if defined(ANDROID20)
    #pragma message("Compile Android SDK 2.0...")
    #define VERSION "Android 2.0"
#elif defined(ANDROID23)
    #pragma message("Compile Android SDK 2.3...")
    #define VERSION "Android 2.3"
#elif defined(ANDROID40)
    #pragma message("Compile Android SDK 4.0...")
    #define VERSION "Android 4.0"
#else
    #error Compile Version is not provided!
#endif
int main()
{
    printf("%s\n", VERSION);

    return 0;
}

在这里插入图片描述

pragma once

  1. #pragma once用于保证头文件只被编译一次
  2. #pragma once是编译器相关的,不一定被支持

tips
效率稍微低,但编译器统统支持
#ifndef _HEADER_FILE_H
#define _HEADER_FILE_H

                            vs            #pragma once(效率高,但编译器不一定支持)

//source code

#endif

prgama pack

  1. 什么是内存对齐?
    ——不同类型的数据在内存中按照一定的规则排列
    ——而不一定是顺序的一个接一个的排列
    在这里插入图片描述
    内存对齐规则两步:
    1、第一步:进行数据成员对齐:
    以上面例子举例:
    struct Test1
    {
    char c1;
    short s;
    char c2;
    int i;
    }
    char 1字节偏移地址为0 ,short 2两字节 ,那么从0地址到short类型的内存大小必须是2的倍数,cpu才好更高效率的访问,所以此时变为4字节,char 类型 1字节,和首地址的偏移是4,1能被4整除,所以不需要补内存,而int 是四个字节,此时的偏移为5,所以要以四字节补齐,所以第三个char类型变成了四字节,总的大小也为12字节
    2、第二步:进行结构本身的对齐:
    对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,该结构中最大数据成员长度为int,占4字节,而默认的#pragma pack 指定的值为8,所以结果本身按照4字节对齐,结构总大小必须为4的倍数,

示例:

#include <stdio.h>

#pragma pack(2)
struct Test1
{
    char  c1; 
    short s;  
    char  c2;
    int   i; 
};
#pragma pack()
#pragma pack(1)
struct Test2
{
    char  c1; 
    short s;  
    char  c2;
    int   i; 
};
#pragma pack(
struct Test3
{
    int   i;
    short s;   
    char  c1;
    char  c2;
   
};

int main()
{
    printf("sizeof(Test1) = %d\n", sizeof(struct Test1));
    printf("sizeof(Test2) = %d\n", sizeof(struct Test2));
    printf("sizeof(Test3) = %d\n", sizeof(struct Test3));
    return 0;
}

结果:
在这里插入图片描述

小结

  1. #pragma用来指定编译器完成一些特定的动作
  2. #pragma所定义的很多指示字是编译器持有的
    ——#pragama message 用于自定义编译消息
    ——#pragma once 用于保证头文件只被编译一次
    ——#pragma pack用于指定内存对齐方式

# 和 ##的意义

#运算符

  1. #运算符用于在预处理期将宏参数转换为字符串
  2. #的转换作用是在预处理期完成的,因此只在宏定义中有效
  3. 编译器不知道#的转换作用

示例:

#include <stdio.h>
#define STRING(x) #x
int main()
{
    
    printf("%s\n", STRING(Hello world!));
    printf("%s\n", STRING(100));
    printf("%s\n", STRING(while));
    printf("%s\n", STRING(return));
    return 0;
}

在这里插入图片描述

运算符的妙用(打印函数的提示信息并调用函数)

#include <stdio.h>

#define CALL(f, p) (printf("Call function %s\n", #f), f(p))
   
int square(int n)
{
    return n * n;
}

int func(int x)
{
    return x;
}

int main()
{
    int result = 0;
    
    result = CALL(square, 4);
    
    printf("result = %d\n", result);
    
    result = CALL(func, 10);
    
    printf("result = %d\n", result);

    return 0;
}

在这里插入图片描述

##运算符

  1. ##运算符用于在预处理期粘连两个标识符
  2. ##运算符的连接作用只在预处期完成的,因此只在宏定义中有效

示例:

#include <stdio.h>

#define NAME(n) name##n

int main()
{
    
    int NAME(1);
    int NAME(2);
    
    NAME(1) = 1;
    NAME(2) = 2;
    
    printf("%d\n", NAME(1));
    printf("%d\n", NAME(2));

    return 0;
}

在这里插入图片描述

简单为结构体命别名

示例:

#include <stdio.h>
#define STRUCT(type) typedef struct _tag_##type type;\
                     struct _tag_##type
STRUCT(Student)
{
    char* name;
    int id;
};

int main()
{
    
    Student s1;
    Student s2;
    
    s1.name = "s1";
    s1.id = 0;
    
    s2.name = "s2";
    s2.id = 1;
    
    printf("s1.name = %s\n", s1.name);
    printf("s1.id = %d\n", s1.id);
    printf("s2.name = %s\n", s2.name);
    printf("s2.id = %d\n", s2.id);

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值