C++编程艺术
漫游世界——C++概述
19世纪80年代初期美国AT&贝尔实验室Bjarne Stroustrup发明并实现了C++,它的面向对象程序设计思想无不使全世界的程序员为之惊呼。由C语言演化而来的C++是一种静态数据类型检查的,支持过程化程序设计、数据抽象、面向对象程序设计等多种程序设计风格的语言。
由于C++面向对象的思想影响如此重大而深远,导致了纯对象语言Java和C#的出现并迅速成为当今主流编程语言。C++全面兼容C,同时提供了比C更严格、更安全的语法。除了具有运行时的高性能特性外,随着软件代码量和复杂程度的急剧增加,比起Java和C#,C++软件工程性的优势越来越来明显。
C++应用广泛。Windows,Linux和Unix是用C语言写的,上层的高级特性是用C++些的;高安全的中型商用机IBM/400系统是用C++写的;Google和baidu搜索引擎是用C++写的;Firefox浏览器是用C++写的等。
简单就是美——正确、简单和清晰第一
1.简单的设计和清晰代码的价值怎么强调都不过分,代码的维护者将因为你编写的代码容易理解而感谢你 而且这个维护者往往就是未来的你。
2.编写程序必须以人为本,计算机第二。
3.软件简单为美的哲理:正确优于速度,简单优于复杂,清晰优于技巧,安全优于不安全。
4.例子一,
if(a<=b)
max=b;
else
max=a;
把上面的代码写成下面的形式会更易读一些:max=(a<=b)?b:a;
例子二,为了避免意外地错误使用变量,在最小的作用域里引进变量是一个很好的想法。一个最优雅的应用就是在条件中声明变量。如下所示:
if(double d=fn(void))
{
global/=d;
}
传递火炬——理解参数传递
1.当一个函数被调用时,将安排好其形式参数所需要的存储,各个形式参数将用对应的实际参数进行初始化。参数传递的语义与初始化的语义完全相同。
2.对于引用参数传递和指针参数传递,若不想修改由这个参数所指的对象,应将相关参数声明为const 以告知读者。例如:
int strlen(const char*);//求字符串的长度
Int strcmp(const char*,const char*);//比较字符串
3.请注意,参数传递的语义不同于赋值的语义。
4.文字量、常量和需要转换的参数都可以传递给const&参数,但不能传递给非const的引用参数。允许对const T&参数进行转换。例如:
void g(double &d);
const double pi=3.14;
g(pi);//错误
5.对非const引用参数不允许做类型转换,这种规定能帮助我们避免一种由于引入临时量而产生的可笑错误。例如:
void update(float&i);
void g(double d,float r)
{
update(2.0f);//error,const参数
update(r);//OK,传递r的
引用
update(d);//error,要求类型转换
}
扔掉镰刀,试试收割机——用vector和string代替数组
1.在当今软件中缓冲区溢出和安全缺陷是罪魁祸首。固定长度的数组所带来的愚蠢限制,即使仍在正确的界限内,也是软件开发人员的一大困扰。这些问题中大多数都是使用原始C风格设施(比如内置数组、指针和指针运算以及手工内存管理)代替缓冲区,向量或字符串等高层概念所引起的。使用vector或者string不仅更轻松,而且还有助于编写更安全、伸缩性更好的软件。
2.应用标准设施代替C风格数组,理由如下:
(1)他们能够自动管理内存。不再需要“比任何长度更长”的固定缓冲区,也不再需要胡乱进行内存重新分配和指针调整。
(2)它们具有丰富的接口,可以轻松明确地实现复杂功能。
(3)它们与C的内存模型兼容。Vector和string::c_str都可以传递给C语言的API。
(4)它们能够提供更大范围的检查。标准设施所能实现的(在调试模式下)迭代器和索引操作符,能够暴露很大范围类型的内存错误。
(5)它们支持上述特性并未牺牲太多效率。
(6)有助于优化。
3.示例,class phone{
public:
string name;//能接受不固定长的名字
int number;
void print_phone(){cout<<name<<" "<<number<<endl;}
};
phone phone_book[1000];
vector<phone> phone_bookvec;
phone_book[1001].print_phone();//不检查范围
phone_bookvec.at(1001).print_phone();//检查范围
上面代码中,内部对象数组phone_book具有固定的规模。如果我们选择了某个很大的规模,那么就会浪费存储;而如果选择一个过小的规模,数组就会溢出。标准库vector就解决了这个问题。
重识庐山真面目——运行时类型识别(RTTI)
1.通过RTTI,程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型。通过下面两个操作符提供RTTI:
(1)dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。
(2)typeid操作符,返回指针或引用所指对象的实际类型。
2.这些操作符只为带有一个或多个虚函数的类返回动态类型信息,对于其他类型,返回静态(即编译时类型的信息。
3.如果绑定到指针的对象不是目标类型的对象,则dynamic_cast失败并返回0;若转换到引用类型dynamic_cast失败,则抛出异常bad_cast ,对于typeid则抛出bad_typeid。
4.示例如下,
class base{virtual void display(){ }};
Class derived:public base{void display(){ }};
void fn(base *baseptr)
{
if(derived *derivedptr=dynamic_cast<derived*>(baseptr))
cout<<"Translated successfully at run time"<<endl;
else{ }
}
5.typeid的伪声明为:const type_info&typeid(type_id);
const type_info&typeid(expression);
type_info类所提供的基本操作有:
(1) t1==t2(如果两个对象t1和t2类型相同,则返回true;否则返回false);
(2) t1!=t2;
(3) t.name() (返回类型名字)。
type_info默认构造函数和复制构造函数以及赋值操作符都定义为private,所以不能定义或复制该类型的对象。程序中创建type_info对象的唯一方法是使用typeid。
6.typeid示例,
#include <iostream>
#include <typeinfo.h>
using namespace std;
class Base {public:virtual void vvfunc() {}};
class Derived : public Base {};
void main() {
Derived* pd = new Derived;
Base* pb = pd;
cout << typeid( pb ).name() << endl; //prints "class Base *"
cout << typeid( *pb ).name() << endl; //prints "class Derived"
cout << typeid( pd ).name() << endl; //prints "class Derived *"
cout << typeid( *pd ).name() << endl; //prints "class Derived"
delete pd;
}
外科医生与助手——体验强大的STL算法
1.STL算法本身就是一种函数模板,算法从迭代器那里获得一个元素。如果把算法想象成一个做手术的外科医生,那么迭代器就是外科医生的助手。当外科医生需要一把手术刀时时候,助手就提供给他;当需要一把剪刀时,助手就递给他。外科医生一心将精力集中在正在进行的手术上,而不是做手术用的器材放到了什么地方或他们放到了哪一种容器中。这样,一个标准的算法就可以处理几乎所有的类型的容器,并且一个容器可以容纳几乎任何类型的元素。这种通用化使得程序员可以无需做任何而外的工作就重复地使用代码和解决方案,花费更少的更少的时间来编写更稳定的程序。
2.排序算法示例, #include<iostream>
#include<algorithm>//STL算法声明文件
#include<function>//函数对象类和函数适配器声明文件
#include<vector>
using namespace std;
void main()
{
int iarray[]={26,17,15,22,23,33,32,40};
vector>int>ivector(iarray,iarray+sizeof(iarray)/sizeof(int));
//查找并输出最大元素
cout<<*max_element(ivector.begin(),ivector.end())<<endl;
//从小到大稳定排序
stable_sort(ivector.begin(),ivector.end(),less<int>());
}