C++和Java的差异

多态差异

先说结论,C++的多态因为虚函数的缘故,他的表现和Java的略与不同,虚函数。当C++程序员进行Java开发的时候如若不能立即切换,可能会因为他们所谓的反直觉导致代码编写出现差异

源代码

C++ Code

#include <iostream>

class Father {
public:
    int money = 0;

    Father() {
        money = 5;
        print();
    }

    ~Father() {
    }

    virtual void print() {
        std::cout << "Father print" << "money: " << money << std::endl;
    }
};

class Son: public Father {
public:
    int money = 0;

    Son() {
        money = 10;
        print();
    }

    ~Son() {
    }

    void print() override {
        std::cout << "Son print" << "money: " << money << std::endl;
    }
};

int main() {
    Father* pSon = new Son();
    std::cout << "pSon->money: " << pSon->money << std::endl;
    return 0;
}

Java Code

public class Main {
    static class Father{
        public int money = 1;

        public Father(){
            money = 2;
            print();
        }

        public void print() {
            System.out.println("I am Father,i have $" + money);
        }
    }

    static class Son extends Father {
        public int money = 3;

        public Son() {
            money = 4;
            print();
        }

        public void print(){
            System.out.println("I am Son and i have $"+ money);
        }
    }

    public static void main(String[] args){
        Father gay = new Son();
        System.out.println("This gay has $"+ gay.money);
    }
}

差异

// C++
Father print money: 5
Son print money: 10
pSon->money: 5
// Java
I am Son and i have $0
I am Son and i have $4
This gay has $2

从表现差异来看,Java的表现其实更加反直觉,但是按照多态的思路去思考,其实倒也没什么,因为在Java中print函数是多态的,在Father里面调用print函数,实际上调用的是SonprintSon因为在Father调用print之前Sonmoney还没有开始初始化,所以说是0

Son的初始化流程

  1. Son执行构造函数,Son调用Father的构造函数

  2. Father初始化自己的money字段为21会被覆盖,无视)

  3. Father调用print函数,因为实际上是Son对象,所以说调用的是Sonprint函数

  4. Sonprint函数打印Son类内部自己的money字段

结论

1. 方法多态性(方法分派)

  • Java:所有非静态方法默认支持多态,除非它们被声明为 final。Java 使用动态绑定来实现方法的多态。

  • C++:只有被声明为虚(virtual)的方法才支持多态。非虚方法和静态方法不支持多态,调用基于对象的静态类型。

2. 字段多态性

  • Java:与 C++ 类似,Java 的字段不参与多态。字段的访问总是基于引用的静态类型,而不是运行时类型。

  • C++:同上,字段访问是静态的。

3. 编译时多态

  • Java:不支持像模板这样的编译时多态。Java 使用泛型来实现类似功能,但是泛型在 Java 中是通过类型擦除实现的,这意味着在运行时泛型类实例不保留关于其泛型类型参数的信息。

  • C++:支持通过模板进行编译时多态,允许更灵活和高效的代码生成。

多态在Java中有很多种,其中比较常见的为方法重载(属于Ad hoc polymorphsim), 泛型编程(属于Parametric polymorphsim), Subtyping

引用传递? or 值传递?

网上大部分教程总说Java的对象在方法间传递其实传递的是引用,其实这有些出入。其实准确来说按照斯坦福大学的说法传递是一个Java对象的指针(实际上总不是如此,但就不涉及jvm虚拟机的设计而言,说传递的是指针也没什么问题)。

至于为什么不能说他是一个引用...其实主要是和C++引用的概念有所冲突

class Person {
  public String name;
  public int age;
}

class Main {
  public static void main(String[] args) {
    // 烦死了,这个Java的死出开头
    var p = new Person();
    p.name = "fuqiuluo";
    p.age = 16;
    onlyAdult(p);
    if(p == null) {
      System.out.println("未成年人禁止入内!");
      System.exit(1);
    }
    System.out.println("欢迎您,老毕登!");
    // doSomething for adult?
  }

  public static void onlyAdult(Person p) {
    if(p.age < 18) 
      p = null;
  }
}
#include <string>
#include <iostream>

class Person {
public:
  std::string name;
  int age;
};

void onlyAdult(Person* &p);

int main() {
  Person* p;
  p = new Person;
  p->name = "whitechi73";
  p->age = 17;
  onlyAdult(p);
  if(p == NULL) {
    std::cout << "未成年人爬一边去!" << "\n";
    exit(1);
  }
  std::cout << "老毕登,逛窑子呢?" << "\n";
  // doSomething
}

void onlyAdult(Person* &p) {
  if(p->age < 18) {
    p = NULL;
  }
}

上面的代码,在C++可以阻止未成年人进入...在Java反而行不通...说白了就是

Java创建的对象在内存中,传递对象实际上传递的是指向这块内存地址(一份复制的地址),而不是把这块内存传来传去或者拷贝一份新的。

Java只有值传递,没有引用传递。

值传递与引用传递的概念

在编程语言中,函数或方法调用时参数的传递方式主要有两种:值传递(Pass By Value)和引用传递(Pass By Reference)。理解这两种传递方式对于编写高效、可靠的代码至关重要。

值传递是指在调用函数时,实际参数(调用者提供的参数)的值被复制给形式参数(函数定义中的参数)。在函数体内,形式参数的任何改变都不会影响到实际参数。这种方式通常用于基本数据类型,如整数或浮点数。例如,在Java中,当你传递一个整数给一个方法时,你实际上是传递了那个整数值的副本。

void exampleMethod(int num) {
  num = 100; // 只改变了副本的值,原始变量不受影响
}

引用传递则是指传递的不是实际数据值,而是数据的地址或引用。因此,如果在函数体内部改变了引用指向的数据,那么这些改变也会反映在原始数据上。这种方式通常用于对象或复杂的数据结构。例如,在Java中,当你传递一个对象给一个方法时,传递的实际上是对象引用的副本,而不是对象本身。

void exampleMethod(MyObject obj) {
  obj.setAttribute("new value"); // 改变了对象的属性,原始对象也会受到影响
}

在Java中,所有的对象都是通过引用传递的,而基本类型则是通过值传递。这意味着,如果你传递一个对象给一个方法,你可以在该方法内部改变对象的状态,而这些改变会反映在原始对象上。但如果你传递一个基本类型的值,如一个整数或字符,方法内部的改变不会影响到调用者的原始值。

值得注意的是,有些语言如C++提供了更多的参数传递选项,包括通过值传递、通过引用传递和通过指针传递。每种方式都有其特定的用途和行为,选择合适的传递方式对于程序的正确性和性能都至关重要。

总的来说,理解值传递和引用传递的区别,可以帮助开发者更好地控制函数或方法中参数的行为,避免不必要的错误,并优化程序性能。