define和可变参数列表

一、可变参数列表

1.1 什么是可变参数

    在C语言中printf函数原型如下:

int printf(const char* format, ...);

    上面的三个点就是可变参数,那就是可变的参数。

1.2 可变参数四个宏

typedef char* va_list;
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1 ) & ~(sizeof(int) - 1) )
#define va_start(ap, v) ( ap = (va_list)&v + _INTSIZEOF(n) )
#define va_arg(ap, t)   ( *(t *) ((ap += _INTSIZEOF(n)) - _INTSIZEOF(n)) )
#define va_end(ap)      ( ap = (va_list)0 )

 1.2.1 _INTSIZEOF(n)

_INTSIZEOF(n)是用于内存对齐的,先说句题外的

----------------------------------------------------------------------------------------------------------

对于整数向上向下取整的问题,

向下取整

int(num / den);

向上取整

int( (num + den - 1) / den)
// 不能 int( num / den ) + 1,4/4这种整除的会出错

对于分数保留小数点后几位的问题

// 保留小数点后3位
double( int( ((double)num / den) * 10000 + 5 ) / 10 ) / 1000;

-----------------------------------------------------------------------------------------------------------------------

然后回到 _INTSIZEOF(n) 就是对4字节取整,按照向上取证的方法,应该

(sizeof(type) + sizeof(int) - 1) / sizeof(int) * sizeof(int);

但是sizeof(int)是4字节,标准的二进制100,所以可以除完再乘可以直接转换成移位,变成

((sizeof(type) + sizeof(int) - 1) >> 2) << 2

然而,你会发现做了这么多,其实就是把(sizeof(type) + sizeof(int) - 1)低两位置00,就有了

( (sizeof(n) + sizeof(int) - 1 ) & ~(sizeof(int) - 1) )

所以,_INTSIZEOF(n)就是找到大于n的最小的4的倍数。不过记得内存分布是可调的,有效对齐不一定是4,也可能不对齐,也可能对齐别的数值,确实在stdarg.h文件中没找到这个定义,不过网上找打的资料说是这个,就先这个了。

1.2.2 va_start(ap, v)

    先看看一个例子看看四个宏怎么用的(例子来源于可变参数函数原理

#include <stdarg.h>

int FindMax(int num, ...)
{
	va_list arg;
	va_start(arg, num);

	int max = va_arg(arg, int);
	for (int i = 0; i < num - 1; i++)
	{
		int x = va_arg(arg, int);
		if (max < x)
		{
			max = x;
		}
	}
	va_end(arg);
	return max;
}

int main()
{
	int max = FindMax(5, 1, 2, 3, 9, 7);
	printf("max = %d\n", max);
	system("pause");
	return 0;
}

va_start将arg指针指向第一个参数的起始位置(第一个参数不是int num, 而是可变参数的第一个参数,就是5 1 2 3 9 7的1位置。)

    va_start原理也很简单,就是取num地址,然后加上num类型对齐后占的内存,然后此时arg指向第一个可变参数。

1.2.3 va_arg(ap, t)

    原理就是先把ap加上t类型内存占用的内存大小,然后转换成t类型返回结果。

1.2.4 va_end(ap)

    ap置空变成空指针。

1.3 模板中的可变参数

本节内容几乎全部来自:

【C++ 泛型编程 进阶篇】C++ 可变参数模板的妙用:解决参数不足问题

C++17 折叠表达式

可变参数模板(3)_深入理解参数包

引用折叠、万能引用、完美转发

    可变参数是不是很简单,然而到了c++11中,模板编程中的可变参数就不是用四个宏,而是一种折叠表达式用法+递归(折叠表达式详见:折叠表达式(C++17 起))。

1.3.1 可变参数的基本概念

    变参数模板,顾名思义就是参数个数和类型都可能发生变化的模板,要实现这一点,那就必须要使用模板形参包。模板形参包主要有三种,即:非类型模板形参包类型模板形参包模板模板形参包,函数参数包。

  • 非类型模板参数包:类型选择一个固定的类型,args其实是一个可修改的参数名。例如:
template<typename... args>
// 例如 template<int... data> xxx;

这里,args 是一个模板参数包,可以接受任意数量的类型参数。但是typename仅限为整型、指针和引用

  • 类型模板参数包:可以接受任意数量的类型参数。例如:
template<typename... Args> xxx;

这里,Args 是一个函数参数包,可以接受任意数量类型的参数。

  • 模板模板形参包:形参包本身它也是一个模板。例如:

template < 形参列表 > class ... Args(可选)

说真的,这个没明白,以后再说。

  • 函数参数包:函数参数包也可以包含零个或多个函数参数。例如:
template<typename... Args>
void my_function(Args... args) {
    // ...
}

1.3.2 sizeof...()获得参数数量

    sizeof… 是一个编译时操作符,可以用于获取参数包中元素的数量。

template<typename... Args>
size_t count_args(Args... args) {
    return sizeof...(args);
}
int main() {
    std::cout << count_args(1, 2.0, "Hello", 3.14) << std::endl; // 输出:4
    return 0;
}

1.3.3 递归展开参数包

#include <iostream>
// 基本情况:这是必须的,处理无参数的情况,否则编译器会找不到匹配函数
void print() {}


// 递归情况:处理一个参数,并递归调用剩余参数
template<typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << " ";
    print(rest...); // 递归调用
}


