第6章 指针 6.1 指针的概念 6.2 变量与指针 6.3 数组与指针 6.4 字符串与指针 6.5 函数与指针 6.6 返回指针值的函数 第6章 指针 6.1 指针的概念 6.2 变量与指针 6.3 数组与指针 6.4 字符串与指针 6.5 函数与指针 6.6 返回指针值的函数 6.7 指针数组和指向指针的指针 6.8 有关指针的数据类型和指针运算的小结 *6.9 引用
6.1 指针的概念 程序中定义的每一个变量,系统根据变量类型分配一定长度内存单元。 例如,C++编译系统一般为整型变量分配4个字节,为字符型变量分配1个字节。 内存区的每一个字节有一个编号,这就是“地址” 。
注意:内存单元的地址与内存单元的内容的区别。
直接存取方式:按变量名存取变量值的方式。 直接将数3送到整型变量i所标识的单元中。 i = 3
间接存取方式:将3送到指针变量i_pointer所指向的单元(这就是变量i所标识的单元)中。 *i_pointer = 3 变量的地址称为该变量的指针。 专门存放变量地址(即指针)的变量,称为指针变量。指针变量的值是地址(即指针)。 任何类型的指针变量在内存中占4个字节。
6.2 变量与指针 指针变量: 用它来指向另一个变量。 “*”符号表示指向。例如,i_pointer是一个指针变量,而*i_pointer表示i_pointer所指向的变量。 下面两个语句等价: ① i=3; ② *i_pointer=3;
6.2.1 定义指针变量 定义指针变量的一般形式: 基类型 *指针变量名; 例:int i , j; //定义整型变量 i , j 基类型 *指针变量名; 例:int i , j; //定义整型变量 i , j int *pointer_1 , *pointer_2; //定义指向整型数据的指针变量*pointer_1 , *pointer_2
例6.2 输入a和b两个整数,按先大后小的顺序输出a和b(用指针变量处理)。(不改变a,b的值)
思考1: 输入a、b、c三个整数,由大到小排序 (用指针变量处理)。(不改变a,b,c的值) 思考2: 输入n个整数到数组a中,由大到小排序 (用指针变量处理)。 (不改变a数组中各元素的值) 解题思路: 通过交换指针变量的值,改变指针变量的指向来排序。
6.2.3 指针作为函数参数 例:通过调用函数交换a和b的值。 注意: 不要将main函数中的swap函数调用写成 swap(*pointer_1,*pointer_2);
注意交换*p1和*p2的值是如何实现的。如果写成以下这样就有问题了: void swap(int *p1,int *p2) {int *temp; *temp=*p1; //此语句有问题 *p1=*p2; *p2=*temp; }
注意:实参变量和形参变量之间的数据传递是单向的“值传递”方式。 不能企图通过改变形参指针变量的值而使实参指针变量的值改变。但可改变实参指针所指向变量的值。
结论:函数的调用只能有一个返回值,而使用指针变量作函数参数,可通过指针变量改变主调函数中其它变量的值,相当于通过函数调用从被调用的函数中得到多个值。
6.3 数组与指针 6.3.1 指向一维数组元素的指针 指针变量可以指向数组元素(把某一元素的地址放到一个指针变量中)。 6.3 数组与指针 6.3.1 指向一维数组元素的指针 指针变量可以指向数组元素(把某一元素的地址放到一个指针变量中)。 int a[10]; //定义10个元素的整型数组a int *p; //定义一个基类型为整型的指针变量p p=&a[0]; //将a[0]的地址赋给指针变量p,使p指向a[0]
数组名代表数组中第一个元素(即序号为0的元素)的地址。因此,下面两个语句等价: p=&a[0]; p=a; 在定义指针变量时可以给它赋初值: int *p=&a[0]; //p的初值为a[0]的地址 也可以写成: int *p=a; //作用与前一行相同
通过指针引用数组元素。 int a[10]; int *p = &a[4]; *p = 8; //对p当前所指向的数组元素赋予数值8,即a[4]=1。 如果指针变量p已指向数组中的一个元素,如何通过p来访问数组中的其它元素?
如果p的初值为&a[0],则: (1) p+i和a+i就是a[i]的地址,或者说,它们指向a数组的第i个元素。
(2) *(p+i)或*(a+i)与a[i]等价。 [ ] 变址运算符。a[i]的求解过程: 先按a+i×d计算数组元素的地址,然后找出此地址所指向的单元中的值。(d为单个数组元素占用的字节数) (3) 指向数组元素的指针变量也可以带下标,如p[i]与*(p+i)等价。
因此,引用一个数组元素,可用以下方法: (1) 下标法,如a[i],p[i] ; (2) 指针法,如*(a+i)或*(p+i)。 假定a是数组名,p是指向数组元素的指针变量。如果已使p的值为a,则*(p+i)就是a[i]。
例6.5 倒序输出5个元素的数组中的全部元素。 (1) 下标法 (2) 指针法 将上面程序第7行和第10行的“a[i]”改为“*(a+i)”。 (3) 用指针变量指向数组各个元素
注意:切实保证指针变量p指向数组中有效的元素。 int a[10],*p=a; //指针变量p的初值为&a[0] cout<<*(p+10); //要输出a[10]的值
在int a[10],*p=a; 前提下: (1) p++(或p+=1)。使p指向下一元素,即a[1]。如果用*p,得到下一个元素a[1]的值。
for(p=a;p<a+10;p++) cout<<*p; (2) *p++。++和*同优先级,结合方向为自右而左,因此等价于*(p++)。 作用是: 先得到p指向的变量的值(即*p),然后再使p的值加1。如: for(p=a; p<a+10; ) cout<<*p++; for(p=a;p<a+10;p++) cout<<*p;
(3) *(p++)与*(++p)的区别。 前者是先取*p值,然后使p加1。 后者是先使p加1,再取*p。 若p的初值为a(即&a[0]),输出*(p++)得到a[0]的值,而输出*(++p) 得到a[1]的值。
(5) 如果p当前指向a[i],则 *(p--) 是先取*p值,得到a[i],然后p减1, p指向a[i-1] 。 *(--p) 是先使p减1,再取*p,得到a[i-1] 。 (4) (*p)++表示p所指向的元素值加1,即 (a[0])++,如果a[0]=3,则(a[0])++后,a[0]的值为4。
p=a; while(p<a+10) p=a; while(p<a+10) {cout<<*p; ++和--运算符用于指向数组元素的指针变量十分有效,可以使指针变量自动向前或向后移动,指向下一个或上一个数组元素。 p=a; while(p<a+10) {cout<<*p; p++;} p=a; while(p<a+10) cout<<*p++;
循环移位问题 如: 1 2 3 4 5 向左循环移动1位 2 3 4 5 1
用数组名作形参与指针变量作形参,本质相同, C++编译系统将形参数组名一律作为指针变量处理。都是接收从实参传递来的数组首元素的地址。 6.3.2 用指针变量作函数参数接收数组地址 用数组名作形参与指针变量作形参,本质相同, C++编译系统将形参数组名一律作为指针变量处理。都是接收从实参传递来的数组首元素的地址。 void sort(int a[],int n){ …… } a其实就是指针变量 void sort(int *a,int n){ …… }
例6.6 将10个整数按由小到大的顺序排列。
实际上函数调用时并不存在一个占有存储空间的形参数组,只有指针变量。 实参与形参的结合,有以下4种形式: 实 参 形 参 数组名 数组名 (如例5.7) 数组名 指针变量 (如例6.6) 指针变量 数组名 指针变量 指针变量
注意:int a[10]数组名a代表一个固定的地址,或者说是指针型常量,因此不能改变a的值。如
例:数组a和b中的元素交叉合并到数组c中。 如: a中有1,2,3 b中有5,6,7,8,9 c中有1,5,2,6,3,7,8,9 要求:用指针变量处理。
6.3.3 多维数组与指针 1. 多维数组元素的地址 设有一个二维数组a,它有3行4列。它的定义为 int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; 可认为二维数组是“数组的数组”,数组a是由3个一维数组构成。
a为二维数组首元素的地址,首元素不是一个整型变量,而是由4个整型元素所组成的一维数组。 因此,*(a+0)、*(a+1)、*(a+2)分别表示: a[0], a[1], a[2];即第0、1、2行一维数组名。 C++规定数组名代表数组首元素地址,因此a[0]代表一维数组a[0]中0列元素的地址,即&a[0][0]。a[1]的值是&a[1][0],a[2]的值是&a[2][0]。
依此类推:*(a[i]+j) 或 *(*(a+i)+j) 是a[i][j]的值。 a[1][0]、 a[1][1]、 a[1][2]、 a[1][3] 指针法表示: *(a[1]+0)、 *(a[1]+1)、 *(a[1]+2)、*(a[1]+3) 也可表示为: *(*(a+1)+0)、 *(* (a+1) +1)、 *(* (a+1) +2)、 *(* (a+1) +3) 依此类推:*(a[i]+j) 或 *(*(a+i)+j) 是a[i][j]的值。
地址 2000 2004 2008 2012
(1) 指向多维数组元素的指针变量 例6.7 输出二维数组各元素的值。 采用基类型为整型的指针变量先后指向各元素,逐个输出它们的值。 2. 指向多维数组元素的指针变量 (1) 指向多维数组元素的指针变量 例6.7 输出二维数组各元素的值。 采用基类型为整型的指针变量先后指向各元素,逐个输出它们的值。
说明: ① p是指向整型数据的指针变量,在for语句中对p赋初值a[0],也可以写成“p=&a[0][0]”。 ② 循环结束的条件是“p<=a[0]+11”,只要满足p<=a[0]+11,就继续执行循环体。 ③ 执行“cout<<*p;”输出p当前所指的列元素的值,然后执行p++,使p指向下一个列元素。 思考:可否用 p = a 语句赋初值?
定义一个指针变量,它不是指向一个元素,而是指向一个包含m个元素的一维数组。 (2) 指向由m个元素组成的一维数组的指针变量 定义一个指针变量,它不是指向一个元素,而是指向一个包含m个元素的一维数组。 基类型 (*数组名)[一维数组长度] 如:int (*p)[4];
如果指针变量p先指向a[0](即p=&a[0] 或 p=a),则p+1不是指向a[0][1],而是指向a[1],p的增值以一维数组的长度为单位,如图示。
例6.8 输出二维数组任意一行任意一列元素的值。(用指向一维数组的指针变量实现) 例6.8 输出二维数组任意一行任意一列元素的值。(用指向一维数组的指针变量实现) *(*(p+2)+3)是a[2][3]的值。
3. 用指向一维数组的指针作函数参数 多维数组名也可作函数参数传递。 例6.9 输出二维数组各元素的值。 用函数实现输出,用多维数组名作函数参数。
在C++中可以用3种方法访问一个字符串 1. 用字符数组存放一个字符串 例6.10 定义一个字符数组并初始化,然后输出其中的字符串。 6.4 字符串与指针 在C++中可以用3种方法访问一个字符串 1. 用字符数组存放一个字符串 例6.10 定义一个字符数组并初始化,然后输出其中的字符串。
2. 用字符串变量存放字符串 例6.11 定义一个字符串变量并初始化,然后输出其中的字符串。
3. 字符指针初始化时指向一个字符串常量 例6.12 定义一个字符指针变量并初始化,然后输出它指向的字符串。
可用下标法或指针法存取字符串中的字符。 例6.13 将字符串str1复制为字符串str2。
6.7 指针数组和指向指针的指针 6.7.1 指针数组的概念 如果一个数组,其元素均为指针类型数据,该数组称为指针数组。 6.7 指针数组和指向指针的指针 6.7.1 指针数组的概念 如果一个数组,其元素均为指针类型数据,该数组称为指针数组。 一维指针数组的定义形式为 类型名 *数组名[数组长度]; 例如:int *p[4];
例6.15 若干字符串按字母顺序(由小到大)输出。(不改变字符数组中字符串的位置)
不区分大小写的排序问题(相同单词排在一起,顺序根据输入定。) 1)、二维字符数组(char word[100][20])完成 2)、用字符串数组(string word[100])完成。
6.7.2 指向指针的指针 p2 p1 x int x = 5; &p1 &x 5 int *p1 = &x; int *(*p2) = &p1; 或: int **p2 = &p1;
由于name[i]的值是地址(即指针),因此name+i就是指向指针型数据的指针。 定义一个指向指针数据的指针变量: char *(*p); 或 char **p; 用来存放指向指针型数据的指针。即: p = name ; 或 p = &name[0]; 说明:p++,p指向name数组的下一个元素。
例6.16 指向字符型数据的指针变量。 int main( ) { char **p; char *name[ ]={"BASIC","FORTRAN","C++","Pascal","COBOL"}; p=name+2; cout<<*p<<endl; cout<<**p<<endl;}
例:用指向指针的指针方法对n个整数排序并输出。要求:整数和n在主函数中输入,排序单独在一个函数中完成。 1)通过指向指针的指针访问整型数组中的元素,但不改变数组中元素的值。 2)直接通过指向指针的指针访问整型数组中的元素,并数组中元素的值。
int *….*p ; 实际上在程序中很少超过二级间址。 单级间址 二级间址 int *….*p ; 实际上在程序中很少超过二级间址。
注意事项: (1)、p=NULL; //指针变量为空值 在iostream头文件中定义: #define NULL 0 NULL代表整数0,但系统中没有地址为0的单元,因此指针变量不指向任何有效单元。 注意,p的值等于NULL和p未被赋值是两个不同的概念。
(2) 两个指针变量可以相减 如果两个指针变量指向同一个数组的元素,则两个指针变量值之差是两个指针之间的元素个数。 假如p1指向a[1],p2指向a[4],则p2-p1=(a+4)-(a+1)=4-1=3。 但p1+p2并无实际意义。
(3) 两个指针变量比较 若两个指针指向同一个数组的元素,则可进行比较。指向前面元素的指针变量小于指向后面元素的指针变量。 如:p1<p2 注意:如果p1和p2不指向同一数组则比较无意义。
总结: 使用指针的优点: ①提高程序效率;②调用函数时,能在被调用函数中改变主调函数中多个变量的值;③可以实现动态存储分配。 但是,指针使用太灵活,对熟练的程序员来说,可以利用它编写出颇有特色、质量优良的程序,实现许多用其他高级语言难以实现的功能,但也十分容易出错,而且这种错误往往难以发现。
*7.1.7 动态分配和撤销内存的运算符new和delete C++动态分配和撤销内存运算符:new和delete C动态分配和撤销内存函数:malloc和free函数。 注意: new和delete是运算符,不是函数,因此执行效率高。 new运算符的例子: new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100); //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址 new char[10]; //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址 new int[5][4]; //开辟一个存放二维整型数组(大小为5*4)的空间,返回首地址 float *p=new float(3.14159); //开辟一个存放单精度数的空间,并指定该实数的初值为3.14159,将返回的该空间的地址赋给指针变量p
new运算符使用的一般格式为: new 类型 [初值] 用new分配数组空间时不能指定初值。 如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。
delete运算符使用的一般格式为: delete[ ] 指针变量 撤销用new开辟的空间,如 delete p; //释放单个空间 delete[ ] pt; //释放数组空间 注意:用new开辟的空间,一定要在用完后用delete释放掉。
例7.6 动态开辟空间以存放n个学生姓名和成绩(2门成绩)。
*6.9 引用 6.9.1 什么是变量的引用 引用是一种新的变量类型,它的作用是为一个变量起一个别名。如: *6.9 引用 6.9.1 什么是变量的引用 引用是一种新的变量类型,它的作用是为一个变量起一个别名。如: int a; //定义a是整型变量 int &b = a; //声明b是a的引用 a和b代表同一变量,共用一个存储单元。
int a; int &b = a; b = 20;
声明引用类型变量时,必须同时初始化,即声明它代表哪一个变量。 一旦初始化后,不能改变引用变量的值。 int a1,a2; int &b[2] = {a1,a2}; int a1,a2; int &b = a1; &b = a2; b = a2; b = 23; 不能定义引用类型的数组。
6.9.3 引用作为函数参数 增加引用类型, 主要是作为函数参数,以扩充函数传递数据的功能。 函数参数传递的三种情况: (1) 将变量名作为实参和形参。 例6.18 要求将变量i和j的值互换。下面的程序无法实现此要求。
运行时输出3 5,i和j的值并未互换。
(2) 传递变量的指针。形参是指针变量,实参是变量的地址。 例6.19 使用指针变量作形参,实现两个变量的值互换。
(3)前面两种都是“值传递”方式,形参与实参不是同一个单元;但用引用型变量作为函数形参,则实参与形参是同一个单元,形参是实参的别名。 例6.20 利用“引用形参”实现两个变量的值互换。
思考:引用类型变量作形参和指针变量作形参的区别? 指针变量要另外开辟内存单元,其内容是地址。而引用变量不是一个独立的变量,不单独占内存单元。 引用比指针变量简单、直观、方便。使用变量的引用,可以部分代替指针的操作。
例6.21 对3个变量按由小到大的顺序排序。 (要求:在函数中完成排序,用引用类型变量实现)
本章到此结束 谢 谢!