C++的多继承和虚继承

news2024/11/25 16:36:21

目录

  • 多继承的定义和用法
    • 定义多继承
    • 多继承中派生类对象的内存布局
    • 访问基类成员
    • 多继承带来的问题
  • 虚继承
    • 虚继承的语法
    • 虚继承对象的内存布局
    • 虚继承中的构造
    • 虚继承的缺点

多继承的定义和用法

C++支持多继承,即一个派生类可以有多个基类。

很多时候,单继承就可以满足开发需求,但在特定的情况下就不行。比如有两个类A和B,现在要有一个类C,它同时具有A和B的属性和行为,这种情况下单继承就不能满足要求。

用鸭嘴兽来举例:

从形态学上来讲,鸭嘴兽应该属于鸟类,原因是鸭嘴兽具有扁平的、像鸭子一样的嘴巴,而且是角质的,不像哺乳动物那种肉质的口唇,关键是鸭嘴兽通过下蛋来繁殖后代,这明显是鸟类的特征。然而,鸭嘴兽也靠乳汁来哺育幼仔,浑身密布着浓褐色的短兽毛,这又是哺乳动物的重要特征。所以鸭嘴兽既是鸟类,又是哺乳动物。

如果要在程序中定义一个鸭嘴兽类,采用单继承肯定是不行的。否则,鸭嘴兽要么是鸟类,要么是哺乳动物,显然不符合实际情况。所以此时应当采用多继承,让鸭嘴兽同时继承鸟类和哺乳动物类的属性和行为。这样,一个鸭嘴兽就既是鸟类,又是哺乳动物,符合实际情况。

定义多继承

多继承的语法同单继承类似,只需要在定义类时在类名后面依次罗列继承方式和基类即可。继承方式同单继承一样,也有public,protected和private。在多继承中,针对不同的基类可以使用不同的继承方法。其语法如下:

      class  派生类名 : 继承方式1  基类名1,
                      继承方式2    基类名2,
                      ⋯⋯
      {
          派生类新增成员
      };

多继承的类图如下:
在这里插入图片描述例如:定义一个鸭嘴兽类,应该继承鸟类和哺乳动物类

#include <iostream>

// 鸟类
class Bird
{
public:

	Bird()
	{
		std::cout << "鸟类的构造函数" << std::endl;
	}

	~Bird()
	{
		std::cout << "鸟类的析构函数" << std::endl;
	}
};

// 哺乳动物类
class Mammal
{
public:

	Mammal()
	{
		std::cout << "哺乳动物类的构造函数" << std::endl;
	}

	~Mammal()
	{
		std::cout << "哺乳动物类的析构函数" << std::endl;
	}
};

// 鸭嘴兽类
class Duckbill
	: public Bird
	, public Mammal
{
public:

	Duckbill()
		: Mammal()
		, Bird()
	{
		std::cout << "鸭嘴兽类的构造函数" << std::endl;
	}

	~Duckbill()
	{
		std::cout << "鸭嘴兽类的析构函数" << std::endl;
	}
};

void Test()
{
	Duckbill duckbill;
}

int main()
{
	Test();

	system("pause");
	return 0;
}

vs2022下的运行结果:

鸟类的构造函数
哺乳动物类的构造函数
鸭嘴兽类的构造函数
鸭嘴兽类的析构函数
哺乳动物类的析构函数
鸟类的析构函数

同定义单继承派生类的构造函数一样,定义多继承派生类时也要注意基类的初始化。如果基类没有默认的构造函数,那么在派生类构造函数的初始化列表里就要依次调用各个基类的构造函数。无论开发者如何安排,基类构造函数的调用次序总是按照其定义时的次序。

我们在初始化列表中先调用哺乳动物的构造函数,再调用鸟类的构造函数,运行结果按照定义时的顺序进行,析构函数相反。

多继承派生类对象在析构时按照与构造相反的顺序进行,即先调用派生类自己的析构函数,再析构各个数据成员,然后按照相反的顺序,依次调用各个基类的析构函数

多继承中派生类对象的内存布局

同单继承一样,通过多继承派生类将拥有基类所有的属性和行为。在多继承派生类的对象中,将依次排列各个基类的非静态数据成员以及派生类新增的数据成员。派生类对象内存中的数据是按照定义时的顺序排列的。也就是说,在定义派生类时,排在前面的基类,其数据在派生类对象中也排在前面。

