C++指针(pointer)
在计算机科学中,指针(Pointer),是编程语言中的一类数据类型及其对象或变量,用来表示或存储一个存储器地址,这个地址的值直接指向(points to)存在该地址的对象的值。
指针的概念
指针(pointer)是一个特殊的变量,它里面存储的数值被解释为内存里的一个地址。
指针是操作系统内存的重要途径。指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。指针所指向的内存区就是从指针的值所代表的那个内存地址开始的,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
程序加载到内存中后,在程序中使用的变量、常量、函数等数据,都有自己唯一的一个编号,这个编号就是这个数据的地址。指针变量是用来保存这些地址的变量。如果指针变量p1保存了变量 a 的地址,则就说:p1指向了变量a,也可以说p1指向了a所在的内存块的首地址,图示如下:
指针变量本身所占据的内存区的大小,可用函数sizeof(指针的类型)测得。
取地址运算符& :作用于内存中一个可寻址的数据(如变量、对象和数组元素等等),操作的结果是获得该数据的地址。
间接寻址运算符* :作用于一个指针类型的变量,访问该指针所指向的变量。也称为取内容运算符,或间接引用运算符。
如果一个变量声明时在前面使用 * 号,表明这是个指针型变量。换句话说,该变量存储一个地址,而 *(此处特指单目运算符*,下同;C++语言中另有双目运算符 * 表示乘) 则是取内容操作符,意思是取这个内存地址里存储的内容。把这两点结合在一起,可将 int *a;看作是 “*a 解得的内容类型为 int”。指针是 C++语言区别于其他同时代高级语言的主要特征之一。
指针不仅可以是变量的地址,还可以是数组、数组元素、函数的地址。通过指针作为形式参数可以在函数的调用过程得到一个以上的返回值(不同于return z这样的仅能得到一个返回值。
下面进行详细解说。
要想深入理解指针,应从回顾变量的定义开始入手,变量的定义,是向系统申请一块适当大小的内存,来存放对应的数据,比如
int a = 100;
char c = 'x';
定义变量时,系统为变量分配相应的存储单元,通过变量名可以直接使用该存储单元。
对编程开发初学者而言,充分理解以上变量a、b的内存地址,可以有助于理解指针。
如何查看变量在内存中的地址和内容?
用C++语言查看变量的地址和变量的值(内容)的例子:
#include <iostream>
using namespace std;
int main ()
{
int i = 5;
/* 在c++里面怎么打印变量的地址
普通变量 int i; cout<<&i; */
cout<<"变量i的地址:" << &i <<endl;
cout<<"变量i的值为:" << i <<endl;
char b[20] = "C++语言";
/* 数组变量 int a[20]; cout<<(void *)a;
或 int a[20]; cout<< &a */
cout<<"数组变量b的地址:" << (void *)b<<endl;
cout<<"数组变量b的地址:" << &b<<endl;
cout<<"数组变量b的值:" <<b<<endl;
return 0;
}
运行之,参见下图:
【附、用C语言查看变量的地址和变量的值(内容)的例子
#include<stdio.h>
int main()
{
/* %p 十六进制,大写,高位零显示
%x 十六进制,小写,高位0不显示 */
int i = 5;
printf("变量i的地址: %p\n", &i);
printf("变量i的地址: %x\n", &i);
printf("变量i的值为:%d\n",i);
char b[20] = "C语言";
printf("数组变量b的地址: %p\n", &b);
printf("数组变量b的地址: %x\n", &b);
printf("数组变量b的值为:%d\n",b);
return 0;
}
运行之,参见下图:
】
指针变量本身所占据的内存区的大小,可用函数sizeof(指针的类型)测得。操作系统的位数决定了指针变量所占的字节数。一个指针变量不管它是指向任何类型(如整型、还是字符型、双精度型)变量,一般而言,对于32位系统,指针变量本身只占4个字节,对于64位系统,指针变量本身只占8个字节。
测试指针变量占有内存空间大小的例子:
#include <iostream>
using namespace std;
int main ()
{
char s = 'a', *p1 =&s;
cout << "指针的大小" << sizeof(p1) << endl;
int a = 10, *p2 = &a;
cout << "指针的大小" << sizeof(p2) << endl;
return 0;
}
运行之,参见下图:
我们已经知道,存储单元的使用可以通过变量名,定义变量时,系统为变量分配相应的存储单元,通过变量名可以直接使用该存储单元。存储单元的使用还可以地址来使用。通过存储单元的地址来使用该存储单元,这就需要用到指针变量。
存贮变量的内存空间的首地址称为该变量的地址。由于指针变量中的值是另一个变量的地址,可以形象地称为指针变量指向该变量。
要清楚“内存单元的地址”和“内存单元的内容”这两个概念。
设有字符变量c,其内容为 ‘K’,c占用了011A号内存单元(地址通常用十六进数表示)。假设有指针变量p,内容为011A,这种情况我们称指针变量p指向字符变量c。
将地址值赋给指针变量的目的是为了通过指针变量访问内存单元的内容。
指针变量的值是一个内存单元的地址,那么这个地址不仅可以是变量的地址,也可以是其它数据结构的地址。因此,指针变量还可以存放一个数组或者一个函数的首地址(也称为起始地址)。因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址,也就找到了该数组或函数。这样一来,凡是出现数组,函数的地方都可以用一个指针变量来表示,只要该指针变量中赋予数组或函数的首地址即可。
综上所述,可以看出:一个指针变量的值就是某个内存单元的地址——变量、数组或者函数的首地址(也称为起始地址)。
指针变量所存放的地址是可以改变的。在程序中,每一个变量都有一个地址。这些地址都可以存放在指针变量中。
指针变量的定义格式为:
类型说明符 *变量名;
其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。如:
float *fp;
如此一来:
fp 是指针变量。
*fp是fp所指向的变量存储的内容,也就是变量fp的内容。
&fp所存储的内容是变量的地址。
请仔细理解它们之间的关系。
又如:
int *p = &i;
指针变量p的前面有一个星号,这个星号被称为指针变量定义标记,指针变量让我们有另一种渠道来操作内存块:
i = 100;
*p = 100;
以上两行代码,效果是完全一致的。也就是说,*p就是i,此处的星号被称为间接寻址运算符,旨在令p指向的目标i。
指针变量的赋值
指针变量同普通变量一样,使用之前不仅要定义说明, 而且必须赋予具体的值。未经赋值的指针变量不能使用, 否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。在C++语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。C++语言中提供了地址运算符&来表示变量的地址。其一般形式为:
&变量名;如:&i变示变量i的地址,&b表示变量b的地址。 变量本身必须预先说明。
假设有指向整型变量的指针变量p,如要把整型变量i 的地址赋予p可以有以下两种方式:
(1)指针变量初始化的方法
int i;
int *p = &i;
(2)赋值语句的方法
int i;
int *p;
p = &i;
再次提醒注意:
1)不允许把一个数赋予指针变量,故下面的赋值是错误的:
int *p;
p = 100;
因为指针变量的赋值只能赋予地址。
2)赋值时指针变量前不能再加“*”说明符,如:
*p = &i; //是错误的
int int I = 10, *p = &i; //指针变量初始化时可以,是正确的
指针赋值有三种情况 :
1)取变量地址:使指针指向该变量。
int y=15;
int *ip;
ip=&y
参见下图:
2)指针相互赋值:使两指针指向同一变量。
(续上)
int *ip1;
ip1=ip;
参见下图:
3)指针赋NULL:空指针,指针悬空。不同于指针未赋值。
int *ip2 = NULL; // ip2的值是 0
下面给出一个使用取地址运算符&和取内容运算符(间接寻址运算符)*的简单例子
#include <iostream>
using namespace std;
int main ()
{
int i=10,*p=&i;
cout<<"i= " << i <<endl;
cout<<"*p= " << *p <<endl;
int a=5,*q;
q=&a;
cout<<"a= " << a <<endl;
cout<<"*q= " << *q <<endl;
return 0;
}
运行之,参见下图:
取内容运算符(间接寻址运算符)的说明
使用指针变量时,*号表示 操作 指针所指向的内存空间中的值。
假设有:
int i = 10;
*p相当于通过地址(p变量的值)找到一块内存;然后操作内存。
*p放在等号的左边赋值(给内存赋值),如:
int *p = &i;
*p放在等号的右边取值(从内存获取值),如:
int *q = *P;
取内容运算符(间接寻址运算符)的使用例子:
#include <iostream>
using namespace std;
int main ()
{
int i = 10, *p;
p = &i; //p指向i的内存地址
cout<<"*p= " << *p <<endl;
cout<<"p= " << p <<endl;
int *q = &i;
*q= *q+4; //等号的左边*q是赋值——给内存赋值;等号的右边*q是取值——从内存获取值。
cout<<"*q= " << *q <<endl;
int j;
j = *p + 5;
cout<<"*p= " << *p <<endl;
cout<<"j= " << j <<endl;
return 0;
}
运行之,参见下图:
顺便说明, *前后有无空格都可以,如:
int*p;
int * p;
int* p;
int *p;
都可以,效果相同,后两种常见。运算符、关键字等符号前后可有任意个空格。与C一样,C++中,空格用来占位,并没有实际的内容,对于编译器,除由" "包围起来的字符串中的空格不会被忽略,其它的空格会被忽略。善用空格、缩进和空行可以让代码结构更加清晰。
特别提示
- C语言设计者在其著作《The C Programming Language》说,指针是一种保存变量地址的变量。指针与数组的关系十分密切。
- C++之父在其著作《A tour of C++》说,在声明中*表示“指向……”,在表达式中运算符*表示“……的内容”。运算符&表示“……的地址”。
指针和数组的关系
先看指针和字符数组样式字符串的关系
回顾一下字符串(详见“C++ 字符串”一文)。
C++支持两种的字符串:C风格字符串和新加入的string类。C风格字符串实际上使用元素类型为char的数组(字符数组),是C风格字符串,以\0结尾的字符数组(详见“C++ 字符串”一文)。
对C风格字符串的操作主要有两种方式,一是使用字符数组,char str[];二是使用字符指针。
初始化字符数组如下:
char str1[10]="Hello";
char str2[]="World";
char str3[]={'H','e','l','l','o'};
赋值
只能对字符数组元素的赋值,而不能用赋值语句对整个数组赋值,如:
如:
char str4[10];
str4={'H','e','l','l','o'}; // 错误
str4="Hello"; // 错误
str4[0]='H';str4[1]='e';str4[2]='l';str4[3]='l';str4[4]='o'; // 正确
注意:C/C++语言中,无论是字符数组还是数值型数组在程序中只能给它的元素赋值。
字符数组的输入输出
其它类型的数组元素在输入输出时,只能逐个元素实现输入输出,但用字符数组来存放字符串时,可以进行整体的输入输出,当然也可以使用循环将字符数组中的字符一个一个处理输入输出。
例、
若有
char v[6]="abcde";
则
char *p = &v[3];
或
char *p;
p=p&v[3];
将使p指向v的第4个元素d,参见下图:
用指针变量操作字符数组的例子
#include <iostream>
using namespace std;
int main ()
{
char v[6]="abcde";
cout << v << endl; //输出abcde
cout << "..............." << endl;
char *p = &v[3];
cout << *p << endl; //输出d
cout << p << endl; //输出de
cout << "..............." << endl;
char *q;
q=&v[3];
cout << *q << endl; //输出d
cout << q << endl; //输出de
return 0;
}
运行之,参见下图:
数组名被看作该数组的第一个元素在内存中的首地址。数组在内存的首地址,逻辑上可看作是存放在该数组的数组名中的。数组名在表达式中被自动转换为一个指向数组第一个元素的指针常量。数组名中所放的地址是不可改变的。
指针变量存储的是一个地址,用来间接访问数据,一般而言,对于32位系统,指针变量(包括void指针)本身只占4个字节,对于64位系统,指针变量本身只占8个字节。
定义一个数组之后,编译器便根据该数组元素的类型和个数在内存开辟一段连续的空间来存放数据,对于数组名,sizeof计算的是整个数组所占的空间。
数组名作为左值(赋值运算符的左侧运算对象)时不能被修改,而指针作为左值时可以被赋值。指针变量可以进行自增(自减)运算(void指针除外,因为void指针无法知道步长),但是数组名不能进行自增或者自减运算。
指针和数组的关系的例:
#include <iostream>
using namespace std;
int main ()
{
int a[3] = {1, 2, 3};
int *p = a;
cout << "数组a的大小" << sizeof(a) << endl;
cout << "............." << endl;
cout << "存储指针变量p的内存单元的地址" << &p << endl; //&p表示取存储指针变量p的地址
cout << "&p的大小:" << sizeof(&p) << endl; //
cout << "............." << endl;
cout << "数组a的首地址" << a << endl; //a表示取数组第一个元素的地址
cout << "数组a的首地址" << & a << endl; //&a表示取整个数组的首地址;
cout << "数组a的首地址" << p << endl; //p表示取指针变量p存储的地址;
cout << "............." << endl;
cout << "数组元素a[0]的值:" << a[0] << endl;
cout << "数组元素a[0]的值:" << * p<< endl;
cout << "数组元素a[1]的值:" << a[1] << endl;
cout << "数组元素a[1]的值:" << *(p+1)<< endl;
return 0;
}
运行之,参见下图:
请注意区分其中几个表达式的含义:
&p:表示取存储指针变量p的内存单元的地址;
p:表示取指针变量p存储的地址;
a:表示取数组第一个元素的地址;
&a:表示取整个数组的首地址。
再给出一个例子
#include <iostream>
using namespace std;
int main ()
{
int a[3] = {1, 2, 3};
int *p = a;
cout << &p << endl;
cout << p << endl;
cout << &p+1 << endl;
cout << p+1 << endl;
cout << "..........." << endl;
cout << a << endl;
cout << &a << endl;
cout << a+1 << endl;
cout << &a+1 << endl; //
return 0;
}
运行之,参见下图:
虽然a和&a的值相同,但是所表达的含义完全不同:a表示取数组第一个元素的地址,而&a表示取数组的首地址。a+1表示将指向该数组的第一个元素的指针向后移一个步长(这里的步长为数组元素类型所占的字节数);而&a+1表示将指向该数组的指针向后移动一个步长(此处的步长为数组元素个数×元素类型所占的字节数)。