面向对象编程三大特征:封装 继承 多态
封装:封装一个类 把一类事物共同拥有的特征抽象为属性(成员变量) 把一类事物共同拥有的行为抽象为方法(成员方法) 并加以访问控制属性的限制(private protected public)
空类(系统会自定义的函数): 无参构造 拷贝构造 拷贝赋值 析构 取址运算符函数 常对象const取址运算符函数 C++11 移动构造 移动赋值
如果手动实现了拷贝构造函数,缺省的无参构造将不再生成
类是对象的抽象化,泛化的概念 对象是类的具体化,特指存在的事物
男人类 < 人类 < 动物类 一个类只是另外一个类的一部分
继承
继承的基本概念: 一个类属于另外一个类的一部分
该类拥有另外一个类的所有的属性和方法
如果一个类继承另外一个类,那么该类拥有另外一个类所有的属性和方法
人类 人类 基类 父类
/ \ / \ \ 继承 派生 衍生
男人类 女人类 学生类 教师类 工人类
继承的语法:
class CLA_NAME:继承方式 F_CLA_NAME,继承方式 F_CLA_NAME1,…{ };
在C++中,定义一个类可以继承其它的类 支持多继承,即一个类有多个父类(java只有单继承) 继承多个类时,每个类的继承方式都可以不一样 继承方式有: public,protected,private 公开继承 保护继承 私有继承 C++中,默认的(缺省的)继承方式为私有继承 在继承的语法中,被继承的类称为父类,基类,继承父类的这个类称为子类或者派生类
继承的意义
子类拥有父类的属性和方法 子类可以定义自己的属性和方法 代码的复用 功能的扩展
#include <iostream>
using namespace std;
class F{
public:
int x;
string name;
public:
F(int x=0,string name=""):x(x),name(name){
}
void hello(){
cout << "Hello!" << endl;
}
void show(){
cout << x << ":" << name << endl;
}
};
class S:public F{
public:
int y;
double z;
public:
S(int y=0,double z=0):y(y),z(z){
}
void hi(){
cout << "HI" << endl;
}
};
int main(){
S s;
cout << sizeof(S) << endl;
s.x = 110;
s.name = "张飞";
s.hello();
s.show();
s.y = 1111;
s.z = 12.123;
s.hi();
return 0;
}
继承方式
私有继承 private 默认的 保护继承 protected 公开继承 public
继承方式所表达的含义是 从父类中继承到子类中的属性将是怎样的访问控制属性
1.访问控制属性
关键字 属性 基类 子类 外部 友元
public 公有 OK OK OK OK
protected 保护 OK OK NO OK
private 私有 OK NO NO OK
2.继承方式对基类中继承下来的成员变量的访问控制属性的影响
基类中不同访问控制属性的变量 通过不同的继承方式 到达子类的变成的访问控制 属性
基类 public公开继承 protected保护继承 private私有继承
公有 公有 保护 私有
保护 保护 保护 私有
私有 私有(不能访问) 私有(不能访问) 私有(不能访问)
基类中的私有属性不管通过什么继承方式,到达子类都不能访问
通过继承,在基类中定义的任何成员,也都成为了子类的成员。 但是基类中的私有成员,子类虽然拥有但却不能访问 基类中的保护成员和公开成员,在子类中是直接可以访问的 公开继承: 基类中保护的成员,通过公开继承方式,在子类中依然是保护的成员 基类中公司的成员,通过公开继承方式,在子类中依然是公开的成员 在使用时基本使用公开继承 保护继承: 基类中保护的、公开的成员,通过保护继承到子类变成子类中保护的成员 私有继承: 基类中保护的、公开的成员,通过私有继承到子类变成子类中私有的成员
#include <iostream>
using namespace std;
class A{
private:
int x;
protected:
int y;
public:
int z;
};
class B:private A{
public:
void func(){
y = 1;
z = 1;
}
};
class C:private B{
public:
void bar(){
}
};
int main(){
A a;
a.z = 1;
cout << sizeof(B) << endl;
B b;
return 0;
}
子类的构造
子类的构造函数 子类的构造顺序 任何一个子类对象中都包含着它的"基类子对象" 基类子对象:在子类对象中包含一个基类的对象 任何一个子类对象都可以被视为它的基类对象—is a 任何时候,一个子类对象的引用或者指针,都可以被隐式转换为它的基类类型的引用或者指针 基类类型的引用,如果引用子类对象,它其实是引用基类子对象(只引用了子类对象的一部分)
A a1 = c;//c C类型对象 is a A类型的对象
如果一个类有多个父类,通过不同的父类,引用同一个子类对象,引用的不是同一部分 通过不同父类的指针,指向同一个子类对象,指针的值可能是不一样的(基类中没有任何成员时就一样)
class A{
int x;
};
class B{
int y;
};
class C:public A,public B{
int z;
};
C c;
A& ra = c;
B& rb = c; // &ra &rb 引用对象的地址值不一样 它们是引用基类子对象
A* pa = &c;
B* pb = &c; //pa pb的值不一样的 指向基类子对象
这里会重点讲,是因为在C的认知中,指向的是同一个对象c,地址就应该一样.但是C++中继承的子类的存储方式
#include <iostream>
using namespace std;
class A{
public:
int x;
int y;
public:
A(){}
A(const A& a){
cout << "A拷贝" << endl;
}
};
class B{
public:
int w;
int z;
};
class C:public A,public B{
public:
int h;
void show(){
x = 1;
y = 1;
w = 1;
z = 1;
cout << x << y << w << z << h << endl;
}
};
int main(){
C c;
cout << sizeof(C) << endl;
cout << "----------"<< endl;
A& ra = c;
cout << &ra << endl;
B& rb = c;
cout << &rb << endl;
cout << "----------" << endl;
A a1 = c;
cout << "----------" << endl;
B b1 = c;
A* pa = &c;
cout << pa << endl;
B* pb = &c;
cout << pb << endl;
A a;
C& rc = static_cast<C&>(a);
C& rcc = static_cast<C&>(ra);
C* pc = (C*)pa;
cout << pc << endl;
cout << "---------" << endl;
cout << pb << endl;
pc = (C*)pb;
cout << pc << endl;
pc = static_cast<C*>(pb);
cout << pc << endl;
cout << "---------" << endl;
return 0;
}
C& rc = static_cast<C&>(a);//语法上支持,但是很危险 A a;因为本质上是a是A类
C& rcc = static_cast<C&>(ra); //没问题 A& ra = c, 子类c中有基类子对象a,用了c中的一部分,本质上就是c,所以这样没问题
虽然子类对象可以隐式转换为基类类型的引用 和 指针类型 但是反过来却不可以隐式转换 必须显示类型转换 static_cast<>
B* pb = &c
pc = (C*)pb;
pc = static_cast<C*>(pb);
如果把一个父类类型对象 通过 强制类型转换 或者 static_cast 或者 reinterpret_cast 转变了子类对象, 如果父类类型引用的对象本质不是子类对象的类型,则能够转换成功 但是后续可能出来未知的错误
如果父类类型的引用 引用子类对象,或者父类类型的指针指向子类对象
通过强制类型转换和静态类型转换,成功地把父类的指针和引用 指向/引用正确的子类对象
通过reinterpret_cast把父类对象的指针强制转换为子类对象的指针
能够成功,但是可能引发问题
子类对象可以直接赋值给父类对象 调用父类的拷贝赋值函数 子类对象可以构造新的父类对象 调用父类的拷贝构造函数
子类中的构造函数,会默认按照继承顺序调用父类的无参构造函数进行构造基类子对象
构造函数的执行顺序: 1.按照继承顺序依次调父基类的构造函数(如果一个基类它还有基类,则会一直去调用基类的基类构造函数) 2.按照成员属性的定义顺序,依次调用各个类类型属性的构造函数 3.执行本类的构造函数体
析构函数的执行顺序和构造函数正好相反 如果用一个父类类型的指针 指向一个new的子类对象 在delete这个父类类型指针时,只会调用父类析构函数函数,内存泄露
在构造函数(拷贝构造除外)的初始化列表中,会依次调用父类的无参构造函数和成员类型的无参构造 如果父类中没有无参构造,子类将必须在初始化列表中显示调用父类的有参构造函数 C():A(10)
class A:public B,public C{
private:
D d;
E e;
public:
A(形参列表,...):B(实参列表),C(实参列表),d(实参列表),e(实参列表){
}
//默认的拷贝构造 如果初始化列表不写,默认调用无参构造函数
A(const A& a):B(a),C(a),d(a),e(a){
}
//拷贝赋值
A& operator=(const A& a){
B::operator=(a); //调用B类中的拷贝赋值
C::operator=(a); //调用C类中的拷贝赋值
d = a;// d.operator=(a); //调用D类的拷贝赋值
e = a;// e.operator=(a); //调用E类的拷贝赋值
}
//析构函数
~A(){
//析构子类资源 自动调用父类的析构
}
};
在初始化列表中,调用基类的构造函数: 基类名(实参列表) 在初始化列表中,调用成员类型的构造函数: 成员属性名(实参列表) 子类默认的拷贝构造函数,会依次调用父类的拷贝构造函数 和 成员的拷贝构造 如果子类实现拷贝构造函数,默认在初始化列表中调用其类 和 类类型成员的无参构造 一般需要在初始化列表中调用基类和类类型成员的拷贝构造函数
#include <iostream>
using namespace std;
class A{
public:
int x;
};
class B{
public:
int y;
};
class C:public A,public B{
public:
int z;
};
int main(){
C c;
cout << &c << endl;
A* pa = &c;
B* pb = &c;
C* pc = &c;
cout << "-------------" << endl;
cout << pa << endl;
cout << pb << endl;
cout << pc << endl;
cout << "-------------" << endl;
pc = (C*)pb;
cout << pc << endl;
pc = static_cast<C*>(pb);
cout << pc << endl;
pc = reinterpret_cast<C*>(pb);
cout << pc << endl;
return 0;
}
基类类型引用 基类类型指针
可以用基类类型引用引用子类对象,本质上是引用子类对象中"基类子对象"
可以用基类类型指针指向子类对象,本质上是指向子类对象中"基类子对象"
通过上面的引用和指针 只能访问父类中的属性和方法,不能访问子类中的成员
基类类型 *指针 = new 子类(实参列表);
delete 指针; //只调用基类的析构函数 子类资源无法得到释放 内存泄漏
基类类型& r = 子类对象; //引用 不会调用构造函数
基类类型 obj = 子类对象; //基类的拷贝构造 用子类对象中的基类子对象来构造基类类型对象
基类类型 *指针 = &子类对象;
指针变量 &子类对象 两个地址可能会不相等
引用变量.func(); 通过引用变量调用方法时,调用哪个类的方法取决于引用变量的类型
而非具体引用的那个对象的类型
#include <iostream>
using namespace std;
class A{
public:
A(int z){
cout << "A()" << endl;
}
A(const A& a){
cout << "A(const A& a)" << endl;
}
A& operator=(const A& a){
cout << "A& operator=(const A& a)" << endl;
return *this;
}
~A(){
cout << "~A()" << endl;
}
};
class B{
public:
B(){
cout << "B()" << endl;
}
B(const B& a){
cout << "B(const B& a)" << endl;
}
B& operator=(const B& a){
cout << "B& operator=(const B& a)" << endl;
return *this;
}
~B(){
cout << "~B()" << endl;
}
};
class C:public A,public B{
public:
C():A(10){
cout << "C()" << endl;
}
C(const C& a):A(a),B(a){
cout << "C(const C& a)" << endl;
}
C& operator=(const C& a){
A::operator=(a);
B::operator=(a);
cout << "C& operator=(const C& a)" << endl;
return *this;
}
~C(){
cout << "~C()" << endl;
}
};
class D:public C{
};
int main(){
C c;
C c1(c);
c1 = c;
return 0;
}
shape.cpp
#include <iostream>
using namespace std;
class Shape{
private:
public:
int x;
int y;
public:
Shape(int x=0,int y=0):x(x),y(y){
}
void show(){
cout << "(" << x << "," << y << ")" << endl;
}
};
class Cube:public Shape{
private:
public:
int l;
int w;
public:
Cube(int l,int w,int x,int y):Shape(x,y),l(l),w(w){
}
int len(){
return 2*(l+w);
}
int area(){
return l*w;
}
};
class Circle:public Shape{
private:
public:
int r;
public:
Circle(int r,int x,int y):Shape(x,y),r(r){
}
double len(){
return 3.14*2*r;
}
double area(){
return 3.14*r*r;
}
};
int main(){
Cube cb(2,1,0,0);
Circle cr(2,1,1);
cb.show();
cr.show();
cout << "-----------" << endl;
Shape& rs = cb;
Cube& rc = static_cast<Cube&>(rs);
cout << rc.len() << endl;
Circle& ri = static_cast<Circle&>(rs);
cout << ri.len() << endl;
cout << &rc << endl;
cout << &ri << endl;
cout << "-----------" << endl;
rs.show();
rs.x = 1;
rs.y = 111;
Shape *ps = &cr;
ps->show();
ps->x = 0;
ps->y = 1;
Shape s = cb;
s.show();
s = cr;
return 0;
}
多继承与多重继承
电话 照相机 计算机
\ | /
智能手机
多继承语法和语义上与单继承没有本质的区别,只是子类对象中包含了更多的基类子对象 这些基类子对象在内存中按照继承表的先后顺序从低地址到高地址依次排列 对象内存布局图 子类对象可以用任意类型的基类引用变量来引用 子类对象的指针可以隐式转换为任意基类类型的指针 无论是隐式转换,还是静态转换,编译器都能保证特定类型的基类指针指向相应的基类子对象 通过父类类型指针指向子类对象,通过强制类型转换和静态类型转换, 编译器能够保证转换之后指针指向的是子类对象的地址 但是,重解释类型转换,无法保证上面两种情况 重解释类型转换之后 地址值不会发生变化 强制类型转换 和 静态类型转换 隐式类型转换 地址值可能发生变化
成员属性和方法尽量避免重名
如果要访问重名的基类属性时: 基类名::属性名
子类对象.基类名::属性名
#include <iostream>
using namespace std;
class Phone{
protected:
string num;
int x;
public:
Phone(string num="17333038809"):num(num),x(1){}
void call(){
cout << "打电话" << endl;
}
};
class Camera{
protected:
int pic;
int x;
public:
Camera(int pic=1000000):pic(pic),x(2){
}
void photo(){
cout << "照相" << endl;
}
};
class Computer{
protected:
string cpu;
int x;
public:
Computer(string cpu="Intel"):cpu(cpu),x(3){
}
void surf(){
cout << "网上冲浪" << endl;
}
void play(){
cout << "play game" << endl;
}
};
class SmartPhone:public Phone,public Camera,public Computer{
private:
int x;
public:
SmartPhone():x(4){
}
void show(int x){
cout << x << endl;
cout << this->x << endl;
cout << this->Phone::x << endl;
cout << this->Camera::x << endl;
cout << this->Computer::x << endl;
}
};
int main(){
SmartPhone sp;
sp.show(5);
sp.photo();
sp.call();
sp.surf();
sp.play();
cout << sizeof(sp) << endl;
cout << "---------" << endl;
Phone& rp = sp;
Camera& rc = sp;
Computer& ro = sp;
Phone* pr = &sp;
Camera* pc = &sp;
Computer* po = &sp;
return 0;
}
钻石继承 和 虚继承
A
/ \
B C
\ /
D
class A{};
class B:public A{};
class C:public A{};
class D:public B,public C{};
钻石继承: 如果一个类有多个基类,而这多个基类又有公共的基类
沿着不同的继承路径,公共的基类子对象会在最终子类对象中有多份实例
通过子类对象去访问时,可能造成数据不一致的问题
#include <iostream>
using namespace std;
class A{
public:
int x;
public:
A(int x = 0):x(x){
cout << "A()" << endl;
}
};
class B:virtual public A{
public:
B():A(1024){}
void set(int x){
this->x = x;
}
};
class C:virtual public A{
public:
C():A(9527){}
int get(){
return x;
}
};
class D:public B,public C{
};
int main(){
D d;
cout << sizeof(d) << endl;
d.set(110);
cout << d.get() << endl;
cout << d.B::x << endl;
cout << d.C::x << endl;
return 0;
}
虚继承virtual
在继承表中通过virtual关键字指定从公共基类中虚继承,这样就可以保证 公共的基类和最终的子类对象中,仅存在一份公共基类子对象实例, 避免了沿着不同的继承路径访问公共基类子对象成员时,所引发不一致问题
只有当所创建对象的类型回溯中存在钻石结构,虚继承才起作用, 否则编译器会自动忽略virtual关键字
用父类指针指向子类对象,只能调用父类中的方法 用父类的指针指向new子类对象,delete只调用父类的析构函数,造成内存泄漏 到目前为止,调用哪个类的方法和析构函数,取决于引用或指针的类型,而非目标类型
虚函数 与 多态 virtual
如果将基类中的成员函数声明为虚函数,那么子类中可以重写基类中该虚函数 (子类无论是否有virtual关键字都是虚函数)这样对基类中的虚函数形成覆盖。 这时,通过一个基类类型的指针指向子类对象时, 或者通过一个基类类型的引用引用子类对象时, 调用该虚函数时,实际被调用的函数不由指针或者引用本身的类型决定, 而是由它们的目标对象来决定,最终导致子类覆盖版本的函数被调用。 这种现象称为多态。
多态: 虚函数 + 指针/引用
class CLA_NAME{
virtual RET_TYPE func_name(arglist...){
}
};
#include <iostream>
using namespace std;
class Shape{
private:
public:
int x;
int y;
public:
Shape(int x=0,int y=0):x(x),y(y){
}
void show(){
cout << "(" << x << "," << y << ")" << endl;
}
virtual double len(){
cout << "shape" << endl;
return 0.0;
}
virtual double area(){
cout << "shape" << endl;
return 0;
}
};
class Cube:public Shape{
private:
public:
int l;
int w;
public:
Cube(int l,int w,int x,int y):Shape(x,y),l(l),w(w){
}
double len(){
cout << "Cube" << endl;
return 2*(l+w);
}
double area(){
cout << "Cube" << endl;
return l*w;
}
};
class Circle:public Shape{
private:
public:
int r;
public:
Circle(int r,int x,int y):Shape(x,y),r(r){
}
double len(){
cout << "Circle" << endl;
return 3.14*2*r;
}
double area(){
cout << "Circle" << endl;
return 3.14*r*r;
}
};
int main(){
Shape *ps = new Cube(2,4,0,0);
cout << ps->len() << endl;
cout << ps->area() << endl;
delete ps;
Cube c(2,5,1,1);
Shape& rc = c;
cout << rc.len() << endl;
cout << rc.area() << endl;
ps = new Circle(5,1,1);
cout << ps->len() << endl;
cout << ps->area() << endl;
return 0;
}
覆盖 override
#include <iostream>
using namespace std;
class A{
public:
void f1(){
cout << "A f1()" << endl;
}
virtual void f2(){
cout << "A f2()" << endl;
}
virtual void f3(int x){
cout << "A f3(int)" << endl;
}
virtual void f4(const int& x){
cout << "A::f4" << endl;
}
virtual void f5(int x)const{
cout << "A f5" << endl;
}
};
class B:public A{
public:
void f1(){
cout << "B f1()" << endl;
}
void f2(){
cout << "B f2()" << endl;
}
void f3(){
cout << "B f3()" << endl;
}
virtual void f4(int& x){
cout << "B f4()" << endl;
x = 111;
}
void f5(int x){
cout << "B f5 " << endl;
}
};
int main(){
A* p = new B;
p->f1();
p->f2();
int x = 1;
p->f4(x);
p->f4(1);
p->f5(x);
B b;
b.f1();
b.f3();
b.A::f3(1);
return 0;
}
重载 overload
同一个作用域下,函数名相同,参数列表不同即构成重载
覆盖 override
子类重写父类同型的虚函数
隐藏 hide
子类隐藏父类同名的标识符 除去重载和覆盖才是隐藏
#include <iostream>
using namespace std;
int x = 11111;
class F{
public:
int x;
F(int x=1024):x(x){}
void hello(){
cout << "F hello" << endl;
}
};
class S:public F{
public:
int x;
S():x(9527){}
void func(int x){
cout << x << endl;
cout << this->x << endl;
cout << this->F::x << endl;
cout << ::x << endl;
}
void hello(){
cout << "S hello" << endl;
F::hello();
}
};
int main(){
cout << sizeof(S) << endl;
S s;
cout << s.x << endl;
cout << s.F::x << endl;
s.func(1);
cout << "---------" << endl;
s.hello();
cout << "---------" << endl;
s.F::hello();
static_cast<F>(s).hello();
F& rf = s;
rf.hello();
return 0;
}
怎样才能形成覆盖? 覆盖的条件: 1.形成覆盖函数,在基类中必须是虚函数 virtual 2.子类中函数名、形参列表、常属性必须严格一致 参数列表,普通类型或者对象 常属性可以不一样,不影响 但如果是指针或者引用,参数的常属性必须相同 3.返回值: 如果函数返回的类型为基本类型或者对象,那么必须严格一致 如果返回的是类类型的指针或者类类型的引用, 那么子类版本的返回值可以基类版本返回值类型的子类类型的指针和引用 class A{}; class B:public A{}; class F{ virtual A* func(){} }; class S:public F{ B* func(){}//覆盖 }; 4.子类的覆盖版本不能比基类版本声明更多的异常抛出 5.子类覆盖版本的访问控制属性不受基类版本的限制
|