== Part 4/4 ============================
=======================================
■□ 第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
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 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&);
Array& operator= (const Array&);
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 ai;
Array af;
Array ac;
Array as;
Array< Array > aai;
} // ^^^-- 注意这空格;不要用 "Array
本文地址:http://com.8s8s.com/it/it29964.htm