??xml version="1.0" encoding="utf-8" standalone="yes"?> //执行python命o //调用无参数函?BR>void InvokeNoParm() //调用一参数函数 //调用多参数函?BR>void InvokeWith2Parm() int main(int argc, char* argv[]) Py_Finalize(); // 垃圾回收、清除导入库 C++历史Q?SPAN lang=EN-US>C++源于C语言Q?SPAN lang=EN-US>C
代码如下Q分别演C直接执行python语句、无q回无参数函数调用、返回单参数函数调用。返回多参数函数调用Q?BR>
#include <Python.h>
#include <iostream>
using namespace std;
void ExecPythonCommand()
{
//直接执行
PyRun_SimpleString("from time import time,ctime\n"
"print 'Today is',ctime(time())\n");
}
{
PyObject* pMod = NULL;
PyObject* pFunc = NULL;
//导入模块
pMod = PyImport_ImportModule("Life");
if(pMod)
{
//获取函数地址
pFunc = PyObject_GetAttrString(pMod, "a");
if(pFunc)
{
//函数调用
PyEval_CallObject(pFunc, NULL);
}
else
{
cout << "cannot find function a" << endl;
}
}
else
{
cout << "cannot find Life.py" << endl;
}
}
void InvokeWith1Parm()
{
PyObject* pMod = NULL;
PyObject* pFunc = NULL;
PyObject* pParm = NULL;
PyObject* pRetVal = NULL;
int iRetVal = 0;
//导入模块
pMod = PyImport_ImportModule("FuncDef");
if(pMod)
{
pFunc = PyObject_GetAttrString(pMod, "square");
if(pFunc)
{
//创徏参数
pParm = Py_BuildValue("(i)", 5);
//函数调用
pRetVal = PyEval_CallObject(pFunc, pParm);
//解析q回?BR> PyArg_Parse(pRetVal, "i", &iRetVal);
cout << "square 5 is: " << iRetVal << endl;
}
else
{
cout << "cannot find function square" << endl;
}
}
else
{
cout << "cannot find FuncDef.py" << endl;
}
}
{
PyObject* pMod = NULL;
PyObject* pFunc = NULL;
PyObject* pParm = NULL;
PyObject* pRetVal = NULL;
int iRetVal = 0;
//导入模块
pMod = PyImport_ImportModule("add");
if(pMod)
{
pFunc = PyObject_GetAttrString(pMod, "add");
if(pFunc)
{
//创徏两个参数
pParm = PyTuple_New(2);
//为参数赋?BR> PyTuple_SetItem(pParm, 0, Py_BuildValue("i",2000));
PyTuple_SetItem(pParm, 1, Py_BuildValue("i",3000));
//函数调用
pRetVal = PyEval_CallObject(pFunc, pParm);
//解析q回?BR> PyArg_Parse(pRetVal, "i", &iRetVal);
cout << "2000 + 3000 = " << iRetVal << endl;
}
else
{
cout << "cannot find function square" << endl;
}
}
else
{
cout << "cannot find add.py" << endl;
}
}
{
Py_Initialize(); //python 解释器的初始?BR>
ExecPythonCommand();
InvokeNoParm();
InvokeWith1Parm();
InvokeWith2Parm();
return 0;
}
习惯CQ+的内存分配释放,H然间不用释放,感觉很蹊P上网查发C没有释放函数。如果真q样的话Q是很可怕的Q因为无法自q理内存,但是我相信编译器作者的垃圾回收机制Q所以OKQ不!Q?BR>
代码下蝲
]]>class BaseA
{
public:
virtual void FuncA1() = 0;
virtual void FuncA2() = 0;
};
class BaseB
{
public:
virtual void FunB1() = 0;
virtual void FunB2() = 0;
};
class Sample : public BaseA,public BaseB
{
public:
virtual void FuncA1()
{
cout<<"BaseA::FuncA1"<<endl;
}
virtual void FuncA2()
{
cout<<"BaseA::FuncA2"<<endl;
}
virtual void FunB1()
{
cout<<"BaseB1"<<endl;
}
virtual void FunB2()
{
cout<<"BaseB2"<<endl;
}
}; 你们有什么方法可以求出基cȝ地址,q个问题现在ȝ我下一文章的发表,我尝试的N多方?发现思\不正?我们可以一h解决q个问题....
]]>
--1960q出C一U面向问题的高语言ALGOL 60 ?/FONT>
--1963q英国剑桥大学推ZCPLQ?SPAN lang=EN-US>Combined Programming LanguageQ语aQ后来经化ؓBCPL语言?/FONT>
--1970q美国贝?SPAN lang=EN-US>[Bell]实验室的K.Thompson?SPAN lang=EN-US>BCPL语言为基Q设计了一U类gBCPL的语aQ取其第一字母BQ称?SPAN lang=EN-US>B语言?SPAN lang=EN-US> --1972q美国贝实验室?SPAN lang=EN-US>Dennis M.Ritchie为克?SPAN lang=EN-US>B语言的诸多不I?SPAN lang=EN-US>B语言的基上重新设计了一U语aQ取其第二字?SPAN lang=EN-US>CQ故UCؓC语言?/FONT>
--1980q贝实验室?SPAN lang=EN-US>Bjarne Stroustrup?SPAN lang=EN-US>C语言q行了扩充,推出?SPAN lang=EN-US>?/SPAN>带类?SPAN lang=EN-US>C?/SPAN>Q多ơ修改后起名?SPAN lang=EN-US>C++。以后又l过不断的改q,发展成ؓ今天?SPAN lang=EN-US>C++?SPAN lang=EN-US>
C++拥有丰富的历Ԍ是许多程序员通往高阶的楼梯,也是许多高校计算Z业和E序爱好者的首选语a。这里我攉了一?SPAN lang=EN-US>C++相关的书c,每一本都?SPAN lang=EN-US>C++开发中的经典教E。部分链接在教育|内更容易下载,大家可以l箋补充?/SPAN>
Effective C++ 中文?/FONT> 链接
Effective C++ 中文?/FONT>--候捷译 链接
More Effective C++ 中文?/FONT>--候捷译 链接
数据l构C++语言描述中文?/FONT> 链接
C++标准库英文版 链接
C++ Primer英文?/FONT> 链接
C++~程思想 链接
Thinking in C++ 2nd Edition 链接
The C++ Programming Language 链接
Inside C++ Object Model(深度探烦C++对象模型Q?/FONT>--候捷译 链接
STL源码剖析--候捷译 链接
E序设计实践 ?SPAN lang=EN-US>?/SPAN>
Com技术内q?/FONT> 链接
Com+技术内q?/FONT> 链接
Windows|络~程(W?/FONT>2?/FONT>) 链接
Windows2000~程技术内q?/FONT>(By Mickey Williams) 机械工业出版C?/FONT> 链接
C++ Builder高~程技?/FONT> 链接
VC++技术内q第四版(潘爱?/FONT>) 链接
VC++技术内q第五版 链接
TCP-IP详解?/FONT>1Q协?/FONT> 链接
TCP-IP详解?/FONT>2Q实?/FONT> 链接
TCP-IP详解?/FONT>3Q?/FONT>TCP事物协议 链接
Q译注:本文的翻译相当艰苦。Bjarne Stroustrup不愧是创立C++语言的一代大师,不但思想博大_深Q而且在遣词造句上,也非常精微深奥。有很多地方Q译者反复斟酌,都不能取得理想的效果Q只能尽力而ؓ?
Html格式的文见译者主:http://www.wushuang.net
如果你对q个译E有M意见和徏议,请发信给译者:onekey@163.com?/P>
原文的地址为:http://www.research.att.com/~bs/bs_faq.htmlQ?/P>
QBjarne Stroustrup博士Q?950q出生于业wQ先后毕业于业w阉K斯大学和英国剑挢大学QAT&T大规模程序设计研I门负责hQAT&T 贝尔实验室和ACM成员?979q_B. S开始开发一U语aQ当时称?C with Class"Q后来演化ؓC++?998q_ANSI/ISO C++标准建立Q同q_B. S推出其经典著作The C++ Programming Language的第三版。)
q是一些h们经常向我问L有关C++的风g技巧的问题。如果你能提出更好的问题Q或者对q些{案有所Q请务必发Emaill我(bs@research.att.com)。请CQ我不能把全部的旉都花在更新我的主上面?/P>
更多的问题请参见我的general FAQ?/P>
关于术语和概念,请参见我的C++术语表(C++ glossary.Q?/P>
h意,q仅仅是一个常见问题与解答的列表。它不能代替一本优U教科书中那些l过_ֿ挑选的范例与解释。它也不能象一本参考手册或语言标准那样Q提供详l和准确的说明。有关C++的设计的问题Q请参见《C++语言的设计和演变》(The Design and Evolution of C++Q。关于C++语言与标准库的用,请参见《C++E序设计语言》(The C++ Programming LanguageQ?/P>
目录Q?/P>
我如何写q个非常单的E序Q?/P>
Z么编译要p么长的时_
Z么一个空cȝ大小不ؓ0Q?/P>
我必dcd明处赋予数据吗?
Z么成员函数默认不是virtual的?
Z么析构函数默认不是virtual的?
Z么不能有虚拟构造函敎ͼ
Z么重载在l承cM不工作?
我能够在构造函C调用一个虚拟函数吗Q?/P>
有没有“指定位|删除?placement delete)Q?/P>
我能防止别hl承我自qcdQ?/P>
Z么不能ؓ模板参数定义U束QconstraintsQ?
既然已经有了优秀的qsort()函数Qؓ什么还需要一个sort()Q?/P>
什么是函数对象Qfunction objectQ?
我应该如何对付内存泄漏?
我ؓ什么在捕获一个异怹后就不能l箋Q?/P>
Z么C++中没有相当于realloc()的函敎ͼ
如何使用异常Q?/P>
怎样从输入中d一个字W串Q?/P>
Z么C++不提供“finally”的构造?
什么是自动指针Qauto_ptrQ,Z么没有自动数l(auto_arrayQ?
可以混合使用C风格与C++风格的内存分z与重新分配吗?
我ؓ什么必M用一个造型来{?voidQ?/P>
我如何定义一个类内部Qin-classQ的帔RQ?/P>
Z么delete不会操作数|?Q?/P>
我能够写“void main()”吗Q?/P>
Z么我不能重蝲点符P::QsizeofQ等{?
怎样一个整型D{换ؓ一个字W串Q?/P>
“int* p”正还是“int *p”正?
对于我的代码Q哪一U布局风格Qlayout styleQ是最好的Q?/P>
我应该将“const”放在类型之前还是之后?
使用宏有什么问题?
我如何写q个非常单的E序Q?/P>
特别是在一个学期的开始,我常常收到许多关于编写一个非常简单的E序的询问。这个问题有一个很具代表性的解决ҎQ那是Q在你的E序中)d几个数字Q对它们做一些处理,再把l果输出。下面是一个这样做的例子:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
vector<double> v;
double d;
while(cin>>d) v.push_back(d); // d元素
if (!cin.eof()) { // 查输入是否出?/P>
cerr << "format error\n";
return 1; // q回一个错?/P>
}
cout << "read " << v.size() << " elements\n";
reverse(v.begin(),v.end());
cout << "elements in reverse order:\n";
for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';
return 0; // 成功q回
}
对这D늨序的观察Q?/P>
q是一D|准的ISO C++E序Q用了标准?standard library)。标准库工具在命名空间std中声明,装在没?h后缀的头文g中?/P>
如果你要在Windows下编译它Q你需要将它编译成一个“控制台E序”(console applicationQ。记得将源文件加?cpp后缀Q否则编译器可能会以为它是一DC代码而不是C++?/P>
是的Qmain()函数q回一个int倹{?/P>
d一个标准的向量(vector)中,可以避免在随意确定大的~冲中溢出的错误。读C个数l?array)中,而不产生“简单错误?silly error)Q这已经出了一个新手的能力——如果你做到了,那你已经不是一个新手了。如果你Ҏ表示怀疑,我徏议你阅读我的文章“将标准C++作ؓ一U新的语a来学习?"Learning Standard C++ as a New Language")Q你可以在本作列?my publications list)中下载到它?/P>
!cin.eof()是对的格式的检查。事实上Q它查@环是否终l于发现一个end-of-file(如果不是q样Q那么意味着输入没有按照l定的格?。更多的说明Q请参见你的C++教科书中的“流状态?stream state)部分?/P>
vector知道它自q大小Q因此我不需要计元素的数量?/P>
q段E序没有包含昑ּ的内存管理。Vectorl护一个内存中的栈Q以存放它的元素。当一个vector需要更多的内存Ӟ它会分配一些;当它不再生存Ӟ它会释放内存。于是,使用者不需要再兛_vector中元素的内存分配和释N题?/P>
E序在遇到输入一个“end-of-file”时l束。如果你在UNIXq_下运行它Q“end-of-file”等于键盘上的Ctrl+D。如果你在Windowsq_下,那么׃一个BUG它无法L别“end-of-file”字W,你可能們于用下面这个稍E复杂些的版本,它用一个词“end”来表示输入已经l束?/P>
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
int main()
{
vector<double> v;
double d;
while(cin>>d) v.push_back(d); // d一个元?/P>
if (!cin.eof()) { // 查输入是否失?/P>
cin.clear(); // 清除错误状?/P>
string s;
cin >> s; // 查找l束字符
if (s != "end") {
cerr << "format error\n";
return 1; // q回错误
}
}
cout << "read " << v.size() << " elements\n";
reverse(v.begin(),v.end());
cout << "elements in reverse order:\n";
for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';
return 0; // 成功q回
}
更多的关于用标准库事情简化的例子Q请参见《C++E序设计语言》中的“O游标准库?"Tour of the Standard Library")一章?/P>
Z么编译要p么长的时_
你的~译器可能有问题。也许它太老了Q也怽安装它的时候出了错Q也怽用的计算机已l是个古董。在诸如此类的问题上Q我无法帮助你?/P>
但是Q这也是很可能的Q你要编译的E序设计得非常糟p,以至于编译器不得不检查数以百计的头文件和C行代码。理Z来说Q这是可以避免的。如果这是你购买的库的设计问题,你对它无计可施(除了换一个更好的库)Q但你可以将你自q代码l织得更好一些,以求得将修改代码后的重新~译工作降到最。这L设计会更好,更有可维护性,因ؓ它们展示了更好的概念上的分离?/P>
看看q个典型的面向对象的E序例子Q?/P>
class Shape {
public: // 使用Shapes的用L接口
virtual void draw() const;
virtual void rotate(int degrees);
// ...
protected: // common data (for implementers of Shapes)
Point center;
Color col;
// ...
};
class Circle : public Shape {
public:
void draw() const;
void rotate(int) { }
// ...
protected:
int radius;
// ...
};
class Triangle : public Shape {
public:
void draw() const;
void rotate(int);
// ...
protected:
Point a, b, c;
// ...
};
设计思想是,用户通过Shape的public接口来操U它们,而派生类Q例如Circle和TriangleQ的实现部分则共享由protected成员表现的那部分实现QimplementationQ?/P>
q不是一件容易的事情Q确定哪些实现部分是Ҏ有的zc都有用的,q将之共享出来。因此,与public接口相比Qprotected成员往往要做多得多的改动。D例来_虽然理论上“中心?center)Ҏ有的囑Ş都是一个有效的概念Q但当你要维护一个三角Ş的“中心”的时候,是一仉帔R烦的事情——对于三角ŞQ当且仅当它实被需要的时候,计算q个中心才是有意义的?/P>
protected成员很可能要依赖于实现部分的l节Q而Shape的用P译注Quser此处译ؓ用户Q指使用Shapecȝ代码Q下同)却不见得必须依赖它们。D例来_很多Q大多数Q)使用Shape的代码在逻辑上是与“颜艜y无关的Q但是由于Shape中“颜艜y这个定义的存在Q却可能需要一堆复杂的头文Ӟ来结合操作系l的颜色概念?/P>
当protected部分发生了改变时Q用Shape的代码必重新编译——即使只有派生类的实现部分才能够讉Kprotected成员?/P>
于是Q基cM的“实现相关的信息?information helpful to implementers)对用h说变成了象接口一h感的东西Q它的存在导致了实现部分的不E_Q用户代码的无谓的重~译Q当实现部分发生改变ӞQ以及将头文件无节制地包含进用户代码中(因ؓ“实现相关的信息”需要它们)。有时这被称为“脆q基类问题?brittle base class problem)?/P>
一个很明显的解x案就是,忽略基类中那些象接口一栯使用的“实现相关的信息”。换句话_使用接口Q纯_的接口。也是_用抽象基cȝ方式来表C接口:
class Shape {
public: //使用Shapes的用L接口
virtual void draw() const = 0;
virtual void rotate(int degrees) = 0;
virtual Point center() const = 0;
// ...
// 没有数据
};
class Circle : public Shape {
public:
void draw() const;
void rotate(int) { }
Point center() const { return center; }
// ...
protected:
Point cent;
Color col;
int radius;
// ...
};
class Triangle : public Shape {
public:
void draw() const;
void rotate(int);
Point center() const;
// ...
protected:
Color col;
Point a, b, c;
// ...
};
现在Q用户代码与zcȝ实现部分的变化之间的关系被隔M。我曄见过q种技术得编译的旉减少了几个数量?/P>
但是Q如果确实存在着Ҏ有派生类Q或仅仅Ҏ些派生类Q都有用的公׃息时怎么办呢Q可以简单把q些信息装成类Q然后从它派生出实现部分的类Q?/P>
class Shape {
public: //使用Shapes的用L接口
virtual void draw() const = 0;
virtual void rotate(int degrees) = 0;
virtual Point center() const = 0;
// ...
// no data
};
struct Common {
Color col;
// ...
};
class Circle : public Shape, protected Common {
public:
void draw() const;
void rotate(int) { }
Point center() const { return center; }
// ...
protected:
Point cent;
int radius;
};
class Triangle : public Shape, protected Common {
public:
void draw() const;
void rotate(int);
Point center() const;
// ...
protected:
Point a, b, c;
};
Z么一个空cȝ大小不ؓ0Q?/P>
要清楚,两个不同的对象的地址也是不同的。基于同L理由QnewLq回指向不同对象的指针?/P>
看看Q?/P>
class Empty { };
void f()
{
Empty a, b;
if (&a == &b) cout << "impossible: report error to compiler supplier";
Empty* p1 = new Empty;
Empty* p2 = new Empty;
if (p1 == p2) cout << "impossible: report error to compiler supplier";
}
有一条有的规则Q一个空的基cdƈ不一定有分隔字节?/P>
struct X : Empty {
int a;
// ...
};
void f(X* p)
{
void* p1 = p;
void* p2 = &p->a;
if (p1 == p2) cout << "nice: good optimizer";
}
q种优化是允许的Q可以被q泛使用。它允许E序员用空cM表现一些简单的概念。现在有些编译器提供q种“空基类优化?empty base class optimization)?/P>
我必dcd明处赋予数据吗?
不必R如果一个接口不需要数据时Q无d作ؓ接口定义的类中赋予数据。代之以在派生类中给出它们。参见“ؓ什么编译要p么长的时_”?/P>
有时候,你必d一个类中赋予数据。考虑一下复数类的情况:
template<class Scalar> class complex {
public:
complex() : re(0), im(0) { }
complex(Scalar r) : re(r), im(0) { }
complex(Scalar r, Scalar i) : re(r), im(i) { }
// ...
complex& operator+=(const complex& a)
{ re+=a.re; im+=a.im; return *this; }
// ...
private:
Scalar re, im;
};
设计q种cd的目的是它当做一个内建(built-inQ类型一栯使用。在声明处赋值是必须的,以保证如下可能:建立真正的本地对象(genuinely local objectsQ?比如那些在栈中而不是在堆中分配的对?Q或者某些单操作被适当地inline化。对于那些支持内建的复合cd的语a来说Q要获得它们提供的效率,真正的本地对象和inline化都是必要的?/P>
Z么成员函数默认不是virtual的?
因ؓ很多cdƈ不是被设计作为基cȝ。例如复数类?/P>
而且Q一个包含虚拟函数的cȝ对象Q要占用更多的空间以实现虚拟函数调用机制——往往是每个对象占用一个字(word)。这个额外的字是非常可观的,而且在涉及和其它语言的数据的兼容性时Q可能导致麻?例如C或Fortran语言)?/P>
要了解更多的设计原理Q请参见《C++语言的设计和演变》(The Design and Evolution of C++Q?/P>
Z么析构函数默认不是virtual的?
因ؓ很多cdƈ不是被设计作为基cȝ。只有类在行Z是它的派生类的接口时(q些zcd往在堆中分配,通过指针或引用来讉K)Q虚拟函数才有意义?/P>
那么什么时候才应该析构函数定义ؓ虚拟呢?当类臛_拥有一个虚拟函数时。拥有虚拟函数意味着一个类是派生类的接口,在这U情况下Q一个派生类的对象可能通过一个基cL针来销毁。例如:
class Base {
// ...
virtual ~Base();
};
class Derived : public Base {
// ...
~Derived();
};
void f()
{
Base* p = new Derived;
delete p; // 虚拟析构函数保证~Derived函数被调?/P>
}
如果基类的析构函C是虚拟的Q那么派生类的析构函数将不会被调用——这可能产生p糕的结果,例如zcȝ资源不会被释放?/P>
Z么不能有虚拟构造函敎ͼ
虚拟调用是一U能够在l定信息不完?given partial information)的情况下工作的机制。特别地Q虚拟允许我们调用某个函敎ͼ对于q个函数Q仅仅知道它的接口,而不知道具体的对象类型。但是要建立一个对象,你必L有完全的信息。特别地Q你需要知道要建立的对象的具体cd。因此,Ҏ造函数的调用不可能是虚拟的?/P>
当要求徏立一个对象时Q一U间接的技术常常被当作“虚拟构造函数”来使用。有关例子,请参见《C++E序设计语言》第三版15.6.2.节?/P>
下面q个例子展示一U机Ӟ如何使用一个抽象类来徏立一个适当cd的对象?/P>
struct F { // 对象建立函数的接?/P>
virtual A* make_an_A() const = 0;
virtual B* make_a_B() const = 0;
};
void user(const F& fac)
{
A* p = fac.make_an_A(); // A作ؓ合适的cd
B* q = fac.make_a_B(); // B作ؓ合适的cd
// ...
}
struct FX : F {
A* make_an_A() const { return new AX(); } // AX是A的派?/P>
B* make_a_B() const { return new BX(); } // AX是B的派?/P>
};
struct FY : F {
A* make_an_A() const { return new AY(); } // AY是A的派?/P>
B* make_a_B() const { return new BY(); } // BY是B的派?/P>
};
int main()
{
user(FX()); // 此用户徏立AX与BX
user(FY()); // 此用户徏立AY与BY
// ...
}
q是所谓的“工厂模式?the factory pattern)的一个变形。关键在于,user函数与AX或AYq样的类的信息被完全分离开来了?/P>
Z么重载在l承cM不工作?
q个问题Q非常常见)往往出现于这L例子中:
#include<iostream>
using namespace std;
class B {
public:
int f(int i) { cout << "f(int): "; return i+1; }
// ...
};
class D : public B {
public:
double f(double d) { cout << "f(double): "; return d+1.3; }
// ...
};
int main()
{
D* pd = new D;
cout << pd->f(2) << '\n';
cout << pd->f(2.3) << '\n';
}
它输出的l果是:
f(double): 3.3
f(double): 3.6
而不是象有些人猜想的那样Q?/P>
f(int): 3
f(double): 3.6
换句话说Q在B和D之间q没有发生重载的解析。编译器在D的区域内LQ找C一个函数double f(double)Qƈ执行了它。它永远不会涉及Q被装的)B的区域。在C++中,没有跨越区域的重载——对于这条规则,l承cM不例外。更多的l节Q参见《C++语言的设计和演变》和《C++E序设计语言》?/P>
但是Q如果我需要在基类和承类之间建立一l重载的f()函数呢?很简单,使用using声明Q?/P>
class D : public B {
public:
using B::f; // make every f from B available
double f(double d) { cout << "f(double): "; return d+1.3; }
// ...
};
q行q个修改之后Q输出结果将是:
f(int): 3
f(double): 3.6
q样Q在B的f()和D的f()之间Q重载确实实CQƈ且选择了一个最合适的f()q行调用?/P>
我能够在构造函C调用一个虚拟函数吗Q?/P>
可以Q但是要心。它可能不象你期望的那样工作。在构造函CQ虚拟调用机制不起作用,因ؓl承cȝ重蝲q没有发生。对象先从基c被创徏Q“基cd于承类(base before derived)”?/P>
看看q个Q?/P>
#include<string>
#include<iostream>
using namespace std;
class B {
public:
B(const string& ss) { cout << "B constructor\n"; f(ss); }
virtual void f(const string&) { cout << "B::f\n";}
};
class D : public B {
public:
D(const string & ss) :B(ss) { cout << "D constructor\n";}
void f(const string& ss) { cout << "D::f\n"; s = ss; }
private:
string s;
};
int main()
{
D d("Hello");
}
E序~译以后会输出:
B constructor
B::f
D constructor
注意不是D::f。设想一下,如果Z不同的规则,B::B()可以调用D::f()的话Q会产生什么样的后果:因ؓ构造函数D::D()q没有运行,D::f()会试图一个还没有初始化的字符串s赋予它的参数。结果很可能是导致立卛_溃?/P>
析构函数在“承类先于基类”的机制下运行,因此虚拟机制的行为和构造函CP只有本地定义(local definitions)被用——不会调用虚拟函敎ͼ以免触及对象中的Q现在已l被销毁的Q承类的部分?/P>
更多的细节,参见《C++语言的设计和演变?3.2.4.2和《C++E序设计语言?5.4.3?/P>
有h暗示Q这只是一条实现时的h为制造的规则。不是这L。事实上Q要实现q种不安全的Ҏ倒是非常Ҏ的:在构造函C直接调用虚拟函数Q就象调用其它函C栗但是,q样意味着QQ何虚拟函数都无法~写了,因ؓ它们需要依靠基cȝ固定的创?invariants established by base classes)。这会D一片乱?/P>
有没有“指定位|删除?placement delete)Q?/P>
没有Q不q如果你需要的话,可以自己写一个?/P>
看看q个指定位置创徏(placement new)Q它对象放q了一pdArena中;
class Arena {
public:
void* allocate(size_t);
void deallocate(void*);
// ...
};
void* operator new(size_t sz, Arena& a)
{
return a.allocate(sz);
}
Arena a1(some arguments);
Arena a2(some arguments);
q样实现了之后,我们可以这么写Q?/P>
X* p1 = new(a1) X;
Y* p2 = new(a1) Y;
Z* p3 = new(a2) Z;
// ...
但是Q以后怎样正确地销毁这些对象呢Q没有对应于q种“placement new”的内徏的“placement delete”,原因是,没有一U通用的方法可以保证它被正地使用。在C++的类型系l中Q没有什么东西可以让我们认Qp1一定指向一个由Arenacd的a1分派的对象。p1可能指向M东西分派的Q何一块地斏V?/P>
然而,有时候程序员是知道的Q所以这是一U方法:
template<class T> void destroy(T* p, Arena& a)
{
if (p) {
p->~T(); // explicit destructor call
a.deallocate(p);
}
}
现在我们可以q么写:
destroy(p1,a1);
destroy(p2,a2);
destroy(p3,a3);
如果Arenal护了它保存着的对象的U烦Q你甚至可以自己写一个析构函敎ͼ以避免它发生错误?/P>
q也是可能的Q定义一对相互匹配的操作Wnew()和delete()Q以l护《C++E序设计语言?5.6中的cȝ承体pR参见《C++语言的设计和演变?0.4和《C++E序设计语言?9.4.5?/P>
我能防止别hl承我自qcdQ?/P>
可以Q但你ؓ什么要那么做呢Q这是两个常见的回答Q?/P>
效率Q避免我的函数被虚拟调用
安全Q保证我的类不被用作一个基c(例如Q保证我能够复制对象而不用担心出事)
Ҏ我的l验Q效率原因往往是不必要的担心。在C++中,虚拟函数调用是如此之快,以致于它们在一个包含虚拟函数的cM被实际用时Q相比普通的函数调用Q根本不会生值得考虑的运行期开支。注意,仅仅通过指针或引用时Q才会用虚拟调用机制。当直接通过对象名字调用一个函数时Q虚拟函数调用的开支可以被很容易地优化掉?/P>
如果实有真正的需要,要将一个类闭h以防止虚拟调用,那么可能首先应该问问Z么它们是虚拟的。我看见q一些例子,那些性能表现不佳的函数被讄拟,没有其他原因Q仅仅是因ؓ“我们习惯这么干”?/P>
q个问题的另一个部分,׃逻辑上的原因如何防止c被l承Q有一个解x案。不q的是,q个Ҏq不完美。它建立在这样一个事实的基础之上Q那是Q大多数的承类必须建立一个虚拟的基类。这是一个例子:
class Usable;
class Usable_lock {
friend class Usable;
private:
Usable_lock() {}
Usable_lock(const Usable_lock&) {}
};
class Usable : public virtual Usable_lock {
// ...
public:
Usable();
Usable(char*);
// ...
};
Usable a;
class DD : public Usable { };
DD dd; // 错误: DD::DD() 不能讉K
// Usable_lock::Usable_lock()是一个私有成?/P>
(来自《C++语言的设计和演变?1.4.3)
Z么不能ؓ模板参数定义U束QconstraintsQ?
可以的,而且Ҏ非常单和通用?/P>
看看q个Q?/P>
template<class Container>
void draw_all(Container& c)
{
for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
}
如果出现cd错误Q可能是发生在相当复杂的for_each()调用时。例如,如果容器的元素类型是intQ我们将得到一个和for_each()相关的含义模p的错误(因ؓ不能够对对一个intD用Shape::draw的方??/P>
Z提前捕捉q个错误Q我q样写:
template<class Container>
void draw_all(Container& c)
{
Shape* p = c.front(); // accept only containers of Shape*s
for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
}
对于现在的大多数~译器,中间变量p的初始化会触发一个易于了解的错误。这个窍门在很多语言中都是通用的,而且在所有的标准创徏中都必须q样做。在成品的代码中Q我也许可以q样写:
template<class Container>
void draw_all(Container& c)
{
typedef typename Container::value_type T;
Can_copy<T,Shape*>(); // accept containers of only Shape*s
for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
}
q样很清楚了,我在建立一个断a(assertion)。Can_copy模板可以q样定义Q?/P>
template<class T1, class T2> struct Can_copy {
static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
Can_copy() { void(*p)(T1,T2) = constraints; }
};
Can_copy(在运行时)查T1是否可以被赋值给T2。Can_copy<T,Shape*>查T是否是Shape*cdQ或者是一个指向由Shapecdq承而来的类的对象的指针Q或者是被用戯{换到Shape*cd的某个类型。注意这个定义被_C最:
一行命名要查的U束Q和要检查的cd
一行列出指定的要检查的U束(constraints()函数)
一行提供触发检查的Ҏ(通过构造函?
注意q个定义有相当合理的性质Q?/P>
你可以表达一个约束,而不用声明或复制变量Q因此约束的~写者可以用不着去设惛_量如何被初始化,对象是否能够被复Ӟ被销毁,以及诸如此类的事情?当然Q约束要查这些属性的情况时例外?
使用现在的编译器Q不需要ؓU束产生代码
定义和用约束,不需要用宏
当约束失败时Q编译器会给出可接受的错误信息,包括“constraints”这个词Q给用户一个线索)Q约束的名字Q以及导致约束失败的详细错误Q例如“无法用double*初始化Shape*”)?/P>
那么Q在C++语言中,有没有类gCan_copy——或者更好——的东西呢?在《C++语言的设计和演变》中Q对于在C++中实现这U通用U束的困难进行了分析。从那以来,出现了很多方法,来让U束cd得更加容易编写,同时仍然能触发良好的错误信息。例如,我信L在Can_copy中用的函数指针的方式,它源自Alex Stepanov和Jeremy Siek。我q不认ؓCan_copy()已经可以标准化了——它需要更多的使用。同P在C++C中,各种不同的约束方式被使用Q到底是哪一U约束模板在q泛的用中被证明是最有效的,q没有达成一致的意见?/P>
但是Q这U方式非常普遍,比语a提供的专门用于约束检查的机制更加普遍。无论如何,当我们编写一个模板时Q我们拥有了C++提供的最丰富的表辑֊量。看看这个:
template<class T, class B> struct Derived_from {
static void constraints(T* p) { B* pb = p; }
Derived_from() { void(*p)(T*) = constraints; }
};
template<class T1, class T2> struct Can_copy {
static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
Can_copy() { void(*p)(T1,T2) = constraints; }
};
template<class T1, class T2 = T1> struct Can_compare {
static void constraints(T1 a, T2 b) { a==b; a!=b; a<b; }
Can_compare() { void(*p)(T1,T2) = constraints; }
};
template<class T1, class T2, class T3 = T1> struct Can_multiply {
static void constraints(T1 a, T2 b, T3 c) { c = a*b; }
Can_multiply() { void(*p)(T1,T2,T3) = constraints; }
};
struct B { };
struct D : B { };
struct DD : D { };
struct X { };
int main()
{
Derived_from<D,B>();
Derived_from<DD,B>();
Derived_from<X,B>();
Derived_from<int,B>();
Derived_from<X,int>();
Can_compare<int,float>();
Can_compare<X,B>();
Can_multiply<int,float>();
Can_multiply<int,float,double>();
Can_multiply<B,X>();
Can_copy<D*,B*>();
Can_copy<D,B*>();
Can_copy<int,B*>();
}
// 典型的“元素必ȝ承自Mybase*”约?
template<class T> class Container : Derived_from<T,Mybase> {
// ...
};
事实上,Derived_fromq不查来源(derivationQ,而仅仅检查{换(conversionQ,不过q往往是一个更好的U束。ؓU束想一个好名字是很隄?/P>
既然已经有了优秀的qsort()函数Qؓ什么还需要一个sort()Q?/P>
对于初学者来_
qsort(array,asize,sizeof(elem),elem_compare);
看上d古怪了Q而且比这个更隄解:
sort(vec.begin(),vec.end());
对于专家来说Q在元素与比较方式(comparison criteriaQ都相同的情况下Qsort()比qsort()更快Q这是很重要的。而且Qqsort()是通用的,所以它可以用于不同容器cd、元素类型、比较方式的L有意义的l合。D例来_
struct Record {
string name;
// ...
};
struct name_compare { // 使用"name"作ؓ键比较Record
bool operator()(const Record& a, const Record& b) const
{ return a.name<b.name; }
};
void f(vector<Record>& vs)
{
sort(vs.begin(), vs.end(), name_compare());
// ...
}
而且Q很多hƣ赏sort()是因为它是类型安全的Q用它不需要进行造型QcastQ,没有人必d为基本类型写一个compare()函数?/P>
更多的细节,参见我的文章《将标准C++作ؓ一U新的语a来学习》(Learning C++ as a New languageQ,可以从我的文章列表中扑ֈ?/P>
sort()胜过qsort()的主要原因是Q比较操作在内联QinlinesQ上做得更好?/P>
什么是函数对象Qfunction objectQ?
思义Q就是在某种方式上表现得象一个函数的对象。典型地Q它是指一个类的实例,q个cd义了应用操作Woperator()?/P>
函数对象是比函数更加通用的概念,因ؓ函数对象可以定义跨越多次调用的可持久的部分(cM静态局部变量)Q同时又能够从对象的外面q行初始化和查(和静态局部变量不同)。例如:
class Sum {
int val;
public:
Sum(int i) :val(i) { }
operator int() const { return val; } // 取得?/P>
int operator()(int i) { return val+=i; } // 应用
};
void f(vector v)
{
Sum s = 0; // initial value 0
s = for_each(v.begin(), v.end(), s); // 求所有元素的?/P>
cout << "the sum is " << s << "\n";
//或者甚臻I
cout << "the sum is " << for_each(v.begin(), v.end(), Sum(0)) << "\n";
}
注意一个拥有应用操作符的函数对象可以被完美地内联化QinlineQ,因ؓ它没有涉及到M指针Q后者可能导致拒l优化。与之Ş成对比的是,现有的优化器几乎不能Q或者完全不能?Q将一个通过函数指针的调用内联化?/P>
在标准库中,函数对象被广泛地使用以获得弹性?/P>
我应该如何对付内存泄漏?
写出那些不会DM内存泄漏的代码。很明显Q当你的代码中到处充满了new 操作、delete操作和指针运的话,你将会在某个地方搞晕了头Q导致内存泄漏,指针引用错误Q以及诸如此cȝ问题。这和你如何心地对待内存分配工作其实完全没有关p:代码的复杂性最lL会超q你能够付出的时间和努力。于是随后生了一些成功的技巧,它们依赖于将内存分配QallocationsQ与重新分配QdeallocationQ工作隐藏在易于理的类型之后。标准容器(standard containersQ是一个优U的例子。它们不是通过你而是自己为元素管理内存,从而避免了产生p糕的结果。想象一下,没有string和vector的帮助,写出q个Q?/P>
#include<vector>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
int main() // small program messing around with strings
{
cout << "enter some whitespace-separated words:\n";
vector<string> v;
string s;
while (cin>>s) v.push_back(s);
sort(v.begin(),v.end());
string cat;
typedef vector<string>::const_iterator Iter;
for (Iter p = v.begin(); p!=v.end(); ++p) cat += *p+"+";
cout << cat << '\n';
}
你有多少Z在第一ơ就得到正确的结果?你又怎么知道你没有导致内存泄漏呢Q?/P>
注意Q没有出现显式的内存理Q宏Q造型Q溢出检查,昑ּ的长度限Ӟ以及指针。通过使用函数对象和标准算法(standard algorithmQ,我可以避免用指针——例如用P代子QiteratorQ,不过对于一个这么小的程序来说有点小题大作了?/P>
q些技巧ƈ不完,要系l化C用它们也q不L那么Ҏ。但是,应用它们产生了惊人的差异Q而且通过减少昑ּ的内存分配与重新分配的次敎ͼ你甚臛_以余下的例子更加容易被跟踪。早?981q_我就指出Q通过我必须昑ּ地跟t的对象的数量从几万个减到几打Qؓ了ɽE序正确q行而付出的努力从可怕的苦工Q变成了应付一些可理的对象,甚至更加单了?/P>
如果你的E序q没有包含将昑ּ内存理减少到最限度的库,那么要让你程序完成和正确q行的话Q最快的途径也许是先徏立一个这L库?/P>
模板和标准库实现了容器、资源句柄以及诸如此cȝ东西Q更早的使用甚至在多q以前。异常的使用使之更加完善?/P>
如果你实在不能将内存分配/重新分配的操作隐藏到你需要的对象中时Q你可以使用资源句柄Qresource handleQ,以将内存泄漏的可能性降x低。这里有个例子:我需要通过一个函敎ͼ在空闲内存中建立一个对象ƈq回它。这时候可能忘记释放这个对象。毕竟,我们不能_仅仅x当这个指针要被释攄时候,谁将负责d。用资源句柄,q里用了标准库中的auto_ptrQ需要ؓ之负责的地方变得明确了?/P>
#include<memory>
#include<iostream>
using namespace std;
struct S {
S() { cout << "make an S\n"; }
~S() { cout << "destroy an S\n"; }
S(const S&) { cout << "copy initialize an S\n"; }
S& operator=(const S&) { cout << "copy assign an S\n"; }
};
S* f()
{
return new S; // 谁该负责释放q个SQ?/P>
};
auto_ptr<S> g()
{
return auto_ptr<S>(new S); // 昑ּ传递负责释放这个S
}
int main()
{
cout << "start main\n";
S* p = f();
cout << "after f() before g()\n";
// S* q = g(); // 被~译器捕?/P>
auto_ptr<S> q = g();
cout << "exit main\n";
// *p产生了内存泄?/P>
// *q被自动释?/P>
}
在更一般的意义上考虑资源Q而不仅仅是内存?/P>
如果在你的环境中不能pȝ地应用这些技巧(例如Q你必须使用别的地方的代码,或者你的程序的另一部分直是原始人类Q译注:原文是NeanderthalsQ尼安d特hQ旧矛_时代q泛分布在欧z的猿hQ写的,如此{等Q,那么注意使用一个内存泄漏检器作ؓ开发过E的一部分Q或者插入一个垃圾收集器Qgarbage collectorQ?/P>
我ؓ什么在捕获一个异怹后就不能l箋Q?/P>
换句话说QC++Z么不提供一U简单的方式Q让E序能够回到异常抛出点之后,ql执行?
主要的原因是Q如果从异常处理之后l箋Q那么无法预知掷出点之后的代码如何对待异常处理,是否仅仅l箋执行Q就象什么也没有发生一栗异常处理者无法知道,在l之前,有关的上下文环境QcontextQ是否是“正”的。要让这L代码正确执行Q抛出异常的~写者与捕获异常的编写者必d彼此的代码与上下文环境都非常熟悉才行。这样会产生非常复杂的依赖性,因此无论在什么情况下Q都会导致一pd严重的维护问题?/P>
当我设计C++的异常处理机制时Q我曄认真地考虑q允许这Ul的可能性,而且在标准化的过E中Q这个问题被非常详细地讨。请参见《C++语言的设计和演变》中的异常处理章节?/P>
在一ơ新ȝ的讨ZQ我曄以一U稍微不同的方式回答q这个问题?/P>
Z么C++中没有相当于realloc()的函敎ͼ
如果你需要,你当然可以用realloc()。但是,realloc()仅仅保证能工作于q样的数l之上:它们被malloc()Q或者类似的函数Q分配,包含一些没有用户定义的复制构造函敎ͼcopy constructorsQ的对象。而且Q要CQ与通常的期望相反,realloc()有时也必d制它的参数数l?/P>
在C++中,处理内存重新分配的更好的Ҏ是,使用标准库中的容器,例如vectorQƈ让它自我增长?/P>
如何使用异常Q?/P>
参见《C++E序设计语言》第4章,W?.3节,以及附录E。这个附录针对的是如何在要求苛刻的程序中写出异常安全的代码的技巧,而不是针对初学者的。一个关键的技术是“资源获得即初始化”(resource acquisiton is initializationQ,它用一些有析构函数的类Q来实现强制的资源管理?/P>
怎样从输入中d一个字W串Q?/P>
你可以用q种方式d一个单独的以空格结束的词:
#include<iostream>
#include<string>
using namespace std;
int main()
{
cout << "Please enter a word:\n";
string s;
cin>>s;
cout << "You entered " << s << '\n';
}
注意Q这里没有显式的内存理Q也没有可能D溢出的固定大的~冲区?/P>
如果你确实想得到一行而不是一个单独的词,可以q样做:
#include<iostream>
#include<string>
using namespace std;
int main()
{
cout << "Please enter a line:\n";
string s;
getline(cin,s);
cout << "You entered " << s << '\n';
}
在《C++E序设计语言》(可在U获得)的第3章,可以扑ֈ一个对诸如字符串与这L标准库工L介。对于用C与C++q行单输入输出的详细比较Q参见我的文章《将标准C++作ؓ一U新的语a来学习?Learning Standard C++ as a New Language)Q你可以在本作列?my publications list)中下载到它?/P>
Z么C++不提供“finally”的构造?
因ؓC++提供了另外一U方法,它几乎L更好的:“资源获得即初始化”(resource acquisiton is initializationQ技术。基本的思\是,通过一个局部对象来表现资源Q于是局部对象的析构函数会释放资源。这PE序员就不会忘记释放资源了。D例来_
class File_handle {
FILE* p;
public:
File_handle(const char* n, const char* a)
{ p = fopen(n,a); if (p==0) throw Open_error(errno); }
File_handle(FILE* pp)
{ p = pp; if (p==0) throw Open_error(errno); }
~File_handle() { fclose(p); }
operator FILE*() { return p; }
// ...
};
void f(const char* fn)
{
File_handle f(fn,"rw"); //打开fnq行d
// 通过f使用文g
}
在一个系l中Q需要ؓ每一个资源都使用一个“资源句柄”类。无论如何,我们不需要ؓ每一个资源获得都写出“finally”语句。在实时pȝ中,资源获得要远q多于资源的U类Q因此和使用“finally”构造相比,“资源获得即初始化”技术会产生得多的代码?/P>
什么是自动指针Qauto_ptrQ,Z么没有自动数l(auto_arrayQ?
auto_ptr是一个非常简单的句柄cȝ例子Q在<memory>中定义,通过“资源获得即初始化”技术支持异常安全。auto_ptr保存着一个指针,能够象指针一栯使用Qƈ在生存期l束旉放指向的对象。D例:
#include<memory>
using namespace std;
struct X {
int m;
// ..
};
void f()
{
auto_ptr<X> p(new X);
X* q = new X;
p->m++; // 象一个指针一样用p
q->m++;
// ...
delete q;
}
如果?..部分抛出了一个异常,p持有的对象将被auto_ptr的析构函数正地释放Q而q指向的X对象则生了内存泄漏。更多的l节Q参见《C++E序设计语言?4.4.2节?/P>
auto_ptr是一个非常简单的cR特别地Q它不是一个引用计敎ͼreference countedQ的指针。如果你一个auto_ptr赋值给另一个,那么被赋值的auto_ptr持有指针,而原来的auto_ptr持?。D例:
#include<memory>
#include<iostream>
using namespace std;
struct X {
int m;
// ..
};
int main()
{
auto_ptr<X> p(new X);
auto_ptr<X> q(p);
cout << "p " << p.get() << " q " << q.get() << "\n";
}
会打印Z个指?的指针和一个指向非0的指针。例如:
p 0x0 q 0x378d0
auto_ptr::get()q回那个辅助的指针?/P>
q种“{UZ语义不同于通常的“复制”语义,q是令h惊讶的。特别地Q永q不要用auto_ptr作ؓ一个标准容器的成员。标准容器需要通常的“复制”语义。例如:
std::vector<auto_ptr<X> >v; // 错误
auto_ptr只持有指向一个单独元素的指针Q而不是指向一个数l的指针Q?/P>
void f(int n)
{
auto_ptr<X> p(new X[n]); //错误
// ...
}
q是错误的,因ؓ析构函数会调用delete而不是delete[]来释放指针,q样׃会调用余下的n-1个X的析构函数?/P>
那么我们需要一个auto_array来持有数l吗Q不。没有auto_array。原因是Ҏ没有q种需要。更好的解决Ҏ是用vectorQ?/P>
void f(int n)
{
vector<X> v(n);
// ...
}
?..部分发生异常Ӟv的析构函C被正地调用?/P>
可以混合使用C风格与C++风格的内存分z与重新分配吗?
在这U意义上是可以的Q你可以在同一个程序中使用malloc()和new?/P>
在这U意义上是不行的Q你不能使用malloc()来徏立一个对象,又通过delete来释攑֮。你也不能用new建立一个新的对象,然后通过free()来释攑֮Q或者通过realloc()在数l中再徏立一个新的?/P>
C++中的new和delete操作可以保证正确的构造和析构Q构造函数和析构函数在需要它们的时候被调用。C风格的函数alloc(), calloc(), free(), 和realloc()却不能保证这一炏V此外,用new和delete来获得和释放的原始内存,q不一定能保证与malloc()和free()兼容。如果这U合的风格在你的系l中能够q用Q只能说是你走运——暂时的?/P>
如果你觉得需要用realloc()——或者要做更多——考虑使用标准库中的vector。例如:
// 从输入中词dC个字W串vector?/P>
vector<string> words;
string s;
while (cin>>s && s!=".") words.push_back(s);
vector会视需要自动增ѝ?/P>
更多的例子与讨论Q参见我的文章《将标准C++作ؓ一U新的语a来学习?Learning Standard C++ as a New Language)Q你可以在本作列?my publications list)中下载到它?/P>
我ؓ什么必M用一个造型来{?voidQ?/P>
在C语言中,你可以隐式地?void转换?T。这是不安全的。考虑一下:
#include<stdio.h>
int main()
{
char i = 0;
char j = 0;
char* p = &i;
void* q = p;
int* pp = q; /* 不安全的Q在C中可以,C++不行 */
printf("%d %d\n",i,j);
*pp = -1; /* 覆盖了从i开始的内存 */
printf("%d %d\n",i,j);
}
使用一个ƈ不指向Tcd的T*是一场灾难。因此,在C++中,如果从一个void*得到一个T*Q你必须q行昑ּ转换。D例来_要得C列程序的q个令h别扭的效果,你可以这样写Q?/P>
int* pp = (int*)q;
或者用一个新的类型造型Q以使这U没有检查的cd转换操作变得更加清晰Q?/P>
int* pp = static_cast<int*>(q);
造型被最好地避免了?/P>
在C语言中,q种不安全的转换最常见的应用之一Q是malloc()的结果赋予一个合适的指针。例如:
int* p = malloc(sizeof(int));
在C++中,使用cd安全的new操作W:
int* p = new int;
附带圎ͼnew操作W还提供了胜qmalloc()的新Ҏ:
new不会偶然分配错误的内存数量;
new会隐式地查内存耗尽情况Q而且
new提供了初始化?/P>
举例Q?/P>
typedef std::complex<double> cmplx;
/* C风格: */
cmplx* p = (cmplx*)malloc(sizeof(int)); /* 错误Q类型不正确 */
/* 忘记试p==0 */
if (*p == 7) { /* ... */ } /* p糕Q忘C初始?p */
// C++风格:
cmplx* q = new cmplx(1,2); // 如果内存耗尽Q将抛出一个bad_alloc异常
if (*q == 7) { /* ... */ }
我如何定义一个类内部Qin-classQ的帔RQ?/P>
如果你需要一个通过帔R表达式来定义的常量,例如数组的范_你有两种选择Q?/P>
class X {
static const int c1 = 7;
enum { c2 = 19 };
char v1[c1];
char v2[c2];
// ...
};
乍看hQc1的声明要更加清晰Q但是要注意的是Q用这U类内部的初始化语法的时候,帔R必须是被一个常量表辑ּ初始化的整型或枚丄型,而且必须是static和const形式。这是很严重的限Ӟ
class Y {
const int c3 = 7; // 错误Q不是static
static int c4 = 7; // 错误Q不是const
static const float c5 = 7; // 错误Q不是整?/P>
};
我們使用枚D的方式,因ؓ它更加方便,而且不会׃我去使用不规范的cd初始化语法?/P>
那么Qؓ什么会存在q种不方便的限制呢?一般来_cd一个头文g中被声明Q而头文g被包含到许多互相调用的单元去。但是,Z避免复杂的编译器规则QC++要求每一个对象只有一个单独的定义。如果C++允许在类内部定义一个和对象一样占据内存的实体的话Q这U规则就被破坏了。对于C++在这个设计上的权衡,请参见《C++语言的设计和演变》?/P>
如果你不需要用帔R表达式来初始化它Q那么可以获得更大的Ҏ:
class Z {
static char* p; // 在定义中初始?/P>
const int i; // 在构造函C初始?/P>
public:
Z(int ii) :i(ii) { }
};
char* Z::p = "hello, there";
你可以获取一个static成员的地址Q当且仅当它有一个类外部的定义的时候:
class AE {
// ...
public:
static const int c6 = 7;
static const int c7 = 31;
};
const int AE::c7; // 定义
int f()
{
const int* p1 = &AE::c6; // 错误Qc6没有左?/P>
const int* p2 = &AE::c7; // ok
// ...
}
Z么delete不会操作数|?Q?/P>
考虑一下:
delete p;
// ...
delete p;
如果?..部分没有涉及到p的话Q那么第二个“delete p;”将是一个严重的错误Q因为C++的实玎ͼ译注Q原文ؓa C++ implementationQ当指VC++q样的实CC++标准的具体工P不能有效地防止这一点(除非通过非正式的预防手段Q。既然delete 0从定义上来说是无害的Q那么一个简单的解决Ҏ是Q不在什么地Ҏ行了“delete p;”,随后都执行“p=0;”。但是,C++q不能保证这一炏V?/P>
一个原因是Qdelete的操作数q不需要一个左|lvalueQ。考虑一下:
delete p+1;
delete f(x);
在这里,被执行的deleteq没有拥有一个可以被赋予0的指针。这些例子可能很见Q但它们的确指出了,Z么保证“Q何指向被删除对象的指针都?”是不可能的。绕q这条“规则”的一个简单的Ҏ是,有两个指针指向同一个对象:
T* p = new T;
T* q = p;
delete p;
delete q; // p糕Q?/P>
C++昑ּ地允许delete操作操作数左值置0Q而且我曾l希望C++的实现能够做到这一点,但这U思想看来q没有在C++的实C变得行?/P>
如果你认为指针置0很重要,考虑使用一个销毁的函数Q?/P>
template<class T> inline void destroy(T*& p) { delete p; p = 0; }
考虑一下,q也是ؓ什么需要依靠标准库的容器、句柄等{,来将对new和delete的显式调用降到最低限度的另一个原因?/P>
注意Q通过引用来传递指针(以允许指针被|?Q有一个额外的好处Q能防止destroy()在右gQrvalueQ被调用Q?/P>
int* f();
int* p;
// ...
destroy(f()); // 错误Q应该用一个非帔RQnon-constQ的引用传递右?/P>
destroy(p+1); // 错误Q应该用一个非帔RQnon-constQ的引用传递右?/P>
我能够写“void main()”吗Q?/P>
q种定义Q?/P>
void main() { /* ... */ }
在C++中从未被允许Q在C语言中也是一栗参见ISO C++标准3.6.1[2]或者ISO C标准5.1.2.2.1。规范的实现接受q种方式Q?/P>
int main() { /* ... */ }
?/P>
int main(int argc, char* argv[]) { /* ... */ }
一个规范的实现可能提供许多版本的main()Q但它们都必返回intcd。main()q回的int|是程序返回一个值给调用它的pȝ的方式。在那些不具备这U方式的pȝ中,q回D忽略了,但这q不低쀜void main()”在C++或C中成为合法的。即使你的编译器接受了“void main()”,也要避免使用它,否则你将冒着被C和C++E序员视为无知的风险?/P>
在C++中,main()q不需要包含显式的return语句。在q种情况下,q回值是0Q表C执行成功。例如:
#include<iostream>
int main()
{
std::cout << "This program returns the integer value 0\n";
}
注意Q无论是ISO C++q是C99Q都不允许在声明中漏掉类型。那是_与C89和ARM C++形成对照Q当声明中缺类型时Qƈ不会保证是“int”。于是:
#include<iostream>
main() { /* ... */ }
是错误的Q因为缺main()的返回类型?/P>
Z么我不能重蝲点符P::QsizeofQ等{?
大多数的q算W能够被E序员重载。例外的是:
. (点符? :: ?: sizeof
q没有什么根本的原因要禁止重?:。仅仅是因ؓQ我没有发现有哪U特D的情况需要重载一个三元运符。注意一个重载了 表达?Q表辑ּ2Q表辑ּ3 的函敎ͼ不能够保证表辑ּ2Q表辑ּ3中只有一个会被执行?/P>
Sizeof不能够被重蝲是因为内建的操作Qbuilt-in operationsQ,诸如对一个指向数l的指针q行增量操作Q必M靠它。考虑一下:
X a[10];
X* p = &a[3];
X* q = &a[3];
p++; // p指向a[4]
// 那么p的整型值必Lq的整型值大Z个sizeof(X)
所以,sizeof(X)不能q序员来赋予一个不同的新意义,以免q反基本的语法?/P>
在N::m中,无论Nq是m都不是值的表达式;N和m是编译器知道的名字,::执行一个(~译期的Q范围解析,而不是表辑ּ求倹{你可以惌一下,允许重蝲x::y的话Qx可能是一个对象而不是一个名字空_namespaceQ或者一个类Q这样就会导致——与原来的表现相反——生新的语法(允许 表达?::表达?Q。很明显Q这U复杂性不会带来Q何好处?/P>
理论上来_.Q点q算W)可以通过使用?>一L技术来q行重蝲。但是,q样做会D一个问题,那就是无法确定操作的是重载了.的对象呢Q还是通过.引用的一个对象。例如:
class Y {
public:
void f();
// ...
};
class X { // 假设你能重蝲.
Y* p;
Y& operator.() { return *p; }
void f();
// ...
};
void g(X& x)
{
x.f(); // X::fq是Y::fq是错误Q?/P>
}
q个问题能够用几U不同的Ҏ解决。在标准化的时候,哪种Ҏ最好还没有定论。更多的l节Q请参见《C++语言的设计和演变》?/P>
怎样一个整型D{换ؓ一个字W串Q?/P>
最单的Ҏ是用一个字W串(stringstreamQ:
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
string itos(int i) // int转换成string
{
stringstream s;
s << i;
return s.str();
}
int main()
{
int i = 127;
string ss = itos(i);
const char* p = ss.c_str();
cout << ss << " " << p << "\n";
}
自然圎ͼq种技术能够将M使用<<输出的类型{换ؓ字符丌Ӏ对于字W串的更多说明Q参见《C++E序设计语言?1.5.3节?/P>
“int* p”正还是“int *p”正?
二者都是正的Q因Z者在C和C++中都是有效的Q而且意义完全一栗就语言的定义与相关的编译器来说Q我们还可以说“int*p”或者“int * p”?/P>
在“int* p”和“int *p”之间的选择与正或错误无关Q而只关乎风格与侧重点。C侧重表达式;对声明往往比可能带来的问题考虑得更多。另一斚wQC++则非帔R视类型?/P>
一个“典型的CE序员”写成“int *p”,q且解释说?p表示一个什么样的int”以语法Q而且可能指出CQ与C++Q的语法来证明这U风格的正确性。是的,在语法上*被绑定到名字p上?/P>
一个“典型的C++E序员”写成“int* p”,q且解释说“p是一个指向int的指针类型”以cd。是的,p是一个指向int的指针类型。我明确地們于这U侧重方向,而且认ؓ对于学好更多的高UC++q是很重要的?/P>
严重的乱(仅仅Q发生在当h们试囑֜一条声明中声明几个指针的时候:
int* p, p1; // 也许是错的:p1不是一个int*
?攑ֈ名字q一边,看来也不能有效地减少q种错误Q?/P>
int *p, p1; // 也许是错的?
为每一个名字写一条声明最大程度地解决了问题——特别是当我们初始化变量的时候。h们几乎不会这样写Q?/P>
int* p = &i;
int p1 = p; // 错误Qint用一个int*初始化了
如果他们真的q么q了Q编译器也会指出?/P>
每当事情可以有两U方法完成,有h׃qh。每当事情仅仅是一个风格的问题Q争论就会没完没了。ؓ每一个指针写一条声明,而且永远都要初始化变量,q样Q׃源就消失了。更多的关于C的声明语法的讨论Q参见《C++语言的设计和演变》?/P>
对于我的代码Q哪一U布局风格Qlayout styleQ是最好的Q?/P>
q种风格问题属于个h的爱好。h们往往对布局风格的问题持有强烈的意见Q不q,也许一贯性比某种特定的风格更加重要。象大多Ch一P我花了很长的旉Q来为我的偏好作Z个固定的l论?/P>
我个Z用通常UCؓ“K&R”的风格。当使用C语言没有的构造函数时Q需要增加新的习惯,q样变成了一U有时被UCؓ“Stroustrup”的风格。例如:
class C : public B {
public:
// ...
};
void f(int* p, int max)
{
if (p) {
// ...
}
for (int i = 0; i<max; ++i) {
// ...
}
}
比大多数布局风格更好Q这U风g留了垂直的空|我喜Ƣ尽可能地在合理的情况下寚w屏幕。对函数开头的大括弧的攄Q有助于我第一眼就分别出类的定义和函数的定义?/P>
~进是非帔R要的?/P>
设计问题Q诸如作Z要接口的抽象基类的用,使用模板以表现有Ҏ的cd安全的抽象,以及正确C用异总表现错误Q比布局风格的选择要重要得多?/P>
我应该将“const”放在类型之前还是之后?
我把它放在前面,但那仅仅是个人爱好问题。“const T”和“T const”L都被允许的,而且是等效的。例如:
const int a = 1; // ok
int const b = 2; // also ok
我猜想第一U版本可能会让少敎ͼ更加固守语法规范Q的E序员感到迷惑?/P>
Z么?当我发明“const”(最初的名称叫做“readonly”,q且有一个对应的“writeonly”)的时候,我就允许它出现在cd之前或之后,因ؓq样做不会带来Q何不明确。标准之前的C和C++规定了很的Q如果有的话Q特定的序规范?/P>
我不记得当时有过M有关序问题的深入思考或讨论。那Ӟ早期的一些用者——特别是我——仅仅喜Ƣ这U样子:
const int c = 10;
看v来比q种更好Q?/P>
int const c = 10;
也许我也受了q种影响Q在我最早的一些用“readonly”的例子?/P>
readonly int c = 10;
比这个更h可读性:
int readonly c = 10;
我创造的那些最早的使用“const”的QC或C++Q代码,看来已经在全球范围内取代了“readonly”?/P>
我记得这个语法的选择在几个h——例如Dennis Ritchie——当中讨Q但我不记得当时我們于哪U语a了?/P>
注意在固定指针(const pointerQ中Q“const”永q出现在?”之后。例如:
int *const p1 = q; // 指向int变量的固定指?/P>
int const* p2 = q; //指向int帔R的指?/P>
const int* p3 = q; //指向int帔R的指?/P>
使用宏有什么问题?
宏不遵@C++中关于范围和cd的规则。这l常D一些微妙的或不那么微妙的问题。因此,C++提供更适合其他的C++Q译注:原文为the rest of C++Q当指C++除了兼容C以外的部分)的替代品Q例如内联函数、模板与名字I间?/P>
考虑一下:
#include "someheader.h"
struct S {
int alpha;
int beta;
};
如果某hQ不明智圎ͼ地写了一个叫“alpha”或“beta”的宏,那么它将不会被编译,或者被错误地编译,产生不可预知的结果。例如,“someheader.h”可能包含:
#define alpha 'a'
#define beta b[2]
宏Q而且仅仅是宏Q全部大写的习惯Q会有所帮助Q但是对于宏q没有语a层次上的保护机制。例如,虽然成员的名字包含在l构体的内部Q但q无于事:在编译器能够正确地L别这一点之前,宏已l将E序作ؓ一个字W流q行了处理。顺便说一句,q是C和C++E序开发环境和工具能够被简化的一个主要原因:Z~译器看到的是不同的东西?/P>
不幸的是Q你不能假设别的E序员L能够避免q种你认为“相当白痴”的事情。例如,最q有人报告我Q他们遇C一个包含goto的宏。我也见q这U情况,而且听到q一些——在很脆q时候——看h实有理的意见。例如:
#define prefix get_ready(); int ret__
#define Return(i) ret__=i; do_something(); goto exit
#define suffix exit: cleanup(); return ret__
void f()
{
prefix;
// ...
Return(10);
// ...
Return(x++);
//...
suffix;
}
作ؓ一个维护的E序员,׃产生q种印象Q将宏“隐藏”到一个头文g中——这q不|见——得这U“魔法”更难以被L别?/P>
一个常见的微妙问题是,一个函数风格的宏ƈ不遵守函数参C递的规则。例如:
#define square(x) (x*x)
void f(double d, int i)
{
square(d); // ?/P>
square(i++); // p糕Q这表示 (i++*i++)
square(d+1); //p糕Q这表示(d+1*d+1); 也就?(d+d+1)
// ...
}
“d+1”的问题Q可以通过在“调用”时或宏定义时添加一对圆括号来解冻I
#define square(x) ((x)*(x)) /*q样更好 */
但是Q?i++被执行了两次Q可能ƈ不是有意要这么做Q的问题仍然存在?/P>
是的Q我实知道有些Ҏ的宏q不会导致C/C++预处理宏q样的问题。但是,我无心去发展C++中的宏。作为替代,我推荐用C++语言中合适的工具Q例如内联函敎ͼ模板Q构造函敎ͼ用来初始化)Q析构函敎ͼ用来清除Q,异常Q用来退Z下文环境Q,{等?BR>
灰狐论坛Google朋友?nbsp; |
与Google一起分享互联网, 现在加?A target=_blank>Google用户l?已有470多名会员)
![]() ![]() 让我们一起交和分nGoogle AdWords, Google AdSense成功的广告模?/A>
| ![]() OpenSolaris,Solaris 操作pȝ ![]() 已有150多位Debian GNU/Linux爱好? 栏目D![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |