分享
 
 
 

转贴:C++语言常见问题解:#105 ~ #120

王朝c/c++·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

这是我从台湾的http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/发现的《C++ Frequently Asked Questions》的繁体翻译,作者是:叶秉哲,也是《C++ Programming Language》3/e繁体版的译者,该文章是非常的好,出于学习用途而将它转贴,本人未取得作者的授权,原文章的版权仍然归属原作者.

C++语言常见问题解

== Part 4/4 ============================

comp.lang.c++ Frequently Asked Questions list (with answers, fortunately).

Copyright (C) 1991-96 Marshall P. Cline, Ph.D.

Posting 4 of 4.

Posting #1 explains copying permissions, (no)warranty, table-of-contents, etc

=======================================

■□ 第17节:和 C 连结/和 C 的关系

=======================================

Q105:怎样从 C++ 中呼叫 C 的函数 "f(int,char,float)"?

告诉 C++ 编译器说:它是个 C 的函数:

extern "C" void f(int,char,float);

确定你有 include 进来完整的函数原型 (function prototype)。一堆 C 的函数可

以用大括号框起来,如下:

extern "C" {

void* malloc(size_t);

char* strcpy(char* dest, const char* src);

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

}

========================================

Q106:怎样才能建一个 C++ 函数 "f(int,char,float)",又能被 C 呼叫?

想让 C++ 编译器知道 "f(int,char,float)" 会被 C 编译器用到的话,就要用到前

一则 FAQ 已详述的 "extern C" 语法。接着在 C++ 模块内定义该函数:

void f(int x, char y, float z)

{

//...

}

"extern C" 一行会告诉编译器:送到 linker 的外部信息要采用 C 的呼叫惯例及签

名编码法(譬如,前置一个底线)。既然 C 没有多载名称的能力,你就不能让 C 程

式能同时呼叫得到多载的函数群。

警告以及实作相关事项:

* 你的 "main()" 应该用 C++ 编译之(为了静态对象的初始化)。

* 你的 C++ 编译器应该能设定连结的程序(为某些特殊的链接库)。

* 你的 C 和 C++ 编译器可能要是同一个牌子的,而且是兼容的版本(亦即:有相

同的呼叫惯例等等)。

========================================

Q107:为什么 linker 有这种错误讯息:C/C++ 函数被 C/C++ 函数呼叫到?

看前两则 FAQs 关于 extern "C" 的使用。

========================================

Q108:该怎么把 C++ 类别的对象传给/传自 C 的函数?

例子:

/****** C/C++ header file: Fred.h ******/

#ifdef __cplusplus /*"__cplusplus" is #defined if/only-if

compiler is C++*/

extern "C" {

#endif

#ifdef __STDC__

extern void c_fn(struct Fred*); /* ANSI-C prototypes */

extern struct Fred* cplusplus_callback_fn(struct Fred*);

#else

extern void c_fn(); /* K&R style */

extern struct Fred* cplusplus_callback_fn();

#endif

#ifdef __cplusplus

}

#endif

#ifdef __cplusplus

class Fred {

public:

Fred();

void wilma(int);

private:

int a_;

};

#endif

"Fred.C" 是个 C++ 模块:

#include "Fred.h"

Fred::Fred() : a_(0) { }

void Fred::wilma(int a) : a_(a) { }

Fred* cplusplus_callback_fn(Fred* fred)

{

fred->wilma(123);

return fred;

}

"main.C" 是个 C++ 模块:

#include "Fred.h"

int main()

{

Fred fred;

c_fn(&fred);

return 0;

}

"c-fn.c" 是个 C 模块:

#include "Fred.h"

void c_fn(struct Fred* fred)

{

cplusplus_callback_fn(fred);

}

把指向 C++ 对象的指针传到/传自 C 的函数,如果传出与收回的指针不是“完全相

同”的话,就会失败。譬如,不要传出一个基底类别的指针却收回一个衍生类别的指

标,因为 C 编译器不懂该怎么对多重及虚拟继承的指针做转型。

========================================

Q109:C 的函数能不能存取 C++ 类别的对象资料?

有时可以。

(请先读一读前一则关于和 C 函数间传递 C++ 对象的 FAQ。)

