父子间的同名冲突
首先来看一段代码:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
};
class Child : public Parent
{
public:
int mi;
};
int main()
{
Child c;
//这里的mi是Parent中的还是Child中的呢?
c.mi = 100;
return 0;
}
编译通过,说明子类可以定义和父类相同的同名成员。
- 子类可以定义父类中的同名成员
- 子类中的成员将隐藏父类中的同名成员
- 父类中的同名成员依然存在于子类中
- 通过作用域分辨符( : : )访问父类中的同名成员
- 访问父类中的同名成员
Child c;
//子类中的mi
c.mi = 100;
//父类中的mi
c.Parent::mi = 1000;
再来看一个例子:
#include <iostream>
#include <string>
using namespace std;
//定义一个命名空间A
namespace A
{
int g_i = 0;
}
//定义一个命名空间B
namespace B
{
int g_i = 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;
//向子类的mi成员赋值100
c.mi = 100;
//通过作用域向父类的mi成员赋值1000
c.Parent::mi = 1000;
//打印子类中mi的地址
cout << "&c.mi = " << &c.mi << endl;
//打印子类中mi的内容
cout << "c.mi = " << c.mi << endl;
//打印父类中mi的地址
cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
//打印父类中mi的内容
cout << "c.Parent::mi = " << c.Parent::mi << endl;
return 0;
}
输出结果为:
Parent() : &mi = 0x7fff57f57a90
Child() : &mi = 0x7fff57f57a94
&c.mi = 0x7fff57f57a94
c.mi = 100
&c.Parent::mi = 0x7fff57f57a90
c.Parent::mi = 1000
- 类中的成员函数可以进行重载
- 重载函数的本质为多个不同的函数
- 函数名和参数列表是唯一的标识
- 函数重载必须发生在同一个作用域中
- 所以父子之间的同名成员不构成重载
比如像这样:
class Parent
{
public:
int mi;
void add(int v)
{
mi += v;
}
void add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mi;
void add(int v)
{
mi += v;
}
void add(int a, int b)
{
mi += (a + b);
}
void add(int x, int y, int z)
{
mi += (x + y + z);
}
};
代码 Parent类 和 Child类 中有同名的函数add ,但是两个类之间不构成重载,只有Parent类中多个add函数构成重载。
- 子类中的函数将隐藏父类的同名函数
- 子类无法重载父类中的成员函数
- 使用作用域分辨符访问父类中的同名函数
- 子类可以定义父类中完全相同的成员函数
父子间的赋值兼容
- 子类对象可以当作父类对象使用(兼容性)
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象
- 父类引用可以直接引用子类对象
举个例子:
#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;
//子类对象可以直接初始化父类对象
Parent p1(c);
//父类引用可以直接引用子类对象
Parent& rp = c;
//父类指针可以直接指向子类对象
Parent* pp = &c;
return 0;
}
在main函数中进行上述几条的操作都没有出现编译出错。现在进行这样操作:
rp.mi = 100;
rp.add(5);
rp.add(10, 10);
发现可以编译通过,并没有出现同名覆盖的问题。但是如果这样操作:
pp->mv = 1000;
pp->add(1, 10, 100);
运行以后就会报错,报错信息如下:
48-1.cpp:51:10: error: no member named 'mv' in 'Parent'
pp->mv = 1000;
~~ ^
48-1.cpp:52:10: error: no matching member function for call to 'add'
pp->add(1, 10, 100);
~~~~^~~
48-1.cpp:16:10: note: candidate function not viable: requires 2 arguments, but 3
were provided
void add(int a, int b)
^
48-1.cpp:11:10: note: candidate function not viable: requires single argument
'i', but 3 arguments were provided
void add(int i)
^
2 errors generated.
信息提示没有找到带有3个参数的add函数。为什么呢?
- 当使用父类指针(引用)指向子类对象时
- 子类对象退化为父类对象
- 只能访问父类中定义的成员
- 可以直接访问被子类覆盖的同名成员
特殊的同名函数
- 子类中可以冲定义父类中已经存在的成员函数
- 这种冲定义发生在继承中,叫做函数重写
- 函数重写是同名覆盖的一种特殊情况
例如:
class Parent
{
public:
void print()
{
cout << "I'm Parent." << endl;
}
};
//函数重写
class Child : public Parent
{
public:
void print()
{
cout << "I'm Child" << endl;
}
};
假如函数重写和赋值兼容同时出现呢? 就像这样:
#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->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;
}
预期输出是 I'm Parent.
和 I'm Child.
。但是实际输出:
I'm Parent.
I'm Parent.
- 问题分析
- 编译期间,编译器只能根据指针的类型判断所指向的对象
- 根据赋值兼容,编译器认为父类指针指向的是父类对象
- 因此,编译结果只可能是调用父类中定义的同名函数
在编译 void how_to_print(Parent* p)
这个函数时,编译器不可能知道指针p究竟指向了什么,但是编译器没有理由报错。于是,编译器认为最安全的做法是调用父类的print函数,因为父类和子类肯定都有相同的print函数。
多态的概念和意义
- 函数重写回顾
- 父类中被重写的函数依然会继承给子类
- 子类中重写的函数将覆盖父类中的函数
- 通过作用域分辨符( : : )可以访问到父类中的函数
就像这样:
Child c;
Parent* p = &c;
c.Parent::print(); //从父类中继承
c.print(); //从子类中重写
p->print(); //父类中定义
虽然程序逻辑是这样,但并不是我们所期望的。面向对象中期望的行为:
- 根据
实际的对象类型
判断如何调用重写函数 -
父类指针(引用)
指向-
父类对象
则调用父类
中定义的函数 -
子类对象
则调用子类
中定义的重写函数
-
这里就引出了面向对象中的 多态
的概念:
- 根据实际的
对象类型决定函数调用
的具体目标 - 同样的
调用语句
在实际运行时有多种不同的表现形态
例如:
p->print();
p指向父类对象时,会执行
void print()
{
cout << "I'm Parent" << end;
}
p指向子类对象时,会执行
void print()
{
cout << "I'm Child" << endl;
}
- C++语言直接支持多态的概念
- 通过使用
virtual
关键字对多态进行支持 - 被
virtual
声明的函数被重写后具有多态特性 - 被
virtual
声明的函数叫做虚函数
- 通过使用
举个例子:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
//用 virtual 关键字修饰,则具有多态特性
virtual void print()
{
cout << "I'm Parent." << endl;
}
};
class Child : public Parent
{
public:
void print()
{
cout << "I'm Child." << endl;
}
};
void how_to_print(Parent* p)
{
// 展现多态的行为
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;
}
执行结果为:
I'm Parent.
I'm Child.
- 多态的意义
- 在程序运行过程中展现出动态的特性
- 函数重写必须多态实现,否则没有意义
- 多态是面向对象组件化程序设计的基础特性
静态联编和动态联编
- 理论中的概念
- 静态联编
- 在程序的编译期间就能确定具体的函数调用。 如:函数重载
- 动态联编
- 在程序实际运行后才能确定具体的函数调用。如:函数重写
- 静态联编
举个例子:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
//函数重载 并且用 virtual 关键字修饰
virtual void func()
{
cout << "void func()" << endl;
}
//函数重载 并且用 virtual 关键字修饰
virtual void func(int i)
{
cout << "void func(int i) : " << i << endl;
}
//函数重载 并且用 virtual 关键字修饰
virtual void func(int i, int j)
{
cout << "void func(int i, int j) : " << "(" << i << ", " << j << ")" << endl;
}
};
class Child : public Parent
{
public:
//函数重载
void func(int i, int j)
{
cout << "void func(int i, int j) : " << i + j << endl;
}
//函数重载
void func(int i, int j, int k)
{
cout << "void func(int i, int j, int k) : " << i + j + k << endl;
}
};
void run(Parent* p)
{
p->func(1, 2); // 展现多态的特性
// 动态联编
}
int main()
{
Parent p;
p.func(); // 静态联编
p.func(1); // 静态联编
p.func(1, 2); // 静态联编
cout << endl;
Child c;
c.func(1, 2); // 静态联编
cout << endl;
run(&p);
run(&c);
return 0;
}
运行结果为:
void func()
void func(int i) : 1
void func(int i, int j) : (1, 2)
void func(int i, int j) : 3
void func(int i, int j) : (1, 2)
void func(int i, int j) : 3
小结
- 子类可以定义父类的
同名成员
,定义时子类中的成员将隐藏
父类中的同名成员
-
子类和父类
中的函数不能构成重载关系
- 使用
作用域分辨符
可以访问父类中的同名成员
-
子类对象
可以当做父类对象
使用 -
父类指针
可以正确的指向子类对象
-
父类引用
可以正确的代表子类对象
- 子类中可以重写父类中的
成员函数
- 函数重写只可能发生在
父类与子类
之间 - 多态是根据
实际对象的类型
确定调用的具体函数
-
virtual关键字
是C++中支持多态
的唯一方式 - 被重写的
虚函数
可表现出多态的特性
网友评论