C++指针和引用

C++指针和引用

★ 指针与引用相同点
1. 都是地址的概念;
    指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。


★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终” ^_^
4. 引用没有 const,指针有 const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的
大小;typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。

7. 指针和引用的自增(++)运算意义不一样;


★ 联系
1. 引用在语言内部用指针实现。
2. 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。


几种指针类型

(1)T  *&p  的指针(语法上同时使用*&)

   在下列函数声明中,为什么要同时使用*和&符号?以及什么场合使用这种声明方式?   

void func1( MYCLASS *&pBuildingElement );

 论坛中经常有人问到这样的问题。本文试图通过一些实际的指针使用经验来解释这个问题。 
 仔细看一下这种声明方式,确实有点让人迷惑。在某种意义上,"*"和"&"是意思相对的两个东西,把它们放

   在一起有什么意义呢?。为了理解指针的这种做法,我们先复习一下C/C++编程中无所不在的指针概念。我

   们都知道MYCLASS*的意思:

   指向某个对象的指针,此对象的类型为MYCLASS。

void func1(MYCLASS *pMyClass);

    // 例如: 

MYCLASS* p = new MYCLASS; func1(p);

 上面这段代码的这种处理方法想必谁都用过,创建一个MYCLASS对象,然后将它传入func1函数。现在假设

    此函数要修改

pMyClass:void func1(MYCLASS *pMyClass) {     
    DoSomething(pMyClass);     
    pMyClass = // 其它对象的指针 
} 

  第二条语句在函数过程中只修改了pMyClass的值。并没有修改调用者的变量p的值。如果p指向某个位于

    地址0x008a00的对象,当func1返回时,它仍然指向这个特定的对象。(除非func1有bug将堆弄乱了,完

    全有这种可能。) 


 现在假设你想要在func1中修改p的值。这是你的权利。调用者传入一个指针,然后函数给这个指针赋值。

    以往一般都是传双指针,即指针的指针,例如 CMyClass**。

MYCLASS* p = NULL; 
func1(&p); void func1(MYCLASS** pMyClass)
{     
    *pMyClass = new MYCLASS;    
    …… 
}  

    调用func1之后,p指向新的对象。在COM编程中,你到处都会碰到这样的用法--例如在查询对象接口的

    QueryInterface

    函数中: 

interface ISomeInterface 
{     
    HRESULT QueryInterface(IID &iid, void** ppvObj);     
    …… 
}; 
LPSOMEINTERFACE p=NULL; 
pOb->QueryInterface(IID_SOMEINTERFACE, &p);  

 此处,p是SOMEINTERFACE类型的指针,所以&p便是指针的指针,在QueryInterface返回的时候,如果

   调用成功,则变量p包含一个指向新的接口的指针。 

 如果你理解指针的指针,那么你肯定就理解指针引用,因为它们完全是一回事。如果你象下面这样声明函数: 

void func1(MYCLASS *&pMyClass){    
    pMyClass = new MYCLASS;    
    …… 
}  

 其实,它和前面所讲得指针的指针例子是一码事,只是语法有所不同。传递的时候不用传p的地址&p,

    而是直接传p本身:   

MYCLASS* p = NULL; func1(p);  

  在调用之后,p指向一个新的对象。一般来讲,引用的原理或多或少就象一个指针,从语法上看它就是一个

    普通变量。所以只要你碰到*&,就应该想到**。也就是说这个函数修改或可能修改调用者的指针,而调用者

    象普通变量一样传递这个指针,不使用地址操作符&。 

    

 至于说什么场合要使用这种方法,我会说,极少。MFC在其集合类中用到了它--例如,CObList,它是一

    个CObjects指针列表。 

class CObList : public CObject {    
    // 获取/修改指定位置的元素    
    CObject*& GetAt(POSITION position);    
    CObject* GetAt(POSITION position) const; 
};  

 这里有两个GetAt函数,功能都是获取给定位置的元素。区别何在呢? 
 区别在于一个让你修改列表中的对象,另一个则不行。所以如果你写成下面这样: 

CObject* pObj = mylist.GetAt(pos);

 则pObj是列表中某个对象的指针,如果接着改变pObj的值: 

pObj = pSomeOtherObj;

 这并改变不了在位置pos处的对象地址,而仅仅是改变了变量pObj。但是,如果你写成下面这样: 

CObject*& rpObj = mylist.GetAt(pos);  

 现在,rpObj是引用一个列表中的对象的指针,所以当改变rpObj时,也会改变列表中位置pos处的对象地

   址--换句话说,替代了这个对象。这就是为什么CObList会有两个GetAt函数的缘故。一个可以修改指针的

   值,另一个则不能。注意我在此说的是指针,不是对象本身。这两个函数都可以修改对象,但只有*&版本

   可以替代对象。 


(2)数组名可以退化成指针

    数组名可以退化成指向数组首元素的地址的指针!

    数组名和指针的本质区别:指针是一个变量,有自己对应的存储空间,而数组名仅仅是一个符号,不是变

    量,因而没有自己对应的存储空间,“数组名永远不等于指针”。

int arrays[9]={1,2,3,4,5,6,7,8,9};
int* pArray;
pArray = &arrays;


(3)指向常量的指针
const T* p1; // 形式1
T const *p2; // 形式2
const int a = 5;
const int b = 6;
T1 = &a;
T1 = &b;    // ok!

  解释:指向常量的指针!  他们指的常量不能被修改,但指针地址可以指向其他地方!

      注意一个指向常量的指针可以指向一个非常量的对象!

const int* p1;
int b = 0;
p1 = &b; //ok!

 注意:指向非常量的指针不可以指向常量的对象!

int* p1;
const int a = 5;
p1 = &a; //error!


(4)指针常量
T *const p3;
int a = 5;
int b = 6;
p3  = &a;
*p3 = 6;     // ok!
p3  = &b;    // error!

  解释:一个指针的常量,指向非常量的值;这种指针不能修改指针地址,但他所指的值可以修改


(5)指向常量的指针常量

const T* const p4;
int a = 5;
int b = 6;
p4  = &a;
*p4 = 7;  // error!
p4  = &b; // error!

  解释:既不能修改指针地址,也不能修改指针所指的值


(6)指向类成员函数的指针并非是指针

 为了访问某成员函数,需要将对象的地址用作this指针的值,进行函数调用,他们通常实现一个小型结构,其中包

 含了虚函数表,一个偏移量,以及其他信息!


(7)指向类成员的指针并非是指针

  其实指向数据成员的指针是一个偏移量,为了访问某类的成员,需要将对象的地址加上这个偏移量相加.


(8)函数指针
void (*pf)(int);    //其中pf是一个指向返回类型为void,形参为一个int的函数的指针!
void print(int b){
    cout<<b<<endl;
}
pf = print;
int bb = 1234;
pf(bb);


(9)多级指针

 同(1)二级指针

//使用二级指针引用字符串。
#include <iostream>
#define SIZE 5
main()
{ 
    char *pc[]={" Beijing", "Shanghai", "Tianjing", "Guangzhou", "Chongqing" };
    char **p;
    int i;
    for ( i=0; i<SIZE;i++)
    { 
        p = pc+i;
        printf ("%s\n", *p);
    }
}

//用多级指针还可以引用整型二维数组
int a[3][3]={1, 2, 3, 4, 5, 6, 7, 8, 9};
int *b[]={a[0],a[1],a[2]};
int **p=b;
main( )
{ 
    int i,j;
    for ( i=0; i<3; i++ )
    for ( j=0; j<3; j++ )
    printf("%d,%d,%d\n", *(b[i]+j), *(*(p+i)+j), *(*(a+i)+j) );
}
运行结果:
1,1,1
2,2,2
3,3,3
4,4,4
5,5,5
6,6,6
7,7,7
8,8,8
9,9,9


注意: 一个指向“指向非常量的指针”的指针不能转换为一个指向“指向常量的指针”的指针。

            在类派生中,一个指向基类指针的指针并不是一个指向子类指针的指针。

(10)其他复杂类型

int *f1()                 //一个返回int*的函数
int (*f1)()               //一个函数指针
int (**ap2)[N];           //一个指针,它指向一个指针,后者则指向一个具有N个int元素的数组
int *(*ap2)[N];           //一个指针,指向一个具有N个int*元素的数组
int (**const fp2)()=0;    //一个常量指针,指向一个指向函数的指针!
int *(*ap2)()             //一个指针,指向一个返回为int*的函数!
int (*ap[N])()            //一个函数指针的数组

    遇到这类复杂声明,请用typedef!

指针运算

 1.两个同类型的指针可以进行减法运算,其结果是参与运算的两个指针之间的元素个数.如果第一个指针大于第

    二个指针,结果为正,否则为负,空指针或者指向同一个对象的指针,结果都为0.

 2.指针的递增或递减运算,等于是请求它指向的对象大小比例的下一个元素或上一个元素.


引用(reference)

一、引用的概念

    引用引入了对象的一个同义词。定义引用的表示方法与定义指针相似,只是用&代替了*。例如:

Point pt1(10,10);
Point &pt2=pt1;

    义了pt2为pt1的引用。通过这样的定义,pt1和pt2表示同一对象。需要特别强调的是引用并不产生对象的

    副本,仅仅是对象的同义词。因此,当下面的语句执行后:

pt1.offset(2,2);

    pt1和pt2都具有(12,12)的值。引用必须在定义时马上被初始化,因为它必须是某个东西的同义词。你不       能先定义一个引用后才初始化它。例如下面语句是非法的:

Point &pt3;
pt3=pt1;

    那么既然引用只是某个东西的同义词,它有什么用途呢?下面讨论引用的两个主要用途:作为函数参数以及从     函数中返回左值。

二、引用参数
1、传递可变参数

    传统的c中,函数在调用时参数是通过值来传递的,这就是说函数的参数不具备返回值的能力。所以在传统

    的c中,如果需要函数的参数具有返回值的能力,往往是通过指针来实现的。比如,实现两整数变量值交换

    的c程序如下:

void swapint(int *a,int *b)
{
    int temp;
    temp=*a;
    a=*b;
    *b=temp;
}

使用引用机制后,以上程序的c++版本为:

void swapint(int &a,int &b)
{
    int temp;
    temp=a;
    a=b;
    b=temp;
}

调用该函数的c++方法为:swapint(x,y); c++自动把x,y的地址作为参数传递给swapint函数。

2、给函数传递大型对象

    当大型对象被传递给函数时,使用引用参数可使参数传递效率得到提高,因为引用并不产生对象的副本,

    也就是参数传递时,对象无须复制。下面的例子定义了一个有限整数集合的类: 

const maxCard=100; 
Class Set 
{
    int elems[maxCard]; // 集和中的元素,maxCard 表示集合中元素个数的最大值。 
    int card; // 集合中元素的个数。 
    public:
    Set () {card=0;} //构造函数
    friend Set operator * (Set ,Set ) ; //重载运算符号*,用于计算集合的交集 用对象作为传值参数
 // friend Set operator * (Set & ,Set & ) 重载运算符号*,用于计算集合的交集 用对象的引用作为传值参数 
...
}

先考虑集合交集的实现

Set operator *( Set Set1,Set Set2)
{
    Set res;
    for(int i=0;i<Set1.card;++i)
        for(int j=0;j>Set2.card;++j)
            if(Set1.elems[i]==Set2.elems[j])
            {
                res.elems[res.card++]=Set1.elems[i];
                break;
            }
    return res;
}

    由于重载运算符不能对指针单独操作,我们必须把运算数声明为 Set 类型而不是 Set * 。每次使用*做交集运

    算时,整个集合都被复制,这样效率很低。我们可以用引用来避免这种情况。

Set operator *( Set &Set1,Set &Set2)
{ 
    Set res;
    for(int i=0;i<Set1.card;++i)
        for(int j=0;j>Set2.card;++j)
            if(Set1.elems[i]==Set2.elems[j])
            {
                res.elems[res.card++]=Set1.elems[i];
                break;
            }
    return res;
}

三、引用返回值

    如果一个函数返回了引用,那么该函数的调用也可以被赋值。这里有一函数,它拥有两个引用参数并返回

    一个双精度数的引用:

double &max(double &d1,double &d2)
{
    return d1>d2?d1:d2;
}

    由于max()函数返回一个对双精度数的引用,那么我们就可以用max() 来对其中较大的双精度数加1:
    max(x,y)+=1.0


C++引用与指针的比较 

    引用是C++中的概念,初学者容易把引用和指针混淆一起。 以下程序中,n是m的一个引用(reference),

    m是被引用物(referent)。 

int m; 
int &n = m;

    n相当于m的别名(绰号),对n的任何操作就是对m的操作。 所以n既不是m的拷贝,也不是指向m的指针,

    其实n就是m它自己。 
    引用的规则: 
    (1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。 
    (2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。 
    (3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。

 
    以下示例程序中,k被初始化为i的引用。 语句k = j并不能将k修改成为j的引用,只是把k的值改变成为6。 

    由于k是i的引用,所以i的值也变成了6。 

int i = 5; 
int j = 6; 
int &k = i; 
k = j; // k和i的值都变成了6;


    引用的主要功能是传递函数的参数和返回值。 C++语言中,函数的参数和返回值的传递方式有三种:

值传递、指针传递和引用传递
    (1)值传递

    由于Func1函数体内的x是外部变量n的一份拷贝,改变x的值不会影响n, 所以n的值仍然是0。 

void Func1(int x) 
{ 
    x = x + 10; 
} 
... 
int n = 0; 
Func1(n); 
cout << "n = " << n << endl; // n = 0

    (2)指针传递

    由于Func2函数体内的x是指向外部变量n的指针,改变该指针的内容将导致n的值改变,所以n的值成为10。 

void Func2(int *x) 
{ 
    (* x) = (* x) + 10; 
} 
... 
int n = 0; 
Func2(&n); 
cout << "n = " << n << endl; // n = 10

    (3)引用传递

    由于Func3函数体内的x是外部变量n的引用,x和n是同一个东西,改变x等于改变n,所以n的值成为10。 

void Func3(int &x) 
{ 
    x = x + 10; 
} 
... 
int n = 0; 
Func3(n); 
cout << "n = " << n << endl; // n = 10

    对比上述三个示例程序,会发现"引用传递"的性质象"指针传递",而书写方式象"值传递"。 实际上"引用"

    可以做的任何事情"指针"也都能够做,为什么还要"引用"这东西? 答案是"用适当的工具做恰如其分的工作"。 

    指针能够毫无约束地操作内存中的任何东西,尽管指针功能强大,但是非常危险。 如果的确只需要借用一下

    某个对象的"别名",那么就用"引用",而不要用"指针",以免发生意外。


转载于:https://my.oschina.net/Ccx371161810/blog/294299

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值