<C++>二、类和对象-构造函数

1.类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

2bb2888a284f040286e4c4ce432e13ea.png

2.构造函数

构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

构造函数的目的就是为了创建对象的时候同时初始化。

其特征如下:

1.函数名与类名相同。
2.无返回值
3.对象实例化时编译器自动调用对应的构造函数。也就是说在创建对象的时候,就对成员变量进行初始化。

下面是一个Stack栈的类:


class Stack
{
public://无参数构造函数 - 函数名与对象名相同Stack(){_a = nullptr;_size = _capacity = 0;}//带参数构造函数Stack(int n){_a = (int*)malloc(sizeof(int) * n);if (nullptr == _a){perror("malloc fail");exit(-1);}_capacity = n;_size = 0;}void Push(int x) {}void Destroy() {}
private:int* _a;int _size;int _capacity;
};
6eed38839bf21f69ace16a24e1d9bf3d.png
4.构造函数可以重载。(一个类可以有多个构造函数,也就是多种初始化方式)

//Date类举例
class Date
{
public://无参构造函数Date(){_year = 1;_month = 1;_day = 1;}//带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};
5c7f4e8d953771168b98db62260cc725.png

使用全缺省构造函数

在写构造函数的时候,推荐使用全缺省或者半缺省


class Date
{
public://全缺省Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};
982f189a5b82c5a671a3eab06a0d0279.png

全缺省构造函数不能和无参构造函数同时存在 - 出现歧义,编译器不知道调用哪个

5.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

为什么只能有一个默认构造函数? 因为调用的时候会发生歧义

不传参数就可以调用构造函数,一般建议每个类都提供一个默认构造函数

e90a1960b3dbb0b8ec350de5d54f9f1b.png
6.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
938304bb86094e0c4aff5e98af2ebdee.png

为什么自动生成了默认构造函数,打印出来的变量还是随机值呢?

C++规定默认生成的构造函数:

1.内置类型成员不做处理
2.自定义类型的成员,会去调用自定义类型的类的构造函数。
注意:C++11中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

默认构造函数的场景:

比如用栈实现队列:


//Stack类
class Stack
{
public://无参构造函数 - 函数名与对象名相同Stack(){cout << "Stack()" << endl;_a = nullptr;_size = _capacity = 0;}//带参数构造函数Stack(int n){cout << "Stack()" << endl;_a = (int*)malloc(sizeof(int) * n);if (nullptr == _a){perror("malloc fail");exit(-1);}_capacity = n;_size = 0;}void Push(int x) {}void Destroy() {}~Stack()  //析构函数{cout << "~Stack()" << endl;free(_a);_a = nullptr;_size = _capacity = 0;}
private:int* _a;int _size;int _capacity;
};
470730d0a0b989c37d0cd01cbc844484.png

可以看到MyQueue中没有构造函数,它会自动调用Stack类中的构造函数。

2.1构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。


class Date
{
public:
Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:
int _year;
int _month;
int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

2.2 初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。


class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}private:int _year;int _month;int _day;
};
【注意】
1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2.类中包含以下成员,必须放在初始化列表位置进行初始化:
① 引用成员变量
② const成员变量
③ 自定义类型成员(且该类没有默认构造函数时)

class B
{
public:B(int):_b(0){cout << "B()" << endl;}
private:int _b;
};class A
{
public://1、哪个对象调用构造函数,初始化列表是它所有成员变量定义的位置//2、不管是否显示在初始化列表写,那么编译器每个变量都会初始化列表定义初始化//3、三个成员变量需要写初始化,一个是const变量,一个引用变量,一个自定义类型A()   //这是一个初始化列表:_x(1),_ref(_a1), _a2(1),_bb(0){_a1++;_a2--;}
private:int _a1 = 1;  // 声明int _a2 = 2;   //缺省值是在没有构造函数的时候,缺省值才有效 //const变量必须在定义的位置初始化,因为后面const变量就无法更改了//const int _x;   //加了这个const  编译器会报错,无法引用A的默认构造函数//const int _x = 1;  //C++11 可以这么给const int _x;int& _ref;B _bb;
};int main()
{A aa;   // 对象整体的定义,每个成员什么时候定义呢?//必须给每个成员变量找一个定义的位置,不然像const这样的成员,没办法初始化return 0;
}
3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。

class Time
{
public:Time(int hour = 0): _hour(hour){cout << "Time()" << endl;}private:int _hour;
};class Date
{
public:Date(int day){}private:int _day;Time _t;    //调用Time的构造函数 ,打印Time();
};int main()
{Date d(1);
}
4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

class A
{
public:A(int a): _a1(a), _a2(_a1){}void Print(){cout << _a1 << " " << _a2 << endl;}private:int _a2; //_a2先初始化,_a1在初始化   此时调用初始化列表,而_a1此时是随机值,所以_a2(_a1)后_a2为随机值int _a1;
};int main()
{A aa(1);aa.Print();
}/*     D
A.输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值
*/

2.3 explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。


class Date
{
public://1.单参构造函数,没有使用explicit修饰,具有类型转换作用Date(int year) : _year(year) {}// 2,虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用 - int转换为Date类Date(int year, int month = 1, int day = 1): _year(year), _month(month), _day(day) {}
private:int _year;int _month;int _day;
};int main()
{Date d1(2022);d1 = 2023;//用一个整型变量给日期类型对象赋值//实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值return 0;
}

class Date
{
public:// explicit修饰构造函数,禁止类型转换explicit Date(int year, int month = 1, int day = 1): _year(year), _month(month), _day(day) {}private:int _year;int _month;int _day;
};int main()
{Date d1(2022);d1 = 2023;  //err 编译器报错,没有与这些操作数匹配的 "=" 运算符,操作数类型为:  Date = intreturn 0;
}

class Date
{
public:Date(int year) : _year(year), _month(1), _day(1) {}friend ostream& operator<<(ostream& out, const Date& d);// operator是重载运算符,后面了解Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out, const Date& d)
{cout << d._year << " " << d._month << " " << d._day << endl;return out;
}int main()
{Date d1(2022); // 构造函数d1 = 2023; // 隐式类型转换  构造+拷贝+优化->构造  有的编译器可能不会优化// 构造,调用Date(year),在拷贝调用的是operator// Date& ref = 10;  //err ref是类类型 不能引用10const Date& ref = 10; // 可以,隐式类型转换,10构造一个对象,临时对象具有常属性,所以const可以引用cout << ref << endl;   //10 1 1return 0;
}
用explicit修饰构造函数,将会禁止构造函数的隐式转换。

3.析构函数

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

其特征如下:

1.析构函数名是在类名前加上字符 ~。
2.无参数无返回值类型。
3.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4.对象生命周期结束时,C++编译系统系统自动调用析构函数。
5.关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定义类型成员调用它的析构函数。(跟构造函数类似)
6.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类
7.析构函数的执行顺序是先创建的对象后析构,后创建的对象先析构

下面是几个可能需要使用析构函数的场景:

1.动态分配内存:当一个对象在堆上分配内存时,需要手动释放内存以避免内存泄漏。在对象的析构函数中释放分配的内存是一种常见的方法。

class MyClass {
public:MyClass() {data = new int[100];}~MyClass() {delete[] data;}
private:int* data;
};
2.异常处理:如果在构造函数中发生了异常,那么对象可能没有完全初始化,因此析构函数应该只释放已经成功初始化的资源。

class MyClass {
public:MyClass() {// 可能会抛出异常data = new int[100];// 如果抛出异常,data指针将保持为nullptr}~MyClass() {if (data != nullptr) {delete[] data;}}
private:int* data = nullptr;
};

4.拷贝构造函数

概念:

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

拷贝构造函数也是特殊的成员函数,其特征如下:

1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

为什么需要拷贝构造呢?

因为编译器只能对内置类型进行直接拷贝,而自定义类型的拷贝,需要使用拷贝构造

09f30d90803d892972981dd6b3150368.png

Date类中的成员变量都是内置类型,编译器可以直接拷贝。而Stack栈中的a是指针,如果直接拷贝,st1和st2指向了同一块内存地址,当st2进行析构处理的时候,也就把st1的a也清理了,当st2push一个1,st1也会跟着push一个1,所以对于自定义不能直接拷贝。

为什么拷贝构造使用传值调用会发生无穷递归?


//传值调用
Date(Date d){_year = d._year;_month = d._month;_day = d._day;}
20ca443f4b000ecc142ab43dc5aed985.png

//正确的写法
class Date
{
public:Date(int year = 2023, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2021, 2, 3);Date d2(d1);d2.Print();return 0;
}
817c4ed378097a9200851bfb078212cd.png
3.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

默认生成拷贝构造和赋值重载:

a、内置类型完成浅拷贝/值拷贝--按byte一个一个拷贝
b、自定义类型,去调用这个成员拷贝构造/赋值重载

//自动生成构造拷贝函数对内置类型进行拷贝
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};
ec23b44cecffb8850a29fd63ca0e65d4.png
4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

//自动生成构造拷贝函数对自定义类型进行拷贝
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){cout << "Stack(size_t capacity = 10)" << endl;_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}private:DataType* _array;size_t    _size;size_t    _capacity;
};
99a5c008f6222e789cc241f21b2a33ae.png

