【C++ 学习 ④】- 类和对象(下)

news2024/11/27 19:34:39

目录

一、初始化列表

1.1 - 定义

1.2 - 使用初始化列表的原因

1.3 - 成员变量的初始化顺序

二、静态成员

2.1 - 静态成员变量

2.2 - 静态成员函数

三、友元

3.1 - 友元函数

3.2 - 友元类

四、内部类

五、匿名对象

5.1 - 匿名对象的特性

5.2 - 匿名对象的使用场景

六、编译器所做的一些优化


参考资料

  1. C++中的初始化列表详解。

  2. C++ 类构造函数初始化列表 | 菜鸟教程 (runoob.com)。

  3. C++ explicit 关键字 - 知乎 (zhihu.com)。

  4. C++ static静态成员变量详解 (biancheng.net)。

  5. C++ static静态成员函数详解 (biancheng.net)。

  6. C++友元函数和友元类(C++ friend)详解。

  7. C++11运算符重载详解与向量类重载实例(<<,>>,+,-,*等)。

  8. C++输入输出(cin和cout) (biancheng.net)。


一、初始化列表

1.1 - 定义

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式

class Date
{
public:
    // 一、在构造函数体内进行赋值操作
    /*Date(int year = 1949, int month = 10, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }*/
​
    // 二、使用初始化列表
    Date(int year = 1949, int month = 10, int day = 1)
        : _year(year), _month(month), _day(day) {}
private:
    int _year;
    int _month;
    int _day;
};

注意,一定要弄清楚初始化(initialization)和赋值(assignment)之间的区别

在构造函数体内进行的赋值操作不能称作初始化,因为初始化只能进行一次,而赋值可以进行多次

1.2 - 使用初始化列表的原因

  1. 性能原因。对于内置类型的成员变量,使用初始化列表和在构造函数体内赋值差别不是很大,但是对于类类型的成员变量来说,最好使用初始化列表。例如

    #include <iostream>
    using namespace std;
    ​
    class A
    {
    public:
        // 默认的构造函数
        A(int x = 0) : _i(x)
        {
            cout << "A(int x = 0)" << endl;
        }
    ​
        // 拷贝构造函数
        A(const A& a) : _i(a._i)
        {
            cout << "A(const A& a)" << endl;
        }
    ​
        // 赋值运算符重载
        A& operator=(const A& a)
        {
            cout << "A& operator=(const A& a)" << endl;
            _i = a._i;
            return *this;
        }
    ​
        void Print() const
        {
            cout << _i << endl;
        }
    private:
        int _i;
    };
    ​
    class B
    {
    public:
        // 拷贝构造函数
        B(const A& a)
        {
            _a.Print();
            _a = a;
            _a.Print();
        }
    private:
        A _a;
    };
    ​
    int main()
    {
        A a(10);
        // A(int x = 0)
    ​
        B b(a);
        // A(int x = 0)
        // 0
        // A& operator=(const A& a)
        // 10
        return 0;
    }

    从概念上来讲,构造函数的执行可以分为两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段

    1. 在初始化阶段,所有类类型(class type)的成员变量都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中

    2. 计算阶段一般用于执行构造函数体内的赋值操作

    所以 B 类中类类型的成员变量 _a 在进入构造函数体之前就已经初始化完成了,即调用了对应的默认构造函数;在进入函数体之后,进行的是对已经初始化的类对象的赋值操作,即调用了对应的赋值运算符重载。

    class B
    {
    public:
        B(const A& a) : _a(a) 
        { 
            _a.Print(); 
        }
    private:
        A _a;
    };

    class B
    {
    public:
        B(int x = 0) : _a(x)
        {
            _a.Print();
        }
    private:
        A _a;
    };

  2. 除了性能原因之外,有时候初始化列表是不可或缺的。类包含以下成员变量时,必须放在初始化列表位置进行初始化

    • const 成员变量,因为 const 对象必须初始化。
    • 引用类型的成员变量,因为引用必须初始化。
    • 没有默认构造函数的类类型成员变量,因为使用初始化列表时不必调用默认构造函数来初始化,可以直接调用对应的构造函数来初始化。
    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
    	// 带参的构造函数
    	A(int x) : _i(x) { cout << "A(int x)" << endl; }
    private:
    	int _i;
    };
    
    class B
    {
    public:
    	B(int y, int& r, int x)
    		: _j(y), _r(r), _a(x) {}
    private:
    	const int _j;  // const 成员变量
    	int& _r;  // 引用类型的成员变量
    	A _a;  // 没有默认构造函数的类类型的成员变量
    };
    
    int main()
    {
    	int n = 20;
    	B b(10, n, 30);
    	// A(int x)
    	return 0;
    }

