面向对象编程(OOP: Object-Oriented Programming
)
它的本质是,定义自己的数据类型,就可根据具体的问题提出解决方案。
- 类是定义数据类型的代码块
- 类的名称就是自定义数据类型的名称
- 类类型的数据项,称为对象
- 创建变量,以存储自定义数据类型的对象时,就要使用类类型名称
- 类是按照现实生活中的实体来设计对象的行为,是直观并易于理解的
- 类提供了一种解决方案
万事万物都皆为对象,对象上有其属性和行为
具有相同性质的对象,我们可抽象称为类,人属于人类,车属于车类
封装
对象由两部分组成:属性 + 行为
- 属性:一组数据值,作为类的成员变量,指定对象的属性
- 行为:一组操作,作为类的成员函数,指定对象的行为
把这些数据值和函数打包到一个对象中,称为 封装。
如何定义和使用类?
封装 - 数据隐藏 - 隐藏内部实现
- 数据权限控制
- 隐藏对象中的数据,可禁止直接访问数据,
但可通过对象的成员函数来访问 - 数据隐藏有助于维护对象的完整性,能够确保对象的内部状态(及其所有成员变量的组合)在任何时候都是有效的
- 将数据隐藏与精心设计的接口结合起来,
能够在修改对象的内部表示(对象的状态)及其成员函数的实现(对象的行为)时,
不必修改程序的其余部分。 - 数据隐藏降低了类和使用类的代码之间的耦合性
class ClassName{
private:
//...
protected:
//...
public:
//...
//Constructor
ClassName(){ }//无参构造 普通
explicit ClassName(dataType para){ }//有参构造"只有一个参数" 普通
ClassName(const ClassName& obj){ }//拷贝构造
dataType property_1;
dataType property_2;
dataType property_3;
ClassName(dataType para_1, dataType para_2, dataType para_3)
: property_1(para_1), property_2(para_2), property_3(para_3){//成员初始化列表
}
//Destructor
~ClassName(){ }
}
void useClassNameExample(){
/* 1. 括号法 */
ClassName obj_1;//调用默认构造函数 即无参构造
ClassName obj_2(value);//调用有参构造函数
ClassName obj_3(obj_1);//调用拷贝构造函数
/* 2. 显示法 */
ClassName obj_1;
ClassName obj_2 = ClassName(value);
ClassName obj_3 = ClassName(obj_1);
/* 3. 隐式转换法 */
ClassName obj_2 = value;
ClassName obj_3 = obj_1;
}
-
类名称
ClassName
大写名称
-
成员权限管理
private
- 默认protected
public
-
构造函数
-
构造函数名称
ClassName
与类名称一致 -
两种分类方式
- 按参数分为
- 无参构造
ClassName( ) { }
- 有参构造
ClassName(dataType para){ }
- 无参构造
- 按类型分为
- 普通构造
- 拷贝构造
ClassName(const ClassName& obj){ }
- 按参数分为
-
三种调用方式:
- 括号法【简洁 + 清晰】 - 推荐
- 显示法【过于详细】
- 隐式转换法【过于简洁】
-
default - 定义默认构造函数
ClassName( ){ } <==等价于==> ClassName() = default
-
explicit - 显式转换
对只有一个参数的构造函数,只要使用 explicit 关键字,
就可避免把参数的数据类型转换为类类型。
类的构造函数只要一个参数是有问题的,
因为编译器可使用此构造函数把参数的类型隐式转换为类类型。
-
-
析构函数
- 析构函数名称
~ClassName
与类名称一致的前提下,
在名称前加上符号~
- 析构函数名称
-
成员初始化列表
-
访问私有类成员
-
访问器函数 -
getMyMember( )
-getter( )
例外,
bool
成员变量的访问器常常被命名为isMyMember( )
,即,布尔成员变量 valid 的 getter 常常被命名为isValid( )
而不是getValid( )
-
更改器函数 -
setMyMember( )
-setter( )
-
-
this - 指向调用者本身
-
const - 不能修改
- 常函数
dataType func(paraList) const{ }
- 常对象
const ClassName obj;
- mutable - 特殊可变
- 常函数
-
friend - 访问另一个类中私有成员
- 全局函数做友元
- 类做友元
- 成员函数做友元
-
static
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化 -
作用域解析运算符 ::
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
- 静态成员变量
继承
继承是根据一个类型定义另一个类型的能力。
-
在派生类中重新定义基类的函数,称为 重写
重新定义继承下来的函数,可使它们在自己的环境下更有意义
-
自由定义适合于自己类型的新特性
-
复用现有代码
class subclassName : inheritanceMode parentClass{
}
class A{
public:
dataType property_1;
protected:
dataType property_2;
private:
dataType property_3;
}
class B : public A{//公共继承
public:
dataType property_1;
protected:
dataType property_2;
}
class B : protected A{//保护继承
protected:
dataType property_1;
dataType property_2;
}
class B : private A{//私有继承
private:
dataType property_1;
dataType property_2;
}
void example(){
B b;
b.property_1//访问的是子类B中的属性
b.A::property_1//访问的是父类A中的属性
}
//多继承语法
class sonClass: InheritanceWay1 parentClass1, InheritanceWay2 parentClass2 ......{
}
//虚继承
class subclassName : inheritanceMode virtual parentClass{
}
-
继承方式
inheritanceMode
- 成员权限变化(访问级别)- 公共继承
public
- 保护继承
protected
- 私有继承
private
- 公共继承
-
继承内容
父类中所有非静态成员属性都会被子类继承下去,
父类中私有成员属性,是被编译器给隐藏
因此是访问不到,但是确实被继承下去
-
继承中构造和析构顺序
继承中先调用父类构造函数,再调用子类构造函数,
析构顺序与构造相反
-
继承成员同名处理方式 -
作用域解析运算符 ::
-
继承同名静态成员处理方式 -
作用域解析运算符 ::
-
多继承 - 不建议 但可继承接口
-
菱形继承/重复继承问题 - 虚继承
多态
多态性表示在不同的时刻有不同的形态。
-
C++中的多态性总是涉及使用 指针 或 引用 来调用对象的成员函数
-
这种函数调用在不同的时刻有不同的效果——函数调用有多种不同的形式
-
此机制仅适用派生于公共类型的对象
-
多态性仅用于共享一个公共基类的类层次结构
-
多态性意味着,属于一组继承性相关的类的对象可通过 基类指针 和 引用 来传送和操作。
-
通过指针调用哪个函数并不是在编译程序时确定的,而是在程序执行时才确定。
因此,同一个函数调用会根据指针指向的对象完成不同的操作 -
常常不能事先确定要处理哪种类型的对象,即在设计或编译期间不能确定类型,
只能在运行期间确定 -
改写对象行为
class Base{
public:
virtual void func() = 0;//纯虚函数
virtual ~Base(){//虚析构函数
cout << "virtual destructor" << endl;
}
virtual ~Base() = 0;//纯虚析构函数
}
Base::~Base(){
cout << "pure virtual destructor" << endl;
}
class Derive_1 : public Base{
public:
virtual void func(){
cout << "rewrite in Derive_1" << endl;
}
}
class Derive_2{
public:
virtual void func(){
cout << "rewrite in Derive_2" << endl;
}
}
class Derive_3{
public:
virtual void func(){
cout << "rewrite in Derive_3" << endl;
}
}
void example(){
Base* base = new Derive_2;
base->func();// "rewrite in Derive_2""
delete base;
base = NULL;
}
-
多态的分类
-
静态多态
函数重载 和 运算符重载 属于静态多态 - 复用函数名
-
动态多态
派生类 和 虚函数实现运行时多态
-
两者的区别
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
-
-
多态的前提条件
-
存在继承关系
-
子类重写父类中的虚函数
重写:函数返回值类型 + 函数名 + 参数列表,
完全一致称为重写
-
-
使用多态
父类指针或引用 指向 子类对象
-
纯虚函数 和 抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,
主要都是调用子类重写的内容。因此,可以将虚函数改为纯虚函数。
纯虚函数语法:
virtual returnType functionName(Parameter list) = 0
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
-
虚析构 和 纯虚析构
多态使用时,如果子类中有属性开辟到堆区,
那么父类指针在释放时无法调用到子类的析构代码,
从而导致内存泄漏解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~ClassName( ){ //code...}
纯虚析构语法:
virtual ~ClassName() = 0
ClassName::~ClassName(){ //code...}