初识Java 18-3 泛型

目录

边界

通配符

编译器的能力范畴

逆变性

无界通配符

捕获转换


本笔记参考自: 《On Java 中文版》


边界

        在泛型中,边界的作用是:在参数类型上增加限制。这么做可以强制执行应用泛型的类型规则,但还有一个更重要的潜在效果,我们可以调用边界类型上的方法了。

    若一个泛型参数没有边界,那么我们只能调用其中的Object方法。

        为了应用边界的限制,Java复用了extend关键字:

【例子:使用extend规定泛型边界】

interface HasColor {java.awt.Color getColor();
}class WithColor<T extends HasColor> {T item;WithColor(T item) {this.item = item;}T getItem() {return item;}// 可以调用位于边界上的方法:java.awt.Color color() {return item.getColor();}
}class Coord {public int x, y, z;
}// 在规定边界时,需要将类排(Coord)在前面,接口(HasColor)排在后面\
// 因此这种写法会失败:
// class WithColorCoord<T extends HasColor & Coord> {}// 这样才能正确定义多重边界:
class WithColorCoord<T extends Coord & HasColor> {T item;WithColorCoord(T item) {this.item = item;}T getItem() {return item;}java.awt.Color color() {return item.getColor();}int getX() {return item.x;}int getY() {return item.y;}int getZ() {return item.z;}
}interface Weight {int weight();
}// 与继承一样,只能继承一个具体类,但可以实现多个接口:
class Solid<T extends Coord & HasColor & Weight> {T item;Solid(T item) {this.item = item;}T getItem() {return item;}java.awt.Color color() {return item.getColor();}int getX() {return item.x;}int getY() {return item.y;}int getZ() {return item.z;}int weight() {return item.weight();}
}class Bounded extends Coord implements HasColor, Weight {@Overridepublic java.awt.Color getColor() {return null;}@Overridepublic int weight() {return 0;}
}public class BasicBounds {public static void main(String[] args) {Solid<Bounded> solid =new Solid<>(new Bounded());solid.color();solid.getY();solid.weight();}
}

        可以通过继承去除上例中的一些冗余代码。继承也可以增加边界的限制:

【例子:使用继承简化代码】

class HoldItem<T> {T item;HoldItem(T item) {this.item = item;}T getItem() {return item;}
}class WithColor2<T extends HasColor>extends HoldItem<T> {WithColor2(T item) {super(item);}java.awt.Color color() {return item.getColor();}
}class WithColorCoord2<T extends Coord & HasColor>extends WithColor2<T> {WithColorCoord2(T item) {super(item);}int getX() {return item.x;}int getY() {return item.y;}int getZ() {return item.z;}
}class Solid2<T extends Coord & HasColor & Weight>extends WithColorCoord2<T> {Solid2(T item) {super(item);}int weight() {return item.weight();}
}public class InheritBounds {public static void main(String[] args) {Solid2<Bounded> solid2 =new Solid2<>(new Bounded());solid2.color();solid2.getY();solid2.weight();}
}

        Solid2变得更加简洁了。

        在这里,每一层的继承都会为对应的类增加边界的限制,同时继承那些来自父类的方法。这样我们就不需要在每个类中重复定义那些代码了。

        另外,创建泛型集合时需要注意,我们可以且只可以继承一个接口或类:

// 可以进行的操作:
List<? extends Coord> list;
List<? extends HasColor> list1;
// 不可行的操作:
// List < ? extends HasColor & Weight > list2;

通配符

        先看一个例子,将派生类的数组赋值给基类数组的引用:

【例子:数组的特殊行为】