1.3 - 成员变量的初始化顺序

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

class A
{
public:
    A(int x = 0) : _j(x), _i(_j) {}
​
    void Print() const
    {
        cout << _i << " " << _j << endl;
    }
private:
    int _i;
    int _j;
};
​
int main()
{
    A a;
    a.Print();  // 随机值 0
    return 0;
}


二、静态成员

2.1 - 静态成员变量

对象的内存中包含了成员变量,不同的对象占用不同的内存,这使得不同对象的成员变量相互独立,它们的值不受其他对象的影响。可是有时候我们希望在多个对象之间共享数据,在对象 a 改变了某份数据后,对象 b 可以检测到。共享数据的典型使用场景是计数

在 C++ 中,可以使用静态成员变量来实现多个对象共享数据的目标。静态成员变量是一种特殊的成员变量,它被关键字 static 修饰。

#include <iostream>
using namespace std;
​
class A
{
public:
    A() { ++_count; }
    A(const A& a) { ++_count; } 
    ~A() { --_count; }
    static int _count;  // 静态成员变量
};
​
int A::_count = 0;
​
int main()
{
    cout << A::_count << endl;  // 0
    A a1;
    A a2;
    A a3(a1);
    A a4(a2);
    cout << A::_count << endl;  // 4
    return 0;
}

这段代码定义了一个类 A,其静态成员变量 _count 用来统计当前创建出来的类对象的个数。

static 成员变量的特性

  1. static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 _count 分配一份内存,所有对象使用的都是这份内存中的数据。

  2. static 成员变量不占用对象内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问,所以 sizeof(A) == 1

  3. static 成员变量既可以通过对象来访问,也可以通过类来访问,例如:A::_count

  4. static 成员变量必须在类的外部初始化,具体形式为

    type class::name = value;

    静态成员变量在初始化时不能再加 static,但必须要有数据类型。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化。

  5. static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在类外初始化时分配,所以没有在类外部初始化的 static 成员变量不能使用

2.2 - 静态成员函数

在类中,static 除了可以声明静态成员变量,还可以声明静态成员函数。

编译器在编译一个普通成员函数时,会隐式地增加一个形参 this,并把调用该函数的对象的地址赋值给 this,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数

普通成员函数可以访问所有成员(包括成员变量和成员函数),而静态成员函数因为没有 this 指针,不知道指向哪个对象,所以无法访问对象的成员变量,即不能访问普通成员变量,也无法调用普通成员函数,只能访问静态成员变量和调用静态成员函数

#include <iostream>
using namespace std;
​
class A
{
public:
    A() { ++_count; }
    A(const A& a) { ++_count; }
    ~A() { --_count; }
    static int GetCount() { return _count; }  // 静态成员函数
private:
    static int _count;   // 静态成员变量
};
​
int A::_count = 0;
​
int main()
{
    cout << A::GetCount() << endl;  // 0
    A a1;
    A a2;
    A a3(a1);
    A a4(a2);
    cout << A::GetCount() << endl;  // 4
    return 0;
}


三、友元

