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
函数,实际上调用的是Son的print
,Son因为在Father调用print
之前Son的money
还没有开始初始化,所以说是0
。
Son的初始化流程
Son执行构造函数,Son调用Father的构造函数
Father初始化自己的money字段为
2
(1
会被覆盖,无视)Father调用
print
函数,因为实际上是Son对象,所以说调用的是Son的print
函数Son的
print
函数打印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++提供了更多的参数传递选项,包括通过值传递、通过引用传递和通过指针传递。每种方式都有其特定的用途和行为,选择合适的传递方式对于程序的正确性和性能都至关重要。
总的来说,理解值传递和引用传递的区别,可以帮助开发者更好地控制函数或方法中参数的行为,避免不必要的错误,并优化程序性能。