1、宏定义define

#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。

  (1)、无参宏定义

格式:#define   宏名  字符串

#表示这是一条预处理命令,所有的预处理命令都以 # 开头。宏名是标识符的一种,命名规则和变量相同。字符串可以是数字、表达式、if 语句、函数等。

  字符串可以是常量、表达式、格式串等

  注意:字符串为表达式时该加括号时记得加括号,如果字符串后面有分号会连分号一同替换。

  例如:

#define N 100

  N为宏名,100是宏的内容(宏所表示的字符串)。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。

  宏定义是由源程序中的宏定义命令#define完成的,宏替换是由预处理程序完成的。

  宏定义只是简单的字符串代换,是在预处理完成的,而typedef是在编译时处理的,它不是简单地代换,而是对类型说明符重新命名。

  宏定义的作用域包括从宏定义命名起到源程序结束,如果要终止其作用域可以使用#undef命令来取消宏定义:

格式:#undef 标识符

  (2)、带参宏定义

格式:#define 宏名(形参表)  字符串

  注意:宏名域形参表之间不能有空格出现,否则会把形参当做字符串处理

  C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。

  带参宏定义和函数的区别:

  带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。

  (3)C语言宏参数的字符串化和宏参数的连接

  在宏定义中,有时还会用到###两个符号,它们能够对宏参数进行操作:

  #用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。例如有如下宏定义:

typescript const与readonly的区别 typedef const_字符串

#define STR(s) #s

那么:
printf("%s", STR(c.biancheng.net));
printf("%s", STR("c.biancheng.net"));

分别被展开为:
printf("%s", "c.biancheng.net");
printf("%s", "\"c.biancheng.net\"");

typescript const与readonly的区别 typedef const_字符串

  可以发现,即使给宏参数“传递”的数据中包含引号,使用#仍然会在两头添加新的引号,而原来的引号会被转义。  

  ##称为连接符,用来将宏参数或其他的串连接起来。例如有如下的宏定义:

typescript const与readonly的区别 typedef const_字符串

#define CON1(a, b) a##e##b
#define CON2(a, b) a##b##00

那么:
printf("%f\n", CON1(8.5, 2));
printf("%d\n", CON2(12, 34));

将被展开为:
printf("%f\n", 8.5e2);
printf("%d\n", 123400);

typescript const与readonly的区别 typedef const_字符串

  对 #define 用法的几点说明

  (1)宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。

  (2)宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。

  (3)宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。

  (4)代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替。

  (5)宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。

  (6)习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。

  (7)可用宏定义表示数据类型,使书写方便。

  应注意用宏定义表示数据类型和用 typedef 定义数据说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。

2、typedef数据类型重命名

  typedef是C语言的关键字,其作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(char、int等)和自定义数据类型(struct等)。

格式:typedef  数据类型   自定义数据类型;

  在大型程序开发中,typedef的应用非常广泛。目的有两点,一是给变量一个易记且意义明确的新名字,二是简化一些比较复杂的类型声明。

  2.1、typedef 和 #define 的区别

  typedef 在表现上有时候类似于 #define,但它和宏替换之间存在一个关键性的区别。正确思考这个问题的方法就是把 typedef 看成一种彻底的“封装”类型,声明之后不能再往里面增加别的东西。

  (1)可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。如下所示:

#define INTERGE int
unsigned INTERGE n;  //没问题
typedef int INTERGE;
unsigned INTERGE n;  //错误,不能在 INTERGE 前面添加 unsigned

  (2)在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型,而 #define 则无法保证。例如:

#define PTR_INT int *
PTR_INT p1, p2;

    经过宏替换以后,第二行变为:

int *p1, p2;

    这使得 p1、p2 成为不同的类型:p1 是指向 int 类型的指针,p2 是 int 类型。

    相反,在下面的代码中:

