C++学习笔记(八)

C++学习笔记(八),第1张

文章目录
  • 多态
    • 什么情况叫多态
    • 覆盖重写overwrite
    • 区分好覆盖重写overwrite和重定义
    • 纯虚函数和抽象类
    • 抽象类和定义对象相关的规定
    • 接口
    • 虚析构函数
      • 为什么要写虚析构函数
    • virtual的本质
    • using的打孔
      • 不用using的解决方法,新写另一个方法间接调用,但开销大
      • 使用using
    • 访问权限控制本质是编译时编译器帮你检查的,而不是运行时

多态 什么情况叫多态

这里讲到一个多肽啊,然后就跟曲函数挂钩了,就就先拿举个例子啊,比如说你现在是定义了一个animal的一个负类,然后有一个汪汪汪狗的一个子类,然后有一个喵喵喵猫的一个子类,是吧?然后你首先在幂函数里面,你三个对象都创建出来,比如说animal a,然后dog。D kat c,你把三个对象都创建出来,然后这个时候你再定义一个animal类型的一个指针,比如说animal新P,然后这个时候你用P去指向你取的那个D里面的那个地址。

就说你animal新批嘛,然后批等于一个and的,就是你取你这个,你这个P指向的是animal类型的,但是你这个的呢,他的是dog类型的,但是你之前说这样是可以的,And取只dog,然后等于他那个animal类型的那个P指针,然后你这时候你去执行这个,你去执行这个函数。你会发现什么,你会发现嗯,它的这个方法,这个呢,你去执行它那个那个那个D的speak方法,比如说d.speak是吧,按理说,那个d.speak里面写的是汪汪汪才对是吧,然后呢,你d.speak你执行,你在那里面写的时候你执行,你会发现它执行的是animal speak也是说执行的是那个animal,那个父类里面的speak。

它执行的是a.speak,也不是d.speak,这个时候的话,你就就怎么去解决这个问题呢,按理说你你这个的本来就是只狗,应该就是汪汪吗,但是你他无法让他汪汪,只能是让他animal speak,让它变成子类,反而让它变成负累的那个animal,因为你用的是animal数据类型吗,这时候怎么办呢?这时候你在那个定义,那个animal,那个方法里面啊,你在定义animal里。方法里面你用virtual把那个方法给virtual掉,就你那个animal里面是不是也有一个speak方法,你把那个speak方法前面加个virtual,就是virtual speak括号,分号嘛,就你把那个东西也watch掉掉,你把这类里面的那个方法,那声明的时候啊,那个speak那方法声明的时候,你在前面加个virtual。

那么这个时候你再去重新执行内函数,你会发现就变成汪汪汪了,这个前面加个visual就是虚函数。

最前面刚刚是animal新P,然后P等于N的D是吧,然后你这个时候下面用的是不是d.speak啊,是D箭头speak啊,因为它这个是指针来的吧,是吧,所以它不是点是是箭头,是谁是所以说是D箭头speak啊。

你如果是想要用猫叫的话,那你就是,嗯你就是animal新批嘛,等于那个end c,就是然后你在这个时候,你再用c箭头speak,就可以用K喵喵了,因为你在那个animal年方里面那个类里面定义的时候,你声明的方法,你那个animal speak方法的时候,你前面加了一个virtual,变成virtual speak括号就是,就变成。你本来声明的时候,你在那个类里面声明是是不是void,然后speak括号里面写个void封号,是不是你本身在那个animal那个类里面声明,这方法这么声明的,你在写你要你你变成区函数,就变成virtual void speak,括号void,分号嘛,就在你的那个声明的前面加virtual嘛。

这种现象,你看你现在看到的这种处理方法,这种做法你就可以理解为这就是多肽,因为你多肽本身是一个比较抽象的,我们平常生活中没有没有过这个词,你可能不知道多肽什么意思,这个就叫多肽。

覆盖重写overwrite

这种实现多态在父类,也就是积累的方法,前面加一个virtual,这种方法叫做重写,也就叫overright,也叫覆盖。

区分好覆盖重写overwrite和重定义

一定要区分这个重定义,还有这个覆盖也就重写,宏观上来看就是重定义,宏观上来看就是那个,嗯,那个覆盖重写,这个宏观上来讲,它是实现了,它是有多肽的。从微观上来讲的话,覆盖也就是这个重写,它是有watch的,它的负累是有watch的,负类的方法也是有watch的。

纯虚函数和抽象类

