当前位置: 首页 > news >正文

重生学c++系列第三课类和对象(上)

    好的我们重生c++系列的前两期已经介绍完了c++祖师爷针对C语言补充的几个新功能,现在我们进入c++的真正课题学习——类与对象:

    C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

    比如说我们洗菜做饭,C语言关注的是 拿盆子  倒水 洗菜  放油 炒菜 等等等这些细致的步骤

   

    C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完
成。

     一共有四个对象 :人  菜   锅  盘子  我们不需要关心菜是如何炒出来的

 类的引入

     

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:
之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,
会发现struct中也可以定义函数。
我们定义一个结构体
//c++兼容C语言,C语言的用法可以接着用,也有新用法
struct Stack
{
    int* a;
    int top;
    int capacity;
};
在c++中,我们更喜欢称其为类,c++兼容C语言,C语言的用法可以接着用,也有新用法
比如说:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
struct Stack
{int* a;int top;int capacity;
};
int main()
{struct Stack st1;Stack st2;return 0;
}

主函数中两种建立类的方式不同,第一种是C语言的玩法,第二种是c++的玩法,明显要更简便一点

同时类中不仅仅可以定义成员变量,并且可以定义成员函数,比如说初始化之类的函数,就没必要在外边写了

上代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
struct Stack
{//成员函数void Init(){a = nullptr;top = 0;capacity = 0;}//成员变量int* a;int top;int capacity;
};
int main()
{Stack st2;st2.Init();return 0;
}

这样调用Init初始化函数,是不是比C语言阶段在外部传参进去容易多了!

类的定义

上面结构体的定义, C++ 中更喜欢用 class 来代替,这也是真正的类与结构体不一样的地方
class className
{
// 类体:由成员函数和成员变量组成
};   // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分
号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者
成员函数。
那么class里的使用和struct有什么区别,第一大区别就是访问限定符,class中有着访问限定符
我们用代码来向大家讲解:

 大家注意看,在将struc换成class之后,就无法访问我们在类中定义的函数了

这是因为我们没有加上访问限定符:publi private 

c++的类是很任性的,我可以选择给你看给你用,也可以选择不给你看,不像struct随便访问,想给你看的加上publi(共有),不想给你看的就加上private(私有)

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Stack
{
public://成员函数void Init(){a = nullptr;top = 0;capacity = 0;}void Push(int x){if (top == capacity){size_t newcapacity = capacity == 0 ? 4 : capacity * 2;a = (int*)realloc(a,sizeof(int) * newcapacity);capacity = newcapacity;}a[top++] = x;}
private://成员变量int* a;int top;int capacity;
};
int main()
{Stack st2;st2.Init();st2.Push(1);st2.Push(2);st2.Push(3);st2.Push(4);return 0;
}

这样,我们在class中写的函数就可以用了,因为我们在他们的前面加上了public访问限定符

很多同学学到这里会有这样一个疑问,private限制类外访问,那限制类里的访问吗,答案是当然可以,就像是你家有一个保险柜,肯定用来防外人,自己家里人肯定知道密码的对吧。

成员变量的风格

我们定义一个新的日期的类

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:void Init(int year, int month, int day){year = year;month = month;day = day;}
private:int year;int month;int day;
};
int main()
{Date d;d.Init(2022,7,13);return 0;
}

大家看这段代码,思考一下有没有初始化成功

答案:

编译是没有任何问题的,但是:

我们调试就会发现,这三个变量都是随机值,没有被初始化

这就是我们在C语言时期讲过的局部优先原则,忘了或者没听过的小伙伴去C语言的博客里就可以找到,所以我们只需要改一下成员变量

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
int main()
{Date d;d.Init(2022,7,13);return 0;
}

 这样就可以啦,这就是成员变量的风格

封装

    面向对象的三大特性:封装、继承、多态
    在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
    封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用
户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日
常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如
何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。
    因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。
    在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来
隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
class date
{
public:void Printdate();
private:char _year;char _month;int  _day;
};
// 这里需要指定Print是属于date这个类域
void date::Printdate()
{cout << _year << " "<< _month << " " << _day << endl;
}

类的实例化

想问大家一个问题,像下面代码这样可以访问吗?

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
int main()
{Data::_year;return 0;
}

答案是肯定不可以,所以接着问大家一个问题,private下边的三个变量,是声明还是定义

这是一个声明,不是定义,他们两个的区别就在于声明没有开空间,所以要定义变量,开空间来使用

1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没
有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个
类,来描述具体学生信息。
类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。
2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
3.做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设
计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象
才能实际存储数据,占用物理空间

类的大小模型

看代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;cout << sizeof(Date) << endl;cout << sizeof(d1) << endl;return 0;
}

大家猜猜这两个的内存大小分别是多少,一个是图纸,一个是根据图纸建造的房子

小tips:大家想想C语言期间学的结构体内存对齐

答案:

有这个结果可以知道,函数是没有存在类里的

大家想想,如果我们直接定义五个类变量,难道要生成五个函数用的空间吗,大家可以把函数想象成公用的,建立的那个类变量用的函数都是一个 

类的大概存储模式

   只保存成员变量,成员函数存放在公共的代码段

每一个成员变量都是不一一样的, 但是函数放在公共代码区

结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象

内存对齐规则

前边在C语言阶段其实我们说过内存对齐的规则,这里再给大家写一下:

1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

大家一定要熟记这个规则并且会运算,这个面试的时候很有可能会用到的,这里小编给大家整理的几个面试上关于内存对齐的面试题:

1. 结构体怎么对齐? 为什么要进行内存对齐?
2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

如果有问题可以在评论区提出来

this指针

上代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:void Printf(){cout << _year << '/' << _month << '//' << _day << endl;}void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2;return 0;
}

还是之前我们写的代码没有什么心意,但是大家看这一区域

我们在调用初始化函数的时候,使用的是我们在private中定义的三个变量吗?

答案:不是

我在之前说过,private里是声明,根本就没有开辟空间,并且在主函数部分我们开辟了两个变量,如果调用的是private里的变量,那么又是怎么一份变成两份的呢?所以我们根本没用这里的变量这就涉及到我们接下来要学的新知识点this指针。

void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

这是我们刚刚写的初始化函数,里边传了三个参数年月日完成传参赋值,其实在这个函数里,含有一个我们看不见的隐藏参数,他真正的原型应该是这样的:

void Init(Date *this ,int year, int month, int day)
    {
        this->_year = year;
        this->_month = month;
        this->_day = day;
    }

对比一下,多了个this指针,加粗的部分都是编译器自己加的

函数主体多了个参数,在在主函数调用时编译器也是自动给我们加了东西

我们自己写的:

int main()
{
    Date d1;
    d1.Init(2023, 7, 23);
    Date d2;
    d2.Init(2013, 7, 23);
    return 0;
}

实际上编译器底层编译的:

int main()
{
    Date d1;
    d1.Init(&d1,2023, 7, 23);
    Date d2;
    d2.Init(&d2,2013, 7, 23);
    return 0;
}

可以说我们可以运行代码,使用变量和函数都多亏了this指针

C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏
的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”
的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编
译器自动完成。

有一点值得注意,this指针在形参和实参的位置并不能由我们自己显示的写出来,但是可以在类里边显示的用

如:

class Date
{
public:void Init(int year, int month, int day){this->_year = year;this->_month = month;this->_day = day;}void Printf(){cout << this->_year << '/' << this->_month << '//' << this->_day << endl;}
private:int _year;int _month;int _day;
};

像这样,但是我们不写编译器会自动给我们加,写了反而多此一举,大家了解一下就行

this指针的特性

1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2. 只能在“成员函数”的内部使用
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递

 this指针的存储

我们都知道电脑的内存分为栈,堆,常量区,静态区,那么this指针存在哪里呢,大家不妨猜一猜

