目录:
1、指针简介
2、指针的声明和使用
1、指针简介
学习指针前需要先分清几个概念。
1.1内存单元的地址和内存单元的内容
在程序中定义一个变量,当程序进行编译时就会给定义的变量分配内存单元,这个内存单元的大小由变量的数据类型决定。例如对有符号整型变量分配 4 个字节、对双精度浮点型变量分配 8 个字节等。在内存空间中每一个字节都有一个编号,这就是我们所说的地址,在地址所标识的内存空间中存放数据就相当于旅馆中相应的门牌号住相应的旅客一样。
如程序定义一个有符号整型变量 a,假设编译时系统给变量 a分配的内存空间地址为 2000、2001、2002、2003 这四个字节。定义一个有符号整型变量 b,假设编译时系统给变量 b 分配的内存空间地址为 2004、2005、2006、2007 这四个字节。程序在经过编译以后将变量名转换为变量的地址,所以对于变量值的存取具是通过地址进行的。存储变量的内存空间的首地址称为该变量的地址。可由如下程序输出来看看a、b的值和地址。
例1.1:
#include <iostream>
using namespace std;
int main(void) {
int a,b;
cin>>a>>b;
cout<<"a="<<a<<'\n'<<&a<<endl;
cout<<"b="<<b<<'\n'<<&b<<endl;
return 0;
}
1.2直接访问和间接访问
例1代码在程序编译时执行过程是这样的:首先根据变量名与地址的对应关系,找到变量名a对应的地址 2000、2001、2002、2003,然后将从键盘输入的数据值传送到地址 2000 开始的有符号整型内存存储单元之中。找到变量名 b 对应的地址 2004、2005、2006、2007,然后将键盘输入的数据值传送到地址 2004 开始的有符号整型内存存储单元之中。假设为 a 输入 10,为b 输入 20,则此时在 a 表示的存储单元中的数据为 10,b 存储单元中为 20。当进行输出时,根据变量名 a找到变量的存储地址 2000、2001、2002、2003,然后在2000开始的四个字节中取出数据,即取出变量的值 10,然后将这个值输出。根据变量名 b找到变量的存储地址 2004、2005、2006、2007,然后在 2004 开始的四个字节中取出数据 20,然后输出。
直接访问:程序执行时,按变量名所对应内存地址处理相应的数据,这种按变量的地址直接存取变量的方法称为“直接访问”方式。
间接访问:如果将一个变量的地址放在另一个变量中,则存放地址的变量称为指针型变量,简称指针变量。这时存取变量,也可以间接地由指针变量取得该变量的地址进行,称为“间接访问”方式。
1.3什么是指针
理清上面几个概念后,我们再理解指针就会容易很多。
指针是指向某种数据类型对象的复合数据类型,提供了对所指向对象的间接访问,其保存的是另一个对象的地址。指针是 C++中的重要概念,它使得 C++可以在运行时取得对象的地址,并且可以通过这些地址操纵对象。正确运用指针可以有效地表示复杂的数据结构,用于数组和函数的控制,还能够动态分配内存且方便地使用字符串。掌握指针的使用可以使程序更加简洁和高效,也可以很好地提升性能。当然指针也是一把双刃剑,正确使用可以使程序提高效率,而错误使用会使程序崩溃。为了更好地驾驭指针,我们有必要充分了解 C++中指针的原理和用法,以便高效正确地使用指针。
2.指针的声明和使用
2.1指针变量的定义
注意:地址是内存单元的编号。指针变量就是存放内存地址的变量。指针和指针变量是两个不同的概念,但通常我们叙述时会把指针变量简称为指针。
指针变量定义的一般格式为:
数据类型 *变量名1,*变量名2,……,*变量名n;
在这里,“*”是一个标志,表示所定义的变量是一个指针变量,以表示与一般变量的区别,定义时星号的位置居左、居中、居右都是正确的。“&”为取地址运算符,这个运算符作用在一个对象上,返回的是该对象的存储地址。
int a=1;
float b=3;
char c='c';
int *p,*q; //定义两个整型指针变量p,q。int*不是一种数据类型
float *s; //定义一个实型指针变量s
char *r=&c; //这里表示将字符变量c的地址赋给字符指针变量r,也称r指向c,r不可指向a或b。
注意:
(1)指针变量的类型与其所指向的数据的类型必须相同。
(2)指针名是p,不是*p。
(3)定义时,每个指针变量前都要有星号。
2.2指针变量初始化
指针变量可以在定义时进行初始化,也可以在定义后进行赋值,赋值或者初始化的值必须是同类型的对象地址。
使用前一定要初始化!
int a=10;
int b=10;
int *p1=&a; //在指针变量定义时进行初始化
int *p2; //首先定义指针变量
p2=&b; //再进行初始化
指针变量也可以使用已经定义并初始化过后的指针变量进行初始化,如:
int a=10;
int*p1=&a; //p1指向a
int*p2;
p2=p1;//p2=&a //p2也指向a
cout<<"*p1="<<*p1<<endl;
cout<<"*p2="<<*p2<<endl;
C++中还提供了一种特殊的指针变量类型 void*,它可以保存任何类型对象的地址。如:
#include <iostream>
using namespace std;
int main(void) {
int a=10;
float b=10;
int *p1=&a;
void *p2=&a,*p3; //可以保存任何类型对象的地址
p3=&b;
p3=&a;
return 0;
}
如果将一个指针变量的地址再放在另一个变量中,则该变量称为二级指针变量。二级指针变量定义的一般格式为:
数据类型 **变量名1, **变量名2, …, **变量名n;
在这里,“*”号同样起到标志的作用。如:
char a=’5’;
char *r=&a; //r为一级指针
char **q=&r; //定义二级指针变量q,指向指针变量r
2.3指针变量的使用
指针变量(指针)能参与赋值运算、部分算术运算、关系运算和逻辑运算。
2.3.1赋值运算
*运算符为指针运算符(也称间接访问运算符,解引用运算符),其作用是求指针变量所指内存空间的值。
对指针本身(p)赋值运算是改变指针所指的位置;
*对指针所指对象(p)赋值运算是改变指针所指内存空间的内容。
例2.1:
#include <iostream>
using namespace std;
int main(void) {
int a[5]={1,2,3,4,5},*p1,*p2;
p1=a,p2=p1; // 指针赋值, p1、p2 均指向a[0]
cout<<"*p1="<<*p1<<endl;
cout<<"*p2="<<*p2<<endl;
cout<<"a[0]="<<a[0]<<endl;
*p2=10; // 指针所指对象赋值,a[0]的值赋成10
cout<<"----------"<<endl<<"*p1="<<*p1<<endl;
cout<<"*p2="<<*p2<<endl;
cout<<"a[0]="<<a[0]<<endl;
return 0;
}
除NULL(0)外,不能将一个具体的值赋给指针变量:允许将数值 0 或者在编译时可以获得 0 值的 const 变量赋值给指针变量.
悬空的指针,或所指内存空间不允许修改的指针,不能对其所指内存空间赋值;
当将指针变量初始化为0值常量表达式时,表明该指针变量不指向任何对象,指针悬空。
int *p1=0;//指针p悬空
int a=0;
int *p2;
p2=a; //用法错误
const int b=0;
int *p3=b;//用法正确
对指针所指内存空间赋值时必须明确所指对象。
int *p1,*p2;
p1=&10; //错误用法,&表示取地址,不能对字面值进行取地址操作
*p2=10; //错误用法
指针变量只能初始化或者赋值为同类型的变量地址或者另外一个指针变量.
int a;
int *p1=&a;
int p2=p1; //正确
int *p3;
p3=p1; //正确
double *p4=p1; //错误
double *p5;
p5=p1; //错误
2.3.2算术运算
指针所指内存空间的算术运算等同于变量的算术运算;
指针本身的算术运算在连续的数组空间才有意义,且不宜超出数组的范围;
指针加/减上一个整数,表示其后/前的整数个存储单元的地址:
元素指针:其后/前若干个元素的地址;
行指针:其后/前若干行的行地址。
两个指针相减,表示相隔多少个存储单元.
#include <iostream>
using namespace std;
int main(void) {
int a[5]={1,3,5,7,9},*p1=&a[3],*p2,*p3; //p1 指向a[3]
p2=p1+1,p3=p1-2; // p2 指向a[4],p3 指向a[1]
cout<<"*p1="<<*p1<<'\t'<<"*p2="<<*p2<<'\t'<<"*p3="<<*p3<<endl;
p2--,p3++; // p2 指向a[3],p3 指向a[2]
cout<<"*p2="<<*p2<<'\t'<<"*p3="<<*p3<<'\t'<<endl;
*p3++=*p2++; // a[2]赋值为7,p2 指向a[4],p3 指向a[3]
cout<<"*p2="<<*p2<<'\t'<<"*p3="<<*p3<<endl;
++*p1=++*p2; //a[3]自增为8,a[4]自增为10 后,将10 赋给a[3],p1(a[3]),p2(a[4])的指向不改变
cout<<"*p1="<<*p1<<'\t'<<"*p2="<<*p2<<endl;
int n=p2-p1,m=p3-p2; // n 的值为4-3=1,m 的值为3-4=-1
cout<<"n="<<n<<'\t'<<"m="<<m<<endl;
return 0;
}
2.3.3关系运算
指针变量可以参加所有的关系运算,用于判断指针所指的位置关系。
当位置关系成立时,运算结果为逻辑值真(true 或1);
当位置关系不成立时,其结果为逻辑值假(false 或0)。
如:
int a[5]={1,3,5,7,9},*p1,*p2,*p3;
p1=p2=a,p3=a+2;
//p1==p2、p1>=p2、p1<=p2、p1<p3、p3>p2 的值为真;
//p1!=p2、p1>p2、p1<p2、p1>=p3、p3<p2的值为假。
2.3.4逻辑运算
指针变量可以参加所有的逻辑运算。
当指针悬空时,即值为NULL(0)时,相当于逻辑值假;
当指针不悬空时,相当于逻辑值真。
如:
int a[5]={1,3,5,7,9},*p1=a,*p2=0,*p3;
static char *p4;
//p1、!p2、p1||p2、p3、!p4的值为真;
//p2、p1&&p2、!p3、p4的值为假。
对于&运算符和**运算符我们还有以下几点需要说明。*
(1)&*p 的含义
&和 * 这两个运算符的优先级是相同的,而两者的结合性是从右向左的,所以首先进行 * p的运算,再执行&的运算。而 * p相当于其指向的对象 a,所以&*pointer 与&a的作用是相同的。
(2)* &a的含义
首先进行&a 的运算,得到 a 的地址,再进行 * 运算,得到&a 所指向的变量即 a,所以*&a和a是等价的。
例:按照大小顺序输出两个数。
#include<iostream>
#include<string>
using namespace std;
int main()
{
int a,b;
int *p,*p1,*p2;
cout<<"Put in a:";
cin>>a;
cout<<endl;
cout<<"Put in b:";
cin>>b;
cout<<endl;
p1=&a;
p2=&b;
cout<<"*p1="<<*p1<<endl;
cout<<"*p2="<<*p2<<endl;
if(a<b){ //指针进行交换
p=p1;
p1=p2;
p2=p;
}
cout<<"-------"<<endl;
cout<<"*p1="<<*p1<<endl;
cout<<"*p2="<<*p2<<endl;
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
return 0;
}
要注意上面的程序中a和b的值并没有进行互换,但是p1和p2的值进行了互换,它们互换后所指向的对象分别为b和a,所以输出*p1, *p2时,输出的是b和a的值。
参考:《从零开始学C++》