【c++随笔14】虚函数表

【c++随笔14】虚函数表

  • 一、虚函数表(Virtual Function Table)
    • 1、定义
    • 2、查看虚函数表
      • 2.1、 问题:三种类型,包含一个int类型的class、一个int类型的变量、int类型的指针:这三个大小分别是多少呢?
      • 2.2、怎么发现虚函数表存在的?
      • 2.3、查看虚函数表里面都有什么?
    • 3、继承——虚函数的重写与覆盖
    • 4、虚函数为何可以实现多态?
    • 5、对象也能切片,为什么不能实现多态?(普通的继承为何不能实现多态?)
    • 6、打印虚函数表
      • 6.1同一个类型它们的虚表内存地址都是一样的,同一类型的对象共用一份虚表。
      • 6.2打印虚函数表
  • 二、多态原理
    • 1、动态绑定、动态类型、静态类型
    • 2、动态绑定和静态绑定对比
    • 3、虚函数表的存储位置
    • 4、汇编层面看多态实现原理
  • 三、多继承中的虚函数表
    • 1、多继承会有两张虚表(继承两个时),
    • 2、派生类定义的虚函数,存放在第一张虚函数表中
  • 四、经典问题

原创作者:郑同学的笔记
原创地址:https://zhengjunxue.blog.csdn.net/article/details/131932164
qq技术交流群:921273910

一、虚函数表(Virtual Function Table)

1、定义

虚函数表是一个由虚函数组成的表格,用于实现动态绑定和多态性。每个包含虚函数的类都有自己的虚函数表,该表列出了该类及其所有基类的虚函数。当一个对象被创建时,它的类虚函数表也被创建,并且可以通过该对象的指针或引用来调用虚函数表中的函数。

虚函数表是一种实现动态多态性的机制。每个包含虚函数的类都有一个虚函数表,其中存储着该类的虚函数地址。当通过基类指针或引用调用虚函数时,程序会根据对象的实际类型查找对应类的虚函数表,并调用正确的虚函数。

2、查看虚函数表

2.1、 问题:三种类型,包含一个int类型的class、一个int类型的变量、int类型的指针:这三个大小分别是多少呢?


#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:void Func1() {cout << "Func1()" << endl;}void Func2() {cout << "Func2()" << endl;}void Func3() {cout << "Func3()" << endl;;}
};int main(void)
{Base b;cout << sizeof(b) << endl;cout << sizeof(int) << endl;cout << sizeof(int *) << endl;return 0;
}

输出

在这里插入图片描述

  • 答案:(64位系统)

    • class 对象实例:占4个字节
    • int 变量:占4个字节
    • int 指针:占用8个字节
  • 其他结论:

    • 类class占用内存的大小,就是类calss成员变量占用内存的大小;
    • 类class占用内存的大小,和成员函数无关;
    • 类也可以作为一种数据类型来看待;
  • Base实例b里面有什么

只有成员变量_b
在这里插入图片描述

2.2、怎么发现虚函数表存在的?

  • 2.2.1加了virtual后,虚函数Base的大小
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:virtual void Func1() {cout << "Func1()" << endl;}virtual void Func2() {cout << "Func2()" << endl;}virtual void Func3() {cout << "Func3()" << endl;;}
};int main(void)
{Base b;cout << sizeof(b) << endl;return 0;
}

输出

结论:
加了virtua后,虚函数Base大小为16;
在这里插入图片描述

  • 2.2.2、加了虚函数表后为何变成了16字节?
    调试查看Base类的实例b里面都有什么

调试截图如下,除了 _b 成员外,还有了一个 _vfptr 在 b1对象中在这里插入图片描述

结论:

  • 由于类里面,除了 _b 成员外,还增加了_vfptr,所以由4字节变成了16字节;
  • _vfptr就是虚函数表指针(virtual function pointer),指向虚函数表;

扩展:虚函数表指针占用8字节,int类型占用4字节;那8+4应该是12字节,为何总的内存变成了18字节呢,这里面有个内存对齐的问题。打个比方,你int类型占用4字节,double占用8字节,那么会总的便会占用16字节,int类型也会分配8字节的空间。

