第8章 泛型程序设计

这篇具有很好参考价值的文章主要介绍了第8章 泛型程序设计。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

为什么要使用泛型程序设计

泛型程序设计(generi c programming)意味着编写的代码可以对多种不同类型的对象重
用。

类型参数的好处

它们会让你的程序更易读,也更安全。
集合中没有使用泛型时:
第8章 泛型程序设计
集合中使用泛型时:
第8章 泛型程序设计

Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。即,把不安全的因素在编译期间就排除了,而不是运行期;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。

谁想成为泛型程序员

实现泛型类典型问题:
一个程序员可能想要将一个ArrayList<Manager>中的所有元素添加到一个ArrayList<Employee>中去。不过,当然反过来就不行了。如何允许前一个调用,而不允许后一个调用呢?
Java设计者为此发明了通配符类型

定义简单泛型类

  1. 类型变量在整个类定义中用于指定方法的返回类型以及字段和局部变量的类型。变量E表示集
    合的元素类型,K和V分别表示表的键和值的类型。T、U和S表示“任意类型”。

泛型方法

  1. 类型变量放在修饰符的后面,并在返回类型的前面。
class ArrayAlg
{
    public static <T> T getMiddle(T... a)
    {
        return a[a.length / 2];
    }
}
  1. 泛型方法可以在普通类中定义,也可以在泛型类中定义。
  2. 调用一个泛型方法时,可以把具体类型包围在尖括号中,放在方法名前面。大多数情况下,方法调用中可以省略/类型参数。编译器有足够的信息推断出你想要的方法。
String middle = ArrayAlg.<String>getMiddle("John", "Q. " , "Public"); 
String middle = ArrayAlg.getMiddle("John", "Q. " , "Public"); 
  1. 泛型方法的类型推导常见错误:
double middle = ArrayAIg.getMiddle(3.14, 1729, 0);

编译器将把参数自动装箱为1个Double和2个Integer对象,然后寻找这些类的共同超类型。事实上,它找到了 2个超类型:Number和Comparable接口,Comparable接口本身也是一个泛型类型。在这种情况下,可以采取的补救措施是将所有的参数都写为double值。

类型变量的限定

  1. 限制T只能是实现了 Comparable接口。
public static <T extends Comparable> T min(T[] a)
  1. 为什么使用关键字extends而不是implements ?毕竟,Comparable是一个接口。下面的记法
    <T extends BoundingType>表示T应该是限定类型的子类型。T和限定类型可以是,也可
    以是接口。选择关键字extends的原因是它更接近子类型的概念,并且Java的设计者也不打
    算在语言中再添加一个新的关键字(如sub)。

  2. 一个类型变量或通配符可以有多个限定,例如:T extends Comparable & Serializable。限定类型用“&”分隔,而逗号用来分隔类型变量。

  3. 可以根据需要拥有多个接口超类型,但最多有一个限定可以是。如果有一个类作为限定,它必须是限定列表中的第一个限定。

泛型代码和虚拟机

对于Java泛型的转换,需要记住以下几个事实:

  1. 虚拟机中没有泛型,只有普通的类和方法。
  2. 所有的类型参数都会替换为它们的限定类型。(类型擦除)
  3. 会合成桥方法来保持多态。
  4. 为保持类型安全性,必要时会插人强制类型转换

类型擦除

  1. 对于泛型类,类型变量会被擦除,并替换为其原始类型

Java泛型与C++模板有很大的区别。C++为每个模板的实例化产生不同的类型,这一现象称为“模板代码膨胀”。Java不存在这个问题的困扰。

  1. 原始类型用第一个限定来替换类型变量,或者,如果没有给定限定,就替换为0bject
    第8章 泛型程序设计

转换泛型表达式

  1. 调用泛型方法时,如果擦除了返回类型,编译器会插人强制类型转换
 Pair<Employee> buddies =...;
  Employee buddy = buddies.getFirst();

getFirst擦除类型后的返回类型是0bject。编译器自动插人转换到Employee的强制类型转换。也就是说,编译器把这个方法调用转换为两条虚拟机指令:
• 对原始方法Pair.getFirst的调用。
• 将返回的Object类型强制转换为Employee类型。

  1. 变量擦除与参数约束无关。

