C++中指针与引用详解
在计算机存储数据时必须要知道三个基本要素:信息存储在何处?存储的值为多少?存储的值是什么类型?因此指针是表示信息在内存中存储地址的一类特殊变量,指针和其所指向的变量就像是一个硬币的两面。指针一直都是学习C语言的难点,在C++中又多了一个引用的概念。初学时很容易把这两个概念弄混,下面就来通过一些例子来说明二者之间的差别。
1、指针的声明
上文中提到,指针和其所指向的变量就像硬币的两面,因此通过取址符号"&"
我们可以找到变量的地址,通过解引用符号"*"
可以找到地址内存放的变量值。
int data = 10; //声明了一个变量data,并赋初始值10,存储的值是int类型
int* p_data = &data; //找到 data 在内存中存放的位置,即p_data
cout << "地址为:" << int(p_data) << "\t 存放的值为:" << data << endl;
输出结果为:
地址为:8191436 存放的值为:10
地址默认是16进制,我们在输出时将其转换成了int
类型,因此以十进制输出。输出结果翻译过来就是:在地址编码为8191436的位置存放了值为10的变量data,再进一步地说,data
与*p_data
表示同一个东西。为了更有助于理解,我们绘制了下图:
因此从本质上看,指针与普通的变量并没有什么太大的区别,只是指针变量可以通过解引用的方式找到指针所对应的地址中存放的数值。假如定义如下:
int data = 10;
int* p_data = &data; //定义指向 int 类型的指针 p_data, 存储的是 int 类型的变量 data的地址,其
int** p_p_data = &p_data; //定义指向 int* 类型的指针 p_p_data, 存储的是 int* 类型的变量 p_data的地址
cout << "p_data:" << p_data << "\t 存放的值为:" << *p_data << endl;
cout << "p_p_data:" << p_p_data << "\t 存放的值为:" << *p_p_data << endl;
输出结果为:
p_data:00EFF96C 存放的值为:10
p_p_data:00EFF960 存放的值为:00EFF96C
从输出结果可以看出,p_p_data
中存储的值就是p_data
,而p_data
中存储的值就是data
,很像”我爱她,她爱他“的这种桥段。下面我们就重点分析一下变量与指针之间的关系:我们在上述例子中把指针初始化为变量的地址,而变量是在编译时分配的有名称的内存,指针只是为可以通过名称直接访问的内存提供了一个别名。还拿上面这个例子:对程序员来说,变量10的名字就是data;而对于计算机来说,变量10就是存在 8191436 地址的数据;实现程序员与计算机沟通的方式就是指针,通过对data取址让程序员能够明白计算机的存储结构,同样,通过对地址解引用,也能轻松地找到该地址中存储的数据。在上述情况下,指针的出现显得有些多余,然而指针的真正用武之地在于,在运行阶段分配未命名的内存以存储值,在这种情况下,只能通过指针来访问内存。
最后关于指针声明的一点建议:在声明一个指针变量时,必须要指定一个确定的地址,否则声明的指针变量不知道指向哪里,因此容易造成系统崩溃。
2、使用new
来分配内存
内存四区之代码区,全局区,栈区和堆区 - ZhiboZhao - 博客园 (cnblogs.com) 中提到过,new
会在堆区创建一个内存空间,其返回值就是该内存空间的地址,因此程序员的责任就是将该地址赋给一个指针。下面是一个示例:
int* p_data = new int; //在堆区开辟一个空间,并返回该内存空间的地址
*p_data = 10; //将向该内存中存储数值10
cout << "p_data:\t" << p_data << "\t *p_data: " << *p_data << endl;
通过比较会发现,new
后面指定了数据类型 int
,同样地,p_data
也被声明为指向 int
的指针。这是因为,计算机的内存是以字节为存储单位,不同类型的变量会占用不同的字节,因此使用 new
时必须要告诉编译器分配多少字节的存储空间,并且接收的指针也必须与声明的类型一致。输出结果为:
p_data: 00D0D9A0 *p_data: 10
当处理大型数据,比如数组时,通常会使用的一种方法是定义一个数组类型的数据,在定义的时候分配足够大的空间。但是这种做法太过于死板,但是当使用 new
时,如果在运行阶段需要数组,那么则创建它,如果不需要则不创建,最重要的是可以在程序运行时选择数组的长度。 下面就看一下如何使用 new
来创建动态数组。在C++中,数组名被解释为数组地址,即数组第一个元素的地址。下面是一个实例:
int Arr[10]; // 定义一个包含10个int类型元素的数组
cout << "Arr:" << Arr << "\t&Arr[0]:" << &Arr[0] <<endl;
输出结果为:
Arr:008FFAB4 &Arr[0]:008FFAB4
这种声明方式只能在刚开始就声明固定的数组长度,在C++中创建动态数组时,只需要将数组的元素类型和元素数目告诉给 new
即可,new
的返回值同样是数组的首地址。
int ele_num = 10; //临时指定数组内元素的个数
int* p_arr = new int [ele_num]; //根据临时指定的元素个数创建数组
通过 new
在堆区开辟空间,由程序员管理释放,因此当 new
的内存不用后,需要通过 delete
进行变量,使用 delete []
来释放开辟的数组空间。代码如下:
int* p_data = new int;
*p_data = 10;
cout << "p_data: " << p_data << "\t*p_data:" << *p_data << endl;
int ele_num = 10;
int* p_arr = new int [ele_num];
for(int i = 0; i<9; i++)
*(p_arr+i) = i+2;
cout << "p_arr:" << p_arr << "\t\t*(p_array):";
for(int i = 0; i<9; i++)
cout << *(p_arr + i) << " ";
cout << endl;
delete p_data;
delete [] p_arr;
cout << "\n******使用delete释放内存后......*******" << endl;
cout << "p_data: " << p_data << "\t*p_data:" << *p_data << endl;
cout << "p_arr:" << p_arr << "\t\t*(p_array):";
for(int i = 0; i<9; i++)
cout << *(p_arr + i) << " ";
cout << endl;
输出结果如下:
p_data: 0082B1C8 *p_data:10
p_arr:0082BB58 *(p_array):2 3 4 5 6 7 8 9 10
******使用delete释放内存后......*******
p_data: 0082B1C8 *p_data:-572662307
p_arr:0082BB58 *(p_array):-572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307
3、malloc 与 new 的区别
学过C语言的朋友都知道,在C语言中通过malloc
函数开辟一块内存空间,malloc
的函数原型如下:
void* malloc(unsigned int numbytes);
从函数原型的参数可以看出,malloc
函数以字节数为参数,开辟固定字节的内存空间。这与 new
就有了第一点不同:new
不需要自己计算字节数,只需要给定内存中存储的数据类型与元数个数即可。
从函数原型的返回类型可以看出,malloc
函数返回 void*
类型,需要我们在使用时自己指定指针类型。比如:
int* p_malloc = nullptr; // 创建一个指向int的指针
p_malloc = (int*) malloc(10); //将 malloc 的返回值强制转换为 int* 类型
而 new
在使用时则不需要。总结看来,malloc
在使用时需要自己根据内存中的数据类型以及内存长度计算处所需要的字节数,然后返回 void*
类型,需要使用对应类型的指针进行接收。而 new
在使用时只需要给定内存的长度与内存中数据的类型,编译器会自动计算所需要的字节数。
4、引用的声明与本质
C++中新增了引用作为已定义的变量的别名。引用的最主要用途是作为函数形参,这样函数就可以使用原始数据而不是数据副本,这样听起来似乎与指针没什么区别,我们还是从引用的声明说起。
int data = 10;
int& p_data = data; //创建一个引用变量 p_data
cout << "data:" << data << "\tp_data:" << p_data << endl; //p_data 与 data 相当于一个变量的两个名字
输出结果为:
data:10 p_data:10
从输出结果来看,p_data
与 data
就是一个变量的两个不同叫法而已。引用必须在声明时就为其指定初始值,而不能像指针一样可以先声明,再赋值。下面将引用作为函数的参数来进一步说明引用与指针的区别:
template <typename T> //定义一个模板函数
void swap(T a, T b){
int temp;
temp = a;
a = b;
b = temp;
}
int main(void){
int a = 10, b = 20;
swap_value<int>(a,b); //首先进行值传递
cout << "a:" << a << "\t\tb:" << b << endl;
swap_value<int&>(a,b); //然后进行引用传递
cout << "\na:" << a << "\t\tb:" << b << endl;
}
从上述代码中可以看到,值传递和引用传递的形参都是一样的,不同的是引用传递时,实参被声明为引用,引用的用法与使用值一模一样,输出结果如下:
a:10 b:20
a:20 b:10
惊奇的发现,引用传递改变了原始数据的值,这点与指针的用法一致,但是指针在书写 swap
函数时应该这样写:
void swap(int* a, int* b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
swap(&a, &b); //调用格式
综上发现,引用其实就是变量的另一个名称,它的用法与变量一模一样,但是能在作为形参传递时,改变原始数据的值。除了这些用法上的区别,引用的本质其实就是一个指针常量,意味着指针指向的位置不可变,但是指针指向位置的值可变。即:
// 这两者的语句是等效的,因此引用被当作指针常量来处理
int& p_a = a;
int* const p_a=&a;
再补充一点小知识,关于 const
修饰符的问题,有些新手朋友来说很容易弄不清楚 const
修饰下什么是可变的,什么是不可变的。具体实例如下:
int data = 10, data2 = 20;
const int* p_data = &data; //修饰的是int,即 p_data 所指向的值不可变,而p_data可变
p_data = &data2;
int* const p_data2 = &data; //修饰的是int*,即 p_data 所指向的值可变,而p_data不可变
*p_data2 = data2;
引用即是第二种用法。
C++中指针与引用详解的更多相关文章
- 关于C中指针的引用,解引用与脱去解引用
*,& 在指针操作中的意义 (1)* 大家都知道在写int *p 时,*可以声明一个指针.很少人知道*在C/C++中还有一个名字就是"解引用".他的意思就是解释引用,说的通 ...
- Bootstrap排版中地址与引用详解
地址元素address 我们的地址在HTML5中增加了一个address标签,可以把我们的地址写在address标签里面,address里面强调换行等等都是可以的. 实例: <address&g ...
- php中关于引用(&)详解
php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...
- c++中vector的用法详解
c++中vector的用法详解 vector(向量): C++中的一种数据结构,确切的说是一个类.它相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间 ...
- C++引用(&)详解
C++引用详解 引用的概念 引用:就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样. 引用的声明方法:类型标识符 &引用名=目标变量名: 如下:定义引用ra,它是变量a的引 ...
- BAT批处理中的字符串处理详解(字符串截取)
BAT批处理中的字符串处理详解(字符串截取) BAT批处理中的字符串处理详解(字符串截取 批处理有着具有非常强大的字符串处理能力,其功能绝不低于C语言里面的字符串函数集.批处理中可实现的字符串处理 ...
- Python中的高级数据结构详解
这篇文章主要介绍了Python中的高级数据结构详解,本文讲解了Collection.Array.Heapq.Bisect.Weakref.Copy以及Pprint这些数据结构的用法,需要的朋友可以参考 ...
- linux-2.6.26内核中ARM中断实现详解(转)
转载:http://www.cnblogs.com/leaven/archive/2010/08/06/1794293.html 更多文档参见:http://pan.baidu.com/s/1dDvJ ...
- 2020你还不会Java8新特性?方法引用详解及Stream 流介绍和操作方式详解(三)
方法引用详解 方法引用: method reference 方法引用实际上是Lambda表达式的一种语法糖 我们可以将方法引用看作是一个「函数指针」,function pointer 方法引用共分为4 ...
随机推荐
- 面试题:ArrayList、LinkedList、Vector三者的异同?
面试题:ArrayList.LinkedList.Vector三者的异同? 同:三个类都是实现了List接口(Collection的子接口之一),存储数据的特点相同:存储有序的.可重复的数据不同: * ...
- UI设计师、平面设计师常用的网站大全,初学者必备,大家都在用!
UI设计师.平面设计师常用的网站大全,初学者必备,大家都在用! 国外的花瓣--Pinterest • The world's catalog of ideas 颜格视觉--app界面设计大全--电商. ...
- MSSQL·查看数据库编码格式
阅文时长 | 0.67分钟 字数统计 | 837.6字符 主要内容 | 1.引言&背景 2.声明与参考资料 『MSSQL·查看数据库编码格式』 编写人 | SCscHero 编写时间 | 20 ...
- [bug] Maven:No valid Maven installation found.maven
原因 从别处复制来的项目,maven路径没有改过来 参考 https://blog.csdn.net/qq_40846086/article/details/81252736
- 【转载】复制文件到已存在的Jar
复制文件到已存在的Jar 技术标签: Ant OSGI Eclipse 脚本 配置管理 问题 这两天在写一个小东西.这个小东西是一个大东西的一部分.其实也就是其中的一两个类.而这个大东西需 ...
- 还可以使用 -c 参数来显示全部内容,并标出不同之处 diff -c test2.txt test1.txt
二.实例 在test目录下存放了两个文本文件,test1.txt test2.txt . 比较这两个文件的异同. diff test1.txt test2.txt "5c5& ...
- Linux如何查看文件的创建、修改时间?
Linux如何查看文件的创建.修改时间? 利用stat指令查看文件信息 三种时间的介绍 ATime --文件的最近访问时间 只要读取时间,ATime就会更新 MTime --文件的内容最近修改的时间 ...
- mysql基础之查询缓存、存储引擎
一.查询缓存 "查询缓存",就是将查询的结果缓存下载,如果查询语句完全相同,则直接返回缓存中的结果. 如果应用程序在某个场景中,需要经常执行大量的相同的查询,而且查询出的数据不会经 ...
- Linux中级之ansible配置(playbook)
一.playbooks 如果用模块形式一般有幂等性,如果用shell或者command没有幂等性 playbooks相当于是shell脚本,可以把要执行的任务写到文件当中,一次执行,方便调用 task ...
- STM32F4-IAP学习笔记--(转)
花了断断续续两天时间在STM32上面写了一个IAP(In Application Programing)Boot,期间多多少少还是遇到的了不少问题.现在就花点时间把这两天写的东西整理一下,就当是学习笔 ...