31 CPP类的继承
上面第2点说明下,如果手工调用派生类的析构函数,也会调用基类的析构函数。
#include <iostream>
using namespace std;
//操作内存修改私有属性
class Person {
int m_age;
int m_score;
public:
string m_name;
Person(string name, int age) {
m_name = name;
m_age = age;
m_score = 0;
}
void show() const {
cout << m_name << " " << m_age << " " << m_score << endl;
}
};
void test() {
Person p("hello", 22);
p.show();
//直接操作内存来修改private属性 m_age
*((int *) &p) = 108;
p.show();
//直接操作内存来修改private属性 m_score
*((int *) &p + 1) = 100;
p.show();
}
int main() {
test();
return 0;
}
hello 22 0
hello 108 0
hello 108 100
#include <iostream>
using namespace std;
class A
{
private:
int m_b;
public:
int m_a;
A() : m_a(0), m_b(0)
{
cout << "调用了基类的默认构造函数A()." << endl;
}
A(int a, int b) : m_a(a), m_b(b)
{
cout << "调用了基类的构造函数A(int a,int b)" << endl;
}
A(const A &a) : m_a(a.m_a), m_b(a.m_b)
{
cout << "调用了基类的拷贝构造函数A(const A &a)" << endl;
}
~A()
{
cout << "调用了基类的析构函数" << endl;
}
void showA()
{
cout << "m_a=" << m_a << " m_b" << m_b << endl;
}
};
class B : public A
{
public:
int m_c;
B() : m_c(0), A() //派生类的默认构造函数,指明用基类的默认构造函数(不指明也无所谓)
{
cout << "调用了派生类的默认构造函数" << endl;
}
B(int a, int b, int c) : m_c(c), A(a, b) //指明用基类的两个参数的构造函数
{
cout << "调用了B的构造函数B(int a, int b, int c)" << endl;
}
B(const A &a, int c) : A(a), m_c(c) //指明用基类的拷贝构造函数
{
cout << "调用了派生类的构造函数B(cosnt A &a,int c)" << endl;
}
//显示派生类 B的全部的成员
void showB()
{
cout << "m_c=" << m_c << endl;
}
};
void test()
{
B b1; //调用基类磨人的构造函数
b1.showA();
b1.showB();
B b2(1, 2, 3);
b2.showA();
b2.showB();
A a(12, 22);
B b3(a, 23);
b3.showA();
b3.showB();
}
int main()
{
test();
return 0;
}
调用了基类的默认构造函数A().
调用了派生类的默认构造函数
m_a=0 m_b0
m_c=0
调用了基类的构造函数A(int a,int b)
调用了B的构造函数B(int a, int b, int c)
m_a=1 m_b2
m_c=3
调用了基类的构造函数A(int a,int b)
调用了基类的拷贝构造函数A(const A &a)
调用了派生类的构造函数B(cosnt A &a,int c)
m_a=12 m_b22
m_c=23
调用了基类的析构函数
调用了基类的析构函数
调用了基类的析构函数
调用了基类的析构函数
名字遮蔽是表象,类作用域是本质
B继承A
B b;
A *a=&b;
在C++中,数据类型决定了操作数据的方法,类似自定义的数据类型,类中有什么成员也属于操作数据的方法。
这里a是A类的指针,不管它指向谁,只会按A类的方法来操作数据。
就比如下面的代码,i是整型变量,a是类的指针,语法没有问题,编译不会报错,不过整型变量和A类的内存模型不匹配,运行报错。
#include "iostream"
using namespace std;
class A
{
int m_b;
public:
int m_a;
void setb(int b)
{
m_b = b;
}
void show()
{
cout << "m_a=" << m_a << ",m_b=" << m_b << endl;
}
};
void test()
{
int i;
A *a = (A *)&i;
a->m_a = 111;
a->setb(11);
a->show();
}
int main()
{
test();
return 0;
}
注意:
1 基类指针或引用只能调用基类的方法,不能调用派生类的方法。
2 可以用派生类构造基类
3 如果函数的形参是基类,实参可以用派生类
4 C++要求指针和引用类型与赋给的类型匹配,这一规则对继承来说是例外。但是,这种例外只是单向的,不可以将基类对象和地址赋给派生类引用和指针。
菱形继承
#include "iostream"
using namespace std;
class A
{
public:
int m_a;
};
class B : public A
{
};
class C : public A
{
};
class D : public B, public C
{
};
void test()
{
D d;
cout << "sizeof(d)=" << sizeof(d) << endl; //两个m_a成员,所以是8字节
//如果直接访问m_a成员,将报错
// d.m_a;//D::m_a" 不明确
d.B::m_a = 100;
d.C::m_a = 200;
cout << "B::m_a的地址是:" << &d.B::m_a << "值是:" << d.B::m_a << endl;
cout << "C::m_a的地址是:" << &d.C::m_a << "值是:" << d.C::m_a << endl;
//菱形继承存在两个问题:数据冗余和名称的二义性,为了解决这两个问题,C++引入虚继承技术
}
int main()
{
test();
return 0;
}
sizeof(d)=8
B::m_a的地址是:0x61fde8值是:100
C::m_a的地址是:0x61fdec值是:200
菱形继承存在两个问题:数据冗余和名称的二义性,为了解决这两个问题,C++引入虚继承技术
#include "iostream"
using namespace std;
class A
{
public:
int m_a;
};
class B : virtual public A
{
};
class C : virtual public A
{
};
class D : public B, public C
{
};
void test()
{
D d;
cout << "sizeof(d)=" << sizeof(d) << endl; //两个m_a成员,所以是8字节
//如果直接访问m_a成员,将报错
// d.m_a;//D::m_a" 不明确
// d.B::m_a = 100;
// d.C::m_a = 200;
d.m_a = 300;
cout << "B::m_a的地址是:" << &d.B::m_a << "值是:" << d.B::m_a << endl;
cout << "C::m_a的地址是:" << &d.C::m_a << "值是:" << d.C::m_a << endl;
//菱形继承存在两个问题:数据冗余和名称的二义性,为了解决这两个问题,C++引入虚继承技术
}
int main()
{
test();
return 0;
}
// sizeof(d)=24
// B::m_a的地址是:0x61fde0值是:300
// C::m_a的地址是:0x61fde0值是:300