这里会发现上面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。

5d80bb2b8cb17c7270bcd10a63368624.png

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

5.拷贝构造函数典型调用场景:

a、使用已存在对象创建新对象

b、函数参数类型为类类型对象

c、函数返回值类型为类类型对象


typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){cout << "Stack(size_t capacity = 10)" << endl;_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}//stack类的拷贝构造深拷贝Stack(const Stack& st){cout << "Stack(const Stack& st)" << endl;//深拷贝开额外空间,为了避免指向同一空间_array = (DataType*)malloc(sizeof(DataType)*st._capacity);if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}//进行字节拷贝memcpy(_array, st._array, sizeof(DataType)*st._size);_size = st._size;_capacity = st._capacity;}~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}private:DataType *_array;size_t    _size;size_t    _capacity;
};class MyQueue
{
public://MyQueue什么都不写,会调用默认的构造函数,也就是Stack类的构造函数// 默认生成构造// 默认生成析构// 默认生成拷贝构造private://默认构造函数初始化 - 默认析构函数Stack _pushST;//默认构造函数初始化 - 默认析构函数Stack _popST;int _size = 0;
};int main()
{Date d1(2023, 2, 5);d1.Print();Date d2(d1);Date d3 = d1; //  拷贝构造d2.Print();Stack st1;st1.Push(1);st1.Push(2);st1.Push(4);Stack st2(st1);cout << "=============================" << endl;MyQueue q1;//q1拷贝q2  q1中有两个Stack类和一个size,size直接拷贝,stack类是调用stack拷贝构造进行拷贝MyQueue q2(q1);return 0;
}

 

 

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

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

