前言

由于左老师的课上用的是java来教授,而我比较熟悉c++,所以我在借助gpt帮我将代码改为c++的同时,还和它对两种语言的相同和不同之处进行了比较详细的探讨。
探讨的内容包括:

  • 比较器的返回值差异
  • 函数对象声明的区别
  • 优雅 - 翩翩起舞的 lambda
  • java 的 key 都是什么意思
  • c++: auto vs it JAVA 的良好特性——默认传递引用、数组自带大小信息

    这是来自左老师的比较器代码(JAVA)

构造函数的区别:

首先,我发现两种语言在构造函数上不太一样。

语言 代码示例 初始化方式
C++ cpp Student(string name, int id) : name(name), id(id) {} 初始化列表
Java java public Student(String name, int id) { this.name = name; this.id = id; } 构造函数体内赋值

我发现原代码没有用初始化列表来赋值,于是查询资料:

1
2
在 Java 中,构造函数的语法不支持像 C++ 中的初始化列表一样的方式来进行构造时赋值。在 Java 中,你需要在构造函数的方法体内手动对成员变量进行赋值。 
如果你在意初始化效率,可以考虑使用 Java 中的双重检查锁定等方式来优化对象的创建过程。

“双重检查锁”通常用于实现单例模式,这意味着在整个应用程序中只有一个实例。通过使用双重检查锁,可以在多线程环境中确保只有一个线程能够成功初始化对象,并且其他线程会等待或被阻塞,以避免并发初始化的问题。

我立马联想到了c++中的“静态对象”,在C++中,可以使用静态成员变量来实现类级别的单例对象。静态成员变量只在类的生命周期内存在一个实例,并且它在类加载时被初始化。这样,每次使用这个类的对象时,都是引用同一个静态对象实例。

但是,查阅资料知道,虽然这两种方式都可以实现对象的单例特性,但在细节上存在一些差异。双重检查锁主要用于在多线程环境下确保单例对象的正确初始化,而C++的静态对象则是一种在类级别上实现单例的方式,不涉及多线程的同步问题。

函数对象声明的区别

特点 Java C++
实现方式 实现Comparator接口,重写compare方法 创建函数对象类,重载函数调用操作符()
返回值 整数值,负数表示o1小于o2,正数表示o1大于o2 布尔值,true表示o1小于o2,false表示o1大于o2
命名空间 需要使用类名限定 可在局部或命名空间内使用
比较规则 返回负数、0、正数分别表示小于、等于、大于 返回true表示小于,false表示大于

示例:

1
2
3
4
5
6
7
8
public static class MyComp implements Comparator<Integer> {

@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}

}
1
2
3
4
5
6
class MyComp {
public:
bool operator()(const int& o1, const int& o2) const {
return o2 < o1;
}
};

java的这些key都是什么意思?

而c++就比较熟悉:class 代表声明类,内部用public、private、protected区分访问权限。

三目运算符

return o1.id != o2.id ? (o1.id < o2.id) \
简洁,美观~

优雅-翩翩起舞的lambda

C++11引入的特性:Lambda表达式。

Lambda 表达式是C++11引入的特性,它允许你在需要的地方创建匿名的、一次性使用的函数。

例如,在sort函数中,可以直接调用:

1
sort(arr.begin(), arr.end(), [](int a, int b) { return a < b; });

创建treeMap,也可以直接调用:
1
TreeMap<Student, String> treeMap = new TreeMap<>((a, b) -> a.id - b.id);

Map vs TreeMap

1
std::map<Student, std::string, decltype(idAscendingComparator)*> studentMap(idAscendingComparator);
1
TreeMap<Student, String> treeMap = new TreeMap<>((a, b) -> (a.id - b.id));
特点 C++ Java
数据结构 std::map studentMap; TreeMap treeMap;
有序映射 有序映射 有序映射
自定义比较器
比较器设置方式 在创建实例后通过函数指针设置 在创建实例时通过 Lambda 表达式设置
Lambda 表达式 适用 适用
代码示例 std::map<Student, std::string> studentMap(idAscendingComparator); TreeMap<Student, String> treeMap = new TreeMap<>((a, b) -> (a.id - b.id));

When input data whose key has already been in it, both Map andTreeMap will not cover the previous data of the same key, and the new data will be discarded and unaccepted.

c++: auto vs it

1
2
3
4
5
6
7
for (const auto& entry : studentMap) {
const Student& s = entry.first;
std::string s1 = entry.second;

std::cout << s.name << "," << s.id << "," << s.age<<" ";
std::cout << s1<< std::endl;
}

看到这一行,我坐不住了,心想,auto有没有可以替代的方案?
于是,查阅资料,得到如下:

1
2
3
4
5
6
7
8
std::map<Student, std::string, decltype(idAscendingComparator)*>::const_iterator it;
for (it = studentMap.begin(); it != studentMap.end(); ++it) {
const Student& s = it->first;
std::string s1 = it->second;

std::cout << s.name << "," << s.id << "," << s.age << " ";
std::cout << s1 << std::endl;
}

也就是说,可以用it来代替auto,只不过要使用 std::map 的迭代器类型,但会使代码变得更冗长一些。

总的来说,使用 auto 是更为简洁和灵活的方式,因为它允许编译器根据上下文来推断类型。如果你手动指定类型,需要确保类型匹配并且正确。

JAVA的良好特性

默认传递引用

数组自带大小信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ArrayExample {

// 方法接受一维数组作为参数
static void processArray(int[] arr) {
for (int num : arr) {
System.out.print(num + " ");
}
System.out.println();
}

// 方法接受二维数组作为参数
static void process2DArray(int[][] arr) {
for (int[] row : arr) {
for (int num : row) {
System.out.print(num + " ");
}
System.out.println();
}
}

public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5};
int[][] arr2 = {{1, 2, 3}, {4, 5, 6}};

processArray(arr1);
process2DArray(arr2);
}
}

相比之下,c++的特性是最难绷的,起码上述Java的两个特性c++都没有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

// 函数接受一维数组作为参数
void processArray(int arr[], int size) {
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}

// 函数接受二维数组作为参数
void process2DArray(int arr[][3], int rows, int cols) {
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
std::cout << arr[i][j] << " ";
}
std::cout << std::endl;
}
}

int main() {
int arr1[] = {1, 2, 3, 4, 5};
int arr2[][3] = {{1, 2, 3}, {4, 5, 6}};

processArray(arr1, 5);
process2DArray(arr2, 2, 3);

return 0;
}

【完】