综述:
在Java中,类实例声明和构造是分开。"T a;"是声明,而"a=new T();"才是构造。引用声明和C++相同。但是Java的机制与C++不同,Java中的引用也叫句柄,或者说句柄才是其真实名称。类声明的都是一个句柄,调用构造函数才会使得句柄指向类实例。因此Java中没有类似于C++中的复制函数,因为Java的复制都是直接复制句柄内容。例如"T b=a;"只不过是将a的句柄复制(赋值)给了b,从而b也指向a指向的类实例。可以看出Java与C++在此处的不同,Java依然只有一个实例,而C++则存在了两个实例。
所以在函数中,Java的形参都是入参的句柄复制,并且是浅复制(只复制该句柄,而不复制句柄指向的下一层句柄)。因此在函数中,直接修改形参是不能改变入参的。但是如果修改形参指向的对象的下一层句柄则会修改入参。因此在Java中不存在像C/C++中一样的Swap函数。函数的返回值,也是句柄复制。如果在函数中构造一个局部变量类实例,那么是可以返回到外部的,当然那个局部变量的句柄是不存在了。Java中要复制对象,需要重载clone函数,并且要分清是浅复制还是深复制(完全构造一个新对象,两者的内部数据和实例不相同)。
c++ 与java引用具体比较:
c++中一个引用指向的地址不会改变,改变的是指向地址的内容,然而java中引用指向的地址在变!!
如果非要对比着看,那么Java中的“引用”倒是和C/C++的指针更像一些,和C++的“引用”很不一样。
java去除指针概念,就用引用罗...
你看 java:
A a = new A(1);
A b = new A(2);
b = a;
没有问题,a 和 b引用同一个对象A(1),原来的A(2)成为没有被引用的对象。 垃圾回收机制会在之后的某个时刻把A(1)干掉。
而C++则不然。C++的引用就语义上说是“别名”【本质是个const指针,又叫指针常量】,而并不是指针的另一种用法:
A a = A(1);
A b = A(2);
A& c = b; //c 是 b的别名
c = a; //并不是 c 引用 a,而是拷贝操作 c.operator= ( a )
就语言机制来说,java的引用是用来管理和命名对象;
而,C++的引用机制是很纯粹的,就是别名而已。
每种语言的特性都是整体的有机部分。 `
我们知道, java的引用机制是一个很复杂的机制。他必须区分“基本对象”和“复合对象”,你可以想象一下,如果其中没有基本对象,那么我们如何完成对象的复制? 唯一的解决方案是提供两个等于号,或者一律用构造函数.... 但是综合来看,他和垃圾回收形成了相当完美的组合方案。
而C++ 的引用机制为运算符重载提供了大幅度的支持。C++ 的引用是用类“模拟”基本对象的根本要求。 如果C++使用java那种引用,那么原本漂亮的 operator[]、 proxy class 等就很难实现了。 更进一步, C++ 的运算符重载对 C++ 的模版机制提供了强力的支持
在c++中,引用只是对于一个变量起的别名,一旦定义就无法修改,即无法再指向其他变量,如程序中,对于a的引用的任何操作都等同于对于a的操作。
java定义的引用并不是这样。在java中,引用相当与指针,它与c中的指针主要有两个区别:一是引用不能进行地址操作,如数组的加一 操作,相当于引用只是只是指向数据的一个副本,而不是数据本身,这样就避免了由于对于地址的误操作而改变其他变量的值,甚至危害到系统的安全。二是 java中的引用只能指向对象,他的引用是在实例化对象时系统直接生成的,因此对于普通数据类型是不能进行引用定义的,如果要对普通数据类型进行函数调用 时的地址传递(即java中的引用传递),必须把数据封装到类中。
java的这种特性使得在java的函数或类的参数传递时可以实现与c中指针相同的功能。
具体应用:
指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法。
1.指针和引用的定义和性质区别:
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。
而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。
(2)可以有const指针,也可以有有const引用;
(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(6)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(7)指针和引用的自增(++)运算意义不一样;
2.指针和引用作为函数参数进行传递时的区别。
(1)指针作为参数进行传递:
#include<iostream> using namespace std; void swap(int *a,int *b) { int temp=*a; *a=*b; *b=temp; } int main(void) { int a=1,b=2; swap(&a,&b); cout<<a<<" "<<b<<endl; system("pause"); return 0; }
结果为2 1;
用指针传递参数,可以实现对实参进行改变的目的,是因为传递过来的是实参的地址,因此使用*a实际上是取存储实参的内存单元里的数据,即是对实参进行改变,因此可以达到目的。
再看一个程序;
#include<iostream> using namespace std; void test(int *p) { int a=1; p=&a; cout<<p<<" "<<*p<<endl; } int main(void) { int *p=NULL; test(p); if(p==NULL) cout<<"指针p为NULL"<<endl; system("pause"); return 0; }
运行结果为:
0x22ff44 1
指针p为NULL
大家可能会感到奇怪,怎么回事,不是传递的是地址么,怎么p回事NULL?事实上,在main函数中声明了一个指针p,并赋值为NULL,当调用test函数时,事实上传递的也是地址,只不过传递的是指地址。也就是说将指针作为参数进行传递时,事实上也是值传递,只不过传递的是地址。当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,即上面程序main函数中的p何test函数中使用的p不是同一个变量,存储2个变量p的单元也不相同(只是2个p指向同一个存储单元),那么在test函数中对p进行修改,并不会影响到main函数中的p的值。
如果要想达到也同时修改的目的的话,就得使用引用了。
2.将引用作为函数的参数进行传递。
在讲引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。
看下面这个程序:
#include<iostream> using namespace std; void test(int &a) { cout<<&a<<" "<<a<<endl; } int main(void) { int a=1; cout<<&a<<" "<<a<<endl; test(a); system("pause"); return 0; }
输出结果为: 0x22ff44 1
0x22ff44 1
再看下这个程序:
这足以说明用引用进行参数传递时,事实上传递的是实参本身,而不是拷贝。
所以在上述要达到同时修改指针的目的的话,就得使用引用了。
#include<iostream> using namespace std; void test(int *&p) { int a=1; p=&a; cout<<p<<" "<<*p<<endl; } int main(void) { int *p=NULL; test(p); if(p!=NULL) cout<<"指针p不为NULL"<<endl; system("pause"); return 0; }
输出结果为:0x22ff44 1
指针p不为NULL
C++ 引用的本质?深入分析C++引用:
引言
我选择写 C++ 中的引用是因为我感觉大多数人误解了引用。而我之所以有这个感受是因为我主持过很多 C++ 的面试,并且我很少从面试者中得到关于 C++ 引用的正确答案。
那么 c++ 中引用到底意味这什么呢?通常一个引用让人想到是一个引用的变量的别名,而我讨厌将 c++ 中引用定义为变量的别名。这篇文章中,我将尽量解释清楚, c++ 中根本就没有什么叫做别名的东东。
背景
在 c/c++ 中,访问一个变量只能通过两种方式被访问,传递,或者查询。这两种方式是:
1. 通过值 访问 / 传递变量
2. 通过地址 访问 / 传递变量 – 这种方法就是指针
除此之外没有第三种访问和传递变量值的方法。引用变量也就是个指针变量,它也拥有内存空间。最关键的是引用是一种会被编译器自动解引用的指针。很难相信么?让我们来看看吧。。。
下面是一段使用引用的简单 c++ 代码
- #include <iostream.h>
- int main()
- {
- int i = 10; // A simple integer variable
- int &j = i; // A Reference to the variable i
- j++; // Incrementing j will increment both i and j.
- // check by printing values of i and j
- cout<< i << j <<endl; // should print 11 11
- // Now try to print the address of both variables i and j
- cout<< &i << &j <<endl;
- // surprisingly both print the same address and make us feel that they are
- // alias to the same memory location.
- // In example below we will see what is the reality
- return 0;
- }
引用其实就是 c++ 中的指针常量。表达式 int &i = j; 将会被编译器转化成 int *const i = &j; 而引用之所以要初始化是因为 const 类型变量必须初始化,这个指针也必须有所指。下面我们再次聚焦到上面这段代码,并使用编译器的那套语法将引用替换掉。
- #include <iostream.h>
- int main()
- {
- int i = 10; // A simple integer variable
- int *const j = &i; // A Reference to the variable i
- (*j)++; // Incrementing j. Since reference variables are
- // automatically dereferenced by compiler
- // check by printing values of i and j
- cout<< i << *j <<endl; // should print 11 11
- // A * is appended before j because it used to be reference variable
- // and it should get automatically dereferenced.
- return 0;
- }
读者一定很奇怪为什么我上面这段代码会跳过打印地址这步。这里需要一些解释。因为引用变量时(使用变量时)会被编译器自动解引用的,那么一个诸如 cout << &j << endl; 的语句,编译器就会将其转化成语句 cout << &*j << endl; 现在 &* 会相互抵消,这句话变的毫无意义,而 cout 打印的 j 值就是 i 的地址,因为其定义语句为 int *const j = &i;
所以语句 cout << &i << &j << endl; 变成了 cout << &i << &*j << endl; 这两种情况都是打印输出 i 的地址。这就是当我们打印普通变量和引用变量的时候会输出相同地址的原因。
下面给出一段复杂一些的代码,来看看引用在级联 (cascading) 中是如何运作的。
- #include <iostream.h>
- int main()
- {
- int i = 10; // A Simple Integer variable
- int &j = i; // A Reference to the variable
- // Now we can also create a reference to reference variable.
- int &k = j; // A reference to a reference variable
- // Similarly we can also create another reference to the reference variable k
- int &l = k; // A reference to a reference to a reference variable.
- // Now if we increment any one of them the effect will be visible on all the
- // variables.
- // First print original values
- // The print should be 10,10,10,10
- cout<< i << "," << j << "," << k << "," << l <<endl;
- // increment variable j
- j++;
- // The print should be 11,11,11,11
- cout<< i << "," << j << "," << k << "," << l <<endl;
- // increment variable k
- k++;
- // The print should be 12,12,12,12
- cout<< i << "," << j << "," << k << "," << l <<endl;
- // increment variable l
- l++;
- // The print should be 13,13,13,13
- cout<< i << "," << j << "," << k << "," << l <<endl;
- return 0;
- }
下面这段代码是将上面代码中的引用替换之后代码,也就是说明我们不依赖编译器的自动替换功能,手动进行替换也能达到相同的目标。
- #include <iostream.h>
- int main()
- {
- int i = 10; // A Simple Integer variable
- int *const j = &i; // A Reference to the variable
- // The variable j will hold the address of i
- // Now we can also create a reference to reference variable.
- int *const k = &*j; // A reference to a reference variable
- // The variable k will also hold the address of i because j
- // is a reference variable and
- // it gets auto dereferenced. After & and * cancels each other
- // k will hold the value of
- // j which it nothing but address of i
- // Similarly we can also create another reference to the reference variable k
- int *const l = &*k; // A reference to a reference to a reference variable.
- // The variable l will also hold address of i because k holds address of i after
- // & and * cancels each other.
- // so we have seen that all the reference variable will actually holds the same
- // variable address.
- // Now if we increment any one of them the effect will be visible on all the
- // variables.
- // First print original values. The reference variables will have * prefixed because
- // these variables gets automatically dereferenced.
- // The print should be 10,10,10,10
- cout<< i << "," << *j << "," << *k << "," << *l <<endl;
- // increment variable j
- (*j)++;
- // The print should be 11,11,11,11
- cout<< i << "," << *j << "," << *k << "," << *l <<endl;
- // increment variable k
- (*k)++;
- // The print should be 12,12,12,12
- cout<< i << "," << *j << "," << *k << "," << *l <<endl;
- // increment variable l
- (*l)++;
- // The print should be 13,13,13,13
- cout << i << "," << *j << "," << *k << "," << *l <<endl;
- return 0;
- }
我们通过下面代码可以证明 c++ 的引用不是神马别名,它也会占用内存空间的。
- #include <iostream.h>
- class Test
- {
- int &i; // int *const i;
- int &j; // int *const j;
- int &k; // int *const k;
- };
- int main()
- {
- // This will print 12 i.e. size of 3 pointers
- cout<< "size of class Test = " << sizeof(class Test) <<endl;
- return 0;
- }
结论
我希望这篇文章能把 c++ 引用的所有东东都解释清楚,然而我要指出的是 c++ 标准并没有解释编译器如何实现引用的行为。所以实现取决于编译器,而大多数情况下就是将其实现为一个 const 指针。
引用支持 c++ 虚函数机制的代码
- #include <iostream.h>
- class A
- {
- public:
- virtual void print() { cout<<"A.."<<endl; }
- };
- class B : public A
- {
- public:
- virtual void print() { cout<<"B.."<<endl; }
- };
- class C : public B
- {
- public:
- virtual void print() { cout<<"C.."<<endl; }
- };
- int main()
- {
- C c1;
- A &a1 = c1;
- a1.print(); // prints C
- A a2 = c1;
- a2.print(); // prints A
- return 0;
- }
上述代码使用引用支持虚函数机制。如果引用仅仅是一个别名,那如何实现虚函数机制,而虚函数机制所需要的动态信息只能通过指针才能实现,所以更加说明引用其实就是一个 const 指针。
补充:const 指针(指针常量)与指向const的指针(常量指针)
当使用带有const的指针时其实有两种意思。一种指的是你不能修改指针本身的内容,另一种指的是你不能修改指针指向的内容。听起来有点混淆一会放个例子上来就明白了。
先说指向const的指针,它的意思是指针指向的内容是不能被修改的。它有两种写法。
const int* p; (推荐)
int const* p;
第一种可以理解为,p是一个指针,它指向的内容是const int 类型。p本身不用初始化它可以指向任何标示符,但它指向的内容是不能被改变的。
第二种很容易被理解成是p是一个指向int的const指针(指针本身不能被修改),但这样理解是错误的,它也是表示的是指向const的指针(指针指向的内容是不能被修改的),它跟第一种表达的是一个意思。为了避免混淆推荐大家用第一种。
再说const指针,它的意思是指针本身的值是不能被修改的。它只有一种写法
int* const p=一个地址; (因为指针本身的值是不能被修改的所以它必须被初始化)
这种形式可以被理解为,p是一个指针,这个指针是指向int 的const指针。它指向的值是可以被改变的如*p=3;
还有一种情况是这个指针本身和它指向的内容都是不能被改变的,请往下看。
const int* const p=一个地址;
int const* const p=一个地址;
看了上面的内容是不是有点晕,没关系,你不用去背它,用的多了就知道了,还有个技巧,通过上面的观察我们不难总结出一点规律,是什么呢?也许你已经看出来了,什么!竟然没看也来,那只好还得听我唠叨了。这个规律就是: 指向const的指针(指针指向的内容不能被修改)const关健字总是出现在*的左边而const指针(指针本身不能被修改)const关健字总是出现在*的右边,那不用说两个const中间加个*肯定是指针本身和它指向的内容都是不能被改变的。有了这个规则是不是就好记多了。
什么还是晕,那就看下面的程序,你把它编译一下看看错误提示就明白了。
2
3 using namespace std;
4
5 int main( int argc, char * argv[])
6 {
7 int a = 3 ;
8 int b;
9
10 /* 定义指向const的指针(指针指向的内容不能被修改) */
11 const int * p1;
12 int const * p2;
13
14 /* 定义const指针(由于指针本身的值不能改变所以必须得初始化) */
15 int * const p3 =& a;
16
17 /* 指针本身和它指向的内容都是不能被改变的所以也得初始化 */
18 const int * const p4 =& a;
19 int const * const p5 =& b;
20
21 p1 = p2 =& a; // 正确
22 * p1 =* p2 = 8 ; // 不正确(指针指向的内容不能被修改)
23
24 * p3 = 5 ; // 正确
25 p3 = p1; // 不正确(指针本身的值不能改变)
26
27 p4 = p5; // 不正确 (指针本身和它指向的内容都是不能被改变)
28 * p4 =* p5 = 4 ; // 不正确(指针本身和它指向的内容都是不能被改变)
29
30 return 0 ;
31 }