导图社区 C ╋ ╋类和对象的思维导图
这是一篇关于C ╋ ╋类和对象的思维导图。该思维导图比较系统全面地归纳和总结了关于这一部分的知识点。
编辑于2021-09-21 15:12:03C++
从C到C++
1. C++简介
1.1. 支持面向对象编程、泛型编程和过程化编程。
1.2. 编程领域广泛:常用于系统开发,引擎开发等应用领域,是最受广大程序员喜爱的编程语言之一
2. OOP思想
2.1. 封装(encapsulation)
将数据( 数据) 和方法( 操作) 捆绑在一起, 创造出一个新的类型的过程。
将接口与实现分离的过程。
使得代码模块化
2.2. 继承(inherit)
一个类共享了一个或多个其他类定义的属性和方法, 在这种关系中子类可以对基类进行扩展、 覆盖、重定义。
扩展已存在的代码模块(类),为了代码重用
2.3. 多态(polymorphism)
类型理论中的一个概念, 一个名称可以表示很多不同类的对象, 这些类和一个共同超类有关。 因此,这个名称表示的任何对象可以以不同的方式响应一些共同的操作集合。
实现接口重用
3. C和C++的差异
3.1. C++融合了三种编程方式: C语法的面向过程编程、 C++基于C语法扩展的面向对象编程、 及C++模板支持的泛型编程。
3.2. 面向过程编程(Procedure Oriented) 是以过程为中心, 把分析解决问题的步骤流程以函数的形式一步步设计实现。
3.3. 面向对象编程(OOP) 是以事务为中心。 一切事物皆对象, 通过面向对象的方式, 将现实世界的事物抽象成对象。
3.4. 学习C++的主要注意的问题是: C++不是++C
作用域及名字空间
作用域
1. 作用域(scope) 描述了一个名字在文件( 编译单元) 的多大范围内可见。
2. C++支持三种形式的域:
3. 局部域(local scope)
1. 局部域是包含在函数定义或者函数块中的程序文本部分。
4. 名字空间域(namespace scope)
1. 名字空间域是不包含在函数声明函数定义或者类定义内的程序文本部分
2. 对象函数类型以及模板都可以在全局域中定义
5. 类域(class scope)
1. 每个类定义都引入了一个独立的类域
名字空间
命名声明区
namespace 名称{ //声明区 }
使用名称空间
声明的话, 则直接使用
using 名称::变量 using 名称::函数名
不声明的话
名称::变量或函数名
输入输出流
C++输出流
cout输出流需要搭配<<输出操作符来使用,如输出语句:
cout << "Hello";
即会在屏幕上显示字符串Hello
本质上,是将字符串"Hello"插入到cout对象里,并以cout对象作为返回值返回,因此你还可以用<<在后面连续输出多个内容,如:
cout << "Hello" << " World";
屏幕上将显示Hello World
额外的,提到cout,最常用到的还有endl操纵符,可以直接将它插入到cout里,起输出换行的效果
cout << "Hello" << endl;
C++输入流
接收一个数据之前,都要先定义一个与之类型一致的变量,用来存放这个数据,然后利用cin搭配>>输入操作符,来接收用户从键盘的输入,如代码:
#include<iostream> using namespace std; int main() { int a; cout << "input number:" << endl; cin >> a; cout << "Get " << a << endl; return 0; }
程序运行后,结果如下:
input number: 24 Get 24
同样的,cin也可以连续接收多个变量,如:
int a,b; cin>>a>>b;
格式控制符
设置状态标志流成员函数setf
一般格式: long ios::setf(long flags)
调用格式: 流对象.setf(ios::状态标志)
类和对象
类的声明和定义 <class>
类的概念
C++中对象的类型称为类(class),类代表了某一批对象的共性和特征, 类是对象的抽象,而对象是类的具体实例(instance)。
先声明一个类类型,然后用它去定义若干个同类型的对象。 对象就是类类型的一个变量。可以说类是对象的模板,是用来定义对象的一种抽象类型。
类是抽象的,不占用内存,而对象是具体的,占用存储空间。
类是用户自己指定的类型。
类的基本语法形式
类定义包含两部分:
类头class head由关键字class及其后面的类名构成。
类体class body由一对花括号包围起来类定义后面必须接一个分号或一列声明。
class 类名 { private: 私有的数据和成员函数; public: 公用的数据和成员函数; protected: 保护的数据和成员函数; };
类的声明
类的定义
对象的定义及公共成员访问
对象的定义: 类名 对象名;
对象指针的定义: 类名 * 对象指针名;
通过对象成员运算符访问公共成员: 对象名.成员名
对象指针通过“->” 访问公共成员: 对象指针->成员名
注意:
private: 私有访问限定符, 仅能被类内部访问
protected: 保护访问限定符, 能被类内部访问或子类继承访问。
如果设计一个类时,没有显示声明定义构造函数、析构函数、拷贝函数则编译器会自动生成。
构造析构和拷贝
构造函数(constructor)
构造函数:
构造函数声明一般格式为:
构造函数名(类型 1 形参1, 类型2 形参2,…)
定义对象的一般格式为 :
类名 对象名(实参1,实参2,…);
注意:
A、不需要用户来调用它也不能调用,而是在建立对象时自动执行 B、构造函数的名字必须与类名同名,而不能由用户任意命名,以便编译系统能识别它并把它作为构造函数处理 C、没有返回值 D、构造函数的功能是由用户定义的,用户根据初始化的要求设计函数体和函数参数 E、如果用户不设计,则编译器自动生成一个
构造函数是一种特殊的成员函数,与其他成员函数不同: 处理对象的初始化。
析构函数(destructor)
是在一个对象的生命期即将结束的时候,应该回收该对象占有的资源,或是完成一些清理工作。
析构函数既没有返回值,也没有函数参数,因此它不能被重载。
析构函数的语法一般是在类名前加一个“~“。
当对象消亡的时候析构函数会自动被调用。
可以显示调用析构函数。一般的,不需要进行析构函数显示调用,也会有特殊需求:譬如对象是静态的时候,存在堆区分配,当是程序未结束时候,需要释放堆,这个时候就可以显式调用析构函数来完成。如果显示调用了析构函数,此时析构函数和普通成员函数是一样的,并不会造成对象被销毁。
拷贝函数
为什么需要拷贝构造
需要对一个类对象实现初始化该类的另一个对象
设计一个拷贝构造函数来实现一个类对象向该类的另一个对象做拷贝是通过一次拷贝每个非静态数据成员。
深拷贝
若对象中,指针成员变量指向堆区空间,在拷贝的时候,需要给新对象,开辟新的空间
是增加一个指针,并且重新申请一块新的内存,使得这个指针指向新的内存。
用新内存存放复制的对象。
浅拷贝
只是将对象成员变量的值拷贝给另一个对象
只是增加一个指针指向已存在的被复制内存地址; 如果原地址发生改变,那么浅拷贝复制出来的对象也会发生改变。
浅拷贝和深拷贝的根本区别在于是否真正获取一个对象的复制实体,而不是引用;
this指针
this 是一种特殊的指针,指向类的对象的首地址。
每个类对象的成员函数都一个this指针,指向调用对象,如果要引用整个对象则*this。
this指针仅能在类内部使用。
可以区分形参和成员属性
可以返回对象自身/地址
static成员
可将类的成员声明为静态(用static 修饰的成员变量或成员方法)
静态成员不和对象关联,也不能直接访问类的其他成员
静态成员属于整个类,被该类的所有对象共享
静态成员函数可以通过 类名加作用域运算符直接调用
静态成员函数不能访问其他成员,只能访问其他静态成员
作用
1. 修饰局部变量:延长生周期、作用域不变
2. 修饰全局变量和函数:限制作用域在本文内
const对象
const成员函数
如果需要保证成员函数不会修改对象,譬如成员函数内部要使用this指针,则需要const成员函数。
在类内声明语法形式
<数据类型> <函数名> (<参数列表>) const;
在类外定义语法形式
<数据类型> <类名> :: <函数名> (<参数列表>) const { ...... }
只要类方法不修改对象就应该将其声明为const
修饰成员函数
void getval2()const //表示成员函数不会修改对象 { myval = 8; //error 如果myval 是成员变量, 不能通过成员函数修改 }
const对象
定义常对象的一般形式为
类名 const 对象名 [(实参表列)];
const 类名 对象名 [(实参表列)];
常对象里面所有成员的值都不能被修改 常对象 不能调用该对象的非const 成员函数(除了 系统自动调用的构造和析构函数)
const成员变量
如果一个类对象的数据成员希望被保护,也可以使用const关键字来声明,其用法与一般只读变量相似。
const数据成员的值是不能改变的。故此,只能通过构造函数的参数初始化表对const数据成员进行初始化。
修饰成员变量:
const int m; //表示该数据成员是只读变量,该变量不允许被修改
友元 <friend>
什么是友元
友元是一种定义在类外部的普通函数或类,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。
友元不是成员函数,但是它可以访问类中的私有成员。
友元函数
友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。
一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
友元函数的调用与一般函数的调用方式和原理一致。
将外部的全局函数 通过在类内部用friend 声明为友元函数,则该函数虽然不属于该类 但是也可以访问类中所有成员。
友元类
友元关系不能被继承。
友元关系是单向的,不具有交换性。 若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
友元关系不具有传递性。 若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的声明
优点:
可以灵活地实现需要访问若干类的私有或保护成员才能完成的任务
方便与其他不支持类概念的语言(如c 语言,汇编等)进行混编
通过使用友元函数重载可以更自然的使用c++语言的io流库
缺点:
一个类将其非公有成员访问权限授予其他函数或类、会破坏该类的封装性,降低该类的可靠性和可维护性。
故:慎用友元
运算符重载
运算符重载简介 <operator>
基本语法:
<返回类型说明符> operator <运算符符号> (<参数..>) { <函数体>; }
友元运算符重载
友元运算符重载基本语法
class 类名 { friend 返回类型 operator 运算符 (形参); } 类外定义格式: <返回类型说明符> operator <运算符符号> (<参数..>) { <函数体>; }
友元函数重载双目运算符(有两个操作数,通常在运算符的左右两则),参数表中的个数为两个。
若是重载单目运算符(只有一个操作数),则参数表中只有一参数。
成员函数运算符重载
成员函数运算符重载基本语法
class 类名 { friend 返回类型 operator 运算符 (形参); } 类外定义格式: <返回类型说明符> operator <运算符符号> (<参数..>) { <函数体>; }
对于成员函数重载运算符而言,双目运算符的参数表中仅有一个参数,而单目则无参数。
同样的是重载,因为友元函数没有this指针,所以在参数的个数上会有所区别的。
注意事项
A、除关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和三目运算符"?:"以外,C++中的所有运算符都可以重载(其中“=”和“&”不必用户重载)
B、重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符
C、运算符重载的实质是函数重载,遵循函数重载的选择原则
D、重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构
E、运算符重载不能改变该运算符用于内部类型对象的含义
F、运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符
G、重载运算符的函数不能有默认的参数,否则就改变了运算符的参数个数
H、重载的运算符只能是用户自定义类型,否则就不是重载而是改变了现有的C++标准数据类型的运算符的规则
I、运算符重载可以通过成员函数的形式,也可是通过友元函数,非成员非友元的普通函数。
模版
模版概念 <template>
C++中模板是支持参数化多态的工具,就是让类或者函数声明为一种通用类型,使得类中的某些数据成员或者成员函数的参数、返回值在实际使用时可以是任意类型。
通常有两种形式
函数模版
函数模版针对仅参数类型不同的函数
template <class 形参名, class 形参名, ......> 返回类型 函数名(参数列表 { 函数体 }
类模板
类模板针对仅数据成员和成员函数类型不同的类
template<class 形参名, class 形参名, …> class 类名 { ... };
模板的声明或定义只能在全局,命名空间或类范围内进行。
类型模板参数
模板形参表示的是一个未知的类型。
类型形参仅由关键字class或typename后接说明符构成
例如:函数模版
template<class T> T func(T var) { … }
其中T就是一个类型形参
形参var及返回值类型为T, 实际类型在实例化时确定
非类型模版参数
非类型形参在模板定义的内部是常量值
非类型模板的形参只能是整型、指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。
非类型模板形参的实参如果是表达式,则必须是一个常量表达式,在编译时计算出结果。
非类型模板形参和实参间允许类型转换
譬如:
template<class T, int var> class Demo { … };
其中int var就是非类型的模板形参。
默认模版参数
类模板的类型形参可以有默认值,函数模板的类型形参则不能。
函数模板和类模板都可以为模板的非类型形参提供默认值。
类模板的类型形参默认值形式为:
template<class T1, class T2=int> class Demo { … };
类型形参T1、T2,其中T2的默认类型为int
友元模版参数
如果一个类是模板类,又要实现运算符重载,一般的,运算符重载是友元函数,那么显然会涉及到一个问题,一个友元如何操作模板类。
其实质就是类模板和函数模板的综合应用。
STL容器
智能指针
什么是智能指针
智能指针(smart pointer)是个特殊的类模板,重载了“->”和“*”运算符,实现了C++的自动内存回收机制。
智能指针通用实现技术是使用引用计数(reference count)。
智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。
头文件 #include <memory>
shared_ptr
shared_ptr( 共享资源的智能指针) 被用来表示共享的拥有权。
当两段代码都需要访问一些数据, 而它们又都没有独占该数据的所有权( 从某种意义上来说就是该段代码负责销毁该对象) 。
shared_ptr是一种计数指针。 当引用计数变为0时, shared_ptr所指向 的对象就会被删除。
shared_ptr分配内存时建议使用make_shared函数
unique_ptr
同一时刻只能有一个unique_ptr指向给定对象 (通过禁止拷贝语义、 只有移动语义来实现) 。
unique_ptr指针的生命期从创建时开始, 直到离开作用域。 离开作用域时, 若其指向对象, 则将其所指对象销毁
weak_ptr
弱指针(weak pointer) , 指向一个已经用shared_ptr进行管理的对象
转换函数
基本概念 <operator>
转换函数的实质就是运算符重载,只是重载的运算符不是内置的运算符而是类名这个特殊的自定义类型
语法形式
operator 类型名( ) { 实现转换的语句 }
转换函数基本规则
转换函数只能是成员函数, 无返回值, 空参数。
不能定义到void的转换, 也不允许转换成数组或者函数类型。
转换常定义为const形式, 原因是它并不改变数据成员的值
explicit关键字
在C++中, explicit关键字用来修饰类的构造函数, 被修饰的构造函数的类, 不能发生相应的隐式类型转换
给单参数的构造函数使用explicit关键字, 阻止可能产生的隐式转换: 由成员变量类型转换为类类型。
标准转换函数
C++标准转换函数
编译时转换: reinterpret_cast、 const_cast、 static_cast
运行时候转换: dynamic_cast
reinterpret_cast
reinterpret_cast<new type>( expression)
将一个类型的指针转换为另一个类型的指针, 它也允许从一个指针转换为整数类型
const_cast
const_cast< new type>( expression)
const指针与普通指针间的相互转换, 注意: 不能将非常量指针变量转换为普通变量
static_cast
static_cast<new type>(expression)
主要用于基本类型间的相互转换, 和具有继承关系间的类型转换
dynamic_cast
dynamic_cast<newtype>(expression)
只有类中含有虚函数才能用dynamic_cast; 仅能在继承类对象间转换
dynamic_cast具有类型检查的功能, 比static_cast更安全
自定义转换函数
慎用转换函数
错误的使用转换函数
转换可能引发二义性
异常
基本思想 <try>
让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。
C++的异常处理机制有3部分组成:
try(检查错误) --> throw(抛出异常) --> catch(捕获异常)
异常处理的一般格式:
try{ //检查错误并抛出错误 if(检错) throw 异常1; .... if(检错) throw 异常n }catch(异常1){ 处理异常 } ... catch(异常n) { 处理异常 }
标准异常
C++标准异常(需要包含头文件stdexcept.h)
其中派生的类名表示了标准的错误, 而what可以打印用户指定的信息, 当然也可以重写。
自定义异常
可以从标准异常exception类派生出来, 也可以完全自定义一个异常类。
多态性
多态性概述
多态(Polymorphism)按字面的意思就是“多种状态”,简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,是面向对象编程领域的核心概念。
面向对象的编程(OPP):
封装
实现细节隐藏,使得代码模块化。
继承
扩展已存在的代码,目的是为了代码重用
多态
目的是为了接口重用
多态性是将接口与实现进行分离; 用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。
虚函数 <virtual>
简单地说,用virtual修饰的成员函数,就是虚函数。
需要注意的是:
1. 虚函数不能是静态成员函数,或友元函数,因为它们不属于某个对象。
2. 内联函数不能在运行中动态确定其位置,即使虚函数在类的内部定义,编译时,仍将看作非内联,
3. 构造函数不能是虚函数,析构函数可以是虚函数,而且通常声明为虚函数。
虚函数的作用就是实现多态性(Polymorphism)。
覆盖、重载和隐藏
覆盖 也称为(重写)
是指派生类重新定义基类的虚函数, 特征如下:
A、 不同的作用域( 分别位于派生类与基类)
B、 函数名字相同
C、 参数相同
D、 基类函数必须有virtual关键字, 不能有static
E、 返回值相同
F、 重写函数的权限访问限定符可以不同
重载
是指函数名相同, 参数不同( 数量、 类型、 次序) , 特征如下:
A、 相同的范围( 在同一个作用域中)
B、 函数名字相同
C、 参数不同
D、 virtual 关键字可有可无
E、 返回值可以不同
隐藏 也称为(重定义)
A、 不在同一个作用域( 分别位于派生类与基类)
B、 函数名字相同
C、 返回值可以不同
D、 参数不同。 此时, 不论有无 virtual 关键字, 基类的函数将被隐藏( 注意与重载的区别)
E、 参数相同, 但是基类函数没有 virtual关键字。 此时, 基类的函数被隐藏( 注意与覆盖的区别)
动态联编
联编(链接)
就是将模块或者函数合并在一起生成可执行代码的处理过程。
静态联编
是指在编译阶段就将函数实现和函数调用关联起来, 因此静态联编也叫早绑定
动态联编
是指在程序执行的时候才将函数实现和函数调用关联, 因此也叫运行时绑定或者晚绑定。
C++中一般情况下联编也是静态联编, 但是一旦涉及到多态和虚拟函数就必须要使用动态联编了。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
C++程序如何在运行时做到晚绑定的,即所谓动态联编,这里引入了一个虚函数表(Virtual Table)的技术。
抽象类
含义
有纯虚函数的类叫抽象类;(除纯虚析构要声明定义外)
语法形式
class 类名{ virtual 返回值类型 函数名1(参数列表) = 0; //只声明不定义 ... virtual 返回值类型 函数名n(参数列表) = 0; //只声明不定义 };
抽象基类
含有纯虚函数的类就是抽象类。
抽象类没有完整的信息,只能是派生类的基类
抽象类不能有实例,不能有静态成员
派生类应该实现抽象类的所有方法
虚继承
多重继承可能引发使用基类成员函数出现二义性
而虚继承解决多重继承产生的二义性;
虚析构
一般将析构函数指定为虚的,主要是避免空间回收不完整;
限制构造
一个类的构造函数访问权限不是public,该类的构造函数就是限制构成函数
只能通过派生类对象或友元访问
继承和派生
继承的概念
基本概念
在C++中,所谓“继承”就是在一个已存在的类的基础上建立一个新的类。
已存在的类(例如“学生”)称为“基类(base class)”或“父类(father class)”。
新建立的类(例如“小学生”)称为“派生类(derivedclass)”或“子类(son class)”。
关于基类和派生类的关系,可以表述为: 派生类是基类的具体化,而基类则是派生类的抽象
派生一个类
语法如下:
class 子类名 : 权限访问限定符 基类名1,权限访问限定符 基类名2,... { //class body };
权限访问限定符
public: 表示基类的public/protected成员可以被子类访问, 通过子类对象可访问基类的public成员
protected/private: 表示基类的public/protected成员可以被子类访问, 通过子类对象不能访问基类任何成员。
继承方式
public继承
基类public成员 --> 子类public成员
基类的protected成员 --> 子类的protected成员
基类的private成员:与子类无关,只能在基类内部访问
protected继承
基类public成员 --> 子类protected成员
基类的protected成员 --> 子类的protected成员
基类的private成员:与子类无关,只能在基类内部访问
private继承
基类public成员 --> 子类private成员
基类的protected成员 --> 子类的private成员
基类的private成员:与子类无关,只能在基类内部访问
派生类的构造和析构
派生类不能继承基类的构造、 析构函数
派生类有自己的构造、 析构函数
如果基类构造函数有参数, 在从派生类的构造函数把参数传递给基类的构造函数
在类外定义派生类的构造函数
派生类名::构造函数名(参数列表) : 基类名(参数列表) { … }
is-a关系
is-a一般是继承关系,has-a一般是组合关系
多重继承
基本语法如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,… { <派生类类体> };
多重继承可能引发使用基类成员函数出现二义性(菱形继承)