C++中对象或其对象指针的赋值

来源:互联网 发布:淘宝用花呗付款好吗 编辑:程序博客网 时间:2024/05/21 14:10

C++中对象或其对象指针的赋值

C++中成员函数的动态绑定:
C++中要实现函数的动态绑定,必须在其基类中将函数声明为virtual且在子类中对函数加以实现。然后用一个基类指针指向某一个子类对象,这样才会在函数调用时实现动态绑定。
在C++中,只有对象指针才有可能使用函数的动态绑定。所有对象实体所能够进行的操作都是静态绑定的。

C++中对象或其指针的赋值操作:
这是很容易混淆和出错的地方。C++将指针暴露给用户,这样用户就有两种方式可以操作对象。通过对象本身操作或通过指针操作,但是这两种操作方式通常会带来不同的效果。但正因为这样的不同通常会给用户带来很大的困扰。这里我们通过几种情况来对C++中对象或对象指针的赋值操作进行总结。
在总结之前,我们需要先明确C++中对象分配的机制。在C++中,对象可以被分配在静态数据区、栈空间或堆空间。被分配在静态数据区的对象在其定义时声明为static,其在编译时刻被放入静态数据区。栈空间的对象是通过对象的显式声明分配的。堆中的对象则是通过new分配的。C++的对象分配机制与Java不一样,Java里的所有对象都被分配在堆空间里。
《浅述Java中OO构造的实现》一文简单叙述了Java中对象的存储模型。Java中这种存储模型的设计借鉴于C++。C++也是采用同样的CIR结构来记录类中的信息。在建立一个类的对象时,C++编译器会为每个对象分配相应的空间来记录对象所属的类中的属性。而对对象所进行的操作,都是静态绑定的。
1) 栈中对象的赋值:
栈中对象的赋值采取深层拷贝的方式。考虑下面一个例子:
#include
using namespace std;

class Father{
public:
int a, b;
Father(){
a = 0;
b = 1;
}
void func1(){
cout << a << " a in Father" << endl;
}
void func2(){
cout << b << " b in Father" << endl;
}
};

class Child: public Father{
public:
int c;
Child(){
a = 10;
b = 11;
c = 12;
}
void func1(){
cout << a << " a in Child" << endl;
}
void func2(){
cout << b << " b in Child" << endl;
}
void func3(){
cout << c << " c in Child" << endl;
}
};

int main(){
Father father;
Child child;
cout << "father addr is " << &father << endl;
cout << "child addr is " << &child << endl;
father.func1();
father.func2();
father = child;
cout << "father addr is " << &father << endl;
cout << "child addr is " << &child << endl;
father.func1();
father.func2();
return 0;
}

在main函数中执行了father=child后,father中所有的属性都被改变为child中对应属性的值,而father中调用的方法还是类Father的方法。
这说明一点,在对象被分配时,其所有的函数都已在静态时刻被绑定。即使变量在运行时被重新赋值,所改变的也只是对象的属性值。
执行结果是:
father addr is 0x22ff48
child addr is 0x22ff3c
0 a in Father
1 b in Father
father addr is 0x22ff48
child addr is 0x22ff3c
10 a in Father
11 b in Father

2) 堆中对象的赋值:
堆中对象的赋值亦采用深层拷贝的方式。当把一个堆对象a赋给另一个堆对象b时,并不是b的指针指向a,而是将b中的属性值改变成为a中的属性值。换言之,即使对象赋值发生,但是对象所处的内存位置并没有发生变化,指向这两个对象的指针也没有发生改变。如下例所示:
#include
using namespace std;

class Father{
public:
int a, b;
Father(){
a = 0;
b = 1;
}
void func1(){
cout << a << " a in Father" << endl;
}
void func2(){
cout << b << " b in Father" << endl;
}
};

class Child: public Father{
public:
int c;
Child(){
a = 10;
b = 11;
c = 12;
}
void func1(){
cout << a << " a in Child" << endl;
}
void func2(){
cout << b << " b in Child" << endl;
}
void func3(){
cout << c << " c in Child" << endl;
}
};

