c++中指针,引用,const

一,指针
你在程序中向计算机申请一个变量(如int a),计算机会分配给你的变量一个空间。用int a举例子,计算机就给你了一个名叫a的房子,只能在里面放int类型的值。当然,你可以直接写a=10,这样计算机会帮你找到名叫a的房子,把10放进去。 那么这个房子在哪呢?就有了地址,每个变量都会有一个地址(即它在内存中的位置),指针,便是一根棍子,指着这个位置,指针里存的就是这个地址
声明:
在类型后面加上*即可,当然指针也是分类型的,如:

int* p;//一个指向(还没确定指向哪里)int类型房子的指针p
double* k;//指向double的k

如果你不确定这个类型,可以用void:

void* p;

void * 可以表示任意一种类型的指针。我们知道相同指针类型之间才能进行赋值,不同指针类型之间会出现不兼容。使用void * 的时候不会出现警告,但是void * 只能接收,不能取值,一旦取值就会出现段错误。

空指针nullptr

int *p=NULL//等价于int *p=0;
但是不能这样写
int zero=0;
int *p=zero;//不能把int变量的值赋给指针;

但是,你如果这样写:

int* p,q;

事实上你是定义了一个指针和一个普通变量,所以通常这样写:

int *p,*q;

注意定义指针后指针会指向一个随机的位置,如果你对这个位置进行操作,就有可能发生内存错误。
操作:
存入地址
这里需要用到一个新运算符:&,它称为取地址符,用于获取一个变量的地址。
例如:

int *p;
int a;
p=&a;

如果连续定义两个指针,那么存放的位置不是连续单元。

输出该地址上的值
如果你想知道指针所指向位置的值,需要再次用到*。

int *p;
int a;
p=&a;
a=10;
printf("%d\n",*p);
a=15;
printf("%d\n",*p);

输出结果
在这里插入图片描述
a的地址已经和指针绑定所以地址永不会改变。

加/减
指针也是存在加减的操作,但不是把这个位置的值+,-,而是把地址往后或往前移动。
例如:

#include<cstdio>
int main()
{
    int a=1,*p,*q;
    p=&a;
    q=p+1;
    printf("%p %p",p,q);
}

输出结果
在这里插入图片描述
这里输出的是地址,因为int类型是4个字节,所以加int类型的1会往后移4个字节。
如果是字符类型的就会往后移一位,不是字符的会转化为ASCII码,后移很多位。

指针数组

int *p[10];

声明一个有十个指针(p[0],p[1],p[2],…,p[9])的数组,每个元素都是一个指针。
可以这样对指针数组进行操作

#include<cstdio>
#define MAXN 1000000
int a[MAXN+5],n;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",a+i);
    for(int i=1;i<=n;i++)
        printf("%d ",*(a+i));
}

输出结果
在这里插入图片描述
数组也可以看成一个指针,可以这样写:

#include<cstdio>
int a[10]={0,2,4,6,8,10,12,14,16,18};
int main()
{
    int *p;
    p=a;
    printf("%d\n",*a);
    printf("%d\n",*(a+5));//注意加括号,否则就是输出“a指向的值加5”了
    printf("%d\n",*p);
    printf("%d\n",*(++p));
}

输出结果
在这里插入图片描述
默认第一个就是数组第一个元素。

多重指针
和数组一样,可以在声明时连续打2个(或多个)*,例如:

#include<cstdio>
int main()
{
    int **p,*q;//p就是指向一个指针的指针,q是指向一个普通变量的指针
    int a;
    a=1;
    q=&a;
    p=&q;
    printf("%p  %p  %p  %p\n",p,*p,q,&a);
    printf("%d  %d  %d",**p,*q,a);
}

输出结果
在这里插入图片描述
*p指向q的地址。

常量指针:

const int *p;
int i=34;
int j=90;
p=&i;

p为常量指针。这里应该注意的是,虽然按照字面意思理解,p指向的对象是常量,即i应该是一个常量,而不是上述代码的变量,那么p指向变量i为什么对呢?其实,p“自以为是”的认为自己所指向的对象一定是常量,其实不然。常量指针既可以指向常量,也可以指向变量,常量指针的真正含义在于不能通过p来改变所指向对象的值,并不代表不能通过其他途径来改变对象的值。比如:


const int *p;
int i=34;
int j=90;
p=&i;
int *r=&i;
*r=8;

指针p和r同时指向变量a,这里通过指针r改变了对象i的值,是正确的。

const int *p;
int i=34;
int j=98;
p=&i;
p=&j;

常量指针指向的地址可以变,p指针先是指向i,最后又指向j:

指针常量

int i=89;
int * const p=&i;

指针p中存放的地址不可改变,即p只能初始化,不能改变所指向的对象,对象的值可以通过p来改变。如下代码就是错误的:

int i=89;
int * const p=&i;
int j=56;
p=&j;

二,引用
就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。

声明
类型标识符 &引用名=目标变量名;
定义引用ra,它是变量a的引用,即别名。

   int a;
     int &ra=a;

以下六点需要注意
(1)&在此不是求地址运算符,而是起标识作用。
(2)类型标识符是指目标变量的类型。
(3)声明引用时,必须同时对其进行初始化
(4)引用声明完毕后,相当于目标变量有两个名称即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
(5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。
(6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。

操作

1、引用作为参数
 引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为 这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。
引用传递能够改变实参的值,即便没有返回值。

void swap(int &p1, int &p2) //此处函数的形参p1, p2都是引用
int main( )
{
 int a,b;
 cin>>a>>b; //输入a,b两变量的值
 swap(a,b); //直接以变量a和b作为实参调用swap函数
 cout<<a<< ' ' <<b; //输出结果
}

简单的交换两个值
我们可以发现:
 (1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
  (2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
  (3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的 形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
  
  如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
  
  2、常引用

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

int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确

3、引用作为返回值

要以引用返回函数值,则函数定义时要按以下格式:
类型标识符 &函数名(形参列表及类型说明)
{函数体}
  说明:

(1)以引用返回函数值,定义函数时需要在函数名前加&
(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。

float &fn2(float r) //定义函数fn2,它以引用方式返回函数值
{
 temp=(float)(r*r*3.14);
 return temp;
}

3、引用总结
  (1)在引用的使用中,单纯给某个变量取个别名是毫无意义的,引用的目的主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题。
  (2)用引用传递函数的参数,能保证参数传递中不产生副本,提高传递的效率,且通过const的使用,保证了引用传递的安全性。
  (3)引用与指针的区别是,指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。
  (4)使用引用的时机。流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。

三,指针和引用的区别

首先,引用不可以为空,但指针可以为空。前面也说过了引用是对象的别名,引用为空——对象都不存在,怎么可能有别名!故定义一个引用的时候,必须初始化。因此如果你有一个变量是用于指向另一个对象,但是它可能为空,这时你应该使用指针;如果变量总是指向一个对象,你的设计不允许变量为空,这时你应该使用引用。
其次,引用不可以改变指向,对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象。说明:虽然引用不可以改变指向,但是可以改变初始化对象的内容。例如就++操作而言,对引用的操作直接反应到所指向的对象,而不是改变指向;而对指针的操作,会使指针指向下一个对象,而不是改变所指对象的内容。
再次,引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节。
最后,引用比指针更安全。由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。const 指针虽然不能改变指向,但仍然存在空指针,并且有可能产生野指针(即多个指针指向一块内存,free掉一个指针之后,别的指针就成了野指针)。
总而言之,言而总之——它们的这些差别都可以归结为"指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名,引用不改变指向。”

猜你喜欢

转载自blog.csdn.net/qq_43627087/article/details/88761223