在计算机存储数据时必须要知道三个基本要素:信息存储在何处?存储的值为多少?存储的值是什么类型?因此指针是表示信息在内存中存储地址的一类特殊变量,指针和其所指向的变量就像是一个硬币的两面。指针一直都是学习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_datadata 就是一个变量的两个不同叫法而已。引用必须在声明时就为其指定初始值,而不能像指针一样可以先声明,再赋值。下面将引用作为函数的参数来进一步说明引用与指针的区别:

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++中指针与引用详解的更多相关文章

  1. 关于C中指针的引用,解引用与脱去解引用

    *,& 在指针操作中的意义 (1)* 大家都知道在写int *p 时,*可以声明一个指针.很少人知道*在C/C++中还有一个名字就是"解引用".他的意思就是解释引用,说的通 ...

  2. Bootstrap排版中地址与引用详解

    地址元素address 我们的地址在HTML5中增加了一个address标签,可以把我们的地址写在address标签里面,address里面强调换行等等都是可以的. 实例: <address&g ...

  3. php中关于引用(&)详解

    php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...

  4. c++中vector的用法详解

    c++中vector的用法详解 vector(向量): C++中的一种数据结构,确切的说是一个类.它相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间 ...

  5. C++引用(&)详解

    C++引用详解 引用的概念 引用:就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样. 引用的声明方法:类型标识符 &引用名=目标变量名: 如下:定义引用ra,它是变量a的引 ...

  6. BAT批处理中的字符串处理详解(字符串截取)

    BAT批处理中的字符串处理详解(字符串截取)   BAT批处理中的字符串处理详解(字符串截取 批处理有着具有非常强大的字符串处理能力,其功能绝不低于C语言里面的字符串函数集.批处理中可实现的字符串处理 ...

  7. Python中的高级数据结构详解

    这篇文章主要介绍了Python中的高级数据结构详解,本文讲解了Collection.Array.Heapq.Bisect.Weakref.Copy以及Pprint这些数据结构的用法,需要的朋友可以参考 ...

  8. linux-2.6.26内核中ARM中断实现详解(转)

    转载:http://www.cnblogs.com/leaven/archive/2010/08/06/1794293.html 更多文档参见:http://pan.baidu.com/s/1dDvJ ...

  9. 2020你还不会Java8新特性?方法引用详解及Stream 流介绍和操作方式详解(三)

    方法引用详解 方法引用: method reference 方法引用实际上是Lambda表达式的一种语法糖 我们可以将方法引用看作是一个「函数指针」,function pointer 方法引用共分为4 ...

随机推荐

  1. 前端必读:Vue响应式系统大PK

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://www.sitepoint.com/vue-3-reactivity-system ...

  2. 【近取 Key】Alpha - 项目展示

    项目与团队亮点 一.团队成员与分工简介 成员组成与分工 本团队由 6 名成员组成,其中有 3 名 PM,2 名后端开发人员与 4 名前端开发人员,由于组内成员数量有限,因此所有 PM 均需同时兼领开发 ...

  3. opencv实战——图像矫正算法深入探讨

    摘要 在机器视觉中,对于图像的处理有时候因为放置的原因导致ROI区域倾斜,这个时候我们会想办法把它纠正为正确的角度视角来,方便下一步的布局分析与文字识别,这个时候通过透视变换就可以取得比较好的裁剪效果 ...

  4. 自定义元类 __call__,__init__,__new__总结

    只要对象能被调用 产生对象的类里必然有__call__方法 在调用类时,必定先触发type里的__call__ __call__下有: 1.产生对象的object.__new__ 2..被调用的类自己 ...

  5. Python运算符 - Python零基础入门教程

    目录 一.算术运算符 二.赋值运算符 三.比较运算符 四.运算符的优先等级 五.重点总结 六.猜你喜欢 零基础 Python 学习路线推荐 : Python 学习目录 >> Python ...

  6. 一文详解 Linux 系统常用监控工一文详解 Linux 系统常用监控工具(top,htop,iotop,iftop)具(top,htop,iotop,iftop)

    一文详解 Linux 系统常用监控工具(top,htop,iotop,iftop)     概 述 本文主要记录一下 Linux 系统上一些常用的系统监控工具,非常好用.正所谓磨刀不误砍柴工,花点时间 ...

  7. C语言风格的 for 循环(SHELL的循环写法 已验证20200517)

    C语言风格的 for 循环 C语言风格的 for 循环的用法如下: for((exp1; exp2; exp3))do    statementsdone 几点说明: exp1.exp2.exp3 是 ...

  8. Bash shell的特性

    bash的特性 命令补全 Tab键 ​ 命令补全 ​ 路径补全 ​ 选项补全 ​ yum install -y bash-completion ​ -长格式 ​ --all ​ -短格式 ​ -a 快 ...

  9. Redis I/O 多路复用技术原理

    引言 Redis 是一个单线程却性能非常好的内存数据库, 主要用来作为缓存系统. Redis 采用网络 I/O 多路复用技术来保证在多个连接时,系统的高吞吐量(TPS). 系统吞吐量(TPS)指的是系 ...

  10. STM32 中的HARDFAULT 的查找方法

    http://blog.csdn.net/zyboy2000/article/details/7668331