辣子鸡丁::天水涧

在天堂与地狱之间有个地方,叫作尘世

原创 混沌 In C++::是类型?还是函数调用?收藏

新一篇: 混沌 IN C++::动态资源管理陷阱 | 旧一篇: 偷窥Boost Conversion Library(一)

难度:

文前提醒:看这篇文章时须家长陪同并引导,以免走火入魔

 

先看看下面的代码?   

struct A

{

     A(){}

};

 

template<typename T>

void func(T() )

{}

 

int main()

{

   A a( A() );    //(1), OK

   func( A() );   //(2), Wrong

   a = 5;          //(3), Wrong

}

 

(1) A  a( A() ); 是什么意思?

在这里是用A()创建一个对象,然后初始化对象a吗?这里真正的语a是一个参数为返回A对象的无参函数指针的函数,所以它的真面目应该是A a( A (*)() ),其中的A()是个函数类型,在实际作参数时会退化为一个A(*)()指针 。而这句仅仅起到声明函数的作用。重点就在于A()并不是创建对象,而是一个无参的、返回为A的函数类型

 

(2) func( A() ); 为什么会出错呢?

这里虽然函数模板func的参数同样也是一个函数类型,那为什么会出错呢? 其实在这里A()就不是一个函数类型了,而是创建一个匿名对象。那么这个函数即成了 void func( A ),而原本的函数模板func 类型是void ( T (*)() ),很明显参数类型不匹配。

 

(3) a = 5; 现在应该没有任何疑问了

int 传递给 A ()( A (*)() ),地球人都知道是错的。

 

千万别走火入魔!

是什么导致了(1)(2)中的A()表现出不同的语义呢?

答:在这里的 A() 就具有二义性:它即可以解释为新创建的类 A 的一个临时对象(在此过程中要调用 A 的构造函数),也可以解释为一种函数类型声明:其返回值为A类型的对象,函数参数为空。如果是前种解释,则 a 为类 A 的一个对象;如果是后一种,则整句就是一个函数声明:声明了一个函数 a,其返回值类型为 A,函数参数(省略了参数名。对于函数声明这样做是可以的)是上述的一个函数类型。到底是前一种还是后一种解释取决于它所在的语义环境。

 

更多

struct A

{

     A(int ){}

};

 

A a(A(1)); 在这里A(1) 就不可能是“类型”了,因为其中有个1,而A(1)就成了一个表达式,所以A(1)这里是调用的A(int)

 

非常感谢whyglinux,为本文指出错误并作出修改。

//The End

发表于 @ 2004年06月29日 05:22:00|评论(loading...)|编辑

新一篇: 混沌 IN C++::动态资源管理陷阱 | 旧一篇: 偷窥Boost Conversion Library(一)

评论

#semage 发表于2004-06-29 20:34:00  IP: 61.48.132.*
struct A {
A();
};

这里的A()不是构造函数的吗?
#AS 发表于2004-06-29 12:50:00  IP: 203.93.220.*
技术爱好者可以研究看看
工程运用就显得过于钻牛角尖!
#Cynics 发表于2004-06-29 13:01:00  IP: 220.168.69.*
确实是很经典。如果没有对C++语法的精确理解,出现这样的问题,还真无法理解出了什么问题。其实类似的语法是很容易出现在自己所写的代码中的。只是没有上面的那样显得如此“故意”。要做个C++高手,这是体现水平的地方。
#辣子鸡丁 发表于2004-06-29 13:26:00  IP: 222.183.140.*
补充一下。注意下面代码在VC6里的结果
#include <iostream>
using namespace std;

struct A
{
A(){}
};

int main()
{
A a( A() );
cout << typeid(A()).name() << endl;
cout << typeid(a).name() << endl;

return 0;
}
不知注意到没有,这里的a是一个类A的实例对象,而不是上面文中说到的函数。
---------
这个具有二义性的声明到底是 一个函数声明 还是 对象声明?

目前的情况是
VC 6/VC.NET 认为是对象声明。
GCC认为是函数的声明。

而C++ Standard 也认为这是函数的声明。
#young 发表于2004-06-29 21:45:00  IP: 221.232.122.*
func( A() ); //(2), Wrong
func要的是一个具体的类型对象,你给的是个函数,怎么可能对呢?模板连func的实例化都无法完成。

#buxoman 发表于2004-06-29 13:27:00  IP: 211.138.140.*
我从来都没有见过这样的语法呢?我翻遍了所有的教材,都没有看到过。哎!原来世界上还有这样神奇的语法!
#ybbqy 发表于2004-06-29 14:18:00  IP: 222.171.7.*
无聊…
#辣子鸡丁 发表于2004-06-29 14:23:00  IP: 222.183.140.*
这个函数是可以通过的,关键在于你怎么调用
template<typename T>
void func(T() )
{}

int my_func(){}

int main()
{
fun(my_func);
}
还有,这里的文章都是谈的是标准C++,所以在旧的编译器上编译上面的代码(特别是模板),是不能通过的
#ninyjun2008 发表于2004-06-29 14:24:00  IP: 211.142.211.*
茴香豆有四种写法。。。
呵呵!
#辣子鸡丁 发表于2004-06-29 23:21:00  IP: 222.183.140.*
>func( A() ); //(2), Wrong
>func要的是一个具体的类型对象,你给的是个函数,怎么可能对呢?模板连func的实例化都无法完成。

文中提到的代码都不在VC6中讨论,而是在标准C++中讨论。
#solotony 发表于2004-06-29 23:27:00  IP: 211.162.148.*
这确实是C++中比较令人费解的部分。但是并不表示它是正确的实践。请注意C++ 是一种许可程序做任何事情的语言,程序员有必要对自己的程序负责。一个对于例子中的代码,良好的C++程序员,会这样写
A a = A(A());编译器和读者都不会把它当成函数声明。而对于func的定义,应写明
template <typename T>
void func( T(*lpfn)())
{
}
而不是用它的简化版
template <typename T>
void func( T())
{
}

The Best Practice is C++.
#fkeslf 发表于2004-06-30 00:48:00  IP: 66.199.168.*
solotony 说的对,写程序时,应该选择最清楚的表诉方式,这是一个程序员最基本的素质。
#辣子鸡丁 发表于2004-06-30 01:11:00  IP: 222.183.140.*
>solotony 说的对,写程序时,应该选择最清楚的表诉方式,这是一个程序员最基本的素质。

