【C++深度解析】33、子类定义父类同名的函数或变量
1 子类定义父类同名成员
1.1 子类定义父类同名变量
子类中可以定义父类的同名成员
- 子类中的成员将隐藏父类中的同名成员
- 父类中的同名成员依然存在于子类中,需要用作用域分辨符(::)访问父类同名成员
// 33-1.cpp
#include<iostream>
using namespace std;
namespace A
{
int g = 0;
}
namespace B
{
int g = 1;
}
class Parent
{
public:
int mi;
Parent()
{
cout << "Parent() : " << "&mi = " << &mi << endl;
}
};
class Child : public Parent
{
public:
int mi;
Child()
{
cout << "Child() : " << "&mi" << &mi << endl;
}
};
int main()
{
Child c;
c.mi = 100;
c.Parent::mi = 1000;
cout << "&c.mi = " << &c.mi << endl;
cout << "c.mi = " << c.mi << endl;
cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl;
return 0;
}
类 Parent 和类 Child 中都定义了变量 mi,Child 类中隐藏了父类中的 mi 变量,我们如果想访问父类中的变量 mi,需要使用作用域分辨符(::)来访问。
父类与子类中同名变量的作用域不同,不会发生冲突,就像命名空间 A 和 B 中都有变量 g,也不会冲突,因为他们的作用域不同。
编译运行
$ g++ 33-1.cpp -o 33-1
$ ./33-1
Parent() : &mi = 0x7ffe80cc1340
Child() : &mi0x7ffe80cc1344
&c.mi = 0x7ffe80cc1344
c.mi = 100
&c.Parent::mi = 0x7ffe80cc1340
c.Parent::mi = 1000
可以看到 c.mi 访问的是子类的成员,c.Parent::mi 访问的是父类的成员。
1.2 子类定义父类同名函数
- 子类中的函数将隐藏父类的同名函数
- 子类无法重载父类中的成员变量
- 使用作用域分辨符访问父类中的同名函数
- 子类可以定义父类中完全相同的成员函数
// 33-2.cpp
#include<iostream>
using namespace std;
class Parent
{
public:
int mi;
void add(int a)
{
mi += a;
}
void add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mi;
void add(int a, int b, int c)
{
mi += (a + b + c);
}
};
int main()
{
Child c;
c.mi = 10;
c.Parent::mi = 100;
c.Parent::add(1);
c.Parent::add(2, 3);
c.add(4, 5, 6);
cout << "c.mi = " << c.mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl;
return 0;
}
Parent 和 Child 中都定义了 add 函数,Child 中的 add 函数会隐藏 Parent 中的 add 函数,他们不构成重载关系
编译运行:
$ g++ 33-2.cpp -o 33-2
$ ./33-2
c.mi = 25
c.Parent::mi = 106
Parent 中的 mi 被加上 1+2+3,等于106;Child 中的 mi 被加上 4 + 5 + 6,等于 25。
如果将第 31,32 行的代码改为 c.add(1); c.add(2, 3); 重新编译,会出错,因为子类掩盖父类中的同名函数,Child 对象找不到对应的函数。
如图,编译器提示,只能找到函数 void add(int a, int b, int c),找不到 void add(int a) 和 void add(int a, int b)
2 父子间的赋值
2.1 父子间的赋值兼容
子类对象可以当父类对象使用
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象
- 父类引用可以直接引用子类对象
当使用父类的指针(引用)指向子类对象时,子类对象退化为父类对象,只能访问父类中的成员,可以直接访问被子类覆盖的同名成员。
编程实验:子类对象的兼容性
// 33-3.cpp
#include<iostream>
using namespace std;
class Parent
{
public:
int mi;
void add(int a)
{
mi += a;
}
void add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mv;
void add(int a, int b, int c)
{
mv += (a + b + c);
}
};
int main()
{
Parent p;
Child c;
p = c; // 子类对象赋值给父类对象
Parent p1(c); // 子类对象初始化父类对象
Parent& rp = c; // 父类引用引用子类对象
Parent* pp = &c; // 父类指针指向子类对象
rp.mi = 100;
rp.add(1);
rp.add(2, 3);
// pp->mv = 1000;
// pp->add(4, 5, 6);
return 0;
}
当使用父类的指针(引用)指向子类对象时,子类对象退化为父类对象,第 40,41 行,pp 已经退化为父类对象,无法访问子类成员。rp 也同样退化为父类对象,可直接访问父类成员,上述代码可编译运行。
2.2 函数重写
函数重写:子类中重定义父类中已经存在的成员函数
当函数重写碰见父子间的赋值兼容会发生什么呢,下面进行编程实验进行验证。
// 33-4.cpp
#include<iostream>
using namespace std;
class Parent
{
public:
int mi;
void add(int a)
{
mi += a;
}
void add(int a, int b)
{
mi += (a + b);
}
void print()
{
cout << "I am Parent" << endl;
}
};
class Child : public Parent
{
public:
int mv;
int add(int a, int b, int c)
{
mv += (a + b + c);
}
void print()
{
cout << "I am Child" << endl;
}
};
void how_to_print(Parent* p)
{
p->print();
}
int main()
{
Parent p;
Child c;
how_to_print(&p);
how_to_print(&c);
return 0;
}
父子类中都定义了 void print() 函数,将父类对象作为参数传入函数 void how_to_print(Parent* p),相当于父类指针分别指向父类对象和子类对象。父类指针指向父类对象,自然调用父类成员函数;父类指针指向子类对象,时,子类对象退化为父类对象,调用的还是父类成员函数,两次打印的都是 I am Parent,真的是这样吗?
编译运行:
$ g++ 33-4.cpp -o 33-4
$ ./33-4
I am Parent
I am Parent
和我们分析的一样
3 小结
1、子类可以定义父类的同名成员,子类掩盖父类同名成员
2、子类和父类的函数不构成重载关系
3、使用作用域分辨符访问父类同名成员
4、子类对象可以当作父类对象使用(赋值兼容)
5、父类指针、引用可以指向子类对象(退化为父类对象)
6、子类可以重写父类成员函数