C++: 类和对象(下) (初始化列表, 隐式类型转换, static成员, 友元, 内部类, 匿名对象)

news2024/12/21 21:40:03

文章目录

  • 一. 再谈构造函数
    • 1. 构造函数体赋值
    • 2. 初始化列表
    • 3. explitcit 关键字
  • 二. static 成员
    • 1. 概念
    • 2. 特性
      • 声明静态成员
      • 使用类的静态成员
      • 定义静态成员
  • 三. 友元
    • 1. 友元函数
    • 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;
};

int main()
{
  Date d1(2023, 11, 2);   //定义加初始化

  return 0;
}

类相当于声明, main 函数中的 Date d1(2023, 11, 2);相当于定义加初始化.

虽然上述构造函数调用一次后, 对象中已经有了个初始值, 但是不能将其称为对对象中成员变量进行初始化.

构造函数体中的语句只能将其称为赋初值, 而不能称为初始化.因为初始化只能初始化一次, 而构造函数体内可以多次赋值.

如果 Date 中有一个引用成员变量或者是 const 成员变量, 也可以用构造函数体初始化吗?

显然是不可以的, 类中成员变量声明和定义分离, 但是引用和 const 常变量都要求在声明的时候同时需要初始化.

在这里插入图片描述

编译器提示引用和 const 常变量没有初始化. 因为构造函数体并不是真正的初始化定义的地方, 只是一个赋值的地方.

真正类成员定义的地方是在构造函数初始化列表的地方.

2. 初始化列表

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

class Date
{
public:
  Date(int year, int month, int day)
  : _year(year)
  , _month(month)
  , _day(day)
  , x(_year)
  , y(month)
  {}

private:
  int _year;
  int _month;
  int _day;

  int& x;
  const int y;
};

int main()
{
  Date d1(2023, 11, 2);   //定义加初始化

  return 0;
}

初始化列表是每个成员定义的地方, 这样引用和 const 成员变量在初始化列表位置进行初始化就不会出现错误了.


还有几点关于初始化列表需要注意:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

编译器会直接报错
在这里插入图片描述


  1. 类中包含以下成员, 必须在初始化列表进行初始化:
  • 引用成员变量
  • const 成员变量
  • 自定义类型成员(且该类没有默认构造函数时)
class A       // A类 没有默认构造函数
{
public:
  A(int a)
  : _a(a)
  {}
private:
  int _a;
};

class B
{
public:
  B(int x, int a, int ref, int n)
  : _x(x)       // 普通成员变量初始化
  , _aobj(a)    // 自定义成员初始化
  , _ref(ref)   // 引用成员初始化
  , _n(n)       // const 成员初始化
  {}
private:
  int _x;       // 普通成员变量
  A _aobj;      // 没有默认构造函数
  int& _ref;    // 引用
  const int _n; // const
};

  1. 没有在初始化列表中的成员也会定义, 但是内置类型是随机值, 自定义类型会调用其默认构造函数.
    如果自定义类型没有默认构造函数, 则会编译报错.

内置成员变量没有在初始化列表定义初始化, 也不在构造函数函数体内部赋值:

  B(int x, int a, int ref, int n)
  : _aobj(a)    // 自定义成员初始化
  , _ref(ref)   // 引用成员初始化
  , _n(n)       // const 成员初始化
  {}

在这里插入图片描述

如果成员没有在初始化列表进行定义, 也没有在构造函数体内部进行赋值, 最终成员是随机值.

注意, 这里的引用也是随机值, 是因为 _ref 引用了一个栈帧开辟的临时变量, 虽然不会因为未初始化报错, 但是这样的引用肯定也是不正确的.

自定义类型没有默认构造函数, 也没有在初始化列表中进行定义, 会直接编译报错:

在这里插入图片描述

如果将 A 类的构造函数改成默认构造函数, 就不会出现编译错误.


  1. 初始化列表优先于缺省值, 只有初始化列表没有显示初始化的时候, 才会使用成员变量的缺省值.
class Date
{
public:
  Date(int year, int month, int day)
  : _year(2)        // 初始化列表也定义了值
  , _month(month)
  , _day(day)
  {}

private:
  int _year = 1;    // 缺省值
  int _month;
  int _day;
};

虽然输入了 4, 并没有用到 4. 同时也没有用到缺省值 1, 最终还是初始化列表中的初始化值 2.

在这里插入图片描述


  1. 尽量使用初始化列表初始化, 因为不管是否使用初始化列表, 对于自定义类型成员变量, 一定会先使用初始化列表初始化.
class Time
{
public:
  Time(int t = 4)
  {
    cout << "Time(int t = 4)" << endl;
    _t = t;
  }
private:
  int _t;
};

class Date
{
public:
  Date()
  {}
private:
  int _day;
  Time _t;
};

如果类中有自定义成员, 且该自定义类有默认构造函数, 即使类中没有初始化列表, 编译器也会自动调用该自定义成员的默认构造函数.

初始化列表是一个成员定义的地方, 即使没有显示写, 程序也会走隐含的初始化列表, 最终结果就是: 内置类型是随机值, 自定义类型调用它的默认构造函数.
在这里插入图片描述


  1. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序, 与其在初始化列表中的先后次序无关

问下面的代码最后打印什么?

class A
{
public:
  A(int a)
    :_a1(a)
    ,_a2(_a1)
  {}

  void Print()
  {
    cout << _a1 << ' ' << _a2 << endl;
  }

private:
  int _a2;
  int _a1;
};

int main()
{
  A aa(1);
  aa.Print();

  return 0;
}

答案是 1 随机值

虽然初始化列表中 _a1_a2 的前面, 但是实际初始化的顺序是按照类中成员定义的顺序的.

在类中 _a2 先于 _a1, 所以先初始化 _a2, 但是此时 _a1 尚未初始化, 是随机值, 即 _a2 是随机值. 接着用 1 初始化 _a1.

在这里插入图片描述


总结
初始化列表解决的问题

  • 必须在定义初始化的成员(引用成员变量, const成员变量, 没有默认构造函数的自定义成员)
  • 有些自定义成员想要自己显示初始化, 自己来控制

80%-100%用初始化列表进行定义

  • 有些事初始化列表并不能做, 比如 _a((int*)malloc(sizeof(int) * capacity)), 虽然这样给 _a 初始化了, 但是却不能在初始化列表进行检查 malloc 是否成功, 检查只能在构造函数函数体进行.

3. explitcit 关键字

下述的类, 会有一种特殊的构造

class A
{
public:
  A(int a)
  :_a(a)
  {
    cout << "A(int a)" << endl;
  }

  A(const A& aa)
  {
    _a = aa._a;
    cout << "A(const A aa)" << endl;
  }
private:
  int _a;
};

int main()
{
  A aa1 = 1;  //会发生隐式类型转换

  return 0;
}

表达式 A aa1 = 1; 看起来会出错, 将一个 int 类型的对象赋值给了一个 A 类型的对象.

但是却自动发生了隐式类型转换, 首先会用 1 构造了一个临时对象, 再用这个临时对象拷贝构造 aa1
在这里插入图片描述

在这里插入图片描述

需要注意的是, 这种隐式转换只适用于内置类型或者单参数(传一个参数的半缺省)构造函数支持

如果类的构造函数需要两个参数, 或者隐式转换不了对应的参数类型, 会直接编译报错.

在这里插入图片描述


如果不想隐式转换, 可以在构造函数前加 explicit 关键字.

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

在这里插入图片描述


如果构造函数有一个参数, 且使用了 explicit 关键字, 强制转换依旧是可以的

A aa1 = A(1);

这样就相当于先使用 1 调用构造函数构造了一个匿名对象, 随后用这个匿名对象拷贝构造 aa1.


补充

