基础软件丨C++知识点总结

2021-07-05
858
0

1、1980年 贝尔实验室 Bjanre Stroustrup(比雅尼·斯特劳斯特鲁普)对C改进与扩充 最初称为“带类的C”,(c with classes). 1983年正式命名为C

2、C 是C的改进与扩充。

C 包括C的全部属性、特征、优点,是在C的基础上的改进与扩充。

 

C 包括过程性语言和类部分。

 

C 是混合型语言,即是过程型的,又是面向对象型的。

3、“面向过程”是一种以事件为中心的编程思想。功能分解、行为抽象的抽象编程。

4、面向对象程序设计的基本特征:

(1)对象:

  • 数据属性(静态)、行为属性(动态);

  • 实现封装与数据隐藏;

  • 对象是对类的实例化。

(2)继承:派生其他类

(3)多态:同一个操作在不同的类上有着不同行为

5、编译过程可分为三个子过程:预处理过程、编译过程、连接过程

内联函数也称内嵌或内置函数,它的语法格式与普通函数一样,只是在函数原型函数定义标题头之前加上关键字inline。

使用内联函数可以省去函数调用所需的建立栈内存环境,进行参数传递,产生程序转移的时间开销。内联函数应是使用频率高,代码却很短的函数。

内联函数的函数体限制:

  • 内联函数中,不能含有switch和while。不能存在任何形式的循环语句

  • 递归函数不能用来做内联函数。

  • 内联函数中不能说明数组。 否则,按普通函数调用那样产生调用代码。 (内联函数是建议性,不是指令性)

  • 内联函数只适合于1-5行的小函数

  • 类结构中所有在类内部定义的函数,都是内联函数。

  • 内联函数中不能有过多的条件判断语

不能对函数进行取址操作

实现函数重载的条件:

  • 同一个作用域

  • 参数个数不同

  • 参数类型不同

  • 参数顺序不同

匹配重载函数的顺序:

(1)严格匹配

(2)内部转换(相容类型匹配)

(3)通过用户定义的转换寻找求一个匹配。

C 用名字粉碎(name mangling)(名字细分、名字压轧)的方法来改变函数名。

返回值类型不能够作为重载依据(区分、细分重载)

1、当又有声明又有定义时,定义中不允许默认参数。 若只有定义,则默认参数才可出现在函数定义中。

2、一个函数中可以有多个默认参数,默认参数应从右至左逐渐定义,当调用函数时,从左向右匹配参数。

void foo(int a, int b = 0, bool c);    //fail,b不是最后一参数,只要有一个参数是默认参数,它后面的所有参数都必须是默认参数

3、默认值可以是全局变量、全局常量、函数。不可以是局部变量。因为默认值是在编译时确定的,必须是静态确定的。

4、默认参数不能用于细分重载函数。

5、函数的定义和声明中,都可以省略形参名

代码区:存放程序的执行代码(各个函数的代码块)

全局变量区:放程序的全局数据和静态数据

堆区:存放程序的动态数据malloc free new delete

栈区:存放程序的局部数据(各个函数中的数据)

“ * ”称为指针运算符(间接访问运算符),表示指针所指向的变量的值。一元运算符

“ & ”称为取地址运算符,用来得到一个对象的地址。一元运算符。

数组名是个指针常量,int *const p,指针自身的值是一个常量,不可改变

(1)void

  • void 指针(void *p) 空类型指针 不指向任何类型,仅仅是一个地址。

  • 不能进行指针运算,也不能进行间接引用。

  • 其他类型指针可以赋值给空类型指针

  • 空类型指针经显示转换后方可赋给其他指针。

(2)NULL

  • NULL是空指针值,不指向任何地方。

  • 任何类型的指针都可以赋该值。

(1)使用const 说明常量:int const x=2; 或const int x=2;

常量定义时,应被初始化。 const int i;(错误)

构造函数初始化时必须采用初始化列表一共有几种情况,

  1. 需要初始化的数据成员是对象(继承时调用基类构造函数)

  2. 需要初始化const修饰的类成员

  3. 需要初始化引用成员数据

  4. 类中含有对象成员,但其类型中,没有无参构造函数,必须在初始化列表中指明相应的有参构造函数。

  5. 基类中缺少无参构造函数,必须在初始化列表中指明基类的有参构造函数。

(2)指针常量: 在指针定义语句的指针名前加const, 表示指针本身是常量。 int a; int* const p=&a; 定义时必须初始化 指针值p不可以修改,指针指向的内容可以修改。 即p是常量,不可以作为左值进行运算,*p可以修改,p不可以修改

(3)常量指针: 在指针的定义类型前加const,表示指向的对象是常量。 如const int *p 或 int const *p; 均可。 以上定义表明,*p是常量,不能将*p作为左值进行操作。 定义指向常量的指针时,定义时不必须先初始化。*p不可以修改,p可以修改