你可以安全地从 C 函数中存取 C++ 对象的资料,只要 C++ 的对象类别:

* 没有虚拟函数(包含继承下来的虚拟函数).

* 所有资料都在同一个存取等级中 (private/protected/public).

* 完全被包含的子对象中也都没有虚拟函数.

如果 C++ 类别有任何基底类别(或是任何被完全包含的子对象中有基底类别)的话

,技术上来说,存取该资料没有可移植性的,因为语言没规定在继承之下的类别配置是

什么样子。不过经验上,所有 C++ 编译器的做法都一样:基底类别对象先出现(在

多重继承之下,则由左到右排列之),子对象次之。

还有,如果类别(或是任何基底类别)含有任何虚拟函数,你时常可以(但不是一直

都可以)假设有一个 "void*" 出现在对象第一个虚拟函数之所在,或是在该对象的

第一个 word 那里。同样的,语言对它也没规定到,但这似乎是「大家」都采取的做

法。

如果该类别有任何虚拟基底类别,情况会更复杂而且更没有可移植性。常见的做法是:

让对象最后才包含基底类别之对象 (V)(不管 "V" 在继承阶层中在哪儿出现),物

件的其它部份则以正常的次序出现。每个有 V 这个虚拟基底类别的衍生类别,实际

上都有个“指针”指向最后一个对象的 V 的部份。

========================================

Q110:为什么我总觉得 C++ 让我「离机器更远了」,不像 C 那样?

因为事实上正是如此。

做为一个 OOPL,C++ 让你以该问题的领域来思考,让你以问题领域的语言来设计程

式,而非以解题的领域来着手。

一个 C 最强的地方是:它没有「隐藏的机制」:你看到的就是你得到的,你可以一

边阅读 C 的程序,一边「看到」每个系统时脉。C++ 则不然; C 的老手(像从前的

我们)对这种特性常会有矛盾的心理(或是说「敌视」),但是很快的他们会发现:

C++ 提供了抽象化的层次及经济的表现能力,大大降低维护成本,又不会损及执行效

率。

很自然的,用任何语言都会写出坏程序;C++ 并不会确保任何高品质﹑可重用性﹑抽

象化,或是任何「正字标记」的品质因子。C++ 不会让差劲的程序者写不出差劲的程

式;她只是协助明智的发展者做出高人一等的软件。

===================================

■□ 第18节:指向成员函数的指针

===================================

Q111:「指向成员函数的指针」和「指到函数的指针」的型态有差别吗?

是的。

考虑底下的函数:

int f(char a, float b);

如果它是普通的函数,它的型态是: int (*) (char,float);

如果它是 Fred 类别的运作行为,它的型态是: int (Fred::*)(char,float);

========================================

Q112:怎样把指向成员函数的指针传给 signal handler﹑X event callback 等等?

【译注】这是和 UNIX﹑X Window System 相关的问题,但其它系统亦可推而广之。

不要这样做。

因为若无对象去激活它,成员函数是无意义的,你不能直接使用它(如果 X 窗口系

统是用 C++ 写的话,或许就可以直接传对象的参考值了,而不光是传个指向函数的

指针;自然地,对象会包含所有要用到的函数,甚至更多)。

若想修改现有的软件,可拿最顶层的(非成员的)函数当作一层包装 (wrapper),透

过其它技巧(或许是放在全域变量中),把该对象包起来。这个最顶层的函数,会透

过适当的成员函数去使用该全域变量。

譬如,你想在中断处理中呼叫 Fred::memfn() 的话:

class Fred {

public:

void memfn();

static void staticmemfn(); // 用个 static 成员函数就行了

//...

};

//wrapper 函数会记得哪个对象该去激活全域对象的成员函数:

Fred* object_which_will_handle_signal;

void Fred_memfn_wrapper() { object_which_will_handle_signal->memfn(); }

main()

{

/* signal(SIGINT, Fred::memfn); */ //不能这样做

signal(SIGINT, Fred_memfn_wrapper); //Ok

signal(SIGINT, Fred::staticmemfn); //Also Ok

}

注意:静态成员函数不需要真正的对象才能激活,所以指向静态成员函数的指针,和

