当前课程知识点:基于Linux的C++ > 第七讲 指针与引用 > 7.3 指针与函数 > LinuxCPP0703
我们现在就来看指针和函数的关系
一个是数据交换函数的例子
第二个就是常量指针和指针常量的
第三个如果一个函数
它要返回一个指针 我怎么办
我们先来看数据互换函数
编程互换两个整型数据对象的值
现在我来使用函数来实现它
Swap这个函数使用两个指针形式的
形式参数 int *x、int *y
一个函数调用Swap( &m, &n )
因为我要传的是两个整数的地址
所以我调用Swap这个函数的时候
应该传&m、&n
我这个程序呢
前面打了一个cout 后面也打一个cout
是要把这个m和n的值给打印出来
然后来看Swap这个函数的调用的前后
m和n值有没有发生变化
这里面使用了个很特殊的技巧
#ifndef NDEBUG、#endif
把cout语句给封装起来了
#ifndef NDEBUG、#endif
这个东西叫什么呢
它是一个条件编译指令
它作为一个宏测试 #ifndef
意思就是如果没有定义这个宏NDEBUG
那么它就会编译这段代码
程序运行的时候就会执行这段代码
#endif就结束这个条件判断
所以如果你要想编译这段代码
就把段代码写在这里
真实编程的时候
如果后来你又不想要cout代码了
不用删除
只需要在整个程序代码的前边
写上一句#define NDEBUG
定义一下这个宏
如果没有定义 它就会测试失败
一旦测试失败 这个分支就不做了
它有什么好处
好处就体现在:当这个程序代码中
插入了很多条类似的cout语句
目的就是为了测试
显示一下中间输出结果对不对
就给你观察用的 调试用的
这个程序正式发布了 没问题了
这些中间结果你都不应该输出
不应该输出 你不需要删除
一个一个去找 哪个地方该输出
哪个地方不该输出
如果代码量很大 很难找的
有的时候没准就搞错的
所以很简单
如果你在写程序的时候一开始就决定
这些代码就临时输出看一看
调试的时候看一看对不对
一旦调试完毕都不应该出现
那么就应该把这样的代码
封装在#ifndef NDEBUG、#endif指令中就行了
然后这些指令都不想要了
你就前面直接写上#define NDEBUG
它就自动地不编译了
这个为我们省很多事
我们来看这段代码
Swap这个函数带了int *x,int *y
这样的两个形式参数
我们会使用带引领操作符的
三步互换操作
完成x、y这两个指针
所指向目标数据对象值的互换
在这个三步互换前后
我依然用cout输出它的x、y所指向的
目标数据对象的值
也依然把这两条cout语句
封装在#ifndef NDEBUG、#endif之间
这个就是标准的Swap函数
在这里要保证x和y这两个指针
是合法有效的
所以前面我要有一个if测试
和我们早期讲函数的时候
整数互换那个例子不一样
看这个函数调用的栈框架
在main函数里边有两个量m和n
m是10,n是20
我们假设放在内存条的某个地方——
底下这一半是内存条
m是放在一个地方 n是放在一个地方
内存条里m和n是存在的
它就有一个数据值
上面是栈框架 它表示m、n是属于main
接下来我调用Swap这个函数
它里边其实就俩参数
x和y,x是&m,y是&n
当这个Swap函数在运行的过程中
x和y也依然会放在内存条的某个地方
假设是在这个位置
Swap这个函数
实际上是覆盖了这个main函数的栈框架了
所以在Swap函数执行的过程当中
你是看不见main函数的数据的
实际上是不能访问m和n的值的
也就是说 你不能在Swap函数里边
使用main函数的m 也不能使用main函数的n
这两个量你是用不了的
并不意味着它真地不存在
只是我们没有办法
通过名字m和n来访问它们
现在我们有指针
我用*x、*y就可以访问
*x就是m,*y就是n
所以在Swap函数里边
凡是对*x、*y的操作
都会自动地反映到m和n里面去
Swap函数中的x和y这两个参数
能够把结果带出去
一旦你完成这个三步互换
m和n的值就会互换
你真互换m和n的值吗 没有
从整个程序设计的角度上看——
你从源代码上看
它实际上互换*x和*y的值
我们没有直接操作m和n
是用x和y间接操作的m和n
但是因为*x就代表m,*y就代表n
所以我们在Swap函数里边
对*x、*y所做的任何改变
都能够立即地反映到m和n里边去
而m和n从属于main函数
这就意味着Swap这个函数
通过使用两个指针作为函数的形式参数
能够将结果带出去
这就是它(指针作为函数参数)的第一个原因
不仅如此 参数传递效率还会高一些
对于整数来讲 当然不会高
但是有一些特定的量
它的参数传递效率就会高了
一个结构体的尺寸往往会很大
你要传结构体 那么结构体得整体赋值
你如果传指向结构体的指针
那么它只需要赋结构体的地址就行了
结构体的地址有多大呢
我们32位的计算机、32位编译器
它的尺寸是固定的 4个字节
所以当这个数据结构很大的时候
我们传递它的地址 而不是传递它的值
会让它的参数传递效率提高很多
接下来这个技术细节
就是常量指针或指针常量
如果有这样一种情况
指针指向一个常量 会发生什么事情
这样一个指针指向的是一个固定的常量
它的值是不可以发生改变的
你不可以通过引领操作符
来修改目标数据对象的值
int n初始化为10
cons tint *p初始化为&n
看这个示例一
你按照这样一个方式来定义指针变量p
指针将指向const int
不能通过*p去修改它的内容
想通过*p赋值为20这种方式
想把n的值改成20
这个方式是做不到的
因为它指向一个常量
所以它不可以被赋值
哪怕n值本身是一个整型量
而不是一个常量
你也不能通过*p修改n的值
这就是常量指针告诉我们的事情
它最典型的使用场合
作为函数的参数
来表示函数内部不会通过引领操作符
修改目标数据对象的值
有一个函数 名字叫PrintObject
完成特定对象的打印
输出目标数据对象的值和它的内容
不会改变它的
这个时候形式参数写const int *p
不应该写int *p
只写int *p就可能会以为函数
在内部通过*p修改目标数据对象的值
和我们函数本身的意义是不符的
PrintObject打印就行了 不会修改的
你就应该在写函数原型的时候
把不通过引领操作符
修改目标数据对象值的限定
明确地表示出来
这就是指向常量指针最关键的用法
限定了指针p
只能作为函数输入集的一部分
不能作为函数输出集的一个部分
这是第一种情况 常量指针
第二种情况 指针常量
指针指向的位置不可变化
指针只能指向固定一个地方
你必须按照这个方向来定义
int * const p要把const写在p的前面
“*”的后面
这个时候必须把它初始化成&n
n的地址传给它
因为p是一个常量 不可以被赋值
所以你定义它的瞬间必须初始化
刚才那个地方 你不初始化是可以的
然后再赋值
把p赋值为&n是可以的
这里因为p是一个常量
是不可以被赋值的
所以必须被初始化
它初始化成一个整数的地址
也就是说 p只能指向n
绝不可以指向其他的量
但是可以通过*p去修改n的值
*p本身是int
刚才*p是什么 const int
哪怕它指向的那个量本身是int
你也不能改 因为*p是const int
而这个*p是int 你是可以改的
第三种情况
如果指针只能指向一个固定的地方
同时那个地方里面的内容必须是固定的
这就叫常量指针常量
精确地说法就是指向常量的指针常量
这就意味着不仅指针量本身 还有目标量
两者都是双重的只读属性
有一个const int n初始化成10
此后定义const int * const p
初始化成n的基地址
就按照这种方式来定义
这就意味着 p不可以被赋值
*p也不可以被赋值
int和“*”这两个标记
前后 中间正好有三个空位
这里边有俩const
你写const int * const p是对的
这就是常量指针常量
你写int const * const也是对的
你就是不能写const int const * p
那是不对的
为啥 因为const这个关键字
实际上是左结合的
它作用在它的左边的标记的上面
你这么写 这个const作用在这个“*”上
你这么写 这个const作用在哪呢
作用在右边int上
你刚才不是说了 这不是左结合的吗
左边有啥 左边什么都没有的
所以它就只能作用它右边的int上面
它不作用在那个“*”上
它也不是作用在int *上
它只作用在那个int上面
表示那个整数常量
实际上 如果你真正地严格按照
const左结合的定义
const int应该写int const
按照这个写法才是对的
所以第一个const
写在int前和int后都是对的
第二个const一定要写在“*”的后边
指针量的名称的前边
最典型的使用场合作为函数的参数
虽然在C和C++编程里边
大部分情况下边
我们不需要写得这么复杂
只需要写一个const int *p就可以了
就表示不能通过*p
去操纵目标数据对象的值
第三个 指针与函数的返回值
指针是可以作为函数返回值的
它会带回来一个目标数据对象的地址
但是指针作为函数返回值的时候
它不能返回函数内部定义的
局部变量的地址
只能返回某个全局量的地址
或者作为函数的参数传给函数的指针
-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 编程实践
-第十五讲 网络编程--编程实践提交入口