C++-类和对象(下)

news2024/11/16 18:45:43

C++-类和对象(下)

  • 一,const成员函数
  • 二,再谈构造函数
    • 1,初始化列表
    • 2,explicit关键字
  • 三,static成员
  • 四,友元(friend)
    • 1,全局函数做友元
    • 2,类做友元
    • 3,成员函数做友元
  • 五,内部类
  • 六,匿名对象
  • 七,拷贝对象时编译器的优化
  • 八,再次理解类和对象

一,const成员函数

一般自定类型的对象传参时,都是传引用这样可以减少一次拷贝构造,同时前面加const进行修饰,以免通过引用来改变实体。

class A
{
public:
    A(int x = 0)
        : _a(x)
    {
    }
    void Print()
    {
        cout << _a << endl;
    }

private:
    int _a;
};
void func(const A &a)
{
    a.Print();
}
int main()
{
    A a1;
    return 0;
}

这种情况下,我们通过形参a来调用Print()函数,会发生报错。原因是因为这里发生了权限的放大,由于在调用成员函数时有一个默认的形参就是this指针,但a的this指针是被const修饰的,也就是说将 const *this 传给了 *this,这是一个明显的权限放大问题, 为了解决这一问题就要引出const成员函数这一概念。

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。并且const是加在成员函数的形参列表的后面。

class A
{
public:
    A(int x = 0)
        : _a(x)
    {
    }
    void Print() const
    {
        cout << _a << endl;
    }

private:
    int _a;
};

思考下面的四个问题:

  • const对象可以调用非const成员函数吗?
    答案是不可以。
    const *this 传给 *this 会发生权限的放大。
  • 非const对象可以调用const成员函数吗?
    答案是可以。
    *this 是可以传给 const *this 的,这里是权限的缩小。
  • const成员函数内可以调用其它的非const成员函数吗?
    答案是不可以。
    const成员函数中,const修饰的是*this ,如果其调用其他非const成员函数,会发生权限的放大。
  • 非const成员函数内可以调用其它的const成员函数吗?
    答案是可以。
    *this 传给 const *this 是允许的,这里是权限的缩小。

二,再谈构造函数

1,初始化列表

之前提到类的实例化是对象定义的时候,但对象内部的成员变量是何时被定义的呢?
因为有些变量是要在定义时必须初始化的,比如 const int p;int& a;
初始化列表就是每个成员变量定义的地方 ,初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。例如下面这样:

class A
{
public:
    A(int a = 0, int b = 0, int c = 0)
        : _a(a), _b(b), _c(c)
    {
    }
    void Print() const
    {
        cout << _a << endl;
    }

private:
    int _a;
    int _b;
    int _c;
};

在调用构造函数的时候,会先进行初始化列表中的内容,在进行构造函数{ } 内部的内容,{ }里的内容实际上是对定义出来的对象进行赋值,而初始化列表是成员变量定义初始化的地方。
注意:

  • 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  • 引用成员变量const成员变量,自定义类型成员变量且没有默认构造函数时,必须使用初始化列表进行初始化。
  • 无论你有没有显示的写出初始化列表,编译器都先使用初始化列表进行初始化。如果没有显示写,那么编译器会去看有没有为成员变量提供缺省值。
  • 成员变量在类中的声明次序,就是其在初始化列表中的初始化次序
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();
}

由于_a2定义在_a1之前,所以在初始化列表中会先对_a2初始化,所以会被初始化成随机值,_a1会被初始化成1.

2,explicit关键字

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

class A
{
public:
    A(int a)
        : _a(a)
    {
    }

private:
    int _a;
};
int main()
{
    A a1 = 8;
    return 0;
}

这里的8会调用A类的构造函数,隐式类型转换成A类型,接着通过拷贝构造初始化a1,这里编译器会优化成一个构造函数(后面会讲到)。

class Date
{
public:
    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 = 2023;
    return 0;
}

这里也会发生隐式类型转换,为了防止这种类型转换可以用explicit 修饰成员函数,来禁止构造函数的隐式类型转换。

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

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

三,static成员

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。

特性:

  • 静态成员变量必须在类外定义初始化,定义时不添加static关键字,类内只是声明。