那接下来讲一下纯虚函数,纯虚函数就是你在virtual后面还加个等于零就你父类的方法吗?你复类里面方法之前不是加了virtual吗?这叫虚函数吗?那你这复位的方法呢,你后面再加个等于零分号,就是在他这个函数的声明的最后啊,给他付一个零,然后它他等于零就是virtual void speak括号,Void等于零星号,这时候他就变成纯虚函数了。那么纯虚函数有什么用呢?纯你用了纯虚函数,你用了纯虚函数的话,它这个函数,你只需要在类里面声明就就行了,你这函数可以不用实体的,就你不用在另外一个CPP文件里面把这函数的定义写出来,它可以不用定义的,它没有实体都行,你只要声明了就行,你只要在类里面声明了就可以。

都会有程序函数这个概念呢,这个从语法上面是完全没问题的,他其实是语义上面有问题,就从从语义上来讲,如果我们要实现多态的话,那么我们这个复类里面,这个函数是完全没有用的,完全没有实际实际作用的,就是那个,所以他你要时,你如果要实现多态的话,你关键是你只类里面那些多肽的方法是吧,所以你父类的方法是没有用的,没有意义的,所以从语义上来讲,这种这个函数体是可以,这个方法提示这个函数的定义是可以省略的,也不用写那没意义的,所以它是从语义上来讲的,所以可以省略的,它是这样子的。

那你如果要实现多肽的话,那么负类的那个,那个本身相同名字的那个方法体,它本身是没有意义的,因为你要执行的不是它,而是你多肽那些猫啊、狗什么的,所以它存不存在都都无所谓的,它是没有意义的。所以它从语义上来讲,是不需要的,但是它,但它从语法上是过得去的。

不是从语法上面去看,语法上面是没有问题的,完全合乎常常理,但是他他为什么要用纯虚函数,是从语义上来看呢?

因为你这个函数啊,你这个函数你的方法如果在外面定义了,他有方法体,它就要占用内存的,所以你不要浪费毫无意义的内存嘛,所以就把他干脆就用纯虚函数,所以你用纯虚函数,它是不占内存的,因为它是在类里面的,她在类里面定义的,它是在这个class里面定义的,它就是一个类别,它是不占内存的,但是你那个你类里面的方声明的那些方法,你再累的外面去用吗?Y你在内的外面去定义吗。那些定义的东西是要占内存的吧,所以你如果那个speak,那方法你定义在外面,它是占内存的,然后你如果它是存续函数,你把它给函数题给它,给写空就把,或者把它删掉,不用你就把它写个程序函数放到内里面,它这样就不占内存了。

你用纯虚函数写的方法,包含就你包含了纯虚函数的方法啊,不是你你做你用,你包含了纯虚函数的种类啊,就你是只包含了纯虚函数的种类,它叫做抽象类,抽象类是不能够定义对象的,所以你啊,他也是不占内存的,所以你用了animal a,这个时候呢,你定义这个对象他就定义不了了,因为你用了它里面用了纯虚函数了,所以用了纯虚函数,它是不能定义对象的,他是不占用内存的,然后这个抽象类啊,它不能用来定义对象,他只能用,他只能作为一个鸡肋,然后去给派生类去使用。

虚函数是不占内存的,尽管你是,你最后你调用了它的一个派生类吗?Pad里面不是也包含了他,那个那个鸡肋的那个方法嘛,是不是,但是其实这里是用了一个right吗?是重写的,所以你尽管那个派生类里面用了,比如说比如说用那个思必客那个那个方法,但他用的也不是激烈的那个那个虚函数的方法,它是派生类里面它自己重写的一个方法,所以它不属于几类里面的方法,所以鸡肋呢,实际上也还是不占用内存的。所以纯虚函数这种类啊,这种抽象类啊,是100%不占用内存的,即使你派生类包含了这个鸡类,但是其实你那个包含同名的那个方法是派生类,它重写之后,过后的不是之前的那个基类里面那个虚函数的方法,所以它无论如何虚函数那个抽象类啊,它是不占内存的。

但是你要注意啊,抽象类里面,你只是要求里面那抽象类里面至少有一个纯虚函数,就说你至少有一个纯虚函数,就叫抽象类了,但是你并不一定只有一个纯虚函数抽象类里面,它是可以除了纯虚函数之外,有别的有实体,有函数实体的一个方法的,就你可以写一个Bo的print void进去,那个抽象类里面的就说他不可不一定只有不一定要求一定只能有需有纯虚函数有实体的函数也是可以有的,但是你只要有一个纯虚函数在里面,那么这个类就叫抽象类了。