typedef int * PTR_INT
PTR_INT p1, p2;

    p1、p2 类型相同,它们都是指向 int 类型的指针。

  (3)执行时间不同

    关键字typedef在编译阶段有效,由于是在编译阶段,因此typedef有类型检查的功能。

    #define则是宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查。

  (4)、功能有差异

    typedef用来定义类型的别名,定义与平台无关的数据类型,与struct的结合使用等。

    #define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

  (5)、作用域不同

    #define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。

    而typedef有自己的作用域。

3、const

  有时候我们希望定义这样一种变量,它的值不能被改变,在整个作用域中都保持固定。例如,用一个变量来表示班级的最大人数,或者表示缓冲区的大小。为了满足这一要求,可以使用const关键字对变量加以限定:

  我们经常将 const 变量称为常量(Constant)。创建常量的格式通常为:

const type name = value;

  const 和 type 都是用来修饰变量的,它们的位置可以互换,也就是将 type 放在 const 前面:

type const name = value;

  但我们通常采用第一种方式,不采用第二种方式。另外建议将常量名的首字母大写,以提醒程序员这是个常量。

  由于常量一旦被创建后其值就不能再改变,所以常量必须在定义的同时赋值(初始化),后面的任何赋值行为都将引发错误。

  (1)用const来定义一个常量

int const a; 
const int a;

    定义了a为整形常量,只能读取a的值,不能更改a的值。a的值一般在定义时对其进行初始化,比如: int const a = 10;

    如果在作为函数形参时,会在函数调用时对其进行初始化。

  (2)用const来定义一个指针常量

const int *p1;
int const *p2;
int * const p3;

  在最后一种情况下,指针是只读的,也就是 p3 本身的值不能被修改;在前面两种情况下,指针所指向的数据是只读的,也就是 p1、p2 本身的值可以修改(指向不同的数据),但它们指向的数据不能被修改。

  当然,指针本身和它指向的数据都有可能是只读的,下面的两种写法能够做到这一点:

const int * const p4;
int const * const p5;

  const 和指针结合的写法多少有点让初学者摸不着头脑,大家可以这样来记忆:const 离变量名近就是用来修饰指针变量的,离变量名远就是用来修饰指针指向的数据,如果近的和远的都有,那么就同时修饰指针变量以及它指向的数据。

  (3)const 和函数形参

  在C语言中,单独定义 const 变量没有明显的优势,完全可以使用#define命令代替。const 通常用在函数形参中,如果形参是一个指针,为了防止在函数内部修改指针指向的数据,就可以用 const 来限制。

  在C语言标准库中,有很多函数的形参都被 const 限制了,下面是部分函数的原型:

size_t strlen ( const char * str );
int strcmp ( const char * str1, const char * str2 );
char * strcat ( char * destination, const char * source );
char * strcpy ( char * destination, const char * source );
int system (const char* command);
int puts ( const char * str );
int printf ( const char * format, ... );

  (4)const 和非 const 类型转换

  当一个指针变量 str1 被 const 限制时,并且类似const char *str1这种形式,说明指针指向的数据不能被修改;如果将 str1 赋值给另外一个未被 const 修饰的指针变量 str2,就有可能发生危险。因为通过 str1 不能修改数据,而赋值后通过 str2 能够修改数据了,意义发生了转变,所以编译器不提倡这种行为,会给出错误或警告。

  也就是说,const char *char *是不同的类型,不能将const char *类型的数据赋值给char *类型的变量。但反过来是可以的,编译器允许将char *类型的数据赋值给const char *类型的变量。

  这种限制很容易理解,char *指向的数据有读取和写入权限,而const char *指向的数据只有读取权限,降低数据的权限不会带来任何问题,但提升数据的权限就有可能发生危险。

  C语言标准库中很多函数的参数都被 const 限制了,但我们在以前的编码过程中并没有注意这个问题,经常将非 const 类型的数据传递给 const 类型的形参,这样做从未引发任何副作用,原因就是上面讲到的,将非 const 类型转换为 const 类型是允许的。