相关文章

【Linux多线程】死锁问题介绍

死锁 &#x1f96d;什么是死锁&#x1f965;死锁产生的条件&#x1f95d;如何避免死锁&#x1f345; 避免死锁算法 &#x1f96d;什么是死锁 死锁是多线程或多进程编程中的一种常见问题&#xff0c;指的是两个或多个线程&#xff08;或进程&#xff09;相互等待对方持有的资源&…

搜索二叉树_SearchBinaryTree

目录 搜索二叉树的原理 搜索二叉树的搜索时间复杂度 二叉搜索树实现_key 模型 节点 构造函数 查找 中序遍历 插入 循环 递归 删除 循环 1.删除叶子节点 2.删除有一个孩子的节点 3.左右孩子都不为空 递归 析构函数 拷贝构造 operator key_value 模型 节点 …

Mysql触发器

1.触发器 触发器是与表有关的数据库对象&#xff0c;指在 insert / update / delete 之前或之后&#xff0c;触发并执行触发器中定义的SL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性&#xff0c;日志记录&#xff0c;数据校验等操作。 使用别名 OLD 和 …

mysql安装教程保姆级

MySQL免安装本地运行 1.下载MySQL2.创建install.bat3.init.sql 初始创建4.环境变量配置5.运行 install.bat 管理员权限运行6.连接成功遇到的问题 1.下载MySQL ①地址&#xff1a;https://downloads.mysql.com/archives/community/ ②解压 2.创建install.bat 放在mysql>b…

算法综合篇专题一:双指针问题

