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)等等,还能操作自定义数据类型。


浙公网安备 33010602011771号