首页 > 编程语言 >C++20中的Concepts 与 Java/C#中的范型约束

C++20中的Concepts 与 Java/C#中的范型约束

时间:2024-11-22 21:48:05浏览次数:3  
标签:范型 Java C# C++ 泛型 public 模板

C++20中的Concepts 与 Java/C#中的范型约束

大家好!最近对C++20中的Concepts非常上头,上一篇聊了C++20中的Concepts与TypeScript,那么今天,就索性连Java和C#中的相似特性一起聊了算了。

C++20 引入了概念(Concepts),它是一种用来对模板参数进行约束的机制,能够提升模板编程的类型安全性和可读性。虽然 Java 和 C# 语言并没有直接等价于 C++20 概念(Concepts)的特性,但它们通过泛型约束和接口机制可以实现类似的功能。

c++-concept-java-c#

Java 中的相似特性

在 Java 中,通过泛型边界(Generic Bounds)和接口(Interfaces)可以实现对类型参数的约束。

泛型边界(Generic Bounds)

通过extends关键字,可以限制泛型参数必须是某个类的子类或者实现了某个接口。

interface Addable<T> {
  T add(T other);
}

public class MyNumber implements Addable<MyNumber> {
  private final int value;

  public MyNumber(int value) {
    this.value = value;
  }

  @Override
  public MyNumber add(MyNumber other) {
    return new MyNumber(this.value + other.value);
  }
}

public class Main {
  // 方法add要求T类型必须实现Addable接口
  public static <T extends Addable<T>> T add(T a, T b) {
    return a.add(b);
  }

  public static void main(String[] args) {
    MyNumber a = new MyNumber(1);
    MyNumber b = new MyNumber(2);
    MyNumber result = add(a, b); // 正常
    System.out.println(result); // 打印结果
  }
}

C# 中的相似特性

在 C# 中,通过泛型约束(Generic Constraints)和接口(Interfaces)可以实现对类型参数的约束。

泛型约束(Generic Constraints)

使用 where 关键字,可以限定泛型参数必须实现某个接口,或者是某个类的子类。

public interface IAddable<T> {
  T Add(T other);
}

public class MyNumber : IAddable<MyNumber> {
  private readonly int value;

  public MyNumber(int value) {
    this.value = value;
  }

  public MyNumber Add(MyNumber other) {
    return new MyNumber(this.value + other.value);
  }
}

public class Program {
  // 方法Add要求T类型必须实现IAddable接口
  public static T Add<T>(T a, T b) where T : IAddable<T> {
    return a.Add(b);
  }

  public static void Main() {
    var a = new MyNumber(1);
    var b = new MyNumber(2);
    var result = Add(a, b); // 正常
    Console.WriteLine(result); // 打印结果
  }
}

差异性

C++的模板(Templates)和Java/C#的泛型(Generics)都是用于泛型编程的特性,它们在允许开发者编写与类型无关的代码方面表现得非常强大。然而,它们之间存在一些根本性的差异。这些差异主要源自模板和泛型的设计哲学、类型检查的时机以及如何处理类型信息的方式。

类型检查的时机

C++ 模板

C++模板是在编译期进行类型检查和代码生成的。模板是编译时的机制,编译器在实例化模板时会生成特定类型的代码。这导致了一些优点和缺点:

  • 优点:由于实例化是在编译期完成的,生成的代码是特定类型的,通常可以进行完全优化(没有运行时开销)。简单来说,编译器会在你编译代码的时候,把模板根据你传入的类型生成具体的代码。这就像一个万能模具,根据你的要求生成不同的部件。这样做的好处是什么呢?生成的代码是特定类型的,非常高效,没有任何额外的运行时开销。你可以把它理解成在出厂前就已经完全加工好的部件。
  • 缺点:编译时错误信息可能较难理解,因为错误往往会在模板实例化的特定上下文中被触发。同时,模板可能会增加编译时间。

举个简单的例子:

template<typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << add(1, 2) << std::endl;   // 生成了整数相加的代码
    // std::cout << add("hello", "world") << std::endl; // 这里只要类型不匹配,编译器就报错了
}

Java/C# 泛型

