建网站需要了解哪些网站建设知识,收录网站源码,哪个做网站比较好,太原网站建设制作报价1.C语言方式
目录
1.C语言方式
1.1.宏介绍
1.2.原理详解
1.3.宏的可变参数
1.4.案例分析
1.5.其他实例
2.C之std::initializer_list
2.1.简介
2.2.原理详解
2.3.案例分析
3.C之可变参数模版
3.1.简介
3.2.可变参数个数
3.3.递归包展开
3.4.逗号表达式展开
3.5…1.C语言方式
目录
1.C语言方式
1.1.宏介绍
1.2.原理详解
1.3.宏的可变参数
1.4.案例分析
1.5.其他实例
2.C之std::initializer_list
2.1.简介
2.2.原理详解
2.3.案例分析
3.C之可变参数模版
3.1.简介
3.2.可变参数个数
3.3.递归包展开
3.4.逗号表达式展开
3.5.Lambda 捕获
3.6.转发参数包
4.总结 1.1.宏介绍
C语言中的可变参数是指函数可以接受可变数量的参数。这些参数的数量在编译时是未知的。在这些可变参数中的参数类型可以相同也可以不同可变参数的每个参数并没有实际的名称与之相对应用起来是很灵活在头文件stdarg.h中涉及到的宏有va_list : 是指向参数的指针 通过指针运算来调整访问的对象va_start 获取可变参数列表的第一个参数的地址va_arg 获取可变参数的当前参数返回指定类型并将指针指向下一参数va_end 清空va_list可变参数列表
1.2.原理详解
函数的参数是存放在栈中地址是连续的所以可以通过相对位置去访问这也是可变参数的访问方式变长参数的实现需要依赖于C语言默认的cdecl调用惯例的自右向左压栈传递方式可变参数是由1.1介绍的几个宏来实现但是由于硬件平台的不同编译器的不同宏的定义也不相同下面是AMD CPU x64平台下的定义
typedef char* va_list;
va_list的定义
//[1]
#ifdef __cplusplus
#define _ADDRESSOF(v) (reinterpret_castconst char (v))
#else
#define _ADDRESSOF(v) ((v))
#endif//[2]
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
#define va_copy(destination, source) ((destination) (source))//[3]
#define _PTRSIZEOF(n) ((sizeof(n) sizeof(void*) - 1) ~(sizeof(void*) - 1))//系统内存对齐
#define _ISSTRUCT(t) ((sizeof(t) sizeof(void*)) || (sizeof(t) (sizeof(t) - 1)) ! 0)
#define _crt_va_start(v,l) ((v) (va_list)_ADDRESSOF(l) _PTRSIZEOF(l))
#define _crt_va_arg(v,t) _ISSTRUCT(t) ? \(**(t**)(((v) sizeof(void*)) - sizeof(void*))) : \( *(t *)(((v) sizeof(void*)) - sizeof(void*)))
#define _crt_va_end(v) ((v) (va_list)0)
#define _crt_va_copy(d,s) ((d) (s))
从上面的源码可以看出: 1) va_list v; 定义一个指向char类型的指针v。 2) va_start(v,l) 执行 v (va_list)l _PTRSIZEOF(l) v指向参数 l 之后的那个参数的地址即 v指向第一个可变参数在堆栈的地址。 3) va_arg(v,t) ( (t )((v _PTRSIZEOF(t)) - _PTRSIZEOF(t)) ) 取出当前v指针所指的值并使 v 指向下一个参数。 vsizeof(t类型) 让v指向下一个参数的地址。然后返回 v - sizeof(t类型) 的t类型指针这正是第一个可变参数在堆栈里的地址。然后 用取得这个地址的内容。 va_end(v) ; 清空 va_list v。
1.3.宏的可变参数
标准C/C语言宏定义的参数允许用三个小数点 ... 表示这里是可变参数在宏替换的时候用 __VA_ARGS__ 表示 ... 位置的所有的参数例如
#define example1(...) printf(__VA_ARGS__)
#define example2(fmt, ...) printf(fmt, __VA_ARGS__)
很多编译器扩展了可变参数的宏替换参数后面带三个小数点这样的写法更容易记忆宏定义的参数后面可以带三个小数点表示这里是可变参数宏替换的时候直接写这个参数就表示这个位置是所有的可变参数了。例如
#define example1(fmt...) printf(fmt)
#define example2(fmt, args...) printf(fmt, args)
1.4.案例分析
#include iostream
#include stdarg.hvoid printValues(const char* format, ...) {va_list args; // 定义一个va_list类型的变量va_start(args, format); // 初始化argsfor (const char* arg format; *arg ! \0; arg) {if (*arg %) {arg;switch (*arg) {case d: // 对于整数std::cout va_arg(args, int);break;case s: // 对于字符串std::cout va_arg(args, char*);break;default:std::cout Invalid format specifier: *arg;}}else {std::cout *arg;}}va_end(args); // 清理va_list变量
}int main() {printValues(say self info: %s, age %d\n, xiao, 45); //输出: say self info xiao, age 45return 0;
}
printValues函数调用的时候展开为
void printValues(const char* format, const char* param1, int param2)
从上面的代码来分析一下这个示例在windows中栈由高地址往低地址生长,调用printValues函数时其参数入栈情况如下 当调用va_start(args, format)时args指针指向情况对应下图 当调用va_arg(args, ...)时它必须返回一个由va_list所指向的恰当的类型的数值同时递增args使它指向参数列表中的一个参数(即递增的大小等于与va_arg宏所返回的数值具有相同类型的对象的长度)。因为类型转换的结果不能作为赋值运算的目标所以va_arg宏首先使用sizeof来确定需要递增的大小然后把它直接加到va_list上这样得到的指针再被转换为要求的类型。 在上面的示例中我们定义了一个名为printValues的函数它接受一个格式字符串和一个可变数量的参数。我们使用va_list、va_start、va_arg和va_end这些宏来处理可变参数。在格式字符串中我们使用%来指定参数的类型例如%d表示整数%s表示字符串。然后我们使用va_arg宏来获取相应的参数值。最后我们使用va_end宏来清理va_list变量。
1.5.其他实例
1) printf实现
#include stdarg.hint printf(char *format, ...)
{va_list ap;int n;va_start(ap, format);n vprintf(format, ap);va_end(ap);return n;
}
2)定制错误打印函数error
#include stdio.h
#include stdarg.hvoid error(char *format, ...)
{va_list ap;va_start(ap, format);fprintf(stderr, Error: );vfprintf(stderr, format, ap);va_end(ap);fprintf(stderr, \n);return;
}
2.C之std::initializer_list 在C中我们一般用和初始化参数或对象还可以用{}来初始化参数或对象比如数组的初始化int m[] {1,4,5}除了数组在STL里面很多标准的容器和自定义类型都用{} 进行初始化。 自C11标准开始就引入了列表初始化的概念即支持使用{}对变量或对象进行初始化且与传统的变量初始化的规则一样也分为拷贝初始化和直接初始化两种方式。
2.1.简介
std::initializer_listT 类型对象是一个访问 const T 类型对象数组的轻量代理对象。 std::initializer_list 对象在这些时候自动构造 1用花括号初始化器列表列表初始化一个对象其中对应构造函数接受一个 std::initializer_list 参数如std::vector的构造函数 vector(initializer_list_Ty _Ilist, const _Alloc _Al _Alloc()) 2以花括号初始化器列表为赋值的右运算数或函数调用参数而对应的赋值运算符/函数接受 std::initializer_list 参数 3绑定花括号初始化器列表到 auto 包括在范围 for 循环中
initializer_list 可由一对指针或指针与其长度实现。复制一个 std::initializer_list 不会复制其底层对象。
注意
a、底层数组不保证在原始 initializer_list 对象的生存期结束后继续存在。 std::initializer_list 的存储是未指定的即它可以是自动、临时或静态只读内存依赖场合。
b、底层数组是 const T[N] 类型的临时数组其中每个元素都从原始初始化器列表的对应元素复制初始化除非窄化转换非法。底层数组的生存期与任何其他临时对象相同除了从数组初始化 initializer_list 对象会延长数组的生存期恰如绑定引用到临时量有例外例如对于初始化非静态类成员。底层数组可以分配在只读内存。
c、若声明了 std::initializer_list 的显式或偏特化则程序为谬构。
2.2.原理详解
源码面前无秘密直接上源码
template class _Elem
class initializer_list {
public:using value_type _Elem;using reference const _Elem;using const_reference const _Elem;using size_type size_t;using iterator const _Elem*;using const_iterator const _Elem*;constexpr initializer_list() noexcept : _First(nullptr), _Last(nullptr) {} //1constexpr initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) noexcept: _First(_First_arg), _Last(_Last_arg) {} //2_NODISCARD constexpr const _Elem* begin() const noexcept {return _First;}_NODISCARD constexpr const _Elem* end() const noexcept {return _Last;}_NODISCARD constexpr size_t size() const noexcept {return static_castsize_t(_Last - _First);}private:const _Elem* _First;const _Elem* _Last;
};// FUNCTION TEMPLATE begin
template class _Elem
_NODISCARD constexpr const _Elem* begin(initializer_list_Elem _Ilist) noexcept {return _Ilist.begin();
}// FUNCTION TEMPLATE end
template class _Elem
_NODISCARD constexpr const _Elem* end(initializer_list_Elem _Ilist) noexcept {return _Ilist.end();
} 从上面的STL的std::initializer_list源码来看std::initializer_list是一个模版类定义了指向该类对象首端、尾端的迭代器即常量对象指针const T*实际上就是对{}表达式内容的简单封装当使用{}时就会调用 initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) 构造出std::initializer_list。 当得到了一个std::initializer_list对象后再来寻找标准容器中以std::initializer_list为形参的构造函数并调用该构造函数对容器进行初始化。
2.3.案例分析
示例1
class IMessageField1 {};//1
void addMessageField(std::initializer_listIMessageField1* t)
{std::vectorIMessageField1* pTest(t);
}#if 0
//2
void addMessageField(std::vectorIMessageField1* t)
{std::vectorIMessageField1* pTest(t);
}
#endifvoid main()
{//[1]std::unique_ptrIMessageField1 a(new IMessageField1);std::unique_ptrIMessageField1 b(new IMessageField1);std::unique_ptrIMessageField1 c(new IMessageField1);std::unique_ptrIMessageField1 d(new IMessageField1);std::unique_ptrIMessageField1 e(new IMessageField1);addMessageField({ a.get(), b.get(), c.get(), d.get(), e.get() });
} 上面代码1和2的方式都可以实现功能2的方式实际上也是先临时生成一个std::initializer_list再调用std::vector的构造函数临时生成一个std::vector最后再用刚生成的std::vector初始化pTest相比1的方式多了几重复制效率比较低一般采用1的方式实现功能。
示例2
#include iostream
#include vector
#include initializer_listtemplate class T
struct S {std::vectorT v;S(std::initializer_listT l) : v(l) {std::cout constructed with a l.size() -element list\n;}void append(std::initializer_listT l) {v.insert(v.end(), l.begin(), l.end());}std::pairconst T*, std::size_t c_arr() const {return {v[0], v.size()}; // 在 return 语句中复制列表初始化// 这不使用 std::initializer_list}
};template typename T
void templated_fn(T) {}int main()
{int a1[] { 1,2,3,4,5,6 }; //数组拷贝初始化int a2[]{ 5,6,7,8,9,0 }; //数组直接初始化Sint s {1, 2, 3, 4, 5}; // 复制初始化s.append({6, 7, 8}); // 函数调用中的列表初始化std::cout The vector size is now s.c_arr().second ints:\n;for (auto n : s.v)std::cout n ;std::cout \n;std::cout Range-for over brace-init-list: \n;for (int x : {-1, -2, -3}) // auto 的规则令此带范围 for 工作std::cout x ;std::cout \n;auto al {10, 11, 12}; // auto 的特殊规则std::cout The list bound to auto has size() al.size() \n;// templated_fn({1, 2, 3}); // 编译错误“ {1, 2, 3} ”不是表达式// 它无类型故 T 无法推导templated_fnstd::initializer_listint({1, 2, 3}); // OKtemplated_fnstd::vectorint({1, 2, 3}); // 也 OK
}
输出:
constructed with a 5-element list
The vector size is now 8 ints:
1 2 3 4 5 6 7 8
Range-for over brace-init-list:
-1 -2 -3
The list bound to auto has size() 3
示例3
struct MyTest{explicit X(int a, int b) :a(a), b(b) { std::cout MyTest(int a,int b)\n; }int a{};int b{};
};int main() {MyTest x{ 1,2 }; //OKMyTest x2( 1,2 ); //OKMyTest x3 { 1,2 }; //Error
}
MyTest x3 {1,2}; 参考复制初始化的规则:复制列表初始化考虑 explicit 和非 explicit 构造函数但只能调用非 explicit 构造函数
3.C之可变参数模版
3.1.简介
一个可变参数模板就是一个可以接受可变参数的模版函数或模板类参数的类型是一种模板是可经推导的可以是任意存在的类型系统类型或自定义类型参数数目可变的可以包括零个、一个或多个可变数目的参数被称为参数包(parameter packet)。存在两种参数包:模板参数包(template parameter packet)表示零个或多个模板参数;函数参数包function parameterpacket)表示零个或多个函数参数。如
templatetypename... Arguments class vtclass;vtclass vtinstance1;
vtclassint vtinstance2;
vtclassfloat, bool vtinstance3;
vtclasslong, std::vectorint, std::string vtinstance4;
3.2.可变参数个数
利用sizeof...()计算可变参数的大小如
templateclass... Types
struct count
{static const std::size_t value sizeof...(Types);
};
3.3.递归包展开
C的包展开是通过 args... 的形式后面... 就意味着展开包需要两个函数递归终止函数 和 递归函数过程就是参数包在展开的过程中递归调用自己每调用一次参数包中的参数就会少一个直到所有的参数都展开为止当没有参数时则调用递归终止函数终止递归过程。如下
#include iostreamusing namespace std;void print() {cout endl;
}template typename T
void print(const T t) { //边界条件cout t endl;
}template typename First, typename... Rest
void print(const First first, const Rest... rest) {cout first , ;print(rest...); //打印剩余参数注意省略号必须有
}int main()
{print(); // calls first overload, outputting only a newlineprint(1); // calls second overload// these call the third overload, the variadic template,// which uses recursion as needed.print(10, 20); //输出 10, 20print(100, 200, 300); //输出100, 200, 300print(first, 2, third, 3.14159); //输出 first, 2, third, 3.14159
}
3.4.逗号表达式展开
逗号表达式是会从左到右依次计算各个表达式并将最后一个表达式的值作为返回值返回我们将最后一个表达式设为整型值所以最后返回的是一个整型将处理参数个数的动作封装成一个函数将该函数作为逗号表达式的第一个表达式…代表参数包列表展开。如
template class T
void printArg(T t) {cout t endl;
}//展开参数包
template class ...Args
void expand(Args... args) {int arr[] { (printArg(args), 0)... };
}
int main()
{expand(1);expand(1, A);expand(1, hello, 3);return 0;
}
函数执行expand(1, hello, 3);的时候调用expand数组arr初始化会展开args参数变化为
int arr[] {(printArg(1), 0), (printArg(hello), 0), (printArg(3), 0)};
根据逗号表达式的规则arr[] 还是 {0,0,0};
另外还可以利用std::initializer_list 可以接收任意长度的初始化列表来展开包如
templateclass F, class... Args
void expand(const F f, Args...args) {std::initializer_listint{(f(std::forward Args(args)), 0)...};
}int main()
{expand([](int i) { cout i endl; }, 23, 44, 2423);return 0;
}
3.5.Lambda 捕获
包展开可以在 lambda 表达式的捕获子句中出现
templateclass... Args
void f(Args... args)
{auto lm [, args...] { return g(args...); };lm();
}
3.6.转发参数包
在C11标准下我们可以组合使用可变参数模板与std::forword机制来编写函数实现将其实参不变地传递给其他函数,关于std::forward的详解讲解可参考我的博客:C之std::forward_c std::forward-CSDN博客
借助std::forwardArgs(args)... 就可以实现参数的完美转发了,如STL中map的插入函数emplace下
template class... _Valty
iterator emplace(_Valty... _Val)
{return _Mybase::emplace(_STD forward_Valty(_Val)...).first;
}
4.总结
纸上得来终觉浅绝知此事要躬行。 参考
形参包 (C11 起) - cppreference.com 文章转载自: http://www.morning.qmkyp.cn.gov.cn.qmkyp.cn http://www.morning.qbjgw.cn.gov.cn.qbjgw.cn http://www.morning.dyght.cn.gov.cn.dyght.cn http://www.morning.jqmqf.cn.gov.cn.jqmqf.cn http://www.morning.fbrshjf.com.gov.cn.fbrshjf.com http://www.morning.ghxtk.cn.gov.cn.ghxtk.cn http://www.morning.gccrn.cn.gov.cn.gccrn.cn http://www.morning.gtylt.cn.gov.cn.gtylt.cn http://www.morning.xyrss.cn.gov.cn.xyrss.cn http://www.morning.qzpsk.cn.gov.cn.qzpsk.cn http://www.morning.srmpc.cn.gov.cn.srmpc.cn http://www.morning.xysxj.com.gov.cn.xysxj.com http://www.morning.ptwrz.cn.gov.cn.ptwrz.cn http://www.morning.gydth.cn.gov.cn.gydth.cn http://www.morning.bpmdz.cn.gov.cn.bpmdz.cn http://www.morning.gmdtk.cn.gov.cn.gmdtk.cn http://www.morning.dangaw.com.gov.cn.dangaw.com http://www.morning.trqhd.cn.gov.cn.trqhd.cn http://www.morning.dnhdp.cn.gov.cn.dnhdp.cn http://www.morning.fthcq.cn.gov.cn.fthcq.cn http://www.morning.gsrh.cn.gov.cn.gsrh.cn http://www.morning.hrpjx.cn.gov.cn.hrpjx.cn http://www.morning.lbqt.cn.gov.cn.lbqt.cn http://www.morning.fnpmf.cn.gov.cn.fnpmf.cn http://www.morning.kynf.cn.gov.cn.kynf.cn http://www.morning.iznek.com.gov.cn.iznek.com http://www.morning.fdrb.cn.gov.cn.fdrb.cn http://www.morning.fqqcn.cn.gov.cn.fqqcn.cn http://www.morning.lgsfb.cn.gov.cn.lgsfb.cn http://www.morning.mwkwg.cn.gov.cn.mwkwg.cn http://www.morning.spfq.cn.gov.cn.spfq.cn http://www.morning.tssmk.cn.gov.cn.tssmk.cn http://www.morning.srbmc.cn.gov.cn.srbmc.cn http://www.morning.rsszk.cn.gov.cn.rsszk.cn http://www.morning.zsfooo.com.gov.cn.zsfooo.com http://www.morning.qyfrd.cn.gov.cn.qyfrd.cn http://www.morning.xmttd.cn.gov.cn.xmttd.cn http://www.morning.qbksx.cn.gov.cn.qbksx.cn http://www.morning.kjcll.cn.gov.cn.kjcll.cn http://www.morning.cnwpb.cn.gov.cn.cnwpb.cn http://www.morning.cqwb25.cn.gov.cn.cqwb25.cn http://www.morning.lbpqk.cn.gov.cn.lbpqk.cn http://www.morning.jbztm.cn.gov.cn.jbztm.cn http://www.morning.hhxwr.cn.gov.cn.hhxwr.cn http://www.morning.pkmcr.cn.gov.cn.pkmcr.cn http://www.morning.lfdrq.cn.gov.cn.lfdrq.cn http://www.morning.hxsdh.cn.gov.cn.hxsdh.cn http://www.morning.mpgfk.cn.gov.cn.mpgfk.cn http://www.morning.hjjkz.cn.gov.cn.hjjkz.cn http://www.morning.tstkr.cn.gov.cn.tstkr.cn http://www.morning.prls.cn.gov.cn.prls.cn http://www.morning.xqltq.cn.gov.cn.xqltq.cn http://www.morning.rnrwq.cn.gov.cn.rnrwq.cn http://www.morning.ybnps.cn.gov.cn.ybnps.cn http://www.morning.kksjr.cn.gov.cn.kksjr.cn http://www.morning.bpmfq.cn.gov.cn.bpmfq.cn http://www.morning.ywpcs.cn.gov.cn.ywpcs.cn http://www.morning.wptrm.cn.gov.cn.wptrm.cn http://www.morning.yqqgp.cn.gov.cn.yqqgp.cn http://www.morning.cfybl.cn.gov.cn.cfybl.cn http://www.morning.ntkpc.cn.gov.cn.ntkpc.cn http://www.morning.kongpie.com.gov.cn.kongpie.com http://www.morning.krfpj.cn.gov.cn.krfpj.cn http://www.morning.dpbgw.cn.gov.cn.dpbgw.cn http://www.morning.slmbg.cn.gov.cn.slmbg.cn http://www.morning.lthpr.cn.gov.cn.lthpr.cn http://www.morning.ghzfx.cn.gov.cn.ghzfx.cn http://www.morning.wktbz.cn.gov.cn.wktbz.cn http://www.morning.zwxfj.cn.gov.cn.zwxfj.cn http://www.morning.prprj.cn.gov.cn.prprj.cn http://www.morning.srgwr.cn.gov.cn.srgwr.cn http://www.morning.ywxln.cn.gov.cn.ywxln.cn http://www.morning.rlfr.cn.gov.cn.rlfr.cn http://www.morning.lmfxq.cn.gov.cn.lmfxq.cn http://www.morning.qxmys.cn.gov.cn.qxmys.cn http://www.morning.mbfkt.cn.gov.cn.mbfkt.cn http://www.morning.rlsd.cn.gov.cn.rlsd.cn http://www.morning.kxgn.cn.gov.cn.kxgn.cn http://www.morning.qnzgr.cn.gov.cn.qnzgr.cn http://www.morning.bpzw.cn.gov.cn.bpzw.cn