包含关系:即一个对象由多个对象共同组成。也被称之为has_a关系,组合关系,复合关系。
代码示例:
#include
using namespace std;
class Man
{
public:
Man()
{
cout << "Man的构造" << endl;
}
~Man()
{
cout << "Man的析构" << endl;
}
};
class Desk
{
public:
Desk()
{
cout << "Desk的构造" << endl;
}
~Desk()
{
cout << "Desk的析构" << endl;
}
};
class Chair
{
public:
Chair()
{
cout << "Chair的构造" << endl;
}
~Chair()
{
cout << "Chair的析构" << endl;
}
};
class Room
{
private:
Chair chair;
Desk desk;
Man man;
public:
Room()
{
cout << "Room的构造" << endl;
}
~Room()
{
cout << "Room的析构" << endl;
}
};
int main()
{
Room room;
return 0;
}
结果展示:
总结:
通过结果我们可知
构造顺序:先构造类中的成员对象,最后再构造最外层的包含对象,构造顺序为类中成员对象的声明顺序相同。
析构顺序:则相反。
我们来打印他们的this
指针看一看
通过上图我们可以发现最外层的包含对象Room和第一个声明的成员对象Chair的地址是一样的,其他因为是空类依次加一。
由此我们可以推断出包含关系的内存布局如下图所示:
友元关系:就是用来描述,类与函数之间,类与类之间的一种亲密关系。
功能:可以访问朋友类中的私有成员。
友元关系的两种形式:友元函数,友元类。
声明一个函数是类的友元函数,那么在这个函数里就可以访问类的私有成员了。
代码举例:
#include
using namespace std;
class Man
{
private:
int money;
public:
Man(int _money):money(_money){}
//友元函数的声明
friend void girlfriend(Man& man);
};
void girlfriend(Man& man)
{
man.money=man.money-100;
cout << man.money <<endl;
}
int main()
{
Man man(1000);
girlfriend(man);
return 0;
}
总结:
由结果可知 当友元函数中,有被use_a的类时,那么这个类的对象将不再受到访问权限的限制。
如果声明B类是A类的友元类,那么在B类中的所有成员函数,都可以访问A类的私有成员。
代码示例:
#include
using namespace std;
class Man
{
private:
string name;
public:
Man(string _name):name(_name){}
friend class girlfriend;
};
class girlfriend
{
private:
string name;
public:
girlfriend(string _name):name(_name){}
void show(Man& man)
{
cout << man.name << "正在陪" << name << endl;
}
};
int main()
{
Man man("小明");
girlfriend girl("小红");
girl.show(man);
return 0;
}
结果展示:
总结:
由结果可知:在A类中声明friend class B,那就是B是A的友元类。如果B类中有定义A的对象时,A类的对象将不受A类的访问权限的限制。
注意:
分文件编程实现boy与girl的相互友元
boy.h
#ifndef BOY_H
#define BOY_H
#include
using namespace std;
class Girl;
class Boy
{
private:
string name;
int age;
public:
Boy(string name,int age);
void show();
void show(Girl& girl);
friend class Girl;
};
#endif // BOY_H
girl.h
#ifndef GIRL_H
#define GIRL_H
#include
using namespace std;
class Boy;
class Girl
{
private:
string name;
int age;
public:
Girl(string name,int age);
void show();
void show(Boy& boy);
friend class Boy;
};
#endif // GIRL_H
boy.cpp
#include "boy.h"
#include "girl.h"
Boy::Boy(string name, int age)
{
this->name=name;
this->age=age;
}
void Boy::show()
{
cout << "正在学习C++" << endl;
}
void Boy::show(Girl &girl)
{
cout << girl.name << "正在陪" << name << "学习" << endl;
}
girl.cpp
#include "girl.h"
#include "boy.h"
Girl::Girl(string name, int age)
{
this->name=name;
this->age=age;
}
void Girl::show()
{
cout << "正在逛商场" << endl;
}
void Girl::show(Boy &boy)
{
cout << boy.name << "正在陪" << name << "逛商场" << endl;
}
main.cpp
#include
#include "boy.h"
#include "girl.h"
using namespace std;
int main()
{
Boy boy("小明",18);
boy.show();
Girl girl("小红",18);
girl.show();
cout << "----------------" << endl;
boy.show(girl);
girl.show(boy);
return 0;
}
继承关系:也就是常说的父子关系,或派生关系。统称为is_a关系。
面向对象的三大特征(封装、继承、多态)
其中描述类与类之间关系的特征,就是继承。
基于一个已有的类,去重新定义一个新的类,这种方式就叫做继承。
简单来说:就是老子的东西,都会被儿子继承下来。
1.代码复用性与高拓展性;
父类的所有属性都被子类继承了下来,所以子类中就可以不用再写父类中的属性与方法了。这样就提高了代码的复用性。同时子类中我们还可以添加一些子类独有的新特性。这样就提高了代码的拓展性。
2.继承是实现多态的必要条件;
一个类B 继承自类A 时,我们一般称呼:
A类:父类,基类
B类:子类,派生类
B继承自A,A派生了B。
单继承语法:
class + 子类类名 : 继承方式 + 父类的类名
{
//单继承类体
};
继承:继承了父类中的所有的属性与行为。(类中的变量及方法)
继承的是父类的属性,与方法的访问权。
静态属性,也是被子类继承,继承的也是访问权。
继承方式也有三种:public protected private
实际使用过程中,一般都用public方式继承。
如果不写继承方式,默认都是private方式继承。
代码示例:
#include
using namespace std;
class Transport
{
public:
string name;
public:
Transport()
{
name="交通工具";
cout << "Transport的构造" << endl;
}
~Transport()
{
cout << "Transport的析构" << endl;
}
void run()
{
cout << "正在行驶" << endl;
}
};
class Car:public Transport
{
public:
Car()
{
cout << "Car的构造" << endl;
}
~Car()
{
cout << "Car的析构" << endl;
}
void show()
{
this->run();
cout << this->name << endl;
}
void what()
{
cout << "我是小汽车" << endl;
}
};
class Bike : public Transport
{
public:
void show()
{
cout << "我是自行车" << endl;
}
};
int main()
{
Car car;
cout << car.name << endl;
car.run();
car.show();
car.what();
cout << "------------" << endl;
Bike bike;
cout << bike.name << endl;
bike.run();
bike.show();
return 0;
}
结果展示:
总结:
以上代码体现了复用性与拓展性。
子类会继承父类的所有成员,包括私有成员,只不过私有成员没法直接在子类中访问需要通过父类提供的public
或protected
的函数接口来访问。
#include
using namespace std;
class A
{
public:
A(int a)
{
cout << "A的构造" << endl;
cout << this << endl;
}
~A()
{
cout << "A的析构" << endl;
}
void show()
{
cout << "学习C++" << endl;
}
};
class B:public A
{
public:
B():A(1)
{
cout << "B的构造" << endl;
cout << this << endl;
}
~B()
{
cout << "B的析构" << endl;
}
void name()
{
cout << "夜猫徐" << endl;
}
};
int main()
{
B b;
b.show();
b.name();
return 0;
}
当父类中与子类中有同名属性或函数时,子类访问时,父类中同名函数或属性将自动隐藏在父类的类域之中,如果要访问父类的类域中的属性或方法,请使用::
域名访问符的形式进行访问 。
代码示例:
#include
using namespace std;
class Car
{
public:
int weight=1000;
public:
Car()
{
cout << "Car的构造" << endl;
}
~Car()
{
cout << "Car的析构" << endl;
}
void show()
{
cout << "车正在行驶" << endl;
}
};
class BMW:public Car
{
public:
int weight=2000;
public:
BMW()
{
cout << "BMW的构造" << endl;
}
~BMW()
{
cout << "BMW的析构" << endl;
}
void show()
{
cout << "BMW正在行驶" << endl;
}
};
int main()
{
BMW b;
cout << b.weight << endl;
b.show();
cout << "-------------" << endl;
cout << b.Car::weight << endl;
b.Car::show();
return 0;
}
父类名::
继承中的拷贝构造函数
继承中的拷贝赋值函数
代码示例:
#include
using namespace std;
class Father
{
private:
string name;
int age;
public:
Father()
{
cout << "Father的无参构造" << endl;
}
Father(string name,int age)
{
this->name=name;
this->age=age;
cout << "Father的构造" << endl;
}
~Father()
{
cout << "Father的析构" << endl;
}
Father(const Father& other)
{
cout << "Father的拷贝构造" << endl;
this->name=other.name;
this->age=other.age;
}
Father& operator=(const Father& other)
{
cout << "Father的=号运算符重载" << endl;
if(this!=&other)
{
this->name=other.name;
this->age=other.age;
}
return *this;
}
void show()
{
cout << "姓名:" << name << " 年龄:" << age << endl;
}
};
class Son:public Father
{
private:
string book;
public:
Son()
{
cout << "Son的无参构造" << endl;
}
//不要在子类的构造函数的函数体中调用父类的构造函数,因为构造函数不允许手动调用的,所以要使用初始化列表
Son(string _name,int _age,string _book):Father(_name,_age),book(_book)
{
cout << "Son的有参构造" << endl;
}
~Son()
{
cout << "Son的析构" << endl;
}
//需要在子类的拷贝构造函数的初始化表中,显性调用父类的拷贝构造函数 --父类的引用可以引用子类对象
Son(const Son& other):Father(other),book(other.book)
{
cout << "Son的拷贝构造" << endl;
}
Son& operator=(const Son& other)
{
cout << "Son的=号运算符重载" << endl;
if(this!=&other)
{
//显性调用父类的拷贝赋值函数来完成对从父类中继承过来的成员的赋值
Father::operator=(other);
this->book=other.book;
}
return *this;
}
void show()
{
Father::show();
cout << book << endl;
}
};
int main()
{
Son s1("张三",18,"小说");
s1.show();
Son s2=s1;
s2.show();
Son s3("小红",17,"散文");
s3=s1;
s3.show();
return 0;
}
通过3.6的代码示例和结果可以发现,在内存的地址上父类和子类的起始地址是相同的。且在继承关系中,构造与析构的顺序是先构造父类,然后再构造子类,析构顺序则反之,与has_a关系是十分类似的。
而且在子类中还可以定义一些子类特有的属性和方法。所以我们可以推断出内存布局关系如下图所示:
通过继承关系的内存布局,我们可以知道子类无非就是在父类的基础之上进行了新的特有属性或方法的拓展。
所以使用父类指针可以指向子类的实例,是天然安全的。无需进行转型。反之则不可能。
代码示例:
#include
using namespace std;
class Father
{
public:
string name="父类";
public:
Father()
{
cout << "Father的无参构造" << endl;
}
~Father()
{
cout << "Father的析构" << endl;
}
void show()
{
cout << "姓名:" << name << endl;
}
};
class Son:public Father
{
public:
string book="名著";
public:
Son()
{
cout << "Son的无参构造" << endl;
}
Son(string _book):book(_book)
{
cout << "Son的有参构造" << endl;
}
~Son()
{
cout << "Son的析构" << endl;
}
void show()
{
cout << book << endl;
}
};
int main()
{
Father *pa=new Son();
cout << pa->name << endl;
pa->show();//父类的show
((Son*)pa)->show();//子类的show
cout << "----------------" << endl;
//Son *pb=(Son*)new Father();
//cout << pb->book << endl;
cout << "----------------" << endl;
//pb->show();
return 0;
}
Son *pb=(Son*)new Father();
如果强转的话,就有可能访问了一个非法空间。(因为子类的类域比父类的类域要大)这种访问是不安全的。static_case<类型指针>(exp)
C++中的静态转换也可以。一个子类可以由多个直接基类共同派生,这种派生方式,就叫多重继承。
子类中会继承每个基类的成员。
多继承语法:
class + 子类类名: 继承方式1 + 父类类名1 , 继承方式2 + 父类类名2, 继承方式3 + 父类类名3 ....
{
//多继承类体
}
多继承一般情况下,会继承多个抽象类,一般抽象类中是没有属性。没有属性就是不带来子类代码膨胀问题。
代码示例:
#include
using namespace std;
class A
{
public:
int a;
A()
{
cout << "A的构造" << endl;
cout << this << endl;
}
~A()
{
cout << "A的析构" << endl;
}
void show()
{
cout << "AAA" << endl;
}
};
class B
{
public:
int a;
B()
{
cout << "B的构造" << endl;
cout << this << endl;
}
~B()
{
cout << "B的析构" << endl;
}
void show()
{
cout << "BBB" << endl;
}
};
class C:public A,public B
{
public:
C()
{
cout << "C的构造" << endl;
cout << this << endl;
}
~C()
{
cout << "C的析构" << endl;
}
};
int main()
{
cout << sizeof(C) << endl;
C c;
//c.a;
//c.show();//出现二义性
//通过类域访问符进行访问
c.A::show();
c.B::show();
cout << "--------------" << endl;
B *b=new C;
cout << "b的地址" << b <<endl;
return 0;
}
多重继承时构造函数和析构函数调用顺序:
与继承时的声明有关。
结果展示:
总结:
图片内的方框就是菱形继承。
菱形继承:一个类由多个基类共同派生,而这多个基类又有共同的基类。此时,在汇聚子类中就会有多份公共基类的成员,访问起来会有歧义。
虚继承:就是在公共基类生成中间子类时,继承方式前加上关键字 virtual
此时,这种继承方式就叫做虚继承。
虚继承时,公共基类的成员需要在汇聚子类中完成构造如果初始化表中没有显性调用A的构造,默认会调用A的无参构造
当出现棱形继承时,在最远端父类在发继承时,会出现父类被多次构造的问题。使virtual
修饰继承方式,就是避免最远端父类被多次构造的。
virtual
修饰的这个继承关系,也叫虚继承,此时的最远端父类,为所有直接继承的子类的共享一份类中的属性。那么这个最远端的父类,也被称之为虚基类。
虚基类的直接继承类中都会被按插一根虚基类表指针,来指向这个虚基类表,这个虚基表中就保存了子类相较于虚基类的偏移量。虚基类表指针通过偏移地址找到已经被始化的基类属性。
内部机制:
当使用virtual修饰继承权后,继承类中,编译器就会默默安插了一根虚指针。这两个直接继承类中各有一根虚基表指针,指向一张共有的虚基表。这张虚基表中存在偏移量,通过偏移量就可以找到共有的那个属性。也就是说B 与 C 是共享了一分虚基类。所以A只需要构造一份,B与C就可以虚基表中的偏移找到A中的属性。
虚基表是属于类的,即创建对象前就是存在的,由于各类中各变量(类型)的确定,偏移量是固定好的,各类创建对象时,生成虚基表指针,计算后拿到地址。
代码示例:
#include
using namespace std;
class A
{
public:
A()
{
cout << "A的构造" << endl;
cout << this << endl;
}
~A()
{
cout << "A的析构" << endl;
}
};
class B:virtual public A//虚继承的方式
{
public:
B()
{
cout << "B的构造" << endl;
cout << this << endl;
}
~B()
{
cout << "B的析构" << endl;
}
};
class C:virtual public A
{
public:
C()
{
cout << "C的构造" << endl;
cout << this << endl;
}
~C()
{
cout << "C的析构" << endl;
}
};
class D:public B,public C
{
public:
D()
{
cout << "D的构造" << endl;
cout << this << endl;
}
~D()
{
cout << "D的析构" << endl;
}
};
int main()
{
D d;
cout << "-----------" << endl;
return 0;
}
总结: