草庐IT

(超级详细)一文看懂指针、地址、引用

xiao张的da世界 2023-07-11 原文

!!!!一文看懂指针、地址、引用

内存地址

地址相当于是门牌号,每家都有一个门牌号,类似每一个分配的内存空间都有独一无二的地址,
在c++中为8字节的16个16进制的数(可以表示2的64次方个内存地址):
例如:000000604CAFF9E4

首地址

如果计算机分配的一块内存是连续的,那么第一个元素的内存空间地址就称为首地址

如数组、函数体,的存储方式为连续存储
例如:数组首地址加上一段地址的变化就能得到相应元素的地址,进而取得数组任意元素的值;
所以我们可以直接用首地址+地址增量来表示这一段内存的全部地址

例如:一栋楼一共有100户,我家在一单元一楼,门牌号为0x01001(假设从一开始);
你家在6楼,咱们中间一共有4个楼层(0x01002,0x01003,0x01004,0x01005),
那么你家的门牌号就为0x01006,依次类推第100楼的门牌号就为0x01100;
通过我家的门牌号跟楼层数就能得到所有楼层的门牌号;

指针的定义:

指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(Pointed to)存在电脑存储器中另一个地方的值。

简单的说:指针是一个变量,且这个变量是专门用来存放地址的。

举个栗子:你想给你朋友打电话,你朋友的电话号为12345678,(你记不住)你拿一个小本本记下来,这个小本本就是指针变量,里面记的东西就是电话号,也就是地址,以后你想找你朋友都可以通过小本本里的电话号联系到。

1、指针变量

普通用法
既然叫做变量里面存储的肯定是常量,这个常量就是-----地址;
指针变量的定义形式为:

int * p ;//表示声明了一个能指向int类型内存空间的指针

其中p为一个指针变量,里面可以存int类型普通变量的地址;

int a = 10;//定义一个int类型的变量,变量的值为10;

a为一个普通变量,值为10;

p = &a;//表示p存了变量a的地址,这时候的p指向的就是a的内存空间;

如果这时候输出p,输出的为a的16进制地址;
输出结果为:000000A39A6FF9F4

这时候想要取a的值就有两种情况:
第一种:直接输出变量;

cout << a << endl;

第二种:通过指针取值;

cout << *p << endl;

其中*号在此时作为取值符

特殊用法
用指针定义数组:

int *d;

这是一个指针变量,里面可以存int类型普通变量的地址,也可以存int类型数组的首地址;
当指针变量中存的值为一块存放变量类型为int的连续内存的首地址时,指针变量可以当作数组;
类似为:

	int *d;
	int b[10];//假设分配10个内存空间
	d = b;

这时候d与b用法类似。
当不知道需要创建多大的数组的时候就可以使用指针来动态创建

int *Arr = (int*)malloc(sizeof(int));//int类型的动态数组

直观一点,直接上代码

#include <iostream>
using namespace std;

void main()
{
	int *c;
	int a = 10;
	char m = 'a';
	char* p;
	p = &m;
	c = &a;

	int *d;
	int b[10];
	b[0] = 11;
	b[1] = 12;
	d = b;

	int *Arr = (int*)malloc(sizeof(int));//动态数组
	Arr[0] = 30;
	Arr[1] = 31;

	//由于<<操作符重载,遇到字符型指针默认当作字符串,所以需要强制转换
	cout << "p:	" << static_cast<void *>(p) << endl;//输出16进制地址(m的地址)

	cout << "c:	" << c << endl;//输出16进制地址(a的地址)

	cout << "*c:	" << *c << endl;//输出a的值:10

	cout << "a:	" << a << endl;//输出a的值:10

	cout << "b[0]:	" << b[0] << endl;//输出b[0]的值:11

	cout << "&b[0]:	" << &b[0] << endl; //输出16进制地址(b[0]的地址)

	cout << "d:	" << d << endl;//输出16进制地址(b[0]的地址)

	cout << "d[1]:	" << d[1] << endl;//输出b[1]的值:12

	cout << "Arr:	" << Arr << endl;//输出Arr[0]的地址

	cout << "Arr[1]:	" << Arr[1] << endl;//输出Arr[1]的值:31
}

输出结果为:

总结:
1、指针变量是一种变量(有内存也有地址),里面存的是地址。
2、地址指向的是空间,所以指针指向内存空间。(所以经常说某个指针指向某一块内存)
3、指针变量存的地址可以是连续内存里的首地址(可以用作数组)

2、 数组指针

这是一个文字理解,实际上可以理解为数组的指针(是指针,指向数组第一个元素的内存空间):
定义为:

int (*p)[10];

其中括号()的优先级最高,所以说这里的整个*p为一个指针变量的定义,p为指针,指向的是一个数组(匿名数组),数组里面存的是int类型的元素;(规定了数组的大小)

这里解释一下,上面定义的10个数组每个数组相当于一个指针。
也就是说每个数组元素就是一个数组样子的指针);
第一个指针为p[0],第二个为p[1]。。。。,所以说元素p[0]实际上是一个地址,*p[0]就相当于是取p[0]存的地址的值(首地址),也就是取p[0][0]的元素。

总结:数组指针是数组样子的指针;
(下面上代码)
例子:

#include <iostream>
using namespace std;

void main()
{
	int arr[10];
	arr[0] = 10;
	arr[1] = 11;
	arr[2] = 12;


	int(*p)[10] = &arr;//赋值的是地址,所以是指针
	p[0][0] = 0;
	p[0][1] = 1;
	p[0][2] = 2;
//数组指针
	cout << "p[0][0]:" << p[0][0] << endl;
	//输出为0;输出第一个指针p[0]指向连续存储空间的的第一个元素
	cout << "p[0][1]:" << p[0][1] << endl;
	//输出为1;输出第一个指针p[0]指向连续存储空间的的第二个元素
	cout << "p[0][2]:" << p[0][2] << endl;
	//输出为2;输出第一个指针p[0]指向连续存储空间的的第三个元素

	cout << "p[0]:" << p[0] << endl;//输出为第一个指针中存的地址,也就是arr的地址

	cout << "p[1]:" << p[1] << endl;//输出为第二个指针中存的地址,未赋值,

	cout << "p[2]:" << p[2] << endl;//输出为第三个指针中存的地址,未赋值

	cout << "p[3]:" << p[3] << endl;//输出为第四个指针中存的地址,未赋值

	cout << "*p[0]:" << *p[0] << endl;
	//输出为输出为0;第一个指针p[0]指向连续存储空间的的第一个元素
	cout << "*p[1]:" << *p[1] << endl;
	//输出为输出为(-)无穷;第二个指针p[1]指向连续存储空间的的第一个元素
	cout << "arr[0]:" << arr[0] << endl;
	//输出arr[0]的值
	cout << "&arr[0]:" << &arr[0] << endl;
	//输出arr[0]的地址,也就是arr的地址
	cout << "arr[1]:" << arr[1] << endl;
	//输出arr[1]的值
	cout << "&arr[1]:" << &arr[1] << endl;
	//输出arr[1]的地址
	
}

3、 指针数组

理解为指针的数组(是数组,里面存的是指针,跟二级指针类似):
定义为

int *p[10];

其中[]的优先级最高,表示它是一个数组。首先申请了一个空间为【10】的数组,然后数组里面存的是指向int类型的指针,p指向的是这个指针数组首地址的内存空间;
二级指针的定义:

int **p;

与二级指针的区别在于预先定义的指针数组的一维大小;

解释一下,p为数组的名字,申请了10个空间,每个空间里面存的是int *(指向存放int类型空间的指针),所以说p[0]里面存的是指针,与上面数组指针不同的是,数组指针是申请的空间本身就是指针(只不过名字看起来像数组),这里申请的是数组,但是里面存的是指针。

总结:指针数组实际上就是内存空间里面存的是指针的数组;

#include <iostream>
using namespace std;