一个多继承类图如下:
在这里插入图片描述
它的派生类的内存布局如下:

在这里插入图片描述举例说明:

#include <iostream>

// 鸟类
class Bird
{
public:

	Bird()
	{
		std::cout << "鸟类的构造函数" << std::endl;
	}

	~Bird()
	{
		std::cout << "鸟类的析构函数" << std::endl;
	}

	char a;
	int b;
	char c;
};

// 哺乳动物类
class Mammal
{
public:

	Mammal()
	{
		std::cout << "哺乳动物类的构造函数" << std::endl;
	}

	~Mammal()
	{
		std::cout << "哺乳动物类的析构函数" << std::endl;
	}

private:

	char a;
	char c;
	char b;
};

// 鸭嘴兽类
class Duckbill
	: public Bird
	, public Mammal
{
public:

	Duckbill()
		: Mammal()
		, Bird()
	{
		std::cout << "鸭嘴兽类的构造函数" << std::endl;
	}

	~Duckbill()
	{
		std::cout << "鸭嘴兽类的析构函数" << std::endl;
	}
};

void Test()
{
	Duckbill duckbill;

	std::cout << "Bird: " << sizeof(Bird) << std::endl;
	std::cout << "Mammal: " << sizeof(Mammal) << std::endl;
	std::cout << "Duckbill: " << sizeof(Duckbill) << std::endl;
}

int main()
{
	Test();

	system("pause");
	return 0;
}

vs2022运行结果:

鸟类的构造函数
哺乳动物类的构造函数
鸭嘴兽类的构造函数
Bird: 12
Mammal: 3
Duckbill: 16
鸭嘴兽类的析构函数
哺乳动物类的析构函数
鸟类的析构函数

Duckbill的内存布局是

	char a;
	int b;
	char c;
	char a;
	char c;
	char b;

按照结构体的内存对齐方式计算得出结果为16,而不是12+3。

派生类对象也可以转换为其基类类型的对象。对于多继承的情况,在转换时编译器可以根据要转换的类型进行适当的转换。例如,对于上面的多继承类,如果要将Derived类对象转换成Base2类的对象,编译器会从Derived对象中按照内存排列的顺序,从中截取出从Base2类继承来的部分构成新对象。

 Derived  d;
 Base2  b2 = static_cast<Base2>( d );

在这里插入图片描述举例说明:
将上述例子中的duckbill转换成基类Mammal对象

Mammal mammal = static_cast<Mammal>(duckbill);
std::cout << "Mammal: " << sizeof(mammal) << std::endl;	// Mammal: 3

访问基类成员

在多继承中,如果多个基类拥有同名成员,那么在访问基类成员时,仅通过成员名并不能区分是哪个基类的成员。解决的方法是在成员名前用域运算符::指明成员所属的基类。通过这种方法访问数据成员和函数成员的语法如下:

      基类名 :: 数据成员名;                          // 在派生类成员函数中访问基类成员数据
      基类名 :: 函数成员名( 参数列表 );              // 在派生类成员函数中访问基类成员函数
      派生类对象 . 基类名 :: 数据成员名;
      派生类对象 . 基类名 :: 函数成员名( 参数列表 );
      派生类指针 -> 基类名 :: 数据成员名;
      派生类指针 -> 基类名 :: 函数成员名( 参数列表 );

前提是基类的成员变量是公有变量

举例:

duckbill.Mammal::a = 'a';
duckbill.Bird::a = 'A';

void Duckbill::Foo()
{
	Mammal::a = 'a';
	Bird::a = 'A';
}

多继承带来的问题

多继承虽然功能强大,可以让派生类同时具有多个基类的属性和行为,但是多继承同时也会带来一些严重的问题。其中比较常见的问题就是多继承会导致数据重复,并由此带来数据不一致的问题。

在这里插入图片描述举例:

#include <iostream>

// 动物类
class Animal
{
public:

	int weight;
};

// 鸟类
class Bird
	: public Animal
{
public:
	char a;
	int b;
	char c;
};

// 哺乳动物类
class Mammal
	: public Animal
{
public:

	char a;
	char c;
	char b;
};

