• 13539426682
  • liuyixi2009@163.com

月度归档二月 2019

鶸断腕(七)

虚函数的复习

当子类要重新定义父类的函数时,为了让基类指针能够识别自己要调用的不是基类的方法,而是子类的方法,我们引进了虚函数,大致就是在父类的要重定义的方法前加个virtual。然鹅这不仅仅是加个标签这么简单,这涉及到了动态联编和静态联编,通过加个virtual来让编译器对某个函数进行跟踪,具体就是给每个对象添加一个隐藏成员,这个成员保存了一个指向函数地址数组的地址。如图:

而其中每个对象都会有这样的一个成员:vptr ——专门存放着自己类对应的虚函数表的地址。比如设 A objA里面的vptr的值就是01。

很明显只要开了虚函数就会带来一定的运行成本:

1.对象增大,多了一张表

2.每个类编译器都会创造一个虚函数地址表(只记录虚函数)

3.每个函数调用都要去找一下这个函数的地址(如果更新就用新地址记录的那个函数)

注意的点:

1.析构函数理应是虚函数,当出现如下情况时:

这样做就会导致 p 调用父类的析构函数而没有调用子类的析构函数,从而子类带来的空间将无法释放,这时如果设成虚函数,那么p将先调用子类的虚构函数,再调用父类的析构函数。

2.虚函数重新定义不等于重载!重新定义意味着虚函数表的覆盖,子类和父类的虚函数的声明应该一样,除了返回的参数允许修改为指向派生类的指针或者引用。

鶸断腕(六)

关于数组和类….

想要声明一个类的数组,然后输入数据进行初始化,怎么办?

直接 ClassName  a[i]  和  a[i](balbla) 是不行的,因为没有这样的构造函数,因此应该

这样子。当然注意到这里是生成了一个“无名氏”,因为是用了构造函数并且赋给class[i],因此当涉及内存管理时要进行深度复制。

如果不想麻烦写构造函数还可以这样子操作:

利用指针进行访问类中的函数,而且还可以有助于日后多态化的实现

鶸断腕(五)

发现的一些奇怪的错误

总的来说就是默认值重复的问题,就是在给类的声明文件(头文件)里面的公有函数写了默认值以后再在方法实现的文件中写函数内容时,在那里假如又写了一次默认值的话,就会报错,事实上是这就是默认值重复造成的错误。

 

鶸断腕(四)

关于内存分配:new与delete

这里的操作是错误的,这里的new使用了定位new运算符,先申请一块区域,指针buffer指向这一片区域,直接delete掉会使这一片buffer区域直接没了,事实上可以看到pc3的数据直接覆盖掉了pc1,因为他们被存在相同的区域里面去了,因此pc3应该改成:

pc3 = new (buffer+sizeof(JustTesting)) JustTesting(“Bad Idea”, 6);

然后如果想让死亡宣告出现,则要显示调用析构函数。

修改后:

值得注意的是,系统默认析构函数只会释放类里面的东西,但是假如这个类new出来的东西,系统默认的析构函数并不会管,因此如果有new,记得用delete!

鶸断腕(三)

为什么要自己设定析构函数和构造函数?—-类和动态内存分配

情景:在类内使用new时,同时有一个函数它的参数刚好不是引用而是把这个类复制进去,如下

先来说一下特殊的成员函数

1.默认构造函数:通常假如你提供了构造函数,那么c++就不会为你提供默认构造函数,但是你依然可以自己定义,但是在定义时要注意这个二义性问题,即默认构造函数没有参数的形式默认构造函数有参数但是参数默认值为零。

 

 

2.默认复制函数:通常涉及到创建类的副本时,你比如说,像刚刚那个函数,就涉及到复制类的副本,然鹅!!!当这个构造函数里面涉及用new来申请空间时,这个沙雕默认复制函数会把 地址 原封不动复制给这个默认复制函数生成出来的副本的相应位置,这就会导致在析构这个副本时会把这个副本里面的那个指针指向的那个空间给删掉,这样就会很蛋疼。因此在涉及申请空间一类的构造函数时要注意进行深度复制,就是不单单只去复制地址而是再次申请一块空间,然后copy内容。当然也可以在函数的传参部分修改改为引用。

 

 

