头文件保护
目的:多个源文件中可能包含同一个头文件,头文件被重复包含,导致重定义错误
方法一:Include Guard
1 |
|
方法二:#pragma once
1 |
|
Include Guard 的命名规则:
- 使用头文件的名词
- 添加项目或者模块前缀
1 |
C++的模板和Java的泛型对比
C++ 的模板和 Java 的泛型虽然都用于支持泛型编程,但在具体实现和功能上有显著区别。以下是它们的对比:
1. 编译时与运行时的区别
C++ 模板:
- 在编译时处理。
- 编译器为每个实例化的类型生成一个独立的代码版本(模板实例化),所以是 静态多态。
- 这可以在编译时优化代码,但也会增加编译时间和代码体积(代码膨胀)。
Java 泛型:
- 在运行时通过类型擦除实现。
- 类型参数在编译后被擦除,泛型类型被替换为原始类型(如
Object
)。 - 所以在运行时泛型类型的信息不可用。
2. 类型安全
C++ 模板:
- 类型是严格检查的。如果传递的类型不满足模板要求,会在编译时报错。
- 模板代码必须支持所传入的类型(比如,模板中调用了传入类型的某些方法或操作符)。
Java 泛型:
- 通过类型擦除的机制,运行时只会检查类型为擦除后的原始类型(如
Object
)。 - 编译器在编译时会对泛型进行类型检查,防止运行时类型转换错误。
- 使用
<? extends T>
或<? super T>
可以灵活地限制类型范围。
- 通过类型擦除的机制,运行时只会检查类型为擦除后的原始类型(如
3. 类型擦除
C++ 模板:
- 没有类型擦除,模板实例会保留具体类型的信息。
- 不同类型的模板实例会生成不同的机器代码。
Java 泛型:
- 使用类型擦除,编译器会将泛型替换为原始类型(如
Object
),并插入必要的类型转换。 - 运行时没有泛型类型信息,因此不能使用反射操作泛型参数类型。
- 使用类型擦除,编译器会将泛型替换为原始类型(如
4. 功能差异
C++ 模板:
- 支持 非类型模板参数:
1
2
3
4template <typename T, int N>
class Array {
T data[N];
};- 模板参数可以是值(如
int
)、类型(如typename
)、甚至模板(模板模板参数)。
- 模板参数可以是值(如
- 可以用于函数、类、别名等多种用途。
- 支持模板特化和部分特化:
- 完全特化:
1
2template <>
class MyClass<int> { /* 特化实现 */ }; - 部分特化:
1
2template <typename T>
class MyClass<T*> { /* 针对指针类型的实现 */ };
- 完全特化:
- 可以结合模板元编程,进行复杂的编译时计算。
- 支持 非类型模板参数:
Java 泛型:
- 主要用于类和方法的类型参数化。
- 不支持非类型参数(如
int
)。 - 没有模板特化的概念。
- 不能在泛型中使用基本类型(如
int
、double
),需要使用对应的包装类(如Integer
、Double
)。
5. 泛型编程的灵活性
C++ 模板:
- 功能更强大,几乎可以用来实现任何形式的编译时泛型。
- 结合模板元编程,可以实现复杂的逻辑,比如条件判断、递归计算等。
Java 泛型:
- 更简单,主要用于确保类型安全的集合和方法。
- 类型系统不如 C++ 模板灵活,但更容易使用和维护。
6. 示例对比
C++ 模板
1 |
|
Java 泛型
1 | public class Main { |
7. 编译与运行效率
- C++ 模板:
- 在编译时生成独立代码,效率高,但编译时间长,代码体积可能较大。
- Java 泛型:
- 通过类型擦除减少运行时开销,但在运行时可能需要额外的类型检查和转换。
总结
特性 | C++ 模板 | Java 泛型 |
---|---|---|
处理时间 | 编译时处理,生成具体类型的代码。 | 编译时检查类型,运行时使用类型擦除。 |
类型信息 | 类型信息在编译和运行时都保留。 | 运行时类型信息被擦除,只保留原始类型。 |
类型安全 | 编译时检查,类型要求更严格。 | 编译时检查,运行时依赖类型擦除和转换。 |
灵活性 | 支持非类型参数、模板特化、模板元编程。 | 主要用于类和方法的类型参数化,不支持特化。 |
性能 | 高效,但可能导致代码膨胀。 | 更小的代码体积,但运行时可能需要类型转换。 |
易用性 | 较复杂,适合高性能和灵活性要求的场景。 | 简单易用,适合日常开发中的类型安全需求。 |
- C++ 模板 更强大灵活,适合高性能系统或需要复杂编译时计算的场景。
- Java 泛型 更简单易用,适合大多数面向对象开发需求,尤其是类型安全的集合操作。