// 鸭嘴兽类
class Duckbill
	: public Bird
	, public Mammal
{
public:

};

void Test()
{
	Duckbill duckbill;

	std::cout << "Bird: " << sizeof(Bird) << std::endl;
	std::cout << "Mammal: " << sizeof(Mammal) << std::endl;
	std::cout << "Duckbill: " << sizeof(Duckbill) << std::endl;

}

int main()
{
	Test();

	system("pause");
	return 0;
}

运行结果:

Bird: 16
Mammal: 8
Duckbill: 24

很明显,Duckbill中的weight有两份。

比较典型的情况是一个派生类D从两个基类B和C中派生,而这两个基类又有一个共同的基类A,这就会导致A的数据在D中被重复两次,如图16-7所示。D多继承B和C,将B和C的数据复制到D中。由于A的数据已经分别被B和C继承,所以A的数据在D中将重复两次。而且在定义D类的成员函数时,或者通过D类对象和指针访问成员数据a时,必须用域运算符::指明a所在的类,即:

    B::a = 1;               // 在D的成员函数中访问A类的数据成员
    C::a = 2;
    D  dObj;                // D类对象
    dObj.B::a = 3;          // 通过D类对象访问A类的数据成员
    dObj.C::a = 4;
    D  *pObj = new D();     // D类指针
    pObj->B::a = 5;         // 通过D类指针访问A类的数据成员
    pObj->C::a = 6;

从编译器的设计角度来讲,当D从B和C继承时并不知道基类A的存在。D只能全盘接受来自B和C的数据,而无法区分其中的数据a到底是从B继承而来的,还是从C继承而来的。所以要访问数据a,只能由用户来指明。

从逻辑的角度来讲,在D类的对象中A的数据应当只有一份。比如有一个动物基类Animal,它具有重量属性。鸟类(Bird)和哺乳动物类(Mammal)都从Animal派生,然后鸭嘴兽类(DuckBill)又从鸟类和哺乳动物类派生。从继承的语义来讲,一个DuckBill对象也是一个Animal,所以鸭嘴兽应当具有重量属性。但是,由于多继承导致数据冗余,所以基类的一份数据,在其间接派生类中产生了多份副本。所以在上述的鸭嘴兽对象中将具有“两”个重量属性。这显然是不符合逻辑的。而且由于数据冗余,也容易导致数据的不一致。例如上例的D类,其中继承自B的数据a和继承自C的数据a可以分别访问,如果开发者不能始终保证每次修改两个数据使其完全一样,那么就很容易导致数据不一致。

    D  dObj;                // 定义D类对象
    ⋯⋯
    dObj.B::a = 1;          // 修改继承自B的数据a
    ⋯⋯
    dObj.C::a = 2;          // 修改继承自C的数据a

显然,在上述代码中很容易导致一个数据a有两个不同值,而这种情况是多继承无法克服的一个缺点。另外,如果A类的构造函数带有参数(而且没有默认构造函数),那么在B类和C类构造时就必须调用这个构造函数。假设由于开发者的疏忽,导致B类和C类在调用A类的构造函数时不一致,那么D类中的两个数据a也就会不一致。

为了解决多继承导致的数据冗余和数据不一致的问题,可以采用虚拟继承机制,也可以禁止最初的基类带有数据。一个不带有任何数据(仅有函数成员)的基类也称做接口。

虚继承

虚拟继承是解决多继承带来的问题的一个重要机制。通过虚拟继承,基类的数据在派生类中将只有一份副本,从而避免了多继承导致的数据冗余和数据不一致问题。

虚继承的语法

虚拟继承是在定义派生类时将基类指明为虚基类,或者说派生类以虚拟的方式从基类派生。虚拟继承的方法是在普通继承的基类名前加上virtual关键字,如下所示:

  class派生类名 : 继承方式  virtual  基类名
  {
      派生类的定义
  };

例如B类从A类虚拟继承,则B类定义如下:

    class  B : public  virtual  A       // B类从A类虚拟继承
    {
        ⋯⋯
    private:
        int b;                          // B类新增的成员数据
    };

虚继承对象的内存布局

虚继承除了常规的数据成员内存,还会有虚表指针。