class A
{
public:
    A(int a = 1)
        : _a(a)
    {
    }

private:
    int _a;
    static int _b;
};
int A::_b = 1;
  • 静态成员为所有类对象共享,不属于某个具体对象存放在静态区。
  • 类的静态成员可以通过类名::静态成员(前提是public作用域下) 或者对象.静态成员。
  • 静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制。

思考:

  • 实现一个类,计算程序中创建出了多少对象?

分析:由于创建对象时都会调用构造函数或者是拷贝构造,所以在这两个函数上动手脚就可以了。可以定义一个计数器,每次调用这两个函数计数器加1,有人会想到用全局变量做计数器,但是这种做法不是很安全,因为全局函数谁都可以更改。
所以我们采用静态的成员变量充当这个计数器,静态的成语变量不被某个对象所拥有,而是属于整个类的。

class A
{
public:
    A()
    {
        _count++;
    }
    A(const A &a)
    {
        _count++;
    }

private:
    static int _count;
};
void func()
{
    A a1;
    A a2(a1);
    A a3;
}
int main()
{
    func();
    return 0;
}

上面这种情况,我们是调用了一个函数,在函数内部创建了许多对象,而出了这个函数我们无法去访问_count,除非我们再创建一个对象,用其去访问_count,最终将_count的值减1。
其实大可不必这样,我们只需再类中publi作用域下定义一个静态的成员函数,起作用就是返回_count的值,这样我们就可以指明类的作用域直接访问这个成员函数,不再需要创建一个新的对象。

class A
{
public:
    A()
    {
        _count++;
    }
    A(const A &a)
    {
        _count++;
    }
    static int sum()
    {
        return _count;
    }

private:
    static int _count;
};
void func()
{
    A a1;
    A a2(a1);
    A a3;
}
int main()
{
    func();
    cout << A::sum() << endl;
    return 0;
}
  • 静态成员函数可以调用非静态成员函数吗?

由于静态成员函数中没有this指针这一默认参数,所以其无法去调用非静态成员函数。

  • 非静态成员函数可以调用类的静态成员函数吗?

静态成员函数是属于整个类的,可以通过非静态成员函数去访问。

四,友元(friend)

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类

1,全局函数做友元

我们之前讲述过运算符重载,但是我们在用成员函数重载<<运算符的时候,会出现问题,因为流插入(<<)操作符有两个操作数,而采用成员函数重载它的时候,this指针充当了第一个操作数,第二个操作数是ostream类的对象,所以使用时会出现下面的情况:
a1 << cout

  • ostream类
    在这里插入图片描述

cout实际上是ostream类的一个对象,之所以他能使用cout << 这种形式打印数据,实际上他做了运算符重载+函数重载。

为了解决上述情况,我们用全局函数进行运算符重载:

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
    }

private:
    int _a;
};
ostream &operator<<(ostream &_cout, const A &a)
{
    cout << a._a << endl;
}

但是这种写法是不对的,无法在类外直接访问类的私有成员,所以我们要将这个函数作为A类的友元函数。

class A
{
    friend ostream &operator<<(ostream &_cout, const A &a);

public:
    A(int a = 0)
        : _a(a)
    {
    }

private:
    int _a;
};
ostream &operator<<(ostream &_cout, const A &a)
{
    cout << a._a << endl;
}

说明:

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

2,类做友元

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

  • 友元关系是单向的,不具有交换性。
class B;
class A
{
    friend class B;
    friend ostream &operator<<(ostream &_cout, const A &a);

public:
    A(int a = 0)
        : _a(a)
    {
    }

private:
    int _a;
};
class B
{
public:
    B()
    {
    }

private:
    int _b;
};

B是A的友元类,B类的所有成员函数都可以访问A类的私有成员,但是A类不是B的友元类,A的成员函数无法访问B的私有成员。

  • 友元关系不能传递
    如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  • 友元关系不能继承。

3,成员函数做友元

class B;
class A
{
    friend void B::func(const A &a);
    friend ostream &operator<<(ostream &_cout, const A &a);

public:
    A(int a = 0)
        : _a(a)
    {
    }

private:
    int _a;
};
class B
{
public:
    B()
    {
    }
    void func(const A &a)
    {
        cout << a._a << endl;
    }

private:
    int _b;
};

