指针

  用指针变量可以表示各种数据结构,能很方便的使用数组、字符串和链表,并能像汇编语言一样处理内存地址,从而写出精炼而高效的程序。但是由于指针不并不是直接操作数据,而且它可以直接于内存打交道,使用稍有不慎,就会造成程序崩溃,所以在使用指针时一定要深刻理解与指针相关的一些问题。

1、使用指针有哪些好处?

  指针包含的是一个指向内存某个位置的地址。他可以带来以下几个好处:

  1. 可以动态处理内存。
  2. 进行多个相似变量的一般访问。
  3. 为动态数据结构,尤其是树和链表,提供支持。
  4. 遍历数组,如解析字符串。
  5. 高效的按引用“复制”数组与结构,特别是作为函数参数的时候,可以按照引用传递函数参数,提高开发效率。

2、引用还是指针

  定义一个引用的一般格式是:

类型标识符 &引用名=已经定义的变量名

  程序设计中的引用还有别名的意思,它用来定义一个变量来共享另一个变量的内存空间,变量是一个内存空间的名字,如果给内存空间起另一个名字,那就能够共享这个内存了,进而提高程序的开发效率。指针指向另一个内存空间的变量,可以通过它来索引另一个内存空间的内容,而指针本身也有自己的空间。

  引用与指针有着相同的地方,即指针指向一块内存,它的内容是所指向内存的地址,引用是某块内存的别名。但是,两者并非完全相同,它们之间也存在着差别,具体表现在以下几个方面:

  1. 从本质上讲,指针是存放变量地址的一个变量,在逻辑上是独立的,他可以被改变,即其所指向的地址可以被改变,其指向的地址中所存放的数据也可以被改变。而引用则只是一个别名而已,他在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且引用的对象在其整个生命周期中是不能被改变的,即自始自终只能依附于同一个变量,具有“从一而终“的特性。
  2. 作为参数传递时,两者不同。在C++语言中,指针与引用都可以用于函数的参数传递,但是指针传递参数和引用传递参数有着本质的不同。指针传递参数本质上是值传递的方式,他所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在占中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值。而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在占中开辟了内存空间,但是这是存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。虽然他们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它讲影响不到主调函数的相关变量。如果像通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。
  3. 引用使用时不需要解引用(*),而指针需要解引用。
  4. 引用只能在定义时被初始化一次,之后不能被改变,即引用具有”从一而终“的特性。而指针却是可变的,指针的初始化不是指指针的定义,而是指针变量存储的数值是个无效的数值。例如,定义float a,该语句表示a会分配一个地址,但初始值是一个随机的值,同样,float *a也会为a分配一个地址,初始值也是随机的值,初始化可以将a=NULL,这样在后面的程序中可以增加If(a==NULL)来判断指针是否有效,否则不行,或者,为指针分配指定空间,如float *a=new float 或者float b;float  *a=&b,都可以为指针指向一块内存以实现初始化。
  5. 引用不可以为空。而指针可以为空。引用必须与存储单元相对应,一个引用对应一个存储单元。
  6. 对引用进行sizeof操作得到的是所指向的变量(对象)的大小,而对指针进行sizeof操作得到的是指针本身(所指向的变量或对象的地址)的大小,typeid(T)==typeid(T&)恒为真,sizeof(T)==sizeof(T&)恒为真,但是当引用作为成员时,其占用空间和指针相同。
  7. 指针和引用的自增(++)运算意义不一样。
  8. 如果返回动态分配的对象或内存,必须使用指针,引用可能引起内存泄露。

  由于引用与指针的区别,所以并非所有使用指针的地方都可以使用引用,也并非所有使用引用的地方都可以使用指针,两者的使用也有其特定的环境。以如下实例为例进行分析。

1、int *a;int *&p=a;int b=8;p=&b;//正确,指针变量的引用
     void & a=3;//不正确,没有变量或对象的类型是void
    int & ri=NULL;//不正确,有空指针,无空引用
2、int & ra=int;//不正确,不能用类型来初始化
     int *p=new int ;//正确
3、引用不同于一般变量,下面的声明类型是非法的:
    int &b[3];//不能建立引用数组
    int &*p;//不能建立指向引用的指针
    int &&r;//不能建立引用的引用
4、当使用&运算符取一个引用的地址时,其值为所引用变量的地址。

 通过上面的实例可以发现,引用与指针都有其特定的使用场景,所以该使用指针时就使用指针,该使用引用时就使用引用,不可混淆。

 

3、指针和数组是否表示同一概念

  指针可以随时指向任意类型的内存块,而数组可以在静态存储区被创建。例如,全局数组可以在栈上被创建。从原理与定义上看,虽然指针与数组表示的是不同的概念,但指针却可以方便的访问数组或者模拟数组,两者存在着一种貌似等价的关系,但也存在着诸多不同之处,主要表现在以下两个方面:

  (1)修改内容不同

  例如,char a[]="hello",可以通过取下标的方式对齐元素值进行修改。例如,a[0]=’X'是正确的,而对于char *p="world",此时p指向常量字符串,所以p[0]='X'是不允许的,编译会报错。

  (2)所占字节数不同

   例如,char *p="world",p为指针,则sizeof(p)得到的是一个指针变量的字节数,而不是P所指向的内存容量。C/C++语言没有办法知道指针所指的内存容量,除非在申请内存时标记出来。

char a[]="hello world";
char *p=a;

  在32位机上,sizeof(a)=12字节,而sizof(p)=4字节。

  但需要注意的时,当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

