什么是指针

变量的内存地址叫指针,存放指针的变量叫指针变量。估计不少人会混淆这2个概念,而且有的书籍资料把“指针变量”称为“指针”。

数据的存储方式

程序中的数据都会占用一块内存空间,不同数据类型占用的内存大小不同。比如char、bool是1个字节、short是2个字节、int是4个字节等。

内存是有地址的,计算机是通过内存地址来访问的。在计算机中,1个地址代表1个字节。我们常说的8位、16位、32位、64位,指的是地址的长度。即地址有几个bit位。

比如:

(1)8位机表示地址长度是8个bit,2进制的11111111转成16进制是FF,所以地址范围是0x00 -- 0xFF。这样就有256个地址,即内存的大小是256个字节。

(2)32位机表示地址长度是32个bit,所以地址范围是0x0000 0000 - 0XFFFF FFFF。有2^32个地址,即内存的大小是4G。

假设现在定义了2个变量:int i, int j,因为每个变量占4个字节内存,则这2个变量在内存中的位置大概是像下面这样的。

数据的读取方式

知道了变量的地址和变量的类型后,就可以这样访问变量int i了:

(1)先找到存放变量的首地址,比如0x0000 0001;

(2)从这个地址开始取4个字节的数据。

我们可以用代码来演示一下:

class A {
public:
    int i = 11;
};
int main()
{
    A a;
    int* i = (int*)&a;
 
    printf(" 变量i的值: %d\n", *i);
 
    return 0;
}

我们先用&a取到了对象a的首地址,根据C++对象模型我们知道这个首地址也是变量i的首地址,所以从这个地址开始取int类型长度的数据就是变量i的值。

通过地址我们能找到所需的变量,也可以说地址“指向”该变量。因此,这个地址也被称为指针,即英文pointer。所以我们在C++中所说的指针其实是一个地址。

指针也需要有地方存储,这个存储指针的变量就叫指针变量,即存储指针的变量。

指针变量

存放指针的变量叫指针变量,定义指针变量的一般形式为:

类型名* 指针变量名;

int* pi; //也可以这么写:int *pi; 或 int * pi;
float* pf; //也可以这么写:float *pf; 或 float * pf; 

另外,也可以在定义指针变量时对它进行初始化。

int i = 12;
float f = 12.0
int* pi = &i;
float* pf = &f;

建议在定义指针变量的同时进行初始,如果此时指针没法指向有效的地址,可以把NULL赋给指针。比如:

int* pi = NULL;

我们可以用下面的图来表示指针变量和其所指向的变量的对应关系。 关于指针变量,我们要注意这几个问题:

(1)指向int数据的指针类型表示为:int*,读作”指向int的指针“或”int指针“。int*、char*、float*分别读作int指针、char指针、float指针,它们是3种不同类型的指针。

(2)int* pi 表示一个指向int型变量的指针变量。它可以指向任何int型变量,但不能指向其他类型。

(3)指针变量前面的“”号表示该变量为指针变量。指针变量名是pi、pf,而不是pi、*pf。在C语言中,

*pi代表指针变量所指向的变量的值,也就是int i的值;同理,*pf代表float f的值。

比如在定义了指针变量后,应该把地址赋给pi、pf,而不是*pi、*pf:

int i = 12;
float f = 12.0
int* pi;
float* pf;
//正确的赋值方式
pi = &i;
pf = &f;

//下面的赋值是错误的
*pi = &i;
*pf = &f;

(4)一个指针变量包含2个重要的信息:地址和数据类型。地址确定了变量在内存中的位置,数据类型确定了变量所占内存的大小。

指针相关的几个概念

假如pi是int型的指针变量,它指向变量a,下面我们来看几个概念。

int a = 2;
int* pi = &a;

(1)int* pi = &a;

把变量a的地址赋给指针pi。指针变量pi指向变量a,pi的值是变量a的地址。

(2)*pi = 100;

把整数100赋给pi指向的变量,即把100赋给变量a,a = 100。

(3)printf("%d\n", *pi);

打印pi所指向的变量的值,即打印变量a的值。

(4)printf("%p\n", pi);

打印指针变量pi的值,即打印变量a的地址。

(5)printf("%p\n", &pi);

打印指针变量自身的地址。

(6)&*pi

从右往左读,*pi表示指针变量pi所指向 变量,即a;&a表示取变量a的地址,即指针变量pi。

(7)*&a

先计算&a,即pi;*pi表示变量a。

(8)(*pi)++

先计算*pi,即a;这条语句的意思是a++。

(9)*pi++

从右往左读,先pi++,再*,所以等价于*(pi++)。即先返回*pi的值,然后指针再+1。

因为pi是指针变量,这里的pi++是什么意思呢?

pi++表示指针变量向下移动1次,假如pi指向的是整型变量,则pi++就是往下移动4个字节。当然,这可能会读到非法内存而导致程序出错。

class A {
public:
    int i = 4;
    int j = 5;
};

int main()
{
    A a;

    int* pi = &a.i;
    int c = *pi++;
    std::cout << c << std::endl;
    std::cout << *pi << std::endl;

    return 0;
}

常量和指针

常量指针:指向常量的指针变量。

const int* p = &a;

因为指针变量的定义是这样的:数据类型* 指针变量,所以这里的数据类型是:const int,即常量int。所以指针指向的这个值不能被修改,但指针p可以指向其他int类型的数据。

int a = 1;
const int* p = &a;
*p = 2; //报错. p指向的值不能被修改

int b = 2;
p = &b; //正确. p可以指向其他int

指针常量:指针是一个常量,它的指向不能被修改。

int* const p = &a;

指针变量是const p,所以这个指针变量是常量,它的指向不能被修改。