以下都是我个人对C++的评述。主以评述C++的面相对象特性。及对C的扩展的内容。作为一个中间语言C是很成功我们可以通过C去直接读写内存。例如:int i; int *pI=&i;指针的操作几乎可以让C成为一个结构化了的汇编语言。C++作为C的扩展或者说在不损C的效率的同时(准确的说是5%的损失)要达到一个面相对象语言所应该举办的基本特性。则必须包含C的所有语法元素又要加入面相对象特性。
下面简要回顾,计算机语言的发展历史。早期计算机编程语言主要用于控制计算机本生所以其语言的特性也是在于与机器交互。汇编语言的出现可以直接通过操作机器码从而操作内存的读写。随这计算机的发展,计算机不在只单单从事科学计算。而要处理更多的数据,和事务。境而结构化编程语言的出现逐步取代了汇编和机器语言。变的人性化,可以和模拟现实中真是的处理流程。基本结构化处理由,顺序,分支,循环,基本的三种结构进行处理和组合。可以处理绝大多数问题。其分析问题的本质也从单单与机器交互变成了分析处理现实事务的特性。如下图:
|
由图可以看出其语言的发展是更加处理问题的复杂性决定的。也就是说随着计算机发展计算机处理问题的加深其针对真实世界处理事务的能力就必然被提高所以计算机编程语言就会发展。计算编程语言的发展就是其针对现实事务的能力的提高相反处理也就与计算机世界变的更远。因此学习C++是作为一个面向对象的分析问题的方法学学习的。
从面向对象看C++
下面主要结合面向对象的基本的三个(封装,继承,多态)基本特性针对C++语言作一个简述。OK,综述之前我总是会对我们的问题做一个说明,或者说,嘿,听着我们为什么要用这东西。对于结构化编程语言,我们也许会很直接的说。数据和针对数据的操作分离。一切问题都是由顺序,分支和循环三种结构流组成。传统的结构框图变的很直观。当我要做某事时,只要定义事先的处理流程我们的程序就会依次执行。毫无疑问这是直观且合理的。可是问题也就随之出现了。听着,也许某天你会对自己说我的程序无法描述算法以外的客观问题。例如。如果你想描述某个人的某个行为的时候,你会发觉特别的困难,也许会这样写:
struct Person
{
bool SleepOrGostate;//判断人的行为
};
void Move(Person* p)
{
if(p->state)
{
printf(“OK I’ll go!”);
}
else
{
printf(“Oh NO I’ll Sleep”);
}
}
.//主函数的调用如下
void main()
{
Person* p=(struct Person*)malloc(sizeof(Person));
p-> SleepOrGostate=true;
Move(p);
p-> SleepOrGostate=false;
Move(p);
If(p!=NULL)
free(p);
}
那么你也许会说一切问题都迎刃而解了。是吗?那么可以移动的物体很多很多。那么也许会说你Person_Move Car_Move Plane_Move 等等。。。可是仔细想想你会发现也许会有更多的解决问题的方案和策略。如果把对象的数据和针对对象的操作放在一起一切问题都会显得很直观。于是这种做法叫做封装。于是程序可以变成这样:
class Person
{
public:
bool SleepOrGostate;
public:
void Move(Person* p)
{
if(p->state)
{
printf(“OK I’ll go!”);
}
else
{
printf(“Oh NO I’ll Sleep”);
}
}
}
//那么你的主函数可以这样写
void main()
{
Person* p=new Person;
p->SleepOrGostate=true;
p->Move();
p->SleepOrGostate=false;
p->Move();
if(p!=NULL)
delete p;
}
上面的两组代码你可以发觉似乎没什么不同,只是把属性和方法放在了类里面。对于类的外部而言可以被看作一个整体。属性和针对属性的操作都由对象本生负责,你还会发觉另外一个问题。Malloc哪儿去了?我该如何分配存储空间问题。还有我该如何释放被占用的空间?C++是会为我们提供一切环境来做这些事情。的新的运算符new 用来分配一个存储空间。delete的出现也取代了free()函数的调用。一切都显得很直观。对象被class模坂创建并随着使用结束而销毁。很自然的法则。
同样随着问题的加深你会发觉有些问题会不断涌出。很多人都会说这个世界很复杂因为总是在不断变化。而起还会涌现出新的事物。当你发觉你所处的角度不同你所分析的结果是完全不相同的。例如你可以用国籍,或者性别来区分不同的人。那么你会发觉你要改写你的源代码。要在Person类中加入国籍或者性别这样的属性来进行扩展。OK如果很少的类也许你这样做问题不大。可是如过是大规模的问题。你会发觉你会被你分析的问题所杀死而不是问题本生。于是你很沮丧的说。我讨厌这样的局面。HO MY God 也许你会放弃尝试新的想法。不过C++的创建者不会那么的粗心。他很清楚面向对象理论。继承的出现显得很自然。OK了。问题已经解决。你会发觉事实本来就是如此。于是你的代码很变的更直观
class WorldPerson:public Person
{
public:
char* m_Country;
bool m_Sex;
public:
// Other Operation
}
//OK 你的主函数可以这样写
int main(int argc, char* argv[])
{
WorldPerson wp;
wp.m_Country="china";
cout<<wp.m_Country<<endl;
WorldPerson* pWp=new WorldPerson;
pWp->m_Country="china";
cout<<pWp->m_Country<<endl;
if(pWp!=NULL)
delete pWp;
return 0;
}
OK,一切运行都很合理 没什么问题,可是如果有个粗心的程序员这样写
WorldPerson wp;
cout<<wp.m_Country<<endl;
天啊。他会大呼小叫起来。发生了什么?听着。常识告诉我们。一个指针要么指向一个NULL要么就必须指向一个已经分配给它的内存空间。否则一切无法确定,你在使用指针之前最好判断指针是否为空于是你会这样写
WorldPerson wp;
If(wp. m_Country!=NULL)
cout<<wp.m_Country<<endl;
else
wp. m_Country=new char[20];
如果为NULL你就为它分配地址空间。这样就OK了。没有什么特别的问题不是吗?可是这样粗心的问题也许谁都会犯。而且你必须考虑另外一个问题那就是。那个成员变量由谁负责释放?如果不那样做你的问题也许不会很快出现可是你运行的越多那么占用的资源也就越多。于是C++为我们提供了另外两个方法。告诉你成员数据由类负责初始化。同也由类负责清理工作。于是你的类可以这样写:
class WorldPerson:public Person
{
public:
char* m_Country;
bool m_Sex;
public:
WorldPerson()
{
this->m_Country=new char[20];
strcpy(this->m_Country,"china");
this->m_Sex=true;
};
~WorldPerson()
{
delete[] this->m_Country;
};
public:
//Other Operation
};
这样应该没问题了把?创建并初始化。销毁并清理所占用的资源一切紧紧有调。没有太多问题吗?恩。。。也许你还少考虑了一个问题。那就是继承本生。父类的初始化由谁来负责?嘿嘿,你的脑袋也许大了。干吗还要考虑这个问题?天知道不是吗,好了。别抱怨。如果你想用面相对象特性你就必须为此付出维护代价。你必须调用父类的构造函数。于是你的构造函数么这样写:
WorldPerson():Person()
{
……..
}
父类的构造函数会先被调用。毫无疑问。这一切就如同自然规律一样。父亲先创建。然后由其派生出子类对象。很自然吗?对的。那么死亡顺序也是如此吗?Oh NO 你错了。并非你想的那样。事实往往与你想的相反。清理函数的调用从子类开始。然后才是父类。原因仔细想想很简单。无论引用还是指针。指向的基地址都是父类的,而子类是父类的超集,大于等于父类的范围。因此销毁指针或引用的同时首先必须销毁的是子类的范围然后才会是父类的。OK试验如下:
int main(int argc, char* argv[])
{
WorldPerson wp;
return 0;
}
输出结果如下:
Person
WorldPerson
~WorldPerson
~Person
OK问题有一次许加深了.你会发觉你目前的方法无法解决问题本生。例如,某些时候你会发觉,很多的问题。你可以在早期就决定其本质。只是到了晚期你希望能够有更多的扩充。例如:某天公司决定让公司所有的员工都提出一份报表。很显然。提出报表是每个员工都会做的事情。只是提出的报表内容很不一样。销售人员和技术人员的报表内容和文档风格是很大的不同的。这你就必须么告诉自己,OK 我必须为没个对象都加入写报表的方法。是的。你可以这样做。于是你的代码如下:
class Seller
{
public:
char* m_name;
public:
void Print_Report()
{
cout<<"使用打印机"<<endl;
cout<<"答应销售记录"<<endl;
}
};
class Programmer
{
public:
char* m_name;
public:
void Print_Report()
{
cout<<"使用打印机"<<endl;
cout<<"答应程序记录"<<endl;
}
};
看看你的代码好了。有多少重复?如果记录更多你重复就更多。那么仔细想想。这两个类仅仅不同的只有打印记录的格式。是吧。那么我们很显然可以分析出一个父类。Employee它提供一个方法。只是晚期决议出具体的不同。
如下:
class Employee
{
public:
char* m_name;
public:
void Print_Report()
{
cout<<"使用打印机"<<endl;
}
};
class Seller:public Employee
{
public:
void Print_Report()
{
Employee::Print_Report();
cout<<"答应销售记录"<<endl;
}
};
class Programmer:public Employee
{
public:
void Print_Report()
{
Employee::Print_Report();
cout<<"答应程序记录"<<endl;
}
};
OK你直接调用父类的方法。那么就没问题了?恩。。。。听这。你的调用成员也许会是这样的:
int main(int argc, char* argv[])
{
Employee* e=new Programmer();
e->Print_Report();
if(e!=NULL)
delete e;
return 0;
}
输出结果:
使用打印机
天啊。。你的结果并没有和你想的那样。。。你在运行其希望能够装如一个实际的Programmer对象。而通过父类的指针e可着到调用到正确函数。可是结果是你没能成功。OH对了。C++是支持多态的。是的那么你必须使用virtual 关键字。那么你的父类的void Print_Report()
申明就必须这样写virtual void Print_Report()
那么你的运行结果就很正确。
OK,针对C++语言的基本面相对象特性我已经基本讲述完毕。只是还有一些相关语法没有讲到。