私有成员只能在类的成员函数内部访问,如果想在别处访问对象的私有成员,只能通过类提供的接口(成员函数)间接地进行。这些固然能带来数据隐藏的好处,利于将来程序的扩充,但也会增加程序书写的麻烦。

C++ 设计者认为,如果有的程序员真的非常怕麻烦,就是想在类的成员函数外部直接访问对象的私有成员,那还是做一点妥协以满足他们的愿望为好,这也算是眼前利益和长远利益的折中。因此,C++ 就有了友元(friend) 的概念。打个比方,这相当于是说:朋友是值得信任的,所以可以对他们公开自己的隐私。

友元分为两种:友元函数和友元类

3.1 - 友元函数

在定义一个类的时候,可以把一些函数(包括全局函数和其他类的成员函数)声明为友元,这样那些函数就成为该类的友元函数,在友元函数内部就可以访问该类对象的私有成员了。注意:友元函数可以在类定义的任何地方声明,不受类访问限定符的限制

将全局函数声明为友元的写法如下:

friend 返回值类型 函数名(参数列表);

将其他类的成员函数声明为友元的写法如下:

friend 返回值类型 其他类的类名::成员函数名(参数列表);

但是,不能把其他类的私有成员函数声明为友元

示例

#include <iostream>
using namespace std;
​
class Date
{
    friend ostream& operator<<(ostream& out, const Date& d);
    friend istream& operator>>(istream& in, Date& d);
public:
    Date(int year = 1949, int month = 10, int day = 1)
        : _year(year), _month(month), _day(day) {}
private:
    int _year;
    int _month;
    int _day;
};
​
// 流插入运算符(<<)重载
ostream& operator<<(ostream& out, const Date& d)
{
    out << d._year << "-" << d._month << "-" << d._day;
    return out;
}
​
// 流提取运算符(>>)重载
istream& operator>>(istream& in, Date& d)
{
    in >> d._year >> d._month >> d._day;
    return in;
}
​
int main()
{
    Date d;
    cout << d << endl;  // 1949-10-1
    cin >> d;  // 假设输入:2023 5 1
    cout << d << endl;  // 2023-5-1
    return 0;
}
  1. 在编写 C++ 程序时,如果需要使用输入输出时,则需要包含头文件 iostream,它包含了用于输入输出的对象,例如常见的 cin 表示标准输入、cout 表示标准输出,cerr 表示标准错误。

    coutcin 都是 C++ 的内置对象,而不是关键字。C++ 库定义了大量的类(Class),程序员可以使用它们来创建对象,coutcin 分别是 ostream 类和 istream 类的对象,只不过它们是由标准库的开发者提前创建好的,可以直接拿来使用。这种在 C++ 中提前创建好的对象称为内置对象。

  2. 无法将 << 和 >> 运算符重载为成员函数

    #include <iostream>
    using namespace std;
    ​
    class Date
    {
    public:
        Date(int year = 1949, int month = 10, int day = 1)
            : _year(year), _month(month), _day(day) {}
    ​
        // 流插入运算符(<<)重载
        ostream& operator<<(ostream& out)
        {
            out << _year << "-" << _month << "-" << _day;
            return out;
        }
    private:
        int _year;
        int _month;
        int _day;
    };
    ​
    int main()
    {
        Date d;
        d << cout;  // 1949-10-1
        // 等价于:
        // d.operator<<(cout);
        return 0;
    }

    因为成员函数的第一个参数一定是隐藏的 this 指针,所以对象 d 必须放在 << 的左侧,显然这不符合程序员的使用习惯,违反了直觉。如果将 >> 重载为成员函数也是同样的问题

3.2 - 友元类

一个类 A 可以将另一个类 B 声明为自己的友元,类 B 的所有成员函数就都可以访问类 A 对象的私有成员。在类定义中声明友元类的写法如下:

friend class 类名;

示例

