面向对象高级编程上

一、面向对象高级编程上

(1)C++代码基本形式

在这里插入图片描述

(2)Header中的防卫式声明

C++头文件中的防卫式声明(也称为包含卫士或头文件保护)是为了防止头文件内容在同一编译单元中被多次包含。这种做法通常使用预处理器指令实现,如下所示:
在这里插入图片描述
防卫式声明的主要目的和优点包括:

避免重复包含:在一个项目中,多个源文件可能都会包含相同的头文件。如果头文件被多次包含,它的内容(如变量定义、函数声明等)将在同一编译单元中出现多次,从而导致编译错误。防卫式声明确保了即使头文件被多次包含,其内容也只被处理一次。

防止编译错误:没有防卫式声明的头文件如果包含多次,可能会引起重复定义的错误。例如,重复的函数定义、类定义或全局变量定义都会导致编译错误。

提高编译效率:通过防止头文件内容的重复包含,可以减少编译器的工作量,提高编译效率。

模块化设计:防卫式声明支持模块化设计,允许开发者在多个文件中重用相同的头文件,而不必担心重复包含的问题。

(3)不带指针类的实现过程

1. 防卫式声明

2. 访问级别

思考哪些类型的数据,放在private下,哪些数据放在public下。
思考需要哪些函数,哪些放在public下,哪些放在private下。

3.构造函数

  • 不需要有返回值,是否需要默认值,参数如何传递(加不加引用),初始列(只有构造函数有),是否需要在函数体内做其他一些事情。
    在这里插入图片描述
  • 构造函数的访问级别是否设为私有
    在这里插入图片描述
    静态局部变量 a。由于其是静态的,它只会被初始化一次,即在第一次调用 getInstance() 方法时。
    静态局部变量 a 的生命周期是从它被创建开始直到程序结束。
    每次调用 getInstance() 方法时,都会返回对同一个静态局部变量 a 的引用。任何后续对 getInstance() 的调用都返回对同一个实例的引用,而不会创建新的对象。

4.重载

  • 注意是否有默认值
    在这里插入图片描述
4.1 成员函数(有this)

在class中的每一个成员函数都默认有一个不可见的参数this,this指向了当前成员函数的调用者。
在这里插入图片描述

在这里插入图片描述

4.2 非成员函数(无this)

在这里插入图片描述

我们想实现复数的三种加法运算符重载。这三种函数绝不可以return by reference,因为他们返回的必然是local对象,在函数体执行完后释放。
语法:typename();

4.3 重载输入输出流

两种选择,一种是成员函数,一种是非成员函数。

当你重载一个操作符作为成员函数时,第一个参数总是调用它的对象实例本身,这对于大多数操作符是适用的,因为它们通常是以对象为中心的操作,比如 obj + something 或 obj == another_obj。

但是,对于流操作符 << 而言,通常的用法是将对象写入输出流,如 std::cout << obj。在这种情况下,输出流对象(如 std::cout)应当是操作符的第一个参数,而要输出的对象实例应当是第二个参数。因为 std::cout 是 std::ostream 类的实例,而不是你自定义类型的实例,所以不能将 << 作为你自定义类型的成员函数来重载,因为这会要求 std::ostream 对象作为成员函数的调用者。

image-20240305161044160

写成 成员函数,用法不符合逻辑,因此只能写为非成员函数。

image-20240305161054047

5.常量成员函数

class里面的函数分为 会改变数据 和 不会改变数据 两种。数据就是this指针所指向的成员变量。

不会改变数据内容的,加上const,用于声明某个成员函数不会修改对象的状态,即这是个只读函数
在这里插入图片描述

6.参数传递和返回值传递

三种:值传递、引用传递、const修饰的引用传递。

6.1 参数传递

在这里插入图片描述

6.2 返回值

在这里插入图片描述
那么什么时候return by value?
如果在函数内部创建了一个变量,并且需要返回该变量,那么考虑值传递,因为当该变量在栈区,函数执行结束后就释放了。

7.友元

在这里插入图片描述
在这里插入图片描述

8. inline函数

函数若在class body内定义完成,便成为inline候选。
是否可以inline由编译器决定,无论加不加inline关键字。
在这里插入图片描述

(4) 带指针类的实现过程

1. 防卫式声明

image-20240305161109504

2. Big Three

字符串里的字符有大有小,所以不能用字符数组来存储,因此在需要的时候开辟内存空间,将指针指向字符串即可(动态分配方式)。
在这里插入图片描述

如果想要实现这样的功能,那么构造函数怎么设计?

image-20240305161120028

image-20240305161124562

2.1 构造函数

image-20240305161130193

2.2 拷贝构造

如果不重写拷贝构造或者拷贝赋值,那么编译器默认的拷贝构造和拷贝赋值函数,是简单的将一个对象的值赋值给另一个对象,也就是说,在有指针成员的情况下,两个对象最后指针指向同一块内存空间,这就是浅拷贝问题。我们想要的是,指向两个不同的独立的内存空间,虽然内存空间里存放的值一样。

image-20240305161134114

浅拷贝会带来堆区的内存重复释放,多次调用析构函数会造成程序崩溃。也会造成内存泄漏,b所指向的空间丢失了。

避免这种情况,也就是深拷贝,如下:

image-20240305161138869

这两种都是调用了拷贝构造函数。

image-20240305161142564

2.2 拷贝赋值

**注意检测自我赋值。**为了正确性与效率。

image-20240305161147241

image-20240305161152442

2.3 析构函数

image-20240305161156213

3.输出函数

在重载<<之前,因为我们实现的是全局函数,又不想声明成MyString类的友元,因此我们需要写一个取得字符指针的函数。

image-20240305161200284

因为ostream可以直接接收字符指针,因此我们取得字符指针传递给ostream即可。

