C中的预处理、泛型、内联以及宏

系列文章目录

预处理

预处理就是在编译代码之前对源代码进行重新组织、改写以达到我们预期编译的目的,我们在编写代码时用#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:表达式1float:表达式2double:表达式3default:表达式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,因为不能确定编译器是否支持,必要时将两个都写上去比较保险。如果编译器支持泛型选择,可以通过泛型选择进一步简化代码,如果支持内联函数则可以通过内联函数实现宏无法实现的复杂内容。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值