C++之指针和引用详解

指针和引用的区别:

      1.指针是一个实体,而引用仅是个别名;

      2.引用必须被初始化,指针不必;

      3.引用只能在定义时被初始化一次,之后不可变;指针可以改变所指的对象;

      4.可以有const指针(如char * const ptr = &value),但是没有const引用(其实引用也算是有的,引用只能在定义时被初始化一次,之后不可变。);

      5.不存在指向空值的引用,但是存在指向空值的指针,即引用不能为空,指针可以为空;

      6.“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;

      7.指针和引用的自增(++)运算意义不一样; 指针自增指向下一个地址,而引用是对变量本身的值的增加。

      8.程序为指针变量分配内存区域,而引用不需要分配内存区域;

      9.指针可以有多级,但是引用只能是一级,例如int **p是合法的,而 int &&a是不合法的; 

      10.指针和引用作为函数参数进行传递时也不同。用指针传递参数,可以实现对实参进行改变的目的;在将引用作为函数参数进行传递时,实质上传递的是实参本身,而不是实参的一个拷贝,因此对形参的修改其实是对实参的修改。

       总而言之指针与引用的一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。

指针和引用的相同点:

      两者都是地址的概念,指针指向一块儿内存,其内容为所指内存的地址;引用是某块儿内存的别名。

 区分用指针和引用的不同情况:

使用引用的情况:

        1.常引用,用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性,函数体不能对引用型参数修改目标变量。声明方式:const  类型标识符  &引用名 = 目标变量名

        2.作为函数的返回值,当类A的数据成员包含B类的对象,可以在类A中定义一个方法,返回B类的对象的引用,返回的是引用就不会有副本产生,可以直接修改类对应类B的对象。

函数定义时要按以下格式:

    类型标识符  &函数名 (形参列表及类型说明)

    {  函数体  }

引用作为返回值,必须遵守以下规则:

  1.不能返回局部变量的引用,主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

  2.不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成内存泄漏。

