数据和代码加载到内存,内存(有编址)可以随机访问。如变量、常量显式使用其值,隐式使用其址(取址符“&”取址)。函数调用可以直接执行其内部语句,而函数名就是函数体包括的全部语句块的首地址。
指针类型可以显式引用地址,隐式使用其值(解引用)。
引用类型的底层也是指针类型,是一种限制使用的指针。
1 C/C++指针
C/C++指针显式引用地址,隐式使用其值,且使用方式不一致(前者使用符号“*”声明和定义后,直接使用指针名,后者增加符号“*”解引用。指针声明没有强制要求初始化,指针指向没有任何限制,可以指向任何内存块(可以存储在全局区、栈区或堆区),由此,指针也允许特别的算术运算(指针加、减一个常数)。
当一个指针指向一块动态内存时,需要手动释放,否则会引起内存泄露。当多个指针指向同一块动态内存时,当用一个指针释放后,另外的指针使用时就会存在问题,所以C++提出了智能指针这一语法机制。
指针使用限制最少,自由度最高,由此带来的影响就是不安全。指针随意指向,且指针做左值和指针解引用做左值很容易混淆使用,造成指针使用极易出错。
2 C++引用
C++引用的底层也是指针,相对于指针,引用的使用有了一定程度的限制。
首先C++引用是一个常量,声明的同时要求初始化,且不能初始化为空。C++引用隐含const,C\C++指针可以显式声明为const。
然后,引用之所以叫引用,其不需要解引用便可引用其值。
引用是类型安全的,而指针不是(引用比指针多了类型检查)。
在不同的上下文中,引用显式使用其地址(函数参数),或显式使用其值(取址如同变量一样,额外使用取值运算符&),由此,C++的引用也不存在指针的算术运算。
3 JAVA引用
Java一切皆类,一切对象皆引用。对象皆生存在堆上。怎样实现?引用本身在栈上定义,右值表达式用new返回(统一由new运算来实现,不能随所欲为地指向任意内存)。
(C++对象可以在堆上new出来,也可以在栈上定义,还可以在静态区或全局区定义。)
虽然在堆上产生的对象相对于栈上的对象来说,速度慢了一点,但Java的对象皆是new出来的,而new的返回值不同由程序员来控制的,所以避免了指针的随意指向,也因此Java的引用不存在算术运算。
Java引用虽然不支持算术运算,但对于数组、链表的遍历当然也有殊途同归的方法来支持的,如前者通过索引,后者通过指向下一个节点的引用来赋值传递。
虽然堆上的对象不存在栈上对象的自动内存管理(栈平衡机制),需要显式释放(如C++ new出来的对象需要显式delete),但Java存在GC功能(垃圾回收)功能,当然,这也存在一定性能损耗。
数组和对象在没有引用变量指向它的时候,才变为垃圾,不能被使用,但仍然占据内存空间,在随后的一个不确定时间才会被垃圾回收器释放。这也是Java比较占内存的原因。
C++引用具有常量性质,Java引用不一样,可以用做左值,因为有GC,所以不用担心所有权和垃圾回收的问题。
public class Main
{
public static void main(String[] args) {
Point a = new Point(1,2); //并不是将新生成的对象赋给a,a是对新生成对象的引用
Point b = new Point(3,4);
Point c = a; // c并不是一个新对象,它是对a对象的引用
a = b;
System.out.println(a.a); // 3
System.out.println(b.a); // 3
}
}
class Point{
int a;
int b;
Point(int a,int b){
this.a = a;
this.b = b;
}
}
Java的引用都是对堆内存的引用,这种统一性避免了指针的任意指向。
在C++中,如果类没有重载复制构造函数,那么有可能会出现浅度复制,而非深度复制,Java也有类似的问题。两者都要程序员采用深度复制的策略编写构造函数;C++需要重载“==”运算法,依次对两个变量比较它的数据成员,看是否都相等。在Java,Object对象定义了equals方法,这个方法是用来比较两个对象如果相等的,Object中的equals方法只是比较两个指针的值是否相等而已,如果需要根据所指向的对象内容进行比较,需要重写该类的equals方法。
Java传参时,当传递的是一个引用时,如果要交换两个引用引用的对象的值,需要逐成员操作:
public class Foo1 {
public static void main(String[] args) {
FakeInt a = new FakeInt(10);
FakeInt b = new FakeInt(20);
Foo1.exchange(a, b);
System.out.println("结果为:\n"+a.getValue());
System.out.println(b.getValue());
}
public static void exchange(FakeInt a, FakeInt b) {
FakeInt t = new FakeInt(a.getValue());
a.setValue(b.getValue());
b.setValue(t.getValue());
}
}
class FakeInt {
private int value;
public FakeInt(int i) {
this.value = i;
}
public void setValue(int i) {
this.value = i;
}
public int getValue() {
int v = this.value;
return v;
}
}
从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
3.1 强引用
只要引用存在,垃圾回收器永远不会回收
Object obj = new Object();
//可直接通过obj取得对应的对象 如obj.equels(new Object());
而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。
3.2 软引用
非必须引用,内存溢出之前进行回收,可以通过以下代码实现
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
sf.get();//有时候会返回null
这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。
3.3 弱引用
第二次垃圾回收时回收,可以通过如下代码实现
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。
3.4 虚引用
垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被称为幽灵引用。
虚引用主要用于检测对象是否已经从内存中删除。
4 总结一下
C/C++的指针可以按字节精准定位,切分和组合,自由切换取址和取值操作;
C++的引用强制要求初始化,传参时隐含址传递,其它情况下统一到自动解引用的取值操作;
Java的引用全部统一到对象,统一对象存储到堆,由new返回地址给引用,垃圾统一由GC系统回收。统一损失了自由,但获得了安全与操作的简洁性。
JAVA中的对象类型本质上应该叫做对象指针类型。那么传统的对象类型呢?在JAVA里已经不见了踪影!既然没有了传统的对象类型,那么 对象引用变量前面的*也就可以不要了。对象引用变量也就可以简称为对象变量了,反正也不会和其它概念混淆! 所有的对象变量都是引用,没有非引用的对象变量,想不用引用都不行,这就是指针的泛化和强化。 不叫指针了,就叫对象变量,这就是概念上的淡化和弱化。 没有了指针的加减运算,也没有了*、->等运算符,这是对指针的简单化。
函数指针一般作为函数的参数来使用,开发人员在使用是可以根据自己的需求传递自动以的函数来实现指定的功能,例如,在实现排序算法时,可以通过传递一个函数指针来决定两个数的先后顺序,从而最终决定算法是按照升序还是降序。
在Java中没有指针的概念,如何在Java语言中类似函数指针的功能呢?在Java8之后,当定义的接口中只有一个函数,可以用作函数指针。
interface TestFuncPoint {
String run(String str);
}
// 定义一个类,在runFunc中使用这个函数指针
class Demo {
void runFunc(TestFuncPoint funcPoint) {
System.out.println(funcPoint.run("Ted"));
}
}
public class Main {
public static void main(String[] args) {
Demo d = new Demo();
// 将函数的具体实现,传入runFunc中
d.runFunc(Main::funcImpl);
}
// 定义具体函数
public static String funcImpl(String str) {
return "hi " + str;
}
}
Implementing a functional interface with lambda:
interface SampleFunctionalInterface {
int modify(int x);
}
public class FunctionalInterface {
public static void main(String[] args)
{
SampleFunctionalInterface sfi = (x)->x+1;
int y = sfi.modify(1);
System.out.println(y);
}
}
Method References to static Methods:
// Demonstrate a method reference for a static method.
// A functional interface for string operations.
interface StringFunc {
String func(String n);
}
// This class defines a static method called strReverse().
class MyStringOps {
// A static method that reverses a string.
static String strReverse(String str) {
String result = "";
int i;
for(i = str.length()-1; i >= 0; i--)
result += str.charAt(i);
return result;
}
}
public class Main {
// This method has a functional interface as the type of
// its first parameter. Thus, it can be passed any instance
// of that interface, including a method reference.
static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args)
{
String inStr = "Lambdas add power to Java";
String outStr;
// Here, a method reference to strReverse is passed to stringOp().
outStr = stringOp(MyStringOps::strReverse, inStr);
System.out.println("Original string: " + inStr);
System.out.println("String reversed: " + outStr);
}
}
Method References to Instance Methods:
// Demonstrate a method reference to an instance method
// A functional interface for string operations.
interface StringFunc {
String func(String n);
}
// Now, this class defines an instance method called strReverse().
class MyStringOps {
String strReverse(String str) {
String result = "";
int i;
for(i = str.length()-1; i >= 0; i--)
result += str.charAt(i);
return result;
}
}
class Main {
// This method has a functional interface as the type of
// its first parameter. Thus, it can be passed any instance
// of that interface, including method references.
static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args)
{
String inStr = "Lambdas add power to Java";
String outStr;
// Create a MyStringOps object.
MyStringOps strOps = new MyStringOps( );
// Now, a method reference to the instance method strReverse
// is passed to stringOp().
outStr = stringOp(strOps::strReverse, inStr);
System.out.println("Original string: " + inStr);
System.out.println("String reversed: " + outStr);
}
}
Method References with Generics:
// Demonstrate a method reference to a generic method
// declared inside a non-generic class.
// A functional interface that operates on an array
// and a value, and returns an int result.
interface MyFunc<T> {
int func(T[] vals, T v);
}
// This class defines a method called countMatching() that
// returns the number of items in an array that are equal
// to a specified value. Notice that countMatching()
// is generic, but MyArrayOps is not.
class MyArrayOps {
static <T> int countMatching(T[] vals, T v) {
int count = 0;
for(int i=0; i < vals.length; i++)
if(vals[i] == v) count++;
return count;
}
}
class Main {
// This method has the MyFunc functional interface as the
// type of its first parameter. The other two parameters
// receive an array and a value, both of type T.
static <T> int myOp(MyFunc<T> f, T[] vals, T v) {
return f.func(vals, v);
}
public static void main(String[] args)
{
Integer[] vals = { 1, 2, 3, 4, 2, 3, 4, 4, 5 };
String[] strs = { "One", "Two", "Three", "Two" };
int count;
count = myOp(MyArrayOps::<Integer>countMatching, vals, 4);
System.out.println("vals contains " + count + " 4s");
count = myOp(MyArrayOps::<String>countMatching, strs, "Two");
System.out.println("strs contains " + count + " Twos");
}
}
C有许多复杂的声明,是因为C以函数为主要构件,函数之间的调用以参数(如数组指针)或返回值(如数组指针)为参数,由此形成了各种复杂的声明。而Java一切皆类与对象,以类和对象为主要构件,因此对一些复杂声明的需求减少了。
int ** pp; //在Java中,一个引用赋值给另一个引用;
int(*arrPtr)[5]; // C的数组指针,Java中所有数组都是引用,int[][]
Java的二维数组:
int[][] twoD0= new int[4][5];
int[][] twoD = new int[4][]; // 2d array like a reference array,
//a reference refer a 1d array
//like C++'s pointer arraay
twoD[0] = new int[5];
twoD[1] = new int[5];
twoD[2] = new int[5];
twoD[3] = new int[5];
也可以以二维数组做参数来处理二维数组(类内方法相成相互调用):
public class Main
{
public static void main(String[] args) {
int[][] arr= {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
test(arr,3,4);
}
public static void test(int arr[][],int r,int c){
for(int i=0;i<r;i++)
for(int j=0;j<c;j++){
System.out.print(arr[i][j]+" ");
}
}
}
-End-