Java
从数据类型来看,JAVA语言支持基本数据类型与引用数据类型,对于基本数据类型而言,其传递、赋值与返回都是直接的copy,对于引用类型(对象、数组),JAVA处理的也很漂亮,直接会将引用值赋值给接收方,无论该对象是全局的还是局部的。
以下这段话引用自网络,说的是数组,但是对象引用也是一样的道理:
JAVA语言可以直接返回数组。作为程序员不必关心这个返回的数组是在哪里定义的,也不必关心这个数组所引用的内存是否会被回收,JAVA的垃圾回收机制会保证以下事实:只要这个引用还在用,就不会回收其所对应的内存。
如果程序中需要在赋值后产生一个新的对象,则需要重写继承自Object类的clone方法,在clone方法中完成对对象的深克隆,这一点与C++是一样的。默认的clone方法只提供浅克隆,即把变量、对象(包括数组)的值、引用进行复制,这样对于基本类型的变量而言是没有问题的,而对于对象(包括数组)则会使得克隆前后的对象指向同一块内存。
实例代码如下:
浅克隆
public class Ex06
{
public static void main(String[] args)
{
// TODO Auto-generated method stub
ShallowClone sc1 = new ShallowClone();
System.out.println("sc1.getA(): " + sc1.getA() + " sc1.getB()[0]: "
+ sc1.getB()[0]);
ShallowClone sc2 = (ShallowClone) sc1.clone();
System.out.println("sc2.getA(): " + sc2.getA() + " sc2.getB()[0]: "
+ sc2.getB()[0]);
sc2.setA(50);
sc2.getB()[0] = 100;
System.out.println("sc1.getA(): " + sc1.getA() + " sc1.getB()[0]: "
+ sc1.getB()[0]);
System.out.println("sc2.getA(): " + sc2.getA() + " sc2.getB()[0]: "
+ sc2.getB()[0]);
}
}
/* 运行结果:
* sc1.getA(): 100 sc1.getB()[0]: 1000
* sc2.getA(): 100 sc2.getB()[0]: 1000
* sc1.getA(): 100 sc1.getB()[0]: 100
* sc2.getA(): 50 sc2.getB()[0]: 100
* 从上述结果可以看到,浅克隆对于引用无济于事,克隆前后的引用均指向同一个对象。
*/
class ShallowClone implements Cloneable
{
private int a;
private int[] b;
public ShallowClone()
{
a = 100;
b = new int[] { 1000 };
}
@Override
public Object clone()
{
// TODO Auto-generated method stub
ShallowClone sc = null;
try
{
sc = (ShallowClone) super.clone();
}
catch (CloneNotSupportedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return sc;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB()
{
return b;
}
public void setB(int[] b)
{
this.b = b;
}
}
深克隆
public class Ex07
{
public static void main(String[] args)
{
// TODO Auto-generated method stub
DeepClone sc1 = new DeepClone();
System.out.println("sc1.getA(): " + sc1.getA() + " sc1.getB()[0]: "
+ sc1.getB()[0]);
DeepClone sc2 = (DeepClone) sc1.clone();
System.out.println("sc2.getA(): " + sc2.getA() + " sc2.getB()[0]: "
+ sc2.getB()[0]);
sc2.setA(50);
sc2.getB()[0] = 100;
System.out.println("sc1.getA(): " + sc1.getA() + " sc1.getB()[0]: "
+ sc1.getB()[0]);
System.out.println("sc2.getA(): " + sc2.getA() + " sc2.getB()[0]: "
+ sc2.getB()[0]);
}
}
/* 运行结果:
* sc1.getA(): 100 sc1.getB()[0]: 1000
* sc2.getA(): 100 sc2.getB()[0]: 1000
* sc1.getA(): 100 sc1.getB()[0]: 1000
* sc2.getA(): 50 sc2.getB()[0]: 100
* 从上述结果可以看到,深克隆完全产生了一个新的对象。
*/
class DeepClone implements Cloneable
{
private int a;
private int[] b;
public DeepClone()
{
a = 100;
b = new int[] { 1000 };
}
@Override
public Object clone()
{
// TODO Auto-generated method stub
DeepClone dc = null;
try
{
dc = (DeepClone) super.clone();
int[] a = dc.getB();
int[] b = new int[a.length];
for (int i = 0; i < a.length; ++i)
{
b[i] = a[i];
}
dc.setB(b);
}
catch (CloneNotSupportedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return dc;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB()
{
return b;
}
public void setB(int[] b)
{
this.b = b;
}
}
CPP
对于C++而言,传递、赋值、返回一个对象就比Java复杂一些。首先,第一点与Java非常不同的是:C++中不能返回局部对象、指针。这是因为一旦离开其作用域,局部对象、指针所指向的内存就会被回收,成为无效内存。
C++中,当需要传递一个对象的时候,如果被调用的函数不对该对象进行修改,那么推荐传递const引用,这样可以避免为对象开辟内存所消耗的时间与资源。如果你传递的直接就是一个对象,那么需要注意该对象所属的类是否重载了析构函数,一般而言,重载了析构函数的类都需要重载拷贝构造函数与赋值运算符,这样才可以保证当向函数传递对象实参或函数返回对象时,对象可以被正确拷贝构造。代码如下:
#include "stdafx.h"
#include <iostream>
using namespace std;
class Test {
private:
int i;
public:
Test(int ii = 100) : i(ii) { cout << "构造函数" << endl; }
Test(const Test&);
Test& operator=(Test&);
~Test() { cout << "析构函数" << endl; }
int getI(){ return i; }
};
Test::Test(const Test& t) {
cout << "拷贝构造函数" << endl;
}
Test& Test::operator=(Test& t) {
cout << "重载赋值操作符" << endl;
return *this;
}
void FunPassObject(Test t) {
cout << "调用FunPassObject" << endl;
}
void FunPassRef(Test& t) {
cout << "调用FunPassRef" << endl;
}
Test FunRetObject() {
cout << "调用FunRetObject" << endl;
Test ret;
return ret;
}
int main() {
//Test test;
//Test test1 = test; // 拷贝构造函数
//Test test2;
//test2 = test; // 重载赋值运算符
//FunPassObject(test); // 拷贝构造函数
//FunPassRef(test); // 仅调用构造函数
Test ret = FunRetObject(); // 返回对象没有调用拷贝构造函数。编译器优化?是的。
cout << ret.getI() << endl;
return 0;
}
/*
缺省情况下,C++以by value方式(一个继承自C的方式)传递对象至(或来自)函数。除非你另外指定,否则函数参数都是以实际实参的复件(副本)为初值,而调用端所获得的亦是函数返回值的一个复件。这些复件(副本)系由对象的copy构造函数产出,这可能使得pass-by-value成为昂贵的(费时的)操作。
Effective C++ 条款20
*/
在C++中,系统默认同样进行的是浅拷贝,当需要进行深拷贝时,就需要我们重载拷贝构造函数与重载运算符。这是因为,在C++设计者看来,其试图达到的目标是让自定义的类也具有同基本数据类型一样的拷贝、赋值运算。这给予了C++更多灵活性,也带来了更多挑战。
C++和Java中,编译器默认提供的构造函数什么也不做。但是,由于C++可以在栈上构造对象,所以当使用默认构造函数时,C++在栈上所构造的对象的成员变量值为随机。代码如下:
#include "stdafx.h"
#include <iostream>
using namespace std;
class ConstructObject
{
private:
int num;
public:
int getNum();
void setNum(int value);
};
int ConstructObject::getNum()
{
return num;
}
void ConstructObject::setNum(int value)
{
num = value;
}
/*
使用默认构造函数在栈上创建对象,成员变量值随机;
而,在堆上创建对象,成员变量会被初始化,这是因为编译器会在堆上清理出一片空间来存放对象实体。
由于Java中所有的对象默认就在堆上,所以Java里创建对象的时候就会进行默认初始化,
这个默认初始化会为成员变量设定初值,但是本质是对堆空间的清空操作。
实际上,C++与Java里的默认构造函数,都什么也不做,只是确保对象能够正确的创建,来隐式调用一下父类的构造函数。
*/
int main()
{
ConstructObject coStack;
cout << coStack.getNum() << endl;
ConstructObject* coHeap = new ConstructObject();
cout << coHeap->getNum() << endl;
}
1867269908
0
请按任意键继续. . .