2.3、查看虚函数表里面都有什么?

在这里插入图片描述

结论:

  • 虚函数表里面是一个数组;
  • 数组里面存储的是每一个虚函数的地址;

扩展
其实,看了我之前写的文章就知道,其实类的实例的每一个成员函数,都有一个单独的地址,存储在代码段.text段。

3、继承——虚函数的重写与覆盖

代码:现在我们增加一个子类 Derive 去继承 Base:

#include <iostream>
using namespace std;class Base {
private:int _b = 1;public:virtual void Func1() {cout << "Func1()" << endl;}virtual void Func2() {cout << "Func2()" << endl;}virtual void Func3() {cout << "Func3()" << endl;;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main(void)
{Derive d;cout << sizeof(d) << endl;return 0;
}

输出

在这里插入图片描述

父类 b 对象和子类 b 对象虚表是不一样的,这里看我们发现 Func1 完成了重写,

所以 d 的虚表中存的是重写的 Derive::Func1,所以虚函数的重写也叫做覆盖。

就可以理解为:子类的虚表拷贝了父类的虚表,子类的 Func1 覆盖掉了父类上的 Func1。

(覆盖指的是虚表中虚函数的覆盖)

  • 虚函数重写:语法层的概念,子类对继承父类虚函数实现进行了重写。
  • 虚函数覆盖:原理层的概念,子类的虚表,拷贝父类虚表进行了修改,覆盖重写那个虚函数。

🔺 总结:虚函数的重写与覆盖,重写是语法层的叫法,覆盖是原理层的叫法。

4、虚函数为何可以实现多态?

多态调用实现是依靠运行时去指向对象的虚表中查,调用函数地址。

#include <iostream>
using namespace std;class Base {
private:int _b = 1;public:virtual void Func1() {cout << "Base::Func1()" << endl;}virtual void Func2() {cout << "Base::Func2()" << endl;}virtual void Func3() {cout << "Base::Func3()" << endl;;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main(void)
{Base b;Derive d;Base* ptr = &b;ptr->Func1();   // 调用的是父类的虚函数ptr = &d;ptr->Func1();   // 调用的是子类的虚函数return 0;
}

输出

在这里插入图片描述

5、对象也能切片,为什么不能实现多态?(普通的继承为何不能实现多态?)

既然指针和引用可以实现多态,那父类赋值给子类对象也可以切片,
根本原因是:对象切片时,子类对象只会拷贝成员给父类对象,并不会拷贝虚表指针。(没有虚函数表)

之前我们讨论过,为何没有静态多态的概念,除了当时说的部符合多态的定义外,本质的原因就在这里。

6、打印虚函数表

6.1同一个类型它们的虚表内存地址都是一样的,同一类型的对象共用一份虚表。

#include <iostream>
using namespace std;class Base {
public:int _b = 1;public:virtual void Func1() {cout << "Base::Func1()" << endl;}virtual void Func2() {cout << "Base::Func2()" << endl;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}virtual void Func3() {cout << "Derive::Func3()" << endl;;}
public:int _d = 2;
};using pf = void(*)();
//typedef void(*pf)(void); //和上面的写法相等,看不懂的可以看下我的另外一篇博客《函数指针》int main(void)
{Base b;Derive d1;Derive d2;return 0;
}

查看局部变量的窗口

在这里插入图片描述

可以看到子类继承自父类的虚函数表中Func1函数地址是重写之后的函数地址,已经将父类的func函数地址覆盖掉。

  • 如果父类中的虚函数没有被子类重写,那么子类的虚函数表中的地址仍然是父类中虚函数的地址。
  • 只有虚函数才会进虚函数表,非虚函数是不进虚函数表的。
  • 如果派生类中存在新增加的虚函数,那么就会按照在派生类中的声明顺序依次添加到派生类的虚函数表的最后。
  • 虚函数表本质就是一个虚函数指针数组,而虚函数表指针本质就是这个数组的首元素地址。虚函数表的最后一个字段通常置为nullptr。

6.2打印虚函数表

我们例子中,每一个虚函数返回值类型void,参数无,所以,虚函数指针数组中元素的类型为void(*)(void);(不懂的可以查看我的另外一篇博客《【c++随笔09】函数指针》

  • 注意:派生类的虚函数,visual并没显示出来,但是我们打印出来了,可见:visual的可视化是有些问题的。
#include <iostream>
using namespace std;class Base {
public:int _b = 1;public:virtual void Func1() {cout << "Base::Func1()" << endl;}virtual void Func2() {cout << "Base::Func2()" << endl;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}virtual void Func3() {cout << "Derive::Func3()" << endl;;}
public:int _d = 2;
};using pf = void(*)();
//typedef void(*pf)(void); //和上面的写法相等,看不懂的可以看下我的另外一篇博客《函数指针》int main(void)
{Base b;Derive d1;Derive d2;Base* ptr = &d1;//ptr->Func1();   // 调用的是父类的虚函数//Base* ptr2 = new Derive();pf* pfun = (pf*)*(long long*)ptr;//2.pp这个指针是函数指针数组的首元素的地址。while (*pfun){cout << *pfun << endl;(*pfun)();cout << endl;pfun++;}cout << "----------------------------------------" << endl;ptr = &d2;pfun = (pf*)*(long long*)ptr;while (*pfun){cout << *pfun << endl;(*pfun)();cout << endl;pfun++;}cout << "----------------------------------------" << endl;ptr = &b;pfun = (pf*)*(long long*)ptr;while (*pfun){cout << *pfun << endl;(*pfun)();cout << endl;pfun++;}return 0;
}

输出

在这里插入图片描述

二、多态原理

1、动态绑定、动态类型、静态类型

我们依然查看《C++ Primer 第5版》第15章节末尾 术语表中的介绍(p575-576页)

  • 动态绑定(dynamic binding) 直到运行时才确定到底执行函数的哪个版本。在C++语言中,动态绑定的意思是在运行时根据引用或指针所绑定对象的实际类型来选择执行虚函数的某一个版本。

  • 动态类型(dynamic type) 对象在运行时的类型。引用所引对象或者指针所指对象的动态类型可能与该引用或指针的静态类型不同。基类的指针或引用可以指向一个派生类对象。在这样的情况中,静态类型是基类的引用(或指针),而动态类型是派生类的引用(或指针)。

  • 静态类型(static type) 对象被定义的类型或表达式产生的类型。静态类型在编译时是已知的。

《C++ Primer 第5版》第15.2章节(p529页)

  • 以动态绑定有时又被称为运行时绑定(run-time binding)。
  • 在C++语言中,当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定。

2、动态绑定和静态绑定对比

由于《C++ Primer 第5版》并没有给出静态绑定的概念,我们暂时把在程序编译期间确定了程序的行为,也称为静态绑定。比如函数重载。

  • 动态绑定
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:void Func1() {cout << "Func1()" << endl;}
};int main(void)
{Base *ptr1 = new Base();ptr1->Func1();return 0;
}
  • 静态绑定
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:virtual void Func1() {cout << "Func1()" << endl;}
};int main(void)
{Base *ptr1 = new Base();ptr1->Func1();return 0;
}
  • 汇编层面分析静态绑定和动态绑定的区别
g++ main.cpp
objdump -h -d -x ./a.out

对比反汇编的代码,如下截图

在这里插入图片描述

3、虚函数表的存储位置

推断:虚表存储在只读数据段上。
在这里插入图片描述

4、汇编层面看多态实现原理

  • 多态
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:virtual void Func1() {cout << "Func1()" << endl;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};void pf(Base *b)
{b->Func1();
}int main(void)
{Base *ptr1 = new Base();pf(ptr1);Base *ptr2 = new Derive();pf(ptr2);return 0;
}
  • 非多态
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:void Func1() {cout << "Func1()" << endl;}
};// 子类 Derive
class Derive : public Base {
public:void Func1() {cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};void pf(Base *b)
{b->Func1();
}int main(void)
{Base *ptr1 = new Base();pf(ptr1);Base *ptr2 = new Derive();pf(ptr2);return 0;
}
g++ main.cpp
objdump -h -d -x ./a.out

对比反汇编的代码,如下截图

在这里插入图片描述

三、多继承中的虚函数表

  • 1、多继承会有两张虚表(继承两个时),

// 基类A
class A {
public:virtual void func1() {std::cout << "A::func1()" << std::endl;}virtual void func2() {std::cout << "A::func2()" << std::endl;}};// 基类B
class B {
public:virtual void func1() {std::cout << "B::func1()" << std::endl;}/*virtual void func2() {std::cout << "B::func2()" << std::endl;}*/};// 派生类C,多继承自A和B
class C : public A, public B {
public:virtual void func1() override {std::cout << "C::func1()" << std::endl;}/*virtual void func2() override {std::cout << "C::func2()" << std::endl;}*/virtual void func3() {std::cout << "C::func3()" << std::endl;}
};int main() {C c;
}

我们先透过监视简单看一下:

在这里插入图片描述

2、派生类定义的虚函数,存放在第一张虚函数表中

#include <iostream>// 基类A
class A {
public:virtual void func1() {std::cout << "A::func1()" << std::endl;}virtual void func2() {std::cout << "A::func2()" << std::endl;}};// 基类B
class B {
public:virtual void func1() {std::cout << "B::func1()" << std::endl;}/*virtual void func2() {std::cout << "B::func2()" << std::endl;}*/};// 派生类C,多继承自A和B
class C : public A, public B {
public:virtual void func1() override {std::cout << "C::func1()" << std::endl;}/*virtual void func2() override {std::cout << "C::func2()" << std::endl;}*/virtual void func3() {std::cout << "C::func3()" << std::endl;}
};int main() {C c;// 打印A的虚函数表std::cout << "A's vtable: " << std::endl;void** aVTable = *(void***)(&c);for (int i = 0; aVTable[i] != nullptr; i++) {std::cout << "    [" << i << "]" << aVTable[i]<<" -> "<<" 函数执行";void(*func)() = (void(*)())(aVTable[i]);func();}// 打印B的虚函数表std::cout << "B's vtable: " << std::endl;void** bVTable = *(void***)(((char*)&c) + sizeof(A));for (int i = 0; bVTable[i] != nullptr; i++) {std::cout << "    [" << i << "]" << aVTable[i]<<" -> " << " 函数执行";void(*func)() = (void(*)())(bVTable[i]);func();} 根据虚函数表地址运行虚函数//std::cout << "Running virtual function using A's vtable: " << std::endl;//void(*aFunc)() = (void(*)())(aVTable[0]);//aFunc();//std::cout << "Running virtual function using B's vtable: " << std::endl;//void(*bFunc)() = (void(*)())(bVTable[0]);//bFunc();return 0;
}

输出

在这里插入图片描述

四、经典问题

    1. inline函数可以是虚函数嘛?

inline函数可以是虚函数,但是其内联的特性也就没有了,因为inline只是对编译器的建议。内联函数是在调用的地方展开,没有函数地址,而虚函数的地址是要写入虚函数表的,所以内联函数和虚函数只能为其中的一个,不可兼得。

    1. 静态成员函数可以是虚函数嘛?

不能,因为静态成员函数没有this指针,使用类名::成员函数的调用方式 无法访问虚函数表,所以静态成员函数无法放进虚函数表。

    1. 构造函数可以是虚函数嘛?

不可以,因为虚函数表指针是在构造函数的初始化列表初始化的,但是虚函数又要借助虚函数表指针来调用虚函数,两者矛盾,所以不可以为虚函数。

    1. 析构函数可以是虚函数嘛?

可以,并且建议将析构函数定义为虚函数,因为这样可以避免内存泄漏的问题。如果子类对象是动态开辟的,使用父类指针指向子类对象,在delete时如果构成多态那么就会调用子类析构函数,而调用子类析构函数前系统会默认先调用父类析构函数,这样可以避免内存泄漏。

    1. 对象访问普通函数快还是访问虚函数快?

如果是通过实例化的对象访问那么是一样快的,如果是指针或引用对象访问的话是访问普通函数快的,因为指针或引用去访问虚函数时走的是多态调用是一个晚绑定,需要在运行时去需表中找函数的地址。

    1. 虚函数表是在什么时候形成的?存在哪?

和虚函数相关的字符,字符在只读数据区(.rodata),但是虚函数的实现代码应该是和其他的函数一样,存储在代码段(.text)

    1. 什么是抽象类?

函数纯虚函数的类叫做抽象类,此类不能实例化出对象,这也强制了其派生类如果想要实例化出对象那么就必须重写纯虚函数。

    1. C++菱形继承解决方案和多态原理?

菱形继承具有数据冗余和二义性的问题,解决的方法是通过虚继承的方式,虚继承的派生类中会产生一个虚基表指针,该指针指向虚基表,表中的内容是一个到冗余数据的偏移量,而原本冗余的数据会被放到派生类对象的最后。
多态的原理是通过重写虚函数,达到在派生类的虚函数表中重写的虚函数地址覆盖掉原本的地址,然后通过基类的指针或者引用指向派生类对象时,调用虚函数调用的时子类重写后的虚函数,而执行基类对象时调用的就是基类的虚函数达到多态的行为。
不要将虚基表和虚函数表搞混。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/172662.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IT问题解答类型网站源码

问答网是一款为IT工程师提供的问答平台&#xff0c;旨在帮助用户在线获取专业知识和相关问题的答案。在问答网&#xff0c;用户可以轻松找到其他人的问答问题&#xff0c;并在这里寻求解答。如果您有任何想要解决的问题&#xff0c;都可以在此发布问题并得到其他同行的解答。 …

CSS之弹性盒子Flexible Box

我想大家在做布局的时候&#xff0c;没接触flex布局之前&#xff0c;大家都是用浮动来布局的&#xff0c;但现在我们接触了flex布局之后&#xff0c;我只能说&#xff1a;“真香”。让我为大家介绍一下弹性盒子模型吧&#xff01; Flexible Box 弹性盒子 在我们使用弹性盒子时&…

【算法】链表-20231127

这里写目录标题 一、面试题 02.02. 返回倒数第 k 个节点二、82. 删除排序链表中的重复元素 II三、141. 环形链表 一、面试题 02.02. 返回倒数第 k 个节点 提示 简单 130 相关企业 实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点。返回该节点的值。 注意&#xff1a;本…

Linux(8):BASH

硬件、核心与 Shell 操作系统其实是一组软件&#xff0c;由于这组软件在控制整个硬件与管理系统的活动监测&#xff0c;如果这组软件能被用户随意的操作&#xff0c;若使用者应用不当&#xff0c;将会使得整个系统崩溃。因为操作系统管理的就是整个硬件功能。 应用程序在最外层…

前端(HTML + CSS + JS)

文章目录 一、HTML1. 概念&#xff08;1&#xff09;HTML 文件基本结构&#xff08;2&#xff09;HTML代码框架 2. 、HTML常见标签 二、CSS1. CSS基本语法规范2. 用法&#xff08;1&#xff09; 引用方式&#xff08;2&#xff09;选择器&#xff08;3&#xff09;常用元素属性…

NX二次开发UF_CURVE_ask_trim 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_trim Defined in: uf_curve.h int UF_CURVE_ask_trim(tag_t trim_feature, UF_CURVE_trim_p_t trim_info ) overview 概述 Retrieve the current parameters of an a…

利用STM32和MFRC522 IC实现智能卡的读取和数据存储

利用STM32微控制器和MFRC522 RFID读写器芯片&#xff0c;可以实现智能卡的读取和数据存储功能。智能卡是一种集成了RFID技术和存储芯片的卡片&#xff0c;它可以用于身份验证、门禁控制、支付系统等应用场景。下面将介绍如何使用STM32和MFRC522芯片进行智能卡的读取和数据存储&…

3.OpenResty系列之Nginx反向代理

1. Nginx简介 Nginx (engine x) 是一款轻量级的 Web 服务器 、反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器 什么是反向代理&#xff1f; 反向代理&#xff08;Reverse Proxy&#xff09;方式是指以代理服务器来接受 internet 上的连接请求&#x…

4面试题--数据库(补充)

隔离性问题 若不考虑隔离性则会出现以下问题 1. 脏读&#xff1a;指⼀个事务在处理数据的过程中&#xff0c;读取到另⼀个 未提交 事务的数据 2. 不可重复读&#xff1a;指对于数据库中的某个数据&#xff08;同⼀个数据项&#xff09;&#xff0c;⼀个事务内的多次查询却…

docker打包前端镜像

文章目录 一、构建镜像二、查看本地镜像三、启动容器四、查看启动的容器五、保存镜像六、读取镜像七、创建镜像八、最后 docker官网 一、构建镜像 -t是给镜像命名&#xff0c;.(点)是基于当前目录的Dockerfile来构建镜像 docker build -t image_web .二、查看本地镜像 docke…

使用echars实现数据可视化

生活随笔 展翅飞翔之际 请下定决心不再回头 echars实现数据可视化 在搭建后台页面时&#xff0c;可能会遇到很多的表格&#xff0c;但有时表格所展现的数据并不能直观的体现出当前用户的宏观信息&#xff0c;所以就可以引入一个新的表格插件——echars 快速上手 - Handbook…

某软件商店app抓包分析与sign加密算法实现

文章目录 1. 写在前面2. 抓包配置3. 抓包分析4. 接口测试5. sign加密算法6. 数据效果展示 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】…

通用电气调查网络攻击和数据盗窃指控

通用电气正在调查有关威胁行为者在网络攻击中破坏了公司开发环境并泄露据称被盗数据的指控。 通用电气 (GE) 是一家美国跨国公司&#xff0c;业务涉及电力、可再生能源和航空航天行业。 本月早些时候&#xff0c;一个名为 IntelBroker 的威胁行为者试图在黑客论坛上以 500 美…

人工智能_机器学习051_支持向量机SVM概念介绍_理解support vector machine---人工智能工作笔记0091

在出现深度学习,神经网络算法之前,支持向量机已经可以解决很多问题了,我们自然界中的问题,无非就是可以转换为回归问题和分类问题. 然后从现在开始我们来看支持向量机,首先看一下这几个字 support 是支持 vector是向量的意思,然后 machine指的是机器 那么我们之前用到的模型…

常见树种(贵州省):021冬青、连香树、白辛树、香合欢、云贵鹅耳枥、肥牛树、杜英、格木、黄连木、圆果化香树、南天竹

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、冬青 …

AIGC ChatGPT 4 快速整理不规则数据

从业务系统中采集到的数据如下: 序号 省份 英文 2022年销售额 2021年销售额 增量 1 广东guangDOng129068.58 124319.67 4748.91 2 江苏 JiangSu 122825.6 116314.2 6511.4 3 山东ShAnDong 87385 83045.9 4339.1 4 浙江…

手把手教会你--Hack The Box的账号注册(HTB Labs部分)

有什么问题&#xff0c;请尽情问博主&#xff0c;QQ群796141573 前言1.1 一次注册正确的注册过程1.2 讲讲我在注册过程中遇到的两个问题&#xff08;1&#xff09;点击REGISTER后无反应&#xff08;2&#xff09;提示Error! reCaptcha validation failed 前言 请务必跟着博主复…

网络爬虫(Python:Selenium、Scrapy框架;爬虫与反爬虫笔记)

网络爬虫&#xff08;Python&#xff1a;Selenium、Scrapy框架&#xff1b;爬虫与反爬虫笔记&#xff09; SeleniumWebDriver 对象提供的相关方法定位元素ActionChains的基本使用selenium显示等待和隐式等待显示等待隐式等待 Scrapy&#xff08;异步网络爬虫框架&#xff09;Sc…

【docker系列】docker高阶篇

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

C语言:写一个函数,求字符串的长度,在main函数中输入字符串并输出其长度(指针)

分析&#xff1a; 在程序中&#xff0c;定义一个函数 fix&#xff0c;该函数使用指针变量来访问字符串中的每个字符&#xff0c;并计算出字符串的长度。fix 函数的参数为指向 char 类型的指针变量 p&#xff0c;表示需要计算长度的字符串。 在主函数 main 中&#xff0c;定义一…