C++98 不支持多参数隐式转换
C++11 支持多参数隐式转换

例如:

Date d1 = {2023, 1, 3}; //列表初始化
//不是下面这种写法
//Date d1 = (2023, 1, 3); //相当于 Date d1 = 3;

先用 2023, 1, 3 作为构造函数参数创建了一个临时变量, 随后用临时变量拷贝构造 d1.

如果想要良好的代码可读性, 推荐使用 explicit 关键字
例如智能指针是不希望隐式类型转换的

二. static 成员

1. 概念

声明为 static 的类成员称为类的静态成员.
static 修饰的成员变量, 称为静态成员变量, 静态成员变量一定要在类外进行初始化.
static 修饰的成员函数, 称为静态成员函数.


需要实现一个类, 计算程序中创建出了多少个类对象.

第一种想法是创建全局变量 count, 在类的所有构造函数中, 加上 count++.
但是全局变量很容易被修改, 容易出错.

第二种想法是为类添加一个普通成员变量.
但是这个成员变量是属于对象的, 每创建一个对象, 就会多一个计数的成员, 数值一直为 1.

第三种想法就是为类添加一个静态成员变量.
静态成员变量是属于每个类的, 类实例化的所有对象都共有这一个成员.

class A
{
public:
  A() { ++_scount; }
  A(const A& a) { ++_scount; }
  ~A() { --_scount; }
  static int getCount() { return _scount; }

private:
  static int _scount; // 声明静态成员
};

int _scount = 0; // 初始化静态成员
int main()
{
  cout << A::getCount() << endl;
  A a1, a2;
  A a3(a1);
  cout << A::getCount() << endl;

  return 0;
}

结果为:

在这里插入图片描述


2. 特性

声明静态成员

通过在成员的声明之前加上关键字 static 使其与类关联在一起.
和其他成员一样, 静态成员可以是 publicprivate 的.
静态数据成员的类型可以是常量, 引用, 指针, 类类型等.

类的静态成员为所有类对象共享, 不属于某个具体的对象, 存放在静态区.
类似的, 静态成员函数也不予任何对象绑定在一起, 它们不包含 this 指针. 所以, 静态成员函数是不能声明成 const 的.

使用类的静态成员

可以使用作用域运算符直接访问静态成员

cout << A::getCount() << endl;  //使用域作用限定符可以直接访问静态成员

虽然静态成员不属于类的某个对象, 但是仍然可以使用类的对象, 引用或指针来访问静态成员.

A a1;
A* a2 = &a1;

cout << a1.getCount() << endl;    //通过对象或者引用调用静态成员函数
cout << a2->getCount() << endl;   //通过指向对象的指针调用静态成员函数

成员函数不需要使用域作用限定符就可以直接使用静态成员. 反之, 静态成员函数不可以使用非静态成员.

A() { ++_scount; }  //成员函数直接使用静态成员

定义静态成员

和其他的成员函数一样, 静态成员函数既可以在类中也可以在类外定义.
在类外定义的时候, 不能重复 static 关键字, 该关键字只出现在类内部的声明语句

int A::getCount()
{
  return _scount;
}

和类的所有成员一样, 当我饿们指向类外不得静态成员时, 必须指明成员所属的类名. static 关键字只出现在类内部的声明语句中.


因为静态成员变量不属于类的任何一个对象, 所以它们并不是在创建类的时候被定义的.
这就意味着静态成员变量不是由类的构造函数初始化的, 必须在类的外部定义和初始化每一个静态成员.

int A::_scount = 0;   //类外定义并且初始化静态成员变量

要想确保对象只定义一次, 最好的方法是把静态成员的变量的定义与其他非内联函数的定义放在同一个文件中.

静态成员函数和静态成员变量就相当于受限制的全局函数和全局变量, 不存在于对象里, 属于所有对象, 受类域和访问限定符限制.


