C语言 宏定义 条件编译 typedef const

390 阅读7分钟

预处理指令

 什么是预处理指令:

    在我们的文件翻译成0和1之前做的操作我们称之为预处理指令 

    一般情况预处理指令都是以#号开头的

宏定义

宏定义的格式

    1.不带参数的宏定义

    2.带参数的宏定义

1.不带参数的宏定义:

 #define 宏名 值

   宏定义的作用:    

 会在程序翻译成0和1之前, 将所有宏名替换为 宏的值

 

 宏定义在什么时候替换

 源代码 --> 预处理 -->汇编 -->二进制 -->可执行程序

 

 规范:

 一般情况宏名都大写, 多个单词之间用_隔开, 并且每个单词全部大写

 有得公司又要求宏名以k开头, 多个单词之间用驼峰命名

 

注意: 

 宏定义后面不要写分号  

 宏定义也有作用域

 从定义的那一行开始, 一直到文件末尾

 虽然默认情况下宏定义的作用域是从定义的那一行开始, 一直到文件末尾. 但是我们也可以通过对应的关键字提前结束宏定义的作用域

#define COUNT 6

// 提前结束宏定义的作用域

#undef COUNT

 

 宏定义的使用场景:

    http://192.168.13.11/login

    http://192.168.13.11/accesstoken

    http://192.168.13.11/file,,,

#define BASE_URL "http://192.168.13.11/"

    获取屏幕的宽度

    获取手机系统版本号

    做一个单利

    判断系统版本

2.带参数的宏定义

#define PF(v1,v2) ((v1)*(v2))

#define 代表要定义一个宏

 PF 宏的名称

 ((v1)*(v2)) 参数, 注意点, 不需要写数据类型

 (v1)*(v2) 用于替换的内容

 带参数的宏定义注意点

 1.一般情况下建议写带参数的宏的时候, 给每个参数加上一个()

 2.一般情况下建议写带参数的宏的时候, 给结果也加上一个()

为了避免使用宏出现计算顺序问题

带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。所以在定义宏时,一般用一个小括号括住字符串的参数。

什么时候用带参数的宏定义什么时候用函数

     如果函数内部的功能比较简单, 仅仅是做一些简单的运算那么可以使用宏定义, 使用宏定义效率更好, 运算速度更快

     如果函数内部的功能比较复杂, 不仅仅是一些简单的运算, 那么建议使用函数

条件编译

#define SCORE 90

#define DEBUG 1 // 0是调试阶段 1是发布阶段

#if DEBUG == 0

// 调试阶段

#define NJLog(format, ...) printf(format,## __VA_ARGS__)

//#elif DEBUG == 0 
//#elif == else if

//#ifndef SCORE 
// 是不是没有定义名称叫做SCORE的宏,没定义执行后面代码

#else

// 发布阶段

#define NJLog(format, ...)

#endif

条件编译和选则结构if的共同点:

 都可以对给定的条件进行判断, 添加满足或者不满足都可以执行特定的代码

 条件编译和选则结构if的共区别

0.条件编译和if非常非常像

if选择结构会对给定条件进行判断, 如果条件满足就执行if后面大括号中的内容

条件编译也一样, 会对给定的条件进行判断, 如果条件满足就编译条件后面的内容

 1.生命周期不同

    if 运行时

    #if 编译之前

 2.#if需要一个明确的结束符号 #endif

 为什么需要一个明确的结束符号?

如果省略掉#endif, 那么系统就不知道条件编译的范围, 那么会将满足条件之后的第二个条件之后的所有内容都清除

 3.if会将所有的代码都编译到二进制中

  #if只会将满足条件的部分一直到下一个条件的部分 编译到二进制中

注意

预处理指令什么时候执行? 编译之前

变量什么时候定义? 执行了才会定义

注意点: 条件编译不能用来判断变量, 因为不在同一个生命周期

一般情况下, 条件编译是和宏定义结合在一起使用的

为什么要使用条件编译