他所谓的抽象类不占内存啊,其实是错的,是抽象,是抽象类里面的纯虚函数不占内存。比如说你这个抽象类里面,你写了一个纯虚函数speak,然后你又写了一个有实体的函数,你又写了一个有实体的函数。

你又写了一个有实体的函数print,那么你当你这个抽象类作为基类去被派生类继承的时候,它的那个print是有占内存的,它只是那个speak不占内存而已,因为speak是纯虚函数,但是print是占内存的。

抽象类和定义对象相关的规定

那这时候你就很奇怪,就是你刚刚说的那个抽象类吗,他不能实例化对象吗?那你这时候你又抽象类里面又可以写有实体化的东西,那你里面有实体化的那个盘数的话,就你刚刚说抽象类里面只要有一个,只要至少有一个程序函数就行了,其他都是可以有实体化的,那你其他有实体化的,你又不能定义对象,你有实体化的方法干嘛呢?所以它这里有个规定,就是你抽象类里面。你必须保证你里面的那个存续函数被使用了,也就是说被你的积累呀,不,也就是说被你的派生类给重写了,给覆盖了,给overright了,这个时候呢,它这个抽象类才能够去创建实体对象,就是说,一旦你的抽象类里面的存序函数被你的派生类去使用了,就是去覆盖重写overright了,你才。

情在这种情况下呢,你的那个怕你的那个抽象类就可以打破那个他不能创建对象的这个这个规定了,他就可以去创建对象了,就可以用他这个抽象类去创建对象了。

也不是说那个抽象类里面的那个纯虚函数被使用掉了才能创建,对象是他的抽象类里面的纯虚函数被你的派生类给覆盖重写,Override掉了,才可以就说你的你的派生类里面必须要写上他的这个几类里面的这个,这个也就是抽,也就是这个抽象类里面那个纯虚函数,你在派生类里面必须要写上去,要覆盖重写override,让他,这样的话,你才能够创建派生类的对象。

如果你的这个派生类啊,你没有over write,要他这个这个抽象类,也就是几类里面的纯虚函数,那么你这个派生类也会被传染,它也会变成抽象类,也不能创建对象,就比如说朱老师他这里,他又重新定义了一个类,这个类叫class X啊,然后继承了那个鸡肋那个抽象类。然后但是它这个X这个类呢,大写的X,那这个大写的X这个类呢,它并没里面并没有overright,也就说并没有写上那个speak,就是抽象类积累的那个,那个纯虚函数并没有写上,所以它就并没有覆盖重写,也就说并没有overright,那么这个时候它的这个大写的X的这个类,它也会被传染。

他也会被传染,然后就也同样变成了一个抽象类,他也他这个造型X这个类呢,他也不能够创建对象。

接口

这里有一个接口的概念,接口是什么?你可以理解为就是它的这个派生类,啊不不不,他的这个抽象类啊,然后里面没有实体的方法,就没有实体的函数,只有虚函数啊,只有纯虚函数,那么这个时候只有纯虚函数,没有其他实体的函数的这种抽象类就可以称之为接口,就是它里面没有任何一个实体函数,只有一个纯虚函数,或者是只有多个纯虚函数,反正是只有纯虚函数的一个抽象类,那么这就叫做纯粹的就是这,这种东西就叫就可以叫做接口。

加加里面它是没有interface这个关键字的,就是没有接口这关键字,但是它从本质上来讲,它完全就是实现了接口,你那个抽象类里面只有存虚函数的情况下,它就是一个接口啊。

然后从这里重新加加,这里也是可以知道他这个接口一般是怎么使用的,就跟我们之前用多肽一样吗,就是你先定义一个接口这种,就这种抽象类的这种类型的一个指针,用这个指针指向你的一个纸类,然后填充好他的这个接口里面的这个程序函数的一个新的函数的那个内容只类的地址,把它传给他,创建个他这种纸类的对象,把他按的传给他这个接口,这种接口,这个抽象类的这个类型的一个变量批就是这么去用的。

然后创建好了之后,你那个P就你就用那P拿去指针,用那个指针去访问嘛,那批箭头speak括号分号嘛,然后这样的话就可以用了,用指针的箭头这指向它就完全搞定了。

P这个指针变量,它的指向的是and and那个你的子你的子类的那个对那个类嘛,所创建的对象嘛,所以你最后你它是一个指针变量嘛,那你就P箭头什么,你就可以用它这个接口的东西了。