#include <iostream>

// 动物类
class Animal
{
public:

	int weight;
};

// 鸟类
class Bird
	: public virtual Animal
{
public:
	int b;
};

// 哺乳动物类
class Mammal
	: public virtual Animal
{
public:

	char a;
	int c;
	int b;
};

// 鸭嘴兽类
class Duckbill
	: public Bird
	, public Mammal
{
public:

};

void Test()
{
	Duckbill duckbill;
	Bird bird;
	Mammal mammal;

	std::cout << "Bird: " << sizeof(bird) << std::endl;
	std::cout << "Mammal: " << sizeof(mammal) << std::endl;
	std::cout << "Duckbill: " << sizeof(duckbill) << std::endl;

}

int main()
{
	Test();

	system("pause");
	return 0;
}

vs2022、x64位运行结果

Bird: 24
Mammal: 32
Duckbill: 48

Duckbill对象的内存布局如下:

在这里插入图片描述

与普通继承不同,在虚拟继承中,派生类对象并不是在其内存中保留一份虚基类数据的副本,而是通过一种间接的引用方式,即将虚基类子对象的数据单独存放,在派生类对象中设置一个指针指向基类子对象。这样,当一个派生类通过多个继承路径继承同一个虚基类时,并不需要产生多个数据副本,而只要维护这个虚基类指针即可。

虚继承中的构造

由于在虚拟继承中,虚基类的数据只有一份,所以在间接派生类构造时需要特殊处理,即只能初始化虚基类一次。

假设Vehicle类有一个带有参数的构造函数(而且没有默认构造函数)Vehicle ::Vehicle(int number),那么在中间派生类(虚拟继承)Tank和Boat的构造函数中都要显式调用Vehicle(int number)。但是在AmphiTank类多继承自Tank类和Boat类之后,如果仍然通过两个基类来初始化Vehicle,那么Vehicle将被初始化两次,从而可能导致数据不一致。

所以在C++中,对于虚基类的初始化进行了特殊处理。**如果是在一级派生中,比如Tank类虚拟继承Vehicle类,那么其初始化同一般继承一样。如果是在多级派生中,那么虚基类的初始化将由最终一级的派生类负责。**所以,在水陆两栖坦克的类层次结构中,虚基类Vehicle的初始化应当由最终一级派生类AmphiTank负责,即Vehicle的构造函数应当放在AmphiTank的初始化列表中。

举例:

#include <iostream>

// 动物类
class Animal
{
public:

	Animal(int _num)
		: weight(_num)
	{
	}

	int weight;
};

// 鸟类
class Bird
	: public virtual Animal
{
public:

	Bird(int _num)
		: Animal(_num)
		, b(_num)
	{}

	int b;
};

// 哺乳动物类
class Mammal
	: public virtual Animal
{
public:

	Mammal(int _num)
		: Animal(_num)
		, b(_num)
	{}

	char a = ' ';
	int c = 1;
	int b = 2;
};

// 鸭嘴兽类
class Duckbill
	: public Bird
	, public Mammal
{
public:

	Duckbill(int animal, int bird, int mammal)
		: Animal(animal)
		, Bird(bird)
		, Mammal(mammal)
	{}
};

void Test()
{
	Duckbill duckbill(1,2,3);
	
	// Bird的weight
	std::cout << duckbill.Bird::weight << std::endl;
	// Mammal的weight
	std::cout << duckbill.Mammal::weight << std::endl;
	// Duckbill的weight
	std::cout << duckbill.weight << std::endl;

}

int main()
{
	Test();

	system("pause");
	return 0;
}

vs2022运行结果:

1
1
1

虽然我们在构造Bird和Mammal时用不同的值都构造了Animal,但是只会由最后一级Duckbill负责。

如果一个派生类既有虚基类(不一定是直接基类),又有非虚基类,那么无论初始化列表如何排列,虚基类总是先初始化。如果有多个虚基类,那么排在前面的先初始化。

派生类的析构顺序总是与构造顺序相反,所以如果一个派生类有虚基类,则虚基类总是在最后析构。

虚继承的缺点

虚拟继承虽然可以解决多继承带来的数据冗余和数据不一致的缺点,但虚拟继承本身也存在一些问题,具体问题如下:

