什么是单例模式?

news2024/10/6 8:36:40

文章目录

  • 01 | 什么是单例模式?
  • 02 | 实现
    • 懒汉式
    • 饿汉式
  • 03 | 总结

在这里插入图片描述

每一次实验课都要把上一节课的实验报告打印出来,交作业,这个时候的打印店总是很多人,这时候打印机就那么几台,粥少僧多的情况下,打印机怎么进行处理呢?

01 | 什么是单例模式?

  • 概念

    单例模式是属于创建型模式中的应用十分广泛的一种,它的设计目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享,简单来说就是在整个软件运行的生命周期里,单例模式保证一个类只能产生一个实例,确保该类的唯一实例性

  • 疑问:为什么一定要保证类的唯一实例性呢?

    主要为了解决一个全局使用的类被频繁的创建和销毁,浪费系统资源,防止多线程环境中资源使用异常导致系统信息错乱、崩溃的情况

    比如,开篇中说到的打印机工作情况,如果打印机对接收到的文件不做任何处理,来啥打印啥,这样子的话就会导致A和B都在使用打印机打印实验报告,这时候打印机就疯狂的吐纸,吐出来的实验报告全是错乱的,上一页是A的封面,下一页是B的实验截图,场面十分的尴尬。

    所以打印机实际上就是单例模式的一个应用场景,保证能最终控制打印机的类只有一个实例,谁先要用它,都必须在它空闲的时候才行,否则就要进行排队,保证正常运行的秩序。

  • 分类

    单例模式可以分为懒汉式、饿汉式,两者之间主要区别就是类实例初始化的时间不同

    • 懒汉式:指系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。不叫它都懒得干活,所以称为懒汉式

      • 优点:只有当使用的时候才进行初始化,节省内存资源

      • 缺点:在多线程中式不安全的,需要考虑线程安全问题;存在内存泄漏问题

    • 饿汉式:指系统一运行,就初始化创建实例,当需要时,直接调用即可。跟没吃过东西一样,一到小吃街就疯狂吃吃吃,压根不管贵不贵、好不好吃,所以称为饿汉式

      • 优点:因为在系统一运行的时候就初始化了,所以没有线程安全问题

      • 缺点:消耗的内存资源可能比懒汉式多(但是不会用的东西谁会卸写在系统里?既然写了那应该就式说未来会用到的,所以这点不知道算不算缺点),而且由于对象初始化在不同的编译环境中顺序式未定义的,这就有可能导致初始化还未完成的时候,其他调用的地方就开始使用这个未完全定义的实例

  • 什么是线程安全?

    前面讲单例模式分类的时候有一个常出现的词线程安全,那什么是线程安全呢?百度百科是这样解释的

    线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

  • 怎么解决线程安全问题?

    操作系统中关于进程/线程管理中有讲到互斥锁的概念,保证线程安全也是用到来确保资源的同一时刻的唯一操作权,简单来说就是给共享的资源加把锁,保证每个资源变量每时每刻至多被一个线程占用

  • 特点

    • 为了禁止外部构造和析构,禁止外部拷贝和赋值,确保实例的唯一性。所以将构造函数和析构函数以及拷贝构造和赋值构造函数设置为private类型

    • 提供一个可以全局访问的用于获取唯一实例的静态函数

02 | 实现

因为对单例模式还是不太熟悉,所以下面是直接从网上扒的单例模式各类源码,再自己对着代码进行剖析,以此为基础进行单例模式的学习

懒汉式

在这里插入图片描述