private T first; public void setFirst(T newValue) { first = newValue; }擦除后 private Object first; public void setFirst(Object newValue) { first = newValue; }。但Pair<Employee>调用setFirst(T newValue)方法时,编译器要求只能传入Employee及其子类型(Manager)的对象。不可能传入一个Object对象,保证了泛型的安全性。
第8章 泛型程序设计

  1. 泛型安全闭环:f(T t)方法传入参数T t有严格的参数类型控制,保证了更改器方法一定是安全的。这样一来,保证了调用get方法时编译器自动进行强制转换的安全。

转换泛型方法

  1. 类型擦除也会出现在泛型方法中。

public static <T extends Comparable> T min(T[] a)是整个一组方法,而擦除类型之后,只剩下一个方法:public static Comparable min(Comparable[] a)。其中,类型参数T已经被擦除了,只留下了限定类型Comparable。

  1. 类型擦除与多态会发生冲突,编译器会使用桥方法

类型擦除与多态会发生冲突

public class Pair<T>
{
    private T first;
    private T second;

    public Pair() { first = null; second = null; }
    public Pair(T first, T second) { this.first = first;  this.second = second; }

    public T getFirst() { return first; }
    public T getSecond() { return second; }

    public void setFirst(T newValue) { first = newValue; }
    public void setSecond(T newValue) { second = newValue; }
}

(1)DateInterval类是Pair的子类,Pair是个泛型类。

public class DateInterval extends Pair<LocalDate> {
    //重写的方法
    public void setSecond(LocalDate second) {
        System.out.println("DateInterval.setScond() is called");
        if(second.compareTo(getFirst())>=0)
            super.setSecond(second);
    }
}

(2)DateInterval类被擦除后变成:

public class DateInterval extends Pair {  //Pair<LocalDate>被擦除
    //重写的方法
    public void setSecond(LocalDate second) {
        System.out.println("DateInterval.setScond() is called");
        if(second.compareTo(getFirst())>=0)
            super.setSecond(second);
    }
}

(3)此时,DateInterval还有一个从Pair继承的setSecond方法:

public void setSecond(Object second) // 泛型类Pair的泛型方法在JVM中被擦除成object

(4)显然,DateInterval的public void setSecond(LocalDate second) 方法,不是对Pair的public void setSecond(Object second) 方法的重写。因为参数类型不同。所以无法自动实现多态。理论上,DateInterval有两个不相关的方法。即类型擦除与多态发生了冲突
第8章 泛型程序设计

桥方法实现多态

(1) 下面的例子可以看出,泛型实现了多态。

此处实现了多态,调用的是DateInterval.setScond()方法。只有second的日期在first之后才会给second赋值。年份换成1999年,输出的就是null。

 public static void main(String[] args) {
        DateInterval dateInterval = new DateInterval();
        Pair<LocalDate> superDateInterval=dateInterval;   // 子类对象赋值给父类变量
        superDateInterval.setFirst(LocalDate.of(2000, 12, 22));
        superDateInterval.setSecond(LocalDate.of(1999, 12, 22)); //not work,second为null
        superDateInterval.setSecond(LocalDate.of(2008, 12, 22));  // work,second为2008-12-22
        System.out.println(superDateInterval.getSecond());

    }

(2) 为了解决类型擦除与多态的冲突,编译器在DateInterval类中生成了一个桥方法

public void setSecond(Object second) { setSecond((LocalDate) second); }

DateInterval的桥方法public void setSecond(Object second) { setSecond((LocalDate) second); }是Pair的public void setSecond(Object second) 方法的重写。所以,多态性让superDateInterval调用的是superDateInterval的桥方法,而桥方法会调用DateInterval的public void setSecond(LocalDate second) 方法。

(3)利用反射查看桥方法
第8章 泛型程序设计

