# Lecture12 - 多态

& 引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。

引用的声明方法:类型标识符 & 引用名 = 目标变量名;

return *this 返回的是当前对象的克隆或者本身(若返回类型为 A, 则是拷贝, 若返回类型为 A&, 则是本身 )。return this 返回当前对象的地址(指向当前对象的指针)

image-20221206140630613

image-20221129140810020

# 操作符重载

# 函数重载

  1. 名同、参数不同,返回值不同没有用的:参数顺序、参数类型匹配 (找到最佳匹配)
  2. 静态绑定

# 歧义控制

最佳匹配:

  1. 原则一:这个匹配每一个参数不必其他的匹配更差
  2. 原则二:这个匹配有一个参数更精确匹配

整型提升:标准转化都是一视同仁的(char to unsigned char, char to double)

窄转换:是被允许的,把表数范围大的 赋值给表数范围小的叫做窄转换,特点是 不安全 不是自动的 可能会发生精度丢失 需要进行强制类型转换

image-20221129141712045

image-20221129142310557

至少要包含一个用户自定义的类型

image-20221129143856395

image-20221129143908332

# 可重载的操作符

操作符的重载本质上是函数的调用,不能够改变原来的语法

不可以重载的操作符:

. (成员访问操作符)、

.* (成员指针访问运算符,如下)、

:: (域操作符)、

?: (条件操作符)、

sizeof 也不重载

  1. 原因:前两个为了防止类访问出现混乱
  2. :: 后面是名称不是变量
  3. ?: 条件运算符是跳转和流程控制,重载之后会出现理解的偏差

image-20221129150203584

  1. 格式: <ret type>operator #(<arg>)
  2. this: 隐含,必然是第一个参数

image-20221129150334475

全局函数要有两个参数

?不懂

注意: =()[]-> 不可以作为全局函数重载

  • 大体上来讲,C++ 一个类本身对这几个运算符就已经有了相应的解释了。
  • 如果将这四种符号进行友元全局重载,则会出现一些冲突
  • 下标和箭头运算符为什么?有保留调用顺序,我们希望能保留原来的顺序,而全局不能要求,而成员函数的 this 就可以解决这个问题
  • 参考

image-20221129151553515

需要支持一些交换律,全局函数可以作为补充

全局函数作为补充:

  1. 单目运算符、类型转换函数最好重载为类的成员函数(不存在上图出现的情况)(单目操作不需要补充)
  2. 双目运算符最好重载为类的友元函数

image-20221206141107489

image-20221206141117260

​ 返回什么值,由应用场景决定,返回右值的就要按值传递,临时变量。

​ 四则运算大多数都是右值。

​ 其他大多数都是左值。

image-20221206141829112

image-20221206141838660

prefix 和 postfix 是不一样的。

prefix & 返回引用

postfix 返回值 dummy argument 传值没有用,仅作为区分

# =

image-20221206142025129

如果程序员没有提供等号的操作符重载,编译器会提供默认的赋值操作符重载函数。

A a,b;
a = b; // 赋值操作符
A b;
A a = b; // A a (b); 拷贝构造函数

必须同时有赋值操作符和拷贝构造函数。

一个对象只能被初始化一次,所以需要有赋值。

拷贝构造函数效率更高,且会被经常使用(函数的传参)。

image-20221206144008728

A & operator = (const A &){
    
	return *this;
}

等号是右结合的

a = b = c

(a = b) = c

可以按左值 -》传递引用

内存可能不够?

这段代码写得不好,应该先申请,再释放

temp = p;
p = new char[strlen(a.p) + 1];
strcpy(p, a.p);
delete temp[];

image-20221206144737520

上面那个代码如果出现 “自我赋值”,会出现错误,把数据删除了。

解决方法 1:做一个额外的检测。

if(this == &rhs) return this; 
// rhs  right hand side
// 加上正同测试
// 以上只是一个很简单的操作

解决方法 2:先申请再释放可以解决。

# []

image-20221206150836412

可以有两个重载函数吗?可以。

在前文歧义控制的最佳匹配中,可以解释,为什么 s 的构建会匹配第一个重载函数,cs 则会调用第二个重载函数。因为是” 更精确的匹配 “

string s("aacd");
s[2] = 'b' ;
// 第一个重载加上 const 可以使得 const 或者非 const 对象都可以调用
const string cs('const');
cout << cs[0];
const cs[0] = 'D';//const 版本不想被赋值 (返回 const 的),非 const 版本想要被赋值,之后再进行重载的时候就需要同时重载两个

image-20221206152243321

image-20221206153301599

用于内存资源、多维数组的管理。

wrapper

隐式转换,explicit 禁用。

# ()

函数调用、类型转换

image-20221206153818186

image-20221206154436607

问题:为什么禁止在类外禁止重载赋值操作符?

  1. 如果没有类内提供一个赋值操作符,则编译器会默认提供一个类内的复制操作符
  2. 查找操作符优先查找类内,之后查找全局,所以全局重载赋值操作符不可能被用到

# ->

image-20221208141511572

CPanel 中有一个 CPen,需要修改 pen 的颜色

CPanel c;
c.getPen() -> SetColor(16);
// 箭头重定义之后
c->setColor(16);

重载时按照一元操作符重载描述

必须返回指针类型?不是,还可以返回一些能够进行箭头操作的引用。

# prevent memory leak

image-20221208143634290

一定不会出现内存泄漏吗?

void test(){
	if() return; //return 的时候就没有办法 delete 了
	throw; // 以上是
	delete p;
}

多出口程序的处理出现了问题。

  1. 用 RAII 进行处理。将 A *p = new A; 写成 AWrapper p(new A); ,资源和对象同生命周期,栈上的对象一定会消亡,不需要管理多出口问题,且是可控的。

    将箭头操作符重载 A *operator -> () { return p;} 使得申请的资源暴露,就像普通的指针一样使用。

  2. 所有类型都需要写一个 wrapper 吗?

    不需要,利用泛型 / 模板解决。

  3. 如此用 wrapper 封装,有什么局限性?

    资源和对象必须同生命周期。

    解决方法:例如,shared_ptr

# new、delete

new 和 delete 的操作步骤

new

  1. 获得一块内存空间
  2. 调用构造函数
  3. 返回一个正确的指针

delete

  1. 调用析构函数
  2. 确定指向分配空间的指针
  3. 归还内存空间

image-20221208144624639

系统自助管理的缺点:系统频繁调用系统的存储管理,影响效率。并且会造成内存碎片。

重载 new 和 delete 之后:程序自身管理内存,提高效率

重载过的 new 和 delete 是静态成员:默认是 static,不能调用非静态函数

image-20221208144830546 返回类型:void * ,不知道 new 什么类型

返回参数:第一个参数是固定的,其他参数可有可无。系统知道要申请的大小,参数 size_t 是给程序员用的。

new 是可以被继承的。

定位 new A *p = new (place) A

可以自己在栈上申请一块内存,反复新建和销毁 A。好处是,效率高(?

image-20221208151134563

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

quas-modo 微信支付

微信支付

quas-modo 支付宝

支付宝

quas-modo 贝宝

贝宝