int main() {
    print(1, 2.0, "Hello", 3.14);
    return 0;
}

 1.3.4 折叠表达式(C++17)

    C++17引入,可以简化一些递归的表达式(简单来说就是将看不懂的递归变成看不懂的运算符解包)

    折叠表达式将分为四种解包规则:

  • 一元右折叠:(E op ...) = ( E1 op ( ... op (EN-1 op En) ) )
  • 一元左折叠:(... op E) = ( ( (E1 op E2) op ... ) op EN)
  • 二元右折叠:(E op ... op I) = (E1 op (... op (EN−1 op (EN op I) ) ) )
  • 二元左折叠:(I op ... op E) = ( ( ( (I op E1) op E2) op ...) op EN)

关于op有哪些操作符详见https://www.apiref.com/cpp-zh/cpp/language/fold.html

然后这个例子

#include <iostream>
using namespace std;


template<class type>
void printAmt(int &iSumAge){
    return;
}


template<class type, int age0, int ... age>
void printAmt(int &iSumAge){
    iSumAge += age0;
    if ( (sizeof ... (age)) > 0 ){
        printAmt<type, age...>(iSumAge);
    }
}

int main(){
    int sumAge = 0;
    printAmt<int,1,2,3,4,5,7,6,8>(sumAge);
    cout << "the sum of age is " << sumAge << endl;
    return 0;
}

就可以这样写

#include <iostream>
using namespace std;


template<class type, int ... age>
void printAmt(int &iSumAge){
    iSumAge = (age + ...);
}


int main(){
    int sumAge = 0;
    printAmt<int,1,2,3,4,5,7,6,8>(sumAge);
    cout << "the sum of age is " << sumAge << endl;
    return 0;
}

非常的简洁(反正咋都看不懂....)

然后看看C++17 折叠表达式中一个很坑的例子。

   c++ 如何实现 x in (1,2,3) 这样的运算?c++17之前 不难写出一个递归的模板函数

template <class Target>
bool In(Target&& /**/) {
  return false;
}
template <class Target, class First, class... Args>
bool In(Target&& t, First&& f, Args&&... args) {
  return t == f || In(std::forward<Target>(t), std::forward<Args>(args)...);
}

这种场景就非常适合 折叠表达式优化

template <class Target, class... Args>
bool In(Target&& t, Args&&... args) {
  return ((t == args) || ...);
}

 但是这么简单个例子里面其实也藏着很多 细节(坑)

  1. || 运算符在参数包为空时返回值为false ,(&&true )
  2. t == args 要用 括号包裹,因为== 优先级高于 || , E 或者I 中有任何优先级高于op 的操作符都要用括号。当然建议不要记,统统加括号准没错(当然你会发现不加括号根本编译不通过,总之加括号就对了)。

最后,为什么模板中用右值引用,见引用折叠、万能引用、完美转发

二、define的使用

    define的用法就相对简单点,可以分为:无参宏有参宏变参宏。而宏定义中还存在一些特殊的符号比如#、##和#@。

2.1 无参宏

    无参宏就很简单,就定义

#define PIE 3.14

2.2 有参宏

  就是包含参数的宏

#define puls(x) x*x

上面这个例子实际上是有问题的,有参宏需要注意加括号,不加括号可能会有问题

plus(1+2)
// 希望计算 3*3
// 但是实际上会变成 1+2*1+2

2.3 变参宏

   __VA_ARGS__ 是一个可变参数宏,与省略号 ... 配合使用,用来替换省略号所代表的字符串。

#define P(...) printf(__VA_ARGS__)
// 例如:P("1%d", 2);

// 也可以这样
#define P(fmt, ...) printf(__VA_ARGS__) 

宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的”,”去掉的作用,否则会编译会报错(GUN中必须要## __VA_ARGS__否则编译会报错,但是visual studio不会报错且可以正常运行)。

2.4 #符号

    将输入变成字符串,例如

#define TOSTRING(x) #x

cout << TOSTRING(123) << endl;    // 输出123
cout << sizeof(TOSTRING(123)) << endl;    // 4

2.5 ##符号

    字符拼接,例如

#define CONCAT(a, b) a##b

int x1 = 1;
cout << CONCAT(x, 1);    // 输出1

cout << CONCAT("hello ", "world");    // 输出hello world

 2.6 #@符号

转化成字符。例如

#define TOCHAR(x) #@x

cout << TOCHAR(1) << endl;    // 1
cout << (int)TOCHAR(1) << endl;    // 49
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值