C++ const 、引用&&指针

const


取代#define进行值替代

#define

  • 用#define定义的常量(无类型信息),只在预处理期间存在,不占用存储空间
  • 预处理其只对其定义的常量进行文本代替,没有类型检查

const

  • const可用来定义变量, const定义的变量在生命期内不可变
  • 在C中,一个const总是需要创建一块内存空间
  • 在C++中,如果const只是同#define一样定义常量,则不必开辟空间

指针

指向const的指针

  • 定义指针的技巧是在标识符的开始处读它并从里向外读。const修饰”最靠近”它的那一个
const int* u;
  • 从标识符开始,是这样读的”u是一个指针,它指向一个const int”,这里不需要初始化,因为u指向的值是不能改变的,但是u这个指针是可变的,它可以指向任何标识符

const指针

  • 是指针本身成为一个const指针,必须把const标明的部分放在*的右边,如:
int d=1;
int* const w=&d;
  • 这个表达式这样读”w是一个const指针,它指向一个int”,因为指针本身现在是不可变的,所以编译器应该给它一个初始值,这个值在指针生命期间内不变
  • 改变指针所指向的值是可以的,比如说:
*w=2;
  • 可以用下面两种方法把一个const指针指向一个const对象:
int d=1;
const int* const x=&d;
int const* const y=&d;
  • C++中对类型检查是非常精细的,对指针也一样。可以把一个非const对象的地址赋给一个const指针,但是不能把一个const对象的地址赋给一个非const的指针

函数参数

  • 若函数参数是按值传递,则可指定参数是const的,这即是说:作为实参传入的变量不会被函数改变
  • 参数按值传递,即要产生原变量的副本
  • 在函数内部用const限定参数优于在参数表里用const限定参数,如果我们想让实参变量不可变,则可以这样定义:
void f(int x)
{
    const int& i=x;
    //i++;  //这将会的到错误信息,因为i不可变
}
//关于引用的详细内容,我们会在下一小节讲到

成员函数

  • const对象:对象的数据成员在其生命期内不可改
  • const成员函数:如果声明一个const成员函数,则等于告诉编译器该函数可以被const对象调用
  • 一个没有被声明为const的成员函数,则可能将要修改对象中数据成员的值,编译器不允许它被一个const对象调用
  • const成员函数的声明必须把const放在函数后面,因为如果放在前面的话,它表示函数的返回值是const

引用


引用的概念

  • 引用不是新定义一个变量,而是给变量起一个别名
  • 定义方法:类型名 &引用变量名=已定义的变量名
  • 我们通过监视窗口来看一下,引用到底是怎么回事:
    这里写图片描述
  • 可以这样说,引用只是给原来的空间重新起了一个名字,我们操作引用变量b的时候,相当于操作a这块空间
  • 一个空间可以有多个名字,我们可以用这个名字来访问、修改这个空间的内容

引用的特点:

  • 一个变量可以起多个别名
  • 引用必须进行初始化(如果不进行初始化,我们不知道这个引用变量是哪个变量的别名,所以这个引用变量是无意义的)
  • 一个变量只能在初始化的时候引用一次,不能改变为引用其他变量(我们不能说b是a的别名,b又是c的别名)

const引用(常引用)

  • 为什么会有const引用?const修饰的变量是不可变的,const修饰一个引用变量,则这个引用变量不可变,即只能对这个引用变量指向的空间读而不能进行修改和删除
  • 比如说下面的这个定义,编译器就会报错:
    这里写图片描述
  • “无法从const int 转换为int &”,这是因为:变量a有const修饰,所以变量a是不可变的,引用变量b是可变的,将一个不可变量赋给一个变量,将原变量的权限放大了,这是不可行的
  • 下面这种定义是可行的:
const int a=10;
const int& b=a;
  • 常量不能赋给一个引用变量。常量具有常属性,不可变,赋值给一个可变的引用变量是不可行的,但是可以赋给一个const引用变量,比如下面这种定义方式:
//int& c=10;  //不可行,无法从const int 转换为int &
const int& c=10;   //可行

常引用特性

  • const引用可以用同类型的变量初始化;
  • 可用同类型的常量来初始化,c++编译器会为常量分配空间,并将引用名作为这段空间的别名;
  • 可用不同类型的变量来初始化,这时候会有类型提升(范围小的可以提升为范围大的)

引用的作用

引用做参数

