当前课程知识点:基于Linux的C++ > 第七讲 指针与引用 > 7.5 指针与复合数据类型(二) > LinuxCPP0705
我们来看这样一个例子
随机生成八个整数保存到数组里边
然后颠倒数组的元素
使用指针来去实现它
这个题实际上我们前面已经实现过
不过那个时候我使用的数组来实现的
现在怎么使用指针来去操纵它
前面代码是一样的 需要使用“random.h”
有一个数组操纵的库叫“arrmanip.h”
数组元素当然还是8个
主程序其实也没变
定义数组int a[NUMBER_OF_ELEMENTS]
然后GenerateIntegers生成这8个整数
然后输出信息
把这个8个元素都打印一遍
调用ReverseIntegers把它颠倒过来
再然后PrintIntegers把它再打印一遍
看运行的结果正不正确
main函数和原来的实现其实是一样的
使用指针来实现我们这个库的
所以这些实现
就和以前那个版本不一样了
GenerateIntegers
第一个参数int *p
第二个参数unsigned int n
这是GenerateIntegers
第二个函数ReverseIntegers
第一个参数int *p
第二个参数unsigned int n
第三个函数Swap 交换
因为我要交换是数组中的两个元素值
所以我要传的是指向这两个元素的指针
就直接传int *p、int*q传两个指针
然后PrintIntegers打印它
const int *p指向常整数的一个指针
因为打印的时候
我不会修改这个数组元素的内容的
所以这个地方要加const
第二个参数还是要传数组的元素个数
具体的实现有个lower_bound和uupper_bound
是10到99之间
这个实际上生成的函数叫GenerateIntegers
我们已经解释过了
第二个你要看到的就是ReverseIntegers
怎么对调数组中的元素
传进来的p这个指针
它将是指向数组0号元的一个指针
然后后面n就是这个数组的元素个数
对调这个数组的时候
实际上将要对调的是数组0号元和7号元、
1号元和6号元的数据
你看到它们之间有什么关系
关系就是我们对调的这两个数据
的元素下标之和刚好是元素个数减1
记住这个就可以了
所以对调的是第i号元和n-i-1号元
两者之和刚好是n减1
我们元素个数是n嘛
那么第i号元
哪一个指针会指向它呢 p+i啊
n-i-1号元是p+n-i-1啊
所以我们Swap对调的就是p+i和p+n-i-1
这两个指针所指向的目标数据对象
实际对调的时候还是标准三步互换
然后是PrintIntegers
我们要打印这个数组全部的元素
for循环里边打印它
设置场宽之后打印它的第i号元
我p+i得到的是它的i号元吗
错 得到的是第i号元的地址
P+i本身是指向第i号元的指针
所以要引领才成 我要*(p+i)才成
但是这个地方没有括号是不对的
没有括号就变成*p了
取出来目标值然后再加i了
因为这个新号操作符的优先级
是远远的高于这个加法的
要p+i找到i号元
然后括号外去引领它 “*”引领它
我们就能够PrintIntegers
整个这个程序实现完了
你会看和我们原来数组那个方案
得到的结果是一模一样的
所以从某种程度来讲
我既可以通过这个指针来解决这个问题
也可以通过数组来解决这个问题
两者的实现方案
你看实际上是几乎没差别
内部实现是有差别
但是从外界来看的话
给出来的结果是没差别的
从某种程度来讲
我们就知道指针和数组是可以互换的
指针一旦指向一个数组基地址
那么是使用指针来操作这个数组
还是使用数组格式来操作这个数组
地址计算方式是一模一样的
你比如讲如果我有一个一维数组
int a[3] 然后int *p初始化成数组的基地址
for(i=0; i<3;i++),cout << p[i]
这个方式是对的
p是数组是吗 不是
是指向这个数组的0号元的指针
但是 你就可以把p
当做数组一样的用p[i]
有的初学者在这个问题就会犯糊涂
这是意味着什么呀
这个地方写a[i]是对的
写p[i]是对的
你一下子就能够反应出来
那a就等于等于p啊
那a就是p ,p就是a啊
否则怎么可以这么替换呢
如果是这样
那指针不就是指向那个数组吗
可是我前面不是说过了吗
这指针是指向那个数组吗
指针不是指向那个数组
指针仅仅指向这个数组的0号元
不是指向那个数组
所以有很多初学者在这个问题上
就会犯糊涂了
为啥可以互换呢
唯一的原因就是用指针的方式
来取这个数组i号元的基地址
还是用数组的方式
来取这个数组i号元的基地址
两者的计算格式是一样的
并不意味着这么写法a就和p是等价的
不是的 是它的计算方式是等价的
所以我们才可以这么写
在这种情况下面
一旦这个指针p指向这个数组的0号元
那么你就可以把p这个量本身
当做一个数组名字一样用p[i]
第二个 把这个数组当做一个指针来用
你比如说我可以这么写:*(a+i)
取的就是这个数组的第i号元
a+i得到的就是这个数组的
第i号元的基地址
我们前面不说了吗
这个数组的第i号元的基地址
是怎么得到的
假设这个数组的基地址是p
p+i*sizeof(int)
C/C++设计者啊
他认为如果让你每次在i上面
都乘上sizeof(int)
太麻烦 不需要你写
自动在i上乘上以后
再累加到a的基地址上
所以你就可以直接这么写
*(a+i)就表示取这个数组的第i号元
可以赋值给别人 可以被赋值
取的就是它 那你这么一看
那a和p不还是等价的吗
我们用指针的话不就是*(p+i)嘛
这就是说 在某种程度上讲
指针和数组是等价的 可以互换
可以把一个指针当成一个数组
可以把一个数组当成一个指针
注意 这并不是全部的应用场合
在一维数组上面是有效的
一旦到了高维它可能就是无效的了
指针和数组真是完全等价的吗
当然也不是 它还是有一些例外的
数组名字本身 它是一个常数
你不能够在这个数组格式上面
进行一些特定指针运算的
有些指针运算是不可以的
你比如是这样 指针p是可以被赋值的
所以你*p++ 这是没有问题的
如果它是一个数组的名字
*a是可以的 但是你*a++是不可以的
为啥 a++你不能做
a作为一个数组的名字
它代表的就那个数组的基地址
它事实上是一个常数
你是不可以做a++的
你可以在上面做a+1、a+2
你不可以做a++
因为a++不是a+1
a++它是a+1赋值给a
因为a是一个常数 它不能被赋值
而p 它可以被赋值
它是指针量 就这一点差别
导致指针和数组的互换性是有例外的
我们可以对指针和数组的关系
做一些总结
使用指针和数组来声明这样的
一个数据对象
它的这个性质是不一样的
如果你int a[3]
那么就定义了3个元素的这个数组
正常情况下面
我们会为这三个元素都分配存储空间
它们依序地存放
如果我用int *p 定义像这样的一个量
那么它会为p分配一个存储空间
这个存储空间有多大呢
我们现在的32位计算机、32位的编译器
p是4个字节
那int a[3]多大呢 12个字节
每一个整数是4个字节
总共3个整数 12个字节
所以分配空间是不一样的
你如果将p初始化成&a
a的基地址
那么p将会“指向a那个数组”
但是它并不是那个数组
这是一个指针量
它指向一个数组的0号元
定义指针的时候
规定的是指针数据对象的存储空间
定义数组的时候
它规定的是数组元素的存储空间
这两者是不一样的
所以说如果是一个全局数组
你比如说a为静态分配的这样一个数组
程序运行前就会分配空间
每个元素每个元素就按照顺序放好了
如果你定义一个指针
它就不会为那个数组分配空间
它只分配指针这个对象的空间
如果这个指针指向那个数组
你必须保证那个数组已经分配了
其他地方分配的跟这个指针没关系
除非你是动态分配的
所以当你使用指针的时候 要特别记住
指针这个对象和指针所指向的目标数据对象
两者之间的关联
在你程序里边必须显示地构造它
要完成这一点
还有一个技术细节
是多维数组作为函数参数
如果是一维数组作为函数参数的时候
我们说最恰当的
参数传递的方式就是写int a[]
中括号里边什么也不写
后面跟着第二个参数
传数组的元素个数
这是最正确的一个方案
如果是个多维数组
这个情况就有点问题
第一个 打印二维的一个数组
PrintTwoDimensionalArray
比如说8乘8的这样一个数组
你如果按照第一种这个方案
我直接传递这个数组的
元素个数 这个方案是不妥的
如果没有这个后面两个参数
这个函数写下来 它就受限
因为你封装这个魔数 8和8
两个魔数8就进去了
它就只能处理8乘8的数组
8乘9的处理不了 9乘8的处理不了
10乘10的处理不了
我怎么办呢 后面带参数
传两个参数m、n
传它有多少行m行
有多少列呢 n列
我这里面不使用8乘8的这个信息
我使用m乘n这样一个信息来编程序
这样就保证程序
就和这个魔数8没关系了
还是不妥当 为啥
就是这里面还是这个魔数
仍然没有取消掉
这个程序这么写 它是对的
但是有那么一点不妥当的地方
如果这个魔数你真地都不写 不行
语法规范要求 两个中括号里边
你只能有一个中括号不写
不管这个数组元素个数有几维
一维数组可以不写
二维数组只能第一个中括号里面
什么都不写
三维数组也只能第一个中括号里面
什么都不写
后面你都必须给写上
没有数据是不对的 编译器是通不过的
我们还有一种方案
把二维数组当一维数组降维
有什么不行的呢
先存第一行 后存第二行 再存第三行
不按照这个方式存的吗
既然是这样
那我干脆把所有行当成一行算了
然后按照一维方式来去写
你比如说这个指针
就按照这个方式写:int *a
传 m行n列 那么我们怎么取a[i][j]
第i行第j列这个元素呢
简单 用指针运算 就是a+n*i+j
我们就按照这个方式来算
但是它也是不妥当的
从某种程度来讲
它明明是一个二维数组
你非要把它降为成一维
这个行吗 这个行
但是逻辑上总觉得有点怪
更别说它的运算很复杂
i、j下标的时候去找
要a+n*i+j 这个方式有点小麻烦
到了高维数组以后三维以上 这么算
能不能算出来 都不好说了
到了高维 降维都有可能会出问题
理论上来讲应该没问题
但是你去算它哪一个元素的时候
它就可能会有问题
所以实际上也还是不妥的
那你说 我有没有一个妥当的方案呢
很抱歉 没有
不管是C还是C++里边
当多维数组作为函数参数传递的时候
就没有一个好方案能够解决这个问题
所以说实在没招 我建议同学们
都按照第一个方案写
我们现在来看这样一个例子
刚才第三种方案
使用一个指针对一个二维数组进行降维
我们怎么来访问它
传指针参数 传m行n列的信息
然后在内部降维
每次想访问a[i][j]这号元素的时候
要+n*i+j 括号外用“*”去引领
按照这个方式
接下来一个主题就是指针和结构体的关系
指针可以指向一个结构体
我有一个struct STUDENT结构体
定义一个pstudent
这样一个指针指向一个STUDENT结构体
然后把它初始化成student的基地址
要用指针来访问目标结构体
我要引领
*pstudent得到那个目标结构体
然后要成员解析 打点取id
这个地方必须写括号
因为那个点号操作符的优先级高
你不加括号它会认为pstudent
是一个结构体
然后取它的id的这个成员
而这个id是一个指针
然后引领这个成员
所指向的那个目标数据对象那就不对了
所以我们这里面应该是(*pstudent)
括号完毕之后打点取id
没辙 语法规范的要求必须这么写
C/C++提供了一个新的操作符
叫做“->”操作符
跟我们成员选择“.”操作符是一样的
还是一个选员操作符
表示pstudent是一个指针
它指向一个结构体
我想取目标结构体id成员
那我就直接这么写 pstudent->id
比刚才写的方便了吧
刚才要用4个符号
现在俩符号就够了
不需要写一个括号对了
这样的方案当然要比刚才那个方案
看上去要好写得多
指针和结构体当然不会这么简单
那如果我一个结构体成员
是一个指针会是什么样子
我定义一个struct ARRAY结构体
它里面带着一个数组元素个数count
后面跟着一个指向整数的指针elements
这个elements会指向什么呢
它将会指向我们实际数组的
0号元、1号元、2号元
按照这个方式
我们这里面有个int a[8]
定义好这个数组
然后构造这个数组array
把它初始化
当我想访问这个elements数组
它的第i个元素的时候怎么写呢
我就这么写array.elements[i]
也就说把elements这个指针
当做一个数组一样用
如果你还有一个定义
parray本身就是一个指针
并且我们把它初始化成array这个基地址
那么你想访问parray所指向的
那个目标结构体中的那个成员
所指向的数组的第i号元
我怎么访问呢 那你就这么写(*parray)
就引领它
括号完毕之后打点取elements[i]
取它的i号元
或者你这么写:parray->elements[i]
取到了parray这个指针
所指向的那个结构体的elements那个成员
所指向的那个目标数组的第i号元
结构体指针
有两个非常重要的使用场合
使用指向结构体对象的指针
来作为函数的参数
它有两个好处 第一个
可以节省结构体整体赋值的时间成本
我们讲作为函数参数
你要传一个结构体 它将是整体赋值的
你如果没有使用引用
它就必须按照这个方式来
如果结构体尺寸很大效率肯定是低的
使用指针 问题就简单了
所以一传 传的是结构体的地址
所以这种情况下传结构体的地址
要比结构体整体赋值快得多
第二个 普通结构体类型的参数
作为参数传进去 它不能把结果带回来
我们就觉得不方便
我们想带回来结果 传它的指针
它那个结果就能带回来了
你又想快 又不想带回来结果呢
就指向const结构体的指针
第二个使用场合
构造复杂的数据结构
我想创建一个数据结构
这个数据结构能够表达
动态的数组信息
表达数组元素个数
可以在整个程序运行期间
随时发生变化的数据结构
它里边保存了一系列元素
这些元素个数
可以在程序运行期间动态的变化
可以大 也可以缩小
这个我们就称它为动态数组
我们就需要使用很特殊的
像这样的数据结构:struct ARRAY
它实际上是用一个结构体
来表达一个数组的概念
这里面包含了一个元素的个数
这个数组元素个数是多少个count个
用一个int * elements来表达
指向特定元素的指针
以后就可以通过elements指针的运算
来访问它的0号元、1号元、2号元
这个就叫动态数组
你定义好了这个数据结构
必须为它创建一系列对动态数组
进行操作的函数
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口