(4)指向常量的指针常量(常量指针常量) 形式: const int *const p=&a; 定义时必须初始化 p与*p都是常量。他们都不能作为左值进行操作

(5)常量的特殊用法:int f(int b) const;

(6)重载和const形参

(7)new和delete进行动态内存分配和释放

使用new较之使用malloc()有以下的几个优点:

  • new自动计算要分配类型的大小,不使用sizeof运算符,比较省事,可以避免错误。

  • 自动地返回正确的指针类型,不用进行强制指针类型转换。

  • 可以用new对分配的对象进行初始化。

  • 不用使用头文件声明(malloc.h),更简洁。

new操作符若申请成功,返回首单元地址;否则返回NULL值。

(1)指针函数:返回指针值的函数。

(2)函数指针:指向函数地址(程序区)的指针。与函数名等价的指针。函数名是指向函数的指针常量。

(3)通过函数指针来调用函数:

(4)函数指针作函数形参:

例:计算以0.10为步长,特定范围内的三角函数之和。

(5)用typedef 来简化函数指针

例子

(6)函数的返回类型可以是函数指针

(1)为一个变量、函数等对象规定一个别名,该别名称为引用。

(2)声明引用:

声明引用,不为之分配内存空间。

(3)引用必须初始化。引用一旦被声明则不能再修改. 

(4)形参和实参结合规则:  

形参为引用时,凡遇到形参(引用)的地方,全部用实参(对象)来代替。

可读性比指针传递好(与传值方式的调用可读性相同,性能却强于传值方式)

可使用引用传递从函数返回多个值(指针和引用都可以)

(5)引用和指针的关系 

指针是个变量,可再赋值; 而引用建立时必须进行初始化并且决不会再关联其它不同的变量。

指针操纵两个实体(指针值、指向的值);引用只能操纵一个实体。

引用在内部用指针实现 ,被看成是指针常量,不能操作自身的地址值,只能访问所指向的实体。

实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用”?

答案是:“用适当的工具做恰如其分的工作”。 指针能够毫无约束地操作内存中的东西,尽管指针功能强大,但是非常危险。 引用是指针出于安全考虑的替代品。 高级编程多用引用,低级编程多用指针,也是基于安全考虑。

在以下情况下你应该使用指针:

一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空)

二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。

如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。

(6)用const 限定引用

不能通过引用对目标变量的值进行修改,保证了引用的安全性。

注意:c 不分变量的const引用,和const变量的引用,因为引用本身就不能重新赋值,使它指向另一个变量。 即没有const int const &a=1, 只有const int &a=1

(7)引用的使用-用引用返回值

用引用返回一个函数值的最大好处是:在内存中不产生被返回值的副本。

别返回一个局部对象的引用

(8)引用的使用-函数调用作为左值

练习:

C 采用“类”来支持对象,同类对象实体抽象出其共性,形成类(数据类型),类封装了数据与处理数据的过程(函数)

C 的类中既能包含数据成员,又包含函数成员或称成员函数。

一般而言,类中的数据成员在定义类的时候是不能初始化的。

C 中类与结构的区别: 默认情况下,class定义的成员是private的,而struct 定义的成员是public的。

访问权限 类内成员函数或者数据成员 派生类 类外 友元函数
public yes yes yes yes
protected yes yes no yes
private yes no no yes

成员函数属于类,成员函数定义是类设计的一部分, 其作用域是类作用域.,而普通函数一般为全局函数

成员函数的操作主体是对象,使用时通过捆绑对象来行使其职责,,而普通函数被调用时没有操作主体

在类内重载成员函数与普通函数重载一样

(1)在类的内部定义成员函数

::是作用域区分符、作用域运算符、名字空间引导符

单独用::表示全局变量和函数

(2)在类之后定义成员函数

(1)文件结构

1、一般情况下, c 的类定义和成员函数定义分离

< >:用于c 提供的头文件,一般存放在c 系统目录中的include 子目录下

“ ”:首先在当前文件所在的目录中进行搜索,找不到,再按标准方式进行搜索.

2、类定义和使用的分离

成员函数的实现和类外定义不要一起放在头文件中

外部链接:函数定义,可以在其他文件中使用。不能重复定义。

内部链接:只能在自己的程序中使用,可以跨文件重复定义。如:类型定义、全局常量、inline函数、模板定义等。

3、头文件卫士

Student.h文件

头文件卫士保证头文件在一个程序文件中只被定义一次

(2)c 程序中的函数的组织方式

头文件的使用:使函数调用免于声明

this是个指针常量

this是对当前对象的引用

this 指针 是一个隐含于每一个类的成员函数中的特殊指针(包括析构函数和构造函数),它用于指向正在被成员函数操作的对象。

