当前课程知识点:基于Linux的C++ > 第七讲 指针与引用 > 7.2 指针基本概念 > LinuxCPP0702
我们首先来看指针
这里面涉及到一个重要的地方
我们前面已经谈到过
每一个变量 也就是说每个数据对象
它有四个基本特征:VANT
一旦你的程序编译完了以后
N和T 也就是它的名称和类型就没了
正常情况下 信息是缺失的
名称会缺失 类型会缺失
在我们的程序代码里边
实际上主要就那么两个东西:
一个是地址 一个是值
地址就是数据对象放在什么位置
值就是在内存区那个位置里
放了什么东西
这就是两个东西 地址和值
把地址和值完全孤立去看待它们
这是不可以的
地址就是地址 值就是值
两者之间完全没有什么关联
你如果按照一种孤立的、僵化的、
机械的观点来看待
这一定是不对的
它不符合我们的马克思主义哲学原理
地址和值是辩证统一的关系
同学们一定要记住这句话
通过一种恰当的机制
我们可以让一个量的地址就是值
可以让一个值就是一个地址
这个机制叫什么呢
同学们现在已经知道了 它叫指针
为什么要讲指针
就讲这个东西的——它就是构造地址和值
辩证统一的最重要的一个桥梁
有了这个概念之后
我们就可以来定义指针类型的变量
使用指针类型的变量
然后我们就会讨论指针到底和函数、
复合数据类型、数组、结构体
具有什么样的关系
在此过程中 我们还需要了解
我们怎么进行简单的指针运算
这个就是我们涉及到的
最基本的指针的内容
后面实际上我们讲字符串
讲动态存储管理
其实仍然还会涉及到指针
包括我们下一讲程序抽象
其实也仍然要涉及到指针
我们首先来看指针的定义或使用
你真定义一个指针变量的话
它的方法其实是非常简单的
就是一个目标数据对象类型、“*”、
指针变量的名字
你比如讲第一个例子 int * p
我就定义了一个指针类型的变量
名字叫p 它是一个指针 指向一个整数
把这个东西倒过来看
定义一个变量 名字叫p
它是一个指针 指向一个整数
你如果有一个struct POINT结构体POINT *p
那么你就定义一个指针变量p
它是一个指针
指向一个结构体POINT 很清楚吧
如果你想定义多个指针变量
那么你必须按照这个方式写:int *p, *q
这样的话p是一个指针 指向一个整数
q是一个指针 指向一个整数
这是非常明确的定义
第二个“*”不能少了
如果第二个“*”没了 写成int * p, q
那么这就意味着
p将是一个指针指向一个整数
而q就是一个整数 它不是一个指针
注意这个概念
初学者非常容易在这个地方犯错误
会忘了写第二个“*”
怎么解决这个问题呢
怎么确保自己不忘呢
第一个 编程你要仔细
除此之外就是用第二种方案——用typedef
把int*给重新定义成PINT
创建一个新的类型PINT
让它等价于int *
我们前面讲typedef这个关键字的时候
特别谈到过
typedef将为我们创建一个新的类型
新的类型将和原始的类型一模一样
也就是说 在我们程序代码里边
凡是能够出现PINT地方
都可以用int *来代替
凡是能够出现int *的地方
都可以用PINT来代替
两者是完全等价的
我们讲typedef的时候就这么谈到过
但是你要记住
这个等价并不是文本上的等价
也就是说你不能把这个例三
这个地方的int *替换成PINT去编译
有了PINT这个类型的定义
那我就写PINT p, q
这样的东西 就会为我们定义两个
指向整数的指针变量
p是个指针 指向一个整数
q还是一个指针 指向一个整数
这一点和前面这个定义是不一样的
你说如果我简单文本替换
我把int *替换成PINT
第三个例子 那就是PINT p, *q
q就是一个指针
它指向什么呢 它可不指向整数
同学们记住了
q那个时候将会指向PINT
也就是 q将是一个指针
它将指向一个指针
而那个指针才指向一个整数 它不是p
p它是一个指针指向一个整数
注意这个概念
所以这个时候如果你写PINT p, *q
那么q将是一个指向指针的指针
绝不是指向整数的指针
注意这个概念
所以一旦有了typedef PINT出来
那么p、q这么定义就是两个指针了
如果你没有typedef这个定义
那么你就必须写int * p, * q
在使用指针的时候 同学们一定要记住
指针数据对象事实上涉及到了
两个数据对象 并不是只有一个
一个就是指针数据对象本身
第二个就是指针所指向的这个
目标数据对象 你比如说讲
我用int * p定义一个指针变量p
它将指向一个整数
你如果按照这样方式来定义它
你也没有对它进行初始化
也没有对它进行赋值的动作
那么在你的内存里边
就只会分配一个指针变量的地址空间
假设我们把它随便放在内存某个地方
然后旁边写个p
OK 这是一个指针变量存储布局
它里面什么东西都没有
因为我们没有初始化
也没有对它进行赋值
所以什么也没有
如果你在定义这个指针变量过程中
同时对它进行初始化
那么这个时候就会涉及到两个数据对象
假设我有一个整型量n被初始化成了10
然后有一个指针p指向这个整数
并且把这个指针p初始化为n这个
整数的基地址 就这么写 int * p = &n
把n的地址传给p
我们初始化的是这个指针变量p
n会放在内存中的某个位置
假设这个地址的编号是0x00130000
里边的内容是10
p被初始化的时候 将会保存n的基地址
n的基地址是0x00130000
所以p里边将会保存0x00130000
计算机硬件架构有一个特殊的功能
有一个间接访问机制
我们可以使用间接访问机制通过p
来访问p所指向这个目标数据对象n
为什么说是指向呢
你看这个图 n放在内存某个地方
n的内容里面是有的 地址空间是有的
而p呢 它里面存的就是n的地址
所以在看上去构造了一个p和变量n
两者之间的一个指向关系吗
我们就通过计算机间接访问机制
通过p来访问n的
正是从这个角度上来讲
p和n通过初始化的动作
构造了一个指向的关联
而这个就是我们这个东西
为什么叫指针的关键的地方
就是因为这个指向的关联
这个是和计算机的间接寻址的机制
完全地吻合在一块的
如果我们定义一个指针变量
让它指向一个数组中的一个元素
和我们指向一个整数非常得相似
有一个整数数组a
它里面包括8个整数 要对它进行初始化
指向整数的指针p
我要把指针初始化为这个数组a的基地址
因为这是个数组的名字
所以前面取它的基地址的时候
前面要不要加“&”都一样
假设我这个数组的起始位置是0x00130000
也就是说它的0号元就放在这个位置
让p这个指针指向这个数组的基地址
指向这个数组的0号元
p的内容就会写入0号元的基地址
0x00130000
就构造了这个指针p和数组0号元的
一个指向关系
p作为一个指针
将指向数组的a的0号元
也就是说p作为一个指针
并不指向这个数组
按照这样方式定义的指针p
并不指向这个数组
仅仅指向那个数组的0号元
因为p作为一个指针
它只能指向一个整数
它并不能够指向8个整数
所以我们用数组的基地址赋值给它
初始化给p
那么它将指向这个数组的0号元
这就是指针变量的存储布局
接下来 如果你有两个指针量
有一个整数量n 两个指针量p、q
p这个指针量被你初始化成n的基地址
然后你做了一个赋值的动作
把p赋值给q 这个赋值是允许的
因为指针变量可以像普通的变量一样赋值
这样的话两个指针将会指向
同一个数据对象n
p一开始是指向n的
假设这个变量n的存储位置是0x00130000
p的内容将会被初始化为n的基地址
把p的内容赋值给q
那q里面当然也是0x00130000
q和p值是一样的
这个时候就意味着
两个指针指向同一个目标数据对象
接下来我们就来讨论
刚才用到的取址操作符“&”
“&”这个操作符
它的目的是获取这个数据对象的地址
得到这个地址
像一个普通的值一样赋值给指针变量
就我前面讲的地址和值辩证统一地来看
就是把这个地址作为值
赋值给另外一个指针变量
把int n初始化成10 我定义指针变量p
int * p 然后把p赋值为&n
把n的地址赋值给p
这就是一个非常典型的动作
如果还有一个指针变量q
也是一个int *
然后你把p再赋值给q
或者把&n赋值给q
那么得到的结果也一样
你就会让这两个指针
指向同一个数据对象
接下来 最重要的一个操作符——引领操作符
我们想用指针
来访问指向的目标数据对象的话
就必须使用引领操作符
定义两个整数m和n
n的初始化成了10
然后我定义指向整数的一个指针p
然后把它初始化成n的基地址
用*p去引领p所指向的目标数据对象
p指向n 那*p就代表着这个指针p
所指向的目标数据对象n
你想取*p的内容 取的就是10
你就可以把n的内容写到m里
我们来看这个整数互换的例子
我们编写一个程序
使用指针来互换两个整数的值
这个程序的代码很简单
我们的main函数里面就这么几条语句
我定义了三个量m、n、t
然后定义了两个指针量p、q
三个整数量m、n、t
两个指针量p、q
我把p初始化为m的基地址
q初始化为n的基地址
m被我设成了10
n被我设成了20
这就是我们的五个基本量
然后我输出m、n的数据
接下来三步标准互换
注意我是通过引领操作符
来操纵指针所指向的目标数据对象
完成这个标准的三步互换的
然后我再输出m和n的值
看看我们互换后的结果是什么样子
程序实际运行的过程中
我们可以划出这样一个框图来
我这里面五个数据对象m、n、t、p、q
m是10,n是20
然后p指向的是m
q将会指向n
你注意看指针指向关系
当我完成这样的一个指针布局之后
调用我们的三步互换操作
t = *p 一旦把*p赋值给t
就意味着事实上是把m的内容传给t
t就会变成10
接下来第二步我把*q赋值给*p
*q是什么呢 是n
*p是什么呢 *p是m啊
*q就是n啊
所以我把*q赋值给*p
就意味着是把n的值赋值给m
m就会变成20
第三步 把t赋值给*q
那么*q的内容就会变成10
这就是完整的三步互换
你看到程序运行的时候
我们存储空间的这个栈框架变化了吧
p和q维持着指向m和n的
指针指向关系是不变的
在整个程序的执行过程中
p、q这两个指针
它们的指向关系都是不变的
p一直指向m,q一直指向n
但是因为我们使用了引领操作符
结果导致m和n的值发生了互换
m变成了20,n变成了10
m和n的值互换了
p、q它们的指向关系没有发生变化
这个就是通过引领操作符
进行标准的三步互换的时候
所产生的结果
我们刚才那个整数互换的例子
是使用引领操作符
互换了目标数据对象的值
然后我们来看同样一个程序
如果我直接互换的是
两个指针数据对象的值
会发生什么事情
现在这个程序代码
需要定义两个整型变量
和三个指针变量p、q、t
因为我要互换的是指针对象的值
所以三步互换t值就不能是整型量了
必须是一个指针量
p仍然指向m,q仍然是指向n
t的指针量会赋值为p
q给p,t给q
我们来看它会发生什么事
一开始指针的存储布局
p指向m,q指向n
t值开始是没有的
当你把p赋值给t的时候
t值的内容就会变成&m
当我做第二步 q赋值给p的时候
p内容就会变成q的内容
p就从&m变成&n
指针的指向关系就会发生变化
p值将不再指向m 它将指向n
第三步 当我把t赋值给q的时候
q将会指向m
三步互换做完 我互换的是p、q的值
也就是互换p、q的指向关系
但是我没有互换m和n的值
当你想真正互换m和n值的时候
一定要使用引领操作符
指针的使用场合就四个
一 指针作为函数通讯的一种手段
可以使用指针作为我的函数参数
不仅可以提高参数的传递效率
还可以让参数作为函数的输出集的一员
把结果带回来
和外界进行通讯的时候
函数可以有几个返回值呢
一个或者没有 它只能带回来一个结果
如果一个函数
想带回来两个以上的结果怎么办呢
把它两个结果合成一个结构体
用这个结构体作为函数的返回值
它就可以把这个同时都带回来
还有一种方案 指针作为函数参数
它就可以把这个结果带回来
第二个指针的使用场合
作为构造复杂数据结构的手段
使用指针构造数据对象之间的关联
可以形成一个极其复杂的数据结构
第三个作为动态的
内存分配和管理的手段
可以在程序的执行期间
动态构造数据对象之间的关联
第四个是作为执行特定的程序代码手段
我可以使用一个指针指向一段程序代码
来执行未来才能够实现的函数
这四个使用场合同学们一定要会用
-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 编程实践
-第十五讲 网络编程--编程实践提交入口