[函数调用][高手回避]问题解答:Java真的能引用调用吗?

1 篇文章 0 订阅
1 篇文章 0 订阅

实践是检验真理的唯一标准——马克思

本人初学Java,想要观看深度剖析的可以点右上角的小红叉了。

有错误希望大家不吝赐教。

顺便,如果有谁硬要NC的比较某某语言孰好孰坏,那我也只好呵呵了,每门语言都有极好的优点和缺点,这是没有办法的。

学过C或C++的可以从头看到尾,如果没学过的可以直接跳到Java部分看重点。

C/C++部分

在C中,要在一个函数中改变一个变量的值,你似乎只有一个方法:

#include <stdio.h>
#include 
  
  
   
   
void func1(int *a)
{
	*a = 10;
}
void func2(int a)
{
	a = 20;
}
int main(int argc, char *argv[])
{
	int n;
	func1(&n);
	printf("%d\n", n); // 10
	func2(n);
	printf("%d\n", n); // 10
	return 0;
}

  
  

OK,你可以看到func1中,n完美的被改变了其值,虽然无论从调用函数上(要加一个&,地址运算符),还是在编写函数上(要加一个*,解除一层指针并引用该指针指向的变量)都很不令人愉快,但是这确实是一个有效的方法。实际运用上也是如此,很多C程序员都采用这种方法改变参数的值。

但是,它真的是简单的改变其参数的值吗?换句话说,为什么使用func2(int)就没法改变a的值了呢?

我们知道,无论是什么语言都必须翻译成机器语言才能运行,实际上任何操作都会变成地址的操作。

事实上,两者都使用了临时变量,那很多人就要问了,为什么func1能改变实参的值啊?不是说好了改变临时变量不能改变实参值吗?

OK,事实上func1(&n)是这么做的:

从系统栈上申请一个字的空间,将其声明为int *型(而不是int型),并将它初始化为&n(即创建一个变量为int *a = &n);我们对*a修改,实际上修改的是*&n的内容,所以当然能修改n的值咯!但是a依然是临时变量,如果你还不懂的话,你可以想想,你修改a的值(如a = NULL),n的地址会发生改变吗?答案是否定的,因为你修改的是临时变量a,而不是&n(当然&n也不允许修改)。

所以,大家也就能够理解为什么type-name function-name(type-name arr[])能够改变实参数组值了,虽然arr是临时变量,但是与实参数组指向同一片空间,你对这片空间进行操作不就修改了数组值吗?所以数组实际上也是地址调用,所以在函数声明中type *a和type a[]等价。

func2(n)就容易理解了,从系统栈上申请一个字的空间,将其声明为int型,作int a = n,对a修改是无法改变n的值的。

在C++中我们有两种选择,除了上面的地址调用,我们可以引用调用:

#include <iostream>
void func(int& a)
{
	a = 10;
}
int main(int argc, char* argv[])
{
	int n;
	func(n);
	std::cout << n << std::endl;
	return 0;
}

有了上面的说法,大家也就能够理解func(int&)的行为了:先从系统栈中申请一个int&,作int& a = n;由于a是引用变量,所以a和n共用同一片空间,所以改变a就一定能改变n。

Java部分

OK,C/C++由于保持了指针,所以大家能够轻松的理解,但是Java由于屏蔽了指针,导致初学者各种理解困难(事实上我个人认为Java不适合入门)

import java.io.*;
class MyInt
{
	int data;
	public MyInt(int x)
	{
		data = x;
	}
	public void prnln()
	{
		System.out.println(data);
	}
}
public class HelloWorldApp
{
	static void func1(int a)
	{
		a = 10;
	}
	static void func2(MyInt a)
	{
		a = new MyInt(20);
	}
	public static void main(String args[]) throws Exception
	{
		int n = 5;
		System.out.println(n);
		func1(n);
		System.out.println(n);
		MyInt Myn = new MyInt(15);
		Myn.prnln();
		func2(Myn);
		Myn.prnln();
	}
}

这上面两种方法都是失败的方法(注意这里指的是术语“方法”),至于类名大家无须在意,这是我入门Java时的一个Helloworld程序。

事实上,Java中所谓的“封装类实参按引用调用”,事实上只是内部有个指针操作,而很多初学者都不知道而已(这也是为什么没人和C/C++吵,但是在Java却吵得不可开交的原因)。

