派生类虚函数返回派生类的指针

时间:2022-09-08 00:13:10
继承体系中虚函数的函数原型应该是完全一样的,唯一可以不同的是:基类返回基类的指针或引用,而派生类可以返回派生类的指针或引用。
class A
{
public:
virtual A *func() { cout << "A" << endl; return this; }
};

class B : public A
{
public:
virtual B *func() { cout << "B" << endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

B *pb = new B;
B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
delete pb;
}

在这里,B继承A,并覆写了虚函数,只是虚函数返回值为B *。
在main函数中,A * pa = new B,pa->func()发生了多态行为,cout 的结果是 B,但是函数的返回值是 A *,如果试图用 B *ppa = pa->func()去接收返回值,则编译错误。为什么会有这种行为?
另外:虚函数发生多态行为时 ,编译器检查了函数特征标(函数的形参类型和形参个数),是否编译器也检查返回值?

29 个解决方案

#1


子类实现父类的虚函数是为了实现多态,多态是为了用父类的指针能调用到正确的子类的函数,这时候调用的函数形式肯定是父类的形式,因此B*的返回值在这里没有意义

这样的覆写价值不大

#2


pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

#3


func()调用的时候只看其对象的虚函数表,是B的就肯定调用B::func,跟返回值有神马关系?

#4


这个得了解一下虚表和虚指针来完善一下理论体系,这样才能解答你的问题

#5


引用 2 楼 lineage191 的回复:
pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

此处不是重载,而是override 

#6


引用 3 楼 passion_wu128 的回复:
func()调用的时候只看其对象的虚函数表,是B的就肯定调用B::func,跟返回值有神马关系?


B::func() 函数返回 this 是指针class B的,为什么实际上是A的?

#7


引用 2 楼 lineage191 的回复:
pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

执行的是B::func,return this;自然也是返回的B *,难道不是这样?

#8


#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

B *pb = new B;
B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
注意修改了子类方法名
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func1() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
B *ppa = (B*)pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
//A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,
ppa->func();
//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果
派生类虚函数返回派生类的指针
这表明
 A *pa = new B; 
// pa其实接受的是类B的指针

所以本身代码这写就是不科学的

#9


引用 7 楼 maliang351 的回复:
Quote: 引用 2 楼 lineage191 的回复:

pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

执行的是B::func,return this;自然也是返回的B *,难道不是这样?



执行的是B::func 不一定代表是B调用的,多态的机制是父类的指针进行调用,而调用的函数是子类的虚函数表中的函数,其实调用的还是父类的函数,只不过在虚函数表中用子类函数指针覆盖了父类函数指针所以才会调用到子类的函数,但调用者依然是父类指针,retrun this,this类型当然是A*类型的,只不过这个this指针指向B类型的实例而已,
多态就是以父类指针指向子类实例,所有函数调用都是调用父类的函数,假设子类函数不是虚函数,是无法被A *pa = new B;中pa调用,如果是虚函数,因为虚函数表的机制,子类函数覆盖了父类函数,所以才会调用子类的函数。

#10


引用 8 楼 cowboylym 的回复:
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

B *pb = new B;
B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
注意修改了子类方法名
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func1() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
B *ppa = (B*)pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
//A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,
ppa->func();
//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果
派生类虚函数返回派生类的指针
这表明
 A *pa = new B; 
// pa其实接受的是类B的指针

所以本身代码这写就是不科学的

感谢你写了这么多,不过你这两段代码想表达的是什么?代码中并没有看出如何解释return this这个现象。最后得出的代码不科学的结论是指虚函数的写法?这种写法只是关于虚函数的一个研究,书上简单的一句话并不简单。

#11


引用 10 楼 maliang351 的回复:
Quote: 引用 8 楼 cowboylym 的回复:

#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

B *pb = new B;
B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
注意修改了子类方法名
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func1() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
B *ppa = (B*)pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
//A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,
ppa->func();
//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果
派生类虚函数返回派生类的指针
这表明
 A *pa = new B; 
// pa其实接受的是类B的指针

所以本身代码这写就是不科学的

感谢你写了这么多,不过你这两段代码想表达的是什么?代码中并没有看出如何解释return this这个现象。最后得出的代码不科学的结论是指虚函数的写法?这种写法只是关于虚函数的一个研究,书上简单的一句话并不简单。

我只是做了几个情况的测试 最后得到的结论就是 我注释里写的A* pa = new B;
这句话 的最终结果是pa 被赋予了一个类B的指针 虽然它被声明为A类的指针

#12


引用 6 楼 maliang351 的回复:
Quote: 引用 3 楼 passion_wu128 的回复:

func()调用的时候只看其对象的虚函数表,是B的就肯定调用B::func,跟返回值有神马关系?


B::func() 函数返回 this 是指针class B的,为什么实际上是A的?


呵呵,你没有搞清楚编译检查和运行检查。
编译器才不管你运行时调用了什么东西呢,pa是A类的指针,它指向的函数就是A类的函数,A类的函数返回值是A*,所以编译器很负责任地告诉你pa->func的返回值就是A*。
佬虚函数,多态什么的,那时运行时才知道的,编译器完全不懂。

举个例子,编译器就像是体育场门口的守卫,它的责任就是所有人凭票入场,比赛开始守卫就会消息,后领导可以随便出入。
于是你装着是领导的样子跟编译器说,我是领导,我要xx。对不起,比赛开始之前编译器只认门票不认人。所以你想xx肯定不行的,不管你是领导还是虚函数,编译器只认指针这张票。

#13



#include "iostream"
using namespace std;
class A
{
public:
virtual A *func() { cout << "A" << endl; return this; }
};

class B : public A
{
public:
virtual B *func() { cout << "B" << endl; return this; }
};

void main()
{
A *pa = new B;
B *ppbb=static_cast<B*>(pa->func());
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

B *pb = new B;
B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
delete pb;
}

//虚函数需要看指针指向的是什么对象,指向什么对象就指向什么方法



static_cast用法:static_cast < type-id > ( expression ) 
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。

#14


谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

#15


派生类在重写基类的虚函数时,不应修改返回类型。楼主这是返回A*和B*,如果假设基类返回void,而派生类重写时返回bool,编译都不会过。

#16


引用 14 楼 reversesoul 的回复:
谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

#17


引用 16 楼 maliang351 的回复:
Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

#18


引用 9 楼 lineage191 的回复:
Quote: 引用 7 楼 maliang351 的回复:

Quote: 引用 2 楼 lineage191 的回复:

pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

执行的是B::func,return this;自然也是返回的B *,难道不是这样?



执行的是B::func 不一定代表是B调用的,多态的机制是父类的指针进行调用,而调用的函数是子类的虚函数表中的函数,其实调用的还是父类的函数,只不过在虚函数表中用子类函数指针覆盖了父类函数指针所以才会调用到子类的函数,但调用者依然是父类指针,retrun this,this类型当然是A*类型的,只不过这个this指针指向B类型的实例而已,
多态就是以父类指针指向子类实例,所有函数调用都是调用父类的函数,假设子类函数不是虚函数,是无法被A *pa = new B;中pa调用,如果是虚函数,因为虚函数表的机制,子类函数覆盖了父类函数,所以才会调用子类的函数。

在调试的时候,pa->func() 函数中this指针的类型是 B *,this 和 pa具有相同的地址值。

#19


引用 17 楼 reversesoul 的回复:
Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

#20


引用 15 楼 zhuyf87 的回复:
派生类在重写基类的虚函数时,不应修改返回类型。楼主这是返回A*和B*,如果假设基类返回void,而派生类重写时返回bool,编译都不会过。

A *和B *这种返回情况是虚函数的一个特例。当然,正常写代码显然不会这么做。现在只是想解释一下这种想象。

#21


引用 19 楼 maliang351 的回复:
Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

#22


引用 21 楼 reversesoul 的回复:
Quote: 引用 19 楼 maliang351 的回复:

Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

你在子类创建一个父类没有的新函数,然后动态绑定一下看看,如果调用不到,说明他被切去了。

#23


引用 5 楼 lineage191 的回复:
Quote: 引用 2 楼 lineage191 的回复:

pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

此处不是重载,而是override 


确实是override,非overload。运行结果放在那了,为什么还不相信呢。在LZ的例子里,只有返回值的类型不同,但返回值类型是可以转换的,因此算override。若返回值改为基本类型,一个是int,一个是float,则返回值不可以转换,因此导致override失败。

#24


引用 15 楼 zhuyf87 的回复:
派生类在重写基类的虚函数时,不应修改返回类型。楼主这是返回A*和B*,如果假设基类返回void,而派生类重写时返回bool,编译都不会过。


引用 20 楼 maliang351 的回复:
A *和B *这种返回情况是虚函数的一个特例。当然,正常写代码显然不会这么做。现在只是想解释一下这种想象。


请参考23楼。

#25


每个成员函数中都隐藏着一个this的参数,pa->func()函数中返回this,其实这个this是由pa->func()传进去的,pa属于类A,所以this指针也是类A。

#26


引用 22 楼 reversesoul 的回复:
Quote: 引用 21 楼 reversesoul 的回复:

Quote: 引用 19 楼 maliang351 的回复:

Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

你在子类创建一个父类没有的新函数,然后动态绑定一下看看,如果调用不到,说明他被切去了。

切去是在 对象赋值的时候发生的。派生类对象赋值给基类对象,会隐式调用基类的拷贝构造函数或赋值运算符,拷贝构造函数或赋值运算符完成对基类成员的初始化或者赋值,函数一旦调用完毕,对象即为基类。在这种情况下,派生类的派生类部分在对基类进行初始化或赋值时被“切掉”了。
而指针的赋值pa = &b,只是pa指向的位置改变了,pa只是一个指针,哪里来的切去?!
pa所能调用的操作,当然是受其静态类型决定的,这一点并不等价于切去!

#27


引用 26 楼 maliang351 的回复:
Quote: 引用 22 楼 reversesoul 的回复:

Quote: 引用 21 楼 reversesoul 的回复:

Quote: 引用 19 楼 maliang351 的回复:

Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

你在子类创建一个父类没有的新函数,然后动态绑定一下看看,如果调用不到,说明他被切去了。

切去是在 对象赋值的时候发生的。派生类对象赋值给基类对象,会隐式调用基类的拷贝构造函数或赋值运算符,拷贝构造函数或赋值运算符完成对基类成员的初始化或者赋值,函数一旦调用完毕,对象即为基类。在这种情况下,派生类的派生类部分在对基类进行初始化或赋值时被“切掉”了。
而指针的赋值pa = &b,只是pa指向的位置改变了,pa只是一个指针,哪里来的切去?!
pa所能调用的操作,当然是受其静态类型决定的,这一点并不等价于切去!

指向的永远是父类部分,并没有改变。

#28


引用 27 楼 reversesoul 的回复:
Quote: 引用 26 楼 maliang351 的回复:

Quote: 引用 22 楼 reversesoul 的回复:

Quote: 引用 21 楼 reversesoul 的回复:

Quote: 引用 19 楼 maliang351 的回复:

Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

你在子类创建一个父类没有的新函数,然后动态绑定一下看看,如果调用不到,说明他被切去了。

切去是在 对象赋值的时候发生的。派生类对象赋值给基类对象,会隐式调用基类的拷贝构造函数或赋值运算符,拷贝构造函数或赋值运算符完成对基类成员的初始化或者赋值,函数一旦调用完毕,对象即为基类。在这种情况下,派生类的派生类部分在对基类进行初始化或赋值时被“切掉”了。
而指针的赋值pa = &b,只是pa指向的位置改变了,pa只是一个指针,哪里来的切去?!
pa所能调用的操作,当然是受其静态类型决定的,这一点并不等价于切去!

指向的永远是父类部分,并没有改变。

B b;
A *pa = &b;  // pa指向的是b的内存地址
pa指向的内存,pa能进行的操作,这是两个概念。

#29


它的名词叫“协变”,自己认真google就可以了。
就别乱展开讨论了。

#1


子类实现父类的虚函数是为了实现多态,多态是为了用父类的指针能调用到正确的子类的函数,这时候调用的函数形式肯定是父类的形式,因此B*的返回值在这里没有意义

这样的覆写价值不大

#2


pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

#3


func()调用的时候只看其对象的虚函数表,是B的就肯定调用B::func,跟返回值有神马关系?

#4


这个得了解一下虚表和虚指针来完善一下理论体系,这样才能解答你的问题

#5


引用 2 楼 lineage191 的回复:
pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

此处不是重载,而是override 

#6


引用 3 楼 passion_wu128 的回复:
func()调用的时候只看其对象的虚函数表,是B的就肯定调用B::func,跟返回值有神马关系?


B::func() 函数返回 this 是指针class B的,为什么实际上是A的?

#7


引用 2 楼 lineage191 的回复:
pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

执行的是B::func,return this;自然也是返回的B *,难道不是这样?

#8


#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

B *pb = new B;
B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
注意修改了子类方法名
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func1() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
B *ppa = (B*)pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
//A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,
ppa->func();
//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果
派生类虚函数返回派生类的指针
这表明
 A *pa = new B; 
// pa其实接受的是类B的指针

所以本身代码这写就是不科学的

#9


引用 7 楼 maliang351 的回复:
Quote: 引用 2 楼 lineage191 的回复:

pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

执行的是B::func,return this;自然也是返回的B *,难道不是这样?



执行的是B::func 不一定代表是B调用的,多态的机制是父类的指针进行调用,而调用的函数是子类的虚函数表中的函数,其实调用的还是父类的函数,只不过在虚函数表中用子类函数指针覆盖了父类函数指针所以才会调用到子类的函数,但调用者依然是父类指针,retrun this,this类型当然是A*类型的,只不过这个this指针指向B类型的实例而已,
多态就是以父类指针指向子类实例,所有函数调用都是调用父类的函数,假设子类函数不是虚函数,是无法被A *pa = new B;中pa调用,如果是虚函数,因为虚函数表的机制,子类函数覆盖了父类函数,所以才会调用子类的函数。

#10


引用 8 楼 cowboylym 的回复:
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

B *pb = new B;
B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
注意修改了子类方法名
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func1() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
B *ppa = (B*)pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
//A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,
ppa->func();
//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果
派生类虚函数返回派生类的指针
这表明
 A *pa = new B; 
// pa其实接受的是类B的指针

所以本身代码这写就是不科学的

感谢你写了这么多,不过你这两段代码想表达的是什么?代码中并没有看出如何解释return this这个现象。最后得出的代码不科学的结论是指虚函数的写法?这种写法只是关于虚函数的一个研究,书上简单的一句话并不简单。

#11


引用 10 楼 maliang351 的回复:
Quote: 引用 8 楼 cowboylym 的回复:

#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

B *pb = new B;
B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
注意修改了子类方法名
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func1() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
//B *ppa = pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果:
派生类虚函数返回派生类的指针
实验
#include <iostream>
class A
{
public:
virtual A *func() { std::cout << "A" << std::endl; return this; }
};

class B : public A
{
public:
virtual B *func() { std::cout << "B" << std::endl; return this; }
};

void main()
{
A *pa = new B;
B *ppa = (B*)pa->func(); //error C2440: 'initializing' : cannot convert from 'A *' to 'B *'
//A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,
ppa->func();
//B *pb = new B;
//B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
//delete pb;
getchar();
}

结果
派生类虚函数返回派生类的指针
这表明
 A *pa = new B; 
// pa其实接受的是类B的指针

所以本身代码这写就是不科学的

感谢你写了这么多,不过你这两段代码想表达的是什么?代码中并没有看出如何解释return this这个现象。最后得出的代码不科学的结论是指虚函数的写法?这种写法只是关于虚函数的一个研究,书上简单的一句话并不简单。

我只是做了几个情况的测试 最后得到的结论就是 我注释里写的A* pa = new B;
这句话 的最终结果是pa 被赋予了一个类B的指针 虽然它被声明为A类的指针

#12


引用 6 楼 maliang351 的回复:
Quote: 引用 3 楼 passion_wu128 的回复:

func()调用的时候只看其对象的虚函数表,是B的就肯定调用B::func,跟返回值有神马关系?


B::func() 函数返回 this 是指针class B的,为什么实际上是A的?


呵呵,你没有搞清楚编译检查和运行检查。
编译器才不管你运行时调用了什么东西呢,pa是A类的指针,它指向的函数就是A类的函数,A类的函数返回值是A*,所以编译器很负责任地告诉你pa->func的返回值就是A*。
佬虚函数,多态什么的,那时运行时才知道的,编译器完全不懂。

举个例子,编译器就像是体育场门口的守卫,它的责任就是所有人凭票入场,比赛开始守卫就会消息,后领导可以随便出入。
于是你装着是领导的样子跟编译器说,我是领导,我要xx。对不起,比赛开始之前编译器只认门票不认人。所以你想xx肯定不行的,不管你是领导还是虚函数,编译器只认指针这张票。

#13



#include "iostream"
using namespace std;
class A
{
public:
virtual A *func() { cout << "A" << endl; return this; }
};

class B : public A
{
public:
virtual B *func() { cout << "B" << endl; return this; }
};

void main()
{
A *pa = new B;
B *ppbb=static_cast<B*>(pa->func());
A *ppa = pa->func(); // 虚函数返回的是A *,但cout B,

B *pb = new B;
B *ppb = pb->func(); // 虚函数返回值是B *,cout B

delete pa;
delete pb;
}

//虚函数需要看指针指向的是什么对象,指向什么对象就指向什么方法



static_cast用法:static_cast < type-id > ( expression ) 
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。

#14


谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

#15


派生类在重写基类的虚函数时,不应修改返回类型。楼主这是返回A*和B*,如果假设基类返回void,而派生类重写时返回bool,编译都不会过。

#16


引用 14 楼 reversesoul 的回复:
谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

#17


引用 16 楼 maliang351 的回复:
Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

#18


引用 9 楼 lineage191 的回复:
Quote: 引用 7 楼 maliang351 的回复:

Quote: 引用 2 楼 lineage191 的回复:

pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

执行的是B::func,return this;自然也是返回的B *,难道不是这样?



执行的是B::func 不一定代表是B调用的,多态的机制是父类的指针进行调用,而调用的函数是子类的虚函数表中的函数,其实调用的还是父类的函数,只不过在虚函数表中用子类函数指针覆盖了父类函数指针所以才会调用到子类的函数,但调用者依然是父类指针,retrun this,this类型当然是A*类型的,只不过这个this指针指向B类型的实例而已,
多态就是以父类指针指向子类实例,所有函数调用都是调用父类的函数,假设子类函数不是虚函数,是无法被A *pa = new B;中pa调用,如果是虚函数,因为虚函数表的机制,子类函数覆盖了父类函数,所以才会调用子类的函数。

在调试的时候,pa->func() 函数中this指针的类型是 B *,this 和 pa具有相同的地址值。

#19


引用 17 楼 reversesoul 的回复:
Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

#20


引用 15 楼 zhuyf87 的回复:
派生类在重写基类的虚函数时,不应修改返回类型。楼主这是返回A*和B*,如果假设基类返回void,而派生类重写时返回bool,编译都不会过。

A *和B *这种返回情况是虚函数的一个特例。当然,正常写代码显然不会这么做。现在只是想解释一下这种想象。

#21


引用 19 楼 maliang351 的回复:
Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

#22


引用 21 楼 reversesoul 的回复:
Quote: 引用 19 楼 maliang351 的回复:

Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

你在子类创建一个父类没有的新函数,然后动态绑定一下看看,如果调用不到,说明他被切去了。

#23


引用 5 楼 lineage191 的回复:
Quote: 引用 2 楼 lineage191 的回复:

pa虽然指向的是一个B的对象,但是pa->func();,调用方是A类型指针,只是指向了B的内存区域而已,调用的时候编译器是不知道B的存在的,所以返回的this是A类型的指针。父转子不是隐式转换所以发生错误,加dynamic_cast应该就可以了(因为指向的是一个B的实例),
虽然两个函数返回类型不一样但,编译器仍识别为重载,这个看一下重载的定义就知道了

此处不是重载,而是override 


确实是override,非overload。运行结果放在那了,为什么还不相信呢。在LZ的例子里,只有返回值的类型不同,但返回值类型是可以转换的,因此算override。若返回值改为基本类型,一个是int,一个是float,则返回值不可以转换,因此导致override失败。

#24


引用 15 楼 zhuyf87 的回复:
派生类在重写基类的虚函数时,不应修改返回类型。楼主这是返回A*和B*,如果假设基类返回void,而派生类重写时返回bool,编译都不会过。


引用 20 楼 maliang351 的回复:
A *和B *这种返回情况是虚函数的一个特例。当然,正常写代码显然不会这么做。现在只是想解释一下这种想象。


请参考23楼。

#25


每个成员函数中都隐藏着一个this的参数,pa->func()函数中返回this,其实这个this是由pa->func()传进去的,pa属于类A,所以this指针也是类A。

#26


引用 22 楼 reversesoul 的回复:
Quote: 引用 21 楼 reversesoul 的回复:

Quote: 引用 19 楼 maliang351 的回复:

Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

你在子类创建一个父类没有的新函数,然后动态绑定一下看看,如果调用不到,说明他被切去了。

切去是在 对象赋值的时候发生的。派生类对象赋值给基类对象,会隐式调用基类的拷贝构造函数或赋值运算符,拷贝构造函数或赋值运算符完成对基类成员的初始化或者赋值,函数一旦调用完毕,对象即为基类。在这种情况下,派生类的派生类部分在对基类进行初始化或赋值时被“切掉”了。
而指针的赋值pa = &b,只是pa指向的位置改变了,pa只是一个指针,哪里来的切去?!
pa所能调用的操作,当然是受其静态类型决定的,这一点并不等价于切去!

#27


引用 26 楼 maliang351 的回复:
Quote: 引用 22 楼 reversesoul 的回复:

Quote: 引用 21 楼 reversesoul 的回复:

Quote: 引用 19 楼 maliang351 的回复:

Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

你在子类创建一个父类没有的新函数,然后动态绑定一下看看,如果调用不到,说明他被切去了。

切去是在 对象赋值的时候发生的。派生类对象赋值给基类对象,会隐式调用基类的拷贝构造函数或赋值运算符,拷贝构造函数或赋值运算符完成对基类成员的初始化或者赋值,函数一旦调用完毕,对象即为基类。在这种情况下,派生类的派生类部分在对基类进行初始化或赋值时被“切掉”了。
而指针的赋值pa = &b,只是pa指向的位置改变了,pa只是一个指针,哪里来的切去?!
pa所能调用的操作,当然是受其静态类型决定的,这一点并不等价于切去!

指向的永远是父类部分,并没有改变。

#28


引用 27 楼 reversesoul 的回复:
Quote: 引用 26 楼 maliang351 的回复:

Quote: 引用 22 楼 reversesoul 的回复:

Quote: 引用 21 楼 reversesoul 的回复:

Quote: 引用 19 楼 maliang351 的回复:

Quote: 引用 17 楼 reversesoul 的回复:

Quote: 引用 16 楼 maliang351 的回复:

Quote: 引用 14 楼 reversesoul 的回复:

谁说返回的是派生类的指针或引用啊?
不论如何,返回的都是基类部分,派生部分被切去了。

那么这个切去是在什么时候发生的?

Father * ptr = new Child();
这时候就发生了

不对,切去行为是指对象的赋值, 
B b;
A a = b;
这种情况下会发生向下转换(切去)。但是指针赋值只是改变了指针的指向,pa指向了new B而已,pa指向的内存仍然保存一个完整的B,这不叫切去。只是说pa所能执行的操作受到了其静态类型的限制(只能指向class A提供的操作)。

跟转换有毛关系。

你在子类创建一个父类没有的新函数,然后动态绑定一下看看,如果调用不到,说明他被切去了。

切去是在 对象赋值的时候发生的。派生类对象赋值给基类对象,会隐式调用基类的拷贝构造函数或赋值运算符,拷贝构造函数或赋值运算符完成对基类成员的初始化或者赋值,函数一旦调用完毕,对象即为基类。在这种情况下,派生类的派生类部分在对基类进行初始化或赋值时被“切掉”了。
而指针的赋值pa = &b,只是pa指向的位置改变了,pa只是一个指针,哪里来的切去?!
pa所能调用的操作,当然是受其静态类型决定的,这一点并不等价于切去!

指向的永远是父类部分,并没有改变。

B b;
A *pa = &b;  // pa指向的是b的内存地址
pa指向的内存,pa能进行的操作,这是两个概念。

#29


它的名词叫“协变”,自己认真google就可以了。
就别乱展开讨论了。