class Fruit {
}class Apple extends Fruit {
}class Jonathan extends Apple {
}class Orange extends Fruit {
}public class CovariantArrays {public static void main(String[] args) {Fruit[] fruit = new Apple[10];// 可行的操作:fruit[0] = new Apple();fruit[1] = new Jonathan();// 但运行时的类型是Apple[],而不是Fruit[]或Orange[]try {// 编译器允许添加Fruit(父类):fruit[0] = new Fruit();} catch (Exception e) { // 但这种操作却会导致ArrayStoreException异常System.out.println(e);}try {// 编译器允许添加Orange:fruit[0] = new Orange();} catch (Exception e) { // 但同样会发生异常System.out.println(e);}}
}

        程序执行的结果是:

        在这个例子中,我们将派生类Apple的数组赋值给了Fruit数组:

Fruit[] fruit = new Apple[10];

这在继承结构上是合理的。

        不过需要注意一点,因为实际的数组类型是Apple[],所以将基类Fruit放入其中是不合理的编译器允许了这个行为,因为从代码上看,这只不过是将Fruit对象赋给了Fruit数组。数组机制能够知道数组的实际类型,因此才会在运行时抛出异常。

    数组可以维持其包含的对象的类型规则,这也是为什么上例这种类似“向上转型”的操作能够成功的原因。它在一定程度上能够确保我们不会乱用数组。

        尽管我们能够在运行时发现这种不合理的数组赋值。但使用泛型,我们可以在编译时提前进行错误检测:

【例子:泛型的编译时检查】

import java.util.ArrayList;
import java.util.List;public class NonCovariantGenerics {List<Fruit> flist = new ArrayList<Apple>();
}

        编译器会在编译时发现如下的问题:

它告诉我们,我们无法将包含Apple的泛型赋值给包含Fruit的泛型。

        之所以会这样,是因为编译器无法掌握足够的信息,它并不知道List<Fruit>List<Apple>是什么关系(另外,这种关系也不会涉及向上转型,二者并不等价)。

        可以发现,在这里我们需要讨论的是集合自身的类型,而不是集合持有的元素类型。

    与数组不同,泛型并没有内建的协变性。数组完全由语言自身定义,而泛型的定义却来自于程序员。因此,编译器和运行系统有足够的信息来检查数组,却无法对泛型做到相同的事。

        若一定需要在List<Fruit>List<Apple>之间建立什么关系,可以使用通配符:

【例子:使用通配符建立关系】

import java.util.ArrayList;
import java.util.List;public class NonCovariantGenerics {public static void main(String[] args) {// 可以用通配符提供协变的能力:List<? extends Fruit> flist = new ArrayList<>();// 但却不能添加任何类型的数据// flist.add(new Apple());// flist.add(new Fruit());// flist.add(new Object());flist.add(null); // 可以添加null,但没什么用// 至少能返回一个Fruit对象:Fruit f = flist.get(0);}
}

        显然,这并不意味着flist真的会持有任何Fruit类型,因为<? extends Fruit>实际上表示的是“某种继承自Fruit的类型”。这里存在着一个矛盾:

集合应该持有具体的类型,但flist只要求提供一种没有被确切指定的类型。

换言之,flist所要求的类型并不具体(这是为了能够向上转型为flist做出的牺牲)

    若一个集合并不要求所持有的类型足够具体,这个集合就会失去意义。而若我们并不知道集合持有的具体元素是什么,我们也无法安全地向其中添加元素。

        因为这种限制,通配符并不适合用于传入参数的集合。但我们可以将其用于接收一个已经打包好的集合,并从中取出元素。

编译器的能力范畴

        按照上面的说法,若使用了通配符,我们似乎无法调用一个带有参数的集合方法了。先看看这个例子:

【例子:调用泛型集合中的含参方法】

import java.util.Arrays;
import java.util.List;public class CompilerIntelligence {public static void main(String[] args) {List<? extends Fruit> flist =Arrays.asList(new Apple());Apple a = (Apple) flist.get(0); // 未产生警告// 方法中的参数是Object:flist.contains(new Apple());// 同样,参数也是Object:flist.indexOf(new Apple());}
}

        程序能够顺利执行。这似乎与之前得出的结论相悖——我们可以调用含参的集合方法。这是否是编译器在其中进行调度呢?

        答案是否定的,可以观察contains()indexOf()方法的参数列表:

contains()indexOf()的参数都是Object的,假若我们调用了flist.add()方法,则会发现:

因为此时add()方法是参数已经变成了? extends Fruit。编译器不会知道应该处理哪种具体的Fruit类型,因此不会接受任何类型。

        这里体现了一种思路:作为泛型类的设计者,若我们认为某种调度是“安全的”,那么可以将Object作为其的参数。例如:

【例子:设置“安全”调度的参数】

import java.util.Objects;public class Holder<T> {private T value;public Holder() {}public Holder(T val) {value = val;}public void set(T val) {value = val;}public T get() {return value;}// 使用Object作为参数@Overridepublic boolean equals(Object o) {return o instanceof Holder &&Objects.equals(value, ((Holder) o).value);}@Overridepublic int hashCode() {return Objects.hashCode(value);}public static void main(String[] args) {Holder<Apple> apple = new Holder<>(new Apple());Apple d = apple.get();apple.set(d);// 不允许这种操作:// Holder<Fruit> fruit = apple;// 但允许这种操作:Holder<? extends Fruit> fruit = apple;Fruit p = fruit.get();d = (Apple) fruit.get(); // 返回一个Object,然后再转型try {Orange c = (Orange) fruit.get();} catch (Exception e) {System.out.println(e);}// 无法这样调用set():// fruit.set(new Apple());// fruit.set(new Fruit());System.out.println(fruit.equals(d));}
}

        程序执行的结果是:

        可以看到,Holder<Apple>无法向上转型为Holder<Fruit>,但却可以向上转型为Holder<? extends Fruit>get()方法和set()方法的使用都会受编译器的限制,值得一提的是,因为get()方法返回了Fruit,所以我们可以手动进行向下转型。

        因为equals()方法接受Object,所以它不会受到上述的限制。


逆变性

        除extends之外,还可以使用超类通配符(重写了super关键字)。如果说extends关键字可以为泛型添加限制,那么super就是为通配符添加了边界限制,其中的边界限制就是某个类的基类。例如:

<? super MyClass>
<? super T> // 可以使用类型参数
// <T super MyClass> // 但无法为泛型参数设置超类边界

        有了超类通配符,就可以向集合中进行写操作了:

【例子:向泛型集合中进行写操作】

import java.util.List;public class SuperTypeWildcards {static void writeTo(List<? super Apple> apples) {apples.add(new Apple());apples.add(new Jonathan());// 但不可以添加基类元素:// apples.add(new Fruit());}
}

        我们可以向apples类型中添加Apple及其的子类型。但由于apples的下界是Apple,所以我们无法安全地先这个泛型集合中添加Fruit

【例子:总结一下通配符】

import java.util.Arrays;
import java.util.List;public class GenericReading {static List<Apple> apples =Arrays.asList(new Apple());static List<Fruit> fruit =Arrays.asList(new Fruit());// 调用精确的类型:static <T> T readExact(List<T> list) {return list.get(0);}// 兼容各种调用的静态方法:static void f1() {Apple a = readExact(apples);Fruit f = readExact(fruit);f = readExact(apples);}// 类的类型会在其实例化后确定:static class Reader<T> {T readExact(List<T> list) {return list.get(0);}}static void f2() {Reader<Fruit> fruitReader = new Reader<>();Fruit f = fruitReader.readExact(fruit);// fruitReader的参数类型是Fruit// 因此不会接受List<Apple>:// Fruit a = fruitReader.readExact(apples);}// 允许协变:static class CovariantReader<T> {T readCovariant(List<? extends T> list) {return list.get(0);}}static void f3() {CovariantReader<Fruit> fruitReader =new CovariantReader<>();Fruit f = fruitReader.readCovariant(fruit);Fruit a = fruitReader.readCovariant(apples);}public static void main(String[] args) {f1();f2();f3();}
}

        f1()使用了一个静态的泛型方法readExact()。从f1()中的调用可以看出,readExact()可以兼容不同的方法调用。因此,若可以使用静态的泛型方法,则不一定需要使用到协变。

        从f2()中可以看出,泛型类的对象会在被实例化时确定下来。因此fruitReader的类型参数被确定成了Fruit


无界通配符

        无界通配符<?>表示“一个泛型可以持有任何类型”,但在更多时候它是一种装饰,告诉别人我考虑过Java泛型,并确定此处的这个泛型可以适配任何类型。

【例子:无界通配符的使用】

import java.util.HashMap;
import java.util.Map;public class UnboundedWildcards2 {static Map map1;static Map<?, ?> map2;static Map<String, ?> map3;static void assign1(Map map) {map1 = map;}static void assign2(Map<?, ?> map) {map2 = map;}static void assign3(Map<String, ?> map) {map3 = map;}public static void main(String[] args) {assign1(new HashMap());assign2(new HashMap());// 出现警告:assign3(new HashMap());assign1(new HashMap<>());assign2(new HashMap<>());assign3(new HashMap<>());}
}

        第一次调用的assign3()会会发出警告,可以在编译时添加-Xlint:unchecked来观察这个警告:

编译器似乎不会区分MapMap<?, ?>。下面的例子会展示出一点区别:

【例子:无界通配符带来的区别】

import java.util.ArrayList;
import java.util.List;public class UnboundedWildcards1 {static List list1;static List<?> list2;static List<? extends Object> list3;static void assign1(List list) {list1 = list;list2 = list;// 会引发警告:list3 = list;}static void assign2(List<?> list) {list1 = list;list2 = list;list3 = list;}static void assign3(List<? extends Object> list) {list1 = list;list2 = list;list3 = list;}public static void main(String[] args) {assign1(new ArrayList());assign2(new ArrayList());// 也会引发警告:assign3(new ArrayList());assign1(new ArrayList<>());assign2(new ArrayList<>());assign3(new ArrayList<>());// 两种定义都被List<?>接受List<?> wildList = new ArrayList();wildList = new ArrayList<>();assign1(wildList);assign2(wildList);assign3(wildList);}
}

        这段代码也会触发一些警告:

这里体现了编译器对List<?>List<? extends Object>在处理上的不同。

        编译器似乎并不关心ListList<?>之间有何区别,因此对它们的处理才会如此相同。然而,尽管这二者都可以被看做List<Object>,但在细节上它们仍有区别,它们实际的指代如下:

  • List:实际上表示“持有任何Object类型的原生List”。
  • List<?>:持有某种具体类型的非原生List(不过我们并不知道具体类型是什么)。

        不过,在一些情况下,编译器仍会区分二者:

【例子:区分不同的泛型】

public class Wildcards {static void rawArgs(Holder holder, Object arg) {// 会触发警告:holder.set(arg);// 当前作用域中也没有T,所以不能这样写:// T t = holder.get();// 可以这么写,但会丢失类型信息:Object obj = holder.get();}// 与rawArgs()不同,方法会触发报错:static void unboundedArg(Holder<?> holder, Object arg) {// 发生报错:// holder.set(arg);// 当然,这样依旧不行:// T t = holder.get();// 可以,但还是会丢失类型信息:Object obj = holder.get();}static <T> T exact1(Holder<T> holder) {return holder.get();}static <T> T exact2(Holder<T> holder, T arg) {holder.set(arg);return holder.get();}static <T>T wildSubtype(Holder<? extends T> holder, T arg) {// 依旧会发生报错:// holder.set(arg);return holder.get();}static <T>void wildSupertype(Holder<? extends T> holder, T arg) {// 引发报错:// holder.set(arg);Object obj = holder.get();}public static void main(String[] args) {Holder raw = new Holder<>();// 这种写法也一样:raw = new Holder();Holder<Long> qualified = new Holder<>();Holder<?> unbounded = new Holder<>();Holder<? extends Long> bounded = new Holder<>();Long lng = 1L;rawArgs(raw, lng);rawArgs(qualified, lng);rawArgs(unbounded, lng);rawArgs(bounded, args);unboundedArg(raw, lng);unboundedArg(qualified, lng);unboundedArg(unbounded, lng);unboundedArg(bounded, lng);// 引发警告:Object r1 = exact1(raw);Long r2 = exact1(qualified);Object r3 = exact1(unbounded); // 方法返回Object类型// 引发异常:// Long r4 = exact1(bounded);// 引发警告Long r5 = exact2(raw, lng);Long r6 = exact2(qualified, lng);// 引发报错:// Long r7 = exact2(unbounded, lng);// 同样会报错:// Long r8 = exact2(bounded, lng);// 引发警告:Long r9 = wildSubtype(raw, lng);Long r10 = wildSubtype(qualified, lng);// 同样会获得Object类型Object r11 = wildSubtype(unbounded, lng);// 引发异常:Long r12 = wildSubtype(bounded, lng);// 引发警告:wildSupertype(raw, lng);wildSupertype(qualified, lng);wildSupertype(bounded, lng);}
}

        先看rawArgs中的holder.set(),编译时会产生警告:

由于这里使用的是Holder的原始类型,所以任何向set()中传入的类型都会被向上转型为Object。编译器知道这种行为是不安全的,所以发出了警告。注意:使用原始类型,就意味着放弃了编译时检查

        再看unboundedArg()中的holder.set(),与原生的Holder不同,使用Holder<?>时编译器提示的警告级别是error

这是因为原生的Holder可以持有任何类型的组合,而Holder<?>只能持有由某种具体类型组合成的单类型集合,因此我们无法传入一个Object

        除此之外,exact1()exact2()也因为参数的不同而受到了不同的限制:

可以看到,exact2()所受的限制更大。

    若向一个有“具体的”泛型类型(即无通配符)参数的方法中传入原生类型,就会产生警告。这是因为具体参数所需的信息并不存在于原生类型中。


捕获转换

        <?>有一个特殊的用法:可以向一个使用了<?>的方法传入原生类型,编译器可能可以推断出具体的类型参数。这被称为捕获转换,通过这种方式,我们可以捕获未指定的通配符类型,将其转换成具体的类型:

【例子:捕获转换的使用例】

 

public class CaptureConversion {static <T> void f1(Holder<T> holder) {T t = holder.get();System.out.println(t.getClass().getSimpleName());}static void f2(Holder<?> holder) {f1(holder); // 捕获类型,并将具体的类型传入f1()中}@SuppressWarnings("unchecked")public static void main(String[] args) {Holder raw = new Holder<>(1);// 若直接传入f1()中,会产生警告f1(raw);// 但使用f2()就不会出现警告f2(raw);Holder rawBasic = new Holder();// 会产生警告:rawBasic.set(new Object());// 也不会出现警告f2(rawBasic);// 即使向上转型为Holder<?>,依旧可以推断出具体类型:Holder<?> wildcarded = new Holder<>(1.0);f2(wildcarded);}
}

        程序执行的结果是:

        需要注意的是,捕获转换经适用于“在方法中必须使用确切类型”的情况。我们无法从f2()方法中返回T,因为对f2()而言,T是未知的(因此,若需要返回值,我们需要自己传入类型参数)

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

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

相关文章

如何在AD的PCB板做矩形槽孔以及如何倒圆弧角

Altium Designer 22下载安装教程-CSDN博客 如何在AD上创建完整的项目-CSDN博客 开始前&#xff0c;请先安装后AD&#xff0c;并创建好项目。 目录 1. 如何在AD的PCB板做矩形槽孔 2. 如何在AD的PCB板倒圆弧角 1. 如何在AD的PCB板做矩形槽孔 首先&#xff0c;我们进入上面创…

PTA 7-9 堆栈操作合法性

7-9 堆栈操作合法性 分数 20 全屏浏览题目 作者 DS课程组 单位 浙江大学 假设以S和X分别表示入栈和出栈操作。如果根据一个仅由S和X构成的序列&#xff0c;对一个空堆栈进行操作&#xff0c;相应操作均可行&#xff08;如没有出现删除时栈空&#xff09;且最后状态也是栈空…

2023年11个最佳免费WordPress主题

如果您刚刚开始使用 WordPress&#xff0c;您可能会很自然地认为&#xff0c;只要免费的WordPress主题看起来像您想要的网站主题&#xff0c;那么它就很合适。不幸的是&#xff0c;事情并没有那么简单。这就是为什么在今天的文章中&#xff0c;我们概述了一份可靠的标准清单&am…

某基金公司赵哥“逆袭”了!!!

赵哥&#xff0c;在上海一家基金公司做运维主管。 平时工作的首要任务&#xff0c;就是保障公司各项信息系统的安全运行。 万一系统运行中出现了一些重要问题&#xff0c;他还要负责进行调查、记录与汇报... 总之&#xff0c;责任很重&#xff0c;该说不说&#xff0c;搞不好…

Java互联网+公立医院绩效考核源码

一、建设信息化医院绩效考核的意义 1.提高考核效率&#xff1a;通过信息化手段&#xff0c;可以将绩效考核数据自动采集、整理、分析和报告&#xff0c;大大提高了考核效率&#xff0c;减少了人工干预和错误率。 2.增强考核公正性&#xff1a;信息化考核可以减少人为因素的干…

Electronica上海 Samtec 验证演示 | FireFly™Micro Flyover System™

摘要/前言 在圆满结束的2023慕尼黑上海电子展上&#xff0c;Samtec虎家团队为观众带来了前所未有的丰富体验&#xff1a;产品展示、采访、Demo演示、抽奖互动~ 尤其是Demo演示&#xff0c;虎家工程师FAE Marcus为大家带来了数个精彩的产品与系统讲解演示。其中更不乏合作伙伴…

Windows、VMware问题集合

Windows、VMware问题集合 一. Windows11安装VMware17提升虚拟机性能1. 桌面右击图标点击属性——>兼容性&#xff0c;找到“以管理员身份运行此程序”勾选&#xff0c;最后点击确定即可。2. 关闭win11的内核隔离功能。 二. VMware虚拟机报错&#xff08;虚拟化性能计数器需要…

C语言——求分段函数 y=f(x)的值

求分段函数 yf(x)的值,f(x)的表达式如下: #define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int x,y;printf("请输入x的值&#xff1a;");scanf("%d",&x);if(x>5){yx3;}else if(x>0 && x<5){y0;}elsey2*x30;pr…

Python 基础【四】--数据类型-字符串【2023.11.23】

1 .定义 字符串是 Python 的一种数据类型&#xff0c;它可以通过单引号 ‘、双引号 "、三引号 ‘’’ 或 “”"来定义。 aabcd bacsdcd c"""accsfv""" print(a) print(b) print(c)2 .基本操作 访问单个字符 注意&#xff1a;从0开始…

Java_注解

1. 简介 在Java中&#xff0c;注解&#xff08;Annotation&#xff09;是一种元数据形式&#xff0c;它为代码添加了额外的信息&#xff0c;这些信息可以被编译器、工具、框架或运行时环境使用。注解提供了一种声明性的方式来向程序中添加元数据&#xff0c;而不需要修改程序的…

Harmony Ble蓝牙App(二)连接与发现服务

Ble蓝牙App&#xff08;二&#xff09;连接与发现服务 前言正文一、BlePeripheral回调二、连接和断连三、连接状态回调四、发现服务五、服务提供者六、显示服务七、源码 前言 在上一篇中我们进行扫描设备的处理&#xff0c;本文中进行连接和发现服务的数据处理&#xff0c;运行…

Mysql数据库 18.Mysql SQL优化

SQL优化 一、插入优化 多条插入语句&#xff0c;影响执行效率 优化方案 1、批量插入&#xff1a; 在一条insert语句中多条数据&#xff0c;但是如果数据量过大&#xff0c;也不能完全使用一条语句语句&#xff0c;建议数据量为一次性插入1000条以下的数据 如果数据量多大&…

plantUML学习与实战

背景 在日常工作或者生活中&#xff0c;使用交互图来描述想法&#xff0c;往往相对于文字来说&#xff0c;可读性更高&#xff0c;同时一定程度上可以提高沟通效率&#xff0c;但是苦于&#xff0c;不想对一堆控件拖拖拉拉&#xff0c;本人就是一个很讨厌画图&#xff0c;但是…

【华为OD题库-036】跳格子2-java

题目 小明和朋友玩跳格子游戏&#xff0c;有n个连续格子组成的圆圈&#xff0c;每个格子有不同的分数&#xff0c;小朋友可以选择从任意格子起跳&#xff0c;但是不能跳连续的格子&#xff0c;不能回头跳&#xff0c;也不能超过一圈:给定一个代表每个格子得分的非负整数数组&am…

Python---把函数的返回值作为另外一个函数的参数

def test1():return 50def test2(num):print(num)# 1. 保存函数test1的返回值 result test1()# 2.将函数返回值所在变量作为参数传递到test2函数 test2(result) # 50

数据结构 栈和队列的应用

在昨天分享了有关栈和队列的基础知识和基本操作后&#xff0c;今天来分享一些有关栈和队列的应用 栈和队列的应用 删除字符串中的所有相邻重复项 #include <iostream> #include <stack> using namespace std; string remove(string S) {stack<char> charS…

MySql表中添加emoji表情

共五处需要修改。 语句执行修改&#xff1a; ALTER TABLE xxxxx CONVERT TO CHARACTER SET utf8mb4;

微型计算机原理MOOC题

一、8254 1.掉坑了&#xff0c;AL传到端口不意味着一定传到的是低位&#xff0c;要看控制字D5和D4&#xff0c;10是只写高位&#xff0c;所以是0A00.。。 2. 3. 4.待解决&#xff1a;

优化C++资源利用:探索高效内存管理技巧

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; &#x1f354;前言&#xff1a; 我们之前在C语言中学习过动态内存开辟&#xff0c;使用malloc、calloc与realloc进行开辟&#xff0c;使用free进行堆上内存的释放。进入C后对于动态内存开辟我们又有了新的内容new与dele…

CCC联盟——UWB MAC(一)

本文在前面已经介绍了相关UWB的PHY之后&#xff0c;重点介绍数字钥匙&#xff08;Digital Key&#xff09;中关于MAC层的相关实现规范。由于MAC层相应涉及内容比较多&#xff0c;本文首先从介绍UWB MAC的整体框架&#xff0c;后续陆续介绍相关的网络、协议等内容。 1、UWB MAC架…