public static void main(String[] args) {
        DateInterval dateInterval = new DateInterval();
        Pair<LocalDate> superDateInterval=dateInterval;   // 子类对象赋值给父类对象
        superDateInterval.setFirst(LocalDate.of(2000, 12, 22));
        //此处实现了多态,调用的是DateInterval.setScond()方法。只有second的日期在first之后才会给second赋值。年份换成1999年,输出的就是null。
        superDateInterval.setSecond(LocalDate.of(2008, 12, 22));
        System.out.println(superDateInterval.getSecond());
        Method[] declaredMethods = dateInterval.getClass().getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(Modifier.toString(declaredMethod.getModifiers())+" "+declaredMethod.getReturnType()
                    +" "+declaredMethod.getName()
                   +"("+declaredMethod.getParameterTypes()[0]+")");
        }
    }
桥方法与可协变的返回类型

假设Datelnterval类也覆盖了 getSecond方法:

class Datelnterval extends Pair<LocalDate>
{
public LocalDate getSecond() { return (LocalDate) super.getSecond(); }
}

在Datelnterval类中,有两个getSecond方法:
LocalDate getSecond() // defined in Datelnterval
Object getSecond() // overrides the method defined in Pair to call the first method

  • 不能这样编写Java代码(两个方法有相同的参数类型是不合法的,在这里,两个方法都没有参数)。但是,在虚拟机中,会由参数类型和返回类型共同指定一个方法。因此,编译器可以为两个仅返回类型不同的方法生成字节码,虚拟机能够正确地处理这种情况。
  • 桥方法会调用新定义的方法LocalDate getSecond()
    第8章 泛型程序设计

调用遗留代码

设计Java泛型时,主要目标是允许泛型代码和遗留代码之间能够互操作。

  1. Swing用户界面工具包提供了一个JSlider类,有方法void setLabelTable(Dictionary table)。
  2. 在Java 5之前,Dictionary类实现为一个Object实例映射。Java 5把Dictionary实现为一个泛型类,不过JSlider从未更新,JSlider使用的依旧是没有类型参数,是原始类型的Dictionary类。
    第8章 泛型程序设计
    第8章 泛型程序设计
  3. 这种不匹配,会带来兼容性问题。

Dictionary<Integer, Components> labelTable = slider.getLabelTablel); // warning
警告: 确保标签表确实包含Integer和Component对象。

限制与局限性

  1. 不能用基本类型实例化类型参数,用对应的包装器类。

基本类型不是对象,无法擦除为object。

  1. 运行时类型查询只适用于原始类型

虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。
第8章 泛型程序设计

  1. 不能创建参数化类型的数组.var table = new Pair<String>[10]; //error

第8章 泛型程序设计
4. Varargs 警告
第8章 泛型程序设计

  1. 不能实例化类型变量,不能在类似newT(…)的表达式中使用类型变量。
    第8章 泛型程序设计
    第8章 泛型程序设计
public class PairTest2
{
   public static void main(String[] args)
   {

      Pair p1=Pair.makePair(new Supplier<String>() {
         public String get()
         {
            return "hhh";
         }
      });
      Pair p2=Pair.makePair(String.class);
   }

}

public class Pair<T> 
{
   private T first;
   private T second;

   public Pair() { first = null; second = null; }
   public Pair(T first, T second) { this.first = first;  this.second = second; }

   public T getFirst() { return first; }
   public T getSecond() { return second; }

   public void setFirst(T newValue) { first = newValue; }
   public void setSecond(T newValue) { second = newValue; }

   public static <T> Pair<T> makePair(Supplier<T> constr) {   //用Supplier创建
      return new Pair<>(constr.get(),constr.get());
   }

   public static <T> Pair<T> makePair(Class<T> cl) {    //用反射创建
      try {
         return new Pair<>(cl.getConstructor(String.class).newInstance("123"),cl.getConstructor(String.class).newInstance("456"));
      } catch (Exception e) {
         throw new RuntimeException(e);
      } 
   }
}
  1. 不能构造泛型数组
    第8章 泛型程序设计

