系列文章目录
文章目录
预处理
预处理就是在编译代码之前对源代码进行重新组织、改写以达到我们预期编译的目的,我们在编写代码时用#include导入头文件,用#define定义宏来增强代码可读性,或者用#if,#else来解决不同平台的代码移植问题,这些预处理命令都是以#开头的,在编译代码之前,编译工具会自动调用预处理程序来执行这些预处理命令,它将修改后的代码放入与源文件同名扩展名为.i的文件中,再对这个.i文件进行编译。相对于其它语言,C和C++更加依赖预处理,因为底层语言需要手动实现的更多,高级语言如跨平台功能有虚拟机代劳。
使用#include导入文件
#include是一个将外部文件内容导入本地文件的命令,它直接导入文件内容不作任何修饰,你完全可以将一个文件拆分成数个文件然后通过#include导入,这种普通的导入会有它的弊端,会发生定义重复等问题。#include <stdio.h> 和 #include “stdio.h”的区别是<>直接查找系统库,引号先查找用户目录,找不到再查找系统库,如果导入是像标准输入输出这样的系统库应该使用<>来提升编译速度。stdio和stdlib是最常用的两个标准库,stdio是标准输入输出库,stdlib包含的功能很多很杂,包含很多常用功能以避免我们重写。
正确使用#include命令和我们组织项目文件息息相关,当我们使用#include导入一个文件时,如果这个文件中也有#include命令会一并导入,这就可能发生重复导入问题,C语言规定同一个头文件可以被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制,另外同一函数也可以声明多次,如果我们在代码中定义了某个函数,导入一个包含该函数的声明的头文件也是不会导致问题的,从这个机制上看,只要头文件按照标准编写,无论导入多少次都是不会造成问题的。但导入源码文件就不一样了,当一个源码文件被导入两次时一定会因为定义重复而报错。
使用#define定义宏
简单的宏
可以用#define定义一条语句,例如:
#define PF puts("pf")
int main(int argc, char* argv[])
{
PF;
}
通常我们都会用大写的名称定义宏,运行程序可以输出pf,注意#define不会对内容进行任何检查,如果将分号写入#define,替换时也会包含分号,如果用#define定义一个加法表达式且不加括号:
#define N a+b
那么计算Nc时结果为a+bc,这可能和我们预想的不同,要保证结果不受运算优先级的影响,通常需要将结果用括号括起来,例如:
#define N (a+b)
定义宏后这个名称就不能当作变量名使用了,如果不再需要这个宏了,可以用#undef来消除宏,例如:
#include<stdio.h>
#define PI 3.14159
#define S=PI*5
void func()
{
printf("%f\n",PI);
}
#undef PI
int main()
{
func();
float PI=3.14;
printf("%f\n",PI);
return 0;
}
预编译执行时是从上到下的,定义PI后,下一个宏S也可以使用上一个宏PI,当执行到#undef PI时名称PI就被释放了,因此在main()中可以重新将PI用作变量名。使用宏时需要注意以下几点:
- 宏只能在当前文件中使用
- 只会对当前文件进行替换,不会影响其它文件,例如RAND_MAX在stdlib库中已经定义,重定义RAND_MAX不会影响stdlib库,因为#define只影响本文件。
重复定义的宏会被覆盖
当我们两次定义RAND_MAX时,第二次定义的RAND_MAX会覆盖第一次,当库中已有RAND_MAX时,自定义RAND_MAX会覆盖库中的宏
无论什么语言当项目变大时都无法避免命名冲突,宏也是。对于C语言来说,标准库内部使用的宏一般为下划线开头,例如:_NFILE,给我们使用的宏没有下划线,例如INT_MAX,RAND_MAX。我们自己定义宏时如果不是库项目就不必使用下划线。当我们无法判断宏是否已定义时,可以用IDE的跳转功能测试一下是否能跳转到定义处,好的编译器如VS会将宏自动着色,容易辨认。
带参数的宏
带参数的宏比直接替换要智能些,它允许定义一个类似函数的表达式,然后在预处理时将形参替换成实参,例如:
#include <stdio.h>
#define MAX(a,b) (a>b) ? a : b
int main()
{
int x, y, max;
scanf("%d %d", &x, &y);
max = MAX(x, y);
printf("max=%d\n", max);
return 0;
}
带参数的宏看起来像一个函数,但和函数有着本质的区别,因为本质还是文本替换,不会参与编译,也就是说在编译之前MAX(x,y)已经被替换成了(x>y) ? x : y,而定义的函数会参与编译。这种简单的替换同样会遇到运算优先级的问题,例如对于#define SQ(y) yy,如果y是表达式2+3则结果为2+32+3,这显然是不对的;如果表达式为100/SQ(a),那么结果为100/yy,而我们原意是将yy作为分母,要要保证代码替换后不受运算优先级的影响,需要将参数和返回结果都用括号包裹起来,例如:
#define SQ(y) ((y)(y))
有时候这样还不够,对于自运算符来说,代码被替换后可能会执行两次,例如SQ(a++)会被替换为((a++)(a++)),如果我们编写函数void SQ(y) {y*y},执行SQ(a++)时自增运算只会执行一次,唯一避免这种情况的方法是不要在宏中使用自增自减等运算符。
如果宏有多条语句,也可以使用分号分隔语句,例如:
#define MYFUN(x,y) x=1; y=2; z=x+y;
但通常情况下我们不会将多条语句定义为宏,那会降低代码的可读性,也不利于调试,因为即使宏中的语句有错误,预编译程序也不会检测,只有代码被编译时才会检测到。
宏参数字符串化
可以使用#来将宏参数转为字符串,例如:
#define STR(s) #s
如果我们输入STR(abc),会被替换为STR(“abc”),如果输入STR(“abc”)会被替换成STR(“\”abc\””),我们使用如下代码测试:
#include <stdio.h>
#define STR(s) #s
int main()
{
float f1=1.0;
float f2=2.2;
puts(STR(123));
puts(STR(1+2));
puts(STR(f1/f2));
return 0;
}
结果输出:
123
1+2
f1/f2
可以看到如果参数为表达式,预处理不会计算表达式的结果,因为预处理并不编译代码。#常用于不需要将参数视作表达式的情况,例如:
#define PSQR(x) printf (“The square of " #x " is 8d.\n”, ( (x)(x)) );
PSQR(y);
#x被替换为"y",x被替换为y,结果为:
printf (“The square of " “y” " is 8d.\n”, ( (y)(y)) );
由于字符串有串联的特性,因此这句话相当于:
printf (“The square of y is 8d.\n”, ( (y)*(y)) );
链接宏参数
##可以将宏的参数连接,例如定义:
#define CON(a, b) a##c##b
那么CON(1,2) 会被替换为1c2,使用如下代码测试:
#include <stdio.h>
#define CON(a, b) a##00##b
#define F(a, b) a##.##b
int main()
{
printf("%d\n",CON(1,1));
printf("%f\n",F(3,14));
return 0;
}
结果输出:
1001
1001
3.140000
可以看到链接格式为a##内容##b,CON()将a, b以及中间内容连接成一个整体,同样即便参数为表达式也不会进行处理,参数会被看作字符串。##常用于给变量名、函数名或者数据加上前缀和后缀。
变参宏
C99不仅支持函数变参,还支持宏变参,变参宏需要“…”配合__VA_ARGS__来使用,如:
#define PR(…) printf(VA_ARGS)
当宏发现…时,会将printf()中的__VA_ARGS__替换为变参,代码如下:
#include<stdio.h>
#include<stdlib.h>
#define PR(...) printf(__VA_ARGS__)
int main(void)
{
PR("%d,%d",20,30);
}
由于printf()本身就是变参,因此可以很顺利的将PR("%d,%d",20,30);替换为printf("%d,%d",20,30);和函数变参一样,变参只能作为最后一个参数,变参宏很好的解决了替换变参函数的问题,是带参数的宏的有力补充。
预定义宏
C语言内置了一些宏,这些内置宏大多用来描述编译信息:
LINE:表示当前源代码的行号
FILE:表示当前源文件的名称
DATE:表示当前的编译日期
TIME:表示当前的编译时间
STDC:当要求程序严格遵循 ANSI C 标准时该标识被赋值为 1
__cplusplus:当编写 C++程序时该标识符被定义
STDC_VERSION 编译器版本
STDC_HOSTED 是否为本机环境
一些内置宏只有特定平台下才能使用,例如:
WIN32:windows的专用宏,是否为windows系统
linux: linux的专用宏,是否为linux系统
其中比较重要的是__STDC_VERSION,用这个检查编译器遵循的标准,是C99还是C11或者更高,C99为199901,C11为201112,C17为201710,但有些编译器不支持该宏,例如VS。如果需要编写跨平台的代码那么__WIN32和__linux就显得比较重要。
条件编译
#if,#elif,#else,#endif
条件编译可以控制编译的流程,格式为:
#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#elif 整型常量表达式3
程序段3
#else
程序段4
#endif
当我们的代码需要跨平台编译时,条件编译就显得很有用,因为有些语句只支持特定平台才支持,例如__WIN32和__linux__只提供给对应的平台,如果在windows中直接用if (linux)来判断会导致报错,这时可以改为#if…#elif…,代码如下:
#include <stdio.h>
int main()
{
#if __WIN32
puts("windows");
#elif __linux__
puts("linux");
#else
puts("can not compile!");
#endif
return 0;
}
注意#if语句不需要括号,#elif可以不要但必须以#endif结束。
#ifdef,#ifndef
与#if类似还有两个条件语句可以控制编译路径,#ifdef语句表示当前宏是否已经定义,#ifndef表示当前宏是否未定义,例如:
#include <stdio.h>
int main()
{
#ifdef DEBUG
printf("正在使用 Debug 模式编译程序...\n");
#else
printf("正在使用 Release 模式编译程序...\n");
#endif
return 0;
}
与#if不同的是这里条件必须为一个宏名,而#if条件为一个整数,#ifdef就算宏名没有定义也不会报错,它返回否。新的C标准还提供了一个关键字defined用于返回宏是否定义,例如:
#include <stdio.h>
#define NUM1 10
#define NUM2 20
int main()
{
#if (defined NUM1 && defined NUM2)
printf("NUM1: %d, NUM2: %d\n", NUM1, NUM2);
#else
printf("Error\n");
#endif
return 0;
}
条件编译除了放在开头也常常出现在代码中,但依然不会参与编译,它只是在预编译时确定使用哪一条代码。
防止重复导入
当我们编写头文件时,会将头文件代码放入到条件编译中来防止重复导入,形式如下:
#ifndef THINGS_H_
#define THINGS_H_
头文件代码…
#endif
THINGS_H_是一个宏,它通常是导入的文件名,用下划线代替点,然后加上前缀或者后缀,C标准库通常使用前缀,为了避免和C库重名,我们可以选择添加后缀。
使用#error阻止程序编译
如果需要将代码限制在一种平台编译,或者发现当前系统不适合编译,可以用#error阻止程序编译并且给出警告,例如:
#ifdef __WIN32
#error This programme cannot compile at Windows Platform
#endif
如果发现系统为windows则不能通过编译,编译器发现#error后就像代码有语法一样拒绝编译。
使用#pragma向编译器发送编译参数
每个编译器都有自己的一套编译参数,编译参数会影响编译器的行为,例如放宽限制、输出编译信息等,通常IDE环境可以通过项目属性修改编译参数,但非IDE环境往往需要自己设置,例如:
#pragma c9x on 使用C99标准编译
#Pragma message(“显示信息!”) 在编译窗口显示信息
#pragma once 头文件只会被编译一次,实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它,有些C标准库在开头既使用了#pragma once又使用了条件编译,例如:
#pragma once
#ifndef _INC_STDIO
#define _INC_STDIO
#endif
泛型选择
带参数的宏相比函数的一个优势是不要求参数类型,利用这个优势C11新增了泛型选择,让宏使用起来像一个可以接受任意类型参数的函数,但不是所有编译器都能实现C11这个标准,泛型选择格式如下:
_Generic(表达式x,int:表达式1,float:表达式2,double:表达式3,default:表达式4)
_Generic是泛型选择的关键字,x是一个表达式,通过对x求值来判断其类型,然后按照类型选择要执行的表达式。例如:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
puts(_Generic(3.14, int:"int", float:"float", double:"double", default:"NaN"));
}
测试发现VS不支持_Generic但gcc可以,对于VS来说C99都没有完全实现,因为它更建议用C++。把_Generic直接放到代码中没有重用性,通常会结合#define来使用,例如:
#include<stdio.h>
#include<stdlib.h>
#define TEST_TYPE(X) _Generic(X, int:"int", float:"float", double:"double", default:"NaN")
int main(void)
{
puts(TEST_TYPE(3.14));
}
TEST_TYPE看起来就像一个可以接受任意类型的函数,但它是在预编译时被替换成float的。
内联函数
带参数的宏虽然方便但有很多限制,例如不适合多行代码?参数不能用自运算符,更重要的是不能定义局部变量和访问全局变量,能否有方法除去这些限制?有的,C99提供了内联函数,它与宏一样可以将代码插入到指定位置。使用内联函数需要使用关键字inline,例如:
#include<stdio.h>
inline int cube(int x)
{
return x * x * x;
}
int main()
{
int n = 2;
printf("%d",cube(n));
}
当进行预编译时,cube(n)会被替换成nnn,由于内联函数的用途也是替换代码,当把一个函数声明为内联函数时,此函数不会被放入静态区,因此它没有入口。C标准还规定内联函数只能进行内部链接,即只能用于文件内,而且内联函数即使命名与其它函数相同也不会发生冲突,因为内联函数被替换后就不存在了。基于这个原因内联函数常常和static一起使用,没有必要暴露给外部。内联函数的定义与声明格式也是一致的,例如:
#include<stdio.h>
inline static int cube(int x);
int main()
{
int n = 2;
printf("%d",cube(n));
}
inline static int cube(int x)
{
return x * x * x;
}
现在新的C编译器即使不进行声明也可以自动找到内联函数的定义。需要注意的是,编译器对内联函数的优化是选择性的,即可能优化也可能不优化,将一个代码众多的函数声明为内联速度并不会提升多少,因为函数内部会占据大部分运行时间,内联函数依然只适合轻量代码,但不管怎样它没有了宏的那些限制。
利用预处理简化代码
宏的优势有两个:
- 使用宏代替一些简单的函数可以增加性能,例如:
#define MAX(X,Y) ((X))>(Y)?(X):(Y))
#define ABS(X) ((X)<0?-(X):(X))
调用函数需要入栈出栈,建立释放栈空间等操作,没有直接写表达式快捷,特别是在循环中调用函数时。
- 简化代码
当我们每次使用printf()输出数据时会觉得格式控制符很繁琐,如果输出的是字符和字符串,我们更愿意选择putchar()和puts(),如果是数值就没有其它函数可以替代了,此时我们可以通过宏来简化输出,如下:
#define PD(X) printf("%d",X)
#define PDN(X) printf("%d\n",X)
#define PL(X) printf("%ld\n",X)
#define PLN(X) printf("%ld\n",X)
#define PF(X) printf("%f",X)
#define PFN(X) printf("%f\n",X)
#define PP(X) printf("%p",X)
#define PPN(X) printf("%p",X)
#define NN printf("\n")
#define PR(...) printf(__VA_ARGS__)
如果编译器支持泛型,可以将格式化输出进一步简化,例如:
#include<stdio.h>
#include<stdlib.h>
#define P(x) _Generic(x, \
char:printf("%c",x),\
short:printf("%hd",x),\
unsigned short:printf("%hu",x),\
int:printf("%d",x),\
unsigned:printf("%u",x),\
long:printf("%ld",x),\
unsigned long:printf("%lu",x),\
float:printf("%f",x),\
double:printf("%f",x),\
short*:printf("%p",x),\
unsigned short*:printf("%p",x),\
int*:printf("%p",x),\
unsigned*:printf("%p",x),\
long*:printf("%p",x),\
unsigned long*:printf("%p",x),\
float*:printf("%p",x),\
double*:printf("%p",x),\
char*:printf("%s",x),\
default:printf("invariable type")\
)
#define PN(x) _Generic(x, \
char:printf("%c\n",x),\
short:printf("%hd\n",x),\
unsigned short:printf("%hu\n",x),\
int:printf("%d\n",x),\
unsigned:printf("%u\n",x),\
long:printf("%ld\n",x),\
unsigned long:printf("%lu\n",x),\
float:printf("%f\n",x),\
double:printf("%f\n",x),\
short*:printf("%p\n",x),\
unsigned short*:printf("%p\n",x),\
int*:printf("%p\n",x),\
unsigned*:printf("%p\n",x),\
long*:printf("%p\n",x),\
unsigned long*:printf("%p\n",x),\
float*:printf("%p\n",x),\
double*:printf("%p\n",x),\
char*:printf("%s\n",x),\
default:printf("invariable type\n")\
)
int main(void)
{
P(-3);NN;
P(INT_MAX);NN;
PN(3.14);
PN(-1.34234232343243);
PN((char)'a');
PN("abc efg");
PN("");
int a=1;
P(&a);
}
这里将P定义为可以将任意类型输出到控制台的宏,PN比P多了一个换行符,NN表示输出一个换行符。C语言允许一行太长时使用\来进行断行,通过测试发现’a’被当作一个int,结果输出了它的ascii码,将它强制转为char后正常输出,强制转型不如直接用putchar(‘a’)来的快。我们可以向泛型加入更多的数据类型例如结构体来扩充输出功能,但是如果将上面代码放入库中,这个库就会变得不通用。
在调试时通常需要用一个循环来输出数组中的所有值,如果想简化可以使用一个函数来输出,如下:
#include<stdio.h>
#include<string.h>
printArr(void* arr, char* typeStr, int len)
{
int i = 0;
putchar('[');
if (!strcmp(typeStr, "%hd"))
{
short* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%hu"))
{
unsigned short* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%d"))
{
int* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%u"))
{
unsigned* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%ld"))
{
long* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%lu"))
{
unsigned* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%f"))
{
float* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%f"))
{
double* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%c"))
{
char* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%s"))
{
char** p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%p"))
{
void** p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
printf("]\n");
}
int main()
{
int arr1[5] = {1,2,3,4,5};
float arr2[5] = { 1.5,2,3.0,4,8 };
char arr3[5] = "abcd";
char* arr4[3] = { "aaa","bbb","ccc" };
printArr(arr1, "%d", 5);
printArr(arr2, "%f", 5);
printArr(arr3, "%c", 4);
printArr(arr4, "%s", 3);
printArr(arr4, "%p", 3);
}
printArr()接受3个参数,arr是数组名或指针,typeStr是格式控制符,len是要输出的数组长度。printArr()能够输出包含各种基本数据类型、字符串以及指针元素的数组。如果支持泛型,还可以进一步简化:
#include<stdio.h>
#include<string.h>
#define PArr(arr,len) printArr(arr, \
_Generic(*arr, \
char:"%c", \
short:"%hd", \
unsigned short:"%hu", \
int:"%d", \
unsigned:"%u", \
long:"%ld", \
unsigned long:"%lu", \
float:"%f", \
double:"%f", \
char*:"%s", \
short*:"%p", \
unsigned short*:"%p", \
int*:"%p", \
unsigned*:"%p", \
long*:"%p", \
unsigned long*:"%p", \
float*:"%p", \
double*:"%p", \
void* :"%p", \
default:"invariable type"\
),len)
printArr(void* arr, char* typeStr, int len)
{
int i = 0;
putchar('[');
if (!strcmp(typeStr, "%hd"))
{
short* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%hu"))
{
unsigned short* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%d"))
{
int* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%u"))
{
unsigned* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%ld"))
{
long* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%lu"))
{
unsigned* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%f"))
{
float* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%f"))
{
double* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%c"))
{
char* p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%s"))
{
char** p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
else if (!strcmp(typeStr, "%p"))
{
void** p = arr;
for (i = 0; i < len - 1; i++)
{
printf(typeStr, p[i]);
putchar(',');
}
if (i < len) printf(typeStr, p[i]);
}
printf("]\n");
}
int main()
{
int arr1[5] = {1,2,3,4,5};
float arr2[5] = { 1.5,2,3.0,4,8 };
char arr3[5] = "abcd";
char* arr4[3] = { "aaa","bbb","ccc" };
int p1=0;
int p2=0;
int p3=0;
int* arr5[]={&p1,&p2,&p3};
PArr(arr1, 5);
PArr(arr2, 5);
PArr(arr3, 4);
PArr(arr4, 3);
PArr(arr5, 3);
}
现在不需要输入数组类型,只需要数组名称和长度,就可以输出整个数组元素。
预处理总结
预处理一定会在编译之前执行,通常都会放在文件头部,#if也会放入代码中,#include是必定用到的预处理命令,#define主要用于定义语句,对于表达式来说一定记住要将参数和结果都用括号括起来,还要观察有没有自增或自减运算。#define不能完全代替函数,对于多行代码#define没有优势,但对于简单的代码可以优化性能。#if, #error主要用于跨平台编译,当我们编写具有重用价值的库文件时,需要根据不同的操作系统选择执行的语句,这比为每个不同的平台都编写一个库成本低。#ifndef是开发库必不可少语句,它可以防止库被多次导入,不能完全依赖#pragma once,因为不能确定编译器是否支持,必要时将两个都写上去比较保险。如果编译器支持泛型选择,可以通过泛型选择进一步简化代码,如果支持内联函数则可以通过内联函数实现宏无法实现的复杂内容。