◆ 增加内存。为了保证虚基类的数据在派生类中只出现一次,采用虚继承的方式引入了虚基类指针,额外增加了类的占用内存。

◆ 派生类要显式初始化其虚拟基类。通常从开发者的角度来讲,设计一个派生类只要初始化其直接基类即可。但是如果在类的派生层次中存在虚拟基类,那么派生类始终要负责这些虚拟基类的初始化,这在一定程度上导致了设计的复杂化。

多继承容易导致数据冗余和数据不一致,而虚拟继承在解决了这个问题的同时又引入了新的问题。对于类层次结构的设计者来讲,可以采取另外一种方法来解决多继承的问题,即只允许一个基类有数据,其他基类只有方法,这样就消除了数据冗余和数据不一致的问题。只有方法没有数据的类也称做接口。

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

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

相关文章

Spring boot:3.X + Security OAuth2 自定义登录页面、登出后跳转到登录页

本文描述了基于 Spring Oauth2 的 code 模式&#xff0c;实现登陆同时授权、自定义登录界面、登出页面的功能。 1.Maven依赖&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactI…

文件打包上传linux服务器,通过http协议url下载

背景&#xff1a;服务器部署tomcat&#xff0c;已经运行项目 直接在编译后的class文件新建文件&#xff0c;把想要下载的文件放入&#xff0c; 编译后新建文件存放 访问时&#xff0c;加zl/xxx.zip

Springboot拦截器及统一异常处理

文章目录 一、Java中异常相关概念1、异常类2、异常处理方法3、注意事项4、自定义异常 二、配置全局异常处理1、统一返回体定义2、定义异常处理实现类3、全局异常处理类 三、Springboot拦截器1、定义拦截器2、注册拦截器 四、验证效果 一、Java中异常相关概念 1、异常类 Throw…

微信小程序开发系列-07组件

在开发小程序的过程中会遇到一个常见的问题&#xff0c;怎样将其他同事提供的样式或者第三方的样式载入自己的工程&#xff0c;本文基于这个问题为索引&#xff0c;探索下小程序的组件。 什么是组件 组件是视图层的基本组成单元。组件自带一些功能与微信风格一致的样式。一个…

C++标准模板库(STL)

标准模板库&#xff08;STL&#xff09;是一组C模板类&#xff0c;提供常见的编程数据结构和函数&#xff0c;如列表、堆栈、数组等。它是一个容器类、算法和迭代器的库。它是一个通用库&#xff0c;因此&#xff0c;它的组件是参数化的。模板类的相关知识是使用STL的先决条件。…

vivado set_max_delay案例分析

案例分析 在一些设计中&#xff0c;某些信号在特定模式下具有恒定值。例如&#xff0c;在功能模式下&#xff0c;测试信号不切换&#xff0c;因此与VDD或VSS相连这取决于它们的活动水平。这也适用于在设计完成后不切换的信号已通电。同样&#xff0c;今天的设计有多种功能模式…

优秀数据库开发工具Navicat Premium 16 Mac/win中文版

Navicat Premium 16作为一款综合性的数据库开发工具&#xff0c;可以支持主流的数据库管理系统&#xff0c;如MySQL、MariaDB、Oracle、SQL Server等。无论是进行数据库建模、数据导入导出、SQL脚本编辑&#xff0c;还是数据同步、备份恢复等操作&#xff0c;Navicat Premium 1…

Linux安装常用的软件(jdk,MySQL,nginx)并完成对前后端项目的部署发布

linux软件安装&#xff1a; 安装方式介绍&#xff1a; 二进制发布包安装&#xff1a; 软件已经针对具体平台编译打包发布&#xff0c;只要解压&#xff0c;修改配置即可 rpm安装&#xff1a; 软件已经按照redhat的包管理规范进行打包&#xff0c;使用rpm命令进行安装&#xff0…

Java 类加载与字节码技术

3 类加载与字节码技术 3.1 类文件结构 类文件结构字节码指令编译期处理类加载阶段类加载器运行期优化 根据 JVM 规范&#xff0c;类文件结构如下 ClassFile {u4 magic;u2 minor_version; // 小版本号u2 major_version; // 主版本号u2 constant_pool_count; // 常量池cp_info…