虚析构函数

所以讲下须虚构函数,就如果你的这个累啊,你的鸡肋,至少有一个或者有多个虚函数的时候呢,至少有一个或多个虚函数的时候,那么你的这个类的虚构函数要写成虚析构函数,也就是要在虚构函数前面加个virtual。注意,这里指的是虚函数就行了,不一定是需要纯虚函数,就不一定需要后面等于零,你只要在那个前面加virtual变成虚函数的基类里面只有虚函数,至少有一个之后,那么你的这个类就要他的虚构函数要写成虚析构函数。

我们之前写那个不太合适,因为我们当时用的是一个有有虚函数在里面,但是我们的析构函数呢,用的是他C++默认给你的,那么这个C++默认给你的肯定不可能是析构函数,C++默认给你的肯定是一个普通的析构函数,默认的嘛,所以这个就不太恰当了,所以你自己需要写,自己手写一个析构函数给他。

刚刚说的是不一定是纯虚函数的,意思是说你虚函数都可以,那纯虚函数就更可以了,但是你不一定是纯虚函数,纯虚函数,你可以是普通的虚函数,但是你如果是纯虚函数是更可以的。

为什么要写虚析构函数

为什么你当你的那个类里面,它用到了抽象类抽,就你的基类是抽象类里面用到了虚函数的时候,你的这个子类的那个虚构函数写成虚析构函数呢,原因是你朱老师他做了一个实验,你如果是创建了这个子类的,他的对象分配在栈上的,那么它在用的时候,他也会虚构的时候,你不加虚,虚构不加过去。他也会先打印出来dog,然后再打印出来animal,但是你如果换成了把这个子类,它你把对对象定义在了堆上,就是一个六的一个堆上,那么你这个时候再去调用它的时候,你会发现你如果不写虚构啊,不写虚虚构函数的话,就是你虚构函数不是虚的的话。

那么它打印出来就不打印animal了纸打印出来一个dog,这个就是不符合常理的,按理说应该是先打印出dog在打印出animal,这样才能虚构掉,这样才能虚构掉嘛。所以说,当你把它分配在了堆上的时候,那么你你如果你的虚构函数之类的那个虚构函数不写成虚析构函数的话,他们就不正常打,不会打印出dog animal,而是只打印出dog。所以说为了要保证你的这个类之类的创建对象,不管在。分配在哪里?不管是分配在站上还是分配在堆上,都能够正常的使用。虚构函数都是先打印了再打印animal。那么就建议你,如果你一旦使用了抽象类你的鸡肋,一旦使用了抽象类里面有虚函数,那么你的子类的那个虚构函数就就要写成虚虚构函数,只有这样的话,你才满足他,不管是分配在哪一个地方,它都能够正常使用。

因为你写这个类的人,你到时候你根本不知道人家要怎么用你这个类,万一他是要把它分配在站上,万一他是要把分配在内那堆上,你是不知道的,所以你就必须得要保证两种情况都可以使用,都正常使用。

virtual的本质

这这个,至于为什么他会在,你如果在分配堆上就不行,分配在那个粘上就就可以,然后你要加Virtue才行呢,这个你要然后溯源Virtue的本质,Virtue本身就是他让你在运行的时候,动态帮你去决定的,就是说它是在运行的时候,嗯,他看你运行的时候,你你选你觉得需要哪一个,它就变成哪一个,它有点像磨的,我感觉它运行的时候,他再给你决定做决定。所以它watch的本质上一个功能是这个,也就是RTS,就是动态实时,就它在运行的时候,动态的帮你看,你你是哪一个,再帮你给给的是哪一个,它是做了一个这样的一个动态的一个运行时动态决定。

如果你不加watch的话,那么编译器在编译链接的时候,它这个时候就能决定你的这个东西是什么了,但是你加了QQ之后,它是在编译链接之后还不决定的,它是到你运行之后才决定。

这个virtual它,它的本质就是一个r t ti机制,就是wrong time template identify就是动态的运行时进行类别识别的一个机制。

其实加了这个virtual其实很简单,你但凡加了virtual,你其实就是告诉他不要再编译时候确定,在运行时再确定,所以你任何方法你都可以加well,这八家well的意思就是说,你不要让他在编译时确定,在运行时确定,那么这个时候嗯,是不是就很好呢?其实不是的,你加了virtual就代表着你的开销增大了,为什么说开销增大呢?因为你如果是编译时确定的话。你从他编译成汇编之后,在汇编里面,他只需要一个跳转指令就可以实现了,跳转就可以实现跳转了,那么它是很方便的,但是你如果是在运行时确定的话,它在编译的时候编译成汇编上,它就不是单单纯纯的做一个跳转指令,它是要到你的那个运行时候做一个判断。