程序中可显式使用this指针

首先是数据与算法(操作)结合,构成一个不可分割的整体。

其次是在这个整体中一些成员是保护的,他们被有效地屏蔽,以防外界的干扰和误操作。

另一些成员是公共的,他们作为接口提供给外界使用。

保护(私有)成员protected(private):

(1)保护类的内部数据不被肆意侵犯

(2)是类对它本身内部实现的维护负责,因为只有类自己才能访问该类的保护数据,所以对一切保护数据的维护只有靠类自己。

(3)限制类与外部世界的接口。 保护成员对使用者不可见。

(4)减少类与其它代码的关联程度。类的功能是独立的,不依赖于应用程序的运行环境。

公共成员(公共接口、对外接口)(public):

需要让外界调用的成员函数指定为公共的,外界通过公共的函数来实现对数据的操作。

对类内所有的private修饰的数据提供get和set接口给外界,避免外界对数据直接进行操作。在java里叫javabean类

(1)变量的作用域-{}内或其后的全部内容

(2)类的作用域

包括类定义作用域和类实现作用域

类定义作用域:一个类的成员函数对同一类的数据成员具有无限制的访问权。 私有成员和受保护成员只能被类内部的成员函数访问; 公有成员是类提供给外部的接口, 可以在类外部被访问. 这种技术实现了信息的隐藏和封装.

类的实现作用域:通过类的对象体现,对象有全局对象、局部对象等作用范围。

可见性:

  • 当内层的变量和外层的变量同名时,在内层里,外层的变量暂时地失去了可见性。

  • 不能在同一作用范围内有同名变量

类名允许与其它变量名或函数名相同

1)如果一个非类名隐藏了类名,则类名通过加前缀即可

 2)如果一个类名隐藏了非类名,则用一般作用域规则即可

指一个对象产生后,存活时间的度量。

在生存期内,对象保持它的状态。

作用域与生命期不尽相同。

整个程序的生命期:全局(静态)数据

静态生命期:静态局部数据

局部生命期:

动态生命期:由new申请到内存空间之后,该空间实体开始有效,一直到delete释放该内存空间

名字空间的作用是建立一些互相分隔的作用域,把一些全局实体分隔开来,以免产生名字冲突。

避免不同人编写的程序组合时会遇到名字冲突的危险

可以包含:变量&对象;常量;函数定义; 类型(结构)定义;名字空间的定义

不能包含:预处理命令 ;

可以开放定义;可以{}外定义

标准c 库的所有标识符都是在一个名为std的名字空间定义的。

对象:物理实体在计算机逻辑中的映射和体现

类:同种对象的集合与抽象

实体:现实世界中需要描述的对象

面向对象:

就是力图从实际问题中抽象出封装了数据和操作的 对象,通过定义对象的各种属性来描述他们的特征和功能,通过接口的定义描述他们的地位及与其它对象的关系,最终形成一个广泛联系的可理解、 可扩充、可维护、更接近于问题本来面目的动态对象模型系统。

OOA:面向对象分析(干什么)

OOD:面向对象设计(怎样干)

OOP:面向对象编程(实现)

构造函数:

  1. 构造函数是特殊的类成员函数。

  2. C 规定与类同名的成员函数是构造函数,在该类的对象创建时,自动被调用。

  3. 构造函数负责对象的初始化 可拥有多个参数。

  4. 可以重载。

  5. 构造函数不返回具体的值,不指定函数的返回类型。

  6. 可内置定义,也可以在类外定义。

  7. 通常是Public的

析构函数:

  1. 析构函数也是一个特殊的成员函数;

  2. 作用与构造函数相反;

  3. 在对象的生命期结束时自动被调用。

  4. 名字是类名前加一个~;

  5. 不返回任何值;没有函数类型,没有函数参数,因此不能被重载。

  6. 一个类可以由多个构造函数,只能有一个析构函数。

  7. 可内置定义,也可以在类外定义。

  8. 用于清理和释放资源的工作。

  9. 通常是Public的

  10. 析构函数并不是销毁对象的,只是释放构造函数在构造时初始化的资源(主要包括堆上分配内存等)

//Default constructor called
//Constructor called
//Destructor called
//Destructor called

//Student constructor called
//Teacher construtor called
//Manager constructor called
//Manager destructor called
//Teacher destrctor called
//Student destructor called

//Temp constructor called
//Student constructor called
//Teacher construtor called
//Manager constructor called
//Manager destructor called
//Teacher destrctor called
//Student destructor called
//Temp destructor called

当一个类既是组合类又是派生类,它在创建对象时,系统对构造函数的调用顺序有相应的规定:

最先调用基类的构造函数,初始化基类的数据成员;