void main()
{
	int arr[10];
	arr[0] = 10;
	arr[1] = 11;
	arr[2] = 12;


	int*p[10] = {arr};
	//首先赋值方式不一样就能体现是指针还是数组,这是数组的一般赋值方式;
	p[0][0] = 0;
	p[0][1] = 1;
	p[0][2] = 2;

//指针数组
	cout << "p[0]:" << p[0] << endl;//指针的值(也就是arr的首地址),输出为16位16进制的地址
	cout << "p[1]:" << p[1] << endl;//输出为16位16进制的0;
	cout << "&p[0]:" << &p[0] << endl;//输出为p[0]这个存储空间的地址
	cout << "&p[1]:" << &p[1] << endl;//输出为p[1]这个存储空间的地址
	
	cout << "*p[0]:" << *p[0] << endl;
	//输出为p[1]这个存储空间存的值指向空间的值(有点yao口)
	//p[0]是指针的值,里面最终存的是地址
	//这么一来跟指针变量的用法一样,*号可以取相应地址的值
	
	cout << "p[0][0]:" << p[0][0] << endl;
	//输出arr[0]的值
	cout << "p[0][1]:" << p[0][1] << endl;
	//输出arr[1]的值
	cout << "&p[0][0]:" << &p[0][0] << endl;
	//实际上就是arr[0]的地址也就是arr首地址
	cout << "&p[0][1]:" << &p[0][1] << endl;
	//实际上就是arr[1]的地址
 	cout << "arr[0]:" << arr[0] << endl;
 	//输出arr[0]的值
 	cout << "&arr[0]:" << &arr[0] << endl;
 	//输出arr[0]的地址,也就是arr的地址
 	cout << "arr[1]:" << arr[1] << endl;
 	//输出arr[1]的值
 	cout << "&arr[1]:" << &arr[1] << endl;
 	//输出arr[1]的地址
}

4、函数指针

函数指针是一个指针,因为函数跟数组一样是一个连续的存储空间,所以函数指针可以指向函数的首地址从而调用;
函数指针定义为:

int (*fun)(int x,int y);

表示的是这个fun指针可以指向类型为返回值为int类型,参数为int x,int y的函数的那一段内存空间

例如两个函数声明为:

int func(int x, int y);
int fun0000(int x);

定义为:

int func(int x, int y)
{

	return 0;
}
int fun0000(int x)
{
	
	return 0;
}

函数指针的定义为:

int(*fun)(int x, int y);

调用类型为:

fun = &func;

如果类型不一样则:

5、指针函数

指针函数实际上就是函数,只不过返回值为指针;
看看下面这个函数声明:

int func();

这只是一个没有参数的,返回值为int类型数据的普通函数;
再看看指针函数的定义:

int* func();

仅仅多了一个*号,其返回值是一个 int 类型的指针,是一个地址值。
实际上指针函数和普通函数对比不过就是其返回了一个指针(即地址值)而已。

6、特殊指针

①空指针

定义为:

// Define   NULL   pointer   value 
#ifndef   NULL 
#   ifdef   __cplusplus 
#     define   NULL      0 
#   else 
#     define   NULL      ((void   *)0) 
#   endif 
#endif //   NULL 
short *pa=NULL;//pa保存0地址,pa保存空指针
short *pa = 0;

它指示指针实际上并不涉及任何有效的内存地址。
该特殊值称为空指针(null pointer),并在内部表示为值0,NULL 是一个标准规定的宏定义,用来表示空指针常量。

②Void类型指针

无类型指针,这个就厉害了,在定义上是无类型指针,但是实际上用起来却包罗万象
通过强制类型转换可以变为所有类型的指针;
例如可以将上文函数指针赋值给它:

int function(int x, int y)
{

	return 0;
}
	int(*fun)(int x, int y);

	fun = &function;
	void * p_void;
	p_void = fun;

可以将函数地址赋值给它:

	int(*fun)(int x, int y);

	fun = &func;
	void * p_void;
	p_void = &function;

甚至可以将函数指针的地址赋值给它:

	int(*fun)(int x, int y);

	fun = &func;
	void * p_void;
	p_void = &fun;

这就是无中生有,道法自然 😊😊😊😊😊

③nullptr指针

C语言:NULL
C++03前:0
C++11:nullptr

nullptr是C++11 新标准引入的方法,在之前使用的是NULL,NULL是一个预处理变量,它的值为0。

int *p1 = nullptr; // 等价于int *p1 = 0
int *p2 = 0;         // 直接将p2初始化为字面常量0
int *p3 = NULL // 等价于int *p3 = 0

④结构体指针(类指针)

结构体(类)类型的指针,也就是指向的是结构体(类)类型的空间的指针。
例如:

class Point
{
int x;
int y 
};
struct Student
{
char* name;
char* sex;
int number;
int age;
};

定义结构体指针为:

Point *pt;
Student *stu;

使用为:

pt->x = 100;
pt->y = 100;
stu->name = "卡尔曼确实慢";
stu->sex  = "未知";
stu->number  = 1;
stu->age  = 23;

