Skip to content

Commit ae6846e

Browse files
committed
Update to Chapter 5, Section 1
1 parent b5658c8 commit ae6846e

18 files changed

+496
-126
lines changed

Structure.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,11 @@
298298

299299
为什么传递一个变量不能满足我们的要求?为什么传递指针能实现我们的目的?当我传入指针时,我提供了什么信息?
300300

301-
#### 指针作为函数返回值
301+
#### 野指针问题
302302

303-
指针在加法运算时的返回值就是指针类型,现在我把它扩展到一般函数来。
303+
#### 指针的运算
304+
305+
指针在加法运算时的返回值就是指针类型,而减法得到的是`std::ptrdiff_t`类型。
304306

305307
> 考虑了一下决定还是把函数指针放精讲篇。
306308

generalized_parts/05_composite_types_and_their_use/01_pointer.tex

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,107 @@ \subsection*{指针参数传递}
114114
}
115115
int main() {
116116
int a {3}, b {4}; //定义a=3,b=4
117-
exchange(3, 4); //交换a和b的值,预期是a变成4,b变成3
118-
cout << a << ' ' << b; //输出a和b的值,检验一下
117+
exchange(a, b); //交换a和b的值,预期是a变成4,b变成3
118+
cout << a << ' ' << b; //输出a和b的值,检验一下结果
119119
return 0;
120120
}
121121
\end{lstlisting}
122122
这个程序的运行结果是\\\noindent\rule{\linewidth}{0.2pt}\texttt{
123123
3 4
124124
}\\\noindent\rule{\linewidth}{0.2pt}
125-
这个代码的逻辑看上去很正确,但是为什么它完全不起作用呢?\par
125+
这个代码的逻辑看上去很正确,但是为什么它完全不起作用呢?\par
126+
回想一下我们之前讲过的内容。每次程序调用一个函数的时候,都会在栈区压入一层堆栈帧。这个堆栈帧中就含有在本次调用过程中创建的变量,如图5.5所示,这时调用栈中发生修改的是 \lstinline@exchange@ 对应内存空间中的 \lstinline@a@ 和 \lstinline@b@,而不是 \lstinline@main@ 函数中的 \lstinline@a@ 和 \lstinline@b@。这两对变量虽然同名,但它们对应的是内存空间中完全不同的区域。\par
127+
\begin{figure}[htbp]
128+
\centering
129+
\includegraphics[width=0.9\textwidth]{../images/generalized_parts/05_parameter_pass_by_value_300.png}
130+
\caption{在调用 \lstinline@exchange@ 函数时,调用栈中发生的变化}
131+
\end{figure}
132+
所以在函数参数传递的过程中,程序并没有把``整个变量''传递给目标函数,而只传递了它的``''。至于被调用的那个函数,它会另外定义一个新的变量(它在内存空间中另一自己的一席之地)来接收传入的值。很多资料会把它称为\textbf{副本(Copy)}。\par
133+
\begin{figure}[htbp]
134+
\centering
135+
\includegraphics[width=0.75\textwidth]{../images/generalized_parts/05_process_of_pass_by_reference_300.png}
136+
\caption{\lstinline@main@ 向 \lstinline@exchange@ 中传入的信息只有值}
137+
\end{figure}
138+
我们会发现,不同函数之间只能共享值,这种现象好像一种屏障——在某些时候,它显得十分安全,我们不会在一个函数中乱改其它函数中的信息;但在另一些时候,它显得非常不灵活,如果我们用别的函数只能读取而不能修改此函数中的值,函数的功能就会受到很大限制——比如我们试图写一个值交换函数而不能。\par
139+
所以我们需要找到一种方法,使我们可以跨过函数作用域的屏障,来修改特定的变量值。我们的答案是使用\textbf{指针传递(Passing by pointer)}。\par
140+
回顾一下前文中讲过的三个基本要点:
141+
\begin{enumerate}
142+
\item \textbf{地址是一个值}。我们可以把它当作和整数值、浮点值、字符值和布尔值等并列的事物来看待。
143+
\item \textbf{只要知道了某个变量的地址,我们就有可能修改内存中相应区域的内容}。我们可以通过取内容运算符来读取或修改其中的内容,效果等同于使用相应的变量名。
144+
\item \textbf{指针是一大类数据类型,它们存储的信息是地址值}。如果一个指针存储了某个变量的值,我们可以说``这个指针指向那个变量''
145+
\end{enumerate}\par
146+
搞清了以上三点之后,我们就不难想到另一种可能性——如果我们把 \lstinline@a@ 和 \lstinline@b@ 的地址作为参数传给 \lstinline@exchange@ 呢?\par
147+
地址是一个值,因此它可以作为实参来传递,没有问题。\par
148+
只要 \lstinline@exchange@ 函数知道了 \lstinline@a@ 和 \lstinline@b@ 的地址,那么它自然具备了修改 \lstinline@a@ 和 \lstinline@b@ 的内容的能力。也没问题。\par
149+
那么 \lstinline@exchange@ 函数要用什么形参来接收地址值呢?当然是用指针,因为只有指针才能存储地址值。\par
150+
于是一切顺理成章,我们的解决方案就给出来了。现在我们就来试着来改一下 \lstinline@exchange@ 函数:
151+
\begin{lstlisting}
152+
void exchange(int *pa, int *pb) { //它接收两个int*参数
153+
int tmp = *pa; //定义一个临时变量tmp,用于存储*pa的值
154+
*pa = *pb; //赋值,现在*pa获得了*pb的值
155+
*pb = tmp; //赋值,现在*pb获得了tmp的值,也就是*pa原来的值
156+
}
157+
int main() {
158+
int a {3}, b {4}; //定义a=3,b=4
159+
exchange(&a, &b); //向exchange函数传递&a和&b的值,也就是地址
160+
cout << a << ' ' << b; //输出a和b的值,检验一下结果
161+
return 0;
162+
}
163+
\end{lstlisting}
164+
最后的运行结果果然不出我们所料,正是\\\noindent\rule{\linewidth}{0.2pt}\texttt{
165+
4 3
166+
}\\\noindent\rule{\linewidth}{0.2pt}
167+
所以这种函数参数指针传递的方式要比我们之前见过的\textbf{值传递(Passing by value)}更有效。但是话又说回来,并不是任何时候用指针传递都比用值传递要更好的。初学者可能很难搞清楚``什么时候要用什么'',其实没关系。你只需要多写一些代码,等到经验丰富之后自然就清楚了。\par
168+
还请读者留意:虽然我们人为地划分出了``值传递''``指针传递''的概念,但它们的本质都是在传递值!只不过后者传递的是``指针值''罢了。\par
169+
\subsection*{野指针问题}
170+
说完了指针在函数传递中的应用,读者应当对指针的强大有了初步认识。指针为我们编写程序带来了极大的灵活性,但其中也潜藏着一些风险。所以现在就让我们返回到一些细节的讨论上来。\par
171+
我曾说过,如果定义一个局部变量而不初始化的话,它将会存储一个不确定的值。使用这种不确定的值可能会导致程序运行的结果不可预测,所以我们需要在使用它之前通过初始化、赋值或输入等等各种方法让它成为一个可控的变量。\par
172+
指针亦如此。如果我们不进行初始化或赋值,让它指向一个安全的位置,那么它可能会指向内存中的任何一个字节,我们把这种指向不确定的指针叫作\textbf{野指针(Wild pointer)}。野指针是很可怕的,一旦我们试图读取或修改对应未知空间中的信息,我们可能会把这个程序乃至其它进程改爆,从而导致严重的后果\footnote{对于个人电脑来说,这种后果可能很轻微——大不了我们重启电脑,这时内存中的一切又焕然一新了。但对于服务器等需要长期运转且不能轻易停工的设备来说,损失可能非常大。}。因此,在使用指针之前,我们也要进行初始化或者赋值\footnote{我们不能通过``输入地址''的方式来改变指针的值,这同样是很危险的。}。\par
173+
我们可以用同类型变量\footnote{实际上,指针也是一种变量。但是人们习惯上把``指针''``变量''视为互不重合的两个概念。本书也沿用这种习惯,通常把指针和变量这两个概念分开。如果需要把指针当作变量的一部分,会特殊说明。}的地址来为指针赋值,我们已经在前面的例子中接触过这种语法了。不同类型数据的地址不能互相赋值,比如我们不能用 \lstinline@float@ 型变量的地址来为 \lstinline@double*@ 型指针赋值。\par
174+
\begin{lstlisting}
175+
float f; //定义float变量f
176+
double *pd; //定义double*指针pd
177+
pd = &f; //试图将f的地址赋值给pd
178+
//error: cannot convert 'float*' to 'double*' in assignment
179+
\end{lstlisting}
180+
这个报错信息的意思是:在赋值时,\lstinline@float*@ 类型(即 \lstinline@&f@)不能隐式类型转换为 \lstinline@double*@ 类型(即 \lstinline@pd@)。这说明指针类型之间不能随便进行隐式类型转换。\par
181+
如果我们定义了一个指针,但暂时还不想让它指向哪个变量,而又害怕空置会产生野指针,那么我们还有一个选择:\lstinline@nullptr@。
182+
\begin{lstlisting}
183+
long long *pll = {nullptr}; //定义long long*型指针并指向空地址nullptr
184+
\end{lstlisting}
185+
空地址(\lstinline@nullptr@)不是一个运算符,它是一个常量地址。\lstinline@nullptr@ 的类型比较特殊,它是 \lstinline@nullptr_t@ 类型的,我们知道这个也没什么用。但这个类型的特点在于,它可以隐式类型转换为任何一个指针\footnote{乃至高阶指针,见后面的章节。}类型。因此,\lstinline@nullptr@ 可以为各种指针进行初始化。\par
186+
\lstinline@nullptr@ 是受保护的。我们对它的内容进行的读取和修改都没有任何意义,也不会造成任何风险。所以当我们不需要这个指针指向什么时,最好让它指向空地址 \lstinline@nullptr@。我们在动态内存分配章节中就会讲到它的一种应用,那就是在动态内存回收之后,为防野指针出现,就让这个指针指向空地址。\par
187+
\subsection*{指针的运算}
188+
接下来讲解一下指针的运算。虽然指针的值很像整型数据,但是它的加减法规则与整型数据截然不同。\par
189+
两个指针的加法是没有意义的,因为我们根本不能确定加完了之后得到的地址是指向什么的(这不就是野指针吗)。\par
190+
而指针与整型的加/减法是有意义的,尤其是在数组当中。一个指针与整数相加/减,它的返回值是和原来指针相同的类型(比如说,\lstinline@int*@ 与 \lstinline@short@ 相加/减,返回值为 \lstinline@int*@)。\par
191+
\begin{lstlisting}
192+
float f, *pf {&f}; //这里不需要初始化f,因为只需用到其地址,而不需要其值
193+
cout << pf << endl << pf - 1 << endl; //输出pf和pf+1的值,它们都是地址值
194+
long double ld; //同理,只是改成了long double型
195+
cout << &ld << endl << &ld + 1; //直接输出&ld,效果相同
196+
\end{lstlisting}
197+
运行结果是这样的\footnote{再次提醒读者,实际输出的内存地址因机而异,我们应当关注的是几个输出地址之间的相互关系。}:\\\noindent\rule{\linewidth}{0.2pt}\texttt{
198+
0x7ffc4bf3837c\\
199+
0x7ffc4bf38378\\
200+
0x7ffc4bf38380\\
201+
0x7ffc4bf38390
202+
}\\\noindent\rule{\linewidth}{0.2pt}
203+
输出内容是很长的十六进制形式,我们来分析下。\par
204+
首先输出的两个是 \lstinline@pf@ 和 \lstinline@pf-1@ 的值。从数值上看,前者是 \lstinline@...37c@,而后者是 \lstinline@...378@,后者虽然是 \lstinline@pf-1@,但地址却减小了4个字节。\par
205+
接下来输出的两个是 \lstinline@&ld@ 和 \lstinline@&ld+1@ 的值。从数值上看,前者是 \lstinline@...380@,而后者是 \lstinline@...390@,后者虽然是 \lstinline@&ld+1@,但地址却增加了16个字节。\par
206+
之所以会出现这种情况,关键就在于不同类型数据在内存中占用的字节数目不同。一般说来,\lstinline@float@ 型占据4字节内存空间,而 \lstinline@long double@ 型占据16字节内存空间。\par
207+
指针和整型的加减法并不是单纯地移动多少个字节,而是移动了多少个数据(可以理解为,偏移量乘以单个数据占用的字节数)。这样设计是有它的道理的,等我们讲到数组,读者就会很容易理解了。\par
208+
两个同类型指针可以相减,其返回值是一个特殊的类型,叫作 \lstinline@ptrdiff_t@。不过我们也无需纠结这种细节,因为 \lstinline@ptrdiff_t@ 可以隐式类型转换为整型,所以我们可以直接把它当作整型来用,比如输出。
209+
\begin{lstlisting}
210+
int a, b, c;
211+
cout << &a << endl << &b << endl << &c << endl;
212+
cout << &a - &b << ' ' << &c - &a << endl;
213+
\end{lstlisting}
214+
运行结果是这样的:\\\noindent\rule{\linewidth}{0.2pt}\texttt{
215+
0x7fff604ad654\\
216+
0x7fff604ad658\\
217+
0x7fff604ad65c\\
218+
-1 2
219+
}\\\noindent\rule{\linewidth}{0.2pt}
220+
我们可以看到,这里的 \lstinline@a@, \lstinline@b@, \lstinline@c@ 地址值依次递增4,而 \lstinline@&a-&b@ 得到 \lstinline@-1@ 而非 \lstinline@-4@,这说明指针的减法也是在计算数据偏移量,而不是字节数偏移量。\par
-43 Bytes
Loading
85 KB
Loading
58.7 KB
Loading
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<mxfile host="Electron" modified="2023-12-30T09:52:10.264Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.16 Chrome/120.0.6099.109 Electron/28.1.0 Safari/537.36" etag="1yuJIPpVYFAQDJmhUH4Z" version="22.1.16" type="device">
2+
<diagram name="Page-1" id="IQq1hr2LD-ZvsHhJWaoq">
3+
<mxGraphModel dx="977" dy="671" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
4+
<root>
5+
<mxCell id="0" />
6+
<mxCell id="1" parent="0" />
7+
<mxCell id="st1XkFkciFkP8LnrTJdS-1" value="调用栈&lt;br&gt;Call Stack" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;align=center;verticalAlign=middle;" vertex="1" parent="1">
8+
<mxGeometry x="200" y="300" width="120" height="280" as="geometry" />
9+
</mxCell>
10+
<mxCell id="st1XkFkciFkP8LnrTJdS-2" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="st1XkFkciFkP8LnrTJdS-4" target="st1XkFkciFkP8LnrTJdS-1">
11+
<mxGeometry relative="1" as="geometry">
12+
<mxPoint x="170" y="445" as="targetPoint" />
13+
</mxGeometry>
14+
</mxCell>
15+
<mxCell id="st1XkFkciFkP8LnrTJdS-3" value="main调用&lt;br&gt;exchange(3,4)&lt;br&gt;压入堆栈帧" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="st1XkFkciFkP8LnrTJdS-2">
16+
<mxGeometry x="-0.0143" relative="1" as="geometry">
17+
<mxPoint y="25" as="offset" />
18+
</mxGeometry>
19+
</mxCell>
20+
<mxCell id="st1XkFkciFkP8LnrTJdS-4" value="调用栈&lt;br&gt;Call Stack" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;align=center;verticalAlign=middle;" vertex="1" parent="1">
21+
<mxGeometry y="300" width="120" height="280" as="geometry" />
22+
</mxCell>
23+
<mxCell id="st1XkFkciFkP8LnrTJdS-5" value="&lt;font face=&quot;consolas&quot;&gt;main()&lt;br&gt;a:3&lt;br&gt;b:4&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" vertex="1" parent="1">
24+
<mxGeometry y="520" width="120" height="60" as="geometry" />
25+
</mxCell>
26+
<mxCell id="st1XkFkciFkP8LnrTJdS-6" value="&lt;font face=&quot;consolas&quot;&gt;main()&lt;br&gt;a:3&lt;br&gt;b:4&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" vertex="1" parent="1">
27+
<mxGeometry x="200" y="520" width="120" height="60" as="geometry" />
28+
</mxCell>
29+
<mxCell id="st1XkFkciFkP8LnrTJdS-7" value="&lt;font face=&quot;consolas&quot;&gt;exchange(3,4)&lt;br&gt;a:3&lt;br&gt;b:4&lt;br&gt;tmp:3&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" vertex="1" parent="1">
30+
<mxGeometry x="200" y="460" width="120" height="60" as="geometry" />
31+
</mxCell>
32+
<mxCell id="st1XkFkciFkP8LnrTJdS-8" value="调用栈&lt;br&gt;Call Stack" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;align=center;verticalAlign=middle;" vertex="1" parent="1">
33+
<mxGeometry x="400" y="300" width="120" height="280" as="geometry" />
34+
</mxCell>
35+
<mxCell id="st1XkFkciFkP8LnrTJdS-9" value="&lt;font face=&quot;consolas&quot;&gt;main()&lt;br&gt;a:3&lt;br&gt;b:4&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" vertex="1" parent="1">
36+
<mxGeometry x="400" y="520" width="120" height="60" as="geometry" />
37+
</mxCell>
38+
<mxCell id="st1XkFkciFkP8LnrTJdS-10" value="&lt;font face=&quot;consolas&quot;&gt;exchange(3,4)&lt;br&gt;a:4&lt;br&gt;b:3&lt;br&gt;tmp:3&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" vertex="1" parent="1">
39+
<mxGeometry x="400" y="460" width="120" height="60" as="geometry" />
40+
</mxCell>
41+
<mxCell id="st1XkFkciFkP8LnrTJdS-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="st1XkFkciFkP8LnrTJdS-1" target="st1XkFkciFkP8LnrTJdS-8">
42+
<mxGeometry relative="1" as="geometry">
43+
<mxPoint x="400" y="440" as="targetPoint" />
44+
<mxPoint x="320" y="440" as="sourcePoint" />
45+
</mxGeometry>
46+
</mxCell>
47+
<mxCell id="st1XkFkciFkP8LnrTJdS-12" value="在&lt;br&gt;exchange(3,4)&lt;br&gt;中交换a和b" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="st1XkFkciFkP8LnrTJdS-11">
48+
<mxGeometry x="-0.0143" relative="1" as="geometry">
49+
<mxPoint y="25" as="offset" />
50+
</mxGeometry>
51+
</mxCell>
52+
</root>
53+
</mxGraphModel>
54+
</diagram>
55+
</mxfile>

0 commit comments

Comments
 (0)