1. c++三大核心功能
(1)封装:提高代码的维护性,遇到问题可以准确定位;
(2)继承:提高代码的复用性,注意不是ctrl+c,ctrl+v,而是不做任何修改或操作源码就能实现代码的复用;
(3)多态:提高代码的扩展性;
2. 代码复用的两种方式
(1)组合(has-a),在一个B类的内部,定义一个A类的实例化成员,这样就可以调用A类里面的成员或者方法;
优点:方便操作,逻辑清晰,安全;
缺点:要在B类中实例化很多其他类的成员,导致占用的内存很大;
#include <iostream>
using namespace std;
class A
{
public:
void print()
{
cout<<"hello world"<<endl;
}
int m_num;
};
class B
{
public:
A a; //组合的方式实现代码复用
void print()
{
a.print();
}
int m_len;
};
int main(int argc, char **argv)
{
B b;
b.print();
return 0;
}
(2)继承(is-a),子类继承父类,或派生类继承基类;注意基类与派生类之间是相互独立的空间,不是共享成员;派生类的实例化对象的大小=基类对象的大小+派生类成员的大小;
覆盖:当派生类拥有与基类相同的属性或者方法时,派生类会覆盖继承过来的属性与方法,空间还是增加的,不会不变;
一共有三种继承方式,实际工程应用中只用public方式:
继承方式 | 基类中的public成员 | 基类中protect成员 | 基类中private成员 |
public公有继承 | 在派生类的内外均可见 | 在派生类内部可见,外部不可见 | 在派生类内外均不可见 |
protect保护继承 | 在派生类的内部可见,外部不可见 | ||
private私有继承 | 在派生类的内部可见,外部不可见 | 在派生类内外均不可见 |
#include <iostream>
using namespace std;
class A
{
public:
void print()
{
cout<<"hello world"<<endl;
}
int m_num;
private:
int m_index;
};
class B : private A
{
public:
void test()
{
cout<<"A::m_num = "<<m_num<<endl; //私有继承,只可以在派生类的内部查看,且只可以查看基类的公有成员;
print(); //基类的公有函数也可以在类内查看;
}
int m_len;
};
int main(int argc, char **argv)
{
B b;
b.test();
return 0;
}
(3)基类与派生类
派生类的构造函数:派生类从基类继承的成员还是要基类的构造函数初始化完成,派生类新增的成员,就在派生类的构造函数中初始化;具体执行顺序如下:
①如果派生类中含有其他类的实例化对象,就是说成员对象,那么构造函数的执行顺序:基类→成员对象→派生类构造函数;析构函数则相反;
②如果基类中没有无参构造函数(例如,只有有参的,那么这是系统提供的默认无参构造函数就失效了,需要自己写一个基类的无参构造函数),那么派生类里所有的构造函数都要显是调用基类的构造函数(例如自定义有参构造函数等);
③若一个派生类继承了多个基类,那么基类中的构造函数执行顺序与其继承顺序相关(即public的前后顺序);
④若要调用内嵌的成员对象的构造函数是,执行顺序按照它们在类中的声明顺序执行;
(4)函数遮蔽
就是指派生类中有与基类重名的函数,相当于重载了基类中的函数方法,系统优先调用派生类自己的函数,如需要调用基类中的函数,需要加作用域限定符;总之就是如需要调用基类的一些函数,包括普通成员函数、拷贝构造函数、移动拷贝构造函数等,都需要显式调用(第一个加作作用域限定符,后两个加显示调用,跟在派生类的对应构造函数后面);
示例:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"A"<<endl;
}
A(int num):m_num(num)
{
cout<<"A int"<<endl;
}
void print()
{
cout<<"hello world"<<endl;
}
~A()
{
cout<<"~A"<<endl;
}
// private:
int m_num;
int m_index;
};
class B : public A
{
public:
B():A() //派生类中的构造函数都要显式调用基类的构造函数;
{
cout<<"B"<<endl;
}
B(int len):m_len(len),A(len) //派生类中的构造函数都要显式调用基类的构造函数;
{
cout<<"B int"<<endl;
}
void print()
{
cout<<"hi world"<<endl;
}
//private:
int m_len;
};
int main(int argc, char **argv)
{
B b1;
B b2(5);
b1.print();
b1.A::print(); //B类中与基类中函数重名,发生了函数遮蔽/函数覆盖,若要调用基类的函数,需要作用于限定符;
cout<<b2.m_num<<endl;
return 0;
}
(5)多继承:在D类访问最原始的基类A中的成员时,容易出现二义性;所以引入了虚继承,这时B和C类里面都有一个续表指针,最终D类在构造函数的时候需要,需要显式调用A类的构造函数;如果B类和C类没有引入虚继承,那么D类访问A类的成员需要作用域限定符,表明走B这边,还是走C这里;文章来源:https://www.toymoban.com/news/detail-463270.html
文章来源地址https://www.toymoban.com/news/detail-463270.html
#include <iostream>
using namespace std;
class A
{
public:
A(int a): m_a(a)
{
cout<<"A"<<endl;
}
int m_a;
};
class B : virtual public A
{
public:
B(int b): m_b(b),A(2)
{
cout<<"B"<<endl;
}
int m_b;
};
class C : virtual public A
{
public:
C(int c): m_c(c),A(3)
{
cout<<"C"<<endl;
}
int m_c;
};
class D : public B, public C
{
public:
D(int d): m_d(d),B(2),C(3),A(7) //B和C都是虚继承,那么D这里需要显式调用A的构造函数
{
cout<<"D"<<endl;
}
int m_d;
};
int main(int argc, char **arg)
{
D d(4);
cout<<d.m_a<<endl; //这里就不用作用域限定符了
cout<<d.B::m_a<<endl; //如果B/C没有采用虚继承,则需要作用域限定符
return 0;
}
到了这里,关于c++—继承、继承方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!