使用指针和引用的情况对比:

        1.是考虑到存在不指向任何对象的可能(在这种情况下,能够设置指针为空)

        2.需要能够在不同的时刻指向不同的对象(在这种情况下,能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么就应该使用引用。

        3.重载某个操作符时,应该使用引用。当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。

 

C++ 引用指针和指针引用

C++不允许定义引用的指针,因为引用本身只是与另一个对象绑定在一起的该对象的别名,而并非一个对象,所以标准规定不能定义指向引用的指针报错

int a = 20;
int &*ptr = &a;// error

参考<< C++ Premier 第五版>>,想要看懂声明符ptr的具体类型是什么,最简单的办法就是从右往左读,离变量名最近的符号对其类型有最直接的影响(此处是*,表示其首先是个指针,指针的类型是一个int型引用)。
但是由于指针是个对象,所以定义一个指针的引用是可以的:

int a = 20;
int *&b = &a;// ok

 

 

数组引用

1. 数组引用既可以作为参数传递,又可以当做 函数返回值。

在这先举个例子,通过模板传入的类型和数组大小可以变的。其他一些关注数组引用的介绍后续再补。

例1:

#include <iostream>
 
template<typename T, int N>
//auto getArrayRef(T(&arr)[N])->T(&)[N] //C++11
decltype(auto) getArrayRef(T(&arr)[N])  //C++14
{
	return arr;
}
 
int main()
{
	const int arr1[] = { 1, 2, 3 };
	auto& r1 = getArrayRef(arr1);
	auto r2 = getArrayRef(arr1);
	auto size1 = sizeof(r1);
	auto size2 = sizeof(r2);
 
	const int arr2[] = { 1,2,3,4,5,6,7,8,9,0 };
	auto& rr3 = getArrayRef(arr2);
	auto size3 = sizeof(rr3);
 
	return 0;
}

在这里通过函数返回数组引用的时候,给r1和r3加上&引用符,给r2不加引用符,结果是不一样的。在getArrayRef函数中sizeof(arr)的值就是数组本身的大小,而不是一个int指针的大小。关于auto的介绍可以看<<Moedern effective c++>>前三章的介绍,很详细也很经典。

结果如下(sizeof(int)大小为4):

 

C++中指针*与指针引用*&的区别说明

C++中*&(指针引用)与*(指针)的区别
*指针是一个存放地址的变量,指针引用指的是这个存放地址的变量的引用。
C++中如果参数不是引用的话,会调用参数对象的拷贝构造函数,
所以如果有需求想改变指针所指的对象即想要改变指针变量里存放的地址,就要使用指针引用。
下面用一个测试例子和过程图结合进行说明:

#include <iostream>
using namespace std;
struct Node {
	int data;
};

void ChangeNode1(Node*& pnode) {
	pnode = new Node;
	pnode->data = 5;
}

void ChangeNode2(Node *pnode) {
	pnode = new Node;
	pnode->data = 5;
}


void Test1() {
	Node *node = new Node;
	node->data = 10;
	ChangeNode1(node);
	std::cout << "指针引用" << node->data << endl;
}

void Test2() {
	Node *node = new Node;
	node->data = 10;
	ChangeNode2(node);
	std::cout << "指针" << node->data << endl;
}

int main() {
	Test1();
	Test2();
	return 0;
}

 结果如下:

分析:在Test1中,第一步先创建了一个对象,假设该对象的首地址是1231,则将地址1231存放在node指针变量中,并赋值该对象data属性值为10,当调用ChangeNode1(Node*& pnode)时,如图步骤二,pnode此时可以理解为node的别名,即pnode指针指向的就是node中的地址。pnode=new Node;表示创建一个新对象(假设新对象的首地址为1233),则将该新对象的首地址存放到指针pnode中,也就是指针node中。如图步骤三,node指针和pnode指针中存放的地址均为1233了,即新对象的首地址。

 分析:在Test2中,第一步先创建了一个对象,假设该对象的首地址是1231,则将地址1231存放在node指针变量中,并赋值该对象data属性值为10,当调用ChangeNode1(Node* pnode)时,如图步骤二,将node指针中存放的地址拷贝给了pnode,即pnode指针存放的也是1231。pnode=new Node;表示创建一个新对象(假设新对象的首地址为1233),则将该新对象的首地址存放到指针pnode中。如图步骤三,node指针存放的地址还是之前旧对象的首地址1231,pnode指针存放的是新对象的首地址1233。


 

 

指针的指针和指针的引用

当指针作为函数的参数进行传递时,实际上本质上是安置传递,即将指针进行了一份拷贝,在函数的内部对这个指针的修改实际上就是对一个在函数内部的那个局部变量的修改。这点事和引用不同的,引用实际上是在参数传递时,将实际变量的地址传了进去,在函数内部访问这个变量时,实际上是使用间接访问的方式来进行了的,所以实际上就是访问了元变量。但是由于只是将地址进行了拷贝,所以对这个指针所指向地址的修改不会对原有的指针产生影响。若果要实现对指针的修改,需要使用指针的指针或者指针的应用进行传递。

一、指针的指针

 

int value=3;

void func(int **p)
{
    *p = &value;
}

int main(int argc, char *argv[])

{

    int n = 1;
    int *pn = &n;

    cout << *pn << endl;

    func(&pn);
    cout << *pn <<endl;

    return 0;

}

int *p 本质为地址的地址,也就是说,p指向了一个内存空间,里面放了一个地址。如果我们通过值传递,将直接传递给函数,那么内部的副本不会改变p本身。类似于在函数外部是int *p,在内部是int * tmpp,两者里面放的内容是一样的,也就是真是的地址,但是两者本身的地址是不一样的,对tmpp的修改不会作用到p上。 使用指针的指针,可以做到这点。int **p;在函数内部首先解引用,实际上就得到了p的真是地址,从而可以对p本身进行修改。

 

二、指针的引用

int value=3;

void func(int *&p)

{

    p = &value;

}

 

int main(int argc, char *argv[])

{

    int n = 2;

    int *pn = &n;

    cout << *pn << endl;

    func(pn);

    cout << *pn <<endl;

    return 0;

}

看一下int *&p 实际上,本质p是一个引用,对一个指针的引用,所以对p的修改实际上就是对指针的修改。这里在从新认识指针 int *p,本质为地址的地址,也就是说p指针中放的是一个地址,本身p也有一个地址。所以当int *& p初始化之后那么p指向的地址就不会改变,也就是存放地址的内存空间。但是这里面放的内容可以改变,这就是引用的特点,在这里实际上就是里面放的地址可以改变。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章