C语言中我们函数传参有两种方式:传值和传地址

  • 传值:如果我们的函数只是实现类似于两位数加法的运算,那么传值确实是很高效的。传值的实质是:系统会会在函数调用的时候为形参开辟一个临时空间,用来存放实参的副本。传值是有空间的开销的,如果我们传送的数据量特别大,那么必定会消耗大量的内存空间,所以这种情况下传值比较浪费空间。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
  • 传地址:函数被调用时,系统会为其开辟一个4字节的空间,用来存放实参变量的地址,函数对形参的各种操作都被处理成间接寻址,所以传指针会改编实参变量

C++中参数的传递方式有三种:传值、传指针和传引用

  • 传值和传指针的原理和C语言中相同
  • 我们通过一个实例来探索一下传引用的原理:
void swap(int& x, int& y)
{
    int tmp = x;
    x = y;
    y = tmp;
}

int main()
{
    int a = 10;
    int b = 20;
    swap(a, b);
    return 0;
}

这里写图片描述

  • 我们知道,引用变量x是a的别名,y是b的别名,所以对x,y进行操作等同于对a,b进行操作
    这里写图片描述
  • 从上图中我们也可以看到,x和a操作的是同一块空间;y和b操作的是同一块空间
  • 一般的如果我们只是对实参进行访问,而并不去修改它的值的时候,我们建议形参采用const &
  • 采用传引用在大多数情况下是比较高效的,x和y是实参的引用,不用经过值的传递机制,已经有了实参值的信息。所以没有了传值和生成副本的时间和空间消耗。当程序对效率要求比较高时,这是非常必要的。
引用做返回值
  • 传值返回:真正传回的并不是函数内部的返回值(这个返回值在出了当前这个函数的作用域的时候就已经被销毁了),而是系统为我们创建的临时变量(寄存器作为临时变量)——返回值的副本

引用作为返回值的几点说明:

  • 以引用返回函数值,定义函数时需要在函数名前加&
  • 用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。
  • 下面我们定义一个实例通过汇编来观察一下传值和传引用的区别:
int& Add (int a, int b)
// int Add ( int a , int b)
{
 int c = a + b;
 return c ;
}
int main()
{
 int ret = Add( 1, 2);
 return 0;
}

这里写图片描述
- 传引用做返回值: 是将临时变量c的地址放到eax寄存器;
- 传值做返回值:是将c这个变量的值放到eax寄存器

我们看一个关于返回引用的实例:

int& Add(int a,int b)
{
    int c=a+b;
    return c;
}

int main()
{
    int &ret=Add(1,2);   //(1)
    Add(10,20);   //(2)
    return 0;
}

这里写图片描述
- 通过监视窗口,我们首先看到的是ret的值为3,当执行了(2)语句时,ret的值变为30,这是为什么呢?
- 我们首先分析(1):ret是局部变量c的别名,但是c出了作用域就不存在了,函数栈帧被销毁了,所以ret这个引用变量接收的是一个非法值。但是虽然原函数的栈帧被销毁了,原空间上的值可能还没有抹去,可能是3,可能是其他的随机值
- (2):再次调用Add(),会产生一个和刚才调用完全一样的栈帧,返回值还是c的别名,所以ret的值为30

引用作为返回值,应该遵守以下规则:

  • 不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了”无所指”的引用,程序会进入未知状态。
  • 如果返回对象出了当前函数的作⽤域依旧存在,则最好使⽤引⽤返回,因为这样更⾼效。

深入理解引用的原理

  • 引用在语法层上面并没有为引用变量开辟空间。但是底层为了实现语法层的愿望,以类似指针的方式实现了引用
  • 我们先定义一个实例,然后通过汇编看一下指针和引用在底层都是如何实现的:
int main()
{
    int a=10;
    int &b=a;
    int *p=&a;
    return 0;
}
  • 通过汇编看底层实现:
    这里写图片描述
  • 我们观察到的现象是:引用和指针的汇编代码是一样的,都是将a的地址放到eax寄存器

指针和应用的区别与联系


  • 引⽤只能在定义时初始化⼀次,之后不能改变指向其它变量(从⼀⽽终);指针变量的值可变
  • 一旦一个引用被初始化为指向一个对象,就不能再指向其他对象,而指针可以在任何时候指向任何一个同类型对象
  • 没有NULL引用,但有NULL指针
  • 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
  • 引用自加改变变量的内容,指针自加改变了指针指向
  • 有多级指针,但是没有多级引用
  • 引用比指针使用起来相对更安全

猜你喜欢

转载自blog.csdn.net/aurora_pole/article/details/80028347