C++学习(自用复习)

day/0

1:引用易错处

int &ref=10是错的

但是const in &ref=10是对的 

因为

 

 

2:

引用的本质

int &ref=a  即const int *ref=a;

 

3:

函数默认值注意事项

 

4:

引用可以作为函数左值,不要返回局部变量的引用(因为结束了就释放了,第一次还正确是因为编译器作了保留)

 

5:

引用注意事项

 

 

6:

前两个都是在程序执行前就存在的

 

 

7:常量引用

//error 不是内存空间,是自变量

 

//error 如图中注释所示,但是const int &ref是正确 

 

 

 

 

 

 8:常量引用常用处

 

 以免误改val的数据(此处改了外面也会被改)为什么要这样?因为我们要传实参,有时又不想让实参跟着改变,那么就可以用const来修饰形参。

 

9:占位参数

//error必须要func(10,10)

 

 

10:

函数重载

函数重载注意事项

 

 

 

 引用可以作为重载条件

 

 

(这两个可以作为 重载,属于类型不同)

但下图调用的是哪个呢?

 

 

第一个版本(没有const)因为a是一个变量

 

 

传常量10,用的const,如果用上面的,即变为int &a=10  不合法  而下面则为const int &a=10 合法;所以加const和不加const可以成为重载的条件。

 

第二个是默认参数的坑

 

 可以,但是设一个默认值

 

 首先在语法上可以通过,但如果操作的时候

 

 

第一个也能调用!出现二义性,报错。

11:

C++面向对象的三大特性:封装 ,继承, 多态

每个对象都有其属性和行为

- 封装

 

 权限访问

 

 

 12:

struct和class差不多,但还是有区别

 

 

 

13:

为什么常把成员变量设置为私有,第一可以自己控制其访问权限,第二对于写操作可以检测数据的有效性。

设置私有属性之后,然后用public来对属性操作操作(从侧面操作),这样就可以只读但不能写了。

甚至还能反过来只写不读

 

 没有public的读操作是不能读出的。

控制数据有效性

 14:

对象的初始化与清理

构造函数与析构函数

 

 写了person的构造和析构函数之后,调用

 

 

发现两个函数都使用了,这是因为这个对象是在栈区。 

 

拷贝构造函数(只有这种形式叫拷贝构造)

 

 

 

构造函数调用方法

括号法

 

 注意默认构造不要加括号

显示法

 

 编译器会出错,显示错误为重定义

 

隐式传唤法

15:

 

 

如果类的设计者不写复制构造函数,编译器就会自动生成复制构造函数。大多数情况下,其作用是实现从源对象到目标对象逐个字节的复制,即使得目标对象的每个成员变量都变得和源对象相等。编译器自动生成的复制构造函数称为“默认复制构造函数”。

注意,默认构造函数(即无参构造函数)不一定存在,但是复制构造函数总是会存在。如果编写了复制构造函数,则默认复制构造函数就不存在了,编译器如何知道是复制构造函数?便为class (const class &a ){what = a.what};

复制构造函数的参数加不加 const 对本程序來说都一样。但加上 const 是更好的做法,这样复制构造函数才能接受常量对象作为参数,即才能以常量对象作为参数去初始化别的对象。

 拷贝构造用途常见为三种

 

 

  第一个方法前面已经介绍

用途2:

 

doWork(p)这个实参p传到形参的时候,是使用了拷贝构造函数,形参p会根据实参p拷贝新的数据。(与dowork函数本身里的p值无关,因为是新数据)

 

用途3:

 

 

 同样会调用拷贝函数,怎么会?不是局部变量函数调用完就应该释放吗?这是因为return的p1不是 函数里的那个Person p1,而是通过拷贝构造函数返回了一个新的对象返回给了函数外。

 

  其实后面两种就是值的方式传数据和值的方式返回。

16:

浅拷贝与深拷贝

浅拷贝:简单的赋值拷贝;深拷贝:在堆区重新申请空间进行拷贝操作。

//WARNING 以下代码为错误展示

 

 

//注意,此处代码m_Height为指针类型,并且在person里添加了此属性,即int *m_Height.并且new函数是在堆里生成并且会返回一个定义类型的(此处为int)指针。

所以输出时候需要*p.m_Height;

 

 析构代码的用处在此处展现,因为堆区数据需要自己释放。

 执行后会出现错误,为什么?因为这是浅拷贝