前面提到过懒汉式单例模式有线程安全的问题,所以下面会分基础的懒汉式单例和解决线程安全的稍微没那么懒的懒汉式单例进行剖析

  • 基础懒汉式单例

    class Singleton
    {
    private:
    	static Singleton* instance;
    private:
    	Singleton() {};
    	~Singleton() {};
    	Singleton(const Singleton&);
    	Singleton& operator=(const Singleton&);
    public:
    	static Singleton* getInstance() 
    	{
    		if(instance == NULL)
        	{
    			instance = new Singleton();
        	}
    		return instance;
    	}
    };
    
    Singleton* Singleton::instance = NULL;
    

    从上面的基础源码中能看出来单例模式的特点:构造函数和析构函数以及拷贝构造和赋值构造函数设置为private类型;一个可以全局访问的用于获取唯一实例的静态函数

    从基础源码上看,在单线程环境中,实例对象仅能通过getInstance()进行创建调用,if (instance == NULL)保证了仅能创建一个实例对象。前面提到这个基础懒汉式在多线程环境中是存在线程安全问题的,也就是说它在多线程环境中,可能会创建出多个实例对象,尔后在销毁实例对象是会导致环境破坏以及一定的内存泄漏问题,理论上能大概想得到,但是还是实际上手检验一下才知道。下面参照源码写了个简单的测试Demo,试验以下多线程环境中基础懒汉式的运行是否存在线程安全问题

    #include <iostream>
    #include <pthread.h>
    using namespace std;
    
    #define THREADS_SIZE    5
    
    class Singleton_BasicLazy
    {
    private:
        static Singleton_BasicLazy* m_Instance;
    
    private:
        Singleton_BasicLazy(){cout << "基础懒汉出街" << endl;};
        ~Singleton_BasicLazy(){};
        Singleton_BasicLazy(const Singleton_BasicLazy& i_singleton);
        const Singleton_BasicLazy &operator=(const Singleton_BasicLazy& i_singleton);
    
    public:
        static Singleton_BasicLazy* getInstance()
        {
            if (nullptr == m_Instance)
            {
                m_Instance = new Singleton_BasicLazy;
            }
            return m_Instance;
        }
    };
    Singleton_BasicLazy* Singleton_BasicLazy::m_Instance = nullptr;
    
    void* callback_Hello(void* i_threadid)
    {
    	// 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收
    	pthread_detach(pthread_self());
    
    	// 将传入的参数由无类型指针强制转换成整形数指针
    	cout << "线程 ID:[" << *((int *)i_threadid) << "]" << endl;
    	Singleton_BasicLazy::getInstance();
    
    	pthread_exit(NULL);
    }
    
    int main()
    {
    	pthread_t threads[THREADS_SIZE] = {0};
    	int ret = 0;
    	int index[THREADS_SIZE] = {0};
    
    	cout << "==========> Ready Go <==========" << endl;
    
    	for (int i = 0; i < THREADS_SIZE; i++)
    	{
        	index[i] = i;
        	ret = pthread_create(&threads[i], NULL, callback_Hello, (void*)&(index[i]));
        	if (ret)
        	{
            	cout << "创建线程失败" << endl;
            	exit(-1);
        	}
    	}
    	return 0;
    }
    

    在这里插入图片描述

    从测试结果上可以看到打印了两次基础懒汉出街,也就说明在多线程环境中,基础懒汉式单例模式确实存在线程安全问题,打破了唯一实例的规则,所以基础懒汉式单例仅适用于单线程环境。

  • 加锁懒汉式单例

    为了让懒汉稍微靠谱一点,让他有点激情,人们选择了给他上把锁……

    class SingleInstance
    {
    public:
    	static SingleInstance *GetInstance()
    	{
        	if (m_SingleInstance == nullptr) 
        	{
            	unique_lock<mutex> lock(m_Mutex);
            	if (m_SingleInstance == nullptr)
            	{
                	auto temp = new SingleInstance;
                	m_SingleInstance = temp;
            	}
        	}
        	return m_SingleInstance;
    	}
    
    private:
    	SingleInstance();
    	~SingleInstance();
    	SingleInstance(const SingleInstance &signal);
    	const SingleInstance &operator=(const SingleInstance &signal);
    
    private:
    	static SingleInstance *m_SingleInstance;
    	static std::mutex m_Mutex;
    };
    SingleInstance *SingleInstance::m_SingleInstance = nullptr;
    std::mutex SingleInstance::m_Mutex;
    

    与基础懒汉式相比,加锁的懒汉改变之处就是在GetInstance()中使用了双检锁技术,保证每一次仅有一个线程能进入第二个if (m_SingleInstance == nullptr),为什么不在外面加一把锁,而是在第一个if之后加呢?通常线程安全问题都是发生在创建实例对象这一步骤中,那么只需要保证m_SingleInstance == nullptr之前是安全的即可,同时加锁堆系统资源来说也是一笔不小的开销,如果每一次调用GetInstance()都加一次锁的话,会浪费很多的系统资源

    同样的,参考以上源码,自己编写了一个测试Demo进行检验,Demo代码如下::

    #include <iostream>
    #include <mutex>
    #include <pthread.h>
    using namespace std;
    
    #define THREADS_SIZE    5
    
    class Singleton_MutexLazy
    {
    private:
        static Singleton_MutexLazy* m_Instance;
        static mutex m_Mutex;
    
    private:
        Singleton_MutexLazy(){};
        ~Singleton_MutexLazy(){};
    
        Singleton_MutexLazy(const Singleton_MutexLazy& i_singleton);
        const Singleton_MutexLazy &operator=(const Singleton_MutexLazy& i_singleton);
    
    public:
        static Singleton_MutexLazy* getInstance()
        {
            if (nullptr == m_Instance)
            {
                unique_lock<mutex> lock(m_Mutex);
                if (nullptr == m_Instance)
                {
                    m_Instance = new Singleton_MutexLazy;
                }
            }
            return m_Instance;
        }
    };
    Singleton_MutexLazy* Singleton_MutexLazy::m_Instance = NULL;
    mutex Singleton_MutexLazy::m_Mutex;
    
    void* callback_Hello(void* i_threadid)
    {
    	// 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收
    	pthread_detach(pthread_self());
    
    	// 将传入的参数由无类型指针强制转换成整形数指针
    	cout << "线程 ID:[" << *((int *)i_threadid) << "]" << endl;
    	Singleton_MutexLazy::getInstance();
    
    	pthread_exit(NULL);
    }
    
    int main()
    {
    	pthread_t threads[THREADS_SIZE] = {0};
    	int ret = 0;
    	int index[THREADS_SIZE] = {0};
    
    	cout << "==========> Ready Go <==========" << endl;
    
    	for (int i = 0; i < THREADS_SIZE; i++)
    	{
        	index[i] = i;
        	ret = pthread_create(&threads[i], NULL, callback_Hello, (void*)&(index[i]));
        	if (ret)
        	{
            	cout << "创建线程失败" << endl;
            	exit(-1);
        	}
    	}
    	return 0;
    }
    

