记得刚从C语言转到C++时,我知道了C语言和C++最大的区别就是从面向过程转到了面向对象。这其中最大的差别就是多了类的封装性,继承性和多态性。
其中继承性和多态性比较好理解。前者可以继承父类的公有、保护的数据和方法,后者就是基类的引用指向派生类的对象。
但是封装性比较抽象,以前和AirChain同学谈起过。他说是为了代码安全。将成员函数和成员变量放到private中可以让别人看不见(至少在没有源代码的情况下别人是不知道的),并且这样可以防止在外部无意间修改类中的变量值。
当时我们都是一知半解的,当时我想如果换到C语言中,只要将需要给外部知道的方法(就相当于类中的public方法和变量)定义放到头文件中(head.h),将不想给人知道的变量和方法(就相当于private部分)放到代码文件(.cpp)中,这样当别人#include "head.h"后也是无法访问里面的“隐私”代码。这样类的封装性就无从谈起了。
后来AirChain同学发了一篇“这下傻×了 ---在刚刚过去的一天, 才发现自己对类的封装性一直有一个严重的误解”的文章(点击此处浏览),讲述了只要是同一个类实例化的两个变量是可以互相访问对方的私有变量的!也就是说从代码安全角度也无法解释类封装性的好处了。
经过暑假对几个超小型的程序开发后发现类封装性带来的几个好处,这也许可以解释为什么C++将封装性视为对C语言提升的三大特性之一。
其中一点是可以减少代码的改写量。我们看下面的例子:
我们需要编写一个用于启动程序的软件。我们可以为每个程序起一个简便的名字,比如我们为魔兽世界取名为WoW,这样我们用热键呼出我们的程序,然后输入昵称,就能快速启动一个或者对应的一组程序。
现在我们来分析一下,这个程序需要两个数据列,一个AppName用于存放昵称,并且是唯一的。其次是AppPath,用于存放程序的路径,这个是必须填写的。这样最简单的程序就做好了。我们来看看不用类封装的情况(C#语法)
public bool InsertNewApp(string NewAppName, string AppPath)
{
using (OleDbConnection odc = new OleDbConnection(ConnectStr))
{
OleDbCommand odCmd = odc.CreateCommand();
odCmd.CommandText = string.Format("INSERT INTO UltraRun(AppName,AppPath) VALUES('{0}','{1}')", NewAppName, AppPath);
odc.Open();
if (odCmd.ExecuteNonQuery() > 0)
return true;
return false;
}
}
//当需要修改这个程序的路径或者名字时
public bool UpdateApp(string originAppName, string NewAppName, string NewAppPath)
{
using (OleDbConnection odc = new OleDbConnection(ConnectStr))
{
OleDbCommand odCommand = odc.CreateCommand();
odCommand.CommandText = string.Format("UPDATE UltraRun SET AppName='{0}',AppPath='{1}' WHERE AppName='{2}'", NewAppName, NewAppPath, originAppName);
odc.Open();
if (odCommand.ExecuteNonQuery() > 0)
{
odc.Close();
return true;
}
else
{
odc.Close();
return false;
}
}
}
//当需要删除这个名称时
public bool DeleteApp(string AppName)
{
using (OleDbConnection odc = new OleDbConnection(ConnectStr))
{
OleDbCommand odCmd = odc.CreateCommand();
odCmd.CommandText = string.Format("Delete FROM UltraRun WHERE AppName='{0}'",AppName);
odc.Open();
if (odCmd.ExecuteNonQuery() > 0)
return true;
return false;
}
}
乍看之下好像没什么不妥啊。我们以前写C函数,甚至大多数C++/C#函数都是这样的。是的,短期看来的确没什么区别。但是现在用户反映,但是这么两个不够啊。当我的昵称设置的太多,比如WoW,War3名字类似的太多了我自己也不知道了。所以要增加一个Descrption(说明)。这下你囧了吧。当你向数据库添加时必须要有重载。用户可以没有说明,也可以有。所以你就要将所有的方法添加一个重载参数,你有多少个方法都 要修改。而且还不仅如此,每个调用方的参数数量也必须修改。
那么试想一下如果当初我们就当这些方法封装到一个类里面会怎么样呢?
{
public string AppName;
public string AppPath;
public string AppDescription;
private AppInfo()
{
//NULL constructor is not allowed
}
public AppInfo(string AppName,string AppPath,string AppDescription)
{
this.AppName = AppName;
this.AppPath = AppPath;
this.AppDescription = AppDescription;
}
public AppInfo(string AppName,string AppPath):this(AppName,AppPath,null)
{
}
我们做了一个 类,并且提供了两个参数的构造函数。这样我们起初设计那三个创建,修改和删除方法的时候参数就是一个类。不管以后要增加些什么,参数都需要改。我们需要改的就只有这个类的变量和构造方法。而调用方的代码也不需要修改,因为参数还是这个类;而这个类在实例化时如果有说明会自动调用三个参数的构造函数,这些你都不用操心,这样我们就可以省下不少力气。
另一方面,我们看看两个程序的效率。不用封装性,方法必须重载,并且需要传3个参数。而如果我们用类来封装,那么只需要传递一个参数而且没有重载。前者如果参数中既有引用类型又有值类型,那么调用时就会既有值传递又有引用传递,值传递又会在栈中重新存放此变量的副本,这样的效率实在难以忍受。后者按引用传递,传递后只是在托管堆上保存一个32位的地址,不存在深复制的问题,效率相对较高。
本文仅为初学者在经历C->C++ -> c#学习后对历史的总结,其中包括不少从实践中体会出来的土方子,不排除其中可能有理解错误或其他的错误。本文仅为抛砖引玉,如果文中有错误或者您有更好的想法欢迎留言或点“联系”E-mail我。