void Func(char a[100])
{
    cout<<sizeof(a);
}

  此时sizeof(a)=sizeof(int*)=4,而不是sizeof(int)*100=400.

4、指针进行强制类型转换后与地址进行加法运算,结果是什么

  假设在32位机器上,在对齐为4的情况下,sizeof(long)的结果为4字节,sizeof(char *)的结果为4字节,sizeof(short int)的结果与sizeof(shrot)的结果都为2字节,sizeof(char)的结果为1字节,sizeof(int)的结果为4字节,由于32位机器上是4字节对齐,以以下结构体为例:

struct Test
{
    long num;
    char *num;
    short  int data;
    char ha;
    short ba[5];
}*p

  当p=0x1000000;则p+0x200=?(Ulong)p+0x200=?,(char*)p+0x200=?

  其实,在32位机器下,sizeof(struct Test)=sizeof(*p)=4+4+2+1+1/*补齐*/+2*5+2/*补齐*/=24字节,而p=0x1000000,那么p+0x200=0x1000000+0x200*24指针加法,加出来的是指针类型的字节长度的整数倍,就是p偏移sizeof(p)*0x200.

  (Ulong)p+0x200=0x1000000+0x200经过Ulong后,已经不再是指针加法,而变成了一个数值加法了。

  (char*)p+0x200=0x1000000+0x200*sizeof(char)结果类型是char *。

 

5、指针能否进行>、<、>=、<=运算

  不可以,对于指针,只能进行==和!=运算。

 

6、指针与数字相加的结果是什么

  下面是一个示例代码:

#include "stdafx.h"
#include<stdio.h>

int main()
{
	unsigned char *p1;
	unsigned long *p2;
	p1 = (unsigned char *)0x801000;//给指针变量赋值,把十六进制0x801000放到字符指针变量中,即指针变量p1的值就是0x801000.
      p2 = (unsigned long *)0x810000; printf("%x\n", p1 + 5); printf("%x\n", p2 + 5); return 0; 
}

  输出结果如下:

 801005是因为指针变量指向的值字符+1表示指针向后移动1个字节,那么加5代表向后移动5个字节,所以输出801005.

 p2+5的值是801014,因为指针变量指向的是长整型,加1表示指针向后移动4个字节,那么加5表示向后移动4个字节长度,那么+5表示向后移动5*4=20个字节,所以输出为810014

 

7、野指针与空指针

  野指针是指指向不可以类存的指针。野指针的产生情况有以下三种

  (1)任何指针变量被创建的时候,不会自动成为NULL(空)指针,即默认值是随机的,所以指针变量在创建的同时应当被初始化,或者将其设置为NULL,或者让它指向合法的内存,而不应该放之不理,否则就会变成野指针。

   (2)由于指针被释放(free或delete)后,未能将其设置为NULL,也会导致该指针变成野指针。虽然free或delete把指针所指的内存给释放掉了,但它们并没有把指针本身释放掉,一般可以采用语句if(p!=NULL)进行防错处理,但是if语句却起不到防错作用,因为即使p不是NULL指针,它也不指向合法的内存块。

  (3)第三种造成野指针的原因是指针操作超越了变量的作用范围。示例如下:

#include "stdafx.h"
#include<stdio.h>
#include <stdlib.h>
#include<string.h>
int main()
{
	char *p = (char *)malloc(100);
	strcpy(p, "hello");
	free(p);
		if(p != NULL)
			printf("Not NULL\n");
	return 0;
}

  运行结果如下:

   上例中,虽然对p执行了free操做,p所指的内存被释放掉了,但是p所指的地址仍然不变,在后续的判断p是否为NULL时,根本没有起到防错的作用,所以程序输出仍然为Not NULL。

  

  空指针是一个特殊的指针,也是唯一一个对任何指针类型都合法的指针。指针变量具有空指针指,表示它当时处于闲置状态,没有指向有意义的内容。为例提高程序的可读性,标准库定义了一个与0等价的NULL。

  通用指针可以指向任何类型的变量。通用指针的类型用(void *)表示,因此也称为void指针。

#include "stdafx.h"
#include<stdio.h>

int main()
{
	int n = 3, *p;
	void *gp;
	gp = &n;
	p = (int*)gp;
	printf("%d\n", *p);
	return 0;
}

  运行结果如下:

 

8、this指针

this指针的特点

  this是一种特殊的指针,他有以下两大特点。

  1. this是每个类的一个隐式私密数据成员。当一个类被定义后,就相当于定义了一个指向本类的指针——this。生成对象后,该对象也就有一个隐含的,被初始化为指向该对象的this指针。所以,this是一个指针常量。成员函数只可以应用它,而不可以对其赋值。
  2. this主要作为每个成员函数(static修饰的静态成员函数除外)的隐式参数,起“本对象”这样的占位符作用。

this指针的应用

  this指针可以显式使用,也可以递引用。示例如下:

  (1)显式使用this指针,例如:

person(string name,int age,char sex){
this ->name=name;
this ->age=age;
this ->sex=sex;
}

  这里赋值号前面与后面的两个变量名字相同,但前面的用this表明是当前对象的成员,后者指初始化构造函数的参数值。

  (2)this指针递引用,例如:

person(string name,int age,char sex){
(*this).name=name;
(*this).age=age;
(*this).sex=sex;
}

  "*this"常用于函数返回语句中。

const Person::topAge(const Person &p)const{
if(p,age>this->age)
        return p;
else
    return *this;
}

  

 

posted @ 2018-07-14 18:58  noticeable  阅读(290)  评论(0编辑  收藏  举报