1)按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。有利于程序的移植和调试。 2)条件编译当然也可以用条件语句来实现。 但是用条件语句将会对整个源程序进行编译,生成的目标代码程序很长,而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目标程序较短。  

 条件编译的优点:

 缩小应用程序的大小

 

 应用场景:

 用于调试和发布阶段进行测试

 调试阶段: 程序写代码的阶段

 发布阶段: 上传到AppStore的阶段

typedef

typedef可以给一个已知的数据类型起别名 (外号)

 利用typedef给数据类型起别名的格式:

 typedef 原有的数据类型 别名(外号);

 注意: 

 1. typedef不仅能给系统原有的数据类型起别名, 也可以给一个自定义的数据类型起别名

 2. 利用typedef给数据类型起别名, 并不会生成一个新的数据类型, 仅仅是给原有的类型起了一个别名而已

注意: 如果是给指向函数的指针起别名, 那么指向函数的指针的指针名称就是它的别名

int sum(int v1, int v2)
{
    return v1 + v2;
}

// functionPotinter == int(*functionPotinter)(int , int)

typedef int(*functionPotinter)(int , int);

int main(int argc, const char * argv[]) {

//    int (*sumP)(int , int ) = sum;
//    指针变量用法

    functionPotinter sumP = sum;

    printf("sum = %i\n", sumP(10 , 20));
    
    return 0;
    
    }

typedef和宏定义的区别

执行时间:

宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便。

一般情况下如果要给数据类型起一个名称建议用typedef, 不要用define

不然连续定义或许会出错

typedef char * String;

#define MY_STRING char *

// 一般情况下如果要给数据类型起一个名称建议用typedef, 不要用define

int main(int argc, const char * argv[]) {

//正确⭕️
    String name1, name2;

    name1 = "lnj";

    name2 = "lmj";

    printf("name1 = %s, name2 = %s\n", name1, name2);

    
//错误❌
//*号只对最近的一个有效, 所以相当于
// char *name3, name4; 
// char *name3; char name4;

    MY_STRING name3, name4;

    name3 = "lk";

    name4 = "xb";

    printf("name3 = %s, name4 = %s\n", name3, name4);

    return 0;

}

const

1.const修饰变量:

将变量转化成常量

2.const修饰指针:

如果const写在指针类型的左边, 那么意味着指向的内存空间中的值不能改变, 但是指针的指向可以改变

如果const写在指针的数据类型和*号之间, 那么意味着指向的内存空间中的值不能改变, 但是指针的指向可以改变

如果const写在指针的右边(数据类型 * const), 那么意味着指针的指向不可以改变, 但是指针指向的存储空间中的值可以改变

     规律:

     如果const写在指针变量名的旁边, 那么指针的指向不能变, 而指向的内存空间的值可以变

     如果const写在数据类型的左边或者右边, 那么指针的指向可以改变, 但是指向的内存空间的值不能改变

//三种写法
//    const int *p = # 指针的指向可以改变
//    int const *p = # 指针的指向可以改变
//    int * const p = # 指向的内存空间的值可以变
*p = 998; // 修改了指针指向的内存空间中存储的值

p = &age;// 修改了指针的指向

为什么用const?

1.函数参数用const修饰,可以放心用

2.节省空间,避免不必要的内存分配

#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ...... double i=Pi; //此时为Pi分配内存,以后不再分配!
double I=PI; //编译期间进行宏替换,分配内存
double j=Pi; //没有内存分配
double J=PI; //再进行宏替换,又一次分配内存! const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存 中有若干个拷贝。

常见的宏

// 如何判断当前是ARC还是MRC?
// 可以在编译的时候判断当前是否是ARC

#if __has_feature(objc_arc)
    NSLog(@"ARC");
#else
    NSLog(@"MRC");
#endif
#define interfaceSingleton(name)  +(instancetype)share##name
//使用,替换name参数,
//interfaceSingleton(Tools)  +(instancetype)shareTools
interfaceSingleton(Tools);

多行一个宏 \