理解问题:为什么return (E) elements[n];可以用强制类型转换。而不能:T a = new T(); return (T) a;

  • 实际上new T()会报错,因为不能实例化类型变量,同时,(T) a这个强制类型转换也不满足强转的条件: a instanceof T为假。经过类型擦除后,new T() 变成new object() , 所以a实际是一个object对象,不能把父类对象强转成子类对象。进一步揭示了为什么不能实例化类型变量。
  • 观察上面的代码,发现elements是对象变量而不是new出来的对象。elements[n]擦除为object,但elements[n]指向了某个T类型,所以可以进行强制转换。

第8章 泛型程序设计

数组父类为object,但B继承了A,那么A[]类型的引用就可以指向B[]类型的对象

第8章 泛型程序设计

  1. 泛型类的静态上下文中类型变量无效
    第8章 泛型程序设计

静态字段属于类,两个不同对象只有一个属于类的singleInstance字段,不可能既是A类型又是B类型。

  1. 不能抛出或捕获泛型类的实例
    第8章 泛型程序设计

  2. 可以取消对检查型异常的检查
    第8章 泛型程序设计

  3. 注意擦除后的冲突
    第8章 泛型程序设计

当Pair<T>重写equals方法时,即使加override注解也会报错。原因:泛型类重写equals方法时,为了实现多态,JVM会自动生成桥方法:boolean equals(Object),桥方法会调用重写的方法boolean equals(T),但由于擦除后T就是Object,所以重写的方法与巧方法冲突。、第8章 泛型程序设计
第8章 泛型程序设计
第8章 泛型程序设计

泛型类型的继承规则

  1. 无论S与T有什么关系,通常,Pair<S>与Pair都没有任何关系
    第8章 泛型程序设计

没有继承关系,不可能多态或者强制转型。

第8章 泛型程序设计

  1. 总是可以将参数化类型转换为一个原始类型。例如,Pair<Employee>是原始类型Pair的一个子类型。

会会产生类型错误:
Pair pair=new Pair<Integer>(1,2); pair.setFirst("牛"); // only a compile-time warning
运行时才会报错ClassCastException。
但失去的只是泛型程序设计提供的附加安全性,更重要的是与遗留代码交互。

  1. 泛型类可以扩展或实现其他的泛型类。

ArrayList<T>类实现了List<T>接口。这意味着,一个ArrayList<Manager>可以转换为一个List<Manager>。但是,如前面所见,ArrayList<Manager>不是一个ArrayList<Employee>或List<Employee>。
第8章 泛型程序设计

8.8 通配符类型

8.8.1通配符概念

  • 在通配符类型中,允许类型参数发生变化。例如,通配符类型
    Pair <? extends Employee>
    第8章 泛型程序设计

  • 类型 Pair<Manager> 是 Pair<? extends Employee〉的子类型
    第8章 泛型程序设计

  • <? extends T>: 可调用get方法,不能调用set方法。get方法的返回值赋值给一个T类型或者T的子类型的对象变量。

第8章 泛型程序设计

第8章 泛型程序设计

8.8.2 通配符的超类型限定

    • <? superT>: 可调用get方法,不能调用set方法。调用set方法时,只能传递T类型的对象。get返回值只能赋给一个object。

第8章 泛型程序设计

第8章 泛型程序设计
第8章 泛型程序设计
第8章 泛型程序设计

8.8.3 无限定通配符

  1. Pair<?>
  2. get方法的返回值只能赋值给一个Object。setFirst方法不能被调用,甚至不能用Object
    调用。
  3. Pair<?>和Pair本质的不同在于:可以用任意Object对象调用原始Pair类的setFirst
    方法。
    第8章 泛型程序设计
    第8章 泛型程序设计

第8章 泛型程序设计