3.赋值运算符:当出现对象给对象用等号赋值时,显然我们需要重载一下这个等号。在赋值前要删掉这个新对象可能存在的值。

 

 

怎么删掉Category Archive

一开始找了很久都找不到…然后点击分类时巨丑,标题变成什么什么 Category Archive C++,Category Archive 算法,balbal什么的,事实上把那个category.php 里面的东西删了就行

鶸断腕(二)

转换函数

设有一个 Time 类,它的构造函数是 Time(double a),那么Time A = 19 是可以的, 程序使用 Time(19)作为初始化的值,生成一个临时对象,并且将成员逐个赋值到 A 中

1.注意此过程是隐式转换,开了explicit 就不行了

explicit Time(double a) //只能显示转换

2.注意这个也只适用于一个参数或者其他参数有默认值的函数

3.注意这里要想到二义性的问题,当构造函数里面的参数可以是int 或者 long 时,这样子的赋值就会包二义性 ambiguous 错误。

这里有一个很神奇的东西就是假如有一个函数它需要一个Time 类的参数时,输入一个整数也是行的

转换函数 2.0

可以很轻松的把 double 转换成 Time 类,但是可不可以把 Time 类转换成 double 类呢,事实上是可以的:可以看成重载double()函数

  • 转换函数不能指定返回值的类型
  • 必须是类方法
  • 不能有参数
  •  

定义完以后

但是假如我很傻雕,写了 long A = B ,那怎么办?

这时还是会成立,转换函数先把 B 变成一个 double 值,再把 double 变成 long。当然这样做只当你定义了一个转换函数时才可以,定义了两个时就会报错。

当然还是有解决办法的,隐式不行就显示转换嘛:

当然最好还是把隐式转换关掉会比较好。。。

当然懒得写也可以这样子:把一个功能相同的非转化函数替换该转换函数,只有当显示地调用时才可以转换

总结一小波

类+类 :

  • 1.重载加法运算符(成员函数,隐式调用)
  • 2. 重载加法运算符 (友元函数,直接显示访问两个类的数据)

类+普通数据(double为例)

  • 1.提供 Time(double)的构造函数,且提供一个上面的任一一个类(实际上就是 double 的隐式转换,把 double 作为初始值去构造一个 Time 类的对象,然后再调用上面类加类的函数)
  • 补充:要注意提供了 Time(double)类的构造函数后就不要再写一个Time::double()const 的转换函数,否则会造成二义性

普通数据+类

  • 1.只能用友元函数,且已经提供了转换函数 Time::double()const ,这时会编译器会把 Time 类转化成 double 类再相加。而不是把 double 转换成 Time 类
  • 2.其实最简单的方法就是写一个函数把两个倒转过来,再传进类+普通数据的函数里面就好啦…

 

鶸断腕(一)

友元函数

在进行类和普通值的加减乘除时我们通常会重载运算符来使代码更加优美而不是用什么 multiplication (int a ,const b & t) 来进行运算

即设 A 和 B 都是某个Time类,A = B * 2.75 成功的前提是已经在Time类里面定义函数:

Time Time :: operator * (double mult) const
//1.其中const是指该函数不能修改类中的值
//2.若Time类被声明为const,那么外界只能访问这种const函数

然鹅,下面的语句怎么办?

A = 2.75 * B

这时就需要一个函数处理这种情况,而且这种函数可以访问 B 中的数据,这个函数就是友元函数,该函数原型为:

Time operator*(double m,const Time& t)

在类中的public中声明:

class Time
{
private:
int hours;
public:
Time(int h);
friend Time operator* (double m, const Time& t);
}

函数定义:

Time operator* (double m, const Time& t)
{
Time result;
/*代码块*/
return result;
}

