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

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

时间:2024-11-24 23:30:56浏览次数:11  
标签:范型 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

相关文章

  • Python 基于 opencv 的疲劳检测系统的研究与设计
    博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w+、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌......
  • Java基于微信小程序的校园跑腿平台(V2.0)
    博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w+、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌......
  • 【分享】这篇教程助力你成为 JavaScript 糕手!(十一)
    第十一章:异步编程11.1异步编程的概念在JavaScript中,异步编程是一种非常重要的编程模式,它用于处理那些不会立即完成的操作,而是在一段时间后才会返回结果的任务。传统的同步编程模式下,代码是按照从上到下的顺序依次执行的,每一行代码都必须等待前一行代码执行完毕后才会......
  • 学习笔记(四十四):自定义组件@LocalBuilder装饰器
    概述:当开发者使用@Builder做引用数据传递时,会考虑组件的父子关系,使用了bind(this)之后,组件的父子关系和状态管理的父子关系并不一致。为了解决组件的父子关系和状态管理的父子关系保持一致的问题,引入@LocalBuilder装饰器。@LocalBuilder拥有和局部@Builder相同的功能,且比局部@B......
  • HCIA-06 IP路由基础
    目录1.路由基本概念2.路由信息获取方式:2.1直连路由2.2静态路由2.3动态路由3.路由条目3.1最佳路由条目优选3.2路由选择4.高级路由特性4.1路由递归4.2等价路由4.3浮动路由4.4路由汇总路由基本概念--路由、路由信息、路由表不同网段之间的通信需要通过第三层的路由......
  • python+Django+MySQL+echarts+bootstrap制作的教学质量评价系统,包括学生、老师、管理
    项目介绍该教学质量评价系统基于Python、Django、MySQL、ECharts和Bootstrap技术,旨在为学校或教育机构提供一个全面的教学质量评估平台。系统主要包括三种角色:学生、老师和管理员,每个角色有不同的功能权限。学生角色:学生可以通过该平台对所选课程进行评价,评价内容包括老师的......
  • Java2024-高频面试题(附答案)
    *1、SpringCloud有哪些常用组件?分别是什么作用?答:Nacos,OpenFeign,Sentinel,Seata,RabbitMQ,GatewayNacos:服务注册中心,提供服务注册和发现功能OpenFeign:实现远程调用Sentinel:提供服务容错保护Seata:实现分布式事务管理RabbitMQ:实现异步通知Gateway:(API网关......
  • C++ vector的超级详细实用用法
    基本概念vector是C++标准模板库(STL)中的一个容器,定义在<vector>头文件中。它可以被看作是一个动态大小的数组,能够在运行时高效地添加或删除元素。这与普通数组不同,普通数组在创建时大小是固定的,而vector的大小可以根据需要自动增长或缩小。主要特点动态大小例如,你可以创建......
  • 【web】Gin+Go-Micro +Vue+Nodejs+jQuery+ElmentUI 用户模块之vue登录开发以及接口联
    在现代Web应用中,实现用户登录模块是一个关键功能。本文将分为初级、中级、高级阶段,详细说明如何使用Vue、ElementUI进行登录开发,并与Gin、Go-Micro、Node.js进行接口联调。初级用法介绍在初级阶段,主要关注于使用Vue和ElementUI创建一个简单的登录界面,并通过Node.js后端进......
  • scratch二次开发:blockly工作区垃圾桶和进度条的隐藏和显示
    大家好,我是小黄。本期给大家介绍的内容是实现blockly工作区的垃圾桶和进度条的显示和隐藏实现。本次基于的项目源码大家可以关注小黄回复垃圾桶自行获取。一.垃圾桶的显示和实现。在blockly中,我们进行块的删除的时候最常用的两种方法是:1.将块拖到toolbox中删除。2.一种......