然后调用子对象所在类的构造函数,初始化子对象的数据成员;

最后调用本类的构造函数,初始化新增数据成员。

如果类内有局部临时对象:

则先初始化类内子对象,再初始化局部临时对象,最后初始化本类对象。

析构时先调用局部临时对象的析构函数,且多个局部临时对象的构造和析构顺序相反

引用应依附于另一个独立的变量,等待初始化。

常量数据成员是仅仅初始化一次,其值便不再改变的数据成员。

二者只能借助冒号语法初始化。

局部和静态局部对象(静态生命期)以文本定义顺序为顺序 (类成员属于此种情况)

静态对象在首次定义时构造一次;程序结束析构

全局对象在main之前构造;程序结束时析构

全局对象如果分布在不同文件中,则构造顺序随机

常对象:数据成员值在对象的整个生存期间内不能被改变。

即常对象定义是必须进行初始化,而且不能被更改。

声明的语法形式: const 类名 对象名 或者 类名 const 对#include <iostreamusing namespace std;

没有常构造函数和常析构函数,常对象和普通对象都调用同一构造函数和析构函数。

函数内部的申请空间要及时释放,否则容易造成内存重复申请和内存迷失(内存泄漏)

对象数组不能通过参数传递初始化。要么默认构造函数,要么构造函数有默认参数。

一个已知对象构造(初始化)另一对象

Student (Student& s);或者是Student ( const Student& s);

一旦提供了拷贝构造函数,就不在提供默认构造函数

浅拷贝:

创建q时, 对象p被复制给了q, 但资源未复制, 使得p和q指向 同一个资源, 这称为浅拷贝。

可能会破坏该堆及自由内存表

深拷贝:

复制指针的同时,开辟同样的空间,把空间的数据复制,让指针分别指向各自的内存空间

关于无名对象的构造与使用:

构造函数:Student(int i){ }

转换不能太复杂,不允许多参数,不允许间接转换

explicit可以禁止隐式类型转换

(1)声明的位置既可在public区,也可在protected区。友元函数虽然是在类内进行声明,但它不是该类的成员函数,不属于任何类。

(2)在类外定义友元函数,与普通函数的定义一样,一般与类的成员函数放在一起,以便类重用时,一起提供其友元函数。

(3)友元函数是能访问类的所有成员的普通函数,一个函数可以是多个类的友元函数,只需在各个类中分别声明。

(4)友元能使程序精炼,提高程序的效率。

(5)友元破坏了类的封装,使用时,要权衡利弊,在数据共享与信息隐藏之间选择一个平衡点。

(6)友元类的所有成员函数都可视为该类的友元函数,能存取该类的私有成员和保护成员。

(7)友元关系不具有对称性。 友元关系不具有传递性。如果类B是类A的友元类,类C是类B的友元类,这并不隐含类C是类A的友元类

前面没有static ,类外初始化

(1) 不管一个类的对象有多少个,其静态数据成员也只有一个,由这些对象所共享,可被任何一个对象所访问。

(2) 在一个类的对象空间内,不包含静态成员的空间,所以静态成员所占空间不会随着对象的产生而分配,或随着对象的消失而回收。

(3) 静态数据成员的存储空间的分配是在程序一开始运行时就被分配。并不是在程序运行过程中在某一函数内分配空间和初始化。

(4) 静态数据成员的初始化语句,既不属于任何类,也不属于包括主函数在内的任何函数,静态数据成员初始化语句最好在类的实现部分定义

(5) 对于在类的public部分说明的静态数据成员,可以不使用成员函数而直接访问,既使未定义类的对象,同样也可以直接访问,但在使用时也必须用类名指明所属的类。 而private和protected部分的静态成员只能通过类的成员函数访问。

(6) 不允许不指明对象访问非静态数据成员;不允许使用this

(7)可以用const限定静态成员吗?

可以限定形态数据成员,不可以限定静态成员函数

懒汉式:

使用运算符重载的一般格式为:

类型名 operator 运算符(形参表) {函数体}

其中operator是关键字,类型名为重载运算符的返回类型,即运算结果类型。

(1)在C 中几乎所有的运算符( 除“.”(成员选择符)、“.*”(成员对象选择符)、“->*(成员指针选择符)”、“::”、“?:”、“size of”外)都可以被重载。 只能重载         C 中已有的运算符,不允许创建新的运算符.

(2) 运算符的重载既不会改变原运算符的优先级和结合性。

(3) 至少有一个操作对象是自定义类型,参数都是基本类型时不能重载.

(4) 不提倡改变参数个数、运算符含义。

(5) 重载运算符的函数不能有默认的参数。

(6) 运算符重载时参数个数不可以超过原来数目

拷贝构造函数和赋值操作符都是用来拷贝一个类的对象给另一个同类型的对象。