class Time
{
    friend class Date;
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 = 1949, int month = 10, int day = 1)
        : _year(year), _month(month), _day(day) {}
​
    void SetTimeOfDate(int hour, int minute, int second)
    {
        _t._hour = hour;
        _t._minute = minute;
        _t._second = second;
    }
private:
    int _year;
    int _month;
    int _day;
    Time _t;
};
​
int main()
{
    Date d(2023, 5, 1);
    d.SetTimeOfDate(12, 30, 00);
    return 0;
}

注意:

  1. 友元关系是单向的,不具有交换性。以上述的 Time 类和 Date 类例,在 Time 类中声明 Date 类为其友元类,那么可以在 Date 类的成员函数中直接访问 Time 类对象的私有成员,但是反过来不行。

  2. 友元关系不能传递。如果 B 是 A 的友元,C 是 B 的友元,不能说明 C 是 A 的友元。

  3. 友元关系不能继承。


四、内部类

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

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

特性

  1. 内部类可以定义在外部类的 public、protected、private 都是可以的。

    如果内部类定义在 public,则可以通过 外部类名::内部类名 来创建内部类的对象。

  2. 内部类可以直接访问外部类的 static 成员,不需要外部类的对象/类名。

  3. sizeof(外部类) = 外部类,和内部类没有任何关系。

#include <iostream>
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x) {}
    // 内部类
    class B
    {
    public:
        B(int y = 0) : _j(y) {}
        void func(const A& a)
        {
            cout << _s << " " << a._i << endl;
        }
    private:
        int _j;
    };
private:
    static int _s;
    int _i;
};
​
int A::_s = 1;
​
int main()
{
    cout << sizeof(A) << endl;  // 4
​
    A a(10);
    A::B b;
    b.func(a);  // 1 10
    return 0;
}


五、匿名对象

#include <iostream>
using namespace std;
​
class A
{
public:
    A(int x = 0) : _i(x) 
    { 
        cout << "A(int x = 0)" << endl; 
    }
​
    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _i;
};
​
int main()
{
    // 非匿名对象:
    // A a1;
    // A a2(10);
​
    // 匿名对象(顾名思义,即没有名字的对象):
    A();
    A(10);  
    return 0;
}

5.1 - 匿名对象的特性

  1. 匿名对象的生命周期只有它所在的那一行

  2. 匿名对象具有常性

    // A& ra = A(10);  // error(权限放大)
    const A& ra = A(10);  // ok
  3. 使用常引用会延长匿名对象的生命周期

5.2 - 匿名对象的使用场景

  1. 当方法只调用一次的时候就可以使用匿名对象

    #include <iostream>
    using namespace std;
    ​
    class Solution
    {
    public:
        int SumSolution(int n)
        {
            int sum = 0;
            for (int i = 1; i <= n; ++i)
            {
                sum += i;
            }
            return sum;
        }
    };
    ​
    int main()
    {
        cout << Solution().SumSolution(100) << endl;  // 5050
        return 0;
    }
  2. 当作参数进行传递

    #include <iostream>
    using namespace std;
    ​
    class Date
    {
        friend void Display(const Date& d);
    public:
        Date(int year = 1949, int month = 10, int day = 1)
            : _year(year), _month(month), _day(day) {}
    private:
        int _year;
        int _month;
        int _day;
    };
    ​
    void Display(const Date& d)
    {
        cout << d._year << "-" << d._month << "-" << d._day << endl;
    }
    ​
    int main()
    {
        Display(Date(2023, 5, 1));  // 2023-5-1
        return 0;
    }

 


六、编译器所做的一些优化

#include <iostream>
using namespace std;

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

	A(const A& a) : _i(a._i)
	{
		cout << "A(const A& a)" << endl;
	}

	A& operator=(const A& a)
	{
		cout << "A& operator=(const A& a)" << endl;
		if (this != &a)
		{
			_i = a._i;
		}
		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _i;
};

