下面哪些调用转换支持java可变长度参数数

博客访问: 7856
博文数量: 17
注册时间:
鏆傛棤浠嬬粛
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
发布时间: 14:55:24
c/c++支持可变参数的函数,即函数的参数是不确定的。[@more@]一、为什么要使用可变参数的函数?一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可以根据需要确定,因此c语言引入可变参数函数。这也是c功能强大的一个方面,其它某些语言,比如fortran就没有这个功能。典型的可变参数函数的例子有大家熟悉的printf()、scanf()等。二、c/c++如何实现可变参数的函数?为了支持可变参数函数,C语言引入新的调用协议, 即C语言调用约定 __cdecl 。 采......
阅读(749) | 评论(0) | 转发(0)
发布时间: 17:42:38
C++中的4种类型转换方式[@more@]static_cast 静态的_cast dynamic_cast 动态的_cast reinterpret_cast 重新解释的 _cast const_cast 常量的_cast C++ 里最好杜绝使用 C 方式的强制转换, 换用以上 4 个. 我们通常用的是 static_cast 在一类东西都可以转, 但是不是一类的就不能转. 即, 语义上说不通的, 两个完全不同的数据类型 static_cast 是拒绝工作的. 比如你想把一个指针转成浮点数, 或者想把 class A * 转成 class B * , 但是 class A 和 class B 又没有任何关系. 等等.... static_cast 在通过编译后, 空间和时间效率实际等价于 C 方式......
阅读(2943) | 评论(0) | 转发(0)
发布时间: 15:38:49
禁止编译器对这个值做优化。
[@more@] 禁止编译器对这个值做优化。
一般的,系统使用这个数值的时候,如果发现他在寄存器中,就会使用这个值,不会从内存中读,这时候如果这个值在别的地方改变,则系统就会使用之前的数值,会出错;使用volatile告诉系统,使用这个值,无论他是否在寄存器中,都从内存重新读取......
阅读(1106) | 评论(0) | 转发(0)
发布时间: 20:53:53
事实上我们昨天就开始上班了,上了两天班,然后明天后天又休息。[@more@]事实上我们昨天就开始上班了,上了两天班,然后明天后天又休息。公司一直以来就这么干的,五一,十一最多只放五天假。前两天放假期间在机器上装了个visual studio.net 2003, 并且装了个MSDN 2006.1 for .net2003 final 版本的。用起来感觉不错。还有就是发现用CImage可以来做图片显示的程序,而且图片的分辨率没有显示。这比目前项目中用的那个ocx控件好多了。只是用这个感觉没有用ACDSee打开的图片效果好。刚开始的时候直接在onDraw()中用CImage的draw函数,结果输......
阅读(448) | 评论(0) | 转发(0)
发布时间: 00:11:33
http://dev.csdn.net/develop/article/17/17663.shtm[@more@]......
阅读(1004) | 评论(0) | 转发(0)
给主人留下些什么吧!~~
请登录后留言。C语言中的可变长度参数列表-转
1.什么是可变长度参数
我们在C语言编程中有时会遇到一些参数个数可变的函数,即函数的入参个数和类型是不确定的,例如printf()函数,其函数原型为:
int printf(char*
format, ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式:
printf("%d",i);
printf("the number is
%d ,string is:%s", i,
以上这些东西已为大家所熟悉。但是究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现的呢?
2.可变长度参数函数的一个例子
为了说明问题,通过编写一个自定义的可处理可变长度参数的函数minprintf来说明。
printf函数的定义格式为:
int printf(char *fmt, …)
这里,…的含义为参数的类型和个数是不确定的。…声明只能出现在参数列表的尾部。函数返回输出的字符个数。
我们自己定义的函数minprintf声明格式为:
void minprintf(char *fmt, …)
因为这里我们不需要返回输出的字符个数,所以定义返回类型为void。
带有可变长度参数的函数里,即没有参数的类型也没有参数的个数,那么编译器是怎么处理这样的函数的呢?
标准头文件stdarg.h定义了一系列的宏来处理这个可变长度的参数列表。这个头文件的实现会因编译器的不同而各异,但它们的接口却是相同的。
类型va_list:&定义为这样一个数据类型,循环使用且每次指向一个可变的参数;在我们举的例子minprintf函数里指变量ap(argument
pointer);
函数va_start:&初始化ap,使ap指向第一个可变的参数;注意,这个函数必须在使用ap前被调用;minprintf必须至少有一个确定的参数,而且,最后一个确定的参数才是va_sart函数的一个入参;
函数va_arg:返回一个可变长度参数的值并使ap指向下一个可变长度参数,该函数使用一个类型名来确定要返回的类型和指针ap需要移动的字节单位;
函数va_end:做一些必要的清理工作,需要在程序结束前调用。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。3853人阅读
using&namespace&std;
& &&virtual&void&f()&{&&&cout&&&&&A::f()
& &&void&f()&const&{&&&cout&&&&&A::f()
const &;&}
class&B&:&public&A
& &&void&f()&{&&&cout&&&&&B::f()
& &&void&f()&const&{&&&cout&&&&&B::f()
const &;&}
void&g(const&A*&a)
& & a-&f();
int&main(int&argc,&char&*argv[])
& & A*&p&=&new&B();
& & p-&f();
& &&delete(p);
& &&return&0;
A. B::f() B::f() const
B. B::f() A::f() const
C. A::f() B::f() const
D. A::f() A::f() const
分析:用 const 来修饰函数和没用 const 是不同的重载,const 修饰函数表明,函数不能修改对象的状态/值。对于非 const 对象调用非 const 函数,当然也可以调用 const 函数(优先前者);然而 const 对象只能调用 const 函数。其次,A 中的 void f() 标注为 virtual ,那么子类 B 中跟它原模原样声明即使米有标注为 virtual 的函数依然是 virtual 函数。virtual 函数就是通常所谓的实现多态的方法了,比如,上面的 p-&f(); p 虽然是
A 类型指针,但是实际上指向 B 对象,调用的时候调用的是 B 的 f() 方法。为嘛 a-&f() 不调用 B 的 f() const 方法呢?c++ 除非标注为 virtual,否则不进行多态,也就是说 a 是 A 类型的指针,就调用 A 对应的 f() const 方法。
问题并没有到此结束,上面的代码就是有 bug 的,试问, 的时候删除对象,是作为 A 对象删呢,还是 B 对象删呢?p 的类型是 A*,但是指向的是 B 对象丫,好吧,这里应该使用多态,正确的做法应该将 A 的析构函数标注为 virtual,否则,程序就有 undefinde behaviour(未定义的行为)。继续讨论,看下面的 code :
这里的结果会和上面的一样么?!——,实际情况是神马呢,——,不一样!这个时候的结果选 D,理由:多态只能通过指针和引用使用,对象不能使用。说到引用,将上面的代码修改为 A& base = B(); 会怎么样呢?编译错误!引用不能引用临时对象!所以代码只能改为 B A& base = 比如下面的:
这个时候程序的结果还是 B。定义 b 的时候,使用上面注释掉的行不行呢?不行,因为那样,编译器会认为 b 是一个函数!!what?!!默认构造对象的时候,() 是不能加的??!!好吧,就是这样。再进一步说,会不会赶脚上面的代码有点“蹩脚”?如果 A a = B(); 就是可以的,然而 A& a = B(); 不可以,而要首先去定义一个 B对象,然后去引用,麻烦!好吧,c++11给 me 们带来了新希望(如果 u 不认为是灾难的话):
这里的结果的答案还是 B。A&& 是右值引用,使得 me 们可以引用到一个临时量 B(); 自此以后那个临时量就归右值引用管了,不再算是临时量。基本到此为止,有点混乱,有点O__O&…。再最后补充一句,少用指针,多用对象和引用!(使用引用的时候,析构函数貌似不需要显式地标注为 virtual,这一点细节有点不清楚,至少编译器对此没有提示。)
链表和数组的区别: ( 3 分 )
A. 在有序的情况下搜索
B. 插入和删除
C. 随机访问
D. 数据存储类型
分析:ABC 是明显的,有序的情况下数组可以二分查找,而链表不可以;插入和删除,数组不如链表灵活;链表不如数组可以随机访问。D 选项,不敢选,me 数据结构感觉跟存储类型么有必然关系,如果说存储类型指的是栈 (stack) 或是堆 (heap) 的话,数组也可以在堆上分配,链表也可以是静态链表(栈上的空间)。
Windows 下进程和线程的描述,哪些是对的:( 3 分 )
A. 操作系统的一个程序必须有一个进程,但是不必须有一个线程
B. 进程有自己的栈空间,而线程只共享父进程的栈空间
C. 线程必从属于一个进程
D. 线程可以更改从属的进程
分析:最初 me 不确定有些说法,因为题目强调说的是 Windows 下的进程和线程,不过后来翻看了下书,基本确定了某些说法。Windows 中的进程和线程的概念和一般操作系统书中的说法是一致的,进程是资源分配的基本单位,而线程是 CPU 调度的基本单位;一个线程从属于一个进程,当然一个进程可以创建多个线程,一个进程中的多个线程共享进程的栈空间(当然也有其他一些资源);但是每一个线程也可以有自己的栈空间,叫 TLS(线程本地存储);如果一个进程中没有显式地创建一个线程,那么就是所谓的单线程进程,其实也就是 main
执行流对应的线程。这样的话,A、B 是错的,C 是对的,而线程可以更改所属进程,太扯了,错误!补充一点,记得以前看 UNIX 高级编程,貌似有这样的说法:UNIX 系统的进程和线程和前面的说法也基本一致,但是 Linux 却特殊一点,特殊到哪一点呢?似乎没有进程和线程的明确划分,都是线程,只不过有些线程就是共享一些数据(类似于线程共享进程的数据),给人的赶脚就是同属于一个进程;有些线程不同享数据,就如同进程一般有所区分。(这里的说法不一定准确,所以,O__O&…)
下面代码段的运行结果:( 3 分 )
A. 10 10&B. 10 11&C. 11 10&D. 11 11
分析:神奇的题目有木有丫!按标准的说法,像 x=x++; 这样的表达式,是 undefined !就是标准中没有定义的。因为 x++ 表达式的值是 10 ,后来要赋值给 x,其次 x 还要自加 1,问题是,先赋值呢,还是先自加呢?这里没有统一的说法!虽然 y = ++y; 不管怎么说结果都一样,me 认为这样的表达式也不是很合理!在一个表达式中对同一个变量多次赋值和多次使用,结果很难把握。
说说运行结果:多数人在 vc++ 和 gcc 下运行结果都是 D,但是 me 的运行结果是 B,me 的系统,win7 64位,使用 Mingw-w64 gcc 4.8.0 编译器。
C# 或是 Java 程序段的结果:( 3 分 )
array[2][2] 返回神马 ?
A. 9 B. 6 C. 2&D. 溢出
分析:介个,貌似不用多说,跟 C 中的数组有所区分,C 中的二维数组就是相同一维数组的数组,结构比较整齐,而 Java/C# 中的二维数组仅仅是一维数组的数组而已,所以 array[2][2] 这个元素其实不存在,在 Java 中运行会抛出越界异常。(实际上面的代码段不是合法的 Java 代码段,不过意思是那样的。)
下面说法哪些正确?( 3 分 )
A. // a 是常数
B. // a 是常数
C. int const *a; // a 指向常数的指针
D. const int *a; // a 是常指针
E. int const *a; // a 是常指针
分析:关于 const 和 static 貌似永远有说不完的话题,O__O&…。A、B、C 都是正确的,D 和 E 都是指向常量的指针,const 放置到 int 前面还是后面不是问题所在,而 int * 才是常量指针,可以修改指向的值,但是不能修改指向。实际话题还可以再深入些,比如 A 的声明是合法的 C++ 声明吗?是合法的 C 声明吗?在 C++ 中 A 的写法肯定是 error,因为没有初始化!而在 C 中,A 只是声明而已,就是说明它是一个 const int,maybe 在其他处定义了。下面的代码段:
c 程序上面是合法程序,c++ 是错误程序!如果 typedef int *PtrI 这种情况下 const PtrI a 是常量指针呢,还是指向常量呢?—— 可以自己去尝试一下。(答案:常量指针。)
下面程序的执行结果:( 3 分 )
分析:程序的运行结果是 ,这里没有答案!至于答案的分析,应该涉及到 c/c++ 内存对象的布局问题,虽然 me 不大懂,但是还是可以“想当然”滴班门弄斧一下。像上面的类 A,和 c 中的结构体是兼容的,在 c++ 中叫做 POD,结构体中数据布置,应该是按照声明的顺序放置的(中间可能有空隙),所谓名字的访问其实也是根据顺序访问的,看个下面的例子:
int 是 4 个字节,long long 是 8 个字节,强制将 long long 的 test 当做结构体 struct Test 处理,.a 访问的是低 4 个字节的内容,.b 访问的是高 4 个字节的内容。有了这点基础,再来看原来的题目,A 是一个类(结构体),里面只容纳一个 long,(me 电脑上 4 个字节),B 继承了 A,又多容纳了一个 long,实际就是俩 long。data 的 B 类型数组 4 个对象,本来是 8 个 long,不看 seta 函数的话,全部是 1,而在函数 seta
中处理的时候,数组变成 A 类型的了,一个 A 元素是一个 long,所以在函数 seta 内部,访问 data[0] - data[3] 实际是访问的 8 个 long 的前 4 个 long,也就是将前 4 个 long 置为 2,前 4 个 long 在 main 函数中对应的就是 B 类型数组的 data[0].a、data[0].b、data[1].a、data[1].b,于是乎 main 函数输出 B 数组对象,结果是 。
1000 个瓶子中有一瓶毒药,一只老鼠吃到毒药一周之内会死,如果要在一周之内检测出有毒药的一瓶,问至少需要几只老鼠?( 5 分 )
A. 9&B. 10&C. 32 D. 999&E. 上面答案都不对
分析:当初图样图森破,图拿衣服。老鼠吃过药不会立马死,当时 me 这个重点么抓到,其次,要测出来这瓶毒药至少几只老鼠,me 当时傻×:最少一只吧,比如正好碰巧测出来!~~~~(&_&)~~~~好吧,网上这个题目有很多人已经回答了,10 只老鼠,因为每只老鼠的死亡状态可能有 2 种,10 只的话,可以拼凑出 2 ^ 10 = 1024 种状态,也就是 1024 只瓶子的情况都可以测出来,至于细节,下面叙述。
0 - 1023 的话,用二进制表示正好是 10 位,每一位都是 0 或是 1。如果第一位表示第一只老鼠吃不吃的情况,第二位表示第二只老鼠吃不吃的情况,第 n 位表示第 n 只老鼠吃不吃的情况,首先说,这 1024 个瓶子,正好可以给老鼠们一种吃法,现在的问题是,比如第 k 瓶有毒,老鼠们肯定有一种状态吧,比如有些死了,有些没有死,我们希望第 k 瓶有毒就对应一种状态,这样的话,根据状态就可以反推是第 k 瓶。现在就看是不是有这么个一一对应关系。
比如第 1 瓶有毒,0000...0001,第一只老鼠吃了,其他的老鼠都没有吃,如果有毒的话,第一只老鼠就死了,其他老鼠都没事(注:其他瓶没有毒,所以即使第一只老鼠吃了其他瓶,其他老鼠也吃了其他的瓶,但是它们的命运却不改变,就因为第 1 瓶有毒!);再比如第 3 瓶有毒,0000...0011,第一、二只老鼠吃了,其他老鼠没有吃,结果第一和第二只老鼠死了,其他老鼠没事(注:它们有没有吃其他的瓶子也不影响它们的命运,因为第 3 瓶有毒!)。由此可见,第 k 瓶有毒,就是 k 对应的二进制数的二进制位为 1 的老鼠死掉,这是必然的。一瓶毒药的方法,比如第
k 瓶,对应一个二进制数,对应一种老鼠死法,所以,一种老鼠死法,就对应一瓶毒药的方法,也就是根据老鼠的死法,就能判断出是第多少瓶:
将第 i 只老鼠死了,记二进制的第 i 位为 1,然后看看这个二进制是多少,就可以了。(前提,根据瓶子的编号,分配老鼠吃药,二进制位上为 1 表示吃。)
方法是不是唯一的呢?对称的还有一种,根据瓶子的编号,还是给老鼠喂药,对应编号为 1 的不喂,对应编号为 0 的反而喂药,最终判断方法如何呢?
将第 i 只老鼠死了,记二进制的第 i 位为 0,然后看看这个二进制是多少,就可以了。(前提,根据瓶子的编号,分配老鼠吃药,二进制位上为 1 表示不吃。)
在 C 语言中下面那个语句的结果是 1 ?( 5 分 )
A. main 函数正常结束的返回值
B. return 7&1;
C. char *p=&hello&; return p == &hello&;
D. return &hello& == &hello&;
E. 上面都不对
分析:A 一定是错的,C/C++ main 函数正常结束,返回 0;B 一定是对的,C 和 D 呢?标准 C 规定是,同样两个字符串字面量,比如 &hello&,内存是放一份还是两份,由编译器决定!所以,C 和 D 的结果,一般的编译器应该会有警告提示,说明是 undefined behaviour !(前面第 2 题和第 5 题,也有遇到过!) 所以,C 和 D 只能说很可能是对的,但是却不一定!
F、G、X 都是32位有符号整数,F=X/2,G=X&&1,如果 F != G,那么:( 5 分 )
A. 编译错误
B. X 是奇数
C. X 是负数
分析:前面第 5 题和第 10 题都出现了 undefined behavior 的行为!不幸的是,这里又出现了一个!~~~~(&_&)~~~~ 这是 Microsoft 的错,这种题就根本就不应该出!
A 肯定是错的,C 肯定是对的,因为正数和0,一定不会出现 /2 和 &&1 不相等的情况。然后 B 和 D 呢?如同上一题,几乎可以肯定滴说,B 和 D 也是对的,为神马是几乎肯定?因为负数的右移,多数都是实现为“算术移位”,如果是“逻辑移位”的话,一个负数移位之后变成了一个正数,B 和 D 都不对。
现在说,“算术移位”,B 是对的。算术移位,最高位填 1,对于负偶数来说,/2 和 &&1 结果还是一样,跟正数类似,所以,结果不一样,一定是奇数!其次,其次!!欲哭无泪丫……5/2 == 2,5%2 == 1,这个结果是绝对的!但是对于 -5/2 == ? -5%2 == ? 上,标准是没有规定的,于是又出现一个 undefined behaviour! 一般的实现是,模运算的结果要和被除数一致,然后保证 (a/b)*b + (a%b) == a,所以-5%2 == -1,-5/2 == -2,同时 -5
&& 1 == -3,于是乎 F-G = 1。me 死的心都有了,O__O&…
3*4 的方格,有多少个长方形?( 5 分 )
E. 上面都不对
分析:高中数学题目,O__O&…(本来想解释一下的,但是赶脚么必要丫,公式 C(2,4) * C(2,5) = 6*10 = 60,横向取 2 条线,纵向取 2 条线,就是一个长方形。)
一个直线将一个平面分成 2 部分,两条直线分成 4 部分,如果直线不平行,多条直线不共一点,问 100 条直线将平面分成几部分?( 5 分 )
分析:小学数学题目,O__O&…(公式:1+1+2+3+4+...+n=1+(n+1)n/2 = 1+5050 = 5051)
下面哪些是稳定排序:( 5 分 )
A. 冒泡儿排序
B. 快速排序
D. 归并排序
E. 选择排序
分析:略。
Web 应用程序中常使用 MVC 模式,关于说法下面哪些是对的?( 5 分 )
A. 模型 ( Model )表示数据以及处理数据的业务逻辑
B. 视图 ( View ) 是对模型的(可视化)展示,它渲染模型的结果,典型的是一个用户接口元素(user interface element)
C. 控制器介于用户和系统之间,它接受用户的输入,指挥着模型和视图来完成输入对应的任务
D. MVC 的常用实践是,模型从用户接收 GET 和 POST 的请求,然后决定做神马,通过移交给控制器和视图
E. 上面都不对
分析:MVC 貌似就是 ABC 的说法。
根据下面哪些可以确定一棵二叉树?( 5 分 )
A. 前序遍历和中序遍历
B. 前序遍历和后序遍历
C. 中序遍历和后序遍历
D. 后序遍历
分析:至少要一个中序遍历,前序+后序遍历不中。
n 个字符构成的字符串,假设每个字符都不一样,问有多少个子串?( 5 分 )
A. n+1
C. n(n+1)/2
分析:介个,貌似也简单,长度为 1 的字符串 n 个,长度为 2 的 n-1 个,长度为 3 的 n-2 个,...,长度为 n 的 1 个,然后 n+(n-1)+(n-2)+...+1 = ?。
根据下面给的表和 SQL 语句,问执行 SQL 语句更新多少条数据?( 5 分 )
sql 语句:
update Books set NumberOfCopies = NumberOfCopies + 1 where AuthorID in
select AuthorID from Books
group by AuthorID
having sum(NumberOfCopies) &= 8
表中数据:
NumberOfCopies
SQL Server 2008
SharePoint 2007
SharePoint 2010
SQL Server 2012
A. 1&B. 2&C. 3 D. 4 E. 5
分析:不多说,SQL 语句执行。
下图中从 S 到 T 的最短路径是多少?(边上是连接点的长度):( 13 分 )
分析:从前向后找最短路径,先是 A1、A2、A3,然后 B1、B2,其次 C1、C2,最后 T 。
N个球中有一个假冒伪劣(重量不足),如果给你一个天平允许你测 3 次找出那个假冒伪劣,问 N 可能的值?( 13 分 )
分析:3 个一次可以测出来,3*3 = 9 个以内 2 次,3*3*3 = 27 个以内,3次!所以,有个公式:n 次可以测出来 3^n 以内的假冒伪劣,至于怎么测,方法都一样,平均分成三墩,然后,略。
题目简单分析:
C 语言 3 道:5、10、11:++ 运算符、字符串指针、按位运算;(各种 bug 程序!)C++ 语言 3 道:2、7、8:多态、const 关键字、const 和指针、POD;Java/C# 语言 1 道:6:多维数组;数据结构 5 道:3、14、16、17、19:链表和数组、稳定排序、二叉树、字符串、最短路径;Windows 程序设计 1 道:1:函数修饰符;操作系统 1 道:4:线程和进程;设计模式 1 道:15:MVC 模式;数据库 1 道:18:sql 语句;数学 2 道:12、13:组合数学;智力题 2 道:9、20:二进制、等比数列;
分值估计
下面是 me 的分值估计,最佳估计,就是将那些比较肯定的暂时米有发现有问题的,就看成是“完全正确”;大致估计,将有些拿不准的但是现在没有发现错误的,化作“半对”;最坏估计,将某些可能错的(和其他人想法有出入),就化为“错误”,但是比较肯定的还是算做“完全正确”。分值列出来,原来自己的水平就是那样丫!希望尽可能滴接近实际情况:
最佳估计:0 + 3 + 3 + 2 - 2 + 3 + 3 + 0 - 3 + 5 + 3 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 13 + 7 = 72
大致估计:0 + 3 + 2 + 2 - 2 + 3 + 3 + 0 - 3 + 3 + 3 + 5 + 5 + 5 + 3 + 5 + 5 + 5 + 13 + 7 = 67
最差估计:0 + 3 - 2 + 2 - 2 + 3 + 3 + 0 - 3 + 3 - 3 + 5 + 5 + 5 - 3 + 5 + 5 + 5 + 13 + 7 = 51
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:12361次
排名:千里之外
原创:10篇
评论:14条可变长度函数参数的原理及使用_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
可变长度函数参数的原理及使用
上传于||暂无简介
阅读已结束,如果下载本文需要使用0下载券
想免费下载更多文档?
定制HR最喜欢的简历
下载文档到电脑,查找使用更方便
还剩4页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢浅析C/C++中的可变参数与默认参数
字体:[ ] 类型:转载 时间:
C支持可变参数的函数,这里的意思是C支持函数带有可变数量的参数,最常见的例子就是我们十分熟悉的printf()系列函数。我们还知道在函数调用时参数是自右向左压栈的
千万要注意,C不支持默认参数
C/C++支持可变参数个数的函数定义,这一点与C/C++语言函数参数调用时入栈顺序有关,首先引用其他网友的一段文字,来描述函数调用,及参数入栈:
------------ 引用开始 ------------ C支持可变参数的函数,这里的意思是C支持函数带有可变数量的参数,最常见的例子就是我们十分熟悉的printf()系列函数。我们还知道在函数调用时参数是自右向左压栈的。如果可变参数函数的一般形式是:&&& f(p1, p2, p3, …)那么参数进栈(以及出栈)的顺序是:&&& …&&& push p3&&& push p2&&& push p1&&& call f&&& pop p1&&& pop p2&&& pop p3&&& …我可以得到这样一个结论:如果支持可变参数的函数,那么参数进栈的顺序几乎必然是自右向左的。并且,参数出栈也不能由函数自己完成,而应该由调用者完成。
这个结论的后半部分是不难理解的,因为函数自身不知道调用者传入了多少参数,但是调用者知道,所以调用者应该负责将所有参数出栈。
在可变参数函数的一般形式中,左边是已经确定的参数,右边省略号代表未知参数部分。对于已经确定的参数,它在栈上的位置也必须是确定的。否则意味着已经确定的参数是不能定位和找到的,这样是无法保证函数正确执行的。衡量参数在栈上的位置,就是离开确切的函数调用点(call f)有多远。已经确定的参数,它在栈上的位置,不应该依赖参数的具体数量,因为参数的数量是未知的!
所以,选择只能是,已经确定的参数,离开函数调用点有确定的距离(较近)。满足这个条件,只有参数入栈遵从自右向左规则。也就是说,左边确定的参数后入栈,离函数调用点有确定的距离(最左边的参数最后入栈,离函数调用点最近)。
这样,当函数开始执行后,它能找到所有已经确定的参数。根据函数自己的逻辑,它负责寻找和解释后面可变的参数(在离开调用点较远的地方),通常这依赖于已经确定的参数的值(典型的如prinf()函数的格式解释,遗憾的是这样的方式具有脆弱性)。
据说在pascal中参数是自左向右压栈的,与C的相反。对于pascal这种只支持固定参数函数的语言,它没有可变参数带来的问题。因此,它选择哪种参数进栈方式都是可以的。甚至,其参数出栈是由函数自己完成的,而不是调用者,因为函数的参数的类型和数量是完全已知的。这种方式比采用C的方式的效率更好,因为占用更少的代码量(在C中,函数每次调用的地方,都生成了参数出栈代码)。
C++为了兼容C,所以仍然支持函数带有可变的参数。但是在C++中更好的选择常常是函数重载。------------ 引用结束 ------------
根据上文描述,我们查看printf()及sprintf()等函数的定义,可以验证这一点:_CRTIMP int __cdecl printf(const char *, ...);_CRTIMP int __cdecl sprintf(char *, const char *, ...);
这两个函数定义时,都使用了__cdecl关键字,__cdecl关键字约定函数调用的规则是:调用者负责清除调用堆栈,参数通过堆栈传递,入栈顺序是从右到左。
下一步,我们来看看printf()这种函数是如何使用变个数参数的,下面是摘录MSDN上的例子,只引用了ANSI系统兼容部分的代码,UNIX系统的代码请直接参考MSDN。
------------ 例子代码 ------------
代码如下:#include &stdio.h&#include &stdarg.h&int average( int first, ... );
void main( void ){&& printf( "Average is: %d/n", average( 2, 3, 4, -1 ) );}
int average( int first, ... ){&& int count = 0, sum = 0, i =&& va_
&& va_start( marker, first );&&&& /* Initialize variable arguments. */&& while( i != -1 )&& {&&&&& sum +=&&&&& count++;&&&&& i = va_arg( marker, int);&& }&& va_end( marker );&&&&&&&&&&&&& /* Reset variable arguments.&&&&& */&& return( sum ? (sum / count) : 0 );}上例代码功能是计算平均数,函数允许用户输入多个整型参数,要求作后一个参数必须是-1,表示参数输入完毕,然后返回平均数计算结果。
逻辑很简单,首先定义&& va_表示参数列表,然后调用va_start()初始化参数列表。注意va_start()调用时不仅使用了marker这个参数列表变量,还使用了first这个参数,说明参数列表的初始化与函数给定的第一个确定参数是有关系的,这一点很关键,后续分析会看到原因。
调用va_start()初始化后,即可调用va_arg()函数访问每一个参数列表中的参数了。注意va_arg()的第二个参数指定了返回值的类型(int)。
当程序确定所有参数访问结束后,调用va_end()函数结束参数列表访问。
这样看起来,访问变个数参数是很容易的,也就是使用va_list,va_start(),va_arg(),va_end()这样一个类型与三个函数。但是对于函数变个数参数的机制,感觉仍是一头雾水。看来需要继续深入探究,才能的到确切的答案了。
找到va_list,va_start(),va_arg(),va_end()的定义,在.../VC98/include/stdarg.h文件中。.h中代码如下(只摘录了ANSI兼容部分的代码,UNIX等其他系统实现略有不同,感兴趣的朋友可以自己研究): 代码如下:typedef char *& va_
#define _INTSIZEOF(n)&& ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)& ( ap = (va_list)&v + _INTSIZEOF(v) )#define va_arg(ap,t)&&& ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )#define va_end(ap)&&&&& ( ap = (va_list)0 )从代码可以看出,va_list只是一个类型转义,其实就是定义成char*类型的指针了,这样就是为了以字节为单位访问内存。其他三个函数其实只是三个宏定义,且慢,我们先看夹在中间的这个宏定义_INTSIZEOF:
#define _INTSIZEOF(n)&& ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
这个宏的功能是对给定变量或者类型n,计算其按整型字节长度进行字节对齐后的长度(size)。在32位系统中int占4个字节,16位系统中占2字节。表达式&(sizeof(n) + sizeof(int) - 1)的作用是,如果sizeof(n)小于sizeof(int),则计算后的结果数值,会比sizeof(n)的值在二进制上向左进一位。如:sizeof(short) + sizeof(n) - 1 = 55的二进制是0x,sizeof(short)的二进制是0x,所以5的二进制值比2的二进制值向左高一位。表达式&~(sizeof(int) - 1)的作用时生成一个蒙版(mask),以便舍去前面那个计算值的"零头"部分。如上例,~(sizeof(int) - 1) = 0x(谢谢glietboys的提醒,此处应该是0xFFFFFF00)同5的二进制0x做"与"运算得到的是0x,也就是4,而直接计算sizeof(short)应该得到2。这样通过_INTSIZEOF(short)这样的表达式,就可以得到按照整型字节长度对齐的其他类型字节长度。之所以采用int类型的字节长度进行对齐,是因为C/C++中的指针变量其实就是整型数值,长度与int相同,而指针的偏移量是后面的三个宏进行运算时所需要的。
关于编程中字节对齐的内容请有兴趣的朋友到网上参考其他文章,这里不再赘述。
继续,下面这个三个宏定义:
第一:#define va_start(ap,v)& ( ap = (va_list)&v + _INTSIZEOF(v) )
编程中这样使用&& va_&& va_start( marker, first );可以看出va_start宏的作用是使给定的参数列表指针(marker),根据第一个确定参数(first)所属类型的指针长度向后偏移相应位置,计算这个偏移的时候就用到了前面的_INTSIZEOF(n)宏。
第二:#define va_arg(ap,t)&&& ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
此处乍一看有点费解,(ap += _INTSIZEOF(t)) - _INTSIZEOF(t)表达式的一加一减,对返回值是不起作用的啊,也就是返回值都是ap的值,什么原因呢?原来这个计算返回值是一方面,另一方面,请记住,va_start(),va_arg(),va_end这三个宏的调用是有关联性的,ap这个变量是调用va_start()时给定的参数列表指针,所以
(ap += _INTSIZEOF(t)) - _INTSIZEOF(t)
表达式不仅仅是为了返回当前指向的参数的地址,还是为了让ap指向下一个参数(注意ap跳向下一参数是,是按照类型t的_INTSIZEOF长度进行计算的)。
第三:#define va_end(ap)&&&&& ( ap = (va_list)0 )
这个很好理解了,不过是将ap指针置为空,算作参数读取结束。
至此,C/C++变个数函数参数的机制已经很清晰了。最后还要说一点要注意的问题:在用va_arg()顺序跳转指针读取参数的过程中,并没有方法去判断所得到的下一个指针是否是有效地址,也没有地方能够明确得知到底要读取多少个参数,这就是这种变个数参数的危险所在。前面的求平均数的例子中,要求输入者必须在参数列表最后提供一个特殊值(-1)来表示参数列表结束,所以可以假设,万一调用者没有遵循这种规则,将导致指针访问越界。
那么,可能有朋友会问,printf()函数就没有提供这样的特殊值进行标识啊。
别急,printf()使用的是另一种参数个数识别方式,可能比较隐蔽。注意他的第一个确定参数,也就是被我们用作格式控制的format字符串,他的里面有"%d","%s"这样的参数描述符,printf()函数在解析format字符串时,可以根据参数描述符的个数,确定需要读取后面几个参数。我们不妨做下面这样的试验:
printf("%d,%d,%d,%d/n",1,2,3,4,5);实际提供的参数多于前面给定的参数描述符,这样执行的结果是
也就是printf()根据format字符串认为后面只有4个参数,其他的就不管了。那么再做一个试验:
printf("%d,%d,%d,%d/n",1,2,3);
实际提供的参数少于给定的参数描述符,这样执行的结果是(如果没有异常的话)
1,2,3,2367460
这个地方,每个人的执行结果可能都不相同,原因是读取最后一个参数的指针已经指向了非法的地址。这也是使用printf()这类函数需要特别注意的地方。
总结:变个数的函数参数在使用时需要注意的地方比较多。我个人建议尽量回避使用这种模式。比如前面的计算平均数,宁可使用数组或其他列表作为参数将一系列数值传递给函数,也不用写这样的变态函数。一方面是容易出现指针访问越界,另一方面,在实际的函数调用时,要把所有计算值依次作为参数写在代码里,很龌龊。
虽然这么说,但有些地方这个功能还是很有用处的,比如字符串的格式化合成,像printf()函数;在实际应用中,我还经常使用一个自己写的WriteLog()函数,用于记录文件日志,定义与printf()相同,使用起来非常灵活便利,如:
WriteLog("用户%s, 登录次数%d","guanzhong",10);写在文件里的内容就是
用户guanzhong, 登录次数10编程语言的使用,在遵循基本规则的前提下,是仁者见仁,智者见智。总之,透彻了解之后,选择一个符合自己的好的习惯即可
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具

我要回帖

更多关于 java 可变参数使用 的文章

 

随机推荐