当前课程知识点:C/C++:从基础语法到优化策略 > Final Exam > Final exam > 5.1 Pointers
返回《C/C++:从基础语法到优化策略》慕课在线视频课程列表
大家好这部分我们来讲一下指针
指针是c++语言里面的一个难点也是一个重点
如果你用不好指针 你应该不能说你会c或者c++语言
那么我们先来看这个指针是什么
指针首先他 我们先回到他本身 指针本质上是一个地址
也就是说当我们在存数据的时候 我们有几个要素需要关心
第一个就是我们第一个就是数据存在哪里
第二就是数据的值 这个内容是什么
第三个是数据的类型 这么三个要素 我们要关心
在很多编程语言里面
特别是脚本类的编程语言里面
一般我们只会关心他的值 甚至连类型都不关心
那么在c和c++里面呢
那么这个数据的地址存在哪里 是需要我们去关心的
那么如果我们想要知道数据存在哪里的话 我们怎么办
我们需要一个取地址的操作 用&符号去做
就是说我们可以对一个变量取一个&符号的操作
这样话我们就可以取到地址
那么地址一般采用16进制来表示
我们可以把它打印出来
在这给大家看一个简单的小例子
我们来看这个例子
这个例子呢 我们定义了两个变量
第一个是int类型的 叫donuts
第二个是cups 这里是double类型的
第一个的值是6第二个是4.5
那么我们过去学的就是我们可以很容易的把这个donuts的值打印出来
如果我们要想要看它的地址是多少 那我们可以在前面加一个&符号
然后他的返回的值就是地址了
我们可以把它打印出来
那么我们来看一看 就是第一个变量
第二个变量也是一样的
我们可以把它的值
这是一个 第二个是一个double类型的变量
把它的值打印出来 也可以把它的地址打印出来
所以这个小例子呢
其实很简单 就是把两个变量的内容也就是他的value打印出来
然后以及把它的地址打印出来
那么这个地址其实就是我们所说的指针
我们来看 编译并运行一下
我编译一下编译无错
然后我们可以运行 生成a.out 我们来看
那么毫无疑问的 它的6和4.5这两个值很容易打印出来
那么它的地址我们可以看一看
是不是 是一个16进制的
这样一个整数很大的整数
他们两个之间只有最后一位是不同的 也就是说他们差了八个字节
这个单位是字节
我们再来运行一次
我们可以看一看 就是这两次运行的时候 它的地址实际上是不一样的 它地址是不一样的
所以说 每次程序运行 他的地址他的指针不能保证完全的一样
好 这是这个简单例子
那么指针的类型又有多种 就是说我们前面讲的是
我们过去一般来说一个value一个值
我们去存的时候 我们关心他 其实他就是我们给他起个名字
起一个名字呢 我们看上面这
我们对jumbo 这个地方 这个变量赋一个23
实际上说我们的内存申请一个int类型的内存 然后把这个23放进去
然后给他起个名字 叫jumbo 这是他的名字
那么如果我们想要取地址 那么就是相当于 他是从这个变量jumbo的
这个衍生出来的这个地址 通过取地址操作衍生出来的这个变量
那么这个新的变量叫pe 我们要赋 如果我们
这个pe变量 它的类型是什么 它就是int*
这个就是指针类型 你可以把pe这个变量
看作是一个指针类型的变量
那么这里面呢
如果我们想要通过地址 通过指针去取它的value取它的内容 怎么做呢
我们可以使用新号来去取 怎么取
我们在前面的编程里面 我们就要取23我们直接用jumbo这个变量直接去去参与运算就可以了
如果你采用地址 那你要前面加个*号
也就是说 我们用&是取地址 用*号是取地址里面的内容
不是地址的值 而是地址指向内存的内容是取内容
你可以认为这个*号是
&符号的反向操作 那么他们都可以取到这个值23
同样 如果你要去关心地址本身 地址这个值而不是23这个值
那么你可以直接对变量取& 你也可以直接这个变量p 它本身就是地址他可以打印出来
那么我们来看第二个例子
point 我们这个例子叫
这个例子里面的我们来看一下
首先我们定一个int类型的变量 updates 是6 就是一个点
然后呢 我们又定义了一个指针 叫p_updates
那么这个指针的指向哪里去呢 大家注意 这提前提醒一下
在我们声明了这个p_updates之后
它里面的值是一个任意值 是随机的
所以他会指向任意部分
所以我们一定记得及时给他初始化 及时给他赋一个值
然后我们把updates的这个地址然后赋给p_updates这个变量
这个时候 那么跟前一个例子相比 前一个例子里面是
直接是只有&操作 那么我们在这个例子里面
我们可以直接把updates值这样打印出来
我们也可以采用*加他的地址他的指针也就是去把它的内容取出来
这两种做法都一样
那么下面这个cout 这个打印的就是
我们也可以这样去把updates这个变量的地址取出来
去打印也可以直接把下面这个p_updates去打印出来这两种打印方式 它的值
它的地址应该是一样的 我们可以看一看
我们来看
第一我们打印值的时候
我们采用updates变量或者 * 加指针的这种方式都可以把它的内容取出来
如果我们要打印地址 我们可以看对变量取地址 是这个
直接对这个p 指针类型的打印出来就是说指针类型是什么 指针类型就是存着地址的一个变量
指针这个是一个变量 这个变量里面存的是地址
那么我们把这个地址打印出来
我们可以看到跟前面的地址是一样的
为什么是一样的 显然是因为这有个赋值操作嘛 所以说他们是一样的
好这是第二个例子 就是说我们在这个例子里面 我们学会了
在第一个例子 我们学会了用 & 符号取地址
在这个例子里面 我们学会了用 * 去取地址指向的内容
* 的操作是去取地址指向的内容
那么说到指针 就是说为什么指针很重要
因为C和C++的编程的一个思想就是说要对内存进行管理
内存管理是很重要的 是由程序员进行管理
而不是由自动的内存管理机制来做 所以说这是他的一个哲学
那么内存管理 那就是指针
基本来说 内存管理 我们一说内存管理是指针
指针就是一个内存管理一个非常关键的要素
所以说这个 我们也可以说指针就是这个C和C++的哲学 或者C++的这个思想的一个贯彻
那么跟其他的一些语言 比如说Java Python等等这些语言相比
这种偏向脚本语言或者更高级的语言相比
C和C++ 我们可以直接去操作内存
那么它带来的优势就是我们在做一些数据的处理的时候 它的速度比较快
在Java里面 如果你可以比较一下
如果你做一个数组的元素 像我们上次作业里提到的把两个向量进行相乘 求他的点积
这个操作如果你用Java做或者Python做 那他的效率是一场灾难
速度可以慢的不可忍受 你可以用C++去做 你可以做非常快
所以这个就可以突出它的优势
那么我们来再来说说指针是怎么初始化的
前面我也简单提了一句 就是说我们这样去定义一个指针int* birddog
这个是这个变量的名字叫birddog
那这个时候我们就定义了一个int类型的指针
那你可以这个解读 你可以这样去看
你可以把它看成 * 和birddog 放在一起的话 那你把它看成 这是一个int类型的变量 叫 *birddog
那么你也可以把birddog看成变量
然后把int* 放在一起来看
那么它就是可以看作是一个int类型的指针
那么这个样子的话 就是birddog 就是指向int数据的一个指针
那么在这呢这个int和 * 之间
和 * 和birddog之间这个空格 可加可不加 都没关系
所以说 那么在这个里面 我们就把这个int*
在很多时候 至少我一般是习惯于把int*看成一个组合的 这样一种类型 叫int类型的指针
他定义的这个变量是一个地址
地址指向了一块内存 内存里面存的是int类型的数据
这样去看 同样的 我们也可以定义double类型的指针
float类型的指针 char类型的指针
以及某种结构类型的指针或者某种类class类型的指针
那么在内存里面 我们怎么去解读它
我们来看 假如这是内存
然后 他的地址从上往下是从小到大
比如说开初始地址是1000 1002 1004
也就说这个地方每一行有两个字节这样
实际上计算机里面的内存是连续的 就这样一直连续
它只是表示成二维方式 那么这个时候 我们
定义了一个 比如说 ducks
他的值是12那么他就会存在这里 那他这个ducks是他的一个标记 那么就是这块
同样的我们如果把他的这个地址赋给birddog的话
那么他就相当于把他的这个变量的地址我们到他内存里看
他的地址是1000那把这个1000就赋给birddog这个变量
它是一个变量他需要一些字节去存
那就赋给这个变量的值 也就是说birddog这个变量
这个指针类型的变量 它存的值就是这个地址
谁的地址呢 就他指向这个内容的地址
大家好好想想这个逻辑啊 好好想想这个逻辑
那么这里面就说涉及到指针
其实在编程里面是很危险的 很容易出bug很容易出问题的
第一个非常容易犯的错误就是在这里
比如说在这里我们定义了一个int类型的指针ptr
在这个时候它给ptr变量分配了一个空间
记住我的话说的是给ptr变量分配了一个空间
这个空间是用来存地址的
如果它是32位系统 一般来说是32位 也就四个字节
如果64位系统 它是八个字节就分给他了 但是他指向的
这里面这个这个值 它指向这个地址是一个随机数
那么它会乱指 如果你去这样去赋值的话
那么他指向哪里 他就是赋到哪里去了
那么这个地方可能是
如果他是在你的这个程序的一个堆里面 那么可能没问题
你直接去操作了 他破坏了别的变量别的数据
也可能是指到你的程序的外面去了
那么被操作系统直接杀死 这个是我们非常常见的bug
那么我们来看下面就是刚才我们说的这个示意图
我们一旦第一行 第一行代码
它只是分配这个蓝色的块 就可以给ptr变量分配一块内存
一般来说 四个或者八个字节
然后存地址 然后那个地址是乱指的
开始如果你没初始化 如果你要硬要赋 那就乱了
所以说我们可以把另外一个变量int var的地址赋给他
比如他的地址是0x123的话 那我们就赋给他
那这个时候这个指针就指向了var这块内存
所以说一定要注意初始化的时候
他可能是NULL 这是非常危险的 我们来看看这个例子
一个初始化的例子 在这个例子里面 我们
定义声明了一个ptr指针 int类型的
我们声明完之后直接去打印他
我们就看他的地址是多少 我们再看他指向内容是多少
然后我们去赋个2给他赋值
然后我们来看再把这个内容打印出来
这样做有没有问题 我们直接编译和运行一下
大家看这个程序非常顺利的运行了
那么他的地址是ptr 这个地址我没有初始化 他就随机的指向了这个地址
然后 我就把这个地址这个 它指向这个地址的内容是多少
那这个如果是它取一个整数 有四个字节
那么发现它指向这个地址的内存全是零
就是他每一位都是0 就是非常偶然的全是0
那么我就对他进行上面进行一个赋值2就把这快地址
指向了那四个字节 直接置成2
然后把他取出来 顺利的成功了
所以说从这个运行结果来看
没有任何的错误提示 你编译的时候没有警告 没有错 什么都没有
你运行的时候也没有问题
但实际上这个是一个错误的程序
因为我们叫他叫野指针 他指向一个任意的地方
所以说这是我们编程里面很容易犯的错误 而且这种错误很难debug
因为你把那块野指针指向的内存修改了没人告诉你
然后可能不是什么重要的位置 所以那就那样了
所以说你以为成绩是正常的
但是假如那个位置正好是存着一些关键数据 你下一次可能一分钟之后你再去读
发现数据变了 那这个程序可能会崩掉 但是他离错误的根源是很远的
所以说很难 debug
这是C++程序 debug的困难就在这里
为了避免这种问题建议大家一旦定义指针立马进行初始化
比如说你可以这样去做
NULL 就是0 0就是NULL 就是一样的
我们可以这样去做 可以直接赋个零也可以
都一样的 我们直接去赋成零初始化它
这是所有的C++编程里面所有的变量记得手动初始化 这样的话避免出错
那我们这个时候再去编译一下 发现还是一样的 没有问题 那我再去运行一下呢
我们看这个时候呢 首先 我们去打印这个地址 他说 这个地址是零
就是我们前面的这个点 然后呢我去取这个地址的内容的时候
他就直接导致了segmentation fault 也就是我们的段错误
这样的话好不好 这样的很好
因为一旦你有问题 他提前就把问题爆出来
这样的话有利于你去纠正这个问题
所以说记得指针如果你不能赋一个值
那你就赋0用0去初始化 因为如果是零指针的话
那么你一旦对他操作 他会段错误 然后告诉你这有问题
而不是引起了其他的问题 过了一会儿再去知道 那就很难去查了
所以这个就是指针的危险之处 非常重要
那么还有就指针存的值本身是一个整数 是一个地址
所以说它是一个整数 是一个无符号的整数
所以他本身是一个数 那么他跟整数又有什么区别呢
区别就是如果我们是一个整数类型 我们可以可以对它进行加减乘除
那么指针你可以加 你可以减 但是你不可以乘 你不可以除 都是有约束的
那么 后面我们会讲如何对指针加一
比如说我有个指针是个地址 然后我加个1会怎么样
我们在后面会减个1会怎么样
那么我们还可以有一种操作是这样做的
既然他是个整数 那我又可以把这个整数直接赋一个指针
这样做先把它转一下类型 就这样
然后去做 这样做不会报错是可以的
但是记得 这样做是非常危险的
因为你不清楚 因为这相当于整个内存
不是由这个分配机制来去分配的 而是由你手动指定了
那么会引起各种各样的问题 所以说这样做语法上是对
但是不要这样做 除非你真的知道这样做的原因是什么
基本上我们很少遇到这种情况
几乎不会遇到你直接去赋一个整数给一个指针的情况
好 那么最后一点 那给大家一个思考题
一个思考题
我们这儿定义了三个指针
他们都是指向int数据 指向char数据与指向double数据的指针
他们指向的内容的类型不同
他们本身都是指针 然后我们都初始化为NULL
那么sizeof(ptr1) ptr2 ptr3他们的返回值分别是多少
是不是说sizeof(ptr1)应该返回4 这个返回4 第二个返回1 第三个返回8呢
大家可以去试一下 在这可以首先告诉大家答案不是这样的 他们返回几不一定
它是取决于你的系统 你的环境
大部分现在我们的个人电脑都是64位的CPU以及64位的操作系统
那么在这种环境下 一般来说
那么指针这个值 这个地址是一个64位的地址
也就是八个字节 所以说sizeof这个指针 那么他返回8
第二个 第三个都是一样的
不管你的类型是什么 只要你是指针它就是地址
那就是64位他就会返回8
当然如果你在一些Arm的操作系统Arm的这个平台上
他如果是一个32位的CPU 不是说操作是32位的
那么他的地址应该是四个字节32位
那么这个时候就是说系统不同 那么就返回值是不同的 这个需要特别的注意
好 我们这一部分就介绍到这儿
-Quiz 1
-Quiz 2
-3.3 Relational expressions (> < ==)
-3.5 Branching statements (if else)
-3.8 break and continue statements
-Quiz 3
-4.4 Structures, Unions and Enumerations
-Quiz 4
-5.3 Allocate memory: C++ style
-Quiz 5
-6.3 Recursion and pointer to functions
-Quiz 6
-Quiz 7
-8.1 C/C++ with ARM development board
-Quiz 8
-9.2 Constructors and destructors
-Quiz 9
-10.1 Operators in cv::Mat in OpenCV
-10.4 Automatic conversions and type casts for classes
-Quiz 10
-11.1 Dynamic memory and classes
-11.2 New and improved String class
-11.3 Using pointers to objects
-Quiz 11
-12.2 Static and dynamic binding
-12.3 Access control: protected
-12.4 Inheritance and dynamic memory allocation
-Quiz 12
-13.1 Constructor and assignment
-13.2 Classes with object members
-Quiz 13
-14.1 CNN for image classification
-Quiz 14
-15.3 RTTI and type cast operators
-Quiz 15
-Final exam

