踩坑记:从C++变量到Java引用

本文约 1300 字,阅读需 3 分钟。

踩坑记:从C++变量到Java引用

本文介绍了Java和C++中变量名(对象类型)的细微差别。

问题引入

问题的产生自另一个题目:

请实现一种数据结构SetOfStacks,由多个栈组成,其中每个栈的大小为size,当前一个栈填满时,新建一个栈。该数据结构应支持与普通栈相同的push和pop操作。给定一个操作序列int[][2] ope(C++为vector<vector>),每个操作的第一个数代表操作类型,若为1,则为push操作,后一个数为应push的数字;若为2,则为pop操作,后一个数无意义。请返回一个int[],为完成所有操作后的SetOfStacks,顺序应为从下到上,默认初始的SetOfStacks为空。保证数据合法。

在参考C++代码写出来之后发现输出总是异常。代码如下

import java.util.*;
public class SetOfStacks {
	public ArrayList<ArrayList<Integer>> setOfStacks(int[][] ope, int size) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
        ArrayList<Integer> temp = new ArrayList<Integer>();
        for (int i = 0; i < ope.length; i++) {
            if (ope[i][0] == 1) {
                if (temp.size() == size) {
                    res.add(temp);
//                  temp = new ArrayList<Integer>();
                    temp.clear();
                    temp.add(ope[i][1]);
                } else {
                    temp.add(ope[i][1]);
                }
            } else {
                if (temp.size() != 0) {
                    temp.remove(temp.size()-1);
                } else if (res.size() != 0) {
                    temp = res.get(res.size()-1);
                    temp.remove(temp.size()-1);
                    res.remove(res.size()-1);
                }
            }
        }
        if (temp.size() != 0) {
            res.add(temp);
        }
        return res;
    }
}

被注释行必须加上,否则输出结果异常!

问题分析

为突出以上代码存在的问题,简化以下两份代码: C++:

#include  <bits/stdc++.h>
using namespace std;
int main(int argc, char const* argv[]) {
    vector< vector<string> > list;
    vector<string> temp;
    string str= "abc";
    temp.push_back(str);
    list.push_back(temp);
    temp.clear();
    str = "def";
    temp.push_back(str);
    list.push_back(temp);
    for (vector<string> strArr : list) {
        for (string s : strArr) {
            cout<<s<<endl;
        }
    }
    return 0;
}

Java:

import java.util.*;
public class Main {
   public static void main(String[] args) {
        ArrayList<ArrayList<String>> list = new ArrayList<>();
        ArrayList<String> temp = new ArrayList<>();
        String str = "abc";
        temp.add(str);
        list.add(temp);
        temp.clear();
        str = "def";
        temp.add(str);
        list.add(temp);
        for (ArrayList<String> strArr : list) {
            for (String s : strArr) {
                System.out.println(s);
            }
        }
    }
}

输出结果如下:

zhaoyu@Dell ~/codelab/acmcoder $ g++ -std=c++11 exam.cpp 
zhaoyu@Dell ~/codelab/acmcoder $ ./a.out 
abc
def
zhaoyu@Dell ~/codelab/acmcoder $ javac Main.java 
zhaoyu@Dell ~/codelab/acmcoder $ java Main 
def
def

可以看到,java代码把原来的数据也改变了。这是因为Java的变量名(基本类型除外)是基于引用,其实就是指针。而C++中必须显示声明为指针,否则变量名存储的就是一份副本。 以下代码意在说明相同问题,更加简短: C++:

#include  <bits/stdc++.h>
using namespace std;
int main(int argc, char const* argv[]) {
    string sb1 = "abc";
    string sb2 = sb1;
    cout<<sb2<<endl;
    sb1 += "eee";
    cout<<sb2<<endl;
    return 0;
}

Java:

import java.util.*;
public class Main {
   public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder("abc");
        StringBuilder sb2 = sb1;
        System.out.println(sb2.toString());
        sb1.append("eee");
        System.out.println(sb2.toString());
    }
}

输出如下:

zhaoyu@Dell ~/codelab/acmcoder $ g++ -std=c++11 exam.cpp 
zhaoyu@Dell ~/codelab/acmcoder $ ./a.out 
abc
abc
zhaoyu@Dell ~/codelab/acmcoder $ javac Main.java 
zhaoyu@Dell ~/codelab/acmcoder $ java Main 
abc
abceee

其实C++中这种赋值会调用复制构造函数,创建一个新的副本,而Java就是将两个引用(指针)指向了相同位置,通过其中一个改变指向内容,另一个引用获得的结果也会改变! 具体参考以下代码: C++:

#include  <bits/stdc++.h>
using namespace std;
class A {
    int val;
public:
    A() {val = 1;cout<<"default"<<endl;}
    A(A& copy) {val = 1;cout<<"copy"<<endl;}
    void add(void) {val++; cout<<val<<endl;}
};
int main(int argc, char const* argv[]) {
    A a;
    A b = a;
    a.add();
    b.add();
    return 0;
}

Java:

import java.util.*;
import java.io.*;
public class Main {
    public static void main(String[] args) {
        A a = new A();
        A b = a;
        a.add();
        b.add();
    }
}
class A {
    private int val;
    public A(){
        System.out.println("create");
        val = 1;
    }
    public void add() {
        val++;
        System.out.println(val);
    }
}

输出为:

zhaoyu@Dell ~/codelab/acmcoder $ ./a.out 
default
copy
2
2
zhaoyu@Dell ~/codelab/acmcoder $ java Main 
create
2
3

一点总结

  • 这本来是一个每本教材都会指出的差别,但是光教材看确实效果有限。考试考到这样的问题其实很容易推断出考察的知识点,但是编程时注意点往往在算法等上面,往往容易忽视细节(其实就是基础还不够扎实)。还是要多写,才能加深理解、积累经验。
  • Java没形成新体系,C++没形成体系。所以对Java和C++之间的差别的掌握和理解(从语法到设计理念)很肤浅。
  • 遇到问题,单步调试,对比实际输出和期望输出可能比冥思苦想效果好。
  • 对于自己的猜想,查阅权威资料(而不是百度)和设计程序验证是最好的两种验证办法。
总阅读量次。