指针 几个概念: 指针也是一种数据类型,具有指针类型的变量,称为指针变量。 指针变量中存放的不是数据,而是内存单元的地址,通过这个地址可以访问其中的内容。 程序中用到的数据都要保存到计算机的内部存储器中。计算机的内部存储器是由很多个存储单元组成 的,每个存储单元都有一个编号,这个编号称为地址。计算机通过这个地址来读写单元中的数据。
数据 内存储器的访问过程 存储器的访问过程:向[0001]单元写数据 11101101 11001101 10001101 11101001 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 地址0001 10000000 数据 10000000 控制:写
程序中定义的一般变量,要在内存中分配相应的存储单元。 如果在程序中给某个变量赋值,实际上就是将这个值存放到该变量的内存单元中,变量名就是这个单元的名称。 由于数据的类型不同,每个变量分配的单元数目不同。 存储单元也可以存放地址。存放变量地址 的内存单元称为指针变量,简称指针。
指针变量的声明和使用 1) 指针变量的声明形式为: 类型名 *标识符; 例如: int *pi; char *pc; 类型名 *标识符; 例如: int *pi; char *pc; double *pd; 其中:pi存储一个整型变量的地址; pc存储一个字符型变量的地址; pd存储一个双浮点型变量的地址。 指针的类型就是它所保存的地址中所存储的数据 的类型。
C++中各种数据类型都可以指定指针变量。 指针除了可以指向一般变量外,还可以指向其他任何数据结构,如数组、结构体、联合体等,还可以指向函数。
理解指针数据类型 指针也是一种数据类型,它与一些基本数据类型的比较如下表: 类型 类型名 占用内存 存储内容 可参与的运算 其他说明 整型 int 4字节 整数 任何对整数有意义的运算,如四则运算 最基本的数据类型之一 字符型 char 1字节 字符对应的整数编码 整数编码也是整数,故同上 指针 int*, char*, double* …… 其它变量的内存地址 思考:对地址有意义的运算有哪些?) 根据所存地址对应变量的类型可分为整型指针,字符型指针等,整型指针也叫指向整型的指针,甚至可以有指向指针的指针。
2) 指针变量的使用 与指针变量有关的两个运算符是: & 取变量地址运算符 * 取指针变量所指对象内容的运算符 其中: &仅对一般变量使用,表示取该变量的地址。 * 放在指针变量名前,表示取指针变量所指单 元的内容。
2020 2020 例如: int a,*p1; p1=&a; ∥将变量a的地址赋给指针p1 如下图所示: 2020 2020
“*” 的区别 注意: int a,*p1; p1=&a; *p1=25; 指针变量声明后,在使用前必须给它赋一个合法的值,可以在程序中赋值,也可以在声明时对指针进行初始化。 例如: int x=50; int *px=&x; // px指针指向整型变量x double *py=0; //声明py指针时初始化为0 (空指针) 空指针表示不指向任何地方。 以下使用会产生语法错误 : int a, b; double *px=&a; //出错,指针与所指变量类型不同 “*” 的区别
因为指针也是个变量,程序中将哪个变量的地址赋给指针,指针就指向哪个变量。例如: int a,b; int *p1; p1=&a; //p1指向变量a *p1=100; //将100存入a p1=&b; //p1指向变量b *p1=200; //将200存入b 注意:指针只能接受地址量。如果将一个任意常数赋予指针,会产生语法错误。 int i=300; int *p; p=300; //错误,编译器不会将一般整数常量理解为地址。 p=i; //错误
同类型的指针之间可以互相赋值,例如: double x=6.78,y=3.45; double *p1=&x,*p2=&y; p1=p2; //执行后p1和p2都指向变量y。 执行p1=p2前后见示意图如下:
例:指针使用示例 执行结果: a=10 *p1=10 *p2=10 30 a=20 #include <iostream.h> void main() { int a=10; int *p1,*p2=&a; p1=p2; cout<<"a="<<a<<endl; cout<<"*p1="<<*p1<<endl; cout<<"*p2="<<*p2<<endl; cout<<a+(*p1)+(*p2)<<endl; *p1=20; cout<<p1<<" "<<p2<<" "<<&a<<endl; } 执行结果: a=10 *p1=10 *p2=10 30 a=20 0x0065FDF4 0x0065FDF4 0x0065FDF4
指针的运算 指针变量可以参加运算。设a为变量,p、q为与a同一类型的指针,指针允许的运算包括: 例如: int a,*p,*q; &a 取变量地址 *p 取指针所指内容 p=q 指针间赋值 p++、p- - 或 ++p、- -p 指针增1或减1 p+n或 p-n 指针加上或减去一个整数n p- q 指针相减 p<=q 指针关系运算
理解指针的运算 p-1 地址编号: 0x1000 . 0x1004 0x1008 0x100c 0x1010 指针存储的地址,因此指针参与的运算是对于地址而言有意义的运算。对于指针的加减运算不能简单的看成地址数值的加减运算。 例如:int* p=&a;而变量a的地址是0x1004.则指针p+1指向的地址并非0x1005,而是0x1008,即紧跟着变量a的下一个整型变量的地址。 如果执行p=p+1;将使p所存的地址由0x1004变为0x1008。如果p是double型指针,则p所存的地址将由0x1004变为0x100c. p p+1 p+2 p+3
理解指针的运算 再如,执行p++后将导致的变化如右图。 p p 地址编号: 0x1000 . 0x1004 0x1008 0x100c
理解指针的运算 对于int *p=&a;a的地址为0x1004,如右图所示。 p 而int* q=&b;b的地址为0x1010,如右图所示。 地址编号: 0x1000 . 0x1004 0x1008 0x100c 0x1010 对于int *p=&a;a的地址为0x1004,如右图所示。 而int* q=&b;b的地址为0x1010,如右图所示。 则q-p的值是3而不是16。 而q+p则无意义。 p q
指针与数组 在C++中指针与数组有密切的关系,任何由数组下标完成的操作可以用指针实现。 数组名可以看成指针常量;对于指针变量: int *p; 则如下两种写法是等效的: p[2] 等效于 *(p+2)
一维数组与指针 … 一维数组,数组名相当于指向数组第0号元素的指针常量。 例如有如下定义: int array[5]={ 1,3,5,7,9},*p=array; 编译程序为该数组分配5个连续的整型量空间以存放5个元素,同时数组名作为该数组的首地址使用,即array[0]元素的地址,见下图所示。 p array &array[0] p+1 array+1 &array[1] p+i array+i &array[i] … *(p+0) *(array+0) array[0] *(p+1) *(array+1) array[1] *(p+i) *(array+i ) array[i] P P+1 P+2 P+3
例: 用数组名访问数组元素 输入5个数:3 6 20 45 2 sum1=76 sum2=76 例: 用数组名访问数组元素 #include <iostream.h> void main( ){ int a[5],i,sum1(0),sum2(0); cout<<"输入5个数:"; for(i=0; i<5; i++) cin>>*(a+i); //与cin>>a[i]相同。 for(i=0,;i<5;i++) sum1+=*(a+i); //用数组名访问数组元素 sum2+=a[i]; //用下标访问数组元素 cout<<"sum1="<<sum1<<endl; cout<<"sum2="<<sum2<<endl; } 运行程序: 输入5个数:3 6 20 45 2 sum1=76 sum2=76
例: 利用数组名和指向数组的指针访问数组中的元素 例: 利用数组名和指向数组的指针访问数组中的元素 #include <iostream.h> 放大 void main(){ int a[5]={10,20,30,40,50}; int *p=a; //等价于*p=&a[0]; for(int i=0; i<5; i++) cout<<a[i]<<" "; cout<<endl; for(i=0; i<5; i++) cout<<*(a+i)<<" "; cout<<*(p+i)<<" "; cout<<p[i]<<" "; p=a+3; cout<<*p<<endl; cout<<p-a<<endl; } 程序执行结果: 10 20 30 40 50 40 3 该例进一步说明了数组与指针的关系。 执行结果中最后的3为p-a的值,两地址相减结果为p指针与a数组首地址之间元素的个数。 而不一定是实际地址的数值差。
用指针处理数组时需要注意的几点: 数组名是个地址常量,编译时数组名作为数组的首地址已确定,程序运行时其值是固定不变的。因此若定义: int a[10],*p=a; p=a+3; //ok p++; //ok 而进行a++;或a=p;或p=&a;等运算都是错误的。 指针是个地址变量,将哪个数组名赋给它,它即指向哪个数组。例如: int a[5], b[10], *p; p=a; //p指向a数组首地址 p=b; //p指向b数组首地址 p++; //p指向b数组中的下一个元素
用指针处理数组时,指针移动的值不要超过数组元素下标定义的范围,否则越界。越界后得到的结果可能毫无意义。 当指针运算符与其他运算符联合使用时,要注意运算符的优先级别,例如: *p++ //取p所指内容后,p指针加1 *++p //p指针加1后所指的内容 ++*p //将所指向的单元内容加1 (*p)++ //将所指向的单元内容加1 用指针处理数组时,指针移动的值不要超过数组元素下标定义的范围,否则越界。越界后得到的结果可能毫无意义。
多维数组与指针 二维数组: 可以看作一维数组的数组,即元素为数组的数组,
多维数组与指针 二维数组: 可以看作一维数组的数组,即元素为数组的数组,例如有以下定义: int a[3][4]; 二维数组: 可以看作一维数组的数组,即元素为数组的数组,例如有以下定义: int a[3][4]; a数组是由3个元素组成的一维数组. 这3个元素分别是名字为a[0]、a[1]、a[2] 的3个一维数组。因此a数组可以分解为: 表示二维数组a中第i行第j列元素的值可以用以下形式: *(*(a+i)+j) *(a[i]+j) a[i][j]
理解多维数组与指针 现在再来理解如下形式的等效性 *(*(a+i)+j) *(a[i]+j) a[i][j] 对于声明int a[3][3] 指针与数组的关系如 下图。
例: 使用指针法访问二维数组 程序执行结果: 1 1 1 5 5 5 10 10 10 例: 使用指针法访问二维数组 #include <iostream.h> 放大 void main() { int a[3][4]={{1, 2, 3, 4,}, {5, 6, 7, 8}, {9, 10, 11, 12}}; cout<<*(*a+0) << *a[0]<<a[0][0]<<endl; cout<<*(*(a+1)+0)<<*(a[1]+0) <<a[1][0]<<endl; cout<<*(*(a+2)+1)<<*(a[2]+1) <<a[2][1]<<endl; } 程序执行结果: 1 1 1 5 5 5 10 10 10 在程序设计中既可以用下标法引用数组元素,也可以用指针(地址)法引用数组元素。
字符指针与字符数组 在C++中字符串用字符型数组或字符型指针处理。 将一个字符串赋给一个字符型指针时,该指针指向字符串的首地址,即第一个字符的地址。 可以通过移动该指针访问字符串中的字符。
character array character point 例: 字符指针与字符数组 例: 字符指针与字符数组 #include <iostream.h> void main() { char str1[ ]="character array"; char *ps="character point"; cout<<str1<<endl; cout<<ps<<endl; } 程序执行结果: character array character point
字符数组和字符指针的区别: 字符数组只能在初始化时为整体赋值,不能在程序中向数组名赋值。而字符指针除初始化外,还可以在程序中赋字符串。例如: char s[10], *p; s=″abcdefg″; //出错,s为地址常量,不能出现在“=”左侧 p=″abcdexy″; //正确, p为指针变量,可以出现在“=”左侧 对于字符数组,编译程序根据定义数组时的大小分配存储空间。 而对于字符指针,仅给该指针变量分配存放一个地址的单元。该单元可以存放任一字符串的首地址。
例: 实现两字符串的连接。 程序执行结果: abcdexyzf 结合字符数组与字符指针处理字符串是十分有效的方式! #include <iostream.h> 放大 void main() { char s1[20]="abcde",*ps1; char s2[ ]="xyzf"; int i=0; ps1=s1; while (*ps1) ps1++; //寻找s1字符串的结束位置 int j=0; while(*(s2+j)){ //将s2字符串逐个复制到s1后 *ps1=*(s2+j); ps1++; j++; } *ps1='\0'; cout<<s1<<endl; 程序执行结果: abcdexyzf 结合字符数组与字符指针处理字符串是十分有效的方式!
指针数组 定义:指针数组即数组中的每个元素都是指针变量, 或称由指针构成的数组。 1. 指针数组的定义: [存储类型] 数据类型*数组名[元素个数] 例: int *s[4]; //定义整型指针数组 char *ch[5]; //定义字符型指针数组
int *px[4]={&a,&b,&c,&d}; 2. 指针数组的初始化: 指针数组在定义的同时初始化。因为指针数组中的每个元素都是指针,所以初始化的值应是变量的地址。 例: int a=4,b=3,c=2,d=1; int *px[4]={&a,&b,&c,&d};
例1:数组a中每个元素翻倍,用指针数组实现。 #include <iostream.h> void main(){ int a[5]={10,20,30}; int *p[ ]={&a[0], &a[1], &a[2]}; for(int i=0; i<3; i++) { *p[i]+=*p[i]; cout<<*p[i]<<endl; } } 程序执行结果: 20 40 60
3. 字符指针数组的初始化 字符型指针数组可以用以下形式初始化: 例: char s1[ ]=“C++”; char s2[ ]=”Pascal”; char s3[ ]=” Fortran”; char *pc[ ]={s1,s2,s3}; char *p1[ ]={“book”,”desk”,”pencil” };
例2:字符型指针数组编写程序,当输入1~12中某个 值时,输出对应的月份。 #include <iostream.h> void main() { char *month[]={"illegal month","January", "February", "March", "april", "May", "june", "July", "August", "September","October", "November","December" }; char *find; int n; cout<<″输入月份(1~12):″; cin>>n; find=(n<1||n>12)?month[0]: month[n]; cout<<n<<″---″<<find<<endl; } 输入月份(1~12): 8 8---August 运行程序:
堆内存分配 程序运行时所需要的数据空间,通常是在程序设计时 通过 定义变量或数组的方式,由系统预先分配。 若在程序设计时,有些数据空间的大小和多少在编写程序时 还不能确定,只有在程序运行时才能确定,使用堆(heap)内 存可以实现这一功能。 堆是一种内存空间,它允许程序运行时 根据需要申请大小的 内存空间。堆又称动态内存分配。 动态内存分配技术使用new和delete两个运算符。 其中:new用于申请一块动态空间。 用new申请的空间不需要时用delete释放。
new运算符: 使用格式: 指针=new 数据类型名; 指针=new 数据类型名(初始值); 作用: 从内存的动态区域申请指定数据类型所需的存 储单元。若分配成功,该存储单元的首地址赋 给指针,否则,指针得到一个空地址。 例如: (1) double *p; p=new double; (2) double *p; p=new double(12.3576); //分配同时初始化
用new在堆中申请一块连续的存储空间: 创建一维数组格式: 指针=new 数据类型[元素个数] 例: int size, *ip; cin>>size; ip=new int[size]; //创建size个元素的整型数组 char *sp=new char[20]; //创建20个字符的字符数组
delete运算符: 使用格式: delete 指针名; //释放指针所指内存单元 delete []指针名; //释放数组内存单元 作用:释放用new创建的动态存储单元 。 使用格式: delete 指针名; //释放指针所指内存单元 delete []指针名; //释放数组内存单元 例如: int *pi=new int(5); delete [ ]pi; pi=new int;
delete运算符使用说明: . (1) delete运算符并不删除指针。 (2) 对于指向动态分配内存单元的指针,在其所指向的 内存单元没有释放前,该指针不能重新赋值。 (3) 每个new运算符创建的内存单元,只能用delete释 放一次。 (4)使用已释放空间容易引发错误。 例: int *p; p=new int; delete p; *p=10; .
例1:计算一批数据的算术平均值。 #include <iostream.h> #include <stdlib.h> void main() { int m,*p; cout<<“输入数据个数:”; cin>>m; p=new int[m]; //动态分配内存单元 if(p==NULL) { cout<<"动态内存分配失败\n"; exit(1); //分配空间失败,结束程序运行,返回操作系统 } cout<<"输入待处理数据:\n"; for( int i=0; i<m; i++) cin>>p[i]; float s=0.0; for(i=0; i<m; i++) s+= p[i]; cout<<"平均值="<<s/m<<endl; delete p; //释放分配的空间
void指针和const指针 一. void指针:可以用void声明指针。 格式: void *指针名; 说明: 指针转换成所需要类型的指针。 例如: int a(24); int *p2=&a; void *p1=p2; //整型指针p2赋给void指针p1
例1:void指针使用 #include <iostream.h> void main() { char src[ ]="Hello!"; void *pv=src; cout<<src<<" "<<(char *)pv<<endl; //输出void指针时必须进行类型转换 int a(5), *pa=&a; pv=pa; cout<<a<<" "<<*pa<<" "<<*(int *)pv<<endl; } Hello! Hello! 5 5 5 运行结果:
二. const指针 可以有以下几种情况: 1)在声明指针语句的类型前边加const,表示是一个指向“常 量“的指针。即指针本身可以改指向别的对象,但不能通过该指针修改他所指向的对象的值,但指针本身的值可以改变 例如: char *p1=”Hello”; char *p2=”ok!”; const char *pc=p1; //声明指向char型常量的指针 pc=”Yes”; //错,不能改变指针所指向的值 pc=p2; //正确,指针本身的值可以改变
pc=p; //错,pc本身在初始化时所指向的 2) 在声明指针的“*”和指针名之间加const,表示声明一个指针 常量,或称常指针。指针常量是固定指向一个对象的指针,即指针本身是常量。此时指针本身的值不能改变,但他指向 的数据的值可以改变。此时在声明指针常量时必须初始化。 例如: char *p=”ok”; char *const pc=”Hello”; //声明指针常量 pc=p; //错,pc本身在初始化时所指向的 地址是不可改变的, pc=”Yes”; //错,改变指针的值 *pc=’h’; //正确,所指向的目标的值可以改变 *(pc+2)=’k’; //正确,所指向目标的值可以改变 int *const p2; //错,声明指针常量时必须初始化
3) 将关键字const在上述两个地方都加,表示声明一个指向常量的 指针常量。因此指针本身的值和他所指向的数据的值都不能改变,同时在声明时也必须进行初始化。 例如: int a=5, b=6; const int * const pa=&a; pa=&b; //错 *pa=7; //错 a=7; //正确
综合举例:输入10个数,将其中最小的数与第一个数对换, 将其中最大的数与最后一个数交换。用指针实现。