加锁懒汉单例测试结果
从结果上可以看到,这一次只存在一次加锁懒汉出街,说明加锁版懒汉单例在多线程环境中是安全的,但是加锁意味着系统 的开销增加。

  • 内部静态变量式懒汉

    C++11中规定了内部静态变量在多线程环境中初始化的行为,要求编译器保证内部静态变量的线程安全性,所以在C++11标准下,出现了一种“优雅且安全“的懒汉实现方法 —— 使用函数内的静态变量对象,这样只有当第一次访问GetInstance()时才创建实例

    class Single
    {
    public:
    	static Single GetInstance()
    	{
        	static Single signal;
        	return signal;
    	}
    
    private:
    	Single();
    	~Single();
    	Single(const Single &signal);
    	const Single &operator=(const Single &signal);
    };
    

    从源码上就能看出来,相比之前的源码干净整洁的多,减少了private中的instance, 也不需要加锁,为什么这样子就能做到线程安全呢?主要是GetInstance()中的Static Single signal这一行,因为静态局部变量只在当前函数内有效,其他函数是无法访问的,而且只在第一次被调用的时候初始化,也存储在静态存储区,生命周期从第一次被初始化起直到程序结束
    老样子,Demo双手奉上

    #include <iostream>
    #include <pthread.h>
    using namespace std;
    
    #define THREADS_SIZE    5
    
    class Singleton_StaicInLazy
    {
    private:
        Singleton_StaicInLazy(){cout << "优雅懒汉出街" << endl;};
        ~Singleton_StaicInLazy(){};
        Singleton_StaicInLazy(const Singleton_StaicInLazy& i_singleton);
        const Singleton_StaicInLazy &operator=(const Singleton_StaicInLazy& i_singleton);
    
    public:
        static Singleton_StaicInLazy& getInstance()
        {
            static Singleton_StaicInLazy m_Instance;
            return m_Instance;
        }
    };
    
    void* callback_Hello(void* i_threadid)
    {
    	// 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收
    	pthread_detach(pthread_self());
    
    	// 将传入的参数由无类型指针强制转换成整形数指针
    	cout << "线程 ID:[" << *((int *)i_threadid) << "]" << endl;
    	Singleton_StaicInLazy::getInstance();
    
    	pthread_exit(NULL);
    }
    
    int main()
    {
    	pthread_t threads[THREADS_SIZE] = {0};
    	int ret = 0;
    	int index[THREADS_SIZE] = {0};
    
    	cout << "==========> Ready Go <==========" << endl;
    
    	for (int i = 0; i < THREADS_SIZE; i++)
    	{
        	index[i] = i;
        	ret = pthread_create(&threads[i], NULL, callback_Hello, (void*)&(index[i]));
        	if (ret)
        	{
            	cout << "创建线程失败" << endl;
            	exit(-1);
        	}
    	}
    	return 0;
    }
    