import java.io.*;
public class HelloWorldApp
{
	static void swap(char[] a, char[] b)
	{
		char[] t = {'\0'};
		for (int i = 0; i < a.length; ++i)
			t[i] = a[i];
		for (int i = 0; i < b.length; ++i)
			a[i] = b[i];
		for (int i = 0; i < t.length; ++i)
			b[i] = t[i];
	}
	public static void main(String args[]) throws Exception
	{
		char[] a_local = {'A'};
		char[] b_local = {'B'};
		swap(a_local, b_local);
		System.out.println(a_local);
		System.out.println(b_local);
	}
}

OK,交换成功。

事实上我们只是把a[0]和b[0]的地址传递了过去,只要我们不修改形参指针a和b的值就不会出问题,能够任意修改原实参的值。

所以很多时候,你明明那么想,程序却不按照你想的那么做,因为程序是程序,是给电脑运行的,你却是人。

这是从网上找到的一段代码:

import java.io.*;
import java.util.*;
public class HelloWorldApp
{
	static void func(StringBuffer str)
	{
		str.append(", World!");
	}
	public static void main(String[] args)
	{
		StringBuffer string = new StringBuffer("Hello");
		func(string);
		System.out.println(string); // Hello, World!
	}
}

这里却改变了实参string的值,为何?str确实还是一个临时对象,但是却与string指向同一片空间,在执行append()函数时对指向的那一片内存空间进行了修改,但是这种代码就没办法修改:

import java.io.*;
import java.util.*;
public class HelloWorldApp
{
	static void func(StringBuffer str)
	{
		str = new StringBuffer("Hello world!");
	}
	public static void main(String[] args)
	{
		StringBuffer string = new StringBuffer("Hello");
		func(string);
		System.out.println(string); // Hello
	}
}

因为当你把str重新构造为一个新对象时,它就指向了另一片空间,无论对它如何修改都无法改变实参的值。

再参考如下代码:

import java.io.*;
import java.util.*;
public class HelloWorldApp
{
	static void func(String str)
	{
		str += ", World!";
	}
	public static void main(String[] args)
	{
		String string = new String("Hello");
		func(string);
		System.out.println(string); // Hello
	}
}

这事实上告诉我们,String在进行字符串连接时会重新分配空间,所以str和string没有指向同一片空间。

传值还是传引用的问题,到此已经差不多搞懂了,但是我们仍然不能解决交换两变量值的问题。

OK,你其实可以这么做:

import java.io.*;
import java.util.*;
public class HelloWorldApp
{
	static void swap(int n1, int n2, int[] arr)
	{
		arr[0] = n2;
		arr[1] = n1;
	}
	public static void main(String[] args)
	{
		int[] temp = new int[2];
		int a = 2, b = 3;
		swap(a, b, temp);
		a = temp[0];
		b = temp[1];
		System.out.println(a + " " + b); //3 2
	}
}
注意输出这里是“ ”而不是' ',不然你将得到整型提升后的int型的37。

这似乎比我们直接在方法中写交换要麻烦多了,事实上我们把a和b一开始就放在数组中就可以了:

import java.io.*;
import java.util.*;
public class HelloWorldApp
{
	static void swap(int[] arr)
	{
		int t = arr[0];
		arr[0] = arr[1];
		arr[1] = t;
	}
	public static void main(String[] args)
	{
		int[] temp = {2, 3};
		swap(temp);
		System.out.println(temp[0] + " " + temp[1]); //3 2
	}
}
那是不是意味着,我们如果要用到变量,就要尽量使用数组呢?不!

通常Java开发并不需要多么完美的算法,只要有严谨的逻辑和整体规划就能做出不错的产品,那么我们完全可以这么做:

import java.io.*;
import java.util.*;
public class HelloWorldApp
{
	static int a, b;
	static void swap_the_a_b()
	{
		int t = a;
		a = b;
		b = t;
	}
	public static void main(String[] args)
	{
		a = 2;
		b = 3;
		swap_the_a_b();
		System.out.println(a + " " + b); //3 2
	}
}
为什么一定要交换两个局部变量的值呢?我们的目的仅仅是交换2和3的值,那么用数据成员完全可以做到。

至此全部结束,如有错误希望大家不吝赐教。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值