1.4 指针
1.?指针的概念
为了理解什么是指针,必须先弄清楚数据在内存中是如何存储的,又是如何读取的。如果在程序中定义了一个变量,在编译时就给这个变量分配内存单元。系统根据程序中定义的变量类型,来分配一定长度的空间。例如,C++编译系统在32位机器上为整型变量分配4Byte,为单精度浮点型变量分配4Byte,为字符型变量分配1Byte。内存区的每一个字节有一个编号,这个编号就是地址,如表1-1所示。
表1-1 用户数据、变量、地址直接的对应关系
地址 用户数据 变量名
… … …
2000 3 变量i
2004 6 变量j
2008 9 变量k
2012 10 变量l
… … …
表1-1中展示了用户数据、变量、地址直接的对应关系。假设有变量i,存的数据是3,那它在内存中的地址就是2000。
请务必弄清楚一个内存单元的地址与内存单元的内容这两个概念的区别。其实程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。这种按变量地址存取变量值的方式称为直接存取方式,或直接访问方式。还可以采用另一种称为间接存取(间接访问)的方式,在程序中定义一种特殊的变量,专门用来存放地址。
由于通过地址能找到所需的变量单元,因此可以说,地址指向该变量单元。因此将地址形象化地称为“指针”,一个变量的地址称为该变量的指针。如果有一个变量是专门用来存放另一变量地址(即指针)的,则它称为指针变量。指针变量的值(即指针变量中存放的值)是地址(即指针)。
指针也是一种变量,普通的变量存放的是实际的数据,而指针变量包含的是内存中的一块地址,这块地址指向某个变量或者函数。指针的内容包括:指针的类型、指针所指向的类型、指针的值以及指针本身所占的内存区。例1.6展示了指针的使用。
【例1.6】 指针使用举例。
#include<iostream>
using namespace std;
int main(){
int p1=1; // p1是一个普通的整型变量
int *p2; // p2是一个指针,指向一个整型变量
p2=&p1; // 把p1的地址赋值给p2,p2也就指向了p1
cout<<p1<<" "<<*p2<<endl; // *p2就是取p2所指向的地址的内容
p1=2; // 那么*p2的值也是2
cout<<p1<<" "<<*p2<<endl;
*p2=3; // 那么p1的值也是3
cout<<p1<<" "<<*p2<<endl;
return 0;
}
程序的执行结果是:
1 1
2 2
3 3
例1.6中定义了一个整型变量p1,一个指向整型变量的指针p2,并将p2指向p1。要使用p2指向的内容,必须在p2前面加个*号,也就是*p2。修改了p1的值,*p2的内容也会跟着改变;同样地,修改*p2的值,p1的值也会跟着改变。
2.?数组与指针
在C++中,数组名代表数组第一个元素的地址,如下程序定义了两个变量:
int *p;
int a[10];
若p=a等价于p=&a[0],可以通过对p、a的偏移(int类型的指针+1或-1,是向上或向下偏移sizeof(int)个byte)来访问数组里的元素,若用*(p+i)、*(a+i)也可以通过传统的数组a[i]访问各个元素。
(1)数组指针,也称行指针,具体内容如下所述。
假设有定义int (*p)[n];且()优先级高,首先说明p是一个指针,且指向一个整型的一维数组。这个一维数组的长度是n,也可以说是p的步长,也就是说执行p+1时,p要跨过n个整型数据的长度。如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; // 该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a; // 将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; // 该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
// 所以数组指针也称指向一维数组的指针,亦称行指针。
(2)指针数组不同于数组指针,具体内容如下所述。
假设有定义int *p[n];且[]优先级高,可以理解为先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里若执行p+1操作则是错误的,p=a这样赋值也是错误的,因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量,只用来存放变量地址。但可以这样*p=a赋值这里*p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组,程序可以是:
int *p[3];
int a[3][4];
for(i=0;i<3;i++){
p[i]=a[i];
}
这里int *p[3]表示一个一维数组内存放着3个指针变量,分别是p[0]、p[1]、p[2],所以要分别赋值。
这样数组指针和指针数组两者的区别就很明显了:数组指针只是一个指针变量,可以认为是C语言里专门用来指向二维数组的,它占用内存中一个指针的存储空间;指针数组是多个指针变量,以数组形式存在内存当中,占用多个指针的存储空间。还需要说明的一点就是,同时用来指向二维数组时,其直接引用和用数组名引用都是一样的。
比如要表示数组中第i行j列一个元素,这几种方式都可以:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]。
其中,优先级:()>[]>*。
3.?字符串与指针
字符串是C++中最经常用到的操作对象之一。用字符数组和字符指针变量都可以实现字符串的存储和运算。例1.7展示了字符数组和字符指针是如何使用的。
【例1.7】 字符数组、字符指针、字符指针数组、字符串变量应用举例。
#include<iostream>
#include<string>
using namespace std;
int main(){
char str[] = "I am a programmer." ; // str 是一个字符数组
char * str1="abc"; // str1是一个字符指针变量,可以指向一个字符串
char * str2[]={"hello world","good bye"};
// str2是一个字符指针数组,可以存多个字符串
string str3 = "I am a programmer, too.";
// str3是一个字符串变量
cout<<"str: "<<str<<endl;
cout<<"str1: "<<str1<<endl;
cout<<"str2[0]: "<<str2[0]<<endl;
cout<<"str3: "<<str3<<endl;
return 0;
}
程序的执行结果是:
str: I am a programmer.
str1: abc
str2[0]: hello world
str3: I am a programmer, too.
例1.7中,str是一个字符数组,str1是一个字符指针变量,str2是一个字符指针数组,str3是一个字符串变量。
(1)字符串指针变量本身是一个变量,用于存放字符串的首地址。可以改变str1使它指向不同的字符串,但不能改变str1所指的字符串常量。因为定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,所以abc会被当成常量,并且被放到程序的常量区,不能被修改。
(2)字符串本身是存放在以该首地址为首的一块连续的内存空间中,并以'\0'作为字符串的结束标志。
(3)字符数组是由于若干个数组元素组成的,每个元素中存放字符串的一个字符。在定义一个字符数组时,编译后就会分配一个内存单元,每个元素都有确定的地址。
4.?函数与指针
函数指针是指向函数的指针变量。所以,函数指针首先是个指针变量,而且这个变量指向一个函数。C++在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,就可以用该指针变量调用函数了。
函数指针的声明方法是:
返回值类型 (*指针变量名)([形参列表]);
其中,返回值类型说明函数的返回值类型,(*指针变量名)这句的括号不能省略。
例如:
int func(int a); // 声明一个函数
int (*f) (int a); // 声明一个函数指针
f=&func;
将func函数的首地址赋值给函数指针,这里也等价于f=&func;赋值时函数不带括号,也不带参数,函数名就代表了函数的首地址。例1.8展示了函数指针调用函数的方法。
【例1.8】 函数指针使用范例。
#include<iostream>
using namespace std;
int Mmin(int x,int y){
if(x<y)return x;
return y;
}
int Mmax(int x,int y){
if(x>y)return x;
return y;
}
int main(){
int (*f)(int x,int y);
int a=10,b=20;
f=Mmin; // 把Mmin函数的入口地址赋给f
cout << (*f)(a,b)<<endl;
f=Mmax; // 把Mmax函数的入口地址赋给f
cout << (*f)(a,b)<<endl;
return 0;
}
程序的执行结果是:
10
20
例1.8中定义了一个函数指针f,两个函数Mmin和Mmax,先后把f指向Mmin和Mmax函数,执行比较两个数,分别得出较小值和较大值。