注意:这样写是会编译不通过的,编译器在编译的时候只会向上去找,不会向下去找,编译器到达 friend void B::func(const A &a);这一行的时候,它并不知道有这个函数,所以要在这之前前先声明一下这个函数,但是成员函数实在类内声明的,所以B类的定义要放在A类的上面。

class A;
class B
{
public:
    B()
    {
    }
    void func(const A &a)
    {
        cout << a._a << endl;
    }

private:
    int _b;
};

class A
{
    friend void B::func(const A &a);
    friend ostream &operator<<(ostream &_cout, const A &a);

public:
    A(int a = 0)
        : _a(a)
    {
    }

private:
    int _a;
};

五,内部类

==概念:==如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
==注意:==内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:

  • 内部类可以定义在外部类的public、protected、private都是可以的。
  • 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
class A
{
private:
    static int k;
    int h;

public:
    class B // B天生就是A的友元
    {
    public:
        void foo(const A &a)
        {
            cout << k << endl;   // OK
            cout << a.h << endl; // OK
        }
    };
};
int A::k = 1;
int main()
{
    A::B b;
    b.foo(A());
    return 0;
}
  • sizeof(外部类)=外部类,和内部类没有任何关系。
cout << sizeof(A) << endl;

在这里插入图片描述
属于A类内部的只有h这一变量。

六,匿名对象

匿名对象就是在类的实例化定义对象的时候,不用写对象的名字。

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
    }

private:
    int _a;
};

int main()
{
    A a1();
    return 0;
}

之前说过这样定义a1的对象使用全缺省的构造函数,是错误的,他会和函数的声明冲突(函数名 a1,返回值A类型,参数为空)。
但可以这样来创建匿名对象。

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
    }

private:
    int _a;
};

int main()
{
    A();
    return 0;
}

==注意:==匿名对象的声明周期就在这一行,过了这一行就会调用析构函数。
在这里插入图片描述

七,拷贝对象时编译器的优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A(int a)" << endl;
    }
    A(const A &aa)
        : _a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }
    A &operator=(const A &aa)
    {
        cout << "A& operator=(const A& aa)" << endl;
        if (this != &aa)
        {
            _a = aa._a;
        }
        return *this;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }

private:
    int _a;
};
void f1(A aa)
{
}
A f2()
{
    A aa;
    return aa;
}
  • 传值传参
int main()
{
    A aa1;
    f1(aa1);
    cout << endl;
    return 0;
}

在这里插入图片描述

在创建aa1时发生一次构造,传值传参发生一次拷贝构造。

int main()
{
    f1(1);
    f1(A(2));
    return 0;
}

第一种方式:1首先会发生隐式类型转换,调用一次构造函数,隐式类型转换出来的对象传递给形参发生一次拷贝构造,但是由于发生在一行中,所以编译器会优化成一次构造。
第二种方式:创建一个匿名对象直接传参,首先会一次构造函数,接着一次拷贝构造,但编译器会优化成为一个构造。
在这里插入图片描述

  • 传值返回
A f2()
{
    A aa;
    return aa;
}
int main()
{
    A aa2 = f2();
    return 0;
}

aa对象创建一次构造,传值返回一次拷贝构造,用返回值初始化aa2又一次拷贝构造,但编译器会优化成一次构造+一次拷贝构造。

A f2()
{
    return A();
}
int main()
{
    A aa2 = f2();
    return 0;
}

如果传一个匿名对象做返回,那么编译器会优化为一次构造。

在这里插入图片描述
总结

  • 接收返回值对象,尽量拷贝构造接收不要赋值接收
  • 函数中返回对象时,尽量返回匿名对象
  • 尽量使用const& 传参
    注意
    编译器不同所作出的优化也可能不太一样,越新的编译器做出的优化越大。如果你的编译器做出的优化和我举例中的优化不太一样,也是正常的。

八,再次理解类和对象

现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现
实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创
建对象后计算机才可以认识。比如想要让计算机认识洗衣机,就需要:

  1. 用户先要对现实中洗衣机实体进行抽象—即在人为思想层面对洗衣机进行认识,洗衣机有什么属性,有那些功能,即对洗衣机进行抽象认知的一个过程
  2. 经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面相对象的语言(比如:C++、Java、Python等)将洗衣机用类来进行描述,并输入到计算机中
  3. 经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才能洗衣机是什么东西。
  4. 用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。
    在类和对象阶段,大家一定要体会到,类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象