"就算没有看清那株灿烂的花蕊&#xff0c;也应该放声歌颂赞美鲜红的玫瑰" 1、移动零 (1) 题目解析 (2) 算法原理 class Solution { public:void moveZeroes(vector<int>& nums) {for(int cur0,dest-1;cur<nums.size();cur){if(nums[cu…

java设计模式-建造者(Builder)设计模式

介绍 Java的建造者&#xff08;Builder&#xff09;设计模式可以将产品的内部表现和产品的构建过程分离开来&#xff0c;这样使用同一个构建过程来构建不同内部表现的产品。 建造者设计模式涉及如下角色&#xff1a; 产品&#xff08;Product&#xff09;角色&#xff1a;被…

前端工程化最佳实践:项目结构、代码规范和文档管理

文章目录 前端工程化最佳实践项目结构设计与组织文档管理和注释规范国际化和本地化实践 前端工程化的未来发展趋势前端工程化领域的最新技术和工具WebAssembly 和前端性能优化可持续性和可访问性的趋势 总结前端工程化的关键知识点前端工程化对项目和团队的价值 前端工程化最佳…

《golang设计模式》第一部分·创建型模式-01-单例模式(Singleton)

文章目录 1. 概述1.1 目的1.2 实现方式 2. 代码示例2.1 设计2.2 代码 1. 概述 1.1 目的 保证类只有一个实例有方法能让外部访问到该实例 1.2 实现方式 懒汉式 在第一次调用单例对象时创建该对象&#xff0c;这样可以避免不必要的资源浪费 饿汉式 在程序启动时就创建单例对象…

卷积神经网络

目录 注意&#xff1a;有参数计算的才叫层 1.应用 1.1分类和检索 1.2超分辨率重构 1.3医学任务 1.4无人驾驶 1.5人脸识别 2.卷积 2.1卷积神经网络和传统网络的区别 2.2整体框架 2.3理解卷积&#xff08;重点&#xff09; 2.4为何要进行多层卷积 2.5卷积核的参数 2.6…

C++STL库中的list

文章目录 list的介绍及使用 list的常用接口 list的模拟实现 list与vector的对比 一、list的介绍及使用 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向带头循环链表结构&#xff0c;双向带头循…

基于ssm+mysql+jsp高校疫情防控出入信息管理系统

基于ssmmysqljsp高校疫情防控出入信息管理系统 一、系统介绍二、功能展示1.登陆2.教师管理3.学生管理4.打卡记录管理5.学生申请通行证6.通行证管理7.留言信息管理8.公告类型管理9.公告管理 四、获取源码 一、系统介绍 学生 : 个人中心、打卡记录管理、学生申请通行证、通行证管…

<C++> STL_string

目录 1.string类 2.string类的接口 2.1 成员函数 2.1.1 string构造函数 2.1.2 string赋值运算 2.1.3 string析构函数 2.2 string对象访问以及迭代器 2.2.1 string的遍历方式 2.2.2 迭代器的使用 2.2.3 const_迭代器的使用 2.2.4 at 2.2.5 back和front 2.3 string容…

Docker基础命令(一)

Docker使用1 一、运行终端 打开终端&#xff0c;输入docker images &#xff0c;如果运行正常&#xff0c;表示docker已经可以在本电脑上使用了 二、docker常用命令 指令说明docker images查看已下载的镜像docker rmi 镜像名称:标签名删除已下载的镜像docker search 镜像从官…

Java如何实现将类文件打包为jar包

目录 将类文件打包为jar包 1.写类文件2.编译3.测试4.打jar包jar包应该怎么打&#xff1f; 1.首先确保你的项目2.选中你的项目,点右键3.选择runnable jar file4.如下图,直接看图5.然后点finish 将类文件打包为jar包 为实际项目写了一个工具类&#xff0c;但是每次使用时都需要…

xcode中如何显示文件后缀

xcode14.3 用不惯mac电脑真恶心&#xff0c;改个显示文件后缀找半天 1、首先双击打开xcode软件 2、此时&#xff0c;电脑左上角出现xcode字样(左上角如果看不到xcode字样&#xff0c;再次点击xcode软件弹出来就有了)&#xff0c;鼠标右键它&#xff0c;点击setting或者Prefere…

安卓音视频多对多级联转发渲染

最近利用自己以前学习和用到的音视频知识和工程技能做了一个android的sdk,实现了本地流媒体ipc rtsp 拉流以及自带mip usb等camera audio节点产生的流媒体通过webrtc sfu的方式进行多对多级联发布共享,网状结构&#xff0c;p2p组网&#xff0c;支持实时渲染以及转推rtmp&#x…

我的第一个前端(VS code ,Node , lite-server简易服务器,npm 运行)

第一种方式&#xff1a;使用Visual Studio Code创建并运行 第一个前端项目的步骤&#xff0c;如下&#xff1a; 1. 下载和安装Visual Studio Code&#xff1a; 访问Visual Studio Code官方网站&#xff08;Visual Studio Code - Code Editing. Redefined&#xff09;并根据你…

Java类的加载过程是什么?

本文重点 本文将学习类的加载过程,java命令将class文件放到类加载器中,那么之后经历了什么?本文将对其进行学习。 类加载方式? 两种加载方式:隐式加载(静态加载)和显式加载(动态加载) 隐式加载指的是在程序使用new等方式创建对象的时候,会隐式地调用类的加载器把…

【Docker】Docker的优势、与虚拟机技术的区别、三个重要概念和架构及工作原理详细讲解

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 作者简介&#xff1a; 辭七七&#xf…

驶向专业:嵌入式开发在自动驾驶中的学习之道

导语: 自动驾驶技术在汽车行业中的快速发展为嵌入式开发领域带来了巨大的机遇。作为自动驾驶的核心组成部分&#xff0c;嵌入式开发在驱动汽车的智能化和自主性方面发挥着至关重要的作用。本文将探讨嵌入式开发的学习方向、途径以及未来在自动驾驶领域中的展望。 一、学习方向:…