继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
类和类之间的关系有三种,分别是:包含关系、使用关系和继承关系。下面主要来谈谈继承。
在C++里面一共有三种访问控制方式, 分别为:private(私有)、protected(保护)和public(共有)
private 指的是只能在本类中使用的属性或者方法
protected 指的是可以在可以在本类和子类中使用的属性或者方法
public 指的是可以在类外部和类内部使用
使用这三种访问控制方式符来继承分为三种继承方式,分别为:
private -- 私有继承
protected -- 保护继承
public -- 公有继承
子类继承了父类中除了构造和析构之外的所有属性和方法, 但是这些属性和方法并不是都可以访问的,
哪些东西能访问, 哪些东西不能访问就是由继承方式来决定的。
具体如下:图。
如果是public继承, 那么子类从父类那里继承过来的属性和方法访问权限不改变, 即
如果在父类里面时private, 子类中还是private; 其他两种也是如此
如果是protected继承, 那么子类从父类那里继承过来的属性和方法访问权限发生变化,
原来在父类里面是public, 在子类中改为protected
原来在父类里面是protected,在子类中仍为protected
原来在父类里面时private, 在子类中仍为private
如果是private继承, 那么无论在父类中是什么, 在子类中一律改为private
例子如下:
子类对象可以当做父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
赋值兼容性原则:
第一层含义
1-1, 基类指针或者引用可以指向 子类对象。
1-2, 基类指针做函数参数
1-3, 基类引用做函数参数
第二层含义
可以让父类对象初始化子类对象
子类就是一种特殊的父类
举例:(源码)
看清楚, 是继承, 不是多态
还有继承与组合同时存在时构造函数和析构函数的调用顺序
总结:继承时, 先执行父类的构造函数, 再执行子类的构造函数。 先执行子类的析构函数在执行父类的析构函数
1, static 遵守派生类的访问控制规则
2, int A::a = 10; 这句话的意思是 初始化static 变量 a, 并且 要求C++编译器请求操作系统给它分配内存, 这句话很重要
3, 注意:除了单例设计模式之外, 其余情况下 一般构造函数一般不用 private(不写默认private)
这种情况下没问题, 但是工程中一般不用多继承, 为什么呢?
类和类之间的关系有三种,分别是:包含关系、使用关系和继承关系。下面主要来谈谈继承。
一: 继承概述
class 派生类名: 访问控制 基类名
{
}
什么是访问控制?在C++里面一共有三种访问控制方式, 分别为:private(私有)、protected(保护)和public(共有)
private 指的是只能在本类中使用的属性或者方法
protected 指的是可以在可以在本类和子类中使用的属性或者方法
public 指的是可以在类外部和类内部使用
使用这三种访问控制方式符来继承分为三种继承方式,分别为:
private -- 私有继承
protected -- 保护继承
public -- 公有继承
子类继承了父类中除了构造和析构之外的所有属性和方法, 但是这些属性和方法并不是都可以访问的,
哪些东西能访问, 哪些东西不能访问就是由继承方式来决定的。
具体如下:图。
如果是public继承, 那么子类从父类那里继承过来的属性和方法访问权限不改变, 即
如果在父类里面时private, 子类中还是private; 其他两种也是如此
如果是protected继承, 那么子类从父类那里继承过来的属性和方法访问权限发生变化,
原来在父类里面是public, 在子类中改为protected
原来在父类里面是protected,在子类中仍为protected
原来在父类里面时private, 在子类中仍为private
如果是private继承, 那么无论在父类中是什么, 在子类中一律改为private
如果继承多个则用逗号隔开。
例子如下:
# include <iostream>
using namespace std;
class Test
{
private:
int b;
public:
int a;
protected :
int c;
};
// 公有继承
class Test2 : public Test
{
public:
void useVar()
{
a = 0;
// b = 0;//error
c = 0;
}
};
int main01(void)
{
Test t1;
t1.a = 10;
// t1.b = 10; // error
// t1.c = 10; // error
system("pause");
return 0;
}
// 父类中的属性和方法在派生类中全部是private
class Test3 :private Test
{
public:
void useVar()
{
a = 0;// ok
// b = 0;// error
c = 0;// ok
}
};
int main02(void)
{
Test3 t1;
// t1.a = 10; // error
// t1.b = 10; // error
// t1.c = 10; // error
system("pause");
return 0;
}
class Test4 :protected Test
{
public:
void useVar()
{
a = 0;// ok
// b = 0;// error
c = 0;// ok
}
};
int main(void)
{
Test4 t1;
// t1.a = 10; // error
// t1.b = 10; // error
// t1.c = 10; // error
system("pause");
return 0;
}
如果为private继承或者protected, 那么在类的外部都是不可用的
二:兼容性原则
主要包括以下几种情况:子类对象可以当做父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
赋值兼容性原则:
第一层含义
1-1, 基类指针或者引用可以指向 子类对象。
1-2, 基类指针做函数参数
1-3, 基类引用做函数参数
第二层含义
可以让父类对象初始化子类对象
子类就是一种特殊的父类
举例:(源码)
看清楚, 是继承, 不是多态
# include <iostream>
using namespace std;
class Text
{
public:
void printP()
{
cout << " 父类" << endl;
}
private:
int a;
};
class AdvText :public Text
{
public:
void printC()
{
cout << " 子类" << endl;
}
private:
int c;
};
void display(Text * base)
{
base->printP();
}
void display2(Text & base)
{
base.printP();
}
int main(void)
{
Text t1;
t1.printP();
AdvText a1;
a1.printC();
a1.printP();
// 1-1, 基类指针或者引用可以指向 子类对象。
Text *pt = NULL;
pt = &a1;
pt->printP();
// 1-2, 指针做函数参数
display(&t1);
display(&a1);
// 1-3, 引用做函数参数
display2(t1);
display2(a1);
// 第二层含义
// 可以让父类对象初始化子类对象
// 子类就是一种特殊的父类
Text t3 = a1;
system("pause");
return 0;
}
/*
在vs2017中输出结果为:
父类
子类
父类
父类
父类
父类
父类
父类
请按任意键继续. . .
*/
三、构造函数和析构函数
主要是谈谈基类的构造函数析构函数与子类的构造函数析构函数的调用顺序。还有继承与组合同时存在时构造函数和析构函数的调用顺序
举例:继承构造函数与析构函数执行顺序
# include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "父类的构造函数" << endl;
}
~A()
{
cout << "父类的析构函数" << endl;
}
void print()
{
cout << "fasd" << endl;
}
};
class B:public A
{
public:
B()
{
cout << "子类的构造函数" << endl;
}
~B()
{
cout << "子类的析构函数" << endl;
}
};
int main(void)
{
B b;
// b.print();
return 0;
}
/*
在vs2017中输出结果为:
父类的构造函数
子类的构造函数
子类的析构函数
父类的析构函数
请按任意键继续. . .
*/
总结:继承时, 先执行父类的构造函数, 再执行子类的构造函数。 先执行子类的析构函数在执行父类的析构函数
举例:继承与组合一起出现时构造函数与析构函数的执行顺序
# include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "父类的构造函数" << endl;
}
~A()
{
cout << "父类的析构函数" << endl;
}
void print()
{
cout << "fasd" << endl;
}
};
class C
{
public:
C()
{
cout << "C类的构造函数" << endl;
}
~C()
{
cout << "C类的析构函数" << endl;
}
};
class B :public A
{
public:
B():c1()
{
cout << "子类的构造函数" << endl;
}
~B()
{
cout << "子类的析构函数" << endl;
}
private:
C c1;
};
int main(void)
{
B b;
return 0;
}
/*
在vs2017中输出结果为:
父类的构造函数
C类的构造函数
子类的构造函数
子类的析构函数
C类的析构函数
父类的析构函数
请按任意键继续. . .
*/
结论:先执行父类的构造函数,再执行组合类的构造函数,再执行子类的构造函数。 析构函数执行顺苏与此相反
四、继承中的同名变量和同名函数
举例:
# include <iostream>
using namespace std;
class TaiShan
{
public:
int heigh = 1545;
void print()
{
cout << "黄山黄山, 我是泰山" << endl;
}
};
class HuangShan :public TaiShan
{
public:
int heigh = 1864;
void print()
{
cout << "泰山泰山, 我是黄山" << endl;;
}
};
int main(void)
{
HuangShan hs;
hs.print(); // 泰山泰山, 我是黄山
cout << "黄山高度heigh = "<< hs.heigh << endl;
// 如果想要输出父类的函数
hs.TaiShan::print(); //黄山黄山, 我是泰山
cout << "泰山高度heigh = " << hs.TaiShan::heigh << endl;
return 0;
}
/*
在vs2017中输出结果为:
泰山泰山, 我是黄山
黄山高度heigh = 1864
黄山黄山, 我是泰山
泰山高度heigh = 1545
请按任意键继续. . .
*/
结论:如果是子类对象,那么默认调用的是子类的属性和方法。如果想要调用父类的属性和方法需要显示调用;
也就是作用域符 "::"
五、继承中的static关键字
举例:
# include <iostream>
using namespace std;
class A
{
public:
int b;
static int a;
public:
A()
{
cout << "A的构造函数(无参)" << endl;
}
void getB()
{
cout << "B:" << b << endl;
}
void print()
{
cout << "AAAA" << endl;
}
protected:
};
//int A::a = 10; // 这句话的意思是 初始化static 变量 a, 并且 要求C++编译器请求操作系统给它分配内存
class B :private A
{
public:
int b, c;
public:
B()
{
cout << "B的构造函数(无参)" << endl;
}
void get_child()
{
cout << "B:" << b << endl;
cout << a << endl;
}
void print()
{
cout << "BBBB" << endl;
}
protected:
};
// 1, static 遵守派生类的访问控制规则
// 2, int A::a = 10; // 这句话的意思是 初始化static 变量 a, 并且 要求C++编译器请求操作系统给它分配内存
// 3, 注意:除了单例设计模式之外, 其余情况下 一般构造函数一般不用 private(不写默认private)
int main(void)
{
B b1;
// b1.a = 10; // error
A a1;
a1.print(); // 如果没有 int A::a = 10; 那么 error
b1.get_child(); // 如果没有 int A::a = 10; 那么 error
system("pause");
return 0;
}
结论:1, static 遵守派生类的访问控制规则
2, int A::a = 10; 这句话的意思是 初始化static 变量 a, 并且 要求C++编译器请求操作系统给它分配内存, 这句话很重要
3, 注意:除了单例设计模式之外, 其余情况下 一般构造函数一般不用 private(不写默认private)
六、多继承(工程中一般不用)
多继承就是一个派生类存在多个基类。
举例:
# include <iostream>
using namespace std;
class Base1
{
public:
Base1(int a)
{
this->a = a;
}
void printB1()
{
cout << "a:" << a << endl;
}
private:
int a;
};
class Base2
{
public:
Base2(int b)
{
this->b = b;
}
void printB2()
{
cout << "b:" << b << endl;
}
private:
int b;
};
class B :public Base1, public Base2
{
public:
B(int c, int a, int b) :Base1(a), Base2(b)
{
this->c = c;
}
void printC()
{
cout << "c:" << c << endl;
}
private:
int c;
};
int main(void)
{
B b(1,2,3);
b.printB1();
b.printB2();
b.printC();
system("pause");
return 0;
}
这种情况下没问题, 但是工程中一般不用多继承, 为什么呢?
举例:
# include <iostream>
using namespace std;
class B
{
public:
int b;
};
class B1:virtual public B
{
public:
int b1;
};
class B2 :virtual public B
{
public:
int b2;
};
class C : public B1, public B2
{
public:
int c;
};
int main(void)
{
C c1;
c1.b1 = 10;
c1.b2 = 20;
c1.c = 30;
// c1.b = 50; //error b 不明确
c1.b = 50;
system("pause");
return 0;
}
解释:B类中有一个b, B1和B2类继承了B类, 所以B1类和B2类中都要一个 b , 现在C类继承了B1类和B2类, 如果要访问C类对象中的 b ,那么就会
出现二义性,即访问不明确。 其实这种情况也是可以解决的, 用 virtual 关键字。
但是如果B1类中有一个 b , B2 类中也有一个 b , 那么 virtual 也解决不了了.因为这个原因, 在工程中一般不使用多继承。
---------------------------------------------------------------------------------------------------------------------------------
部分内容参考于:传智播客C/C++视频