优雅懒汉单例测试结果

结果和加锁懒汉式单例一样,只进行了一次构造函数的调用,说明其在多线程环境中也是安全的

饿汉式

在这里插入图片描述
饿汉式单例模式:指单例实例在程序运行时就被立即执行初始化的单例模式。

class Singleton
{
	public:
		static Singleton* GetInstance();

	private:
    	Singleton();
    	~Singleton();
    	Singleton(const Singleton &signal);
    	const Singleton &operator=(const Singleton &signal);

	private:
    	static Singleton *g_pSingleton;
};

Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton();

Singleton* Singleton::GetInstance()
{
    return g_pSingleton;
}

相对懒汉式来说,由于在main()函数之间就完成了初始化的动作,所以在多线程环境中式没有线程安全问题的。还是原来的配方,Demo测试走起

#include <iostream>
#include <pthread.h>
using namespace std;

#define THREADS_SIZE    5

class Singleton_Hungry
{
    private:
        static Singleton_Hungry* m_Instance;

    private:
        Singleton_Hungry(){cout << "饿汉出街" << endl;};
        ~Singleton_Hungry(){};
        Singleton_Hungry(const Singleton_Hungry& i_singleton);
        const Singleton_Hungry &operator=(const Singleton_Hungry& i_singleton);

    public:
        static Singleton_Hungry* getInstance()
        {
            if (nullptr == m_Instance)
            {
                m_Instance = new Singleton_Hungry;
            }
            return m_Instance;
        }
};

Singleton_Hungry* Singleton_Hungry::m_Instance  = new Singleton_Hungry;

void* callback_Hello(void* i_threadid)
{
    // 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收
    pthread_detach(pthread_self());

    // 将传入的参数由无类型指针强制转换成整形数指针
    cout << "线程 ID:[" << *((int *)i_threadid) << "]" << endl;
    Singleton_Hungry::getInstance();

    pthread_exit(NULL);
}

int main()
{
    pthread_t threads[THREADS_SIZE] = {0};
    int ret = 0;
    int index[THREADS_SIZE] = {0};
    
    cout << "==========> Ready Go <==========" << endl;

    for (int i = 0; i < THREADS_SIZE; i++)
    {
        index[i] = i;
        ret = pthread_create(&threads[i], NULL, callback_Hello, (void*)&(index[i]));
        if (ret)
        {
            cout << "创建线程失败" << endl;
            exit(-1);
        }
    }
    return 0;
}

饿汉式单例测试结果

03 | 总结

在这里插入图片描述

单例模式的精髓就在于保证一个类仅有一个实例,并提供一个访问它的全局访问点,无论是懒汉式还是饿汉式,都需要遵循这一点,两者之间的主要区别就是

  1. 懒汉式主要通过时间资源替换空间资源,比较适合在访问量比较小的软件系统中使用(从上面的分析可以看到,使用内部静态变量单例模式比较简洁明了且具有一定的线程安全性)

  2. 饿汉式主要通过空间资源替换时间资源,比较适合在访问量比较大,或者说线程比较多的软件系统中使用

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

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