this指针是一个形参,一般都存在栈区

好了关于类和对象(上篇)就写到这,大家敬请期待下一期内容!!!

相关文章:

重生学c++系列第三课类和对象(上)

好的我们重生c系列的前两期已经介绍完了c祖师爷针对C语言补充的几个新功能&#xff0c;现在我们进入c的真正课题学习——类与对象: C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 比如说我们洗菜做饭&am…...

驱动开发-按键中断

编写LED灯的驱动&#xff0c;使用GPIO子系统&#xff0c;里面添加按键的中断处理 1.应用程序发送指令控制LED亮灭 2.按键1 按下&#xff0c;led1电位反转 按键2按下&#xff0c;led2电位反转 按键3 按下&#xff0c;led3电位反转 功能函数 #include<stdlib.h> #inclu…...

数据结构 | 二叉树的应用

目录 一、解析树 二、树的遍历 一、解析树 我们可以用解析树来表示现实世界中像句子或数学表达式这样的构造。 我们可以将((73)*(5-2))这样的数学表达式表示成解析树。这是完全括号表达式&#xff0c;乘法的优先级高于加法和减法&#xff0c;但因为有括号&#xff0c;所以在…...

python:卡尔曼和贝叶斯滤波器

本文分享一个Filerpy的说明文档和代码示例文档&#xff0c;有关于 Python 中的卡尔曼和贝叶斯滤波器。该方法可以应用于气象遥感等领域。 说明文档&#xff1a;https://filterpy.readthedocs.io/en/latest/kalman/KalmanFilter.html 参考代码链接&#xff1a;https://nbviewer.…...

走进 Go 语言基础语法 | 青训营 (1)

Powered by:NEFU AB-IN 文章目录 走进 Go 语言基础语法 | 青训营 (1)代码注释代码模板 走进 Go 语言基础语法 | 青训营 (1) 代码注释 /** Author: NEFU AB-IN* Date: 2023-08-06 09:44:15* FilePath: \GoTest\a.go* LastEditTime: 2023-08-06 11:00:45*/ package mainimport (&…...

基于边缘无线协同感知的低功耗物联网LPIOT技术:赋能智慧园区方案以及数字工厂领域

回到2000年左右&#xff0c;物联网的底层技术支撑还是“ZigBee”&#xff0c;虽然当时ZigBee的终端功耗指标其实也并不庞大&#xff0c;但是&#xff0c;“拓扑复杂导致工程实施难度大”、“网络规模小导致的整体效率低下”都成为限制其发展的主要因素。 LPWAN&#xff0c;新一…...

【《快速构建AI应用——AWS无服务器AI应用实战》——基于云的解决方案快速完成人工智能项目的指南】

基于云的人工智能服务可以自动完成客户服务、数据分析和财务报告等领域的各种劳动密集型任务。其秘诀在于运用预先构建的工具&#xff0c;例如用于图像分析的Amazon Rekognition或用于自然语言处理的AWS Comprehend。这样&#xff0c;就无须创建昂贵的定制软件系统。 《快速构…...

vue运行在IE浏览器空白报错SCRIPT1006: 缺少‘)‘ -【vue兼容IE篇】

其他浏览器均正常&#xff0c;但是切换ie模式&#xff0c;打开空白&#xff0c;F12打开报错缺少‘)‘ &#xff0c;如下图 在搜狗浏览器下点开报错&#xff1a;定格在crypto-js处 解决&#xff1a; 步骤一&#xff1a;使用npm安装babel-polyfill 依赖&#xff08;已安装了可忽…...

接口自动化测试Mock Get和Post请求

Mock可以模拟一个http接口的后台响应&#xff0c;可以模拟request&#xff0c;response 下载 moco-runner-0.11.0-standalone.jar 下载链接: https://pan.baidu.com/s/1bmFzvJPRnDlQ-cmuJ_3iRg 提取码: kpjv 确保安装了jdk,cmd下可以运行java -version 一、模拟不带参的get请求…...

