Bootstrap

C++与Java区别浅析:泛型编程

C++模板与Java泛型

无论C++还是Java,我们在学习集合的时候都会发现任何一种集合都没有固定匹配一种数据类型,这便是泛型编程带来的好处。

C++模板与Java泛型两者在目的是一致的,都是不希望重写相同的功能,提高复用性,两者的区别是由于语言本身不同特征造成的

  1. 在C++中,攻城狮采用的是女娲视角,她自己本身具备属性(参数)或行为(方法),拿来即可使用,不需要为此专门创建一个类描述自己,她在造人时才会把对应的属性与行为捆绑到人身上(即创建class);
  2. 在Java中,攻城狮采用的是平民视角,抬头才是女娲,他是女娲造出来的,所以攻城狮本身就是一个class,得把自己包裹在类中(也因此main函数也得在类中)。

因此C++的模板主要分为函数模板类模板,而Java泛型主要分为泛型类泛型方法泛型接口

一、C++模板

1. 函数模板
  • 格式:template<typename T>
  • 说明:template为创建模板的声明,typename表示其后的T是通用数据类型,一般可用class代替typename。
/函数模板举例/
template<typename T>
T getSum(T t1, T t2){
	return t1 + t2;
}
2. 类模板

格式与函数模板一致,建议用class代替typename。

template<class T1, class T2>
class MyClass{
public:
	T1 para1;
	T2 para2;
public:
	MyClass(T1 p1, T2 p2) : para1(p1), para2(p2){}
}

注意:类模板中的成员函数在调用时才被创建出来。

二、Java泛型

1. 泛型类

泛型类是指具有一个或者多个类型变量的类。

  • 格式:修饰符 class 类名 <数据类型>{ }
  • 举例:public class MyGeneric{ }
/泛型类举例/
public class MyGeneric<T> {

    T num;

    public T getNum() {
        return num;
    }

    public void setNum(T num) {
        this.num = num;
    }
}

/调用方式/
MyGeneric<Integer> iMg = new MyGeneric<>();  //T=Integer
MyGeneric<String> sMg = new MyGeneric<>();  //T=String

使用场景:
当存在多个类的属性(参数)与行为(函数方法)相同,仅数据类型不同时,可使用泛型类;

2. 泛型方法
  • 格式:修饰符 <数据类型> 返回值类型 方法名(数据类型 变量名){ }
  • 举例:public void myFunc(T t) { }
/泛型类举例/
public class MyGeneric{
	public <T> void myFunc(T t) {
		System.out.println(t);
	}
}

/调用方式/
MyGeneric mg = new MyGeneric();
mg.myFunc(2022);  //T=Integer
mg.myFunc("Hello");  //T=String

使用场景:
当类中存在多个函数方法,但执行过程相同时,可使用泛型类。

3. 泛型接口

与泛型接口类似,虽然接口不是类,但纯抽象类像是孪生的接口。

  • 格式:修饰符 class 类名 <数据类型>{ }
  • 举例:public class MyGeneric{ }

不再举例,使用场景与泛型类类似。
Java数据集合中很多都是实现了泛型接口。

三、模板与泛型重载

1. C++函数模板重载
  • 函数模板
template<typename T>
int getSum(T t1, T t2){
	return t1 + t2;
}

template<typename T>
int getSum(T t1, T t2, T t3){
	return t1 + t2 + t3;
}
  • 类模板
template<class T1, class T2>
class MyClass{
public:
	T1 para1;
	T2 para2;
public:
	MyClass(T1 p1, T2 p2) : para1(p1), para2(p2){}
}
2. Java泛型重载
  • 泛型类
public class MyGeneric<T> {
    public void func(T t){
        System.out.println(t);
    }
    public void func(T t1, T t2){
        System.out.println(t2);
    }
}
  • 泛型方法
public class MyGeneric{
    public<T> void func(T t){
        System.out.println(t);
    }
    public<T> void func(T t1, T t2){
        System.out.println(t2);
    }
}

泛型接口也类似,不再举例

四、数据类型写法的自动类型推导

当我们调用模板或泛型时,如果想要在语句中省略数据类型,要确保编译器能够自动正确识别出来。

1. C++模板的自动类型推导

(1) 函数模板的自动类型推导

  • 要实现自动类型推导,必须保证能够推导出一致的数据类型T。
/函数模板/
template<typename T>
int getSum(T t1, T t2){
	return t1 + t2;
}

/函数模板完整调用/
int a = getSum<int>(10, 20);
/自动类型推导/
int b = getSum(10,20);  //正确,数据类型一致
int c = getSum(10,'a');  //错误,数据类型不一致
  • 要确保模板能够确定出数据类型T
/函数模板/
template<typename T>
void func(){
	cout <<"Hello, world!" << endl;
}
/函数模板完整调用/
func<int>()  //正确,指定了数据类型
/自动类型推导/
func()  //错误,无法识别数据类型

注意:
当存在函数模板与普通函数均可调用时,调用规则如下

  1. 优先调用普通函数;
  2. 可通过空模板参数列表来强制调用函数模板,如func<>();
  3. 如果函数模板可以产生更好的匹配,优先调用函数模板。

(2) 类模板的自动类型推导

  • 不可以!必须显示指定数据类型
  • 可以存在默认参数
MyClass mc1("Hello", 2022);  //错误
MyCLass<String, int> mc2("Hello", 2022);  //正确,显示指定数据类型
MyCLass<String> mc3("Hello", 2022);  //正确,存在默认参数
2. Java泛型的自动类型推导
  1. 调用泛型类或泛型接口的自动类型推导
-------------------------数据类型以Integer为例-------------------------
/完整写法/
MyGeneric<Integer> iMg = new MyGeneric<Integer>();

/省略写法/
MyGeneric<Integer> iMg = new MyGeneric<>();
MyGeneric iMg = new MyGeneric<Integer>();

/可以全部省略,这种写法也是通过的,可将不同数据类型传入对象iMg中,但不建议/
MyGeneric iMg = new MyGeneric<>();
iMg.func(2002);
iMg.func("Hello");

泛型接口与泛型类相似,不再举例。

  1. 调用泛型方法的自动类型推导
    这种情况下,编译器自动识别,不必写出数据类型。
;