针从当前位置向后或向前移动n个sizeof(<数据类型>)字节的存储单元。指针的数据类型决定了加减运算的单位尺度。
一个数组是一组相同类型的元素的集合,每个元素的字节大小相同。指针的加减运算就是元素指针的前移或后移,这样就可用来访问数组元素。例如:
int a[] = {11,22,33,44,55,66,77,88}; //定义一个int数组a cout<<\ //输出a是头一个元素的地址 int * pb = &a[4]; //pb指向a[4]元素
cout<<\//输出a[4]元素的地址和值
cout<<\输出a[7]元素的地址和值 cout<<\输出a[2]元素的地址和值
假设数组变量a的首地址是0x0012FF60,则a[4]元素的地址计算如下:
pb = &a + 4*sizeof(int) = 0x0012FF60 + 4*4 = 0x0012FF70。
pb+3 = &pb + 3*sizeof(int)= 0x0012FF70 + 3*4 = 0x0012FF7C,指向a[7]元素。 pb-2 = &pb - 2*sizeof(int)= 0x0012FF70 - 2*4 = 0x0012FF68,指向a[2]元素。
0012FF600012FF64pb - 20012FF68pb : 0012FF700012FF680012FF6Cpb0012FF700012FF740012FF78pb + 30012FF7C0012FF7C1122334455667788a[0]a[1]a[2]a[3]a[4]a[5]a[6]a[7]aa : 0012FF60 图8.4 指针的加法和减法运算 图8.4给出了指针移动示意图。注意这种计算只能用于访问数组,而且访问地址不能越界,就如同下标不能越界一样。
通过上面例子我们知道,一个指针加减一个整数常量就是指针后移或前移。但要注意不能对两个指针变量相加。我们也看到,一个数组的名字本质上就是头一个元素的地址,但这个指针的值是一个常量,不能改变它的值。
(2)自增、自减运算 指针的自增、自减运算是指针加减运算的一种特例,就是指针从当前位置向后或向前移动sizeof(数据类型)字节大小的1个存储单元。例如:
int a[] = {11,22,33,44,55,66,77,88}; int * pb = &a[4]; int * p1 = pb--;
此时,指针p1指向a[4],而pb指向a[5]元素。 注意,指针的自增、自减往往与间接引用混合运算,此时应注意计算顺序及语义。例如:
int a[] = {11,22,33,44,55,66,77,88}; int b, * p = &a[4];
下面分别计算各个表达式语句(以自增为例):
b = *p++; b = *(p++); b = (*p)++; b = *++p; b = ++*p; b = ++*p++;
//先取p指向的a[4]的值55,然后使指针p后移指向a[5] //等同于*p++
//读取p指向的a[4]的值55,然后对其自增1,成为56 //先使指针p后移指向a[5],然后读取p所指向的值66 //先读取p指向的变量a[4],然后对a[4]自增1,成为56 //a[4]自增1,成为56,然后指针p后移指向a[5]
4.两指针相减
当两个指针都指向同一数组元素时,这两个指针的相减才有意义。两个指针相减的结果为一整数,表示两个指针之间数组元素的个数。例如:
int a[10] = {11,22,33,44,55,66,77,88}; p1 = &a[1]; //p1指向a[1] p2 = &a[6]; //p2指向a[6] cout< 对于两个指针,不适合进行其它算术运算,如加法、乘法、除法、求余等,也不适合进行按位运算。这些计算即便能执行,但因结果难以控制,也是危险的。 5.关系运算 指针关系运算一般有下列两种情况: ? 比较两个指针所指向的对象在内存中的位置,如相等还是不相等,判断两指针是否 指向同一个对象。 ? 判断一个指针是否为常量NULL,即判断空指针。 例如下面代码段: int a[10], *p1, *p2; p1 = &a[1]; //p1指向a[1] p2 = &a[6]; //p2指向a[6] if (p1 == p2) cout<<\ else cout<<\ //执行 if (p1 != NULL) cout<<\//执行 8.2 指针与结构 结构作为一种自定义类型,也可说明指针变量。指针变量也可作为结构的成员。 8.2.1 结构的指针 说明一个结构类型的指针与基本类型一样。假设已定义一个结构Employee,就能定义该类型的指针,例如: Employee emp, * ep1 = &emp; //说明一个指针ep1指向一个结构变量emp 通过一个结构指针变量,就能访问该结构变量内的各个成员。格式为: <结构指针变量> -> 成员名 其中,“->”是一种成员访问运算符(中间不能有空格),其左边必须是一个结构或类的指针变量。成员访问运算符的优先级高于间接引用运算符*和求址运算符&。如果用间接引用运算符,有一种等价形式如下: (*<结构指针变量>).成员名 注意上面表达式中不能缺少括号,这是因为成员访问运算符“.”的优先级高于间接引用运算符“*”。这种用法比较少见。例如: ep1 -> sex = 'f' (*ep1).sex = 'f' //通过指针来访问结构的成员 //等价方式 例8-3 结构体指针的说明与使用 #include struct Employee{ //定义职工信息结构类型 char num[5],name[20]; char sex; Date birthday; float salary; }; void printAnDate(Date *d){ //结构指针作为函数形参 if (d != NULL) cout< } void printTitle(){ cout< void printAnEmp(Employee * emp){ //结构指针作为函数形参 if (emp == NULL) return; cout< printAnDate(&emp->birthday); //A cout< void main(void){ Employee e1 = {\李四\ Employee *ep = &e1; printTitle(); printAnEmp(ep); //B } 执行程序,输出如下: 上面printAnDate函数有一个指针形参Date *d,表示该函数将接受一个Date变量的地址。在A行调用时,用&emp->birthday表达式来计算emp所指结构变量的birthdate成员的地址。 函数printAnEmp有一个指针形参Employee *emp,表示该函数将接受一个Employee变量的地址。在B行调用时,用一个ep指针作为实参,调用前已指向一个Employee结构变量emp。这是一个36字节的“大”对象,而传递给形参的只是4字节的地址。这里的函数与前一章相比,具有更高计算效率。 8.2.2 指针作为结构成员 指针变量可以作为结构类型的成员,使结构变量能通过指针来关联一个变量。 修改上面Employee结构类型,让每个职员都能知道自己的部门经理manager是哪一名职员。为该结构添加一个成员manager,但不是Employee类型,因为结构不能嵌套定义,但能定义Employee的指针。 struct Employee{ … Employee *manager; //添加一个指针变量,指向一个Employee变量 }; 编号 姓名 性别 出生日期 工资 024 李四 女 1977.8.3 4567 下面代码可确定一名雇员如何作为另一名雇员的经理。 Employee emp1; Employee emp2; emp1.manager = &emp2; 设计一个函数,能为一组职员设置一名经理,也能为一组职员撤销经理。这里将用到结构的数组。完整编程如下。 例8-4 指针作为结构的成员。 #include short year,month,day; }; struct Employee{ char num[5],name[20]; char sex; Date birthday; float salary; Employee *manager; //一个指针成员 }; void printAnDate(Date *d){ if (d != NULL) cout< void printTitle(){ cout< cout< void printAnEmp(Employee * emp){ if (emp == NULL) return; cout< printAnDate(&emp->birthday); cout< cout< void printEmps(Employee emps[], int n){ //打印一组职员 printTitle(); for(int i = 0; i < n; i++) printAnEmp(&emps[i]); //打印一名职员 } void setManager(Employee emp[], int n, Employee *mgr){//为一组职员设置经理 for(int i = 0; i < n ; i++) emp[i].manager = mgr; } void main(void){ Employee emps[] = { {\张三\ {\李四\ {\王五\ printEmps(emps, 3); //显示初始记录 setManager(emps, 3, &emps[2]); //设置经理 printEmps(emps, 3); setManager(emps, 3, NULL); //撤销经理 printEmps(emps, 3); } 执行程序,输出如下: 编号 姓名 性别 出生日期 工资 经理姓名 021 张三 男 1983.3.4 3456 空 024 李四 女 1977.8.3 4567 空 027 王五 男 1970.9.2 5432 空 编号 姓名 性别 出生日期 工资 经理姓名 021 张三 男 1983.3.4 3456 王五 024 李四 女 1977.8.3 4567 王五 027 王五 男 1970.9.2 5432 王五 编号 姓名 性别 出生日期 工资 经理姓名 021 张三 男 1983.3.4 3456 空 上面setManager函数为一组职员设置一名经理,第3个形参是一个指针。调用时如果实参是一名职员的地址,就设置其为经理,如果实参是一个空指针,就表示撤销这些职员的经理。在结构类型中通过指针成员能表示数据关联,能表示更复杂的数据结构。 上面printEmps函数和setManager函数都有结构数组作为形参。在调用函数时,并不是将整个数组的内容传递给形参,而是仅传递了数组的指针。 024 李四 女 1977.8.3 4567 空 027 王五 男 1970.9.2 5432 空 8.3 指针与数组 指针与数组之间有密切关系。一个数组的名字本身就是一个指针,可用指针来访问数组元素。可以定义指针的数组,每个元素都是一个指针。指针与字符串密切相关,指针可用来定义并处理字符串。也能定义一个指针指向一个数组,即数组的指针。还能定义一个指针指向另一个指针,即二级指针,二级指针能用来访问二维数组。 8.3.1 用指针访问数组 一般情况下,使用数组名加下标来访问数组元素,数组名本身就是头一个元素的地址,下标要换算为偏移地址来确定元素的位置,因此可直接用指针来访问数组。 1.一维数组 一个数组名就是一个指针,其值就是该数组在内存中的首地址,这是一个常量,不许改。 例如:int a[5] = {1,2,3,4,5}; 定义了一个数组a,那么a的类型是什么? 执行cout< 数组名a表示了该数组的首地址,即第0个元素的地址。由指针的加法运算规则可知,a+1就表示了第1个元素的地址。同理,a+i表示第i个元素的地址(0<=i<=4)。 进一步,由间接引用运算符的语义可知,*(a+i)就表示了第i个元素,即a[i],因此有下面等价式: a[i] == *(a+i) //两者都表示了第i个元素 在上面等价式两侧都进行求址运算&,就能得到下面等价式: &a[i] == &*(a+i) == a+i //都表示了第i个元素的地址 上面使用了前面介绍的一个等价式:对于任何一个指针p:&*p == p 上面这些等价式还将用于二维数组的访问。 例8-5 用指针常量来访问一维数组元素。 #include int a[] = {1,2,3,4,5}; for(int i = 0; i < 5; i++) cout<<\} 执行程序,输出如下: a[0]=1 at 0x0012FF6C a[1]=2 at 0x0012FF70 a[2]=3 at 0x0012FF74 a[3]=4 at 0x0012FF78