你们的观点是无人能推翻的,呵呵,只是如果遇到这样的代码时,你会不会找不到入口点呢? 我的文章前面都加了“混沌 In C++”字样,意思就是很说一些隐蔽的问题
#noproblem_jyb 发表于2004-06-30 08:17:00  IP: 202.96.229.*
错,错,错!
What are you doing? Which book you are learning?

Effective STL (by Scott Meyers), item 6 (be alert for C++'s most vexing parse) has already explained what wrong of this statement.

Study it carefully, please!
#别逗了 发表于2004-06-29 09:49:00  IP: 61.129.126.*
看晕了。
#noproblem_jyb 发表于2004-06-30 08:53:00  IP: 202.96.229.*
Sorry, I just browsed through this serial articles. I want to say, all of your talk is evil.

前段时间(几乎有2年了吧),在国内风风火火的C++图书热,没有锻炼出多少对这门语言有一点精确理解的人。从The C++ Programming Language (by Bjarne Stroustrup),到C++Primer (by Stan Lippman),从The C++ Standard Library(by Nicolai Josuttis)到STL源码分析(by 侯捷),从Generic Programming and STL(by Matthew Austern)到C++ Templates(by Nicolai Josuttis),从Inside C++ Object Model(by Stan Lippman)到The Design and Evolution of C++(by Bjarne Stroustrup),在图书市场一片繁荣——至少比以前繁荣——的情况下,用功的人依然遥遥领先,稀松的人还是远远落后。

网络在中国现在可以说是很发达了,有多少人在认真找资料学习呢?就这几篇文章,我来提醒一下它们的前辈吧。

混沌 IN C++::Template Metaprograms
"Traits: The else-if-then of Types", C++ Report, April 2000 by Andrei Alexandrescu

混沌 IN C++::模板参数的奥秘
C++ Templates, 8.2.2 Nontype Parameters

混沌 IN C++::动态资源管理陷阱
The RAII (Resources Requisition Is Initialization) pattern has already be published!

其中,C++ Templates在November 12, 2002开始发布,然而具体技术细节早在网络上和ANSI C98(clause 14.3.2)中有很详细的说明。

看来我们都得好好评估一下,网络在中国发挥的作用(也许这也是中国国情吧)。

我因该在开篇第一行就把《茶馆》里的那张写了四个大字的纸给亮出来。
#cat5 发表于2004-06-29 15:24:00  IP: 218.22.24.*
.NET 2003中的C++编译器品质还不错。
上面的代码编译后第一句有一行警告:
warning C4930: 'A a(A (__cdecl *)(void))': prototyped function not called (was a variable definition intended?)
跟作者表达的意思是一样的。
#Allen 发表于2004-06-29 10:08:00  IP: 218.80.61.*
其实没必要那么搞拉。
会基本语法就可以了。
#ztne 发表于2004-06-29 15:36:00  IP: 218.79.97.*
极其无聊
#bluecoff 发表于2004-06-30 09:05:00  IP: 221.232.93.*
A a( A() )应该写成 A a((*)()),前者一般编译器都会出错,不出错这编译器就有问题,不知道作者用的什么编译器?因为形参必须是一个变量或变量声明,不能是函数声明也不能是一个对象实列。
同理template<typename T>

void func(T() )

{}
也是错误的,如果按照作者本意应该写成template<typename T>

void func(T(*)() )

{}
如果要求形参是个T类型的对象,应该
template<typename T>

void func(T)

{}或对象引用
template<typename T>

void func(T& )

{}


#bluecoff 发表于2004-06-30 09:10:00  IP: 221.232.93.*
这样的函数声明本就是错的一踏糊涂,写出这样函数的人应该把C++语法再温习温习,何来混吨之有,可笑大家还在这讨论。
#bushido 发表于2004-06-30 09:11:00  IP: 218.4.185.*
看晕了的說。
#bluecoff 发表于2004-06-30 09:13:00  IP: 221.232.93.*
A a(A(1)); 在这里A(1) 就不可能是“类型”了,因为其中有个1,由于1不是“类型”,所以A(1)就成了函数调用
---------------------------------------------
晕啊,函数的调用居然可以当成函数声明的形参。错的更没边了,哈哈哈
#e 发表于2004-06-29 15:50:00  IP: 202.102.84.*
尝试在你的boss面前讲解这高深的C++知识,看看你的boss什么反应~~
#david4no 发表于2004-06-29 10:43:00  IP: 61.49.248.*
template<typename T>

void func(T() )

{}

这个函数就有问题,不会编过去的
#lanzhengpeng 发表于2004-06-30 09:50:00  IP: 61.49.184.*
A a(A(1));
A(1)是调用构造函数,生成一个临时变量。本应该是const的,可是有些编译器并不强制为const。a(A(1))则明显是个拷贝构造函数。
emplate <typename T>
void func( T())
{
}
这个代码太怪异了,缺省了函数指针变量也确实该这个写。

但是,我不认同这样写代码的。现在的代码,更多是给人看的,不是给机器看的
#爱上小白 发表于2004-06-29 11:14:00  IP: 218.242.221.*
重点就在于A()并不是创建对象,而是一个无参的、返回为A的无名函数

==========================
为什么A()不是创建对象呢? 照理说, 这样解释也对啊??
#moonsilver 发表于2004-06-29 11:36:00  IP: 219.76.185.*
不是混沌,而是基本的语法都没有搞清楚。还是沉下心好好学一下为好。
#gccr 发表于2004-06-30 10:46:00  IP: 202.107.78.*
我觉得弄这些东西有点SA,我在搞这些东西前.会去看编译原理什么.
#li 发表于2004-06-29 17:17:00  IP: 61.145.178.*
SB
#duyanning 发表于2004-06-29 11:49:00  IP: 222.90.12.*
苏格拉底?这位哥们是谁呀,你的文章真不错!是翻译的吗?继续关注.....
#zhouwei 发表于2004-06-29 17:21:00  IP: 222.182.4.*
你不如去研究回香豆的回有多少种写法算了。
语法上的细节我觉得真的很无聊。你写程序的时候真的遇得到?
#aleaku babala 发表于2004-06-29 17:37:00  IP: 222.76.21.*
没有实用价值!
#stevenxu 发表于2004-06-30 11:07:00  IP: 219.82.160.*
有些人自己不懂,还想让别人陪你糊涂。把程序写明白当然是正确做法,但是所谓正确做法恰是建立在对混沌C++的正确理解之上的。谁是生而知之者?
#zealot198226 发表于2004-06-30 12:57:00  IP: 221.232.94.*
而且我觉得楼主也给我们一个机会让我们知道什么样的代码是必须避免的。
#Caoyu015 发表于2004-06-30 11:37:00  IP: 218.14.30.*
又学了一招。
#毕业生a 发表于2004-06-29 18:02:00  IP: 61.145.143.*
基本功问题,这应该是在科班时扎扎实实打下的基础
#辣子鸡丁 发表于2004-06-30 13:24:00  IP: 222.183.141.*
建议你们不要人云亦云,先把回复看完在发表自己的看法。我本想不反驳这些无关的话题。可是有些人不反驳是不行的。

1、先说语法问题,如果错,就说出理由。不要认为你自己知道的就是正确的,你在上面列出了很多关于C++的书,说明你也看了不少,希望你不要败在不起眼的问题上。但是关于A a(A())的语法用编译器来判断是否正确,这是学C++的一大忌,我到是想反问那位是用的什么编译器。

template <typename T>
void func( T()) { } 的参数到底是什么?知者自明。
反驳并无错,但是
“晕啊,函数的调用居然可以当成函数声明的形参。错的更没边了,哈哈哈” 你这样的回复就不知你用意了,是表示自己很技艺高超吗?还是想贬低别人?

2、
你看了那些文章,就代表别人都读过吗?而对于上面的文章,不知你为什么不直接列出IOS/IEC 14882的相关章节呢
#boysoft 发表于2004-06-30 12:26:00  IP: 221.224.222.*
其实func( A() ); 是有两义性,A()既符合类型定义又符合函数调用,而具体实现就要看各个编译器厂家了,如果你要把A()作为类型的话,建议改成func( (A()));
#zealot198226 发表于2004-06-30 12:54:00  IP: 221.232.94.*
楼主的意思是说a只是一个函数声明吗???如果a不是函数声明的话那么a就只能算的上是A的一个实例,而A是结构类型,那么能不能这样给结构付值是个问题。
希望能和楼主谈谈C++的问题,我本人觉得在应用上也许这些问题真的没什么作用,但是绝对还是有助与我们对语法的理解,要成为一位大师一定要内外兼修,当然现在我对语法的了解还很浅,有些东西的确看的不是很清楚,不过也没关系,C++博大精深,我敢说连Bjarne Stroustrup先生也不见得能搞通所有的问题,呵呵,毕竟C++的标准也不是他一个人制定的/。
#DemoHunter 发表于2004-06-30 16:04:00  IP: 61.144.39.*
in fact it is not usable!!!!!
fucking rubbish!@!!
#seacloud 发表于2004-06-29 20:01:00  IP: 61.170.213.*
怎么就是有人喜欢骂人呢? 你认为不好就不看嘛。

记得在Perl的FAQ上看到没有Perl对应的Lex/Yacc的对应文件,原因是Perl的有些语法实在是太奇怪了以至于不太好用Yacc描述。不知道有没有对应的Yacc文件来满足百分之一百的C++标准,呵呵。
#ggstudy 发表于2004-06-29 20:13:00  IP: 202.194.19.*
对于搂主的钻研精神值得赞扬

对一样东西研究的透彻是一个问题,实际的应用是另一个问题。
我们需要研究的人,也离不开应用的人。
#bluecoff 发表于2004-07-01 08:42:00  IP: 221.232.74.*
有两点混淆了,

第一,函数指针和函数绝对是两回事,A(fun*)()绝对不等于A fun(),我相信这大家都知道,函数指针是变量,当然可以做为函数申明中的形参数。但函数能做为形参出现吗?我从来都认为不能,我在任何书本包括原版英文书上没看到这样的东西,我也从未看到和写过类似 T func(T2 funcp(int&))的代码。但现在我发现大家都似乎承认函数声明可以做函数声明的形参数。是我错了吗?

第二,变量声明和初始化一个变量是两回事(但不绝对是两回事)
如 A a(3)和A a既是声明也是初始化,只不过调用了不同的重载构造函数。但在函数形参中出现时候 int func(A a)和int func(A a(3))绝对是两回事,前者中的A a仅仅是一个声明,没有任何 初始化构造操作,后者的A a(3)却是要构造一个A类型的对象实例,一个实例对象肯定不能做为一个形参出现,所以后者不可能通过编译。

归纳起来A()如果看成函数,是否对错我不得而知,因为我现在不敢冒天下之大不wei,认为函数在C++中不是一种类型。(苦笑:),如果把A()看成A tem_a(),则他是用一个默认构造函数初始化一个tem_a对象,万万是不能做为函数形参出现的,除非编译器有点“聪明”,在他在函数形参的地方出现时,“聪明”的把他识别为
A tem_a,自动把后面括号去掉。
#LoopyPuzzle 发表于2004-06-30 17:36:00  IP: 202.196.50.*
这样写有用么?虽然C++很强大,强大到可以写出这样的代码。但是,我还是真的很迷茫——这样写有用么?源程序不仅仅是用来编译后执行的,也是让人用来分析和修正的,弄出来那么晦涩的代码,我想就是程序员本人过一段时间后再来看也不是很快就能看动的吧。
一句话,我还是认为程序写的越清晰易懂越好。这样的程序,没有多大的价值。
#noproblem_jyb 发表于2004-07-01 08:58:00  IP: 202.96.229.*
to 辣子鸡丁:
>> 一个语法的存在有它的原因,这个语法本身背后代表了什么意思
I don't agree with what you say. The reason of programming language is that you must "write" your problem solution on something. You can't make use of "nothing" to perform something.

When we got a problem what must be solved, you think it, get a possible solution, try the solution, and re-think if the result is wrong. The programming likes this situation. You get the ideas about the problem, present it (your ideas) using code, and you solved the problem. But what can I do when I got your code? I can't think like you, investigate the problem like you, even the problem doesn't like you encountered. So I won't understand you code completely.

No code, no constraints. When the idea (or solution) "write" itself using something, most of design compromises are lost!

I'd like to say, get your code from your solution, don't do it reversely.
#chunhuizhao 发表于2004-07-01 16:10:00  IP: 221.197.43.*
我不太清楚模板的用法,但是
void func(T() )

{}

是否应该加上个参数名称?像这样:
void func(T() fp )

{}


#辣子鸡丁 发表于2004-06-30 17:54:00  IP: 222.183.141.*
可能你没理解我的本意。我的意思并不是说要把代码如何如何的难懂,而是说的是从一些不常见的语法中,对C++本身的思考。一个语法的存在有它的原因,这个语法本身背后代表了什么意思,我想这是我们都想知道的
#bluecoff 发表于2004-07-01 09:11:00  IP: 221.232.74.*
补充一下,void func(A tem_a())也有可能被编译器看成是tem_a的默认参数是一个默认构造函数构造的临时对象。
#ilovevc 发表于2004-07-01 16:41:00  IP: 218.17.229.*
问题的关键应该有点类似回字的四种写法,不过这里讲的是无名函数指针的省略写法,即T (*pfun)() -> T (*)() -> T()。
例如通常的写法是T (*pfun)(); 声明一个函数指针pfun,指向一个函数,函数的返回类型为T,参数无。

如果我们不关心pfunc参数,可以写成T (*)(),当然这样实际上就无法使用函数指针了。

更进一步,(*)都可以省略,写成 T()。 这样其实就有岐义了。因为T()也可能为一个匿名对象。这就需要根据上下文才能判断。

如果T()是在函数参数声明中,例如void func(T()),没有岐义地T()应该解释成T(*)()。

如果两种情况都可以解释,例如A a(A())的情况,即可以解释成创建一个匿名A对象,然后调用A的拷贝构造函数,也可以将A()解释成A (*)(),但是无论VC7还是GCC编译器,都是优先选择将A()解释为A(*)()。VC6就解释成前者A::A(),估计是错误的,否则VC7应该就不会有此改动。



#别逗了 发表于2004-07-01 17:42:00  IP: 61.129.126.*
怎么总有人拿回字有四种写法来做比方?研究回字有几种写法真的没意义吗?写文章确实不用管这些,但是,研究文字学的人也说这对自己毫无意义,不过是为自己遮羞罢了。
而这里的问题,至少,对做C++编译器的人算不上无聊吧?说无聊的人,不过是这篇文章不适合你罢了,何必攻击贴主呢?在某些地方,这确实价值,这就够了。
#ilovevc 发表于2004-07-01 18:42:00  IP: 218.17.229.*
只是打个比方,绝对没有攻击的意思。而且通过这个我也学到了新知识。
不过照你的说来,我觉得倒是比较贴切:写程序的不用管这些(我看而且要尽量避免),但是研究的(写编译器的,反正我不是)倒是要知道。

我也奇怪C++为什么要弄些在我看来诡秘的语法,例如
int c[10];
0[c] = 10;
c[0] = 10;
都合法,平常大伙使用0[c]去取c数组的第0个元素,还是使用c[0]?
除了学院的原因,0[c]有什么其他的愿意使得是合法的?哪位大侠给解释一下?
#辣子鸡丁 发表于2004-07-01 19:15:00  IP: 222.183.142.*
int a[10];
a[N]==*(a+N);

int a[10][10];
a[M][N]==*(a+M*10+N);
依次类推

这种用法就是把数组退化成指针的做法,继续,所以0[c]合法是无须质疑的,见下面代码
int a[10][10];
a[M][N]==N[a[M]];
----------------------
其实很不想写的,怕写出来又有人要说rubbish。不过最后还是决定,因为语法是最干净的东西!
#SamWord 发表于2004-07-01 10:09:00  IP: 218.76.40.*
C++的具体语法细节与各厂商的编译器也有何大关系,所以不能一概而论!
#奇怪 发表于2004-07-01 20:43:00  IP: 61.234.56.*
"这里真正的语义是a是一个参数为返回A对象的无参函数类型的函数",说到函数调用,它应作为右值出现

A a( A() );// A a(A temp = A() );

说的越多,越叫人糊涂
#ilovevc 发表于2004-07-01 20:51:00  IP: 218.17.229.*
有道理。
不过extern vector<int> vi;
vi[0] = 10;
可以,

而0[vi] = 10; 错误。

原生类型就显得有特别优待了。另外,除了多一个选择,有什么特别的理由使用0[a]?有什么 a[0]作不到的?




#dnnupt 发表于2004-07-01 22:19:00  IP: 218.12.100.*
学习。
但是还是烦请 辣子鸡丁 or ilovevc 能否详细的讲讲为什么
c[0] = 10;和a[M][N]==N[a[M]]; 是合法的?我实在是想不通啊,详细讲讲吧,谢谢!!
#wanginvc 发表于2004-07-01 11:22:00  IP: 202.106.103.*
首先强调一点:A()就是调用无参构造函数产生A的一个对象。
A a(A()); //(1), OK
原因:这是拷贝构造函数造成的,首先产生A()对象,然后付给a;

其次:void func(T() ),这段声明是不能通过编译的, 应改为
void func(T (*)()).
改为void func(T (*)())后,仍然产生错误2,错误3,原因如下:
func(A()); //(2), Wrong
原因:与void func(T (*)())声明不一致.

a = 5; //(3), Wrong
原因:怎么能给struct A赋值5呢?显然是错误的.

总之:笔者是在错误的假设,即:
"
在这里并不是用A()创建一个对象,然后实例化对象a。这里真正的语义是a是一个参数为返回A对象的无参函数类型的函数,所以它的真面目应该是A a( A (*)() ) 。"

的前提下去错误的解释出错原因,而且还漏掉了void func(T())的声明错误.

测试代码:
#include <cstdio>
#include <iostream>
using namespace std;

struct A
{
int i;
A(){printf("A create\n");}
};



template<typename T>

void func(T(*)())
{
printf("hehe\n");
}

int main()
{
A a(A()); //(1), OK copy constructer
func(A()); //(2), Wrong
/*可改为如下代码*/
//A (*p)();
//func(p);
a = 5; //(3), Wrong
}
#达到 发表于2004-07-02 10:56:00  IP: 221.203.25.*
大脑体操。无聊
#Muf 发表于2004-07-02 11:12:00  IP: 211.138.144.*
看了原文的评论,发现有不少人误解“函数声明怎么能当形参”。一开始,我也是一头雾水,但后来总算是理解了,就在这里稍微总结一下,也算是给自已提个醒吧。
#Pingback/TrackBack 发表于2004-07-02 11:14:00  IP: 211.138.144.*
看了原文的评论,发现有不少人误解“函数声明怎么能当形参”。一开始,我也是一头雾水,但后来总算是理解了,就在这里稍微总结一下,也算是给自已提个醒吧。
#辣子鸡丁 发表于2004-07-01 22:45:00  IP: 222.183.142.*
指针的访问
a[N]; 其实就是 *(a+N) ,那么,按照这个公式
N[a]; 也就是 *(N+a); 这个问题好比1+2==2+1 一样

a[M][N]==N[a[M]]; 稍微需要转一个弯
a[M] 返回一个一维的指针,先把这个指针看作P,那么
a[M][N]==P[N] ==> P[N]==N[P],然后把P还原成a[M]
现在 就得出N[a[M]]的合法性 结果了
#辣子鸡丁 发表于2004-07-01 22:50:00  IP: 222.183.142.*
对于这类问题,它是被语言定义的,知道就知道,不知道就不知道。真要说无聊的话,我想只有下类问题才叫无聊
++i+(++i)+(++i)=?;
#辣子鸡丁 发表于2004-07-01 23:15:00  IP: 222.183.142.*
to bluecoff
>>我也从未看到和写过类似 T func(T2 funcp(int&))的代码。但现在我发现大家都似乎承认函数声明可以做函数声明的形参数,是我错了吗?
--------------------
我不说到底是不是你错了。这是这种代码是客观存在的,最常见的是数组的传递。
void fun(int a[10])
{
a=NULL; //a是数组吗?我想任何人肯定可以一口答出它是指针
}

int main()
{
int a[10];
fun(a);
}
的确,fun里的a是指针,这是数组地址传递给一个指针,背后的代价就是退化到一个弱受限的指针上,对于函数一样
---------------------
void fun(int a())
{
a=NULL; //?
}

int a(){}

int main()
{
fun(a);
}
fun的型参 就是个函数指针,一个退化过程。
------------
函数和函数指针不一样,这个谁都知道
----------------------------------------------
A obj(); //这真是调用A的默认构造函数楼初始化obj这个对象吗?
我想这是最简单的语法问题了!!!!
--------------------------------------------------
>>void func(A tem_a())也有可能被编译器看成是tem_a的默认参数是一个默认构造函数构造的临时对象。
我想我再也没必要来证明这是错误的了
#周星星 发表于2004-07-02 11:55:00  IP: 219.145.130.*
支持 辣子鸡丁!
1。这个问题在编码时经常出现,我不明白为什么有人会说这是钻牛角尖,难道他们从来没遇到过?反正我每次编码时都会碰到。
2。能通过VC++的不等于就符合C++标准,这句话说得好。

但就这一点而言,我赞同VC++的做法
A() 就是构造一个无名A的对象;
要作为函数申明应该写成 A(*)();
#辣子鸡丁 发表于2004-07-01 23:51:00  IP: 222.183.142.*
你说远了,我不是说在代码中要使用这样的语法。而是说这个语法的问题。
总有些人喜欢跑题
#noproblem_jyb 发表于2004-07-02 08:02:00  IP: 202.96.229.*
to 辣子鸡丁:
Woo, I think you misunderstand what I say.

If you've read clause 6 of Effective STL, you'll know I understand this syntax(T (*)() and T()).

The thing I opposed is that syntax isn't important enough. There are so many columns on world, such as obfuscated C++ series. Others (out of syntax) are worth learning (idioms, patterns, generative programming, and so on).

Please don't take too much time on syntax, maybe you can write something about libraries introduction or patterns investigation, it will help us to program at *higher* level. I'll join this kind of talk actively, I guess.

#stevenxu 发表于2004-07-01 13:51:00  IP: 219.82.176.*
@bluecoff:
其实整篇文章都在说同一个意思:当函数出现在不能出现函数的地方,它就是函数指针。稍微旧一点的编译程序是无法编译的,所以不能总是用老经验验证新标准。
#辣子鸡丁 发表于2004-07-01 14:57:00  IP: 222.183.142.*
请不要把VC能通过的代码,就当作是C++
我发现这样的人真不少
#辣子鸡丁 发表于2004-07-03 15:36:00  IP: 222.183.140.*
对于这个问题我也不想再去争论。各位都说这是回字的四种写法,那么你们最后想要的结果是什么呢?是要把文章删掉吗?
#zhouqingyuan(浪帆) 发表于2004-07-02 16:40:00  IP: 202.120.168.*

有些人自己不能理解,就说无用处,真是可笑,很多东西并不是你说没有用就没有用的,也许只是对你没有用而已!只是做一些简单MIS系统当然不需要了解很多东西。
如果一个人不能作到尊重别人的成果,无论多牛,也是我所不佩服的!
#Muf 发表于2004-07-02 17:04:00  IP: 211.138.144.*
看了原文的评论,发现有不少人误解“函数声明怎么能当形参”。http://blog.csdn.net/muf/archive/2004/07/02/32200.aspx

不知道有没有人能告诉我,如何直接PingBack回来啊?难道就是这么手工回复,并粘贴Blog地址啊?
#step_by_step 发表于2004-07-03 17:00:00  IP: 218.95.233.*
我觉得这篇文章还行,我曾经在无名函数上吃过亏。
#netcasper 发表于2004-07-03 11:40:00  IP: 218.109.166.*
我来从另一个角度说说noproblem_jyb的意思吧。

如果你的知识仅仅是基于特定语言的特定版本中特别容易让人混淆的细节的话,特别是这种细节是由于某些历史原因被错误的引入,又由于兼容问题暂时无法消除的时候,你就很难通过这种知识来实现什么价值,甚至你的知识很可能因为版本的升级而瞬间毫无价值。

我想说的是,即使你知道这么做是合法的,也不要这么做,你现在能够这么做是因为法律不健全,但这不意味着你以后仍然可以。

如果说这种细节还需要有人来关心的话,那也应该是从如何消除它的角度。

我到觉得用“回字的四种写法”做比喻非常恰当,你知道回字有四种写法并不意味着你可以写出好文章,而如果你能够写出好文章,那知不知道回字有四种写法又有什么关系呢。各位觉得呢?
#zhouwei 发表于2004-07-03 11:47:00  IP: 222.182.0.*
楼上的说得中肯极了.即使你愿意关心这种语法,也只是出于如何消除它的影响.
#zhouqingyuan(浪帆) 发表于2004-07-04 23:32:00  IP: 218.1.102.*
鸡丁不要管别人说什么!
做自己想做的事情就够了!
你写的东西并不需要每个人都认可,只要有人认可就可以了!

没有什么东西是能够得到所有人认可的!!

支持你继续你的工作!
#stevenxu 发表于2004-07-05 12:13:00  IP: 218.0.236.*
有的网友是真的有疑问来讨论,但也有个别的就是想表现自己比作者高明,好像是高屋建瓴,一语道破天机,而实际上对人对己一点益处都没有。

不 要 跟 小 人 纠 缠
#whyglinux 发表于2004-07-05 19:14:00  IP: 133.1.245.*
首先说明,辣子鸡丁 提供的这篇文章是一篇优秀的、很有深度的文章(尽管存在着些许的Bug,见下面的描述)。在C语言中可能很少碰到这种情况,但是在C++中却经常遇到类型和表达式的二义性问题,程序员需要了解遇到这种情况的处理方法。看到上面很多人还没有理解就胡乱指责一气,窃认为这不是一种科学和谦虚的态度(这只能说明自身的水平还有待提高),所以感到有必要对楼主的题目再加以解释,对一些错误之处和错误观点加以指出,以还事实的本来面目。

为方便起见,先把楼主的代码贴在这里:
struct A {
A() {}
};

template <typename T>
void func( T() )
{}

int main()
{
A a( A() ); //(1), OK
func( A() ); //(2), Wrong
a = 5; //(3), Wrong
}

在这里的 A() 就具有二义性:它即可以解释为新创建的类 A 的一个临时对象(在此过程中要调用 A 的构造函数),也可以解释为一种函数类型声明:其返回值为A类型的对象,函数参数为空。如果是前种解释,则 a 为类 A 的一个对象;如果是后一种,则整句就是一个函数声明:声明了一个函数 a,其返回值类型为 A,函数参数(省略了参数名。对于函数声明这样做是可以的)是上述的一个函数类型。到底是前一种还是后一种解释取决于它所在的语义环境。

一般情况下很容易解释。例如对于func( A() );这一语句,由于func()是函数调用,不是函数声明,所以要求函数参数必须是一个表达式,而不是一个类型声明。因此,A()在这里只能解释为前一种情况,即为 A 的一个临时对象,其类型为 A(至于由此导致的问题的分析,见楼主的帖子)。

有些情况下就比较令人困惑了。例如 A a( A() ); 这样的情况。A() 可以表示临时对象也可以表示函数类型,相应地 a 可以是 A 的对象也可以是声明的函数名,没有语义环境的限制。根据C++语言规则,这种情况下 A() 解释为函数类型(类型优先原则)。如果想让 A() 解释为 A 的临时对象,可以这样做:A a( (A()) );,即在 A() 上加上括号,强制变为求值表达式,从而这时的 a 就是用 A() 临时对象初始化的一个 A 对象。

下面是对楼主这篇文章中我认为有错误的地方的一些改正。如有错误之处,欢迎指正。

>> 在这里并不是用A()创建一个对象,然后实例化对象a。
这里的“实例化”这个术语显然是“初始化”的笔误。

>> (3)、 a = 5; 现在应该没有任何疑问了
>> 把int 传递给 A (*)( A (*)() ),地球人都知道是错的。
a的真正类型应该是 A ()( A (*)() )。

>> 当A()这个具有二意性东西出现在声明或定义中,那么它都被看作是“类型”而不是函数调用。所以这样一来,(2)明显不是定义,所以A()就被解析成调用。
当 A() 不是类型的时候,把 A() 解释为“函数调用”是错误的:根本没有一个全局性的叫做 A 的函数存在。这时 A() 到底是什么,我
#noproblem_jyb 发表于2004-07-05 13:42:00  IP: 202.96.229.*
Sorry, 在看了这么多讨论之后,有一些话不得不说。

首先,要感谢辣子鸡丁把这么细节的东西写出来,这本身就需要付出很多。

我反对在这里讨论语法上这么细微的地方,是因为还有很多人会程序设计感兴趣,但远没有入门。太多的语法细节只会把真正重要的东西掩藏掉。想一想你会在什么情况下范这个(主题讨论的)错误呢?基本上,你在用(匿名的)functor。如果你在用functor,你就不应该对algorithm陌生,如果对他们都很熟悉,你就一定知道iterator,知道for语句中变量的作用域。看这个:

typedef container<Type> MyContainer;
MyContainer myContainer;
// do something on myContainer
MyContainer::iterator it =
std::find( myContainer.begin(),
myContainer.end(),
obj);
if (it != myContainer.end())
{
// do something
}
else
{
// otherwise, ...
}

或者,你用这样的风格:

typedef MyContainer::iterator IT;
IT begin = myContainer.begin();
IT end = myContainer.end();
IT res = find(begin, end, obj);
if (res != end)
{
// do something
}
else
{
// otherwise, ...
}

很显然,后者比较没有错误倾向,并且效率丝毫不低于前者(大致如此,如果你debug的速度比CPU执行几条汇编指令慢的话)。

如果你对语言达到了吹毛求疵的程度(在不做工程的时候,我差不多是这样),请你说出下面这几个变量声明的差别:

char *a[10];
char (*b)[10];
char *(c[10]);
#走火入魔 发表于2004-07-05 21:54:00  IP: 218.1.75.*
走火入魔鸟,撤~~
#辣子鸡丁 发表于2004-07-05 22:09:00  IP: 222.183.140.*
谢谢您的补充和更正
A a( (A()) ); //初始化class A对象, 被我忽略掉了。

GCC 3.4 也通不过,不过这是GCC的一直未解决的BUG
#教训傻瓜 发表于2004-07-05 16:06:00  IP: 155.97.192.*
1. 基本上,你在用(匿名的)functor。
先把functor,函数的区别搞清楚,再来考别人.ztmd搞笑.

2. 太多的语法细节只会把真正重要的东西掩藏掉。
这话基本上是不经过大脑的瞎扯. 照这个逻辑,语言就不用设计语法细节了,人想怎么写就怎么写只要大概差不多就行了,不然真正重要的东西就要掩藏掉了。另外什么是真正重要的东西?人死后一了百了,没什么能是真正重要的东西。

3. 我反对在这里讨论语法上这么细微的地方,是因为还有很多人会程序设计感兴趣,但远没有入门。
多好的忧国忧民的好同志呀。不过谁也没说每个人都得学习我的文章呀。再说不是说了本文少儿部益么?

4.
请你说出下面这几个变量声明的差别:
char *a[10];
char (*b)[10];
char *(c[10]);

就这点谱还敢摆呀,要笑死人的 -- 算你丫狠。

辣子鸡丁的文章倒没什么.
你丫这回帖真是( ).
#whyglinux 发表于2004-07-06 00:17:00  IP: 133.1.245.*
你的程序注释掉出问题的两句之后(如下),在RH上用GCC 3.0.4编译是通不过,但是用GCC 3.4.0编译是成功的呀。不知道你说的通不过是什么情况?

struct A {
A(){}
};

template<typename T>
void func(T() )
{}

int main()
{
A a( (A()) ); //(1), OK
// func( A() ); //(2), Wrong
// a = 5; //(3), Wrong
}
#辣子鸡丁 发表于2004-07-06 00:23:00  IP: 222.183.140.*
哦?我以前机上有GCC 3.4,后来看buglist上面说这个问题没解决,难道是我看错了?
#whyglinux 发表于2004-07-06 00:31:00  IP: 133.1.245.*
顺便说一下,如果是 A a( A() );,我的测试表明在GCC 2.96、3.0.4和 3.4.0上都没有问题,编译通过。
#wi 发表于2004-07-06 03:42:00  IP: 219.144.213.*
你试试这样的代码就知道了:

typedef int f();

f a;

这里a实际上是一个函数原形,两句话连起来就相当于这样:
int a();
#南郁 发表于2004-07-06 15:57:00  IP: 211.97.134.*
偶尔路过。。。看到有人提这个问题 。。


char *a[10];
char (*b)[10];
char *(c[10]);

第一:
char* a[10];
这个最常见的了。a首先和 char* 结合,表示在声明字符指针char*, 后面的[10]表示a是个数组。因此char* a[10];表示声明了一个char*数组,这个数组大小为10个成员。所以可以:
char* a [10] =
{
"aaaa",
"bbbb",
"ccccccc",
...
};

第二:
char (*b)[10];
括号估先,所以,先看(*b),表示b是一个指针。一个什么指针?一个指向char [10]这样一个字符数组的指针。请看:

char b10_data[10];
char b11_data[11];

char (*b)[10];

b = &b10_data; //正确
b = &b11_data; //错。

结论。char (*b)[10]; 声明的b,是一个只能指向“元素个数为10的字符数组”的指针。

最后一个:
char *(c[10]);
同样,括号估先。 (c[10])是一个数组。。是一个什么数组?是一个char* 的数组。
由此,它和 char* c[10];应无二致。同样可以:
char* (c[10]) =
{
"fasdf","sfasdfsj","sfsdfa",
};

char* p1,*p2,*p3;
char* (c[10]) = {p1,p2,p3};

大家讨论一下?
#周星星 发表于2004-07-07 13:30:00  IP: 221.2.225.*
A a( (A()) );
强, 辣子鸡丁 和 whyglinux 真高手也,真高手重视技术,假高手因为不懂技术,所以喜欢诬蔑技术。
#回锅肉 发表于2004-07-09 12:11:00  IP: 211.100.10.*
我觉得这个问题严格的说是c++语言的缺点之一,除了兼容性,再也找不出保留的必要性
#Ery 发表于2004-07-09 10:10:00  IP: 221.8.13.*
BT
#bzerg 发表于2004-07-12 17:19:00  IP: 202.204.176.*
class b{
public:
b();
}
应该没什么好奇怪的吧,

那么
struct a{
public:
a();
}
也应该不奇怪了。

再则
struct a{
a();
}
也应该不奇怪了。

最后
struct a{
a() {}
}
也应该不奇怪了。

你试试
struct a{
private:
a(){}
}
看你的
A a( A() );
还能通过吗?
a(){}应该说是默认构造函数。而struct默认数据类型是public,所以这里简单写后会迷惑自己了。
#whyglinux 发表于2004-07-12 19:07:00  IP: 133.1.245.*
struct a{
private:
a(){}
}

A a( A() );

编译当然不能通过了,因为你没有定义 A,你定义的是 a。你把自己给”迷惑“了。

上面已经说得很明白了,A a( A() );在这里是声明了一个函数 a,其参数的类型是 A()。只要A是一个类型就是了,跟A中的构造函数是公有还是私有这样的属性无关。

不过你在这里也提供了一个证明 A a( A() );中的 A() 不是A的一个临时对象而是类型的一个方法。如果 A() 是对象,则创建这个对象的过程中要调用构造函数 A()。显然,如果这个构造函数是私有的,则这个对象就不能被创建,会出现编译错误。但是实际情况是:无论类A的构造函数A()是私有的还是公有的,编译都能够通过。这也证明了A a( A() );中的 A() 只能作为类型解释,而不是一个对象。
#oldjacky 发表于2004-07-14 20:00:00  IP: 218.106.101.*
TO:whyglinux



struct a{
private:
a(){}
}

A a( A() );

编译当然不能通过了,因为你没有定义 A,你定义的是 a。你把自己给”迷惑“了。



你还需要修炼你的C++基础......
#whyglinux 发表于2004-07-14 21:46:00  IP: 133.1.245.*
还请楼上指教,我到底哪儿说错了?也好让我知道应该修炼哪方面。你也知道,C++的内容太多了。谢谢。
#如水江南 发表于2004-07-15 09:57:00  IP: 221.216.168.*
大家做了不少争论,但是像这样的代码,我们是要尽量回避的。
作为一种语言,这也是不足之处
如果A 定义了拷贝构造函数,那这个问题到应该怎么理解?(当然
C++标准里面还是当作函数声明来解决的)这显然没有太大的用处,反而增加了编译器设计的复杂性,增加了理解的二义性!做学问不是研究茴香豆,大家显然是研究茴香豆,我们在研究C++语法的同时,也要给C++减肥,那样语言才更完美
#如水江南 发表于2004-07-15 09:57:00  IP: 221.216.168.*
大家做了不少争论,但是像这样的代码,我们是要尽量回避的。
作为一种语言,这也是不足之处
如果A 定义了拷贝构造函数,那这个问题到应该怎么理解?(当然
C++标准里面还是当作函数声明来解决的)这显然没有太大的用处,反而增加了编译器设计的复杂性,增加了理解的二义性!做学问不是研究茴香豆,大家显然是研究茴香豆,我们在研究C++语法的同时,也要给C++减肥,那样语言才更完美
#pmliming 发表于2004-07-17 16:22:00  IP: 218.7.43.*
去看Effective STL
#TOTO VS POPO 发表于2004-07-19 15:59:00  IP: 222.95.26.*
haha
#sboom 发表于2004-07-20 00:53:00  IP: 218.19.215.*
没啥,表明我来过而已。
#FlyindanceDDr 发表于2004-08-11 02:47:00  IP: 220.113.107.*
参观
#别逗了 发表于2004-10-06 15:35:00  IP: 61.171.58.*
// 定义一个整型vector,并从标准输入对其初始化。
vector<int> v( (istream_iterator<int>( cin )), (istream_iterator<int>()) );

// 你也可以试试下面没有括号的情况会出现什么编译错误。
vector<int> v( istream_iterator<int>( cin ), istream_iterator<int>() );

whyglinux老兄的这个例子举的极好,佩服!
夏虫不可以语冰,没写过这样的代码的人,和他们谈论什么意义,似乎也是美什么意义的,呵呵。
#ADF 发表于2004-12-12 04:05:00  IP: 211.90.107.*
这是我见过的讨论的最为热烈的帖子了,呵呵,不错不错
#polyrandom 发表于2005-01-05 16:25:00  IP: 218.80.224.*
那些说这样的问题无聊的人,等到你们自己能够随心所欲的解决这样的问题的时候,再来说无聊吧。这就好比一次我和我同事经过一个千万豪宅,他对我说“住在这里面的人也不见得幸福。”我对他说的就是“等到你买得起的时候才说这句话吧。”那样可以减少不少酸味。

to 辣子鸡丁:
你说“++i+(++i)+(++i)=?;”这样的问题才叫无聊,未免有些过分了。要知道,你讨论的问题固然有意义,如果能就“++i+(++i)+(++i)=?;”来让别人知道sequence point的意义,也很重要。你犯了那些攻击你的人同样的错误。

to oldjacky:
我证明whyglinux的C++基础很好:D
#cleni 发表于2005-01-05 16:04:00  IP: 218.10.60.*
struct a{
private:
a(){}
}

忘写结尾的分号了,
不是茴香豆,可能参加讨论的闲人多吧。明白的是少数

#干啥都费事 发表于2006-06-14 17:44:00  IP: 222.35.44.*
这种代码只有可能出现在BT的考题里
如果在你编写的模块里出现这种代码
看你的头有没有干你的冲动
编码是一种艺术,是给别人看的
#阿黑 发表于2006-09-12 14:33:00  IP: 218.18.192.*
辣子鸡丁和whyglinux真高人也,又让我等看见了C++的鬼斧神工,真心地希望可以多看到这样的文章。衷心地感谢两位。
#Maxwell 发表于2006-11-02 11:47:00  IP: 60.216.11.*
这是讨论茴字有几种写法还是讨论茴字有几种意义?如果你不知道茴字的四种写法你怎么保证在所有的地方都能认出茴字来?当然,抄书匠没必要研究这么高深的问题,不过请抄书匠们注意这个世界上不是只有你们一种职业。
#kmlxk 发表于2006-11-24 19:16:00  IP: 222.180.184.*
晕了o.o,翻课本都无济于事
#yxhua 发表于2007-01-24 18:50:00  IP: 192.168.0.*
在VC60中研究了一下这2个问题(已经打开/Za选项):
1) A a(A())以及相关问题
2) ++i+(++i)+(++i)

