一些无关紧要的好奇

来源:互联网 发布:网络语狗子是啥意思 编辑:程序博客网 时间:2024/04/29 16:04

此文 来自BS个人主页 http://www.stroustrup.com/bsfaqcn.html

1、普通类有什么用处?

类可以帮助你组织代码和分析程序。你也可以大致理解为,类可以让你避免反犯错,而如果你犯错了,类使你易于寻找错误。也就是说,类大大提高了代码的可维护性。

类是思想和观念的代码形式。类的对象数是思想的具体实例在代码上的体现。没有类,读代码的人只能猜测数据和函数的关系——类能清楚的表明这种关系,并且能被编译器理解。有了类,程序的高层结构就更能反映在代码上,而不单单是在注释中。

一个设计良好的类能为用户提供简洁易用的接口,并将其内部结构隐藏起来,用户根本不必了解其内部结构。如果内部结构不应该被隐藏——例如,因为用户需要随意改变类中的任何数据成员——你可以把这种类认为是“普通的老式的数据结构(data structure)”;例如:

struct Pair {Pair(const string& n, const string& v) : name(n), value(v) { }string name, value;};
代码巨丑格式

注意,数据结构也可以使用辅助函数,例如构造函数。

设计类时,思考类有哪些方面在任何时刻对其每个对象都是通用的,这会很有用。这种通用的属性被称之为不变要素(invariant)。例如,vector的不变要素是,其内部有一个指针,指向一系列元素,这些元素的数目保存于一个整型变量。每个构造函数都有责任去构建类的不变要素,这样成员函数才能依赖这些不变要。成员函数退出时必须保持这些不变要素的有效性。这种思维方式对于管理资源的类来说更是特别有益,如管理锁(locks)、sockets和文件的类。例如,一个处理文件的类的不变要素是,它有一个指针,指向一个打开的文件。该类的构造函数负责打开文件,而其析构函数负责释放构造函数获取的资源。例如,该类的析构函数负责关闭构造函数打开的文件:

     class File_handle {                         public:                                 File_handle(const char* n, const char* rw)                                         { f = fopen(n,rw); if (f==0) throw Open_failure(n); }                                 ~File_handle() { fclose(f); } // destructor                                 // ...                         private:                                 FILE* f;                         };

如果你未曾使用过类,你会觉得这番说明有些地方相当费解并且会低估类的用处。要寻找例子,请阅读一些经典书籍。


2、面向对象编程的伟大之处?

面向对象编程起源于Simula(约40年前!),它依赖于封装、继承以及多态。就C++而言,OOP的意思是利用类层级(Class hierarchies)及虚函数进行编程,从而可以通过精制的接口操作各种类型的对象,并且程序本身也可以通过派生进行功能增量扩展。
如果只考虑普通类组织代码的作用是不够的,把多个类组织成类层级是为了表达类之间的层次关系并且利用这些关系简化代码。
举一个多态的例子。例如,你可能要有两个(或者更多)设备驱动共用一个公共接口:

class Driver1 :public Driver{public:Driver1(Register);int read(char*, int);bool reset();Status check();};class Driver2 : public Driver { // 另一个驱动 public:Driver2(Register);int read(char*, int n);bool reset();Status check();// 实现细节 };
注意,这些驱动含有数据成员,可以通过它们创建对象。它们实现了Driver中定义的接口。不难想象,可以通过这种方式使用某个驱动:
 void f(Driver& d) // 使用驱动                 {                         Status old_status = d.check();                         // ...                         d.reset();                         char buf[512];                         int x = d.read(buf,512);                         // ...                 }

这里的重点是,f() 不需要知道它使用的是何种类型的驱动;它只需要知道有个Driver传递给了它;也就是说,有一个接口传递给了它。

  void g()                 {                         Driver1 d1(Register(0xf00)); // create a Driver1 for device                                                                     // with device register at address 0xf00                        Driver2 d2(Register(0xa00)); // create a Driver2 for device                                                                      // with device register at address 0xa00                         // ...                         int dev;                         cin >> dev;                        if (dev==1)                                 f(d1); // use d1                         else                                 f(d2); // use d2                         // ...                 }
注意,当 f()使用某个驱动时,与该驱动相对应的操作会在运行时被隐式选择。例如,当 f()得到d1 时,d.read() 使用的是 Driver1::read();而当 f() 得到 d2 时,d.read() 使用的则是 Driver2::read()。这被称为运行时调度或者动态调度。本例,f() 无法得知调用的是何种设备,因为那是根据输入选择的。

请注意,OOP 并非万能药。不要简单地把“OOP”等同于“好”。如果你的问题的基本要素中没有与生俱来的层级关系,那么类层级和虚函数对你的代码不会有任何帮助。OOP 的优势在于类层级可以有效地表达很多问题;OOP 的主要弱点在于太多人设法强行用层级模式解决问题。并非所有问题都应该面向对象。也可以考虑使用普通类(plain class)、泛型编程和独立的函数(就像数学、C,以及 Fortran 中那样)作为解决问题的方案。

3、为什么C++允许不安全的代码?

也就是说,为什么C++ 支持的一些操作能够违反静态(编译时)类型安全机制?
  • 为了直接访问硬件(例如,把整数当作指向设备寄存器的指针)
  • 为了获取最佳的运行时效率和空间效率(例如,不检测访问数组元素的操作(是否越界)),不检测访问对象的指针(是否有效)
  • 为了和 C 兼容(从此处跳到下一段落需要按两下enter)
所以,当你并不需要以上三种特性时,最好避免有如瘟疫搬的不安全代码:
  • 不要用类型转换(cast)
  • 不要将数组用作接口(如有必要,请将它们隐藏于高效函数和类的内部),而使用合适的string、vector等编写其余代码),
  • 避免void* (如果你真的需要它们,请将它们限制于低级(low-level)函数和数据结构的内部,并且为用户提供类型安全的接口(通常是模板),
  • 避免联合体
  • 如果你对指针的有效性存在有任何的怀疑,请用智能指针,
  • 不要“赤裸裸”地使用 new 和 delete (使用容器,资源句柄),
  • 不要使用.....风格的函数(如printf)。
几乎所有 C++ 代码都能遵循这些简单的原则。请不要因为你编写 C 代码或者 C风格的 C++ 代码不能遵循这些规则而认为 C++ 不安全。

4、何谓泛型编程?其伟大之处何在?

        泛型编程(Generic Programming, GP)是一种基于参数化(parameterization)的编程技巧:可以使用类型参数化另一种类型(例如,vector 的元素类型就是通过参数确定的);算法也可以参数化另一种算法(例如,使用比较函数参数化排序函数)。GP 的目的是将有用的算法或者数据结构尽可能地一般化,并使其最优化。例如,如果没有 GP,你必须为整型 vector 专门写一个类;然后,为了寻找其中的最大值,你又得为它写一个专门的函数。但是,使用 GP 可以把一切变得更美好:只需要写一个类,即可拥有任何类型的 vector;只需要写一个函数,即可寻找任何类型 vector 中的最大值。例如:

                vector<string>::iterator p = find(vs.begin(), vs.end(), "Grail");

                vector<int>::iterator q = find(vi.begin(), vi.end(), 42);

这些例子出自标准模板库(ISO C++ 标准库中容器和算法那部分)。TC++PL 中,漫游标准库那章对标准模板库(Standard Template Library, STL)作了简要的介绍。

        GP 在某些方面比 OOP 要灵活得多。特别是,它不依赖于层级。例如,int 和 string 之间没有任何层级关系。总的来说,GP 的结构化程度更甚于 OOP。事实上,GP 常被称为“参数多态(parametric polymorphism)”;而 OOP 常被称为“ad hoc 多态”。就 C++ 而言,GP 于编译时就解析了所有名称;它不需要任何动态(运行时)调度。因此,GP 在对运行时效率要求很高的领域占据了主导地位。

        请注意,GP 并非万灵丹。很多时候,程序并不需要参数化多态,而需要运行时调度(OOP)。

6、C 是 C++ 的子集吗?

严格意义上来说不是,C 与 C++有些不兼容的地方。http://www.stroustrup.com/bsfaqcn.html#C-is-subset