一、C++对c的扩展
1.1 面向对象编程概述
1.1.1 面向过程
面向过程是一种以过程为中心的编程思想,通过分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。面向过程编程思想的核心:
功能分解,自顶向下,逐层细化(程序=数据结构+算法)
面向过程编程语言存在的主要缺点是不符合人的思维习惯,而是要用计算机的思维方式去处理问题,而且面向过程编程语言重用性低,维护困难。
-
-
-
目录
面向对象
-
-
面向对象编程简称OOP技术,是开发计算机应用程序的一种新方法,新思想。过去的面向过程编程语常常会导致所有的代码都包含在几个模块中,使程序难以阅读和维护。再做一些修改时常常牵一发而动全身,使以后的开发和维护难以为继,而使用OOP技术,常常要使用许多代码模块,每个模块都只提供特定的功能,它们是彼此独立的,这样就增大了代码重用的几率,更加有利于软件的开发,维护和升级。
在面向对象中,算法和数据对象被看作是一个整体,称作对象,现实世界中任何类的对象都具有一定的属性和操作,也总能用数据结构和算法两者合一的来描述,所以可以用下面的等式来定义对象和程序:
对象 = 算法 + 数据结构
程序 = 对象 + 对象 + …
从上面的等式可以看出,程序就是许多对象在计算机中相继表现自己,而对象则是一个个程序实体。面向对象编程思想的核心:应对变化,提高复用。
-
-
- 面向对象的三大特点
-
封装
把客观使用封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。类将成员变量和成员函数封装在类的内部,根据需要设置访问权限,通过成员函数管理内部状态。
继承
继承所表达的时类之间相关的关系,这种关系使得对象可以继承另一类对象的特征和能力。
继承的作用:避免公用代码的重复开发,减少代码和数据冗余
多态
多态性可以简单地概括为“一个接口,多种方法”,字面意思为多种形态。程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。
-
- ::作用域运算符
::解决归属问题(谁是谁的谁)
-
- 命名空间namespace
创建名字是程序设计过程中一项最基本的活动,当一个项目很大时,它不可避免包含大量名字。C++允许我们对名字的产生和名字的可见性进行控制,我们之前在学习c语言可以通过static关键字来使得名字只得在本编译单元内可见吗,在C++中我们将通过一种通过命名空间来控制对名字的访问。
1.3.1 C++命名空间(namespace)
在C++中,名称(name)可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大,名称互相冲突性的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的C++库时,这些标识符的命名发生冲突,标准C++引入关键字namespace (命名空间/名字空间/名称空间),可以更好地控制标识符的作用域。
- 创建一个命名空间
正在上传…重新上传取消
-
-
- using声明 命名空间中的成员可用
-
正在上传…重新上传取消
using声明成员碰到函数重载
正在上传…重新上传取消
如果命名空间包含一组用相同名字重载的函数,using声明就声明了这个重载函数的所有集合。
-
-
- using声明整个命名空间可用
-
正在上传…重新上传取消
正在上传…重新上传取消
注意:使用using声明或using编译指令会增加命名冲突的可能性。也就是说,如果有名称空间,并在代码中使用作用域解析运算符,则不会出现二义性。
-
- struct类型增强
C中定义结构体变量需要加上strut关键字,C++不需要。C中的结构体只能定义成员变量,不能定义成员函数。C++即可以定义成员变量也可以定义成员函数。
1.4.1 结构体中既可以定义成员变量,也可以定义成员函数
正在上传…重新上传取消
C++可以直接在结构体中定义成员函数,C中不可以。
-
-
- C++中定义结构体变量不需要加struct关键字
-
正在上传…重新上传取消
-
- bool类型关键字
标准C++的bool类型有两种内建的常量true(转换为整数1)和false(转换为整数0表示状态。这三个名字都是关键字。bool类型只有两值,true(1值),false(0值)。
bool类型占1个字节大小,给bool类型赋值时,非0值会自动转换为true(1),0值会自动转换false(0)。
正在上传…重新上传取消
-
- 引用(reference)
在C/C++中指针的作用基本都是一样的,但是C++增加了另外一种给函数传递地址的途径,这就是按引用传递(pass-by-reference)。变量名实质上是一段连续内存空间的别名,是一个标号(门牌号)程序中通过变量来申请并命名内存空间通过变量的名字可以使用存储空间对一段连续的内存空间只能取一个别名吗? C++中新增了引用的概念,引用可以作为一个已定义变量的别名。
1.6.1 引用的定义
引用的本质:就是给变量名取个别名。(typedef给类型取别名)
引用定义的步骤:
1、&别名(引用必须初始化)
2、给哪个变量取别名 就定义该变量
3、从上往下整体替换
1.6.2 普通变量的引用
正在上传…重新上传取消
1.6.3 数组的引用
正在上传…重新上传取消
1.6.4 指针变量的引用
正在上传…重新上传取消
1.6.5函数的引用
正在上传…重新上传取消
1.6.5引用作为函数的参数
函数内部可以通过指针变量*p1,*p2去操作外部的int变量。
void swap01(int *p1, int *p1)
指针的缺点:
1、形式参数带指针*的,传的时候就要传变量的地址,实际参数就要有变量的地址传给了p;
2、在函数内部通过p去访问a的值的时候,必须对指针变量取*去拿到空间内容。
实参要取 地址,形参要取*,比较麻烦。
函数内部可以通过引用操作外部变量
正在上传…重新上传取消
引用的语法更清晰:
1) 函数调用时传递的实参不必加“&”符
2) 在被调函数中不必在参数前加“&”符,引用作为其它变量的别名而存在,因此在一些场合可以代替指针。
3) C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。
1.6.7 引用作为函数的返回值类型
1、不要返回普通局部变量的引用
正在上传…重新上传取消
num已经被释放,此时再通过引用去接,会出现段错误
2、返回值类型为引用 可以完成链式操作
正在上传…重新上传取消
1.6.8 常引用
1.给常量取别名,不能通过常引用 修改 内容
正在上传…重新上传取消
2.常引用 作为函数的形式参数:防止函数内部修改外部的值
通过形参节约空间(取别名),使用const常引用,在函数内部无法给a赋值。
正在上传…重新上传取消
1.7 内联函数
1.7.1 声明内联函数
内联函数 必须在定义的时候 使用关键字inline修饰,不能在声明的时候使用inline。
正在上传…重新上传取消
内联函数:在编译阶段 将内联函数中的函数体 替换函数调用处,避免函数调用时的开销。(与宏函数具有相同功能)
1.7.2 宏函数和内联函数的区别
1、宏函数和内联函数都会在 适当的位置 进行展开 避免函数调用开销。
2、宏函数在预处理阶段展开;
内联函数在编译阶段展开。
3、宏函数的参数没有类型,不能保证参数的完整性;
内联函数的参数有类型,能保证参数的完整性。
4、宏函数没有作用域的限制,不能作为命名空间、结构体、类的成员;
内联函数有作用域的限制,能作为命名空间、结构体、类的成员。
1.7.3 内联函数的注意事项
1、在内联函数定义的时候加inline修饰类中的成员函数
2、默认都是内联函数 (不加inline 也是内联函数)有时候 就算加上inline也不一定是内联函数 (内联函数条件)
3、不能存在任何形式的循环语句
4、不能存在过多的条件判断语句
5、函数体不能过于庞大
6、不能对函数取地址
7、有时候不加inline修饰 也有可能是内联函数
8、内不内联 由编译器决定
1.8 函数重载
1.8.1 函数重载的概述
能使名字方便使用是任何程序设计语言的一个重要特征!
我们现实生活中经常会碰到一些字在不同的场景下具有不同的意思,比如汉语中的多音字“重"。当我们说:"他好重啊,我都背不动"我们根据上下文意思,知道“重"在此时此地表示重量的意思。如果我们说"你怎么写了那么多重复的代码?维护性太差了!这个地方我们知道,"重“表示重复的意思。同样一个字在不同的场景下具有不同的含义、那么在C+中也有一种类似的现象出现、同一个函数名在不同场景下可以具有不同的含义。
函数重载 是C++的多态的特性 (静态多态:一个接口多种功能)。
函数重载:用同一个函数名 代表不同的数功能。
1.8.2 函数重载的条件
同一作用域,函数的参数类型不同、个数不同、顺序不同,都可以重载。(返回值类型不同不能作为重载的条件)
正在上传…重新上传取消
思考:为什么函数返回值不作为重载条件呢?
当编译器能从上下文中确定唯一的函数的时,如int ret =func(),这个当然是没有问题的。然而,我们在编写程序过程中可以忽略他的返回值。那么这个时候,一个函数为 void func(int x);另一个为int func(int x); 当我们直接调用func(10),这个时候编译器就不确定调用哪个函数。所以在C++中禁止使用返回值作为重载的条件。
1.8.3 函数重载的底层实现原理
正在上传…重新上传取消
不同的编译器可能会产生不同的内部名,只是举例说明。
1.9 函数的默认参数
C++在声明函数原型时可为一个或多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。
正在上传…重新上传取消
1.10 占位参数
C++在声明函数时,可以设置占位参数,占位参数只有参数类型,而没有参数名,一般情况下,在函数体内无法使用占位参数。
正在上传…重新上传取消
1.11 extern”C”浅析
以下在Linux下测试: c函数: void MyFunc(){}被编译成函数: MyFunc
C++函数: void MyFunc(){},被编译成函数: _Z6Myfuncv
通过这个测试,由于C++中需要支持函数重载,所以和C+中对同一个函数经过编译后生成的函数名是不相同的,这就导致了一个问题:
如果在C++中调用一个使用语言编写模块中的某个函数,那么C++是根据C++的名称修饰方式来查找并链接这个函数,那么就会发生链接错误,以上例,C++中调用MyFunc函数,在链接阶段会去找Z6Myfuncv,结果是没有找到的,因为这个MyFunc函数是语言编写的,生成的符号是MyFunc。那么如果我想在C++调用C的函数怎么办?extern"C"的主要作用就是为了实现C++代码能够调用其他语言代码。加上extern"C"后,这部分代码编译器按C语言的方式进行编译和链接,而不是按C++的方式。
正在上传…重新上传取消
二、类和对象
2.1 类和对象的基本概念
2.1.1 类的封装性
我们编写程序的目的是为了解决现实中的问题,而这些问题的构成都是由各种事物组成,我们在计算机中要解决这种问题,首先要做就是要将这个问题的参与者:事和物抽象到计算机程序中,也就是用程序语言表示现实的事物。那么现在问题是如何用程序语言来表示现实事物?现实世界的事物所具有的共性就是每个事物都具有自身的属性,一些自身具有的行为,所以如果我们能把事物的属性和行为表示出来,那么就可以抽象出来这个事物。
类将具有共性的数据和方法封装在一起,加以权限区分,用户只能通过公共方法 访问私有数据。
类的权限分为:private (私有)、protected (保护)、public (公有)3种权限。
在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时,private和protected是同等级的,外部不允许访问。用户在类的外部可以通过public的方法间接访问private和protected数据。
正在上传…重新上传取消
2.1.2 定义一个类
Class是类的关键字
正在上传…重新上传取消
设计类的步骤:
1、思考这个类有哪些成员数据,且成员数据不用初始化;
2、操作这些数据的成员函数,数据为私有,成员函数为公有
2.1.3 设计一个Person类
请设计一个Person类,Person类具有name和age属性,提供初始化函数(Init),并提供对name和age的读写函数(set, get),但必须确保age的赋值在有效范围内(0-100),超出有效范围,则拒绝赋值,并提供方法输出姓名和年龄。
正在上传…重新上传取消
正在上传…重新上传取消
2.1.4 成员函数在类外实现
成员函数只用在类中声明,类外实现。
正在上传…重新上传取消
2.1.6 类在其他源文件中实现
类定义在同文件data.h中,而data.cpp是用来实现类的成员函数。
Data.cpp
正在上传…重新上传取消
Data.h
正在上传…重新上传取消
2.2 构造函数
2.2.1 初始化和清理
当我们创建对象的时候,这个对象应该有一个初始状态,当对象销毁之前应该销毁自己创建的一些数据。对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未知,同样的使用完一个变量,没有及时清理,也会造成一定的安全问题。C++为了给我们提供这种问题的解决方案,构造函数(负责初始化)和析构函数(负责清理),这两个函数将会被编译器自动调用,完成对象初始化和对象清理工作。无论你是否喜欢,对象的初始化和清理工作是编译器编译我们要做的事情,即使你不提供初始化操作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类就应该顺便提供初始化函数。
为什么初始化操作是自动调用而不是手动调用?
既然是必须操作,那么自动调用会更好,如果靠程序员自觉,那么就会存在遗漏初始化的情况出现。
2.2.2 构造函数的概述
1.类实例化对象的时候,系统自动调用构造函数,完成对象的初始化;
2.如果用户不提供构造函数,编译器会自动添加一个默认的构造函数(空函数)。
2.2.3 构造函数的定义方法
1.构造函数名和类名相同,没有返回值类型(返回void也不行),可以有参数(可以重载),权限为public;
2.先给对象开辟空间(实例化),然后调用构造函数(初始化)。
正在上传…重新上传取消
2.2.3 提供构造函数的影响
1.如果用户不提供任何构造函数,编译器默认提供一个空的无参构造;
2.如果用户定义了构造函数(不管是有参、无参),编译器不再提供默认构造函数。
2.4 析构函数
2.4.1 析构函数的定义方式
1.函数名和类名称相同,在函数名前加~,没有返回值类型,没有函数形参(不能被重载);
2.当对象生命周期结束的时候系统自动调用析构函数,先调用析构函数,再释放对象的空间。
正在上传…重新上传取消
构造函数和析构函数的顺序:利用栈的思想,先进后出,后进先出。
一般情况下,系统默认的空析构函数就足够,但是如果一个类有指针成员,这个类必须写析构函数,释放指针成员所指空间(防止内存泄漏)。
正在上传…重新上传取消
2.4 拷贝构造函数
2.4.1 拷贝构造函数的定义
1.拷贝构造:本质是构造函数;
2.拷贝构造的调用时机:旧对象初始化新对象才会调用拷贝构造;
3.如果用户不提供拷贝构造,编译器会自动提供一个默认的拷贝构造(完成赋值动作-浅拷贝)。
正在上传…重新上传取消
#ob2在初始化时没有调用构造函数。
拷贝构造是一种特殊的有参构造函数,其参数为当前类的常引用。
正在上传…重新上传取消
正在上传…重新上传取消
2.4.2 拷贝构造和无参构造/有参构造的关系
如果用户定义了拷贝构造或者有参构造,都会屏蔽无参构造(深拷贝);
如果用户定义了无参构造或者有参构造,不会屏蔽拷贝构造(浅拷贝)。
2.4.3 拷贝构造几种调用形式
1.旧对象给新对象初始化 调用拷贝构造
正在上传…重新上传取消
2.给对象取别名 不会调用拷贝构造
正在上传…重新上传取消
3.普通对象作为函数参数调用函数时,会发生拷贝构造。
正在上传…重新上传取消
函数的形参不会立即开辟空间,只有当函数调用的时候系统才会给函数的形参开辟空间。
4.函数返回值普通对象
正在上传…重新上传取消
正在上传…重新上传取消
Vscode会发生拷贝构造,return函数的作用:
1.结束函数;
2.返回特定值,如果返回的返回值如果字节小于4,则放到寄存器里;大于4字节,放在栈区的临时区域,放在临时区域的匿名对象中(此时触发了拷贝构造)。
在main()中,用ob2去接getObject(),此时仅仅是用ob2去命名匿名对象,不会发生旧对象初始化新对象,不会发生拷贝构造。
注意:
Qt和linux下不会发生拷贝构造:ob2直接接管ob1
正在上传…重新上传取消
2.4.4 拷贝构造的浅拷贝和深拷贝
1.默认的拷贝构造都是浅拷贝;
2.如果类中没有指针成员,不用实现拷贝构造和析构函数;
3.如果类中有指针成员,且指向堆区空间,必须实现析构函数释放指针成员指向的堆区空间,必须实现拷贝构造完成拷贝动作。
2.5 初始化列表
2.5.1 对象成员
1.在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象,叫做对象成员。
2.先调用对象成员的构造函数,再调用本身的构造函数。析构函数和构造函数调用顺序相反,先构造,后析构。类会自动调用对象成员的无参构造。
正在上传…重新上传取消
正在上传…重新上传取消
2.5.1 初始化列表
1.类想调用对象成员有参构造必须使用初始化列表。
2.书写规则,“:“+”对象(形参)”
正在上传…重新上传取消
正在上传…重新上传取消
顺序:
1.使用列表初始化先传递对象成员 2.此时再调用有参构造函数
正在上传…重新上传取消
2.6 explicit关键字
1.C++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转换中使用。
2.注意explicit用于修饰构造函数,防止隐式转化。是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)而言。
正在上传…重新上传取消
2.7 类的对象数组
对象数组:本质是数组,数组的每个元素是对象。
对象数组的每个元素都会自动调用构造和析构函数:
- 对象数组不初始化,每个元素调用无参构造;
- 对象数组的初始化,必须显示使用有参构造,逐个元素初始化。
正在上传…重新上传取消
2.8 动态对象创建
2.8.1 动态对象创建的概述
当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用数组的时候,会有这样的问题:1.数组空间太大了,浪费空间;2.数组空间不足。所以对于数组来讲,如果能根据需要来分配空间大小再好不过,所以动态的意思意味着不确定性。为了解决这个普遍的编程问题,在运行中可以创建和销毁对象是最基本的要求。
当然C早就提供了动态内存分配(dynamicmemory allocation),函数malloc和free可以在运行时从堆中分配存储单元。
然而这些函数在C++中不能很好的运行,因为它不能帮我们完成对象的初始化工作。
2.8.2 C语言的方式创建动态对象
当创建一个C++对象时会发生两件事:
1.为对象分配内存;
2.调用构造函数初始化那块内存。
第一部我们能保证实现,需要我们确保第二步一定能发生。C++强迫我们这么做是因为使用未初始化的对象是程序出错的一个重要原因。C动态分配内存方法为了在运行时动态分配内存,C在他的标准库中提供了一些函数,malloc以及它的变种calloc和realloc,释放内存的free,这些函数是有效的,但是原始,调用时需要谨慎。为了使用C的动态内存分配函数,在堆上创建一个类的实例,我们必须这样做:
正在上传…重新上传取消
问题:
1) 程序员必须确定对象的长度。
2) ma11oc返回一个void指针,C++不允许将void赋值给其他任何指计,必须强转。
3) ma1loc可能申请内存失败,所以必须判断返回值来确保内存分配成功,
4) 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。c的动态内存分配函数太复杂,容易令人混淆,是不可接受的,C++中我们推荐使用运算符new 和delete。
2.8.3 new创建动态对象
C++中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为new的运算符里,当用new创建一个对象时,它就在堆区中为对象分配内存并调用构造函数完成初始化。
正在上传…重新上传取消
new操作符能确定在调用构造函教初始化之前内存分配是成功的,所有不用显式确定调用是否成功。现在我们发现在堆里创建对象的过程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类型转换和安全检查,这样在堆中创建一个对象和在栈中创建对象一样简单。
2.8.4 delete释放动态函数
new表达式的对立是delete表达式。delete表达式先调用析构函数,然后释放内存。
正在上传…重新上传取消
2.8.5 动态对象数组
当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数,除了在栈上可以聚合初始化,必须提供一个默认的构造函数。
正在上传…重新上传取消
2.9 静态成员
在类定义中,它的成员(包括成员变量和成员函数),这些成员可以用关键字static声明为静态的,称为静态成员。不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象共享。
2.9.1 静态成员变量
static修饰的静态成员属于类而不是对象(所有对象共享一份静态成员数据)。
正在上传…重新上传取消
Static修饰的成员定义类的时候,必须分配空间。
Static修饰的静态成员数据必须类中对应,类外初始化。
正在上传…重新上传取消
正在上传…重新上传取消
1.静态成员数据可以通过类名称(作用域)来访问;
2.可以通过定义类的对象去访问静态成员;
3.可以通过修改类的对象来修改静态成员。
对象的静态成员数据通常用于计数:
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
2.9.2 静态成员函数
静态成员函数是属于类而不是属于对象。
正在上传…重新上传取消
静态成员函数只能操作静态成员数据,不能操作普通成员数据。
正在上传…重新上传取消
2.9.3 单例模式设计
单例模式是一种常用的软件设计模式,在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问(该类只实例化一个对象),从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
正在上传…重新上传取消
正在上传…重新上传取消
2.10 C++面向对象模型
2.10.1 成员变量和函数的储存
C++实现了封装,数据和处理数据的操作函数是分开储存的,C++的非静态数据成员直接包含在类对象中,成员函数虽然包含在class声明之内,却不出现在对象中。每一个非内联成员函数只会诞生一份函数实例。
sizeof(Data1)的大小只是这个类在实例化对象a和b时,给对象开辟的空间大小。
正在上传…重新上传取消
4个类的大小都是4字节,因为静态成员、静态成员函数和成员函数都是对象共享的,不占用类的空间。
2.10.2 this指针
通过上例我们知道,C++的数据和操作也是分开存储,并且每一个非内联成员的数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。那么问题是:这一块代码是如何区分那个对象调用自己的呢?
正在上传…重新上传取消
C++通过提供特殊的对象指针this指针解决上述问题。this指针指向被调用的成员函数所属的对象。成员函数通过this指针即可知道操作的是哪个对象的数据。this指针是一种隐含指针,它隐含于每个类的非静态成员函教中。this指针无需定义,直接使用即可。
注意:静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量。
正在上传…重新上传取消
函数形参和成员同名可以使用this指针解决。
正在上传…重新上传取消
正在上传…重新上传取消
this来完成链式操作
正在上传…重新上传取消
this代表成员函数地址;*this代表成员函数对象
2.10.3 const修饰成员函数
用const修饰成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量,当成员变量类型符前用mutable修饰时例外。
正在上传…重新上传取消
输出:正在上传…重新上传取消
2.11 友元
类的主要特点之一是数据隐蔽,即类的私有成员无法在类的外部作用域之外访问。但是,有时候需要在类的外部访问类的私有成员,怎么办?
解决方法是使用友元质数,友元质数是一种特权函数,C++允许这个特仅函数访问私有成员。这一点从现实生活中也可以很好的理解;比如你的家,有客厅,有你的卧室,那么你的客厅是Public的,所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你就进去,但是呢,你也可以允许你的闺童好基友进去。程序员可以把一个全局函数、某个类中的成员函数、甚至整个类声明为友元。
2.11.1 友元的语法
使用friend关键字声明友元。
friend关键字只出现在声明处,一个函数或者类作为了另一个类得友元,那么这个函数或类就可以直接访问另一个类得私有数据。
友元主要用在运算符重载上。
2.11.2 普通全局函数作为类的友元
正在上传…重新上传取消
正在上传…重新上传取消
2.11.3 类的某个成员函数作为另一个类的友元
正在上传…重新上传取消
正在上传…重新上传取消
注意:友元的成员函数所在类放在最上面 在类中声明 类外实现
2.11.5 友元的注意事项
1.友元关系不能被继承
2.友元关系是单向的,类A是类B的友元,但类B不一定是类A的友元;
3.友元关系不具有传递性,类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友。
2.11.6 友元实际应用
电视机遥控案例
#include<iostream>
using namespace std;
class TV;
//遥控器的类作为TV的友元
class Remote
{
private:
TV *ptr;
public:
Remote(TV *p);
void offOrOn(void);
void upVolume(void);
void downVolume(void);
void upChannel(void);
void downChannel(void);
void showTv(void);
void setChannel(int channel);
};
class TV
{
friend class Remote;
enum{OFF,ON};
enum{minVol,maxVol = 10};
enum{minChan, maxChan = 25};
private:
int state;
int volume;
int channel;
public:
TV()//构造函数:初始化
{
state = OFF;
volume = minVol;
channel = minChan;
}
void offOrOn(void);
void upVolume(void);
void downVolume(void);
void upChannel(void);
void downChannel(void);
void showTv(void);
};
int main()
{
TV tv;
Remote remote(&tv);
remote.offOrOn();
remote.downVolume();
remote.setChannel(10);
remote.showTv();
return 0;
}
void TV::offOrOn(void)
{
state == OFF?ON:OFF;
/*三目运算符:返回值:先求表达式 1 的值,
如果为真,则执行表达式 2,并返回表达式 2 的结果;
如果表达式 1 的值为假,则执行表达式 3,
并返回表达式 3 的结果*/
}
void TV::upVolume(void)
{
if (volume == maxVol)
{
cout<<"音量已经最大"<<endl;
return;
}
volume++;
}
void TV::downVolume(void)
{
if(volume == minVol)
{
cout<<"音量已经最小"<<endl;
return;
}
volume--;
}
void TV::upChannel(void)
{
if(channel == maxChan)
{
cout<<"频道已经最大"<<endl;
return;
}
channel++;
}
void TV::downChannel(void)
{
if(channel == minChan)
{
cout<<"频道已经最小"<<endl;
return;
}
channel--;
}
void TV::showTv(void)
{
cout<<"当前电视机的状态:"<<(state==OFF?"关":"开")<<endl;
cout<<"当前电视机的音量:"<<volume<<endl;
cout<<"当前电视机的频道:"<<channel<<endl;
}
Remote::Remote(TV *p)
{
this->ptr = p;
}
void Remote::offOrOn(void)
{
ptr->offOrOn();
}
void Remote::upVolume(void)
{
ptr->upVolume();
}
void Remote::downVolume(void)
{
ptr->downVolume();
}
void Remote::upChannel(void)
{
ptr->upChannel();
}
void Remote::downChannel(void)
{
ptr->downChannel();
}
void Remote::showTv(void)
{
ptr->showTv();
}
void Remote::setChannel(int channel)
{
if(channel>= TV::minChan && channel <= TV::maxChan)
{
ptr->channel = channel;
}
else
{
cout<<"频道"<<channel<<"不再有效范围内"<<endl;
}
}
2.11.7 设计动态数组类
正在上传…重新上传取消
1.元素实际个数
2.空间首元素地址
3.容量
"2.11.7.h"
#include"string.h"
#include<iostream>
using namespace std;
#ifndef ARRAY_H
#define ARRAY_H
class Array
{
private:
int *arr;//存放数组首元素地址
int size;//存放数组实际元素个数
int capacity;//容量
public:
Array();
Array(int capacity);
Array(const Array &ob);
~Array();
int getCapacity(void);
int getSize(void);
void printArray(void);
//尾部插入
void pushBack(int elem);
//尾部删除元素
void popBack(void);
//查看某个元素
int& at(int pos);
};
#endif //ARRAY_H
#include<iostream>
#include<string.h>
using namespace std;
#include"2.11.7.h"
Array::Array()
{
capacity = 5;
size = 0;
arr = new int[capacity];//堆中申请空间
//空间清0
memset(arr, 0, sizeof(int)*capacity);
/*1.若str指向char型地址,value可为任意字符值;
2.若str指向非char型,如int型地址,要想赋值正确,
value的值只能是-1或0,因为-1和0转化成二进制后每一位都是一样的,
设int型占4个字节,则-1=0XFFFFFFFF, 0=0X00000000。*/
}
Array::Array(int capacity)
{
this->capacity = capacity;
size = 0;
arr = new int[capacity];//堆中申请空间
//空间清0
memset(arr, 0, sizeof(int)*capacity);
}
//拷贝构造:旧对象给新对象初始化
Array::Array(const Array&ob)
{
capacity = ob.capacity;
size = ob.size;
//深拷贝
arr = new int[capacity];
memcpy(arr, ob.arr, sizeof(int)*capacity);
}
int Array::getCapacity(void)
{
return capacity;
}
int Array::getSize(void)
{
return size;
}
void Array::printArray(void)
{
int i = 0;
for(i=0;i<size;i++)
{
cout<<arr[i]<<endl;
}
cout<<endl;
}
void Array::pushBack(int elem)
{
//判断容器是否满
if(size == capacity)
{
//申请空间
int *tmp = new int[2*capacity];
//新空间清零
memset(tmp,0,sizeof(int)*2*capacity);
//将旧空间内容拷贝到新空间
memcpy(tmp,arr,size*sizeof(int));
//释放旧空间
delete [] arr;
//让arr指向新空间
arr = tmp;
//更新容量
capacity = 2*capacity;
}
arr[size] = elem;
size++;
}
void Array::popBack()
{
if(size == 0)
{
cout<<"容器为空"<<endl;
return;
}
size--;
//清空
arr[size] = 0;
}
int &Array::at(int pos)
{
if(pos<0 || pos>=size)
{
cout<<"位置无效"<<endl;
exit(-1);
}
return arr[pos];
}
Array::~Array()
{
if(arr!=NULL)
{
delete [] arr;
arr = NULL;
}
}
int main()
{
Array ob;
cout<<"容量:"<<ob.getCapacity()<<",大小:"<<ob.getSize()<<endl;
ob.pushBack(10);
ob.pushBack(20);
ob.pushBack(30);
ob.pushBack(40);
ob.printArray();
cout<<"容量:"<<ob.getCapacity()<<",大小:"<<ob.getSize()<<endl;
ob.pushBack(50);
ob.pushBack(60);
ob.printArray();
cout<<"容量:"<<ob.getCapacity()<<",大小:"<<ob.getSize()<<endl;
ob.popBack();
ob.popBack();
ob.printArray();
cout<<"容量:"<<ob.getCapacity()<<",大小:"<<ob.getSize()<<endl;
cout<<"arr[2] = "<<ob.at(2)<<endl;
ob.at(2) = 100;
ob.printArray();
return 0;
}