int main(){
Father* father = new Father();
Child* child = new Child();
cout << "father obj addr is " << father << endl;
cout << "child obj addr is " << child << endl;
father->func1();
father->func2();
*father = *child;
cout << "father obj addr is " << father << endl;
cout << "child obj addr is " << child << endl;;
father->func1();
father->func2();
cout << "===============================" << endl;
Child* child_1 = (Child*) father;
child_1->func3();
return 0;
}
*father=*child便是堆中对象的赋值。执行结果为:
father obj addr is 0x3e2d68
child obj addr is 0x3e2d88
0 a in Father
1 b in Father
father obj addr is 0x3e2d68
child obj addr is 0x3e2d88
10 a in Father
11 b in Father
===============================
131074 c in Child
我们可以看到执行前后,指针father和child的都没有发生改变。唯一改变的是father所指向对象中的内容。
在执行了Child* child_1 = (Child*) father后,我们使用了一个Child*类型的指针指向了father指针所指向对象的内存空间。这个Father类型对象内存空间只记录了a,b两个属性的值,故在其访问属性c时,便因为地址偏移访问到了这个内存空间之外,所以c的值是一个不确定的整型数。这也是使用C++指针容易出现的错误之一。
3) 对象指针的赋值:
对象的赋值尚好理解,重点问题在于对象指针之间的赋值。到底是指针所指向对象的内容被改变,还是指针本身转向指向了另一个对象呢?
从赋值这个操作本身的意义来看,是后者。C++也是这样做的。当一个指针被赋成另一个指针后,这两个指针将指向相同的对象。
但是,我们要讨论最重要的问题在于一个对象指针被赋值成另一个对象指针后,它所调用的函数究竟是原来类中定义的函数还是新指向的对象对应的函数呢?
考虑下面一段代码:
#include
using namespace std;

class Father{
public:
int a, b;
Father(){
a = 0;
b = 1;
}
void func1(){
cout << a << " a in Father" << endl;
}
void func2(){
cout << b << " b in Father" << endl;
}
};

class Child: public Father{
public:
int c;
Child(){
a = 10;
b = 11;
c = 12;
}
void func1(){
cout << a << " a in Child" << endl;
}
void func2(){
cout << b << " b in Child" << endl;
}
void func3(){
cout << c << " c in Child" << endl;
}
};

int main(){
Father* father = new Father();
Child* child = new Child();
cout << "father obj addr is " << father << endl;
cout << "child obj addr is " << child << endl;
father->func1();
father->func2();
father = child;
cout << "father obj addr is " << father << endl;
cout << "child obj addr is " << child << endl;;
father->func1();
father->func2();
cout << "===============================" << endl;
Child* child_1 = (Child*) father;
child_1->func3();
return 0;
}

可以预见,在执行了father=child后,指针father和child指向了同一个Child类型的对象。但是问题是,现在father->func1和father->func2执行的是Father还是Child中的函数?
如果大家对Java比较熟悉,那么显然知道调用的是Child中的方法,因为Child将Father中的func1与func2进行了重写(覆盖),在调用father->func1和father->func2时则发生了动态绑定。父类指针father指向了一个子类对象,所以在运行过程中理所当然地将被调用的方法绑定到子类中重写的方法上。
但是C++也是如此么?答案是否定的。重申一遍,Java中之所以father->func1和father->func2调用的是子类Child中的方法,是因为发生了动态绑定!但是这段C++的程序没有任何动态绑定的发生,C++中发生动态绑定的前提是子类要实现基类中的virtual函数。但是这段程序没有virtual函数,也就是说所有的函数调用都是静态绑定的。所以无论father指针哪个对象,其在进行函数调用时调用的一定是在其定义时所声明的Father类中的函数!
换句话说,如果我们想让father指针可以调用Child中重写的方法,必须将基类Father变成虚基类,其中的函数被改成virtual函数。这样在使用father指针指向Child类型对象的时候调用的才是Child中的函数。
上面代码的执行结果为:
father obj addr is 0x3e2d68
child obj addr is 0x3e2d88
0 a in Father
1 b in Father
father obj addr is 0x3e2d88
child obj addr is 0x3e2d88
10 a in Father
11 b in Father
===============================
12 c in Child
由于father指针指向了一个Child类型的对象,虽然father无法直接访问到属性c,但是在做类型扩展后,可以通过地址偏移找到存储属性c的地址空间。从而访问到c的值12.

0 0
原创粉丝点击