void func1(A a)
{

}

A func2()
{
	A a;
	return a;
}

int main()
{
	func1(10);  
	// 构造(隐式类型转换)+ 拷贝构造 --> 优化成一个构造
	cout << "------------------" << endl;

	func1(A(10));
	// 构造 + 拷贝构造 --> 优化成一个构造
	cout << "------------------" << endl;

	A ret1 = func2(); 
	// 在一个表达式中,连续的拷贝构造 --> 优化成一个拷贝构造
	cout << "------------------" << endl;

	// A ret2;
	// 1. 构造
	// ret2 = func2();
	// 2. 赋值运算符重载
	// 编译器无法优化
	return 0;
}

 使用 explicit 关键字修饰构造函数,可以禁止类型转换

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

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

相关文章

3.View的绘制流程

View是在什么时候显示在屏幕上面的?(如:MainActivity的布局文件activity_main.xml) setContentView最终的结果是将解析的xml文件中的View添加到DecorView中. 那么这个DecorView是什么时候添加到Window(PhoneWindow)的呢? DecorView是在ActivityThread.java的handleResumeA…

2-Zookeeper单机版安装

2-Zookeeper单机版安装 本文介绍的是 Linux 系统下 Zookeeper 安装方式 ① 下载 进入官网 https://zookeeper.apache.org/ 点击下载按钮 进入下载页 https://zookeeper.apache.org/releases.html 后选择 最新的稳定版本&#xff0c;如下&#xff1a; 3.7.1 为最新的稳定版本…

号称分割一切的图片分割模型开源了——Segment Anything Meta SAM

头条号:人工智能研究所 微信号:启示AI科技 微信小程序:AI人工智能工具 以前,要解决任何类型的分割问题,有两类方法。第一种是交互式分割,允许分割任何类别的对象,但需要人通过迭代细化掩码来指导。第二种,自动分割,允许分割提前定义的特定对象类别(例如,猫或椅子),…

【计算机系统】指令

leaq指令 一元指令 二元指令 例子 指令addq 指令subq 指令incq 指令subq 移位指令 移位指令用途 特殊运算指令

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分 前言&#xff1a;Web&#xff1a;我Flag呢&#xff1f;导弹迷踪&#xff1a;Follow me and hack me&#xff1a;PHP是世界上最好的语言&#xff01;&#xff01;作业管理系统&#xff1a;Vim yyds&#xff1a;Ping&#xff1a…

Java基础-面向对象总结(2)

这篇文章主要讲解 Java中的 变量方法代码块访问修饰限定符Java 是值传递&#xff0c;还是引用传递&#xff1f;类和对象的生命周期..... 希望给您带来帮助 目录 变量 成员变量与局部变量的区别 静态变量和实例变量的区别&#xff1f;静态方法、实例方法呢&#xff1f; 可以…

数据分析06——Pandas中的数据抽取

1、前言&#xff1a; 在Pandas中进行数据抽取主要有两种方法&#xff0c;一种是loc方法&#xff0c;一种是iloc方法&#xff1b;在获取数据时可以获取的数据有三种形式&#xff0c;一种是Series类型&#xff0c;一种是DataFrame类型&#xff0c;还有一种是直接获取数据值&…

Nginx make报错处理

文章目录 make报错&#xff1a;fatal error:sys/sysctl.h:No such file or directory问题处理 make 报错&#xff1a;error: this statement may fall through [-Werrorimplicit-fallthrough]问题处理 make报错&#xff1a;error: struct crypt_data has no member named curre…

DCGAN--Keras实现

文章目录 一、Keras与tf.keras&#xff1f;二、keras中Model的使用三、使用Keras来实现DCGan1、导入必要的包2.指定模型输入维度&#xff1a;图像尺寸和噪声向量 的长度3、构建生成器4、构造鉴别器5、构建并编译DCGan6、对模型进行训练7、显示生成图像8、运行模型 总结 一、Ker…