浅拷贝

 

 注意 m_Height在两个对象里面存的值是一样的,而当p2执行了析构函数之后,p1再执行析构函数会发现堆区内存重复释放!因为m_Height确实不为空,但是已经释放过了。

 

怎么解决呢?

:深拷贝

重写一个拷贝函数

 

 

 

 

 总结:如果属性有需要在堆区开辟的,一定要自己提供拷贝构造函数

 

 17:初始化列表

注意冒号位置,在形参列表后一个冒号,然后属性/此属性的值,属性/此属性的值。

 

18

 

 注意,这里传的是一个string pName,但是初始化的属性m_Phone 是phone对象而不是string(如下图),但是依然能够成功

 

 这是因为其相当于隐式转换法,而刚好前文有string类型的构造函数

隐式转换法:

 

 

 

 于是便可以进行初始化操作。

 

19

类对象作为类成员

A {

B b=B();

}

先构造其他类的对象,再构造其自身。(先B后A)而析构的顺寻于构造相反(类似于栈,后进先出的感觉)

 20

静态成员

 

  编译阶段分配的时候意思就是程序运行前就有了,编译器在编译分配内存的时候就有了
类内声明:                                 类外定义(初始化)(因为不知道谁的成员,必须限制作用域):

 

 

 

 

 不过要注意的是,静态成员变量也是有权限的,比如private的静态成员变量类外就无法访问了。

 

静态成员函数:

 

同样的 静态成员函数也有两种同样的访问方式

(此处未截到图的地方是static void func)

 

 

 

 为什么会使用非静态变量会出错?因为静态static成员函数它只属于类本身不属于任何一个对象实例,独立存在。非静态成员,仅当实例化对象之后才存在。静态成员函数产生在前,非静态成员函数产生在后,静态函数无法访问一个不存在的东西。

 同样的

 

 

 

 21 成员变量和成员函数分开存储。

 

 如果是一个空对象,他占用的内存空间为1字节。这是为了区分空对象在内存的位置。

但如果不为空 假如有一个非静态成员变量,例如int a;那么为4字节 并且其属于对象上的数据

 

 注意是算的sizeof(p) 所以是算的对象的数据,加入了一个静态成员函数,其值不变

 

sizeof(p)之后数值不变。

21  this指针。

this的本质是指针常量(指针常量不可修改!所以this也不能修改指针指向,但是可以修改指向的值)

知道了c++中成员变量和成员函数是分开存储的,可是 比如有对象p1调用函数,p2调用函数,怎么区分是哪个对象调用的呢?

p1调用this指向p1,p2调用this指向p2

this指针的用途

 

 

 第一个其实就是解决名称冲突,第二个返回对象本身

第一用途:

 

 

 注意,上面三颜色一样的,被编译器看作是同一份,所以根本不是传给成员变量了。如何更正呢?使用this

 

这时候Person p1=Person p1(17)便正确了,并且this指向的p1.

 

 用箭头是因为this指针

 第二用途:

首先增加一个成员函数(非静态)

 

 

 把人的年龄加到自身上。可是觉得加一次还不够,想多加几次,于是   //下面的代码会出错

 

出错了,因为函数调完了之后返回了void。所以不能再调了。怎么更改?只要返回了p2本身,不就可以一直调用了吗。

 

 

 

请注意,不仅加了return *this 还需要声明是返回一个引用!

 

 

  不加引用返回一个值会出现什么呢?在第一次调用之后,p2加了10岁之后,返回的不是p2的本体了,而是用本体来创建一个新的数据(通过拷贝构造函数 ),因为以值方式返回局部对象会调用拷贝构造函数。

所以p2还是经过了一次加年龄的操作的,但之后的操作就是针对p2'、p2''了,所以p2.age不会一直增加

 

 

 

 

 

引用指向本身内存,不用引用就是拷贝了,而拷贝指向另一个内存。不加引用的话返回的就不是p2了,而是别的Person 对象

 22: 空指针调用成员函数

设置两个函数

 

创建一个指向NULL的指针,然后调用上面两个函数

 

 

 结果崩了 ,而且检查后发现第一个能正常运行,是第二个的原因,为什么?

因为第二个函数用到了一个属性m_Age,其实每一个属性前面都加了一个this->m_Age,可是这个是空指针,this根本没东西,所以为了增加健壮性

 

 

 24: 成员函数。

 

 

 this的本质是指针常量

 

 

 如果没有上面的const,第一个注释是可以实现的(这里是为了明显化,如果m_A=100也是一样的,忘了看23),因为虽然指针常量(this)不可修改,但是可以修改指向的值)