image-20240305161204157

(5) 堆、栈与内存管理

何谓堆、栈?

image-20240305161208780

1. 栈的生命期

在这里插入图片描述
栈是存在某作用域的一块内存空间中。例如当调用函数,函数本身会形成一个stack来存放它接收的参数,以及返回地址。在函数体内声明的任何变量,其所使用的内存块都取自于stack。
c1就是所谓的stack object,其生命在作用域结束后结束,又称为auto object,会自动被清理(调用析构函数)。
在这里插入图片描述
c2是静态对象,作用域结束后生命也不会结束,直到程序结束后,结束生命。
在这里插入图片描述
c3是全局对象,其生命在整个程序结束后结束。

2. 堆的生命期

image-20240305161217914

在这里插入图片描述
new:先分配memory,再调用构造函数。
delete:先调用析构函数,再释放内存。

array new一定要搭配array delete。
在这里插入图片描述

(6) new和delete

1.new

image-20240305161225344

image-20240305161229208

2.delete

析构函数先将字符串指针m_data指向的动态分配的空间销毁,之后再释放指向MyString对象的指针。

image-20240305161234823

image-20240305161239266

注意:array new 一定要搭配 array delete。

image-20240305161243653

(7) static

image-20240305161248127

在没有使用static声明对象的时候,我们创建了三个复数对象c1、c2、c3,这三个对象调用了同一个函数real,但是编译器怎么知道是哪个对象调用的函数real,这个时候凭借的就是this指针。所有成员函数都隐含一个this指针。谁调用real,谁就是this,所以要传入地址。
比如c1调用real,那么传入c1的地址,real里的this指针就是c1。因此不同的对象调用real,传入的this是各自的对象,不同的。

在调用real函数时候,对象的this指针自动传递进去,因此编译器知道是哪个对象调用该函数。

然而,加了static后的数据,与对象脱离,不属于对象,单独在内存中开辟空间。

多个对象拥有同一个静态数据,不属于任何一个对象所独有。比如说:银行系统中的利率,银行的账户对象有多个,但是每个账户共享同一个利率。

静态函数没有this指针,因此不能够像一般函数一样去访问对象中的普通数据,所以只能访问静态数据。

如何调用静态函数?

image-20240305161252570

静态数据成员的定义必须在类外部进行,而且需要在定义时加上数据类型。

单例

诉求:只产生一个对象。

image-20240305161256562

外界无法创建A的对象,因此要提供一个接口可以使外界能取到唯一的A的对象。

但是如果外界一直不使用a,那么这样有点浪费。

image-20240305161300308

这样写的好处是,如果没有人用这个单例,那么该对象就不存在。如果有人使用这个单例,那么该对象有且仅有一份。

(8) 模板

1.类模板

语法:

image-20240305161303920

在类声明中,欲采用通用数据类型的数据成员、成员函数的参数或返回类型前面需要加上类型参数。

image-20240305161306969

2.函数模板

语法:

image-20240305161310632

image-20240305161313825

(9) namespace

东西包装在命名空间中。可以保证同名的东西不会交叉,在各自的命名空间中封装。

(10) 类与类之间的关系(面向对象的思想)

1.继承

inheritance,表示is-a

image-20240305133426721

继承的特性:

  • 父类的数据是可以完整继承下来的。
从内存角度看

子类的对象中含有父类的成分。

image-20240305133932189

2.复合

Compositon,表现为has-a

比如,queue这个class中有个变量c,c是一个deque类型。那么queue和deque表现出来的关系就是复合。

屏幕截图 2024-03-04 163445

Adapter:已有完善、功能很强大的类,根据客户需求修改接口来实现具有一小部分功能的类(改造一下),所使用的接口都是已有的类里的。

从内存的角度看

image-20240305113521918

composition关系下的构造和析构

image-20240305113656682
构造就像搭积木,一层一层由内像外。因此在调用container的构造函数时候,第一步先调用component的构造函数。
析构就像剥洋葱,由外向内一层一层剥开。因此最后才调用component的析构函数。

image-20240305115237911

3.委托

两个类之间的关系是用指针相连。

image-20240305115454818

如图所示:String类只是一个对外的接口,具体的实现都在StringRep类中。

有三个不同的String对象同时指向"Hello",那么是共享同一份数据。

特点就是:读时共享,写时拷贝。copy on write。如果a想要改变内容,那么单独提供一份副本给a,b、c继续指向同一个"Hello"。

(11) 虚函数与多态

函数继承的是调用权,子类可以调用父类的函数。

重点在于:子类需不需要重新定义父类的函数。

image-20240305135436127

  • 非虚函数:子类不可以重写。
  • 虚函数:子类可以重写,不重写的话也有默认的定义。
  • 纯虚函数:子类一定要重写,否则编译不通过。
  • 空函数:子类不需要重写。
Template Method

image-20240305141704694

父类设计好某些功能的通用、一般性的实现,只留无法决定的函数交给子类去重写。

如图,创建一个子类的对象myDoc,调用父类的函数OnFileOpen(),在OnFileOpen函数的执行过程中,发现Serialize函数被子类重写了,因此转过去执行子类重写的该函数,最后再回到父类的OnFileOpen中执行。

image-20240305144032438

继承+复合关系

构造和析构

image-20240305145041323

image-20240305145058843

image-20240305145219065

image-20240305145245957

继承+委托关系
observer

什么情况需要用到这种设计模式?

假设我们有一份数据,但是需要用多种方式对这份数据进行查看。

image-20240305145436943

如图所示,我们只有一份数据,但是需要用多种不同的方式对数据进行查看(直观、柱状图、折线图等等)。拥有同一份数据的副本我们就可以想到委托的特点读时共享。

image-20240305145646612

image-20240305150826859

image-20240305150854932

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值