力扣sql中等篇练习(二十)

力扣sql中等篇练习(二十) 1 寻找面试候选人 1.1 题目内容 1.1.1 基本题目信息1 1.1.2 基本题目信息2 1.1.3 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # 分为以下两者情况,分别考虑,然后union进行处理(有可能同时满足,需要去进行去重) # ①该用户在 三场及更多…

软件测试八股文,软件测试常见面试合集【附答案】

PS&#xff1a;加上参考答案有几十万字&#xff0c;答案就没有全部放上来了&#xff0c;高清打印版本超过400多页&#xff0c;评论区留言直接获取 1、你的测试职业发展是什么? 2、你认为测试人员需要具备哪些素质 3、你为什么能够做测试这一行 4、测试的目的是什么? 5、测…

一图看懂 attrs 模块:一个在类定义时可替换 `__init__`, `__eq__`, `__repr__`等方法的样板,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 attrs 模块&#xff1a;一个在类定义时可替换 __init__, __eq__, __repr__等方法的样板&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1…

吴恩达|chatgpt 提示词工程师学习笔记。

目录 一、提示指南 写提示词的2大原则&#xff1a; 模型的限制 二、迭代 三、总结 四、推断 五、转换 六、扩展 七、对话机器人 吴恩达和openai团队共同开发了一款免费的课程&#xff0c;课程是教大家如何更有效地使用prompt来调用chatgpt&#xff0c;整个课程时长1个…

ctfshow周末大挑战2023/5/12

本周周末大挑战用到的函数讲解 parse_url() 作用&#xff1a;解析URL&#xff0c;返回其组成部分 语法&#xff1a; parse_url ( string $url [, int $component -1 ] ) 参数&#xff1a; url&#xff1a;要解析的 URL。无效字符将使用 _ 来替换。 component&#xff1a; …

Sentinel———隔离和降级

FeignClient整合Sentinel SpringCloud中&#xff0c;微服务调用都是通过Feign来实现的&#xff0c;因此做客户端保护必须整合Feign和Sentinel。 第一步 修改OrderService的application.yml文件&#xff0c;开启Feign的Sentinel功能&#xff08;消费者服务&#xff09; feig…

算法基础第二章

算法基础第二章 第二章&#xff1a;数据结构1、链表1.1、单链表&#xff08;写邻接表&#xff1a;存储图和树&#xff09;1.2、双链表(优化某些问题) 2、栈与队列2.1、栈2.1.1、数组模拟栈2.1.2、单调栈 2.2、队列2.2.1、数组模拟队列2.2.2、滑动窗口&#xff08;单调队列的使用…

操作系统实验二 进程(线程)同步

前言 实验二相比实验一难度有所提升&#xff0c;首先得先掌握好相应的理论知识&#xff08;读者-写者问题和消费者-生产者问题&#xff09;&#xff0c;才能在实验中得心应手。任务二的代码编写可以借鉴源码&#xff0c;所以我们要先读懂源码。 1.实验目的 掌握Linux环境下&a…

linux系统状态检测命令

1、ifconfig命令 用于获取网卡配置于状态状态的等信息&#xff1a; ens33:网卡名称 inet:ip地址 ether:网卡物理地址&#xff08;mac地址&#xff09; RX、TX:接收数据包与发送数据包的个数及累计流量 我们也可以直接通过网卡名称查对应信息&#xff1a; 2、查看系统版本的…

设计模式 - 工厂 Factory Method Pattern

文章参考来源 一、概念 创建简单的对象直接 new 一个就完事&#xff0c;但对于创建时需要各种配置的复杂对象例如手机&#xff0c;没有工厂的情况下&#xff0c;用户需要自己处理屏幕、摄像头、处理器等配置&#xff0c;这样用户和手机就耦合在一起了。 可以使代码结构清晰&a…