编译器提供默认的拷贝构造函数和赋值运算符的运作机制。

就是将对象中的每个数据成员拷贝到目标对象相应的数据成员中。

若类的数据成员中有指向动态分配空间的指针,通常定义拷贝构造函数,此时,应重载赋值运算符。

实现深拷贝

类外显式调用赋值函数

类型转换函数的作用是将一个类的对象转换成另一类型的数据

类型转换运算符声明的形式:

operator 类型名();

没有参数,没有返回类型,(其返回类型由函数名字指定)但是函数体中必须包含return语句。

只能作为成员函数。

同一个类中不能定义多个转换运算符重载函数.

重载<<运算符

派生类继承了基类的除了构造函数、析构函数、拷贝构造函数和赋值运算符重载函数之外的所有成员,因此派生类对象由两部分组成:一部分是由基类继承的成员,另一部分是派生类新增加的自己特有的成员。

当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。

直接基类:直接参与派生出某类的基类。间接基类:基类的基类,甚至更高层的基类。

继承的本质实际上就是由上到下完全的复制;但是在对内可见性上做了手脚,对外可见性则没有改变。

c 提供了类的继承机制,解决了软件的复用问题。

赋值兼容规则是指需要基类对象的任何地方都可以使用公有派生类的对象来替代。替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。

里氏代换原则: (LSP-Liskov Substitution Principle) 在软件里面,把基类都替换成它的子类,程序的行为没有变化。

使用时还应注意:基类指针指向派生类对象时,只能通过基类指针访问派生类中从基类继承来的成员,不能访问派生类中的其它成员。

不允许将基类的对象赋值给派生类的对象

当类的继承方式为保护继承时,基类的公有和保护成员都以保护成员身份出现在派生类中,而基类的私有成员不可访问。

当类的继承方式为私有继承时,基类的公有和保护成员都以私有成员身份出现在派生类中,而基类的私有成员不可访问。

protected继承和private继承得到的类都不是子类 “凡是父类对象可以出现的地方可以用子类对象代替”,不再适用

继承类型省略时默认为私有继承

派生类中初始化基类数据成员,不可以调用基类的构造函数,可以使用初始化成员列表

派生类构造函数执行的一般顺序是:

(1)基类构造函数,

(2)派生类对象成员类的构造函数(如果有的话)。

(3)派生类构造函数体中的内容。

析构函数的执行顺序相反

在一个类中定义的类称为嵌套类,定义嵌套类的类称为外围类。定义嵌套类的目的在于隐藏类名,减少全局的标识符,从而限制用户能否使用该类建立对象。这样可以提高类的抽象能力,并且强调了两个类(外围类和嵌套类)之间的主从关系。 

嵌套类中说明的成员不是外围类中对象的成员,反之亦然。

嵌套类的成员函数对外围类的成员没有访问权,反之亦然。

嵌套类仅仅是语法上的嵌入。 在嵌套类中说明的友元对外围类的成员没有访问权。

组合是“has a”关系的模型。 汽车=方向盘 轮子 车窗 ……

继承是"is a"关系模型。是为了产生子类型。让开发人员设计“kind of”关系的模型。 鸟类->老鹰

组合和继承不是绝对的。组合可以用继承来实现,继承也可以由组合来实现。

避免继承带来的重负:继承是C 中第二紧密的耦合关系,仅次于友元关系。紧密的耦合是一种不良现象,应该尽量避免。因此,应该用组合代替继承,除非知道后者确实对设计有好处。人们曾经过度地使用继承,即使是有经验的程序员也会如此。软件工程的一条明智原则,就是尽量减少耦合:如果一种关系不只有一种表达方式,那么就 应该用最可行的最弱关系。考虑到继承关系几乎是C 中所能表达的最强关系,因此只有在没有更弱的等价代替选择时,才适合使用。如果用组合就能表示类的关系,那么应该优先使用。

所以更加严格的继承规则应当是:若在逻辑上B是A的“一种”,并且A的所有功能和属性对B而言都有意义,则允许B继承A的功能和属性。

若一个派生类具有两个或两个以上基类,这种继承称为多重继承。

多继承派生类构造函数执行顺序是:

(1)    所有基类的构造函数;多个基类构造函数的执行顺序取决于定义派生类时所指定的顺序,与派生类构造函数中所定义的成员初始化列表的参数顺序无关。

(2)    对象成员(如果有的话)的构造函数;

(3)    派生类本身构造函数的函数代码。

虚基类

格式如下:

class 派生类名:virtual public 基类名 { //声明派生类成员 };

这时,从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。

当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,即基类成员只保留一次。

C 规定,虚基类子对象是由最后派生类的构造函数通过调用虚基类的构造函数进行初始化的。

如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始化列表中必须列出虚基类构造函数的调用.

如果没有列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。

多继承的构造顺序:

(1)任何虚拟基类的构造函数按照他们被继承的顺序构造

(2)任何非虚拟基类的构造函数按照他们被继承的顺序构造

(3)任何成员对象的构造函数按照他们被声明的顺序构造

(4)类自己的构造函数

例子:

(1)如果不用虚继承,用多继承:

(2)使用虚继承#include <iostream>using namespace std;

虚函数的使用方法:

(1)在基类用virtual声明成员函数为虚函数

(2)在派生类中重新定义此函数,要求函数名、函数的参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。

(3)定义一个指向基类对象的指针变量,并使它指向同一类族中的某一对象。

(4)通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。 c 规定,当一个成员函数被声明为虚函数后,其派生类中原型相同的函数都自动成为虚函数 。派生类没有对基类的虚函数重新定义,则派生类继承其直接基类的虚函数。

类型转换:

static_cast:静态转型,必须是相关类型,非多态类层次的祖孙互易,void*转换任何类型

dynamic_cast:动态转型,专门针对有虚函数的继承结构,将基类指针或引用转换成想要的子类指针或引用

const_cast:常量转型,去掉常量性的转换。

使用:

虚函数的实用意义:从基类继承来的某些成员函数不完全适应派生类的需要,允许其派生类中对该函数重新定义,赋予它新的功能,当基类的这些成员函数声明为虚函数后,可以通过指向其基类的指针指向同一类族中不同类的对象,从而调用其同名的函数。

由虚函数实现的多态性是:同一类族中不同类的对象,对同一函数调用作出不同的响应。

多态的实现:

联编(编联、束定、绑定)(binding):就是把一个标识符名和一个存储地址联系在一起的过程。将一个函数调用链接上相应于函数体的代码,这一过程就是函数联编。

静态联编:出现在运行前的联编(在编译时完成),也称为早期联编。

动态联编:联编工作在程序运行阶段完成的情况。在编译、连接过程中无法解决的联编问题,要等到程序开始运行之后再来确定。 也称为滞后联编。

虚函数的工作机理:

Virtual出现则每个类增加一个虚函数表保存类的虚函数 。凡有虚函数的类均维护一个虚函数表 ,实例化每个对象中会增加一个指针指向虚函数表(对象大小会有变化).。虚函数调用时不需要确定对象类型,通过该指针即可找到所要链接函数

多态注意事项:

非成员、静态成员、内联函数不能是虚函数

构造函数、赋值运算符函数不能是虚函数 

析构函数经常定义成虚函数 delete p;

多态实现深拷贝的例子:

纯虚函数:在基类中只声明虚函数而不给出具体的函数定义体,称此虚函数为纯虚函数。

纯虚函数的声明如下: (注:要放在基类的定义体中)

virtual 函数原型=0;

声明了纯虚函数的类,称为抽象类(抽象基类)

通过该基类的指针或引用就可以调用所有派生类的虚函数,基类的纯虚函数只是用于继承,仅作为一个接口,具体功能在派生类中实现。

使用纯虚函数时应注意:

(1)抽象类中可以有多个纯虚函数。

(2)抽象类也可以定义其他非纯虚函数。

(3)从抽象类可以派生出具体或抽象类,但不能从具体类派生出抽象类。

问题:抽象类需要有构造函数和析构函数吗?

【答】虽然抽象类不能实例化,但是抽象类被继承之后,它的派生类可以实例化;而派生类在实例化调用构造函数的时候会先调用基类中的构造函数,所以抽象类的构造函数也是可以被调用的,所以抽象类中可以有构造函数。但是注意:C 核心准则C.126:抽象类通常不需要构造函数‍,因为抽象类通常不包含任何需要构造函数初始化的数据。

抽象类通常代表一个抽象的概念,它提供一个继承的出发点。 在一个复杂的类继承结构中,越上层的类抽象程度越高,有时甚至无法给出某些成员函数的实现,显然,抽象类是一种特殊的类,它一般处于类继承结构的较外层。 引入抽象类的目的,主要是为了能将相关类组织在一个类继承结构中,并通过抽象类来为这些相关类提供统一的操作接口,更好的发挥多态性。

一、纵向关系:(耦合关系相同)

1、继承(is-a) 虎是一种动物

2、实现接口(is like a)(接口:是对行为的抽象) 飞机和鸟都会飞

二、横向关系:(耦合关系渐弱)

1、合成、组合(is a part of)(强拥有,严格的部分整体) 鸟和翅膀;生命周期同步。 形式:成员

2、聚合(own a)(弱拥有,A可以包含B,但B不是A的一部分) 大雁和雁群,群体和个体,生命周期不同步 形式:成员,一般为容器

3、关联(has a) 人有朋友,不是包含关系。企鹅和气候的关系 形式:成员

4、依赖(use a)(运行期关系) 动物需要呼吸氧气 形式:局部变量、方法的参数或者对静态方法的调用

其关系强弱(耦合度)为 依赖<关联<聚合<组合<继承<实现<继承

能使用组合或聚合就不要使用继承(降低耦合度)

关联和聚合的区别:主要在语义上,关联的两个对象之间一般是平等的,例如你是我的朋友,聚合则一般不是平等的,例如一个公司包含了很多员工,其实现上是差不多的。聚合和组合的区别则在语义和实现上都有差别,组合的两个对象之间其生命期有很大的关联,被组合的对象是在组合对象创建的同时或者创建之后创建,在组合对象销毁之前销毁。一般来说被组合对象不能脱离组合对象独立存在,而且也只能属于一个组合对象,例如一个文档的版本,必须依赖于文档的存在,也只能属于一个文档。聚合则不一样,被聚合的对象可以属于多个聚合对象,例如一个员工可能可以属于多个公司。

我想举个通俗的例子。
你和你的心脏之间是composition组合关系(心脏只属于自己)
你和你买的书之间是aggregation聚合关系(书可能是别人的)
你和你的朋友之间是association关联关系

“高内聚低耦合”

1、单一职责原则  

2、开放封闭原则

3、依赖倒转原则

4、里氏替换原则

5、合成聚合复用原则

6、迪米特法则(最少知识原则)

7、接口隔离原则

前提:不通过关键词final

思路:构造函数私有则该类不能用来继承,则为终结类

方法一:静态成员方法

方法二:虚拟继承

(1)典型方法

把基类的构造函数先设为protected,然后在派生类中变为private,使基类不可在被继承

虚拟继承的核心:一个基类如果被虚拟继承,那么在创建它的孙子类的对象时,该基类的构造函数需要单独被调用。此时,如果该基类的构造函数在孙子类的构造函数中无法访问,那么就实现了基类的子类不能被继承。基类 FinalParent,它不定义任何数据成员,这样任何类从它派生并不会增加任何空间上的开销。将它的默认构造函数的访问权限设定为 protected,这样它自身不能产生任何实例,只能用作基类

当 FinalClassChild 试图继承 FinalClass 的时候,FinalClassChild 的构造函数中需要调用 FinalParent 的构造函数,而 FinalParent 的构造函数在 FinalClass 中已经变成了私有 private,不能被 FinalClassChild 的任何成员函数所访问,导致编译错误。所以,任何一个类,只要虚拟继承类 FinalParent,就不能被继承,从而简单、高效、安全地实现了终结类。

(2)把基类构造函数设为private,派生类设为友元类,可以调用基类构造,而派生类的派生类无法调用基类构造函数

1、类被final修饰,不能被继承

2、虚函数被final修饰,不能被override

3、被override修饰后如果父类无对应的虚函数则报错。override就是编译器辅助你检查是否继承了想要虚继承的函数

定义函数模板使用保留字template,

定义格式如下:

template < 模板参数表 >

返回类型 函数名( 函数形式参数表 ) { ... }

模板参数表中的参数可以有多个,可以是类型形参,也可以是表达式形参。多个参数间用逗号间隔。

模板参数若是代表一个类型,模板类型参数形式如下:

class 类型参数名 (或 typename 类型参数名)

函数模板的定义可以看作由两部分组成, 一是模板类型的定义,template<class 模板形参表>。 二是函数定义,它与普通函数的定义类似。 函数模板只是对函数的描述,编译系统不为其产生任何执行代码。

C 编译器在遇到调用用模板方式定义的函数时,会根据调用函数的参数类型构造出一个个具体的函数。这个过程称为函数模板的实例化(instantiation)。

同一个模板生成的不同的模板函数是不同名字的函数,不是重载函数。函数模板反映的是不同函数的函数族。使用模板实现了代码重用。

模板参数表中的参数可以有多个,多个参数间用逗号间隔。参数值可默认

优先匹配非模板函数

模板非类型参数:则形参的类型是某种具体的数据类型。

模板非类型参数表示该参数名代表了一个潜在的值,而该值代表了模板定义中的一个常量。

模板非类型参数被用作一个常量值出现在模板定义的余下部分。 它可以用在要求常量的地方,如 数组声明中指定数组的大小或作为枚举常量的初始值.

类模板可以有多个类型参数

C 语言系统为实现数据的输入和输出定义了一个庞大的类库,它包括的类主要有:

ios:抽象基类

iostream:输入流类istream,输出流类ostream,输入输出流类iostream;

对标准输入设备和标准输出设备的输入输出,简称为标准I/O流。

fstream:输入文件流类ifstream,输出文件流类ofstream,输入输出文件流类fstream;

对在外存磁盘上文件的输入输出,简称为文件I/O流。

Strstream:输入字符串流类istrstream,输出字符串流类ostrstream,输入输出字符串流类strstream.

sstream:输入字符串流类istringstream,输出字符串流类ostringstream,输入输出字符串流类stringstream.

对内存中指定的空间进行输入输出。通常指定字符数组、string类对象做为存储空间的输入输出,简称为串I/O流。

(1)框定异常(try语句块) 将那些有可能产生错误的语句放在try块中

(2)抛掷异常(throw语句) 检测是否产生异常,若是,则抛掷异常。

(3)捕捉异常,定义异常处理(catch语句块)将异常处理语句放在catch块中,以便异常被传递过来时就处理它。

C 只理会受监控的异常。 Try块之后必须紧跟一个或多个catch语句。 Catch括号中只能容纳一个形参,与throw抛掷的异常类型匹配时,便捕获了异常。 避免把正常逻辑淹没在错误处理代码中,从而使程序更易于阅读。

异常捕捉的类型匹配之苛刻程度可以和模板的类型匹配媲美,它不允许相容类型的隐式转换。

对于没有捕捉到的异常,Abort()进程被调用,从而无条件的中止程序的执行。

异常也可以抛出类:

如果在函数的声明中没有包括异常接口声明,则此函数可以抛掷任何类型的异常,例如: void net( );

一个不抛掷任何类型异常的函数可以进行如下形式的声明: void net( ) throw();

static关键字

用于声明静态对象;

静态函数只在本文件可见。(默认是extern的)

全局静态对象:全局静态对象,存储在全局/静态区,作用域整个程序,在程序结束才销毁;

局部静态对象:在函数内部加上static声明的变量,在首次调用时初始化,然后一直驻留在内存,作用域是该函数,可用于函数调用计数(primary有例子),程序结束释放;

静态数据成员:归属于类,类对象共享,类外初始化,类对象可访问;

静态函数成员:归属于类,只能访问静态数据成员。

const 关键字

核心功能:限定只读

const T var; 声明常量,存储在常量区;

const T* p : 不可通过p指针修改对象值;T * const p : 常量指针,指针不可被赋值/地址不可改变。

const T function(const T, const T*, T* const, const T&) const &/&& {…;}

限定返回值为常量、常量形参、指针常量、常量指针、常引用、防止对象属性被改变(对象只读)STL源码大量使用

如何突破const的限制?mutable 在const函数中修改成员变量

重载和覆盖和隐藏

重载指的是函数名相同而参数个数/类型不同从而实现调用同名函数,给定不同参数实现静态多态。

覆盖多用再类继承中,派生类继承基类,然后实现自己的函数版本,如果是虚函数一般在后面加上关键字override(可以提示编译器检查覆盖语法是否正确)。

隐藏是指派生类实现了与基类同名的函数,导致基类函数在派生类内不可见的现象。

函数重载二义性

二义性是在编译阶段编译器进行函数匹配(匹配函数名、参数种类和个数)的时候出现的错误。多种情景会导致函数重载二义性。

默认参数与无参函数;

隐式类型转换造成的。比如:double, int, ßlong

类类型的转换,比如派生类对象指针/引用可以传给基类指针/引用,当出现派生类指针和基类指针两个函数时,编译器无法匹配,出现错误。

解决方法:①编程时注意;②使用explicit关键字声明函数,避免隐式类型转换的出现。

 

转载声明:本文来源于网络,不作任何商业用途。

免责声明:本文内部分内容来自网络,所涉绘画作品及文字版权与著作权归原作者,若有侵权或异议请联系我们处理。
收藏

全部评论

您还没登录

暂无留言,赶紧抢占沙发
绘学霸是国内专业的CG数字艺术设计线上线下学习平台,在绘学霸有2D绘画、3D模型、影视后期、动画、特效等数字艺术培训课程,也有学习资源下载,还有行业社区交流。学习、交流,来绘学霸就对了。
绘学霸iOS端二维码

IOS下载

绘学霸安卓端二维码

安卓下载

绘学霸微信小程序二维码

小程序

版权声明
本网站所有产品设计、功能及展示形式,均已受版权或产权保护,任何公司及个人不得以任何方式复制部分或全部,违者将依法追究责任,特此声明。
热线电话
18026259035
咨询时间:9:00~21:00
在线客服
联系网站客服
客服微信:18026259035
公司地址
中国·广州
广州市海珠区晓港中马路130号之19
绘学霸客户端(权限暂无,用于CG资源与教程交流分享)
开发者:广州王氏软件科技有限公司 | 应用版本:Android:6.0,IOS:5.1 | App隐私政策> | 应用权限 | 更新时间:2020.1.6