C++之指针和引用

文章目录

  • 1.引用(&)
    • 1.1 引用的产生背景和本质
    • 1.2 引用作为函数的参数
    • 1.3 引用作为函数的返回值
    • 1.4 引用初始化的2种特殊情况之一const引用
    • 1.5 引用初始化的2种特殊情况之二实现多态
    • 1.6 引用的小结
    • 1.7 左值引用和右值引用
  • 2.指针
    • 2.1 指针的定义以及使用指针的操作
    • 2.2 空指针、悬空指针和野指针
    • 2.3 指针初始化的2种特殊情况之一const指针
    • 2.4 指针初始化的2种特殊情况之二实现多态
    • 2.5 常量指针,指针常量和void*指针
    • 2.6 指针的迭代器与算术运算
    • 2.7 指针数组和数组指针
    • 2.8 指针作为函数参数,指针型函数和指向函数的指针
    • 2.9 指向函数的指针与形参和返回值
    • 2.10 指向指针的指针
    • 2.11 对象成员指针
    • 2.12 指针的引用
    • 2.13 C++中NULL和nullptr区别
  • 3.相关面试题
    • 1.指针与引用的区别
    • 2.数组名和指针(这里为指向数组首元素的指针)区别
  • 5.应用

1.引用(&)

1.1 引用的产生背景和本质

  • 背景:在C语言中,引用&是用来取地址符,经常用在函数传参的指针赋值。但是在C++引入引用&,可以把引用认为是一种弱化的指针,成为C++的新特性,正确使用引用&可以使程序更加简洁高效。
  • 本质:本质是* constconst int &e ; //相当于const int *const e;,相当于是指向常量的指针常量,既不能修改其值,也不能修改指向的内存空间。另外,普通引用相当于int *const e1;
int a = 3;int &ra = a;
int b = 4;ra = b;  //相当于把B的值赋给ra,并不是引用新的对象。
cout << a << ' ' << ra << ' ' << b << endl; // 4  4  4
b = 5;
cout << a << ' ' << ra << ' ' << b << endl; // 4  4  5
a = 6;
cout << a << ' ' << ra << ' ' << b << endl; // 6  6  5
//下面为什么没有编译报错,奇怪;
struct Student{
   
	int &a;
};

1.2 引用作为函数的参数

  • 在C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,同时可以避免没有必要的拷贝参数动作,可以提高程序的效率。但是C++中又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。 例如现在需要交换两个数的数值:
void swap(int &p1, int &p2) {
    //此处函数的形参p1, p2都是引用;
	int p;
	p=p1; 
	p1=p2; 
	p2=p; 
}
  • 由上面可以知道

1)传递引用与传递指针效果一样。这时被调函数的形参就成为原来主调函数中的实参对象的一个别名,所以在被调函数中对形参的操作就是对其相应的实参的操作。
2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参是实参的副本。如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
3)使用指针形参虽然也能达到引用形参的效果,但是在被调函数中同样要给形参分配内存空间,且需要使用*指针变量名的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
4)如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用const引用
5)使用引用形参返回额外信息。第一种方法,使用自定义类型,让它包含我们需要的成员;第二种方法,可以给函数传入更多的引用形参来达到效果。

1.3 引用作为函数的返回值

  • 定义:要以引用返回函数值,则函数定义时要按以下格式:类型标识符 &函数名(形参列表){ 函数体 }
  • 意义:用引用返回一个函数值的最大好处是,在内存中不产生返回值的副本。例如:
#include
using namespace std;
int temp; //定义全局变量temp
int fun1(int x){
      //以返回值的方法返回函数值
	temp=x*x;
	return temp;
}
int &fun2(int x){
     //以引用方式返回函数值
	temp=x*x;
	return temp;
}
int main()
{
   
	int a = fun1(10);
	//返回全局变量temp的值时,C++会在内存中创  建临时变量并将temp的值拷贝给临时变量。
	//当返回到主函数main后,赋值语句a=fun(10)会把临时变量的值再拷贝给变量a。
	
	int &b = fun1(10);//编译错误;
	//返回时,首先拷贝temp的值给临时变量;
	//返回到主函数后,用临时变量来初始化引用变量b,使得b成为该临时变量到的别名。
	//由于临时变量的作用域短暂;
	//在C++标准中,临时变量或对象的生命周期在一个完整的语句表达式结束后便宣告结束,也就是在语句int &b=fun1(10);之后;
	//所以b面临无效的危险,很有可能以后的值是个无法确定的值,因此编译错误。
	//如果真的希望用函数的返回值来初始化一个引用,应当先创建一个变量;
	//将函数的返回值赋给这个变量,然后再用该变量来初始化引用;
	
	int c=fun2(10);
	//函数fun2()的返回值不产生副本,而是直接将变量temp返回给主函数。
	//即主函数的赋值语句中的值是直接从变量temp中拷贝而来,
	//(也就是说c只是变量temp的一个拷贝而非别名)这样就避免了临时变量的产生。		
	//尤其当变量temp是一个用户自定义的类的对象时,
	//这样就避免了调用类中的拷贝构造函数在内存中创建临时对象的过程,
	//提高了程序的时间和空间的使用效率。
	
	int &d=fun2(10);
	//函数fun2()的返回值不产生副本,而是直接将变量temp返回给主函数
	//在主函数中,一个引用声明d用该返回值初始化,也就是说此时d成为变量temp的别名。
	//由于temp是全局变量,所以在d的有效期内temp始终保持有效,故这种做法是安全的。
	return 0;
}
  • 总结

1)不能返回局部变量的引用。如果使用局部变量的引用,那么局部变量会在函数返回后被销毁,此时对temp的引用就会成为没有指向的引用,也就是引用非法内存,因此编译错误。
2)不能返回函数内部通过new分配的内存的引用。虽然不存在局部变量的被动销毁问题,但如果返回函数的引用只是作为一个临时变量出现,而没有将其赋值给一个实际的变量,那么就可能造成这个引用所指向的空间无法释放的情况(由于没有具体的变量名,故无法用delete手动释放内存),从而造成内存泄漏。因此应当避免这种情况的发生。
3)当返回类成员的引用时最好是const引用,这样可以避免在无意的情况下破坏该类的成员。
4)可以用函数返回的引用作为赋值表达式中的左值
5)当函数返回值为引用时,若返回栈变量,不能成为其它引用的初始值,不能作为左值使用;若返回静态全局变量,可以成为其他引用的初始值即可作为左值使用,也可作为右值使用。

1.4 引用初始化的2种特殊情况之一const引用

  • 特殊点:在初始化const引用的时候允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其是,允许常量引用绑定到非常量的对象、字面值,甚至是个一般表达式。
  • const引用声明方式const 类型标识符 &引用名=目标变量名;,用这种方式声明的引用,不能对const引用的值进行修改,从而达到引用的安全性。换句话说,const引用仅仅对引用本身可参与的操作做出限定,没有对引用的对象做限定。因为如果引用的对象可能是一个非const,允许通过其它途径来改变它的值。例如:
int a=2;
const int &ra=a; //正确,允许const引用绑定到非const的对象;
ra=1;            //错误,不能通过const引用对目标变量值进行修改;
a=1;             //正确;

int &n=10;         //编译报错;
const int &n = 10; //正确,const引用可以绑定非const引用、字面值等等;
//编译器处理:int tmp=10; const int& y=tmp;
  • 非const引用不能绑定const对象。例如:
string foo();
void bar(string &s);
bar(foo());         //非法;
bar("hello world"

你可能感兴趣的:(C++基础知识合集)