总结

  • 静态成员所有类对象共享, 不属于某个具体的对象, 存放在静态区.
  • 静态成员变量必须在类外定义, 定义时不添加 static 关键字, 类中只是声明.
  • 类静态成员可用 类名::静态成员 或者 对象.静态成员 来访问
  • 静态成员函数没有隐藏的 this 指针, 不能访问任何非静态成员
  • 静态成员也是类的成员, 受 public, private, protected 访问限定符的限制
  • 静态成员函数不可以调用非静态成员函数
  • 非静态成员函数可以调用类的静态成员函数

三. 友元

友元提供了一种突破封装的方式, 有时提供了遍历. 但是友元会增加耦合度, 破坏了封装, 所以友元不宜多用.

友元分为: 友元函数友元类

1. 友元函数

在之前的博客由详细讲解, 用友元函数处理 <<>> 重载的问题

友元函数可以直接访问类的私有成员, 它是定义在类外部普通函数, 不属于任何类, 但需要在类的内部声明, 声明需要加 friend 关键字.

说明:

  • 友元函数可访问类的私有和保护成员, 但不是类的成员函数
  • 友元函数不能用 const 修饰
  • 友元函数可以在类定义的任何地方声明, 不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

2. 友元类

友元类的所有成员函数都可以是另一个类的友元函数, 都可以访问另一个类中的非公有成员.

  • 友元是单向的, 不具有交换性.
    比如 Time 类和 Date 类, 在 Time 类中申明 Date 类为其友元类, 那么可以在 Date 类中直接访问 Time 类的私有成员变量, 但想在 Time 类中访问 Date 类中私有的成员变量则不行.
  • 友元关系不能传递
    如果 C 是 B 的友元, B 是 A 的友元, 则不能说明 C 是 A 的友元.
  • 友元关系不能继承.
class Time
{
  friend class Date; //声明 Date 类为 Time 类的友元类, 则在 Date 类中可以直接访问 Time 类的私有成员变量
  public:
    Time (int hour = 0, int minute = 0, int second = 0)
      :_hour(hour)
      ,_minute(minute)
      ,_second(second)
    {}

  private:
    int _hour;
    int _minute;
    int _second;
};

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

    void SetTimeOfDate(int hour, int minute, int second)
    {
      // 直接访问 Time 类的私有成员变量
      _t._hour = hour;
      _t._minute = minute;
      _t._second = second;
    }

private:
  int _year;
  int _month;
  int _day;
  Time _t;
};

四. 内部类

概念: 如果一个类定义在另一个类的内部, 这就叫做内部类.
内部类是一个独立的类, 它不属于外部类, 更不能通过外部类的对象去访问内部类的成员.
外部类对内部类没有任何优越的访问权限.

注意: 内部类就是外部类的友元类, 参见友元类的定义, 内部类可以通过外部类的对象参数来访问外部类的所有成员. 但是外部类不是内部类的友元.

特性:

  • 内部类可以定义在外部类的 public, protected, private
  • 内部类可以直接访问外部类的 static 成员, 不需要外部类的对象/类名
  • sizeof(外部类) = 外部类, 和内部类没有任何关系.
class A
{
  private:
    static int _k;
    int _h;
  public:
    class B // B 天生是 A 的友元
    {
      public:
        void func(const A& a)
        {
          cout << _k << endl;   // ok
          cout << a._h << endl; // ok
        }
    };
};

int A::_k = 1;

int main()
{
  A::B b;
  b.func(A());

  cout << sizeof(A) << endl;
  cout << sizeof(A::B) << endl;

  return 0;
}

B 就是一个普通类, 只是受 A 的类域和访问限定符限制, 本质相当于被封装了一下
在这里插入图片描述

五. 匿名对象

class A
{
  public:
    A(int a = 0)
      :_a(a)
    {
      cout << "A(int a)" << endl;
    }

    ~A()
    {
      cout << "~A()" << endl;
    }

  private:
    int _a;
};

