1.7 指针
指针是C和C++灵活强大的重要因素,但也是难以控制的不稳定因素,在C#、Java语言中都取消指针类型,不能够直接操作物理内存。指针是变量的内存地址,不同类型的变量需要不同的指针类型,尽管指针的实际值是一个数字,但用于存储指针值的变量必须明确声明为指针类型。
1.7.1 指针概述
指针存储变量的内存地址,指针类型为变量类型加*,如int* p1;若p1设定为指向int类型的指针,则不能接受其他类型的指针。&用于获取变量的内存地址,如int* p1=&a;要获取指针指向的值,使用*如int b=*p1;在不同的场合*有不同的意义,应小心区分。
指针变量实际存放的就是一个地址值,但编译器将其抽象为一种数据类型,在实际应用中,经常需要进行指针类型的转换,如int*转为void*、void*转为double*、派生类指针转为基类指针等,这些转换都是编译器层次上的表面转换,实际上就是一个地址。
【实例1-25】输出指针变量存储的地址值,指针指向的值,指针本身所在的地址,指针变量占用的内存大小。
#include <iostream> using namespace std; int main() { int a=120; //整型变量 int* p=&a; //将a的地址赋给整型指针p cout<<"指针p的值为:"<<p<<endl; //p存储地址值 cout<<"p指向的值为:"<<*p<<endl; //p指向的值 int** q=&p; //指向指针p的指针 cout<<"指针p的地址:"<<q<<endl; //指针p所在的内存地址 cout<<"指针p的大小:"<<sizeof(p)<<endl; //指针占用的空间大小 return 0; }
编译运行,结果如图1-20所示。通过&获取整型变量a的地址值,存放到int指针p中。通过*获取指针p所指向的变量的值。指针作为一个变量,也有其地址,通过&获取指针变量的地址值,存放到q中,int**表示一个指向int指针变量的指针。通过sizeof获取指针变量占用的内存大小,指针变量占用4个字节。
图1-20 指针
1.7.2 指针与数组
数组变量实际上就是一个指针,数组名为数组的第一个元素地址,即数组的首地址。根据下标索引确定元素地址,如a[1]的地址等于a[0]的地址加上一个元素的长度。
尽管指针实际上是个地址值,但不能把指针当做数值进行数学运算,只能按照编译器设定的方式进行运算,如a[2]等同于*(a+2),数组名a为数组首地址,2表示两个元素的长度,(a+2)即为第3个元素的地址,通过*获取第3个元素的值。
【实例1-26】将数组名赋给指针变量,用指针变量访问数组元素,输出每个元素的值和地址值。
#include <iostream> using namespace std; int main() { int a[3]={12,45,60}; int* p=a; //数组名赋给指针变量p cout<<p[0]<<" "<<(long)&p[0]<<endl; //输出第1个元素值及地址值 cout<<p[1]<<" "<<(long)&p[1]<<endl; cout<<p[2]<<" "<<(long)&p[2]<<endl; return 0; }
编译运行,结果如图1-21所示。将数组名a赋给指针变量p后,指针p同样可以使用下标访问数组元素,由此可见,数组名a等同于数组的首地址&a[0]。&p[0]得到数组第1个元素的地址,(long)强制将地址值转换为长整型数,默认为十六进制数。三个元素的内存地址是连续的,相邻元素相差4个字节,为一个元素的大小。
图1-21 指针与数组
1.7.3 指针与函数
函数有多个参数,但只有一个返回值,若函数执行后有多个输出值,仅依赖函数的返回值是不够的。可以将部分参数作为输出值,实现输出多个值,如将变量a、b作为参数传给函数f,即f(a,b),调用函数f后,a和b存放的是输出值。但一般情况下,传递给函数f的是变量a、b的值拷贝,在函数f内部的任何修改对a、b本身都没有影响,在C语言中使用传入变量的指针的方法解决该问题。
将变量的指针值传递给函数,如f(&a,&b),传入函数f的是a、b的地址值的拷贝,在函数内部通过该地址值可以访问变量a、b,从而修改其值,在函数f的内部操作过程中已经将外部变量a、b的值修改了。C++提供了引用方式可达到同样的效果,两者形式不同,但实质上都是通过传入变量的内存地址达到改变外部变量值的效果。
【实例1-27】使用传递变量指针和引用两种方式,实现修改外部变量。
#include <iostream> using namespace std; //指针方式改变参数值 void CalcByPointer(int a,int b,int* pAddResult,int* pSubResult) { *pAddResult=a+b; //相加结果保存到指针参数所指向的变量中 *pSubResult=a-b; } //引用方式改变参数值 void CalcByReference(int a,int b,int& AddResult,int& SubResult) { AddResult=a+b; //相加结果保存到引用参数中 SubResult=a-b; } int main() { int a=120,b=30; int c1,c2,d1,d2; CalcByPointer(a,b,&c1,&c2); //指针方式 CalcByReference(a,b,d1,d2); //引用方式 cout<<"指针方式:"<<c1<<","<<c2<<endl; //输出结果 cout<<"引用方式:"<<d1<<","<<d2<<endl; return 0; }
编译运行,结果如图1-22所示。通过指针方式传入变量c1、c2的指针值后,pAddResult指向要改变的变量c1,pSubResult指向要改变的变量c2,在CalcByPointer函数内部执行*pAddResult=a+b;后,直接在内存上进行变量的修改,pAddResult所指向变量c1的值已经改变。
图1-22 指针与函数
引用相当于变量的别名,引用和变量占用同一块内存,改变引用的值等同于改变变量本身。引用定义的同时必须初始化,如int& b=a;表示b是变量a的一个引用,对b的任何操作等同于对a的直接操作。CalcByReference函数中AddResult和SubResult两个参数是整型引用,将d1、d2传入函数,相当于int& AddResult=d1; int& SubResult=d2; 。对AddResult、SubResult的修改相当于直接修改d1、d2的值。
1.7.4 指针与字符串
字符串可看做常量字符数组,如字符串“language”是长度为9的字符数组,前8个元素存储单个字符,最后1个为结束标志'\0',表明字符串到此结束。字符'\0'的ASCII值为0,可根据元素值是否等于0来判断是否到达字符串末尾。
一个字符数组能存放的字符数目为数组长度减1,字符数组初始化时,C++自动在字符串末尾添加空字符'\0'。字符数组名相当于一个指向字符串首地址的指针,通过移动指针指向的位置可灵活操作一个字符串。
【实例1-28】通过字符指针遍历一个字符数组,输出所有字符。
#include <iostream> using namespace std; int main() { char a[]="Study Program"; //字符数组 char* p=a; //字符指针,指向数组首地址 while(*p!=0) //若指针指向的字符不等于0(ASCII值) { cout<<*p<<" "; //输出字符 p++; //字符指针向前移动一位 } cout<<endl; return 0; }
编译运行,结果如图1-23所示。字符数组名等同于字符数组的首地址,将字符数组名赋给字符指针p后,p指向第1个元素,通过*获取p指向的元素的值。p每次递增移动一个元素大小的位置,指向下一个元素,当指向字符串结束标志'\0'时,停止循环。
图1-23 指针与字符串