预处理命令(#)

**#**号开头的命令称为预处理命令
C语言源文件需要经过编译、链接后才能生成可执行程序。

#include

**#include**叫作文件包含命令,用来引入对于的头文件(**.h**文件)
#include的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。

用法

  1. 方法1 #include <stdio.h>
  2. 方法2 #include "stdio.h"
  • 使用尖括号**< >**,编译器会到系统路径下查找头文件;
  • 使用双引号**" "**,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找
  • stdio.h这类标准头文件一般存放在系统路径下,而自己编写的头文件,一般存放在当前项目路径下,所以不能使用**< >**,只能使用**" "**
  • 头文件只能包含变量和函数声明,不能包含定义,否则多次引入时会出现重复定义的错误

    宏定义命令(#define)

    宏定义只是简单的字符串替换,由预处理器进行替换处理
    typedef是在编译阶段由编译器处理的,并不是简单的字符串替换,是给原有的数据类型起一定新的名字,作为一种新的数据类型

    宏定义形式

    ```c

    define 宏名 字符串

其中,宏定义中的字符串可以是数字、表达式、if语句、函数等。

  1. <a name="ksrih"></a>
  2. ### #define用法的说明
  3. - 宏定义不是说明或语句,行末不需要加分号;
  4. - 宏定义必须写在函数外面,其作用域是宏定义命令起,一直到源程序结束。
  5. - **如果需要中途终止其作用域,需要使用**`**#undef**`**命令**。
  6. - **代码中的宏名如果被引号包围,那么预处理程序不对其作宏替代**;
  7. ```c
  8. #include <stdio.h>
  9. #define OK 100
  10. int main(){
  11. printf("OK\n"); //打印结果是“OK”而非100
  12. return 0;
  13. }
  • 宏定义允许嵌套;

    1. #define A 100
    2. #define B A*100
  • 习惯上,宏名用大写字母表示,以便于与变量区别(允许小写字母);

  • 可用宏定义表示数据类型; ```c

    define UINT unsigned int

UNIT a,b;

  1. <a name="iofHE"></a>
  2. ### 带参数的宏定义
  3. **对带参数的宏,在展开的过程中不仅要进行字符串替换,还要用实参去替代形参**。
  4. <a name="MmIfw"></a>
  5. #### 带参数宏定义的形式
  6. ```c
  7. #define 宏名(形参列表) 字符串
  8. 其中,字符串中可以含有各个形参。

带参数宏定义的调用

  1. 宏名(实参列表);

举例

  1. /* 用带参数宏定义实现找出两个数据的最大值 */
  2. #include <stdio.h>
  3. #define MAX(a, b) ((a > b) ? a : b)
  4. int main(void)
  5. {
  6. int i, j, max;
  7. printf("请连续输入两个数字,按空格分开: ");
  8. scanf("%d %d", &i, &j);
  9. max = MAX(i, j);
  10. printf("max = %d\n", max);
  11. return 0;
  12. }

带参数宏定义的说明

  • 带参数宏定义中,形参之间可以出现空格,但宏名和形参列表之间不能有空格

    1. 如果:
    2. #define MAX(a, b) ((a > b) ? a : b)
    3. 写成:
    4. #define MAX (a, b) ((a > b) ? a : b)
    5. 编译器将认为MAX是一个无参数宏定义,宏调用结果将是:
    6. (a, b)((a > b) ? a : b)
  • 在带参数宏定义中,不会为形参分配内存空间,因此不必指定数据类型;

  • 在宏定义中,字符串的形参通常要用括号括起来,以避免出错; ```c 示例1:

    include

define A(a) ((a) * (a))

int main(void) { int i, j; printf(“请输入数字: “); scanf(“%d”, &i); j = A(i + 1); printf(“test你好!!!!\n”); printf(“max = %d\n”, j); return 0; } //输入3 //输出16

include

define A(a) a *a

int main(void) { int i, j; printf(“请输入数字: “); scanf(“%d”, &i); j = A(i + 1); printf(“test你好!!!!\n”); printf(“max = %d\n”, j); return 0; } //输入3 //输出7

  1. <a name="N6lGH"></a>
  2. ### 宏参数的字符串化(#)和连接(##)
  3. <a name="LPUiF"></a>
  4. #### 宏参数的字符串化(#)
  5. <a name="c3oIU"></a>
  6. ##### 定义形式
  7. ```c
  8. #define STR(s) #s

用法

**#**用来将宏参数转换为字符串,即在宏参数的开头和末尾添加引号

  1. #include <stdio.h>
  2. #define STR(s) #s
  3. int main(void)
  4. {
  5. printf("test你好!!!!\n");
  6. printf("%s\n", STR(abc)); //实参不添加引号,输出abc
  7. printf("%s\n", STR("abc")); //实参添加引号,输出"abc"
  8. return 0;
  9. }

宏参数的连接(##)

定义形式
  1. #define CONN1(a,b) a##e##b
  2. #define CONN2(a,b) a##b##00

用法

##称为连接符,用来将宏参数或其他的字符串连接起来

  1. #include <stdio.h>
  2. #define CONN1(a, b) a##e##b
  3. #define CONN2(a, b) a##b##00
  4. int main(void)
  5. {
  6. printf("test你好!!!!\n");
  7. printf("%f\n", CONN1(8.8, 2)); //输出880.000000
  8. printf("%d\n", CONN2(12, 34)); //输出123400
  9. return 0;
  10. }

C语言中的预定义宏

  1. ANSI C规定了以下几个预定义宏:
  2. __LINE__:表示当前代码的行号;
  3. __FILE__:表示当前文件的名称;
  4. __DATE__:表示当前的编译日期;
  5. __TIME__:表示当前的编译时间
  6. __STDC__:表示当要求程序严格遵循ANSI C标准时,该标识被赋值为1
  7. __cplusplus:表示当编写C++程序时,该标识符被定义。
  8. 示例:
  9. #include <stdio.h>
  10. #include <stdlib.h>
  11. int main()
  12. {
  13. printf("Date : %s\n", __DATE__);
  14. printf("Time : %s\n", __TIME__);
  15. printf("File : %s\n", __FILE__);
  16. printf("Line : %d\n", __LINE__);
  17. system("pause");
  18. return 0;
  19. }

条件编译

能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。
Windows有专用的宏_Win32,Linux有专用的宏__linux__
VS/VC有两种编译模式,Debug和Release。调试阶段用Debug模式;最终发布程序用Release模式,该模式下,编译器会进行很多优化,提高程序运行效率,删除冗余信息

#if

  1. /* #if的用法 */
  2. #if 整型常量表达式1 //常量表达式中不能包含变量,且为整型
  3. 程序段1
  4. #elif 整型常量表达式2 //#elif可省略
  5. 程序段2
  6. #elif 整型常量表达式3
  7. 程序段3
  8. #else //#else可省略
  9. 程序段4
  10. #endif

#ifdef

  1. /* #ifdef的用法 */
  2. #ifdef 宏名 //只能是宏名,不能是其他
  3. 程序段1 //宏被定义执行
  4. #else //#else可省略
  5. 程序段2
  6. #endif

#ifndef

  1. /* #ifndef的用法 */
  2. #ifndef 宏名 //只能是宏名,不能是其他
  3. 程序段1 //宏未被定义执行
  4. #else //#else可省略
  5. 程序段2
  6. #endif

#error命令

**#error**命令用于在编译期间产生错误信息,并阻止程序的编译

定义形式

  1. #error error_msg

用法

  1. 示例1
  2. #include <stdio.h>
  3. #ifdef WIN32
  4. #error 这个程序不能在mac上运行!!
  5. #endif
  6. int main(void)
  7. {
  8. printf("test你好!!!!\n");
  9. return 0;
  10. }
  11. 示例2
  12. #include <stdio.h>
  13. #ifndef __cplusplus
  14. #error 这个程序不必须以C++方式编译!!
  15. #endif
  16. int main(void)
  17. {
  18. printf("test你好!!!!\n");
  19. return 0;
  20. }

typedef

定义形式

  1. typedef oldName newName; //末尾需要分号
  2. 示例:
  3. //常规定义别名
  4. typedef int INTEGER;
  5. INTEGER a,b;
  6. a = 1;
  7. b = 2;
  8. //给数组定义别名
  9. typedef char ARR20[20]; //ARR20是char [20]的新别名
  10. ARR20 a1; //等价于char a1[20];
  11. //给结构体定义别名
  12. typedef struct stu
  13. {
  14. char name[20];
  15. int age;
  16. char sex;
  17. }STU;
  18. STU body; //等价于struct stu body;
  19. //给指针类型定义别名
  20. typedef int (*POINT_TO_ARR)[4]; //POINT_TO_ARR是int *[4]的新别名,是一个二维数组指针类型
  21. POINT_TO_ARR p; //等价于int *p[4]; //二维数组指针
  22. //给函数指针类型定义别名
  23. typedef int (*POINT_TO_FUNC)(int, int);
  24. POINT_TO_FUNC pfunc;

用法

  1. 示例(为指针类型定义别名):
  2. #include <stdio.h>
  3. typedef char (*POINT_TO_ARR)[30]; //二维数组指针
  4. typedef int (*POINT_TO_FUNC)(int, int); //函数指针
  5. char str[3][30] =
  6. {
  7. "aaa",
  8. "bbb",
  9. "ccc"
  10. };
  11. int max(int a, int b)
  12. {
  13. return ((a > b) ? a : b);
  14. }
  15. int main(void)
  16. {
  17. POINT_TO_ARR parr = str;
  18. POINT_TO_FUNC pfunc = max;
  19. printf("test你好!!!!\n");
  20. printf("max = %d\n",(*pfunc)(3,4)); //输出4
  21. for (int i = 0; i < 3; i++)
  22. {
  23. printf("str[%d]: %s\n", i, *(parr + i)); //读取二维数组列的数据
  24. }
  25. return 0;
  26. }

typedef与#define的区别

  • #define可以使用其他类型说明符对宏类型进行扩展,但typedef不可以; ```c

    define INTEGER int

    unsigned INTEGER n; //ok

typedef int INTEGER; unsigned INTEGER n; //error

  1. - 在连续定义多个变量时,`typedef`可以保证定义的所有变量均为同一类型,但`#define`不可以;
  2. ```c
  3. #define PTR_INT int *
  4. PTR_INT p1,p2; //等价于int *p1;int p2;
  5. typedef int * PTR_INT
  6. PTR_INT p1,p2; //等价于int *p1;int *p2;

const变量

**const**变量被称为常量,它的值不能被改变,在整个作用域中保持固定,不可被修改

定义形式

  1. const type Name = value; //常量在创建后不可修改,初始化时要赋值
  2. 其中,type是数据类型,name是变量名,在const中,typename的位置可以互换,即:
  3. type const Name = value;

const与指针

**const**和指针一起使用时,可以限制指针变量本身,也可以限制指针指向的数据

  1. const int *p1; //指针所指向的数据是只读的,但p1本身的值可被修改
  2. int const *p2; //同上
  3. int * const p3; //指针变量是只读的,p3本身的值不能被修改
  4. const int * const p4; //指针和它所指向的数据都是只读的
  5. int const * const p5; //同上

记忆技巧

**const**后面紧接着指针变量名(如**int * const p3**)==>指针变量是只读的;存在两个**const**既描述数据类型,又描述指针变量(如**const int * const p4**)==>指针变量和它所指向的数据是只读的;剩下的情况,则表示的是指针所指向的数据是只读的

const与函数形参

const通常用在函数的形参中,如果形参是一个指针,为了防止字函数内部修改指针所指向的数据,就可以用**const**来限制

  1. #include <stdio.h>
  2. #include <string.h>
  3. size_t strnch(const char *str, char ch)
  4. {
  5. int count = 0;
  6. int len = strlen(str);
  7. for (int i = 0; i < len; i++)
  8. {
  9. if (str[i] == ch)
  10. {
  11. count++;
  12. }
  13. }
  14. return count;
  15. }
  16. int main(void)
  17. {
  18. char *str = "fjsalfjalgvjljklfjgjklgslkajkl";
  19. char ch = 'a';
  20. int i = strnch(str, ch);
  21. printf("%d\n", i);
  22. return 0;
  23. }

随机数

一般使用**<stdlib.h>**头文件中的**rand()**函数来生成随机数

定义形式

  1. int rand(void);
  • rand()函数可以生成一个0~RAND_MAX之间的随机整数
  • RAND_MAX是头文件中的一个宏,是随机数的最大值,C语言标准并没有规定它的具体数值,只是规定它的值至少为32767。 ```c

    include

    include

int main(){ int a = rand(); printf(“%d\n”,a); return 0; }

  1. <a name="P4GCw"></a>
  2. ## 随机数的本质
  3. rand()函数产生的随机数,**本质是伪随机数**,是根据一个数值按照某个公式推算出来的,这个数值称之为“种子”。种子和随机数之间的关系是一个正态分布。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/566095/1638108152097-aa2c6447-dfa6-4895-9747-795c4ff9bda6.png#clientId=u804bc15e-6399-4&from=paste&height=93&id=u6fdaa4cc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=185&originWidth=445&originalType=binary&ratio=1&size=5701&status=done&style=none&taskId=u8ab50360-dea8-4508-970b-d3d49bc6673&width=222.5)<br />**种子在每次启动计算机的时候随机生成,一旦计算机启动后,它就不再变化**。
  4. <a name="flwqr"></a>
  5. ## 重新播种
  6. **通过srand()函数来重新“播种”**。
  7. <a name="AtE3u"></a>
  8. ### srand()的定义形式
  9. ```c
  10. void srand(unsigned int seed);

使用时间作为参数生成随机数

  1. /* 使用time()函数前需要包含time.h头文件 */
  2. srand((unsigned)time(NULL)); //使用rand()之前先播种
  3. int a = rand(); //生成随机数

生产一定范围的随机数

  1. int a = rand() % 10; //产生0~9的随机数
  2. int a = rand() % 51 + 13; //产生13~63的随机数

连续生成随机数

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. int main()
  5. {
  6. int a, i;
  7. srand((unsigned)time(NULL)); //播种必须放在循环外
  8. //使用for循环生成10个随机数
  9. for (i = 0; i < 10; i++)
  10. {
  11. // srand((unsigned)time(NULL));
  12. a = rand();
  13. printf("%d\n", a);
  14. }
  15. return 0;
  16. }