相关文章

动态路由的原理与配置

一.动态路由介绍 1.动态路由选择 指路由器使用路由选择协议来获悉网络并更新路由选择表。 2.路由协议分类 3.管理距离介绍 管理距离&#xff08;AD&#xff09;用于判断从邻接路由器收到的路由选择信息的可信度&#xff0c;它是 0-255的整数&#xff0c;0 表示可信度最大&#…

Android9 Settings源码导入Android Studio查看并调试

Android9 Settings源码导入Android Studio查看并调试 本次调试在已编译的工程下 1. 导入packages/app/settings 不要直接导入这个模块&#xff0c;导入上一级app目录 在选择时选择Settings相关所有目录&#xff0c;如下 如下这里的sdk配置是我已经添加的&#xff0c;后面可…

用了这几个方法,Facebook广告转化真上去了!

随着全球电商市值的猛涨&#xff0c;Facebook广告的价值再次被放大。然而&#xff0c;在Facebook上的广告资源竞争日益激烈的情况下&#xff0c;即使你有最好的广告策略和投放预算&#xff0c;如果你的广告创意不能吸引潜在客户的注意&#xff0c;那么你的广告投放也可能失败。…

at单一次任务,crontab周期性任务 rhce(21)

目录 1.atd和crond两个任务管理程序的区别 2.指定在2023/08/26 09:00将时间写入testmail.txt文件中 3.指定在每天凌晨4:00将该时间点之前的系统日志信息备份到个目录下&#xff08;/var/log/messages&#xff09;&#xff0c;备份后日志文件名显示格式logfileYY-MM-DD HH-MM…

08-java之io流基础

io流学习流程 说白了&#xff0c;本节的目的就是对 文件增删改查&#xff0c;先说一下学习顺序吧 定位文件 File类可以定位文件&#xff1a;可以进行删除文件内容&#xff0c;读取文件本身信息等操作&#xff0c;但是不能读写文件内容 字符集 想要读取文件中的数据&#xff0…

Nginx+Tomcat负载均衡、动静分离

1.Tomcat多实例部署 Tomcat的多实例部署简单的讲就是基于端口的虚拟主机设置 步骤一&#xff1a;安装jdk (1&#xff09;关闭防火墙和selinux&#xff0c;防止其对安装过程的干扰 &#xff08;2&#xff09;将准备好的软件包拖入/opt目录下&#xff0c;进行安装 #某rpm包尚未…

【HCIP】BGP实验(联邦,路由反射器,手工汇总)

目录 需求&#xff1a; Ⅰ、IP规划 Ⅱ、配置IP与做通IGP(用的ospf) Ⅲ、开启BGP协议 Ⅳ、AS1、AS3的10...环回互相通讯 Ⅴ、对路由表进行汇总 Ⅵ、回望需求 需求&#xff1a; 1.AS1存在两个环回&#xff0c;一个地址为192.168.1.0/24该地址不能在任何协议中宣告 AS3存在两…

马斯克的Starship,除了“大”还有什么呢?

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 种种迹象表明&#xff0c;由ChatGPT引发的算力军备竞赛已经悄然而至了。 终于&#xff0c;到了检验人类最大火箭发射的时候了。 2023年4月17日&#xff0c;SpaceX将其大型深空火箭Starship发射升空&#xff0c;发射地点为Sp…

CANoe使用记录(三):CANoe发送报文设置

目录 1、概述 2、报文发送 2.1、interactive generators&#xff08;交互生成器&#xff09; 2.2、重命名发送报文 2.3、增加报文 2.4、触发方式 2.5、从DBC导入报文 2.6、设置信号值 1、概述 很大一部分时候&#xff0c;在控制零部件时候&#xff0c;需要发送报文到下…

辉煌优配|军工板块逆市上涨,16只概念股已披露一季度业绩预喜

今日&#xff0c;军工股逆市上涨。 4月21日&#xff0c;A股三大股指低开低走&#xff0c;半导体、AI使用、信创工业、软件等科技属性概念领跌&#xff0c;国防军工、食品饮料和电力设备等板块上涨。 工业互联网中心工业规模超1.2万亿元 据央视新闻报道&#xff0c;本年是《工业…