普通的指向函数的指针,具有兼容的型态(详见 ARM ["Annotated Reference

Manual"] p.25, 158)。

========================================

Q113:当我想以成员函数做为中断服务例程 (ISR) 时,为什么编译器产生(型态不

符)的错误?

这是前两个问题的特例,所以请先看看前两则解答。

非静态的成员函数,都有一个隐藏的参数,对应到 'this' 指针,该 'this' 指针会

指向该对象的案例资料 (instance data),可是系统中断的硬件/韧体并未提供这个

'this' 参数。你得用「正常的」函数(不是类别的成员)或是静态成员函数来做为

中断服务例程才行。

一个可行的解法是:用一个静态成员做为中断服务例程,让它能自己到某处去找案例

/成员的配对,以供中断呼叫之用。这么一来,当中断产生时,正常的 method 就会

被激活,不过以技术观点来看,你得先呼叫一个中介函数。

========================================

Q114:为什么我取不出 C++ 函数的地址?

这可由前一则 FAQ 推论过来。

详细的解答:在 C++ 里,成员函数有一个隐含的参数,指向该对象本身(成员函数

内的 "this" 指针)。正常的 C 函数与成员函数的呼叫惯例可视为不同,所以它们

指针的型态(指向成员函数 vs 指向函数)既不同也不兼容。C++ 引进一个新的指针

型态:指向成员的指针,要提供一个对象才能激活之(见 ARM ["Annotated

Reference Manual"] 5.5)。

注意:不要去把指向成员函数的指针强制转型成指向函数的指针;这样做的结果是未

定义的,且下场可能会很惨。譬如,指向成员函数的指针,“不必然”会包含某正常

函数的机器地址(看 ARM, 8.1.2c, p.158)。如前例所提,如果你有个指向正常 C

函数的指针的话,请用上层的(非成员的)函数,或是用 "static" 成员函数(类别

成员函数)。

========================================

Q115:怎样宣告指向成员函数的指针数组?

用 "typedef" 好让你的脑筋保持清醒。

class Fred {

public:

int f(char x, float y);

int g(char x, float y);

int h(char x, float y);

int i(char x, float y);

//...

};

typedef int (Fred::*FredPtr)(char x, float y);

这是指向成员函数的指针数组:Here's the array of pointers to member functions:

FredPtr a[4] = { &Fred::f, &Fred::g, &Fred::h, &Fred::i };

呼叫对象 "fred" 的某一个成员函数:

void userCode(Fred& fred, int methodNum, char x, float y)

{

//假设 "methodNum" 在 [0,3] 区间内

(fred.*a[methodNum])(x, y);

}

你可以用 #define 让这个呼叫清楚些:

#define callMethod(object,ptrToMethod) ((object).*(ptrToMethod))

callMethod(fred, a[methodNum]) (x, y);

====================================

■□ 第19节:容器类别与 template

====================================

Q116:怎样自一个连结串行/杂凑表等等里面,插入/存取/改变元素?

我将以最简单的「插入连结串行」为例。想把元素插入串行的头尾很容易,但只限

于这些功能的话,会使链接库过于低能(太低能的链接库比没有更糟)。

完整的解答会让 C++ 新手消化不良,所以我只提几个项目。第一个是最简单的,第

二和第三是比较好的。

[1] 替 "List" 加入一个「现在位置」的性质,加入像是 advance()﹑backup()﹑

atEnd()﹑atBegin()﹑getCurrElem()﹑setCurrElem(Elem)﹑insertElem(Elem)

﹑removeElem() 等等的运作行为。

即使在这个小例子里已经够用了,但「只有一个」现在位置的记号的话,想存取

串行中两个以上位置的元素就不太容易(譬如:「对所有 x,y 序对,做底下的

事情……」)。

[2] 把上述的 List 运作行为拿掉,移到独立的类别 "ListPosition" 中。

ListPosition 的作用是:代表 List 里「现在的位置」,这样就允许许多位置

并存于同一个串行中。ListPosition 是 List 的伙伴,所以 List 的内部可对

外界隐藏起来(否则 List 的内部就会被它的公共运作行为所公开)。注意:

ListPosition 可以把运算子多载起来,像是 advance()、backup(),因为运算

子多载只是正常运作行为的语法糖衣而已。

[3] 把整个位置处理(iteration)当成是一个基元事件(atomic event),建一个

class template 去涵盖该事件。

它不会在内部循环中使用公共存取运作行为(它有可能是虚拟函数),所以效率

能增进。不幸的,你的应用软件会多出些额外的二元码,因为 template 是以空

间换取时间的。欲知详情,请见 [Koenig, "Templates as interfaces,"

JOOP, 4, 5 (Sept 91)], 以及 [Stroustrup, "The C++ Programming Language

Second Edition," under "Comparator"].

========================================

Q117:「样版」(template)的用意是什么?

Template 本意是个压饼模子,它把饼干都压成差不多一个样子(虽然饼干的原料不

尽相同,但它们都有相同的基本外形)。同理,class template 是个样版模子,用

来描述如何将一系列的对象类别弄成同一个基本型式;而 function template 则是

用以描述一系列看起来差不多的函数。

Class template 常用于制造型别安全的容器(即使这仅止于「如何使用它」而已)。

========================================

Q118:"function template" 的语法/语意是什么?

考虑底下这个交换两个整数自变量的函数:

void swap(int& x, int& y)

{

int tmp = x;

x = y;

y = tmp;

}

假如我们想交换 float、long、String、Set 和 FileSystems,我们还得写那些

大致看起来都一样、只有型态不同的程序代码,有够烦人。这种不花脑筋的重复性工作

,正是计算机的专长,于是我们想出了 function template:

template<class T>

void swap(T& x, T& y)

{

T tmp = x;

x = y;

y = tmp;

}

每次我们以一组型别来使用 "swap()",编译器会找到上面这定义,并造出另一个

"template function" ,来当作它的「案例」(instantiation)。譬如:

main()

{

int i,j; /*...*/ swap(i,j); // 案例化 "int" 的 swap

float a,b; /*...*/ swap(a,b); // 案例化 "float" 的 swap

char c,d; /*...*/ swap(c,d); // 案例化 "char" 的 swap

String s,t; /*...*/ swap(s,t); // 案例化 "String" 的 swap

}

(注意:"template function" 是 "function template" 实体化之后的案例。)

========================================

Q119:"class template" 的语法/语意是什么?

考虑像是个整数数组的容器类别:

// 这会放在像是 "Array.h" 的标头档中

class Array {

public:

Array(int len=10) : len_(len), data_(new int[len]){}

~Array() { delete [] data_; }

int len() const { return len_; }

const int& operator[](int i) const { data_[check(i)]; }

int& operator[](int i) { data_[check(i)]; }

Array(const Array&);

Array& operator= (const Array&);

private:

int len_;

int* data_;

int check(int i) const

{ if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);

return i; }

};

如同前述的 "swap()" ,一再为 float、char、String、Array-of-String 等等来重

复设计 Array 类别,是很烦人的。

// 这会放在像是 "Array.h" 的标头档中

template<class T>

class Array {

public:

Array(int len=10) : len_(len), data_(new T[len]) { }

~Array() { delete [] data_; }

int len() const { return len_; }

const T& operator[](int i) const { data_[check(i)]; }

T& operator[](int i) { data_[check(i)]; }

Array(const Array<T>&);

Array& operator= (const Array<T>&);

private:

int len_;

T* data_;

int check(int i) const

{ if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);

return i; }

};

不像 template function 那样,template classes(案例化的 class template)必

须将那些用来案例化的参数型态明示出来:

main()

{

Array<int> ai;

Array<float> af;

Array<char*> ac;

Array<String> as;

Array< Array<int> > aai;

} // ^^^-- 注意这空格;不要用 "Array<Array<int>>"

// (编译器会把 ">>" 看成单一的元素)

========================================

Q120:什么是「参数化型别」(parameterized type)?

另一种 "class template" 的说法。

「参数化型别」是一种型别,它被另一个型别或数值所参数化(parameterized)了。

像 List<int> 是一个型别 ("List") ,它被另一个型别 ("int") 所参数化。

========================================

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有