class Solution
{
  public:
    int Sum_Solution(int n)
    {
      //..
      return n;
    }
};

int main()
{
  A();    // 定义匿名对象, 生命周期只有这一行, 下一行就会自动调用析构函数

  A aa1(2); // 先用 2 构造匿名对象, 再用匿名对象拷贝构造 aa1

  // 匿名对象在这样的场景下就很好用
  Solution().Sum_Solution(10);

  return 0;
}

在这里插入图片描述

本章完.

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

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

相关文章

一张数学地图带你尽览数学分支

我们在学校学习的数学可能也只是数学领域的冰山一角&#xff0c;作为庞大而多样的学科&#xff0c;我今天将通过一张数学地图带你尽览数学分支。 本数学地图对应的视频讲解地址如下&#xff1a; https://www.youtube.com/watch?vOmJ-4B-mS-Y 另外&#xff0c;由于图片较大&a…

STM32中断简介

中断系统 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行&#xff1b; 以上是中断的…

【Linux】JREE项目部署与发布

目录 一.jdk安装配置 1.1.传入资源 1.2. 解压 1.3. 配置 二.Tomcat安装 2.1.解压开启 2.2. 开放端口 三.MySQL安装 3.1.解压安装 3.2.登入配置 四.后端部署 今天就到这里了哦&#xff01;&#xff01;希望能帮到你哦&#xff01;&#xff01;&#xff01; 一.jdk…

半导体(芯片)制造工艺流程简单说

半导体行业是国民经济支柱性行业之一&#xff0c;是信息技术产业的重要组成部分&#xff0c;是支撑经济社会发展和保障国家安全的战略性、基础性和先导性产业&#xff0c;其发展程度是衡量一个国家科技发展水平的核心指标之一&#xff0c;属于国家高度重视和鼓励发展的行业。 …

ant design pro of vue怎么使用阿里iconfont

一 使用离线iconfont 首先需要生成图所有图标对应的js文件。如下图所示&#xff0c;将生成的js代码复制&#xff0c;在项目中创建一个js文件&#xff0c;将代码粘贴进去。这里我将js文件放在了src/assets/iconfont下面 然后&#xff0c;在main.js中引入文件&#xff0c;并进…

强化IP地址管理措施:确保网络安全与高效性

IP地址管理是网络安全和性能管理的关键组成部分。有效的IP地址管理可以帮助企业确保网络的可用性、安全性和高效性。本文将介绍一些强化IP地址管理的关键措施&#xff0c;以帮助企业提高其网络的安全性和效率。 1. IP地址规划 良好的IP地址规划是强化IP地址管理的基础。它涉及…

【操作系统】考研真题攻克与重点知识点剖析 - 第 2 篇:进程与线程

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

基于SSM框架的管理系统-计算机毕设 附源码 23402

基于SSM框架的管理系统 摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。在目前的形势下&#xff0c;无论是从国家到企业再到家庭&#xff0c;计算机都发挥着其不可替代的作用&#xff0c;可以说…

【洛谷 P1012】[NOIP1998 提高组] 拼数 题解(贪心算法+字典序排序)

[NOIP1998 提高组] 拼数 题目描述 设有 n n n 个正整数 a 1 … a n a_1 \dots a_n a1​…an​&#xff0c;将它们联接成一排&#xff0c;相邻数字首尾相接&#xff0c;组成一个最大的整数。 输入格式 第一行有一个整数&#xff0c;表示数字个数 n n n。 第二行有 n n …

mac 安装 selenium + chrome driver

前言 使用 selenium 模拟浏览器渲染数据&#xff0c;需要依赖各浏览器的驱动才能完成&#xff0c;因此需要单独安装chrome driver 查看本地 chrome 浏览器的版本 可以看到我这里已经是 arm 架构下最新的版本了 下载对应的 chrome driver 访问下面的地址&#xff1a; Chrome…

集合贴3——智能客服系统

