`
lovnet
  • 浏览: 6692578 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

C++与C#对比学习:函数(五)克隆函数,面向接口编程思想

 
阅读更多

我们知道C++中有复制构造函数的概念,C#其实也有复制构造函数的,但平时我们一般没有提到这个说法,而且基本上不这么用.C#中常用到的克隆函数.它们实现的功能基本类似,都是拷贝一些值.但复制构造函数是在调用构造函数实例化一个类时直接拷贝另外一个对象的值,而克隆函数是等你实例化完了之后再拷贝另外对象的值.

为啥需要克隆函数


克隆,顾名思义,是复制一个啥东西.我们有时需要把一个对象中的内容复制以另外一个相同类型的对象时就需要用到这样的函数.举个简单的例子.有这样的一个C#类
class Arwen
{
public string name;
public int age;
}

Arwen arwen = new Arwen();
arwen.name = "weiwen";
arwen.age = 24;
Arwen anotherArwen = new Arwen();//此时有另外一个对象anotherArwen,它想需要对象arwen中的值.咋整啊?


如果在C++中非常好办.直接Arwen anotherArwen = arwen; //系统会默认把arwen中的值都给你复制过来了.但在C#中这样写虽然也完全可以.但表达的意思是完全不一样的.在C#中你自定义的对象都是引用类型.你这样直接赋值不是把另一个对象的所有值都复制过去,而是只复制一个地址过去.可能你会想到一个笨办法
anotherArwen.age = arwen.age;
anotherArwen.name = arwen.name;
这样不就行了嘛,这确实是个办法,如果一个类中的字段比较少时确实可以这样简单的处理.但如果里面有很多字段时这样就不方便.这就需要克隆函数了.具体怎么用呆会再说.

为啥C++不需要克隆函数而C#需要

你会发现一个很有趣的现实,对类对象的处理,C#和C++刚好是反过来的.当你像上面这样直接用一个对象给另一个对象赋值,或者给一个函数传一个对象参数.比如有类void Fun(Arwen wen) { } 你调用时Fun(arwen).此时是先把arwen的值都拷贝一份然后再传过去.如果你不想传值,就必须用另外的办法.比如用指针或引用.
而C#像上面那样给函数传一个对象参数比如Fun(arwen).此时不是传值,而是传的引用.如果你要传值得另外想办法.这就是要用克隆函数去克隆

克隆函数中的面向接口编程思想

克隆函数怎么用呢?你可以随便整个啥函数,然后在里面实现复制,比如整个啥叫CloneMe或CopyMe这样的函数.但显然这是一个不好的方式.如果类一多,
你想一个个去克隆,你咋知道每个类里面的克隆函数叫啥名啊?

于是推荐的作法是你在类中如果要实现克隆就继承一个接口ICloneable,这个接口里面就一个函数声明object Clone();所以你以后调用类中的克隆函数时就调用函数Clone()就OK,所有的类都接受这个命名规范.所以此时你知道接口的好处所在了吧.它就是定义一组规范,使你以后用起来方便.

面向接口编程的一个例子

举个例子说说接口编程的思想魅力所在,假如我们模拟世界上所有的事物,生成一个个的类.现在的是经济社会,大家都可喜欢谈钱的事了.那我们就想知道下每类事物的价钱,价值.咋整?首先想到的自然是让每个类都提供一个函数,然后计算出自己的价钱.但函数名字咋取?随便乱取可就乱套了,于是来个接口IValuable.接口里面有函数声明double GetValue();
于是所有类继承这个接口,然后用GetValue这个函数去实现计算自己价钱的功能.当然有些事物是无价的,没法计算.比如人家要问你你家老婆值多少钱啊,你肯定会发火的.当然除非你老婆是干某个特殊行业的就不在此例了啊.于是我们只让那些有价的事物去继承IValuable这个接口

interface IValuable

{

double GetValue();

}


有类Car和House继承了此接口.假如你设计一个软件输入Car的牌子或House种类,就会返回是多少钱.于是可以这样写代码


double money;

法拉利 car = new 法拉利(); //类法拉得是继承自Car的一个类

money = car.GetValue();
奇瑞QQqqCar = new 奇瑞QQ();
money = qqCar.GetValue();

别墅 house = new 别墅(); //类别墅是继承自House的一个类
money = house.GetValue();

我们发现如果车的各类太多,你这样用起来不方便,于是想到用多态.于是来个这样的函数,当然前提是类Car中的函数GetValue声明为virtual类型
int GetAllCarsValue(Car car)
{
return car.GetValue();
}
Car car;
double money;
car = new 法拉利();
money = GetAllCarsValue(car);
car = new 奇瑞QQ();
money = GetAllCarsValue(car);
这样用着挺爽吧.但是你可能还想更爽一点,如果不仅是Car类,或者House类这样用多态处理.而是把所有的类合到一起用多态处理.你马上想到在C#类的世界中所有类都继承自objet,用它貌似可以实现这样的多态,但object可不给你提供GetValue()这函数,我们想想也知道,如果object针对无数的类定义一些函数,那它就是一个巨无霸了啊.而且既然它是最顶端的父亲就得体现所有类的共性,这GetValue也不是所有类的共性啊.


那咋整啊?自然要用到接口了,我们知道用虚函数可以实现多态.但用接口也能实现多态.于是代码这样写
double GetAllClassValue(IValuable iv)
{
return iv.GetValue();
}

然后这样调用
IValuable ivClass;
double money;


ivClass= new 法拉利();
money = GetAllClassValue(ivClass);
ivClass = new 奇瑞QQ();
money = GetAllClassValue(ivClass);
ivClass = new 别墅();
money = GetAllClassValue(ivClass);

通过上面的例子你发现面向接口编程的美妙了之处了吧.
一是定义了了一组规范,这样继承自接口的类实现某个相同功能时可以给函数取同样的名字.方便类使用者
二是可以使用多态.


克隆函数具体用法

浅拷贝


class Arwen :ICloneable
{
public int height;
public int age;

object Clone()
{
return this.MemberwiseClone(); //一个继承自object的函数
}
}

Arwen arwen = new Arwen();
arwen.height = 111;
arwen.age = 24;
Arwen anotherArwen = new Arwen();
anotherArwen =(Arwen) arwen.Clone(); //由于克隆函数返回的是object类型所以要做下类型转换


像上面的例子拷贝的都是值类型,但如果类中有引用类型呢
假如有另外一个类class Book
{
public int number;
object Clone()
{
return this.MemberwiseClone();
}
}

然后类Arwen变成这样的
class Arwen :ICloneable
{
public int height;
public int age;
public Book book;

object Clone()
{
return this.MemberwiseClone();
}
}

此时只拷贝book的一个引用.这就不是我们想要的.我们需要拷贝完之后两个对象所有的值独立,不能有任何联系.于是要用到深度拷贝

深拷贝



class Arwen :ICloneable
{
public int height;
public int age;
public Book book;

object Clone()
{
Arwen an = new Arwen();
an.height = this.height;
an.age = this.age;
an.book = (Book)this.book.Clone();
return an;
}
}

所以如果要是深拷贝的话类中的引用类型自己必须也提供拷贝函数,并且也要是深拷贝函数.当然如果类中只有值类型的话深度拷贝和浅拷贝是没有任何区别的.
所以比较麻烦的是如果类中的引用类型本身字段又还有引用类型.然后这样一直递归下去.那就必须所有涉及到的类提供了深度拷贝函数才能真正实现深度拷贝.但我们知道,设计类的时候不可能每个类都去整个拷贝函数
所以我们不是每次都能成功做深拷贝的.有些时候我们做深拷贝时会得不到真正想要的让拷贝来的值真正独立,是真正的深拷贝

C#中的复制构造函数



上面讲的是实例化之后再拷贝,实际上实例化的时候也能拷贝

class Arwen
{
public int age;

public Arwen(Arwen arwen)
{
this.age = arwen.age;
}
public Class1() { }

}

Arwen arwen = new Arwen();
arwen.age = 999;
Arwen an = new Arwen(arwen); //此时把arwen中的值拷过来了.an.age = 999;

当然我们很少这样用.很多时候还是用克隆函数


C++成员变量是指针时的问题

我们知道C++中不需要用克隆函数,因为你直接用等号赋值默认是拷贝值过去了.实际上相当于C#中的默认调用了MemberwiseClone()函数

但出现像C#中有引用类型,需要深拷贝的类似情形时咋整??也就是C++类的成员变量有指针时.
class Arwen
{
public:
int* age;
};

int num = 444;
Arwen arwen;
arwen.age = #

Arwen an = arwen; //把arwen中的值复制过去.age的值自然只是个指针值.
cout<<*an.age; //结果是444

*arwen.age = 888;
cout<<*an.age; //结果为888

显然当改变了对象arwen中的值时,对象an也跟着改了.这不是我们需要的.我们需要的是arwen与an中的数据完全独立*an.age仍为444这里的情形跟C#的引用类型浅拷贝一样.当改变一个对象中引用类型值时另一个对象的引用类型值也跟着变了.
而且这时碰到的问题不仅仅是an中值跟着改变,更恐怖的是还会出现指针悬挂的问题.弄不好把程序整崩溃了.举例

class Arwen
{
public:
int* age;
};

Arwen arwen;
arwen.age = new int(444);

Arwen an = arwen; //把arwen中的值复制过去.age的值自然只是个指针值.
cout<<*an.age; //结果是444

delete arwen.age;
cout<<*an.age; //指针an.age指向的内存被释放了.指针悬挂了啊.

这时咋整?那处理起来就复杂了啊.得用到复制构造函数,还有啥智能指针之类的东东.暂时还没完全搞懂.得研究下先.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics