注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。
测试环境:Ubuntu 10.10
GCC版本:9.2.0
一、父子间的赋值兼容
1)子类对象可以当作父类对象使用(兼容性)
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象(子类退化为父类)
- 父类引用可以直接引用子类对象(子类退化为父类)
编程实验
子类对象的兼容性
48-1.cpp
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
cout << "void add(int i) : mi = " << mi << endl;
}
void add(int a, int b)
{
mi += (a + b);
cout << "void add(int a, int b) : mi = " << mi << endl;
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
cout << "void add(int x, int y, int z) : mi = " << mi << endl;
}
};
int main()
{
Parent p;
Child c;
p = c; //子类对象c,赋值给父类对象p,体现兼容性
Parent p1(c); //子类对象c初始化父类p1
Parent& rp = c; //父类引用子类,子类对象退化为父类
Parent* pp = &c; //父类指针指向子类,子类对退化为父类
rp.mi = 100; //
rp.add(5); // 没有发生同名覆盖,因为子类退化为父类
rp.add(10, 10); // 没有发生同名覆盖,因为子类退化为父类
return 0;
}
操作:
1. g++ 48-1.cpp -o 48-1.out编译正确。打印结果:
void add(int i) : mi = 105
void add(int a, int b) : mi = 125
父类指针或者引用指向父类,调用同名函数时都显示的是父类中的
2. 修改代码:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
cout << "void add(int i) : mi = " << mi << endl;
}
void add(int a, int b)
{
mi += (a + b);
cout << "void add(int a, int b) : mi = " << mi << endl;
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
cout << "void add(int x, int y, int z) : mi = " << mi << endl;
}
};
int main()
{
Parent p;
Child c;
c.add(5);
p = c; //子类对象c,赋值给父类对象p,体现兼容性
Parent p1(c); //子类对象c初始化父类p1
Parent& rp = c; //父类引用子类,子类对象退化为父类
Parent* pp = &c; //父类指针指向子类,子类对退化为父类
rp.mi = 100; //
rp.add(5); // 没有发生同名覆盖,因为子类退化为父类
rp.add(10, 10); // 没有发生同名覆盖,因为子类退化为父类
return 0;
}
g++ 48-1.cpp -o 48-1.out编译错误:
root@ubuntu:/home/gxn/DT/cpp/48# g++ 48-1.cpp -o 48-1.out
48-1.cpp: In function ‘int main()’:
48-1.cpp:44:9: error: no matching function for call to ‘Child::add(int)’
c.add(5);
^
48-1.cpp:44:9: note: candidate is:
48-1.cpp:31:7: note: void Child::add(int, int, int)
void add(int x, int y, int z)
^
错误:没有匹配的函数'Child::add(int)'
c.add(5);
提示:候选函数是:
提示:void Child::add(int, int, int)
void add(int x, int y, int z)
分析:
子类访问继承自父类的同名函数,编译器提示找不到对应函数,继承时发生同名覆盖,找不到继承自父类的同名函数。
解决办法:作用域分辨符(::)
c.Parent::add(5);
3. 测试父类指针是否能访问子类,修改代码如下:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
}
};
int main()
{
Parent p;
Child c;
p = c; //子类对象c,赋值给父类对象p,体现兼容性
Parent p1(c); //子类对象c初始化父类p1
Parent& rp = c; //父类引用子类,子类对象退化为父类
Parent* pp = &c; //父类指针指向子类,子类对退化为父类
rp.mi = 100; //
rp.add(5); // 没有发生同名覆盖,因为子类退化为父类
rp.add(10, 10); // 没有发生同名覆盖,因为子类退化为父类
/* 为什么编译不过? */
pp->mv = 1000; //不能访问子类
pp->add(1, 10, 100); //不能访问子类
return 0;
}
g++ 48-1.cpp -o 48-1.out编译错误:
48-1.cpp: In function ‘int main()’:
48-1.cpp:49:6: error: ‘class Parent’ has no member named ‘mv’
pp->mv = 1000;
^
错误:'class Parent'没有成员'mv'
48-1.cpp:50:20: error: no matching function for call to ‘Parent::add(int, int, int)’
pp->add(1, 10, 100);
^
错误:没有匹配的函数'Parent::add(int, int, int)'
48-1.cpp:50:20: note: candidates are:
48-1.cpp:11:7: note: void Parent::add(int)
void add(int i)
^
48-1.cpp:11:7: note: candidate expects 1 argument, 3 provided
48-1.cpp:16:7: note: void Parent::add(int, int)
void add(int a, int b)
^
48-1.cpp:16:7: note: candidate expects 2 arguments, 3 provided
分析:
pp本意想访问子类成员,但是编译器不允许这种行为发生。综合之前打印结果:子类赋值给父类,只能通过父类引用对象或指针访问父类的成员。
2)当使用父类指针(引用)指向子类对象时
- 子类对象退化为父类对象
- 只能访问父类中定义的成员
- 可以直接访问被子类覆盖的同名成员
二、特殊的同名函数
1)子类中可以重定义父类中已经存在的成员函数
2)这种重定义发生在继承中,叫做函数重写
3)函数重写是同名覆盖的一种特殊情况
父类和子类中都有print() ,这叫函数函数重写。
三、思考
当函数重写遇上赋值兼容会发生什么?
编程实验
赋值兼容的问题
48-2.cpp
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
void print() //函数重写
{
cout << "I'm Parent." << endl;
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
}
void print() //函数重写
{
cout << "I'm Child." << endl;
}
};
void how_to_print(Parent* p) //p指向子类,子类Child退化为父类Parent
{
p->print();
}
int main()
{
Parent p;
Child c;
c.print(); //I'm Child.
p.print(): //I'm Parent.各自打印自己,两个print不冲突
return 0;
}
操作:
1) g++ 48-2.cpp -o 48-2.out编译正确,打印结果:
I'm Child.
I'm Parent.
分析:
函数重写时,通过对象名都能访问各自函数!
2) 注释掉子类的print()函数,代码如下:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
void print() //函数重写
{
cout << "I'm Parent." << endl;
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
}
};
void how_to_print(Parent* p) //p指向子类,子类Child退化为父类Parent
{
p->print();
}
int main()
{
Parent p;
Child c;
c.print();
p.print():
return 0;
}
g++ 48-2.cpp -o 48-2.out编译正确,打印结果:
I'm Parent.
I'm Parent.
结论:注释掉子类同名函数,父类和子类访问的都是父类print()
如果注释父类的print函数,子类编译正常,父类访问错误,因为函数没有定义!
3) 修改代码:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
void print() //函数重写
{
cout << "I'm Parent." << endl;
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
}
void print() //函数重写
{
cout << "I'm Child." << endl;
}
};
void how_to_print(Parent* p) //p指向子类,子类Child退化为父类Parent
{
p->print();
}
int main()
{
Parent p;
Child c;
how_to_print(&p); // Expected to print: I'm Parent.
how_to_print(&c); // Expected to print: I'm Child.
return 0;
}
g++ 48-2.cpp -o 48-2.out编译正确,打印结果:
I'm Parent.
I'm Parent.
分析:
期望父类p的print打印'I'm Parent.',子类c的print打印'I'm Child.'。但是都打印了'I,m Parent.'。
这是为什么?如下分析。
四、父子间的赋值兼容(重点理解)
1)问题分析
- 编译期间,编译器只能根据指针的类型判断所指向的对象
- 根据赋值兼容,编译器认为父类指针指向的是父类对象
- 因此,编译结果只可能是调用父类中定义的同名函数
void how_to_print(Parent* p)
{
p->print(); //编译器这里解释为父类的print函数
} //子类虽然重定义了父类中同名函数,但是这里统一默认为父类的print函数!(重点理解)
在编译这个函数的时候,编译器不可能知道指针p究竟指向了什么。但是编译器没有理由报错。于是,编译器认为最安全的做法是调用父类的print函数,因为父类和子类肯定有相同print函数。
五、问题
编译器的处理方法是合理的吗?是期望的吗?
合理的,不是我们期望的。
小结
1)子类对象可以当作父类对象使用(赋值兼容)
2)父类指针可以正确的指向子类对象
3)父类引用可以正确的代表子类对象
4)子类中可以重写父类中的成员函数