WPF上位机8——C#与MySQL

ADO.NET 数据库连接 数据插入、删除、更改 数据查询 带单个参数 带多个参数 using MySql.Data.MySqlClient; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Text; using System.Threading.Tasks;namespace Wp…...

[JAVAee]网络编程-套接字Socket

目录 基本概念 发送端与接收端 请求与响应 ​编辑客户端与服务器 Socket套接字 分类 数据报套接字 流套接字传输模型 UDP数据报套接字编程 DatagramSocket API DatagramPacket API InetSocketAddress API 示例一: 示例二: TCP流数据报套接字编程 ServerSock…...

批量导出pdf为zip文件(可以修改zip中pdf名称)

核心代码 public static void compressZip1(HashMap<String,File> map, String rootPath, String zipFileName) throws FileNotFoundException {FileOutputStream fileOutputStream new FileOutputStream(zipFileName);ZipOutputStream zipOutputStream new ZipOutputS…...

[国家集训队] Tree II 题解报告

[国家集训队] Tree II 一道真板子题 就是练习LCT懒标记的题目 除了翻转标记以外还要维护乘法标记和加法标记 注意加法标记和乘法标记的维护&#xff01;&#xff01;&#xff01; 加法标记 因为splay的区间大小不是固定的&#xff0c;所以我们要维护size&#xff0c;并且…...

【redis】docker搭建redis集群

docker搭建redis集群&#xff0c;超级简单方便。 # 1. 拉取redis. 目前我拉取最新的是7.0.12 docker pull redis # 2. 下载配置文件 wget https://raw.githubusercontent.com/redis/redis/7.0/redis.conf # 3. 移到对应目录 mkdir -p /opt/docker/redis mv redis.conf /opt/d…...

前端个人年度工作述职报告(二十篇)

前端个人年度工作述职报告篇1 尊敬的各位领导、各位同仁&#xff1a; 大家好!按照20__年度我公司就职人员工作评估的安排和要求&#xff0c;我认真剖析、总结了自己的工作情况&#xff0c;现将本人工作开展情况向各位领导、同仁做以汇报&#xff0c;有不妥之处&#xff0c;希…...

TypeScript 编译配置

TypeScript的编译配置&#xff1a; 对单独一个ts文件进行监听编译 可使用tsc demo.ts -w 如果想对所有ts文件进行监听编译&#xff0c;监听到变化就自己编译&#xff0c;可以直接创建一个tsconfig.json文件。内容空着也OK&#xff1a;{}&#xff0c;执行 tsc 或 tsc -w 如果有…...

使用DMA传输实现单片机高效串口转发——以STM32系列为例

使用DMA传输实现单片机高效串口转发——以STM32系列为例 DateAuthorVersionNote2023.08.06Dog TaoV1.01. 完成了文档的撰写。 文章目录 使用DMA传输实现单片机高效串口转发——以STM32系列为例应用场景实现流程源码示例串口与中断配置DMA外设配置DMA发送数据函数串口中断服务函…...

一文了解 Android Auto 车载开发~

作者&#xff1a;牛蛙点点申请出战 背景 我的的产品作为一个海外音乐播放器&#xff0c;在车载场景听歌是一个很普遍的需求。在用户反馈中&#xff0c;也有很多用户提到希望能在车上播放音乐。同时车载音乐也可以作为提升用户消费时长一个抓手。 出海产品&#xff0c;主要服务…...

Pixel4 安卓源码及内核修改编译教程 | 基于Android12 AOSP

之前整理了 Pixel4上的源码过程&#xff0c;下载的话大家可以去镜像网站下载&#xff0c;可以节约很多时间。 实验设备&#xff1a;Ubuntu18.04 32G2T Pixel4 文章目录 一、安卓源码下载1.准备下载环境&#xff08;1&#xff09;安装Python 3.9&#xff08;2&#xff09;安装g…...

如何做好Code Review

本文主要从我们为什么需要CR&#xff1f;CR面临哪些挑战&#xff1f;CR的最佳实践几个方面分析&#xff0c;希望可以给读者一些参考。 为什么需要CR&#xff1f; 代码质量 定性来看&#xff0c;大家都认可Code Review&#xff08;后文简称CR&#xff09;能显著改善代码质量&…...

Unity技术框架集合、Unity技术栈汇总

引擎技术尝试 [Animancer-Pro] (https://assetstore.unity.com/packages/tools/animation/animancer-pro-116514) (基于Playable的简单强大的动画解决方案)[ProBuilder/UModeler] (https://assetstore.unity.com/packages/tools/modeling/umodeler-80868) (快速关卡原型构建…...

安卓SDK开发的一些疑问

目前&#xff0c;公司需要开发一套iOS和安卓的sdk&#xff0c;主要包含蓝牙管理、网络请求、倒计时等方案执行、蓝牙数据交互等功能。之前没有过开发安卓sdk的经历&#xff0c;写个笔记用以记录。 现在iOS sdk已经写了一部分&#xff0c;安卓开发我也习惯从iOS的角度类比来开发…...

【基础类】—三栏页面布局的方案和优缺点

一、假设高度已知&#xff0c;中间宽度自适应&#xff0c;三栏&#xff08;列&#xff09;布局的方案有哪些&#xff1f; float浮动、absolute绝对定位、flex弹性盒子、table表格布局、grid网格布局 浮动 float <style>* {margin: 0;padding: 0;}.container {width: 1…...

OPENCV C++(四)形态学操作+连通域统计

形态学操作 先得到一个卷积核 Mat kernel getStructuringElement(MORPH_RECT,Size(5,5)); 第一个是形状 第二个是卷积核大小 依次为腐蚀 膨胀 开运算 闭运算 Mat erodemat,dilatemat,openmat,closemat;morphologyEx(result1, erodemat, MORPH_ERODE, kernel);morphologyEx…...

tomcat上部署jpress

一.确保有jdk&#xff0c;tomcat和mysql环境 二.新建jpress数据库&#xff0c;新建jpress用户并赋予所有权限 三.将jpress的war上传到tomcat/apache-tomcat-8.5.70/webapps&#xff0c;具体根据你的实际tomcat安装路径为准&#xff0c;上传完成后他会自己解包 四.到浏览器完…...

篇十:外观模式:简化复杂系统

篇十&#xff1a;“外观模式&#xff1a;简化复杂系统” 开始本篇文章之前先推荐一个好用的学习工具&#xff0c;AIRIght&#xff0c;借助于AI助手工具&#xff0c;学习事半功倍。欢迎访问&#xff1a;http://airight.fun/。 另外有2本不错的关于设计模式的资料&#xff0c;分…...

linux gcc __attribute__

__attribute__ 1. 函数属性1.1 __attribute__((noreturn))1.2 __attribute__((format))1.3 __attribute__((const)) 2. 变量属性2.1. __attribute__((aligned))2.2. __attribute__((packed)) 3. 类型属性 __attribute__ 是 GCC 编译器提供的一种特殊语法&#xff0c;它可以用于…...

【SpringCloud】RabbitMQ基础

1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&#xff0c;…...

css, resize 拖拉宽度

效果如下&#xff1a; 可直接复制预览查看属性值: 关键样式属性&#xff1a; resize: horizontal; overflow-x: auto; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content…...

Python识别抖音Tiktok、巨量引擎滑块验证码识别

由于最近比较忙&#xff0c;所以本周搞了一个相对简单的验证码&#xff0c;就是抖音Tiktok的滑块验证码&#xff0c;这也是接到客户的一个需求。这种验证码通常在电脑端登录抖音、巨量引擎的的时候出现。 首先看一下最终的效果&#xff1a; 验证码识别过程 1、利用爬虫采集图…...