在这里插入图片描述

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

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

相关文章

冷冻电镜 - ChimeraX Density Map 密度图 操作

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://blog.csdn.net/caroline_wendy/article/details/129055160 由冷冻电镜所生成的Volume,需要观察其内部结构,使用ChimeraX进行操作。 加载Volumes,例如my_volume.mrc 效果如下: 高斯滤波 在命令行(Co…

python 数据分析可视化实战 超全 附完整代码数据

代码数据&#xff1a;https://download.csdn.net/download/qq_38735017/873799141.1 数据预处理1.1.1 异常值检测①将支付时间转为标准时间的过程中发生错误&#xff0c;经排查错误数据为‘2017/2/29’,后将其修改为‘2017/2/27’。②经检测发现部分订单应付金额与实付金额都为…

解决jupyter以及windows系统中pycharm编译器画图的中文乱码问题大全

一、jupyter环境下中文乱码问题解决 我们在jupyter的notebook中使用matplotlib画图的时候&#xff0c;经常性的会遇见一些中文乱码显示□的情况,如下所示: 在此&#xff0c;网上给出的方法大多是以下的解决方法&#xff1a; import matplotlib.pyplot as pltplt.rcParams[fo…

界面组件Telerik UI for WinForms R1 2023——全新的Windows 11主题

Telerik UI for WinForms拥有适用Windows Forms的110多个令人惊叹的UI控件。所有的UI for WinForms控件都具有完整的主题支持&#xff0c;可以轻松地帮助开发人员在桌面和平板电脑应用程序提供一致美观的下一代用户体验。Telerik UI for WinForms组件发布了2023年第一个重大版本…

QCon演讲实录(下):多云管理关键能力实现与解析-AppManager

在上篇中&#xff0c;我们已经基本了解了多云管理。现在&#xff0c;我们将深入探讨多云管理关键能力实现&#xff1a;AppManager。 什么是AppManager&#xff1f; 上面我们讲了理论、我们自己使用的交付流程和整体架构&#xff0c;下面我们进入关键能力实现与解析的环节&…

Allegro如何通过视图显示区分动态和静态铜皮操作指导

Allegro如何通过视图显示区分动态和静态铜皮操作指导 用Allegro做PCB设计的时候,通常动态和静态铜皮是无法通过视图显示区分的,只能通过show element查看得知,如下图 左边铜皮是动态铜皮,右边是静态铜皮 但Allegro可以通过一些设置让动静态铜皮以不同效果显示出来 具体操…

Elasticsearch:使用 intervals query - 根据匹配项的顺序和接近度返回文档

Intervals query 根据匹配项的顺序和接近度返回文档。Intervals 查询使用匹配规则&#xff0c;由一小组定义构成。 然后将这些规则应用于指定字段中的术语。 这些定义产生跨越文本正文中的术语的最小间隔序列。 这些间隔可以通过父源进一步组合和过滤。 上述描述有点费解。我…

【计算机网络】HTTPS协议原理

文章目录一、认识HTTPS协议二、为什么要发明HTTPS三、HTTP与HTTPS的区别四、常见的加密方式1. 对称加密2. 非对称加密3. 数据摘要4. 数字签名五、HTTPS的原理探究方案1&#xff1a;只使用对称加密方案2&#xff1a;只使用非对称加密方案3&#xff1a;双方都使用非对称加密方案4…

2.15学习总结

上次被学长的问题给问住了&#xff0c;突然发现自己动规有点糊涂&#xff0c;然后就去屁颠屁颠的复习&#xff0c;找几个之前做过的题&#xff0c;突然发现&#xff0c;竟然还是写了好久才写出来&#xff0c;怎么说呢&#xff0c;信心被强烈打击到&#xff0c;然后自己找了一个…

MyBatis 之二(增、删、改操作)

文章目录1. 修改操作1.1 在 mapper&#xff08;interface&#xff09;里面添加修改方法的声明1.2 在 XMl 中添加 <update> 标签和修改的 sql 代码1.3 在 UserMapper 中右键 Generate 点击 Test 生成 update 测试类2. 删除操作2.1 在 mapper &#xff08;interface&#x…