注意:这里可以看到友元函数虽然可以访问类里面的 private 数据,但是它并不是类里面的方法,因此 this 指针无法调用该方法。

友元是否违背OOP?

显然是没有的,看起来好像打破了数据封装原则,但是决定权依然在类的手上(友元函数在类中声明)应该把友元函数看出成类的扩展接口

实际上也可以不用这么麻烦,也可以定义一个非友元函数来实现:

Time operator*(double m,const Time &t)
{
return t* m; //用 t.operator* (m)
}

常用的友元:重载 << 运算符

说白了就是让输出更加简单:

ostream & operator << ( ostream & os ,const Time & t ) { os << t.hours << "hours" << t.minutes << "minutes" ; return os; //返回ostream类的引用 }

这个计网有丶东西(三)

关于各种各样的欺骗..

  1. ARP 欺骗:先来复习一下 arp 协议,其实就是把 ip 地址转化成 mac 地址,而当同一子网里面一台机子不知道另一台机子的mac地址时,这一台机子就会广播一个 ARP 查询分组,同一子网内的所有计算机都会收到并且进行检查,符合的就回报回去,告诉那个机子我在这里。这时,我们可以发现,总有沙雕可以冒充那个目标机子告诉要找的机的那个计算机说我就是它,欸这时就欺骗成功了。防止方法网上查查就有,我觉得最有效的就是把动态arp表改成静态的(表内有所有的内网机子mac地址)

这个计网有丶东西(一)

是关于路有选择算法的,也是Dijkstra算法和Bellman-Ford算法的应用把,终于知道这个有什么用了….

总的来说这个路由算法分为两个:链路状态路由选择算法 和 距离向量路由选择算法,前者简称 LS,后者简称 DV。

LS:看上去很高端霸气,实际上也就那个样子,LS算法是由链路状态广播算法加上这个Dijkstra算法而来的,事实上由于Dijkstra算法需要知道全局的链路信息,因此必须在进行疯狂计算前进行数据之间的传递,然后开始松弛计算最短路径(某个点到其余点的最短路径)。当然这个算法也存在着路由震荡的问题,简单的说就是假如四个路由器都在同一时间点进行计算,分发,则会出现其余三个路由器一起选择同一条在它们当时看来不拥挤的道路,然后….就炸了,因此每台路由器会发送链路通告让时间随机化

DV:不同于上,LS算法并不需要知道每一条链路的信息,相反它只知道自己和邻居链路的信息以及它邻居发给他的信息。在收到第一批邻居发的信息后,该路由器开始计算,更新(把所有已知的边带进去算,看是否能进行松弛)。更新后把结果分发给自己的邻居,显然这个过程是异步的。然后这个算法的问题是会出现故障(废话),就是因为它是异步的,信息改变时感觉会给各个路由器之间造成误会,而每个路由器知道的也只是 “诶,我的邻居告诉我他有一条可以去x点的路而且代价不高,可以啊小老弟”。这里就看出来和LS算法的不同,LS算法中的路由器是确切的知道每一条链路的信息的,因此不会有“误会”,而误会造成的后果就是一旦某一条链路的拥挤程度,假设是 x(u,v)发生猛增,这时对于第三方路由器 a 来说 a 本来认为 u 有一条路代价不高就可以去到 v 因此就对 u 说 “我有一条路去v代价不高哦”(实际上这时这一条路已经高的一比,u不知道a这个沙雕说的路就是自己通向v的路),这时 u 为了把东西发往 v 会把文件发给 a ,同时告诉a说我知道一条路可以以很低的代价通向 x(其实就是z传递错误信息造成的误会,u说的就包含z说的那一条)然后 a 说不错然后转手把文件甩给 u 。。。

然后,这样的误会会一直加深直到超过某个阈值(通常是 y(a,v)的代价),即文件在 a 和 u 之间反复横跳 🙂

这个时候就有一种方法叫毒性逆转,其实就是欺骗啦,a骗u说自己去x的路是无穷大的。。也不是很有用,只适合于三角形的路由。