Java和C#中的泛型大部分是在运行时进行类型检查的。泛型机制更多的是一种编译时的类型安全保证,具体类型信息在编译后会被擦除(Java)或保持部分信息以供运行时使用(C#)。简单来说,编译器会在编译的时候检查你泛型参数是否合法,但一旦编译完成,所有的类型信息都会被擦掉,运行时并不知道你传入的具体类型,就像镇上有家糕点店,做各种口味的蛋糕,但是卖出去的时候全都是统一包装,外人根本不知道里面是什么口味。

  • Java的类型擦除(Type Erasure): 在Java中,类型参数在编译时被擦除,编译器生成的字节码中不会保留泛型参数的具体类型信息。这意味着在运行时,所有泛型类型都视为它们的上限类型(通常是Object)。

    public class Box<T> {
        private T t;
    
        public void set(T t) { this.t = t; }
        public T get() { return t; }
    }
    
    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<>();
        integerBox.set(10);
    
        Box<String> stringBox = new Box<>();
        stringBox.set("Hello");
    
        // 运行时无法区分Box<Integer>和Box<String>
    }
    
  • C#的泛型保留(Generics Preservation): 在C#中,泛型类型信息在运行时部分保留。这允许在运行时通过反射获取类型信息。C#通过“泛型特化”避免了类型擦除,实现了比Java更灵活的泛型机制。

    public class Box<T> {
        private T t;
    
        public void Set(T t) { this.t = t; }
        public T Get() { return t; }
    }
    
    public static void Main() {
        Box<int> intBox = new Box<int>();
        intBox.Set(10);
        
        Box<string> strBox = new Box<string>();
        strBox.Set("Hello");
    
        // 运行时可以通过反射获取泛型类型信息
    }
    

类型参数的应用范围

C++ 模板

C++模板非常灵活,它不仅支持类模板和函数模板,还可以用在模板元编程(Template Metaprogramming)中,可以编写编译期的计算、条件编译等。这让C++模板在类型特化和元编程能力方面显得非常强大。

// 模板元编程例子:计算阶乘
template<int N>
struct Factorial {
    static const int value = N * Factorial<N - 1>::value;
};

template<>
struct Factorial<0> {
    static const int value = 1;
};

int main() {
    int result = Factorial<5>::value; // 5! = 120
}
Java/C# 泛型:

Java和C#的泛型主要用于类和方法,提供类型安全的集合类和方法调用。它们不支持像C++模板那样的模板元编程。

// Java 中使用泛型的类和方法
public class GenericMethodTest {
   // 泛型方法
   public static <T> void printArray(T[] inputArray) {
      for (T element : inputArray) {
         System.out.printf("%s ", element);
      }
      System.out.println();
   }

   public static void main(String args[]) {
      Integer[] intArray = { 1, 2, 3, 4, 5 };
      printArray(intArray);  // 调用泛型方法
   }
}

运行时类型信息

  • C++ 模板: 由于在编译时已经生成了特定类型的代码,C++模板实例在运行时没有类型信息。这意味着在运行时,模板类型实例是完整且独立的,没有额外的开销。
  • Java 类型擦除: Java的类型擦除意味着泛型参数在运行时是不可知的,对于泛型集合中的元素类型检查无法在运行时创建(通常通过instanceof操作和反射实现)。
  • C# 运行时类型信息: C#保留了部分泛型类型信息,可以在运行时通过反射来访问,这是C#泛型更加灵活的一个体现。

性能对比

讲到这里,重点来了,咱们聊聊它们在性能上的差异。

  • C++ 模板性能: 由于C++模板是在编译时生成具体类型的代码,所以它的性能是非常高的。没有额外的开销,运营起来就像量身定制,特别高效。编译器会为每种类型实例化模板,生成独立的代码,这意味着函数调用是内联的,完全没有运行时的类型检查开销。
  • Java/C# 泛型性能: Java的类型擦除会带来一定的运行时开销,因为在运行时,所有类型信息都被擦除,所有类型都被视为Object。操作对象时可能需要进行类型转换,这就稍微慢一点了。C#虽然在运行时保留了一点类型信息,但总体性能不能和C++模板相比,因为它有额外的类型检查和操作开销。

简单来说,就是:

  • C++模板 :就像定制家具,都是提前加工好的,质量高,效率高。

  • Java/C#泛型 :更像是组装家具,虽然灵活,能适配各种情况,但内部有些工序在跑的时候还要再处理一下,稍微牺牲了一点点性能。

小结

Evolution-is-necessary-to-meet-the-challenges