重生之我是赏金猎人-SRC漏洞挖掘(六)-记一次有趣的客户端RCE+服务端XXE挖掘

0x01 起因 朋友给某甲方做渗透测试&#xff0c;奈何甲方是某知名保险&#xff0c;系统太耐艹&#xff0c;半天不出货 兄弟喊我来一块来看&#xff0c;于是有了本文 0x02 客户端RCE一处 朋友把靶标发给我看了下&#xff0c;除了两个下载链接啥也没有 链接下载下来的东西如图…

回溯算法理论基础

目录什么是回溯法回溯法的效率回溯法解决的问题如何理解回溯法回溯法模板什么是回溯法 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 所以以下讲解中&#xff0c;回溯函数也就是递归函数&#xff0c;指…

SpringCloud: sentinel降级配置、热点参数、系统规则 配置到nacos

一、application.yml spring.cloud.sentinel cloud:nacos:discovery:# 服务注册地址server-addr: xxx.xxx.xxx.xxx:8848sentinel:eager: truetransport:# 控制台地址dashboard: localhost:9999# nacos配置持久化datasource:ds2:nacos:server-addr: xxx.xxx.xxx.xxx:8848dataId…

YOLO 格式数据集制作

目录 1. YOLO简介 2.分割数据集准备 3.代码展示 整理不易&#xff0c;欢迎一键三连&#xff01;&#xff01;&#xff01; 1. YOLO简介 YOLO&#xff08;You Only Look Once&#xff09;是一种流行的目标检测和图像分割模型&#xff0c;由华盛顿大学的 Joseph Redmon 和 Al…

B端产品设计表单的主要分类和相关控件认识

在 Ant、TDesign、Arco 等开源系统中&#xff0c;表单的控件罗列、解释都已经非常全面了&#xff0c;即使是新手完整的看一遍&#xff08;这可不能偷懒&#xff5e;&#xff09;&#xff0c; 也能对表单相关控件有个大致的认识了。 之所以还要更新今天这篇内容&#xff0c;就是…

「5」线性代数(期末复习)

&#x1f680;&#x1f680;&#x1f680;大家觉不错的话&#xff0c;就恳求大家点点关注&#xff0c;点点小爱心&#xff0c;指点指点&#x1f680;&#x1f680;&#x1f680; 目录 第四章 向量组的线性相关性 &5&#xff09;向量空间 第五章 相似矩阵及二次型 &a…

HIVE 安装

目录 启动hadoop 把hive压缩包拷贝到虚拟机里面 解压 改名 配置环境变量 新建一个hive-site.xml文件&#xff0c;并编辑 配置文件 添加jar包 初始化mysql 启动hive 创建数据库 使用数据库 创建表 添加数据 查看数据 删除表 安装虚拟机 安装JDK 安装Hadoop …

亿级高并发电商项目-- 实战篇 --万达商城项目 九(广告服务、安装Redis优化用户缓存、广告服务实现类等开发)

专栏&#xff1a;高并发---分布式项目 亿级高并发电商项目-- 实战篇 --万达商城项目搭建 一 &#xff08;商家端与用户端功能介绍、项目技术架构、数据库表结构等设计&#xff09; 亿级高并发电商项目-- 实战篇 --万达商城项目搭建 一 &#xff08;商家端与用户端功能介绍、项…

多功能手机-课后程序(JAVA基础案例教程-黑马程序员编著-第三章-课后作业)

【案例3-3】多功能手机 记得 关注&#xff0c;收藏&#xff0c;评论哦&#xff0c;作者将持续更新。。。。 【案例介绍】 案例描述 随着科技的发展&#xff0c;手机的使用已经普及到每个家庭甚至个人&#xff0c;手机的属性越来越强大&#xff0c;功能也越来越多&#xff0c;因…

测试开发之Django实战示例 第十一章 渲染和缓存课程内容

第十一章 渲染和缓存课程内容在上一章中&#xff0c;使用了模型继承和通用关系建立弹性的课程、章节和内容的关联数据模型&#xff0c;并且建立了一个CMS系统&#xff0c;在其中使用了CBV&#xff0c;表单集和AJAX管理课程内容。在这一章将要做的事情是&#xff1a;创建公开对外…