贴出代码:
//#include <iostream>

//using std::cout;
//using std::endl;

struct A
{
public:
A(){}
A(int){}
};

int ai;
//A a(A());
A af(int());
//A af(int(*p)())
//{
//}
static const int ii = 0;
A e(A(*))
{
return A();
}
// the function equals -->
// A e(A*)
// {
// return A();
// }

A e(A(m))
{
return A();
}
//the function equals -->
// A e(A)
// {
// return A();
// }

int main(int argc, char* argv[])
{
#define iii 0

int i(2);
int ai(i);

A aii(i);//call constructor A(int)
aii = A(i);

A f(A); //declare a function f, with one parameter: type A
//A e(A(ii)); //here, e is a function declaration under vc60
A e(A); //equals A e(A(ii));
// A e( A( ix ) ) -- ix isn't defined, but no error under vc60: ix is ignored!

//A e(A(*));
A e(A*); //equals A ef(A*)

A d(A(iii));

A c(A(i));

A a(A());//A() == A(*)() and 'a' is a function
//but at here, 'a' is a variable under vc60;

int b(A(*p)());//no error

//a=5;

e(2);
//e(&A(2)); //error if /Za opened
e(0); // call e(A*)
e((A*)0);

// int x =
#yxhua 发表于2007-01-24 19:01:26  IP: 192.168.0.*
我认为研究这种问题有以下几个好处:
1) 找工作时如果碰上了这类题目, 那叫爽!
2)不小心写了晦涩的代码, 导致程序出问题, 可以凭这种积累快速的分析并找出问题所在, 还是爽!
3)可以提醒自己避免写这种代码, 没什么不爽!
4)帮别人解决晦涩的问题,也会有成就感, 还是很爽!
5)越来越深入了解你正在使用的东西,有什么不爽?
发表评论  


当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
Csdn Blog version 3.1a
Copyright © 辣子鸡丁