但是加了const之后 指向的值也不能更改了。

但是也有特例 加入mutable关键词,就算是常函数,也能进行修改。

常对象

 

 常对象不能调用普通的成员函数,只能调用成员函数。

 

25: 友元

 

 

友元有三种方法

第一种是全局函数作友元

//注意以下代码会报错

 

 

 客厅可以访问,卧室不能访问,可是想让特殊的人访问卧室怎么办?

把这个全局函数 复制粘贴到类的最上方,前面加上friend 后面加上分号

 

 

 之后便能正常运行,

 第二种是类里用友元。

 

 building类实现不变。可以用点花样,比如在类外写成员函数。

比如这个函数在building类外:build();

 

 

lover里应该干什么呢?首先要有一个build的对象呀,然后在类外实现visit函数。(注意:好像class类里面本身需要其声明,类外在此处属于是实现?)

//下图代码有误(与此处所学知识)

 

为什么?因为lover类里没有声明class,而c++中,对类的成员(包括成员变量和成员函数)没有定义属性,默认是private。

 现在在lover visit里添加访问卧室,发现 编译器提示有误,怎么修改?

只需要在class声明朋友就行。

 

 

 以上就是友元类。

友元还有第三种方法  成员函数作友元。

 

 

 

 其他的和之前差不多。

 

26:C++运算符重载

这个用‘+’完全不行

 

所以要自己修改+符号的定义

 

 

上面是成员函数运算符重载。下面是全局函数重载(右边是person类加整数类)

 

 重载左移运算符

 

 怎么做? 重载成员函数?但是要记住重载是p.operator<<() 所以

 

 可我们是想cout<<p的呀 所以一般不会用成员函数重载左移运算符,因为cout只能在右边。所以一般都用全局变量

 

 但是cout是什么类型呢?

 

 标准输出流对象,所以修改代码:

 

 cout<<p便能成功执行,但是cout<<p<<endl又会 失败了,原因就像之前的链式编程 一样,这个重载函数返回一个void void怎么能够<<endl呢,所以和之前一样,要return 对象(此处是cout而不是*this哦)

然后函数返回值要改成引用

 

 如果想要用此自定义运算符重载区访问私有属性,用上一节的友元弄到类里。

 

27:

首先定义一个类型

递增运算符重载

 

 目的:

 

 

 

 

 

 为什么要这样  不做自身返回后面cout又会出错了。因为会返回一个void!,void怎么继续<<呢?

 

还有记得要返回引用,不然返回值调用拷贝函数,是另一个变量了,如下图

 

打印如下:

 

 因为不是一个对象了,做了一次递增之后调用拷贝函数返回的是一个新的对象。

之后的后置递增怎么写呢?返回值不能构成重载条件,很方便加一个int占位符即可,表示和之前函数不一样了。

 

 

需要特别注意的是,此处返回的是一个值而不是引用。因为返回的temp是一个局部对象,在当前函数执行完之后再访问就是非法操作了。(第一次使用是因为是编译器保留了)

 

 28:

=运算符重载。

首先:

 

 编译器提供的最后一个函数,赋值运算函数。注意赋值运算符也是值拷贝,只要是值拷贝基本都会出现深浅拷贝的问题。

 

 

 执行后发现正常运行

 

 但注意!已经内存泄漏了,因为没有析构函数,加上析构。

 

 发现程序直接崩溃了。遇到了之前的浅拷贝问题。怎么办?重载赋值运算符成深拷贝

 

 为什么要判断是否有属性在堆区中?因为原先已经有一个了。

 

 

 在正常的赋值运算符中

 

打印出来都等于十,但是想用修改的赋值运算符却出错。为什么?

 

 看前面  因为返回的是一个void。所以还是要返回自身。

29:

重载关系运算符:

 

 重载函数调用运算符(重载小括号)(仿函数)

一个有点相似的函数

 

 

 

 

 

 

 

 

 匿名函数创建仿函数

 

 

 MYadd()使用匿名函数(以后看见一个类型一个小括号,第一反应就是一个匿名函数)再使用重载的小括号

 

30:

继承

 

 

 

 

继承1:继承中的对象模型:

 

在父类中所有非静态的成员属性,都会被子类继承下去。(私有成员也会,但是被编译器隐藏了。)