《我与地坛》当时只道是寻常;只是当时已惘然

《我与地坛》当时只道是寻常&#xff1b;只是当时已惘然 史铁生&#xff08;1951/1/4-2010/12/31&#xff09;&#xff0c;作家&#xff0c;散文家&#xff0c;代表作有《我与地坛》《命若琴弦》《奶奶的星星》等。 文章目录 《我与地坛》当时只道是寻常&#xff1b;只是当时已…

Thinkphp+vue+mysql学生作业管理系统21j0r

运行环境:phpstudy/wamp/xammp等 开发语言&#xff1a;php 后端框架&#xff1a;Thinkphp5 前端框架&#xff1a;vue.js 服务器&#xff1a;apache 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat/phpmyadmin 为设计一个安全便捷&#xff0c;并且使用户更好获取本学院…

c基础学习(一)

学习网站&#xff1a; C语言的过去与未来 - C语言教程 - C语言网 (dotcpp.com)https://www.dotcpp.com/course/c-intros/ C 语言简介 - C 语言教程 - 网道 (wangdoc.com)https://wangdoc.com/clang/intro 变量&#xff1a; #include<stdio.h> /*引入头文件-- 标准…

深入浅出理解转置卷积Conv2DTranspose

温故而知新&#xff0c;可以为师矣&#xff01; 一、参考资料 论文&#xff1a;A guide to convolution arithmetic for deep learning github源码&#xff1a;Convolution arithmetic bilibili视频&#xff1a;转置卷积&#xff08;transposed convolution&#xff09; 转置…

【Linux】深度解剖环境变量

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟悉并掌握Linux的环境变量。 > 毒鸡汤&#x…

【2023 CCF 大数据与计算智能大赛】基于TPU平台实现超分辨率重建模型部署 基于FSRCNN的TPU平台超分辨率模型部署方案

2023 CCF 大数据与计算智能大赛 基于TPU平台实现超分辨率重建模型部署 基于FSRCNN的TPU平台超分辨率模型部署方案 WELL 刘渝 人工智能 研一 西安交通大学 中国-西安 1461003622qq.com 史政立 网络空间安全 研一 西安交通大学 中国-西安 1170774291qq.com 崔琳、张…

vue项目hdr格式文件放在assets下rgbeloader.load获取不到问题解决

如下图 我再App.vue组件中这样写 艾特符号定位 告诉系统 要src下的assets下的xhdr下的xidis.hdr 但是运行项目 他会告诉你找不到这个资源 我们改一下 我们组件时 App.vue 与assets同在 src目录下 用 ./去找 这样也是找不到的 我们需要将它放在静态资源包public下 public路…

Vite+Vue3学习笔记(2)——语法、渲染、事件、数据传递、生命周期、第三方库、前端部署

官网链接&#xff1a;https://cn.vuejs.org/ 如果出现普通用户无法新建项目&#xff0c;必须要管理员身份新建&#xff0c;那么可以在nodejs的安装路径设置安全选项&#xff0c;提高普通用户的权限。 具体方法参考&#xff1a; https://blog.csdn.net/weixin_43174650/article/…

毫米波雷达:从 3D 走向 4D

1 毫米波雷达已广泛应用于汽车 ADAS 系统 汽车智能驾驶需要感知层、决策层、执行层三大核心系统的高效配合&#xff0c;其中感知层通过传感器探知周围的环境。汽车智能驾驶感知层将真实世界的视觉、物理、事件等信息转变成数字信号&#xff0c;为车辆了解周边环境、制定驾驶操…

2023 年有什么流行的开源项目?

背景介绍 作为一个资深开源参与者&#xff0c;我盘点下2023年有意思的开源项目&#xff0c;从GitHub热榜角度盘点下。 作为工程师我对技术理解顺势而为&#xff0c;当风口袭来拥抱研究使用&#xff0c;理解其中精髓为风口添砖加瓦。 2023年很特殊&#xff0c;AI生成开始火热…

挑战Python100题(7)

100+ Python challenging programming exercises 7 Question 61 Print a unicode string "hello world". Hints: Use ustrings format to define unicode string. 打印一个unicode字符串“helloworld”。 提示:使用u“字符串”格式定义unicode字符串。 Solution…