前言
因为基础不好,C++的指针和引用,我一直分不大清,但是老被问区别啊,两者用的场合啊什么的。所以趁着最近有时间,特意去网上查资料,看公开课等,进行学习,最终整理了这篇博客。
理论知识及示例代码
学过C++的我们知道访问对象的方式有三种:引用、指针和变量。变量这个不用解释,就是直接访问;而指针和引用是什么?之前我觉得他俩差不多,都可对原对象进行操作,在函数传参中属于地址传递。我比较常用指针,可能是C语言用习惯了(C语言是没有引用这一概念的),但是错误使用指针是有安全隐患的(内存泄漏、重复释放、越界等)。所以还是均衡的使用吧,下面只介绍指针和引用。
指针
一级指针和二级指针
指针本质上很特殊的数据,它里面存储的是内存地址数据,指向这一块内存位置。又因为他是数据,故它还能进行部分运算:算数运算(常用的是指针加减,即指针的指向偏转)、关系运算和赋值运算。故可以说指针是一种特别灵活的访问数据的方式。举个例子:
int num=10;
int* pVal=#
qDebug()<<QString("num的地址是")<<#
qDebug()<<QString("pVal指向的数据的值是")<<*pVal;
qDebug()<<QString("pVal指向的地址是")<<pVal;
qDebug()<<QString("pVal的地址是")<<&pVal;
打印出来的结果是这样的。
可以看出内存间的位置大概是这样的,如下图。
这是较为基础简单的一级指针操作:指向某块内存,进行某些操作,比如动态内存分配(new和delete);但是有时候一级指针无法满足我们的需求,如在函数中修改指针的指向,见下例子
int a=10;
int b=100;
int *q;
void func(int* p)
{
cout<<"func:&p="<<&p<<",p="<<p<<",*p="<<*p<<endl;
p=&b;
*p=101;
cout<<"func::&p="<<&p<<",p="<<p<<",*p="<<*p<<endl;
}
int main()
{
cout<<"&a="<<&a<<",&b="<<&b<<",&q="<<&q<<endl;
q=&a;
cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl;
cout<<"-----------------------------------------"<<endl;
func(q);
cout<<"-----------------------------------------"<<endl;
cout<<"b="<<b<<endl;
cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl;
return 0;
}
打印出来的结果是:
可以看到:当我们把指针q指向a后,再将指针q作为参数传入函数func后,发现指针的指向并没有发生变化。从打印的结果可看出 指针q和输入参数p,所占地址并不同,只是值被拷贝了,当函数结束后,指针p也就被释放了,所以函数并没有改变指针的指向。当然若只是改变指向的值(解引用的值),还是变化的。
为了解决这一问题,我们用到了二级指针,如下:
void func2(int**p)
{
cout<<"func2:&p="<<&p<<",p="<<p<<endl;
*p=&b;
cout<<"func2:&p="<<&p<<",p="<<p<<endl;
}
int main()
{
cout<<"&a="<<&a<<",&b="<<&b<<",&q="<<&q<<endl;
q=&a;
cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl;
func2(&q);
cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl;
return 0;
}
打印结果:
二级指针,可以这么理解就是指向一级指针,值是一级指针的地址。像上面的一级指针的例子,我们无法改变指针的指向,但可以改变指向的值,那也是说,当我们将一级指针的地址作为指针指向的的值,就可改变其指向,这就解决了改变指针指向的问题。函数func2()内存指向如下图:
智能指针
有时候为指针新开辟了一块内存,但忘了释放,这就造成了内存的泄漏。所以就有了智能指针的出现:使内存管理方面更加安全。它分为三类(这部分内容我涉足不多,下次补充):
unique_ptr:不允许多个指针共享资源,可以用标准库中的move函数转移指针
shared-ptr:多个指针共享资源
weak_ptr:可复制shared_ptr,但其构造或释放多资源不产生影响
引用(Reference)
引用,是C++中一个新型的数据类型,C语言中是没有的。引用可以看做是目标对象的一个别名,对引用操作其实是对目标对象操作。这样看好像指针的作用,虽两者有区别(不然也不会出现这个新的数据类型),但其实引用可以看成是特殊的指针。
声明和定义:(1)对于局部变量和全局变量,定义时,则必须与目标对象绑定,且无法解绑,与指针不同,不能为空。
大概形式为:type& refname=name;
(2)在参数列表和成员变量中,可以先声明不绑定对象,然后在方法调用时或构造函数初始化时绑定。
声明时形式为:type& refname;
引用的规则(Rules of references)
(1)定义时必须初始化
(2)初始化创建绑定
int x=3;
int &y=x;
const int& z=x;
//As a function argument
void f(int& x);
f(i); //initialized when function is called
(3)在运行时不能改变绑定,不像指针似的
(4)目标引用变量赋值时,改变被引用的对象(双向传递),如
int &y=x;
y=12; //changes value of x
(5) 别引用的目标对象必须有位置(内存空间)
vioid func(int& );
func(i*3); //warning or error
比较指针和引用(Points vs. References)
References | Points |
can't be null | can be set to null |
are dependent on an exsting variable,they are an alias for a variable | pointer is independent of existing objects |
can't change to a new "address" location | can change to point to a different address |
限制(Restrictions)
(1)没有引用的引用
(2)没有指向引用的指针
int &* p; //illegal *p的类型是int& (引用)
//Reference to pointer is ok
void f(int* &p)
(3)没有引用的数组
何时用指针,何时用引用?
通过上面对指针和引用的理论介绍,对两者也有了个系统的理解。两者有很多相似的地方,那若作为参数传递,何时用指针,又何时用引用?下面进行叙述。
怎么说呢,指针和引用很多时候都可以通用的,能够用引用的场合必定可用指针代替,但能用指针的时候,未必能用引用代替。相对来说,指针很灵活(可以为空,可以改变指向),这个是他的优点,也是他的缺点(有安全隐患,运行效率相对引用来说低)。引用呢,则很稳定安全。
参数传递
这部分是看到一篇很不错的博客(https://blog.csdn.net/hbtj_1216/article/details/56843014),先贴这里了
何时使用引用参数
使用引用参数的主要原因有两个:
(1)程序员能够修改调用函数中的数据对象。
(2)通过传递引用而不是整个数据对象,可以提高程序的运行速度。
什么时候使用指针?什么时候使用引用?什么时候应该按值传递?
下面是一些指导原则:
对于那些函数,它们只使用传递过来的值,而不对值进行修改。
(1)如果数据对象很小,如内置数据类型或小型结构,使用按值传递。
(2)如果数据对象是数组,则使用指向const的指针。
(3)如果数据对象是较大的结构,则使用const指针或者const引用,以提高程序的效率。
(4)如果数据对象是类对象,则使用const引用。因此,传递类对象参数的标准方式是按引用传递。
对于那些函数,它们需要修改传递过来的值。
(1)如果数据对象是内置数据类型,则使用指针。
(2)如果数据对象是数组,则只能使用指针。
(3)如果数据对象是结构。则使用指针或者引用。
(4)如果数据对象是类对象,则使用引用。
结束语
感觉基础没打好,当程序员真难受,继续努力吧!