重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
go语言中的指针和地址值,在使用上常常具有迷惑性,主要是其特殊的*、符号的使用,可能会让你摸不透,本文希望能讲清楚go语言的指针(pointer)和值(value)。
我们提供的服务有:成都网站设计、成都网站建设、微信公众号开发、网站优化、网站认证、望都ssl等。为1000+企事业单位解决了网站和推广的问题。提供周到的售前咨询和贴心的售后服务,是有科学管理、有技术的望都网站制作公司
这里先简单的对指针和地址值概念做一个定义:
这是因为go方法传递参数的方式导致的,go方法函数传递参数传递的是一个拷贝,看看下面的程序会输出什么?
答案是8,而不是9,因为AddAge函数修改的是学生的一个备份,而不是原始的学生对象
如果你想正确的给学生年龄增加的话,函数传递的需要是这个值的指针,如下所示:
需要注意的是,这里我们的指针传递的仍然是一个拷贝,比如,如果你将s赋值给另外一个指针地址,不会影响原有的指针,这点可以自行实践下。
那在使用go语言开发的时候,何时该用指针何时改用地址值呢?比如考虑以下场景:
简单原则: 当你不确定该使用哪种的时候,优先使用指针
如果考虑在数组、切片、map等复合对象中使用指针和值,比如:
很多开发者会认为b会更高效,但是被传递的都是一个切片的拷贝,切片本身就是一个引用,所以这里被传递的其实没有什么区别。
对于指针和地址值的使用,大家需要牢记的一点就是go数据传递的不可变性,活学活用此特点,在无状态函数中此特性非常有用。
tips: *号,可以指向指针类型内存地址上的值,号,可以获取值类型的内存地址
每一个变量都有内存地址,可以通过变量来操作内存地址中的值,即内存的大小
go语言中获取变量的内存地址方法:通过 符号可以获取变量的地址
定义:普通变量存储的是对应类型的值,这些类型就叫值类型
变量b,在内存中的地址为:0x1040a124,在这个内存地址上存储的值为:156
定义:指针类型的变量存储的是⼀个地址,所以⼜叫指针类型或引⽤类型
b 是值类型,它指向的是内存地址上的值
a是指针类型,它指向的是b的内存地址
指针类型定义,语法: var 变量名 *类型
指针类型在定义完成后,默认为空地址,即空指针(nil)
在定义好指针变量后,可以通过***** 符号可以获取指针变量指向的变量
在这里的 *a 等价于 b,通过修改 *a ,最终修改的是值类型b的值
这里a,d是值类型,b,c是指针类型
d就相当于把a内存地址上值,在内存中从新开辟了一块空间存储,d和a互不影响
b,c相当于指向了a的内存地址,当使用*号引用出内存地址上的变量上,修改值得,a的值也会跟着改变
野指针指向一个已删除的对象或未申请访问受限内存区域的指针。
与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。需对指针进行初始化。野指针主要是因为这些疏忽而出现的删除或申请访问受限内存区域的指针。
扩展资料
指针变量未初始化
任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。如果没有初始化,编译器会报错“ ‘point’ may be uninitializedin the function ”。
指针释放后之后未置空
有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。
参考资料来源:百度百科-野指针
野指针,也就是指向不可用内存区域的指针。通常对这种指针进行操作的话,将会使程序发生不可预知的错误。首先请诸位看以下一段“危险”的C++代码:
void function( void )
{
char* str = new char[100];
delete[] str;
// Do something
strcpy( str, "Dangerous!!" );
}
之所以说其危险,是因为这是一段完全合乎语法的代码,编译的时候完美得一点错误也不会有,然而当运行到strcpy一句的时候,问题就会出现,因为在这之前,str的空间已经被delete掉了,所以strcpy当然不会成功。对于这种类似的情况,在林锐博士的书中有过介绍,称其为“野指针”。
那么,诸位有没有见过安全的“野指针”呢?下面请看我的一段C++程序,灵感来自CSDN上的一次讨论。在此,我只需要C++的“类”,C++的其余一概不需要,因此我没有使用任何的C++标准库,连输出都是用printf完成的。
#include stdio.h
class CTestClass
{
public:
CTestClass( void );
int m_nInteger;
void Function( void );
};
CTestClass::CTestClass( void )
{
m_nInteger = 0;
}
void CTestClass::Function( void )
{
printf( "This is a test function.\n" );
}
void main( void )
{
CTestClass* p = new CTestClass;
delete p;
p-Function();
}
OK,程序到此为止,诸位可以编译运行一下看看结果如何。你也许会惊异地发现:没有任何的出错信息,屏幕上竟然乖乖地出现了这么一行字符串:
This is a test function.
奇怪吗?不要急,还有更奇怪的呢,你可以把主函数中加上一句更不可理喻的:
((CTestClass*)NULL)-Function();
这仍然没有问题!!
我这还有呢,哈哈。现在你在主函数中这么写,倘说上一句不可理喻,那么以下可以叫做无法无天了:
int i = 888;
CTestClass* p2 = (CTestClass*)i;
p2-Function();
你看到了什么?是的,“This is a test function.”如约而至,没有任何的错误。
你也许要问为什么,但是在我解答你之前,请你在主函数中加入如下代码:
printf( "%d, %d", sizeof( CTestClass ), sizeof( int ) );
这时你就会看到真相了:输出结果是——得到的两个十进制数相等。对,由sizeof得到的CTestClass的大小其实就是它的成员m_nInteger的大小。亦即是说,对于CTestClass的一个实例化的对象(设为a)而言,只有a.m_nInteger是属于a这个对象的,而a.Function()却是属于CTestClass这个类的。所以以上看似危险的操作其实都是可行且无误的。
现在你明白为什么我的“野指针”是安全的了,那么以下我所列出的,就是在什么情况下,我的“野指针”不安全:
在成员函数Function中对成员变量m_nInteger进行操作;
将成员函数Function声明为虚函数(virtual)。
以上的两种情况,目的就是强迫野指针使用属于自己的东西导致不安全,比如第一种情况中操作本身的m_nInteger,第二种情况中变为虚函数的Function成为了属于对象的函数(这一点可以从sizeof看出来)。
其实,安全的野指针在实际的程序设计中是几乎毫无用处的。我写这一篇文章,意图并不是像孔乙己一样去琢磨回字有几种写法,而是想通过这个小例子向诸位写明白C++的对象实例化本质,希望大家不但要明白what和how,更要明白why。李马二零零三年二月二十日作于自宅。
关于成员函数CTestClass::Function的补充说明
这个函数是一个普通的成员函数,它在编译器的处理下,会成为类似如下的代码:
void Function( const CTestClass * this ) // ①
{
printf("This is a test function.\n");
}
那么p-Function();一句将被编译器解释为:
Function( p );
这就是说,普通的成员函数必须经由一个对象来调用(经由this指针激活②)。那么由上例的delete之后,p指针将会指向一个无效的地址,然而p本身是一个有效的变量,因此编译能够通过。并且在编译通过之后,由于CTestClass::Function的函数体内并未对这个传入的this指针进行任何的操作,所以在这里,“野指针”便成了一个看似安全的东西。
然而若这样改写CTestClass::Function:
void CTestClass::Function( void )
{
m_nInteger = 0;
}
那么它将会被编译器解释为:
void Function( const CTestClass * this )
{
this-m_nInteger = 0;
}
你看到了,在p-Function();的时候,系统将会尝试在传入的这个无效地址中寻找m_nInteger成员并将其赋值为0,剩下的我不用说了——非法操作出现了。
至于virtual虚函数,如果在类定义之中将CTestClass声明为虚函数:
class CTestClass
{
public:
// ...
virtual void Function( void );
};
那么C++在构建CTestClass类的对象模型时,将会为之分配一个虚函数表vptr(可以从sizeof看出来)。vptr是一个指针,它指向一个函数指针的数组,数组中的成员即是在CTestClass中声明的所有虚函数。在调用虚函数的时候,必须经由这个vptr,这也就是为什么虚函数较之普通成员函数要消耗一些成本的缘故。以本例而言,p-Function();一句将被编译器解释为:
(*p-vptr[1])( p ); // 调用vptr表中索引号为1的函数(即Function)③
上面的代码已经说明了,如果p指向一个无效的地址,那么必然会有非法操作。
备注:
①关于函数的命名,我采用了原名而没有变化。事实上编译器为了避免函数重载造成的重名情况,会对函数的名字进行处理,使之成为独一无二的名称。
②将成员函数声明为static,可以使成员函数不经由this指针便可调用。
③vptr表中,索引号0为类的type_info。