虽然 Java 和 C# 没有直接等同于 C++20 概念(Concepts)的特性,但是通过泛型约束和接口,它们也能够提供类似的功能来确保类型安全。这里是一个简单的对比:

  • C++20 Concepts: 定义模板参数的约束条件,可以应用到模板函数和类中。
  • Java 的泛型边界: 通过extends关键字约束泛型参数,比如要求必须实现某个接口。
  • C# 的泛型约束: 使用where关键字约束泛型参数,比如要求必须实现某个接口。

这些语言特性能够提升代码的类型安全性和可读性,确保在编译时发现类型不匹配的问题。希望这些对比对你理解各语言的相似特性有所帮助!

标签:范型,Java,C#,C++,泛型,public,模板
From: https://blog.csdn.net/2404_88048702/article/details/143867917

相关文章

  • Pycharm中如何丝滑放大或缩小字体
    先来看一种不丝滑放大字体的方法如果再次放大或缩小字体再次操作就可以了,但是一点也不丝滑,是不是!!!跟随我,完成如下设置就可以丝滑放大或缩小Pycharm中的字体啦。还是先打开设置,找到Keymap,右侧搜索font我们可以看到DecreaseFontSize和IncreaseFontSize,也就分别是缩小......
  • C#WPFDataGrid表单查询,利用泛型、反射、委托、可兼容多对象查询
     结合上篇帖子进行深入编写,通过使用泛型、反射、委托可实现多表单查询,同时通过datagrid绑定List<T>通过查询集合降低对数据库的访问。usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Linq;usingSystem.Reflection;usingS......
  • 【25年最新版】从零开始入门java多线程,看这篇就够啦
    文章目录一、认识程序、进程和线程1.程序2.进程3.线程4.并发和并行二、多线程的实现方式1.继承Thread类【最简单,但扩展性差】2.实现Runnable接口【扩展性好,可以继承其他类】3.实现Callable接口【可以拿到线程执行结果】4.设置和获取线程名称5.线程休眠6.线程......
  • JAVA开源项目 卫生健康系统 计算机毕业设计
    博主说明:本文项目编号T076,文末自助获取源码\color{red}{T076,文末自助获......
  • JAVA开源项目 人事管理系统 计算机毕业设计
    博主说明:本文项目编号T077,文末自助获取源码\color{red}{T077,文末自助获......
  • C++中移动语义和拷贝语义的区别及智能指针
    一、C++中移动语义和拷贝语义的区别区别拷贝语义:在赋值或函数参数传递等操作时,会创建对象的一个完整副本。这意味着会分配新的内存空间,把原对象的数据复制到新内存中。如果对象数据量很大,复制操作会消耗较多资源(如时间、内存)。移动语义:是C++11引入的概念,用于优化对象资源所......
  • 【论文阅读】【计算机视觉-分割任务】Unstructured Road Segmentation Using Hypercol
    UnstructuredRoadSegmentationUsingHypercolumnBasedRandomForestsofLocalExpertsAuthors:GemmaM.Sanchez,PrassannaGaneshRavishankar,AntonioM.Lopez个人总结本篇文章提出了基于预训练卷积网络提取特征,并用局部专家优化的随机森林模型实现结构化道......
  • [PaperReading] EgoPoseFormer: A Simple Baseline for Stereo Egocentric 3D Human P
    目录EgoPoseFormer:ASimpleBaselineforStereoEgocentric3DHumanPoseEstimationTL;DRMethodDeformableStereoAttentionExperiment效果可视化总结与思考相关链接资料查询EgoPoseFormer:ASimpleBaselineforStereoEgocentric3DHumanPoseEstimationlink时间:EC......
  • Claude写的
    importorg.junit.Before;importorg.junit.Test;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.MockitoAnnotations;importorg.elasticsearch.client.RestHighLevelClient;importorg.elasticsearch.snapshots.SnapshotInfo;importja......
  • 人工智能之深度学习基础——反向传播(Backpropagation)
    反向传播(Backpropagation)反向传播是神经网络的核心算法之一,用于通过误差反传调整网络参数,从而最小化损失函数。它是一种基于链式法则的高效梯度计算方法,是训练神经网络的关键步骤。1.反向传播的基本步骤1.1前向传播在前向传播过程中,输入数据从输入层经过隐藏层传递到输出层,......