8.8.4 通配符捕获

  1. 通配符不是类型变量,因此,不能在编写代码中使用“?”作为一种类型。
 ? t = p.getFirst(); // ERROR
  p.setFirst(p.getSecond());
  p.setSecond(t);
  1. swapHelper方法的参数T捕获通配符。
   public static void swap(Pair<?> p) { swapHelper(p); }

   public static <T> void swapHelper(Pair<T> p)
   {
      T t = p.getFirst();
      p.setFirst(p.getSecond());
      p.setSecond(t);
   }
  1. 编译器必须能够保证通配符表示单个确定的类型。例如,ArrayList<Pair<T>>中的T永远不能捕获ArrayList<Pair<?>>中的通配符。数组列表可以保存两个Pair<?>,其中的?分别有不同的类型。 (有待考证)
   var ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
      var cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
      Employee employee1=new Employee("employee1",80000,1999,11,22);
      Employee employee2=new Employee("employee2",80000,1999,11,22);
      var buddies = new Pair<Manager>(ceo, cfo);
      ceo.setBonus(1000000);
      cfo.setBonus(500000);
      Manager[] managers = { ceo, cfo };
      ArrayList<Pair<?>> arrayList=new ArrayList<>();  // ArrayList存储了不同类型的两个pair
      arrayList.add(new Pair<Manager>(ceo,cfo));
      arrayList.add(new Pair<Employee>(employee1,employee2));
      PairAlg.swap(arrayList.get(1));    //可行
      PairAlg.swap(arrayList.get(0));    //可行
      System.out.println();

8.9 反射和泛型

  1. 反射允许你在运行时分析任意对象。
  2. 泛型类型参数你将得不到太多信息,因为它们已经被擦除了。
    在下面的小节中,我们将学习利用反射可以获得泛型类的哪些信息。

8.9.1 泛型 Class 类

Class类是泛型的。Class类的常见方法:
第8章 泛型程序设计

8.9.2使用Class<T>参数进行类型匹配

Class<T>参数用于解决无法new T()的局限,创建T对象。
第8章 泛型程序设计

8.9.3 虚拟机中的泛型类型信息

  1. 原始的Pair类知道它源于泛型类Pair<T>,尽管一个Pair类型的对象无法区分它是构造为Pair<String>还是Pair<Employee>。 即可通过反射获得泛型类的所有信息。
  2. java.lang.reflect包中的接口Type
    第8章 泛型程序设计
...
public static void printType(Type type, boolean isDefinition)
   {
      if (type instanceof Class)
      {
         var t = (Class<?>) type;
         System.out.print(t.getName());
      }
      else if (type instanceof TypeVariable)   //类型变量
      {
         var t = (TypeVariable<?>) type;
         System.out.print(t.getName());
         if (isDefinition)
            printTypes(t.getBounds(), " extends ", " & ", "", false);
      }
      else if (type instanceof WildcardType)   //通配符
      {
         var t = (WildcardType) type;  
         System.out.print("?");
         printTypes(t.getUpperBounds(), " extends ", " & ", "", false);
         printTypes(t.getLowerBounds(), " super ", " & ", "", false);
      }
      else if (type instanceof ParameterizedType)  // 泛型类或接口
      {
         var t = (ParameterizedType) type;
         Type owner = t.getOwnerType();
         if (owner != null)
         {
            printType(owner, false);
            System.out.print(".");
         }
         printType(t.getRawType(), false);
         printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);
      }
      else if (type instanceof GenericArrayType)   // 泛型数组
      {
         var t = (GenericArrayType) type;
         System.out.print("");
         printType(t.getGenericComponentType(), isDefinition);
         System.out.print("[]");
      }
   }
   ...

8.9.4 类型字面量

第8章 泛型程序设计文章来源地址https://www.toymoban.com/news/detail-470277.html

个人理解:为什么要擦除?

  1. 一个泛型类和泛型方法里,由于参数是T类型,不可能调用任何具体类的个性化函数。比如String.subString方法。因为当T不是String而是Integer时,T没有subSTring方法。
  2. 擦除后变为原始类型,也保证了不会调用任何具体类的个性化函数。
  3. 泛型具有的是各种类型的共同之处抽取出来的,不关心个性化的东西,核心在不同类型的共同之处。
  4. 调用泛型方法时,传入一个String,擦除后,调用泛型方法时,参数类型是object。而泛型方法类不会有任何String个性化方法,保证了object可以完成泛型方法,不可能出现调用String的subSTring方法,而object无法提供的情况。而当返回时,想要String对象,编译器会自动强制类型转换。
  5. 假如说,你想个性化泛型类的方法,只能继承,使用多态