玩转ChatGPT:论文翻译润色

一、写在前面 首先还是让小Chat推销下自己&#xff1a; 嘿&#xff01;你是否在写论文的过程中感到头疼&#xff0c;无从下手&#xff1f;你是否在担心自己的语言表达不够专业、不够流畅&#xff0c;影响了论文的质量&#xff1f;不要担心&#xff0c;ChatGPT的润色服务可以帮…

JavaScript的基础语法学习

文章目录 一、JavaScript let 和 const二、JavaScript JSON三、javascript:void(0) 含义四、JavaScript 异步编程总结 一、JavaScript let 和 const let 声明的变量只在 let 命令所在的代码块内有效。 const 声明一个只读的常量&#xff0c;一旦声明&#xff0c;常量的值就不…

React Native 组件基础

基于组件的架构模式&#xff0c;或许是现在重展示、重交互应用的最好选择 拆组件要准守一个原则&#xff0c;单一责任原则。 这也是 React 官方倡导的原则&#xff0c;这个原则的意思是每个组件都应该只有一个单一的功能&#xff0c;并且这个组件和其他组件没有相互依赖。当然…

Zookeeper数据模型与Watch机制

数据模型 ZooKeeper 中的数据模型是一种树形结构&#xff0c;非常像电脑中的文件系统&#xff0c;有一个根文件夹&#xff0c;下面还有很多子文件夹。ZooKeeper 的数据模型也具有一个固定的根节点&#xff08;/&#xff09;&#xff0c;我们可以在根节点下创建子节点&#xff0…

华为 WLAN基本配置

华为 无线基本配置 一、前言二、项目拓扑三、项目需求四、配置思路五、配置步骤1. 创建vlan&#xff0c;配置vlan接口ip2. trunk放行对应流量&#xff0c;保证链路可达3.开启DHCP服务&#xff0c;配置DHCP地址池&#xff0c;为AP和终端设备分配IP地址4.WLAN基本配置 六、结语 一…

一文解读类的加载过程(类的生命周期)

目录 概述总览 过程一&#xff1a;Loading&#xff08;加载&#xff09;阶段 加载完成的操作 二进制流的获取方式 类模型与Class实例的位置 数组类的加载 过程二&#xff1a;Linking&#xff08;链接&#xff09;阶段 环节1&#xff1a;链接阶段之Verification&#xff…

第一次的医美体验,决定了客户的回头率

爱美之心&#xff0c;人皆有之。随着人们求美需求的增加&#xff0c;医美消费成为了越来越多人的选择&#xff0c;而这种需求往往不是一次性的。 根据艾媒咨询于2021年发布的一份医美报告显示&#xff0c;30.6%的医美消费受访者只体验了1家医美机构&#xff0c;有78.1%的受访者…

代码随想录算法训练营第六十天 | 84. 柱状图中最大的矩形

84. 柱状图中最大的矩形 本题与接雨水问题的区别是记录每个柱子左边第一个小于该柱子的下标&#xff0c;而不是左边第一个小于该柱子的高度。 因为本题是要找每个柱子左右两边第一个小于该柱子的柱子&#xff0c;所以从栈头&#xff08;元素从栈头弹出&#xff09;到栈底的顺…

恢复误删文件

误删恢复 用losf恢复进程存在的文件 注意此处要后台进程存在 创建一个文件&#xff0c;用tail命令&#xff0c;模拟文件一直被监听 打开另外一个终端&#xff0c;删除这个文件 用lsof命令查看被删除的文件&#xff0c;可以发现文件虽然被删除&#xff0c;但是进程依然在 然后…

Make-A-Video(造啊视频)——无需文字-视频数据的文字转视频(文生视频)生成方法

© 2022 Uriel Singer et al (Meta AI) © 2023 Conmajia 本文基于论文 Make-A-Video: Text-to-Video Generation without Text-Video Data&#xff08;2209.14792&#xff09;。 本文已获论文第一作者 Uriel Singer 授权。 本视频由这句话生成&#xff1a;穿着超人装…