预处理命令(#)
以**#**
号开头的命令称为预处理命令。
C语言源文件需要经过编译、链接后才能生成可执行程序。
#include
**#include**
叫作文件包含命令,用来引入对于的头文件(**.h**
文件)。#include
的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
用法
方法1: #include <stdio.h>
方法2: #include "stdio.h"
- 使用尖括号
**< >**
,编译器会到系统路径下查找头文件; - 使用双引号
**" "**
,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找; - stdio.h这类标准头文件一般存放在系统路径下,而自己编写的头文件,一般存放在当前项目路径下,所以不能使用
**< >**
,只能使用**" "**
; - 头文件只能包含变量和函数声明,不能包含定义,否则多次引入时会出现重复定义的错误。
宏定义命令(#define)
宏定义只是简单的字符串替换,由预处理器进行替换处理;
typedef是在编译阶段由编译器处理的,并不是简单的字符串替换,是给原有的数据类型起一定新的名字,作为一种新的数据类型。宏定义形式
```cdefine 宏名 字符串
其中,宏定义中的字符串可以是数字、表达式、if语句、函数等。
<a name="ksrih"></a>
### #define用法的说明
- 宏定义不是说明或语句,行末不需要加分号;
- 宏定义必须写在函数外面,其作用域是宏定义命令起,一直到源程序结束。
- **如果需要中途终止其作用域,需要使用**`**#undef**`**命令**。
- **代码中的宏名如果被引号包围,那么预处理程序不对其作宏替代**;
```c
#include <stdio.h>
#define OK 100
int main(){
printf("OK\n"); //打印结果是“OK”而非100
return 0;
}
宏定义允许嵌套;
#define A 100
#define B A*100
习惯上,宏名用大写字母表示,以便于与变量区别(允许小写字母);
- 可用宏定义表示数据类型;
```c
define UINT unsigned int
UNIT a,b;
<a name="iofHE"></a>
### 带参数的宏定义
**对带参数的宏,在展开的过程中不仅要进行字符串替换,还要用实参去替代形参**。
<a name="MmIfw"></a>
#### 带参数宏定义的形式
```c
#define 宏名(形参列表) 字符串
其中,字符串中可以含有各个形参。
带参数宏定义的调用
宏名(实参列表);
举例
/* 用带参数宏定义实现找出两个数据的最大值 */
#include <stdio.h>
#define MAX(a, b) ((a > b) ? a : b)
int main(void)
{
int i, j, max;
printf("请连续输入两个数字,按空格分开: ");
scanf("%d %d", &i, &j);
max = MAX(i, j);
printf("max = %d\n", max);
return 0;
}
带参数宏定义的说明
带参数宏定义中,形参之间可以出现空格,但宏名和形参列表之间不能有空格;
如果:
#define MAX(a, b) ((a > b) ? a : b)
写成:
#define MAX (a, b) ((a > b) ? a : b)
编译器将认为MAX是一个无参数宏定义,宏调用结果将是:
(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
<a name="N6lGH"></a>
### 宏参数的字符串化(#)和连接(##)
<a name="LPUiF"></a>
#### 宏参数的字符串化(#)
<a name="c3oIU"></a>
##### 定义形式
```c
#define STR(s) #s
用法
**#**
用来将宏参数转换为字符串,即在宏参数的开头和末尾添加引号。
#include <stdio.h>
#define STR(s) #s
int main(void)
{
printf("test你好!!!!\n");
printf("%s\n", STR(abc)); //实参不添加引号,输出abc
printf("%s\n", STR("abc")); //实参添加引号,输出"abc"
return 0;
}
宏参数的连接(##)
定义形式
#define CONN1(a,b) a##e##b
#define CONN2(a,b) a##b##00
用法
##称为连接符,用来将宏参数或其他的字符串连接起来。
#include <stdio.h>
#define CONN1(a, b) a##e##b
#define CONN2(a, b) a##b##00
int main(void)
{
printf("test你好!!!!\n");
printf("%f\n", CONN1(8.8, 2)); //输出880.000000
printf("%d\n", CONN2(12, 34)); //输出123400
return 0;
}
C语言中的预定义宏
ANSI C规定了以下几个预定义宏:
__LINE__:表示当前代码的行号;
__FILE__:表示当前文件的名称;
__DATE__:表示当前的编译日期;
__TIME__:表示当前的编译时间
__STDC__:表示当要求程序严格遵循ANSI C标准时,该标识被赋值为1;
__cplusplus:表示当编写C++程序时,该标识符被定义。
示例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Date : %s\n", __DATE__);
printf("Time : %s\n", __TIME__);
printf("File : %s\n", __FILE__);
printf("Line : %d\n", __LINE__);
system("pause");
return 0;
}
条件编译
能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。
Windows有专用的宏_Win32
,Linux有专用的宏__linux__
。
VS/VC有两种编译模式,Debug和Release。调试阶段用Debug模式;最终发布程序用Release模式,该模式下,编译器会进行很多优化,提高程序运行效率,删除冗余信息。
#if
/* #if的用法 */
#if 整型常量表达式1 //常量表达式中不能包含变量,且为整型
程序段1
#elif 整型常量表达式2 //#elif可省略
程序段2
#elif 整型常量表达式3
程序段3
#else //#else可省略
程序段4
#endif
#ifdef
/* #ifdef的用法 */
#ifdef 宏名 //只能是宏名,不能是其他
程序段1 //宏被定义执行
#else //#else可省略
程序段2
#endif
#ifndef
/* #ifndef的用法 */
#ifndef 宏名 //只能是宏名,不能是其他
程序段1 //宏未被定义执行
#else //#else可省略
程序段2
#endif
#error命令
**#error**
命令用于在编译期间产生错误信息,并阻止程序的编译。
定义形式
#error error_msg
用法
示例1:
#include <stdio.h>
#ifdef WIN32
#error 这个程序不能在mac上运行!!
#endif
int main(void)
{
printf("test你好!!!!\n");
return 0;
}
示例2:
#include <stdio.h>
#ifndef __cplusplus
#error 这个程序不必须以C++方式编译!!
#endif
int main(void)
{
printf("test你好!!!!\n");
return 0;
}
typedef
定义形式
typedef oldName newName; //末尾需要分号
示例:
//常规定义别名
typedef int INTEGER;
INTEGER a,b;
a = 1;
b = 2;
//给数组定义别名
typedef char ARR20[20]; //ARR20是char [20]的新别名
ARR20 a1; //等价于char a1[20];
//给结构体定义别名
typedef struct stu
{
char name[20];
int age;
char sex;
}STU;
STU body; //等价于struct stu body;
//给指针类型定义别名
typedef int (*POINT_TO_ARR)[4]; //POINT_TO_ARR是int *[4]的新别名,是一个二维数组指针类型
POINT_TO_ARR p; //等价于int *p[4]; //二维数组指针
//给函数指针类型定义别名
typedef int (*POINT_TO_FUNC)(int, int);
POINT_TO_FUNC pfunc;
用法
示例(为指针类型定义别名):
#include <stdio.h>
typedef char (*POINT_TO_ARR)[30]; //二维数组指针
typedef int (*POINT_TO_FUNC)(int, int); //函数指针
char str[3][30] =
{
"aaa",
"bbb",
"ccc"
};
int max(int a, int b)
{
return ((a > b) ? a : b);
}
int main(void)
{
POINT_TO_ARR parr = str;
POINT_TO_FUNC pfunc = max;
printf("test你好!!!!\n");
printf("max = %d\n",(*pfunc)(3,4)); //输出4
for (int i = 0; i < 3; i++)
{
printf("str[%d]: %s\n", i, *(parr + i)); //读取二维数组列的数据
}
return 0;
}
typedef与#define的区别
typedef int INTEGER; unsigned INTEGER n; //error
- 在连续定义多个变量时,`typedef`可以保证定义的所有变量均为同一类型,但`#define`不可以;
```c
#define PTR_INT int *
PTR_INT p1,p2; //等价于int *p1;int p2;
typedef int * PTR_INT
PTR_INT p1,p2; //等价于int *p1;int *p2;
const变量
**const**
变量被称为常量,它的值不能被改变,在整个作用域中保持固定,不可被修改。
定义形式
const type Name = value; //常量在创建后不可修改,初始化时要赋值
其中,type是数据类型,name是变量名,在const中,type和name的位置可以互换,即:
type const Name = value;
const与指针
**const**
和指针一起使用时,可以限制指针变量本身,也可以限制指针指向的数据。
const int *p1; //指针所指向的数据是只读的,但p1本身的值可被修改
int const *p2; //同上
int * const p3; //指针变量是只读的,p3本身的值不能被修改
const int * const p4; //指针和它所指向的数据都是只读的
int const * const p5; //同上
记忆技巧
**const**
后面紧接着指针变量名(如**int * const p3**
)==>指针变量是只读的;存在两个**const**
既描述数据类型,又描述指针变量(如**const int * const p4**
)==>指针变量和它所指向的数据是只读的;剩下的情况,则表示的是指针所指向的数据是只读的。
const与函数形参
const
通常用在函数的形参中,如果形参是一个指针,为了防止字函数内部修改指针所指向的数据,就可以用**const**
来限制。
#include <stdio.h>
#include <string.h>
size_t strnch(const char *str, char ch)
{
int count = 0;
int len = strlen(str);
for (int i = 0; i < len; i++)
{
if (str[i] == ch)
{
count++;
}
}
return count;
}
int main(void)
{
char *str = "fjsalfjalgvjljklfjgjklgslkajkl";
char ch = 'a';
int i = strnch(str, ch);
printf("%d\n", i);
return 0;
}
随机数
一般使用**<stdlib.h>**
头文件中的**rand()**
函数来生成随机数。
定义形式
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; }
<a name="P4GCw"></a>
## 随机数的本质
rand()函数产生的随机数,**本质是伪随机数**,是根据一个数值按照某个公式推算出来的,这个数值称之为“种子”。种子和随机数之间的关系是一个正态分布。<br /><br />**种子在每次启动计算机的时候随机生成,一旦计算机启动后,它就不再变化**。
<a name="flwqr"></a>
## 重新播种
**通过srand()函数来重新“播种”**。
<a name="AtE3u"></a>
### srand()的定义形式
```c
void srand(unsigned int seed);
使用时间作为参数生成随机数
/* 使用time()函数前需要包含time.h头文件 */
srand((unsigned)time(NULL)); //使用rand()之前先播种
int a = rand(); //生成随机数
生产一定范围的随机数
int a = rand() % 10; //产生0~9的随机数
int a = rand() % 51 + 13; //产生13~63的随机数
连续生成随机数
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int a, i;
srand((unsigned)time(NULL)); //播种必须放在循环外
//使用for循环生成10个随机数
for (i = 0; i < 10; i++)
{
// srand((unsigned)time(NULL));
a = rand();
printf("%d\n", a);
}
return 0;
}