⑤this指针

成员函数中都包含一个特殊的指针,这个指针的名字是固定的——this。它是当前类对象的指针。
1)对象o.成员函数x(…){…},则函数体中的this表示“&o”;
2)对象指针p.成员函数x(…){…},则函数体中的this表示“p”;
3)对象引用r.成员函数x(…){…},则函数体中的this表示“&r”;

引用

一句话,引用相当于是别名,指的是同一个东西。
引用常用于函数中的参数传递。

例子

	int a = 1;
	int b = 1;
	int &x = a;
	x = 20;

	cout << a << endl;

结果为20;

与取地址容易混淆的地方:
等号左边或者无等号用&就是引用,
等号右边用&就是取地址。

有关(超级详细)一文看懂指针、地址、引用的更多相关文章

  1. ruby - 从 Ruby 中的主机名获取 IP 地址 - 2

    我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge

  2. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

  3. 在VMware16虚拟机安装Ubuntu详细教程 - 2

    在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主

  4. ruby - Chef LW 资源属性默认值如何引用另一个属性? - 2

    我正在尝试将一个资源属性的默认值设置为另一个属性的值。我正在为我正在构建的tomcat说明书定义一个资源,其中包含以下定义。我想要可以独立设置的“名称”和“服务名称”属性。当未设置服务名称时,我希望它默认为为“名称”提供的任何内容。以下不符合我的预期:attribute:name,:kind_of=>String,:required=>true,:name_attribute=>trueattribute:service_name,:kind_of=>String,:default=>:name注意第二行末尾的“:default=>:name”。当我在Recipe的新block中引用我

  5. ruby - 在 Ruby 中,为什么 Array.new(size, object) 创建一个由对同一对象的多个引用组成的数组? - 2

    如thisanswer中所述,Array.new(size,object)创建一个数组,其中size引用相同的object。hash=Hash.newa=Array.new(2,hash)a[0]['cat']='feline'a#=>[{"cat"=>"feline"},{"cat"=>"feline"}]a[1]['cat']='Felix'a#=>[{"cat"=>"Felix"},{"cat"=>"Felix"}]为什么Ruby会这样做,而不是对object进行dup或clone? 最佳答案 因为那是thedocumenta

  6. ruby-on-rails - 验证电子邮件地址是 Paypal 用户 - 2

    我想验证一个电子邮件地址是否是PayPal用户。是否有API调用来执行此操作?是否有执行此操作的ruby​​库?谢谢 最佳答案 GetVerifiedStatus来自PayPal'sAdaptiveAccounts平台会为您做这件事。PayPal没有任何codesamples或SDKs用于Ruby中的自适应帐户,但我确实找到了编写codeforGetVerifiedStatusinRuby的人.您需要更改该代码以检查他们拥有的帐户类型的唯一更改是更改if@xml['accountStatus']!=nilaccount_status

  7. ruby - 引用具有指定索引的枚举器值 - 2

    假设我有一个可枚举对象enum,现在我想获取第三个项目。我知道一种通用方法是转换成数组,然后使用索引访问,如:enum.to_a[2]但这种方式会创建一个临时数组,效率可能很低。现在我使用:enum.each_with_index{|v,i|breakvifi==2}但这非常丑陋和多余。执行此操作最有效的方法是什么? 最佳答案 你可以使用take剥离前三个元素,然后剥离last从take给你的数组中获取第三个元素:third=enum.take(3).last如果您根本不想生成任何数组,那么也许:#Ifenumisn'tanEnum

  8. ruby 变量作为同一对象(指针?) - 2

    >>a=5=>5>>b=a=>5>>b=4=>4>>a=>5如何将“b”设置为实际的“a”,以便在示例中,变量a也将变为4。谢谢。 最佳答案 classRefdefinitializeval@val=valendattr_accessor:valdefto_s@val.to_sendenda=Ref.new(4)b=aputsa#=>4putsb#=>4a.val=5putsa#=>5putsb#=>5当您执行b=a时,b指向与a相同的对象(它们具有相同的object_id).当你执行a=some_other_thing时,a将指向

  9. 100个python算法超详细讲解:画直线 - 2

    1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva

  10. H2数据库配置及相关使用方式一站式介绍(极为详细并整理官方文档) - 2

    目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式

随机推荐