当前课程知识点:基于Linux的C++ > 第七讲 指针与引用 > 7.4 指针与复合数据类型(一) > LinuxCPP0704
接下来就是指针和复合数据类型的关系
我们先看指针和数组的关系
这里边涉及到几个地方
第一个 数据对象的地址该怎么去计算
第二个 作为函数参数的指针和数组
它们之间相同的地方和不同的地方
很多情况下边 指针和数组是可互换的
多维数组作为函数参数的时候
怎么去处理它
接下来就是指针和结构体
指针指向一个结构体怎么弄
指针就是结构体的一个成员
该怎么去处理指针和结构体之间的关系
我们先看指针和数组之间的关系
回忆我们介绍数组的时候谈到过的
数据对象地址的计算
我们原来有一个数组定义int a[8]
数组的基地址就是&a或者写a
因为我们当时说过 这个数组元素
它的存储顺序就是0号元、1号元、2号元
它就按照这个顺序放
前面没有任何控制格式的存储空间
后续也没有结束标记
数组的基地址就是
数组的0号元的基地址
所以你写&a[0]
取的就是数组的0号元的基地址
当然就是数组的基地址
我想知道这个数组的
第i号元的基地址怎么办呢
因为它紧挨着存放的
所以就用0号元的基地址加上i * sizeof(int)
就能够算出它的第i号元的基地址
因为每一个元素都是一个整数
每一个整数占的存储空间的大小是sizeof(int)
乘上i(第几号元)
就得到了第几号元的基地址
这就是标准的计算的式子
因为我们前面讲了真实表达地址的时候
&a[0]它其实就是和a是等价的
表达它的地址概念的时候
数组第i号元的基地址就是a + sizeof(int)
定义了这个int a[8] 我用个指针int *p
p赋值为0号元的基地址
p将指向我们的0号元
如果你还有一个指针q
让q赋值为&a[2] 让q指向2号元
现在怎么能够表达这两个指针
p、q之间的联系呢
p、q这两个指针
两个彼此之间一点关系都没有
它明明是指向一个数组中的两个元素啊
它们怎么能够一点关系都没有呢
它们都指向同一个数组中的元素
怎么表达这两者之间的关系
这个就涉及到了指针的算术运算
指针可以和一个整数进行一种加减运算
如果我设p为指向数组中的
某一个元素的指针 i为整数
p+i就表示这个指针
向后滑动i个整数单位
p-i就表示指针向前滑动i个单位
p如果一开始指向a[0]的
p+2就会指向a[2]
p如果一开始指向a[3]的
p-2就会指向a[1]
加2就是向后滑动两个单位
就向数组的尾部滑动两个元素
元素的类型是什么 就滑动多少
整数数组 那就滑动两个整数
如果是一个指向结构体的数组
那么每个元素都是一个结构体
那就滑动i个结构体
因为我们这里面是整数
所以滑动i个整数
不是滑动多少个字节数
你如果这么说 p是指向a[0]的
一开始q是指向a[2]的
p+2就是指向a[2]的
所以q如果赋值为p+2
那么q就会指向a[2]
注意这个式子
这是一个赋值表达式
指针和整数加减运算的时候
它是以一个指针
所指向目标数据对象为单位的
不是以字节为单位的
还有一种情况就是加加减减操作符
我们的递增递减运算
指针允许你做递增递减运算
如果p是指向a[0]的
那么p++就会指向a[1]
如果p是指向a[1]的
那么--p就会指向a[0]
回到我们刚才例子 Q赋值为p+2
q会指向a[2]
p+2也会指向a[2]
这就意味着这两个指针指向同一个地方
就意味着这两个指针的值
本身是相等的 在数学上是相等的
所以你一改 把p挪到等式的左边
你一下就看到 这就是q-p结果就是2
这个就是指针的减法运算
你可以对两个指针进行一个
标准的减法运算
减法的结果就是一个整数
它表示这两个指针
它们中间间隔的元素个数
它是连头不连尾 或者连尾不连头
也就是说两边的数据你不能都算
p是指向0号元 q指向2号元
所以q-p结果就是2
如果我用一个指针
指向一个数组的0号元
我还有一个指针
指向这个元素的最后一个
这两者差值就是数组的元素个数减一
减一这个过程很讨厌
就可以弄一个招
我在这个数组最后
专门设计一个过尾的元素
让p指向数组的0号元
让q指向这个数组的
最后一个元素后边的位置
所以这两者一减
就正好是数组的元素个数
最后那个元素就叫过尾元
指针还可以进行关系运算
我可以判定两个指针是不是相等
也就说是不是指向同一个地方
p==q、p!=q 这样判断都是可以的
还有一种很特殊的东西
它是一个空指针
有一个定义的宏 叫NULL
它表示空指针 指针值就是0
表示这个指针不指向任何一个地方
就用NULL来表示它
其实它指向的是内存条最开头的存储区
不过现在主流的操作系统下边
几乎所有的操作系统在那个存储区
都是什么数据都不保存的
专门就用来捕获我们的指针错误的
所以如果p赋值为NULL
就表示p不指向任何有意义的
目标数据对象 有了它
就可以测试指针p是不是有意义的
如果我在写程序的过程中
我保证了这个指针p
要么是指向一个合法有效的数据对象
要么就是指向0
每次使用指针之前
就可以通过这个测试if(p != NULL)
来测试这个指针是不是有效的
这样的话 p != NULL说明它是有效的
p == NULL 说明它是无效的
如果你不测试这个指针是不是有意义的
当你在编写带有指针的程序的时候
它非常容易出错
如果这个指针是错的
它指向一个无意义的地方
或者它没有权力去访问的地方
这个程序不是错的 而是崩溃
这个错误就太严重了
所以我们每次使用指针
就需要特别注意这一条
每次访问指针
想通过引领操作符
访问它的目标数据对象的时候
都要测试这个指针是不是有意义的
指针数据对象 理论上来讲
你应该对它进行初始化
要么是合法有效的
目标数据对象的地址 要么就是0
只有这样才能够保证你的测试
每次都是有意义的
因为如果你不对它进行初始化
它是个全局量 自动被初始化为0
如果这个指针变量本身是个局部量
那么它内部的位序列就是随机的
极有可能不是0
如果不是0 你一访问
目标数据对象区域没有权力访问的
程序一下就崩了
当作为函数参数的时候
指针和数组具有什么样的关系
我们先来回忆数组作为函数参数的时候
我们当时是怎么用的
我们当时函数定义是按照这个格式:
void GenerateIntegers( int a[], unsigned int n)
我们是按照这个方式写的
第一个参数要传这个数组
第二个参数要传数组的元素个数
我们那时候讲
如果你直接在这个数组中括号里面
写元素个数 写一个魔数进去
当时我们说那个不好
你不写也不成
所以我们就真不写
但是后边传一个额外的第二个参数
把元素个数给传进去
就按照这个方式去写的
到这个函数内部可以写一个for循环
然后a[i]一个一个去操作
我们当时讲了 数组作为函数参数
是能够带回来结果的
用的时候就会定义像这样的数组:
int a[NUM_OF_ELEMENTS]
NUM_OF_ELEMENTS
就是我们定义的一个宏 值是8
然后我们调用这个函数GenerateIntegers
第一个参数传数组的基地址
第二个参数传数组的元素个数
这就是数组作为函数参数的时候
我们当时是这么定义 这么使用的
现在如果我们使用指针
作为函数的参数
那么我们应该这么写:int *p
这是指向整数的一个指针
第二个参数同样地要传元素的个数
我们内部去访问它
就使用*p(引领操作符)
来访问它所指向的数组0号元
i从0开始 p一开始传数组基地址进来
它指向的就是数组的0号元
*p一操作 一赋值
那么就会把0号元给设了值
然后我用指针运算p++
让它指向1号元
看这段代码 写的是*p++
这是两个操作符*p、p++
因为是后缀++
所以要把GenerateRandomNumber函数调用以后
生成的那个整数赋值给*p
然后才能做p++
它做的绝不是(*p)++,不是(*p)
如果你是想把*p的目标量累加
那么你必须写(*p) 括号完毕之后++ 那才对
p一开始指向0号元
所以把生成的那个数赋值给0号元
然后p++ 让p指向1号元
循环第二次迭代
赋值 p指向2号元
它是按照这个方式
不断去操纵这个数组的每个元素的
这是指针作为函数参数
在实际调用的时候
它和数组作为函数参数没什么差别
同样定义是一个数组
int a[NUM_OF_ELEMENTS]
同样地调用GenerateIntegers
传数组的基地址 传数组的元素个数
实际使用的时候
传递的和刚才是一模一样的
但是你要记住
你要传已经分配空间的数组的基地址
你不能传其它的指针(没有分配空间的)
我们这里边传递int a[NUM_OF_ELEMENTS]
这个数组的空间
在调用之前就分配好了的
把它基地址传进去
-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 编程实践
-第十五讲 网络编程--编程实践提交入口