一、指针与内存地址
(一)指针解决的问题
C语言的高效、高能主要来自于指针
·可以通过指针来操作某些不能被直接访问的数据
·可以通过指针作为函数来实现调用一次函数得到多个结果
·可以通过指针来进行计算机的动态分配
·可以通过指针来处理复杂的数据结构,如链表、数、图等
-在函数调用需要传递大量数据时,可以使用指针传递地址而不是复制大量的实际数据,既提高传输速度,又节省大量内存
-在重复性操作数据的情况下,使用指针来读取数据,可以利用指针对某一块内存区重复操作,明显改善程序的读写性能,例如在遍历字符串,查取表格及操作树状结构时
(二)变量的内存地址
程序设计语言
问题:有几种方法可以找到某个实验室上机?
·按实验室编号来找到实验室,类似于C语言中通过指针来间接访问变量
·要理解指针,首先要理解变量、内存单元和地址之间的关系
数据、地址与内存(64KB)
二、指针基础知识汇总
(一)指针变量
1.指针变量示意图
·存放变量的地址需要一种特殊类型的变量
·指针变量——存放变量地址的变量
2.指针变量的概念和定义
(1)什么是指针变量
一个变量在内存中存储的首地址称为该变量的指针,值为指针的变量称为指针变量
(2)指针变量的定义
【格式】数据类型名 *指针变量名[=初值],··· ···;
【示例】int *p
【功能】定义变量p是一个指针,p将指向一个int类型的变量(p的值是一个int类型变量的首地址)
3.取地址运算符与取值运算符
取地址运算符(&):对常规变量取址,获得指向该变量的指针
取址运算符(*):对指针变量或指针表达式取值,获得指针所指的变量
4.编程经验
(1)指针必须先指向某个变量,然后才能使用该指针
(2)指针通过“==”或“!=”判断是否指向同一个变量
(3)如果指针指向单一变量(非数组),除赋值外,只允许三个运算:*、==、!=
(4)指针变量错误使用示例
(二)指向数组元素的指针
1.数组名作为指针常量(指向数组首元素)
例:用数组名做基地址完成数组的输入输出
2.通过指针变量访问数组
3.通过指针变量访问数组
指针指向数组的一个元素,基于指针的各种表达式
4.编程经验
(1)指针指向数组时,数组元素有多种访问方式
(2)指针指向数组时,允许的其他运算符
(3)特殊情况下,允许下标取负值
例:用指针完成数组的逆序输出
分析:使用指针实现数组的循环遍历,先从前到后输入,再从后到前输出。
(三)指针与字符串
1.思考:字符串如何存储?
C语言并没有为字符串提供任何专门的表示法
2.字符数组与字符指针的重要区别
·如果要改变数组sa所代表的字符串,只能改变数组元素的内容
·如果要改变指针sp所代表的字符串,通常直接改变指针的值,让它指向新的字符串
例:如何改变sa所指向的字符串
3.字符串输入到字符数组的基本方法
(1)在输入前准备好一个足够“大”的字符数组
(2)输入一行字符串(行)
(3)输入一个字符串(词)
4.字符串输出的基本方法
例:用不同方式输出同一个字符串
5.编程经验
(1)字符串输入时数组空间太小会溢出
(2)字符串赋值应使用专用函数
(3)字符串比较应使用专用函数
(4)字符串初始化
(5)区别字符串长度与字符数组长度
(6)区别字符 '\0' 、 '0' 和 0
例:将输入字符串按大写字母、数字字符分别提取,合成新字符串并输出。
输出格式要求:[大写字母部分][数字字符部分]
(源代码)
(运行结果截图)
(四)指针与函数
1.指针作为函数参数的应用
要通过函数调用来改变主调函数中某个变量的值:
(1)在主调函数中,将该变量的地址或者指向该变量的指针作为实参
(2)在被调函数中,用指针类型形参接受该变量的地址
(3)在被调函数中,改变形参所指向变量的值
(4)也可以利用指针作为函数参数达到调用一次函数得到多个返回值的目的
2.传递单个变量的指针(授权让函数操作变量)
例:编写函数求出main函数的两个整数变量a和b的最大值和最小值,并把结果存储到main函数的max和min变量中。
3.传递数组到函数(授权让函数操作数组)
·数组名是一个指向数组首元素的常量指针,数组名作函数参数,相当于指针作为函数的参数
·数组名做实参,形参是指针变量(数组)
·形参也可以写成数组形式
例:输入十个成绩,排序后输出前三名的成绩
分析:数组的输入、输出、排序分别用函数实现,形参为指针或数组,实参为数组名
(源代码)
(运行结果截图)
4.返回指针的函数
·函数可以用return关键字向调用它的函数返回指针类型的数据,主要的目的是为了告诉主调函数某变量或数组的地址。
·返回指针的函数定义一般形式如下:
例:编写一个子函数,在主函数定义的字符串中找到第一个大写字母,并向主函数返回这个字母的地址
(源代码)
(五)指针与结构体
1.概念
·结构体指针就是指向结构类型变量的指针
·通过结构体指针可以访问结构体变量或者结构体数据
·结构体指针的一般定义形式为:
struct 结构体类型名 *结构体指针变量名
2.赋值
·结构体指针定义后必须先赋值才能使用,也就是把结构体变量地址或者结构体数组名赋值给结构指针,如有语句:
3.使用
(1)用*p访问结构成员。如:
(2)用指向运算符 “->” 访问指针指向的结构成员。如:
4.结构体指针访问结构体数组
5.结构体指针与函数
用结构体指针或结构数组名作为函数参数仅仅只需要传递结构体变量或者是结构体数组的地址,因而效率更高;而且被调函数内还可以修改结构体指针所指向的结构体变量或数组的内容。
三、特殊指针
(一)指针数组
1.指针数组的概念
·C语言中的数组元素可以是任何类型,如果数组的各个元素都是指针类型,用于存放内存地址,那么这个数组就是指针数组
·指针数组就是元素均为同类型指针的数组
·定义格式:类型名 *指针数组名[数组长度];
2.普通数组和指针数组的比较
3.指针数组的初始化
·多个指针组成的数组,如char *pname[10];
·如对指针数组做初始化,指向结果如图
4.指针数组的使用
·使用指针数组打印多个字符串。
·pname[2]指向“Sun”的首字符'S',pname[2]+1指向'u',*(pname[2]+1)指向'u',*(pname[2]+1)就是访问字符'u',与pname[2][1]等价
5.思考:多个字符串如何处理
·p多个字符串可以采用二维的字符数组来处理,二维数组的每一行可以存储一个字符串
·也可以采用字符型的指针数组来处理,指针数组的每一个元素都指向一个字符串
例:名字字符串排序——二维数组
6.编程经验
·结论:用指针数组处理多个字符串更为灵活和高效
【案例】人名按字典顺序排列
分析:
例:姓名排序的代码——排序函数
(源代码)
(运行结果截图)
(二)二级指针
1.以指针数组为基础
·如果一个指针变量存放的是另一个指针变量的地址,则称这个指针变量为二级的指针变量,也称为指向指针的指针。
·二级指针变量定义的形式为:类型名 **指针名
如:
2.二级指针的使用
·char *pname[ ]={“Zhao”,“Qian”,“Sun”,··· ···};
·char **p2; p2=pname; 或 p2=&pname[0]; //定义二级指针并赋值
·第i个姓名:pname[i] 或 p[2] 或 *(p2+i)
·第i个姓名的第j个字符
pname[i][j] 或 p2[i][j] 或 *(pname[i]+j) 或 *(*(p2+i)+j)
·打印多个姓名字符串的两种方法
例:颜色字符串排序
问题:将5个字符串排序,将字符串排序的功能用函数来实现。函数fsort用指针数组名作为形参,函数调用时用一个指向pcolor的二级指针作为实参
(源代码)
(运行结果截图)
(三)指向一维数组的指针
1.以二维数组为基础
·如何用指针来访问二维数组呢?
·回顾二维数组的存储方式:二维数组是连续存放在一块内存区域的,各个元素在内存中首先“按行”;而每行的元素按照列下标的递增来逐个存放
·以int a[2][3]为例,如图所示
2.指针和二维数组间的关系
·C语言允许把一个二维数组看为虚拟的二重一维数组
·第一重:数组a看成两个元素的数组:a[0]和a[1]
·第二重:a[0]包含3个元素:a[0][0],a[0][1],a[0][2];a[1]也包含3个元素:a[1][0],a[1][1],a[1][2]
·那么:a、a[0]、a[1]是什么指针?
3.思考:二维数组名是什么地址
a 代表二维数组的首地址,第0行的地址,行地址
a+i 代表第 i 行的地址但并非增加 i 个字节
4.指向一维数组指针的定义
·指向一维数组的指针(也称为行指针)的定义格式:数据类型(*指针变量名)[常量表达式]
·如有int a[2][3];
int (*p)[3];注意*p两边加了括号,定义指针p是一个指向一维数组的指针,且所指一维数组有3个元素
·p=a;则p指向a[0],a[0]是有3个元素的一维数组,p+1指向a[1]行,*(p+1)等价a[1]
5.指向一维数组指针的使用
·二维数组的行指针
·逐行查找->逐列查找
其他等价法
例:输入一个3行4列的二维数组,然后输出这个二维数组的元素值
(四)函数指针
1.概念
·函数名具有类似数组名的地址特性:
数组名——该数组的首地址
函数名——该函数的入口地址
·一个函数作为一段程序,在内存中占据一片连续的存储单元,其中第一条执行指令所在的位置称为函数的入口地址,函数名就代表了这个入口地址,取值为改地址的指针就称为指向函数的指针变量,简称函数指针
·通过函数指针可以调用并执行函数
2.函数指针的使用
通过函数指针来调用函数的基本操作可以分为以下几个步骤:
(1)被调用函数的函数声明
(2)定义函数指针
(3)函数指针赋值
(4)通过指针pfunc来调用函数
例:求三角函数sin(),cos()和tan()在30度,60度,90度,120度,150度,180度时的数值。函数printvalue形参为函数指针fun,实参依次为sin,cos,tan,通过调用(*fun)间接调用sin等函数
(源代码)
3.编程经验
·给函数赋值时,不能给函数参数。
如:
·通过函数名调用和通过函数指针调用等价
·对函数指针变量,像p+i,p++,p--等运算无意义
·函数指针可以组成数组
四、综合案例
例:猴子选大王
问题描述:山上有50只猴子要选大王,选举办法如下:所有猴子从1到50进行编号并围坐一圈,从第一号开始按顺序1,2,···,n连续报数,凡是报n号的猴子都退出到圈外,照此循环报数,直到圈内只剩下猴子时,这只猴子就是大王,输出大王的编号。
分析:该问题使用数组比较方便,将数组元素按1-50赋值。出局的猴子通过调用函数来删除。
(源代码)
小结
·指针即地址,指针变量就是专门用于存放变量内存地址值的变量
·利用指针可以间接访问它指向的变量或者数组
·结构体指针就是指向结构类型变量的指针,通过结构体指针可以间接访问结构体变量或者结构体数组
·指针可以作为函数参数,也可以作为函数返回值,在主调函数和被调函数间传递变量、数组、结构体的地址
在实际的编程应用中,可以使用字符型的指针数组来处理多个字符串,这比用二维字符数组处理多个字符串更加灵活和高效;也可以使用指向一维数组的指针(即行指针)来处理二维数组。
该文章属于个人课后学习笔记,内容来源于知到智慧树共享课-C语言程序设计(上海电力大学)。