指针
总览图
指针的概念
指针是C语言中广泛使用的一种数据类型,指针定义的变量,存储的是某一块内存的地址。我们称该指针变量指向了这块内存的首地址。例如数组或函数,由于数组、函数都是连续存储,因此可以用指针变量来表示数组或函数。
地址的说明
C++编译系统在编译时会为不同数据类型的对象分配大小不同的存储空间。在计算机中内存被分成若干个存储单元。每个存储单元(单位字节)都有一个固定的编号即地址。根据一个存储单元的编号即可准确地找到并访问这些存储单元。
指针的定义
3种定义格式
类型名* 变量名; |
类型名 *变量名; |
类型名 * 变量名; |
//符号*表示此变量是指针变量。
int *ip1,ip2; //声明了1个指针变量ip1和1个普通变量ip2
int * ip3,* ip4; //声明了指针变量ip3,ip4都是整型指针
指针的应用
指针运算符
取地址运算符(&)
单目运算符、结合性为自右向左,功能是取变量的地址。
格式
&变量名;
解引用运算符(*)
单目运算符、结合性为自右向左,功能是表示指针变量所指变量的值,也称解引用操作符。
格式
*变量名;
int num = 10;
int *p = #//*表明该变量为一个指针变量
printf("%d\n", num);//num的值 10
printf("%d\n", &num);//num的地址 1571252
printf("%d\n", p);//p的值 1571252
printf("%d\n", &p);//p的地址 1571256
printf("%d\n", *p);//p所指向的变量的值 10
指针的运算
指针变量在使用之前必须有确定的指向,未经赋值的指针变量不能使用。
NULL是一个指针常量,表示空地址。当指针变量暂时无法确定其指向或暂时不用时,需将它指向空地址,以保证程序的正常运行,避免空指针调用以致程序崩溃。
赋值运算
6种赋值形式
指针变量初始化赋值。 |
把一个变量的地址赋予指向相同数据类型的指针变量。 |
把一个指针变量的值赋予指向相同类型变量的另一个指针变量。 |
把数组的首地址赋予指向数组的指针变量。 |
把字符串的首地址赋予指向字符类型的指针变量。 |
把函数的入口地址赋予指向函数的指针变量。 |
int a,*pa=&a;/*定义pa的同时初始化它为变量a的地址*/
int b,*pb;
pb=&a; /*把整型变量a的地址赋予整型指针变量pb*/
int c,*pc=&c,*pd;
pd=pc; /*把c的地址赋予指针变量pd*/
int d[5],*pe;
pe=d; /*数组名表示数组的首地址*/
pe=&d[0]; /*数组第一个元素的地址赋予pe*/
char *pf;
pf="c language"; /*把字符串的首地址赋予指针变量*/
char *pg="C Language";
int (*pf)();
pf=f; //f为函数名
解引用运算
符号*也称为间接引用运算符,其运算结果为该指针所指对象的值。
int a=16,b=28;
int *pa=&a,*pb; //符号*表示pa、pb是指针变量
a*=b; //符号*表示进行乘法运算
*pa=123; //符号*表示间接引用pa所指向的对象a
算术运算
指针变量存储的是数据的内存地址,因此可以将指针变量视为类似整型的变量。指针加上或减去一个整数,其结果是一个新的地址值。
当存在一系列数据的地址是连续存储的,指针的算术运算可以让我们很方便地读取下一个地址的值(如用指针遍历数组的元素)。
与整数的加减运算
指针加上或减去一个整数n,表示指针从当前位置向后或向前移动n*sizeof(数据类型)大小的地址空间。
int a=16, *pa=&a,*p1,*p2;
p1=pa+3;
p2=pa-2;
/*假设变量a分配的首地址是0066FDF4,则p1=&a+3*sizeof(int)= 0066FDF4+3*4=0066FE00。同理p2=&a-2*sizeof(int)=0066FDF4-2*4=0066FDEC。*/
自增、自减运算
指针的自增或自减表示指针从当前位置向后或向前移动sizeof(数据类型)大小的地址空间。
两指针的相减运算
当两个指针指向同一数组时,两个指针的相减才有意义;两个指针相减的结果是一个整数,表示两个指针之间数组元素的个数。
int a[10];
int* p1=&a[1]; //p1指向a[1]
int* p2=&a[6]; //p2指向a[6]
int b=p2-p1; // b=5
两指针的比较运算
两指针变量可以进行关系运算。
pf1==pf2 | 表示pf1和pf2指向同一数组元素; |
pf1>pf2 | 表示pf1处于高地址位置; |
pf1<pf2 | 表示pf1处于低地址位置。 |
int a[10];
int* p1=&a[1]; //p1指向a[1]
int* p2=&a[6]; //p2指向a[6]
if(p1==p2)
cout<<"=="<<endl;
else
cout<<"!="<<endl;
空指针判定
指针变量还可以与0比较,判断指针是否为空指针;指针变量为0和指针变量未赋值是不同的概念。
p==0 | 表明p是空指针,表示它不指向任何变量; |
p!=0 | 表示p不是空指针。 |
二级指针
二级指针定义格式
类型说明符** 指针变量名;
二级指针的应用
由于指针变量直接指向变量,所以称为单级间接访问。而如果通过指向指针的指针变量来访问变量,则构成了二级或多级间接访问。
char *ps[]={ "BASIC","DBASE","C","FORTRAN","PASCAL"};
char **pps;
int i;
for(i=0;i<5;i++)
{
pps=ps+i;
cout<<*pps<<endl;
}
指针与数组
指向一维数组指针变量定义
语法格式
类型说明符 *指针变量名[ 数组长度];
用指针变量访问数组元素
指向数组的指针变量称为数组指针变量。数组名会被解释成数组首元素的指针,即首个数组元素的地址。
int a[]={12,22,32,42,52,62,72,82,92,102};
int *pa=a; //指针变量p指向数组a的首地址
2种格式
用指针变量对数组元素的访问可以采用下标形式和指针形式两种。
下标形式 | p[i] |
指针形式 | *(p+i) |
二维数组指针变量的定义
语法格式
类型说明符 (*指针变量名)[ 维数2];
指向二维数组指针变量访问二维数组
2种格式
用指向二维数组指针变量对数组元素的访问同样可以采用下标形式和指针形式两种。
下标形式 | p[i][j] |
指针形式 | *(*(p+i)+j) |
/*C允许把一个二维数组分解为多个一维数组来处理;
把二维数组a分解为一维数组a[0],a[1],a[2]之后,设p为指向二维数组的指针变量;
它表示p是一个指针变量,它指向二维数组a 或指向第一个一维数组a[0],其值等于a、a[0]、或&a[0][0]。
*/
int a[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
int (*p)[4];
p = a;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf(%d,a[i][j]);
printf(%d,p[i][j]);
printf(%d,*(*(p+i)+j));//p可以看成是指针的指针,需要解两道引用
}
}
指针与n维数组
指针数组
表示一个数组,数组元素为指针类型。
n维指针数组变量的定义
类型说明符 *指针变量名 [维数n-1] [维数n-2] …[维数1];
数组指针
表示一个指针,指向一个数组。
n维数组指针变量的定义
类型说明符 (*指针变量名) [维数n-1] [维数n-2] …[维数1];
注意
“(*指针变量名)”两边的括号不可少,如缺少括号则表示是指针数组。
int (*p)[4];
//表示一个指向二维数组的指针变量,该二维数组的列数为4
int *p[4];
//表示p是一个指针数组,4个下标变量p[0]、p[1]、p[2]、p[3]均为指针变量。
指针与const
普通指针
语法格式
类型名* 指针变量名;
特点
可以修改指向 |
可以通过指针修改指向的数据 |
常量指针
指向const类型(常量)的指针。
语法格式
const 数据类型 *指针变量;
特点
无法通过指针修改指向的数据 |
可以修改指向 |
该指针能够指向常量 |
const int A=3;
int B=5;
const int *pInt=&A;
*pInt=10; //错误,无法修改
pInt=&B; //正确,可以改变指向
//将一个指向常量的指针传递给普通指针将会导致一个编译错误。
指针常量
在指针定义语句的指针名前加const,表示指针本身是常量,称为指针常量,即const指针。
语法格式
数据类型 * const 指针变量;
特点
可以通过指针修改指向的数据 |
不可以修改指向 |
说明
修饰符const与指针变量紧邻,说明指针变量不允许修改,但可以通过*(指针变量)修改指针所指向变量的值。
指向const型变量的const型指针变量
语法格式
const 数据类型 *const 指针变量;
特点
无法通过指针修改指向的数据 |
不可以修改指向 |
说明
指向const型变量的const型指针变量既不可以修改指针变量的值,也不可以通过*(指针变量)修改指针所指向变量的值。
char strText[32] = "head";
char* const strName = strText;
strName = strName + 2; //错误,无法修改指向
*strName = 'n'; //正确,可以修改
*(strName + 2) = 'r'; //正确
指针与函数
函数指针
可以用指针变量指向整型变量、字符串、数组、结构体、也可以指向一个函数。一个函数在编译时被分配一个入口地址。这个入口地址就称为函数指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。
定义一个指向函数的指针变量 p, 它不是固定指向哪一个函数的,而只是表示定义这样一个类型的变量,它是专门用来存放函数的内存地址的。函数指针指向的函数应该和指针声明的函数类型(包括返回值,参数)完全一致。
函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量,也可以是指向函数的指针,函数也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。
函数指针变量的定义
返回值的数据类型 (*指针变量名)(函数参数列表)
void printNumber(int ival)
{
printf("%d\n", ival);
}
int printNumber(int ifig)
{
return ifig;
}
void printFun(int (*pf)(int))
{
printf("%d\n", pf(250));
}
void main()
{
//该指针可以保存返回值为int并且参数为int和int类型的函数的内存地址
void (*funPointer)(int) = printNumber;
printf("%d\n", funPointer);
printf("%d\n", &printNumber);
//指向函数的指针在调用所指向的函数时,无需解引用,直接使用指针名代替函数名
funPointer(125);
printFun(printNumber);
}
函数指针数组
函数指针的数组,可以用来保存符合返回值和参数的一系列函数的内存地址
函数指针数组的定义
函数返回值数据类型 (*指针名[数组大小]) (函数参数列表)
void printNumber(int ival)
{
printf("%d\n", ival);
}
void printFun(int ival)
{
printf("%d\n", ival);
}
void main()
{
void (*pf[5])(int);//函数指针数组,可以保存5个返回值为int,参数为int的函数的内存地址。
pf[0] = printNumber;
pf[1] = printFun;
pf[0](12);
pf[1](24);
}
指针函数
指针函数是指:函数的返回值类型是一个指针类型;函数都有返回类型(如果不返回值,则为void),只不过指针函数返回类型是某一类型的指针。
使用指针函数的时候需要注意,避免返回局部变量的地址,局部变量的内存释放后,导致当前指针指向无效的内存地址,所以使用指针函数时要注意返回的指针是否有效。
指针函数的定义
函数返回数据类型 *函数名称(函数参数列表)
int* printNumber(int ival)
{
printf("%d\n", ival);
return &ival;
}
char* copyString()
{
char str[100] = {};
return str;
}
void main()
{
int ival = 10;
int* (*pf)(int) = printNumber;
//函数有自己的内存地址,和局部变量ival的内存地址,函数的返回值的内存地址均不相同
printf("%d\n", printNumber(10));//10 0053FA7C
printf("%d\n", &ival);//0053FB6C
printf("%d\n", pf);//00041325
//=======无效内存地址示例1========
int *pi = printNumber(12);//12
*pi = 25;//pi为无效内存地址,因此一下输出不为12,而是未知值
printf("%d\n", *pi);//266350
//=======无效内存地址示例2========
char str[50] = "helloWorld";
char* pc = copyString();//pc为无效内存地址
strcpy(pc,str);
printf("%d\n", pc);//输出为 烫烫烫烫烫烫烫烫烫烫烫烫埢烫烫烫烫烫 烫烫烫烫H 烫烫烫烫
}