怎么看?打开 VS开发人员命令提示符

 

 

 

 

 

 

 报告单个对象布局+类名  空格  03+tap键位补全。(03为左边编号)

 

 

继承2:

继承和析构的顺序谁先谁后?

 

 继承3:

同名成员处理方式(成员属性+成员函数):

子类优先级比父类高,但是怎么通过子类对象访问父类中的同名成员呢?加作用域

 

 

 

继承4:

 

上面父类重载了,下面子类重写了func()(没有int哦),然后func(10),报错!因为:

 

31:同名静态成员, 与同名静态成员函数:

 

 类名访问下面第三行

第一个双括号,意思是要通过类名的方式访问数据。第二个双冒号代表访问父类作用域下(其实直接Base::m_A也可以,但是是通过父类作用域,如果想通过子类访问父类就得这样)

同名静态成员函数

 

 

 注意:静态成员函数如果子类重载了,会覆盖所有父类同名函数(无论是不是重载过的)

 

32:多继承(不建议)

语法:

 

注意要声明

 

 

 不然会出现二义性。

 

33:菱形继承

 

 

 

 当羊驼又继承羊又继承驼时,会出现二义性。(下面代码可以运行并且正确,但是羊驼怎么会有两个年龄呢)

先看路径

 

 

 

 

 cd 路径之后dir

 

 

 

 (打08的时候能够tap补齐)

怎么解决菱形问题?用虚继承

 

 当用了虚继承之后  数据只会有一个了

 

 之前红线是会报错的,因为有二义性(输出为28,因为先赋值18然后赋值28)并且内容也不一样了(左边是虚继承)

 

 vbptr:虚基类指针 V-BASE-PTR(pointer)这个指针会指向vbtable(虚基类表)

 

 虚基类表记录的是偏移量,类+偏移量的地址上的数据就是想要的。(两个虚基类拿到的都是指针,不是继承的两份数据)

 

35:多态

 

父类引用指向子类对象(Animal &animal = cat)

 

 

 猫在说话还是动物再说话? 

动物。

为什么?因为地址早绑定,在编译阶段就确定了地址。想猫说话,使用动态多态,即地址晚绑定。怎么做?

 

使用了虚函数之后,speak()函数的地址并不是上来就确定了,要看传的对象走不同的函数

 

子类重写父类虚函数(注意重写和重载不同)

 

 

子类的vitual可写可不写,父类必须写(如果子类的子类需要那么子类也要写) 

 36:多态原理剖析

首先sizeof 一个class类,只有一个非静态函数的,发现他只有1(和成员变量分开存储)

然后添加virtual sizeof 发现有4个比特  为啥?首先想的指针,毕竟是4

(成员函数要加入作用域,然后需要地址加上&)

 

 子类也研究下,首先不重写,即继承

 

 

 

子类重写虚函数:直接把下面的&Animal::speak覆盖了

发生多态的条件:

 

 传的是什么对象,就走谁的虚函数表(通过替换的函数的入口地址)当调用公共的接口时
会从子类的去找函数的切实入口地址

 

37:

注意new是在堆里建立的,记得销毁。注意在销毁之后还是能继续abc=子对象。因为指针的类型没有被销毁。

 

 

差了什么? 销毁! 别忘啦

38:纯虚函数与抽象类

 

 无法实例化

 

 

 

为什么要多态?就是想通过一个父类指针(一个接口)能根据不同的子类对象调用不同的函数。

 

39:虚析构,纯析构

 注意如果子类没有堆数据不必需要虚析构。

 

40:实例中电脑类不知道怎么做

根本没想到要先设置属性,然后用设置好的属性指针进行接受

目的:

:

实现:

 

 电脑组装好后

 

 能正常运行,但现在有一个问题。

电脑是在堆区创建的并且释放了,但是零件也是在堆区创建的却没有释放。现在有两种方法释放,第一种是挨个delete,

第二种就是在delete(释放)电脑函数的时候,会走computer的析构函数。所以可以提供一个析构函数释放三个电脑零件。

 

 

 

另外在创建电脑时,也能

 

41:写文件

 

 

 

 读数据常用四种方法

 

 

 

44:二进制读写文件

二进制读写文件比起文本文件功能更多,除了内置数据类型(int double)等等,还能操作自定义数据类型。

 

posted @ 2022-09-14 20:35  熵0w0  阅读(31)  评论(0)    收藏  举报