到了这里,关于第8章 泛型程序设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis为什么被设计为单线程

            redis是单线程的原因在于redis用单个CPU绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的。redis核心就是 如果我的数据全都在内存里,我单线程的去操作就是效率最高的。所以,redis是单线程。 一、 Redis为什么那么快 1、完

    2024年02月21日
    浏览(43)
  • 架构篇03-为什么要做架构设计?

    谈到架构设计,相信每个技术人员都是耳熟能详,但如果深入探讨一下,“为何要做架构设计?”或者“架构设计目的是什么?”类似的问题,大部分人可能从来没有思考过,或者即使有思考,也没有太明确可信的答案。 关于架构设计的目的,常见的误区有: 因为架构很重

    2024年01月21日
    浏览(66)
  • 为什么程序员喜欢用Linux?

      Linux哪些行业在运用? Linux系统运用极其广泛,不少用户只知道windows,是因为,Linux的运用主要是在企业端。现在科技极其发达,我们手机在手,就能干很多事情,只需点一点屏幕,轻松完成聊天、娱乐、甚至支付功能。这些操作看似简单,但其背后,有一系列复杂请求和

    2024年02月04日
    浏览(73)
  • 单元测试优化:为什么要对程序进行测试?测试有什么好处?

    单元测试 (Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。 程序单元是应用的最小可测试部件。简单来说,就是测试数据的稳定性是否达到程序的预期。 我们日常开发时可能在不经意间写错,如果等到最后阶段去检验项

    2024年02月13日
    浏览(46)
  • 为什么单片机可以直接烧录程序的原因是什么?

    单片机(Microcontroller)可以直接烧录程序的原因主要有以下几点: 集成性:单片机是一种高度集成的芯片,内部包含了处理器核心(CPU)、存储器(如闪存、EEPROM、RAM等)、输入/输出接口(如GPIO、UART、SPI、I2C等)以及时钟电路等功能模块。这种高度集成的设计使得单片机能

    2024年02月16日
    浏览(78)
  • 为什么很多程序员喜欢linux系统?

    a Linux哪些行业在运用? Linux系统运用极其广泛,不少用户只知道windows,是因为,Linux的运用主要是在企业端。现在科技极其发达,我们手机在手,就能干很多事情,只需点一点屏幕,轻松完成聊天、娱乐、甚至支付功能。这些操作看似简单,但其背后,有一系列复杂请求和响

    2024年02月03日
    浏览(67)
  • 为什么程序员都喜欢开源的软件?

    程序员宝藏库 :https://gitee.com/sharetech_lee/CS-Books-Store 商业软件 :下载、安装、注册账号、登陆、看广告…费了半天功夫之后发现竟然收费! 开源软件 :开放透明、完全免费。 这么明显的对比,为啥不喜欢用开源软件呢? 以PDF阅读为例,这类商业软件不少,用的时候会发现

    2023年04月09日
    浏览(61)
  • DCL 单例模式设计为什么需要 volatile 修饰实例对象

     DCL 问题,是在基于双重检查锁设计下的单例模式中,存在不 完整对象的问题。而这个不完整对象的本质,是因为指令重排序导致的。 当我们使用 instance=new DCLExample()构建一个实例对象的时候,因为 new 这个操作并不是原子的。所以这段代码最终会被编译成 3 条指令: 为对象

    2024年02月08日
    浏览(49)
  • 《服务器无状态设计:为什么&如何实现无状态API?》

    🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通Golang(基础

    2024年02月09日
    浏览(56)
  • 工控上位机程序为什么只能用C语言?

    工控上位机程序并不只能用C#开发,实际上在工业自动化领域中,常见的上位机开发语言包括但不限于以下几种: C#: C#是一种常用的编程语言,在工控领域中被广泛使用。它具有良好的面向对象特性和丰富的类库支持,可以实现高性能的上位机程序开发。 C/C++: C/C++是传统的编

    2024年02月10日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包