基础课17——智能客服系统-CSDN博客文章浏览阅读56次。近年来&#xff0c;实体客服机器人开始出现在银行办公厅、电信商务厅等场合&#xff0c;形成了网络智能客服、电话智能客服、实体客服机器人综合智能客服系统&#xff0c;这初步形成了一种新的产业形式。https://blog.csdn…

Verilog刷题[hdlbits] :Always nolatches

题目&#xff1a;Always nolatches Suppose you’re building a circuit to process scancodes from a PS/2 keyboard for a game. Given the last two bytes of scancodes received, you need to indicate whether one of the arrow keys on the keyboard have been pressed.…

Matlab使用cftool进行曲线拟合

第一步&#xff0c;导入要拟合的输入和输出数据 导入excel时&#xff0c;如果作为列矢量导入&#xff0c;则会将excel的数据按列导入&#xff0c;并且&#xff0c;默认将第一行的变量名作为每一列的矢量名。 第二步&#xff0c;打开插件curve fitting 在应用程序里打开&#…

MathType公式编辑器7.4.4最新中文版下载

不得不承认MathType公式编辑器是一个强大的数学公式编辑器&#xff0c;专门为理科生准备&#xff0c;MathType数学公式编辑器用于编辑数学试卷、书籍、报刊、论文、幻灯演示等文档&#xff0c;能够帮助使用者快速的在各种文档中插入符号和公式&#xff0c;多复杂的公式都可轻松…

金融工作怎么做?低代码如何助力金融行业

10月30日至31日&#xff0c;中央金融工作会议在北京举行。金融是国民经济的“血脉”&#xff0c;是国家核心竞争力的重要组成部分。会议指出&#xff0c;党的十八大以来&#xff0c;在党中央集中统一领导下&#xff0c;金融系统有力支撑经济社会发展大局&#xff0c;坚决打好防…

小白高效自学-网络安全(黑客技术)

网络安全零基础入门学习路线&规划 初级 1、网络安全理论知识&#xff08;2天&#xff09; 了解行业相关背景&#xff0c;前景&#xff0c;确定发展方向。 学习网络安全相关法律法规。 网络安全运营的概念。 等保简介、等保规定、流程和规范。&#xff08;非常重要&#x…

MarkdownPad2, CSDN及有道云笔记对数学公式的支持

MarkdownPad2, CSDN及有道云笔记对数学公式的支持 MarkdownPad2的安装 下载并安装MrakdownPad2软件&#xff0c;下载地址安装awesomium_v1.6.6_sdk_win&#xff0c; 下载地址安装支持公式编辑的插件&#xff0c;注意&#xff0c;在MarkdownPad2的 Tools > Options > Ad…

Qt/C++开发经验小技巧286-290

国内站点&#xff1a;https://gitee.com/feiyangqingyun 国际站点&#xff1a;https://github.com/feiyangqingyun 很多时候项目越写越大&#xff0c;然后就可能遇到&#xff0c;明明之前很简单的一段代码&#xff0c;运行的好好的&#xff0c;就那么几行几十行&#xff0c;为何…

VxLAN 网络,看这一篇就够了!

你们好&#xff0c;我的网工朋友。 VLAN作为传统的网络隔离技术&#xff0c;是网工们工作中离不开的技术&#xff0c;这已经给你说过太多。 今天想跟你聊的是VXLAN。 一个字母的区别&#xff0c;你分得清VLAN和VXLAN吗&#xff0c;他们有啥区别&#xff1f;你知道VXLAN到底是…

Python Opencv实践 - 车牌定位(纯练手,存在失败场景,可以继续优化)

使用传统的计算机视觉方法定位图像中的车牌&#xff0c;参考了部分网上的文章&#xff0c;实际定位效果对于我目前使用的网上的图片来说还可以。实测发现对于车身本身是蓝色、或是车牌本身上方有明显边缘的情况这类图片定位效果较差。纯练手项目&#xff0c;仅供参考。代码中im…