然后你很容易可想而知,他运行时候做的判断是,它会根据一个你的一张表,然后去一一的去找你对应的是哪一个,你找到对应的是哪个,再去再去跳转,所以它多了一个这一步,所以它比在编译的时候就确定要开销大,消耗的资源要多。

也就是说你要经过一个rg ti的机制,就是runtime template identify,你要经过一个这样一个动态动态类型识别,动态运行类型识别才能够找到,所以它开销是很大的,所以你能尽量确定它不用virtual能可以,可以不用virtual也可以的时候,你就尽量不要加virtual。

所以就是说嗯,你加不加virtual就是看你,就是看你想不想要,让它在运行时确定,然后其实这个时候你就可以意识到,其实多肽的本质就是一个动态实时动态实时类型校验啊,动态实时类型识别就是RTTI,你这时候你现在你可以理解的很深刻的多肽的本质就是让让那个那个识别类型识别,不在编译时候进行类型识别,而是在他到了运行时候再进行一个类型识别,再用http runtime template identified去识别它,在运行中去识别,这个就是多肽的本质。所以多肽呢,其实就是用virtual,然后多肽的话,它其实资源开销是很大的,所以这个就是一个取舍问题。

using的打孔 不用using的解决方法,新写另一个方法间接调用,但开销大

你之前说的这一个就是,就是你的那个负累吗?你的鸡肋里面本身是public,但是你通过继承的那个水龙头加个protected之后,你到了子类之后,他就不是public的吗?那这个时候你就不能直接去调用你那个,比如说你就不能直接用第一.speak吗?因为你的speak可能是是几类里面的吗?然后你通过水龙头之后,那你就变成了。一个protected,所以说你不能直接去访问,这个时候你要在你的子类里面重新写一个方法,然后重新写那方法,那那个方法里面包含了你的那个鸡肋的那个被水龙头限制之后的那个speak方法,把它包含进去,这个时候你再去使用的话,那么你就可以你在,你在使用的话,你就用那个,第一点,你用串串联在子类里面那个方法,那么它就可以去用了。也就是说你要新建一个方法,在子类里面新建个方法,然后在方法里面包含基类的那个被权限限制掉了的那个方法,这时候你在幂函数里面,你去调用那个新建的方法,那么就可以使用,但是这样的开销是很大的,因为你多调用了一层函数吗?是不是很容易就想明白,开销是很大的。

使用using

那么如何在这种情况下有更更好的方法呢?更好的方法就是你在那个纸类里面去,去用using把你那个打孔,把你那个你需要的。那个个别的十个里面有有九个是你要权限管控,那有一个是你想放开权限的,那你就用using,你在纸类里面用一个using,然后using animal冒号,冒号,Speak分号,你用这格式之后,你就可以把这个打孔,单独的把这个speak拎出来,使它的权限又重新变回public。

说你在子类里面写上using冒号,冒号animal,啊不不不不,不是using animal冒号,冒号speak分号,这样的话,你在内函数里面用的话,你就可以直接用第一.speak括号分号了,你这样的话可以直接用了,因为它已经变成public了,你用using的,就把它的权限打孔了,把它这个一个单独拎出来,使它变成public,那么你这样的话就可以在内函数直接用了,这样就节省了开销。

访问权限控制本质是编译时编译器帮你检查的,而不是运行时

为什么会这么神奇呢?因为权限这东西本来就不是运行时做的决定,是你在编译的时候,编译器帮你做了决定,所以说你加个u型,那你是告诉编译器这么做这么做这么做嘛,是不是?所以它其实就是告诉编译器,你这个东西就能单独拎出来。所以这其实是一个很简单的一个 *** 作,就是它是在编译器阶段的,就是你加个u型之后,你告诉编译器啊,你要把这个东西单独拎出来,让它public。权限的东西都是在编译器时候,编译器帮你做,层层的判定,是编译器层面帮你做的,那么既然是编译层面层面帮你做,那就好说话了,你用一个你设计,你发明个特殊的关键字是吧,然后跟编辑说一下,那就可以了。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/1324325.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-12
下一篇2022-06-12

发表评论

登录后才能评论

评论列表(0条)

    保存