Skip to content

Commit c0843bc

Browse files
committed
Reviewed Chapter 5
1 parent 5cab7d2 commit c0843bc

23 files changed

+519
-504
lines changed

.vscode/Test.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#include <iostream>
2-
#include <limits>
3-
#include <cmath>
4-
int main() {
5-
std::cout << std::sqrt(3);
2+
#include <type_traits>
3+
int main(){
4+
using namespace std;
5+
constexpr static const char *str {"cppHusky"};
6+
cout << (void*)str << endl;
7+
cout << &str; //这两个输出结果可是不一样的
68
return 0;
79
}

.vscode/Test.exe

-3.8 KB
Binary file not shown.
File renamed without changes.

generalized_parts/05_composite_types_and_their_use/01_pointer.tex

Lines changed: 16 additions & 17 deletions
Large diffs are not rendered by default.

generalized_parts/05_composite_types_and_their_use/02_constant_pointer_and_pointer_to_constant.tex

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
\section{``常量指针''``指向常量的指针''}
22
我们在前面讲基本数据类型时,曾经用 \lstinline@const@ 限定符来标记一个变量,使之成为常量;或用 \lstinline@constexpr@ 限定符来标记一个变量,使之成为常量表达式。\par
33
既然指针也是``变量''的一种,那么我们能否用 \lstinline@const@ 或 \lstinline@constexpr@ 来把它们变成常量或常量表达式呢?\par
4-
先说结论:我们可以使用 \lstinline@const@,将指针限定为``常量指针''或者``指向常量的指针''乃至``指向常量的常量指针''但是我们不能定义成 \lstinline@constexpr@,因为编译器是不可能未卜先知,知道某个变量在运行时的地址\par
4+
先说结论:我们可以使用 \lstinline@const@,将指针限定为``常量指针''或者``指向常量的指针''乃至``指向常量的常量指针''我们也的确可以使用 \lstinline@constexpr@,但是这种操作的限制很大,意义也很有限,所以我们就不讲了\par
55
那么什么是常量指针,什么是指向常量的指针呢?本节就来介绍这两个概念。\par
66
当我们拿到一个没有任何限制的指针时,我们可以对它的指向进行修改——也就是修改这个指针所存储的地址值;我们还可以通过取内容运算符 \lstinline@*@ 来对它的内容进行修改——也就相当于修改对应变量的值,而修改之后这个指针依然指向原来的位置。如图5.8所示。\par
77
\begin{figure}[htbp]
@@ -13,18 +13,18 @@ \section{``常量指针''与``指向常量的指针''}
1313
\begin{itemize}
1414
\item \textbf{常量指针(Constant pointer)}限制了指针改变指向的能力。这种指针一经初始化,就不能改变指向。但是这不影响我们可以修改它的内容。
1515
\item \textbf{指向常量的指针(Pointer to constant,简称指针常量\footnote{笔者十分不推荐使用``指针常量''这个名字!它极易与常量指针混淆。})}限制了指针改变内容的能力。我们不能用这种指针来改变内容,但是它可以改变指向。虽然名为``指向常量的指针'',但它也完全可以指向变量。如果它指向变量,那么我们可以用变量名来修改内容;但是不能用这个指针来修改内容。
16-
\end{itemize}
16+
\end{itemize}\par
1717
我们还可以用更符号化的表述来阐释它们之间的关系:\par
18-
如果 \lstinline@pd@ 是一个常量指针,那么 \lstinline@pd@ 不能改变;但 \lstinline@*pd@ 是有可能改变的\par
19-
如果 \lstinline@pd@ 是一个指向常量的指针,那么 \lstinline@*pd@ 不能改变;但 \lstinline@pd@ 是有可能改变的\par
18+
如果 \lstinline@pd@ 是一个常量指针,那么 \lstinline@pd@ 不能改变;但 \lstinline@*pd@ 是可以改变的\par
19+
如果 \lstinline@pd@ 是一个指向常量的指针,那么 \lstinline@*pd@ 不能改变;但 \lstinline@pd@ 是可以改变的\par
2020
当你理顺了它们的区别之后,我们就来讲讲怎么用 \lstinline@const@ 限定符来把指针限制成常量指针或指向常量的指针。这是定义的语法:
2121
\begin{lstlisting}
2222
const int *ptc1; //定义一个指向常量的指针。它可以改变指向,所以无需初始化
2323
int const *ptc2 {nullptr}; //int与const的位置可互换,也是定义指向常量的指针
2424
int* const cp {nullptr}; //将const置于*之后,定义一个常量指针,必须初始化
2525
\end{lstlisting}
2626
怎么理解这个定义语法,并把这三种看上去非常相像的语法区分开呢?我们可以这样想:\par
27-
\lstinline@const@ 会对它所标记之物作出限制,使其不能改变。如果 \lstinline@const@ 限制的是 \lstinline@p@,那么 \lstinline@p@ 就不可改变,但 \lstinline@*p@ 可以改变——这正是常量指针的特性;如果 \lstinline@const@ 限制的是 \lstinline@*p@,那么 \lstinline@*p@ 就不可改变,但 \lstinline@p@ 可以改变——这正是指向常量指针的特性\par
27+
\lstinline@const@ 会对它所标记之物作出限制,使其不能改变。如果 \lstinline@const@ 限制的是 \lstinline@p@,那么 \lstinline@p@ 就不可改变,但 \lstinline@*p@ 可以改变——这正是常量指针的特性;如果 \lstinline@const@ 限制的是 \lstinline@*p@,那么 \lstinline@*p@ 就不可改变,但 \lstinline@p@ 可以改变——这正是指向常量的指针的特性\par
2828
回到我们的定义语法。\lstinline@const int *ptc1@ 这里,我们看,\lstinline@const@ 之后的部分除了 \lstinline@int@(这个不用管)就是 \lstinline@*ptc1@,于是 \lstinline@const@ 直接限定了 \lstinline@*ptc1@,所以 \lstinline@*ptc1@ 不可变,而 \lstinline@ptc1@ 可变,所以它是指向常量的指针;\par
2929
\lstinline@int const *ptc2@ 同理,\lstinline@const@ 直接限定了 \lstinline@*ptc2@,所以 \lstinline@*ptc2@ 不可变,而 \lstinline@ptc2@ 可变,所以它也是指向常量的指针;\par
3030
到了 \lstinline@int* const cp@ 这儿,情况有点不太一样。\lstinline@const@ 直接限定了 \lstinline@cp@,所以 \lstinline@cp@ 不可变,而 \lstinline@cp*@ 仍然可变,所以它是常量指针。\par
@@ -41,6 +41,6 @@ \section{``常量指针''与``指向常量的指针''}
4141
在这里,第一个 \lstinline@const@ 限制了 \lstinline@*cptc@ 不能改变,而第二个 \lstinline@const@ 限制了 \lstinline@cptc@ 不能改变,所以这个指针一经定义,既不能改变指向,也不能改变内容。\par
4242
指向常量的指针常见于函数参数,因为很多时候我们需要限制函数对参数的修改能力,确保有些信息是只读的,以免在函数中不小心修改与参数有关的信息。举例来说,\lstinline@cstring@ 库中有 \lstinline@strlen@ 函数,它可以求出一个字符串\footnote{我们会在后面讲到字符串。}的长度。这个函数的声明格式是
4343
\begin{lstlisting}
44-
std::size_t strlen( const char* str );
44+
std::size_t strlen(const char *str);
4545
\end{lstlisting}
4646
这很好理解。既然它只是求算字符串的长度,那么当然没有必要让它具备修改目标字符串的能力,因此直接定义成指向常量的指针就没有潜在风险了。\par

generalized_parts/05_composite_types_and_their_use/03_lvalue_reference_and_passing_arguments.tex

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ \subsection*{什么是引用?}
2121
int a {3};
2222
int &ref {a}; //定义一个引用,它充当a的别名
2323
++ref; //更改ref相当于更改a
24-
cout << a; //输出a的值,观察结果
25-
\end{lstlisting}
26-
这个程序的输出结果将会是 \texttt{4}。这说明我们用 \lstinline@ref@ 和用 \lstinline@a@ 的效果是相同的。\par
27-
如果你去输出一下它们的地址就会发现,\lstinline@ref@ 与 \lstinline@a@ 的地址是相同的。
24+
cout << a; //输出4
25+
\end{lstlisting}\par
26+
如果你去输出一下它们的地址,就会发现,\lstinline@ref@ 与 \lstinline@a@ 的地址是相同的。
2827
\begin{lstlisting}
2928
cout << &ref << endl << &a; //输出ref和a的地址,它们应当相同
30-
\end{lstlisting}\par
29+
\end{lstlisting}
30+
地址相同就可以说明,我们使用 \lstinline@a@ 和使用 \lstinline@ref@ 的效果一致\footnote{但是引用和变量仍然是两个类型,详见后文。}。\par
3131
我们还可以定义常量引用,它同样是一种引用,但 \lstinline@const@ 限制了我们通过这个引用来修改变量值的能力。
3232
\begin{lstlisting}
3333
int a {3};
@@ -37,15 +37,15 @@ \subsection*{什么是引用?}
3737
//error: increment of read-only reference 'ref'
3838
\end{lstlisting}
3939
在这种情况下,我们可以用 \lstinline@a@ 来修改相应的值,但不能用 \lstinline@ref@ 来修改相应的值。这说明,``变量''``常量''并不是直接体现在数据中的信息,而只是``变量名''对数据内容是否拥有修改权限的体现。\par
40-
至于本来就定义成常量的数据,如果我们用普通引用来作为它的别名,编译器就不会允许。
40+
至于本来就定义成常量的数据,如果我们用非常量引用来作为它的别名,编译器就不会允许。
4141
\begin{lstlisting}
4242
const double std_gravity {9.80665}; //这是一个常量
4343
double &refgravity {std_gravity}; //错误!
4444
//error: binding reference of type 'double&'
4545
//to 'const double' discards qualifiers
4646
const double &ref_gravity {std_gravity}; //正确
4747
\end{lstlisting}
48-
总而言之,我们只能用常量引用来绑定常量。而我们可以使用普通引用或常量引用来绑定变量。如果使用常量引用来绑定变量,我们可以用变量名来修改变量的值,但不能使用引用\par
48+
总而言之,我们只能用常量引用来绑定常量。而我们可以使用非常量引用或常量引用来绑定变量。如果使用常量引用来绑定变量,我们可以用变量名来修改变量的值,但不能使用这个引用\par
4949
\subsection*{引用参数传递}
5050
还记得我们在讲指针时介绍的 \lstinline@exchange@ 函数吗?如果要使用引用来接收实参,那么我们就相当于在 \lstinline@exchange@ 函数中创造了这个实参的别名,它和 \lstinline@main@ 函数中的变量共享了相同的内存地址。于是我们可以直接用这个``别名''来修改实参的值了。这种方式又被称为\textbf{按引用传递(Passing by reference)}。
5151
\begin{lstlisting}
@@ -80,7 +80,7 @@ \subsection*{引用参数传递}
8080
//...
8181
}
8282
\end{lstlisting}
83-
这样只是为传入的实参创建了一个别名而已,并不需要浪费大量的时间和空间来作复制工作。如果要防止误修改实参,我们也可以把它定义成常量引用,这样就不会出问题啦。\par
83+
这样一来,函数参数传递时只需要为函数传递一个地址值就好\footnote{实际上,引用是通过指针来实现的,只是在C++代码中我们看不到它的本质。如果读者去看汇编代码,想必就能理解这一点。},并不需要浪费大量的时间和空间来作复制工作。如果要防止误修改实参,我们也可以把它定义成常量引用,这样就不会出问题啦。\par
8484
\subsection*{引用作为返回值}
8585
来看一看这个语句,它初看上去可能有点费解:
8686
\begin{lstlisting}
@@ -114,7 +114,7 @@ \subsection*{引用作为返回值}
114114
cout << num;
115115
\end{lstlisting}
116116
编译器报错信息的意思是:``不能把 \lstinline@int&@ 型的左值引用绑定到 \lstinline@int@ 型的右值上。''关于左值右值的问题,我们暂不讨论;但是这个问题,我们需要解决。\par
117-
问题的关键在于,\textbf{函数返回的返回值,其实是一个``副本'',而不是 \lstinline@return@ 所跟的变量本身!}。因此我们是在对着一个不应该取引用的内容\footnote{我们在精讲篇中会更详细地谈讨此类问题。简而言之,不是所有数据都可以取地址的,也不是所有数据都可以被引用的。}按引用传递参数,那当然就会产生问题了。\par
117+
问题的关键在于,\textbf{函数返回的返回值,其实是一个``副本'',而不是 \lstinline@return@ 所跟的变量本身!}。因此我们是在对着一个不应该取引用的内容\footnote{我们在精讲篇中会更详细地谈讨此类问题。简而言之,不是所有数据都可以取地址的,所以也不是所有数据都可以被引用的。}按引用传递参数,那当然就会产生问题了。\par
118118
这和我们在按值传递参数的过程中面临的窘境如出一辙。而解决方法也很相似,就是使用引用来返回值。\par
119119
\begin{lstlisting}
120120
int& mul_ass(int &a, const int &b) {
@@ -138,14 +138,14 @@ \subsection*{引用的类型与 \lstinline@is_same@}
138138
\end{lstlisting}
139139
再看地址,\lstinline@num@ 和 \lstinline@ref@ 的地址也永远相同,它们的内存大小可以用 \lstinline@sizeof@ 求得,这也是相同的。\par
140140
看了这么多,我们发现,引用好像是一个分身,或者是真假美猴王那样的关系,我们根本分辨不清谁是本体,谁是别名。\par
141-
那么,变量与引用的类型一样吗?其实是不一样的。\lstinline@num@ 是 \lstinline@int@ 类型无疑,而 \lstinline@ref@ 和 \lstinline@rref@ 都是 \lstinline@int&@ 类型的。这几个名字看似一模一样,但正主还是原变量,六耳也终究不是孙悟空\footnote{其实从汇编代码中看,C++中的引用全都是通过指针实现的。所以说,在定义引用的过程中,程序上会创建一个我们看不见的指针,包括传引用参数等操作,本质上都是传指针。关于这里的细节,我就不多谈了。}\par
142-
那么如何检验类型呢?我们可以用 \lstinline@type_traits@ 库的 \lstinline@is_same@ 来检验之。它是一个类模板,可以接收两个模板参数,并检验它们是否是同一类型。如果相同话,其静态成员 \lstinline@value@ 的值就是 \lstinline@true@;如果不同的话,其静态成员 \lstinline@value@ 的值就是 \lstinline@false@。\footnote{这里出现了很多新概念,比如类模板,模板参数、静态成员等。读者无须知道细节,我们会在后面慢慢道来。}
141+
那么,变量与引用的类型一样吗?其实是不一样的。\lstinline@num@ 是 \lstinline@int@ 类型无疑,而 \lstinline@ref@ 和 \lstinline@rref@ 都是 \lstinline@int&@ 类型的。这几个名字看似一模一样,但正主还是原变量,六耳终究不是孙悟空\footnote{C++中的引用全都是通过指针实现的。在定义引用的过程中,程序中会创建一个我们看不见的指针。传引用参数等操作,本质上都是传指针。}\par
142+
那么如何检验类型呢?我们可以用 \lstinline@type_traits@ 库的 \lstinline@is_same@ 来检验之。它是一个类模板,可以接收两个模板参数,并检验它们是否是同一类型。如果相同的话,其静态成员 \lstinline@value@ 的值就是 \lstinline@true@;如果不同的话,其静态成员 \lstinline@value@ 的值就是 \lstinline@false@。\footnote{这里出现了很多新概念,比如类模板,模板参数、静态成员等。读者无须知道细节,我们会在后面慢慢道来。}
143143
\begin{lstlisting}
144144
//需要包含头文件type_traits
145145
cout << is_same<int, int>::value << endl; //int与int相同,故输出1
146146
cout << is_same<double, long double>::value; //不同,故输出0
147147
\end{lstlisting}
148-
提醒读者,\lstinline@cout@ 输出 \lstinline@bool@ 类型的值时,会默认以整数的形式输出。我们也可以用 \lstinline@cout.setf(ios_base::boolalpha)@ 来让它以布尔值的方式输出\par
148+
提醒读者,\lstinline@cout@ 输出 \lstinline@bool@ 类型的值时,会默认以整数的形式输出。如果想要布尔值的方式输出的话,我们也可以在前面用 \lstinline@cout.setf(ios_base::boolalpha)@ 来设置\par
149149
但是这里有另一个问题:尖括号(Angle brackets)中只能接收类型信息,我们不能直接把一个变量,或者引用,或者指针塞进去。这时我们就要用到 \lstinline@decltype@ 了。\lstinline@decltype@ 是一个编译时操作,它会解释出一个表达式的类型。
150150
\begin{lstlisting}
151151
int num;
@@ -155,15 +155,15 @@ \subsection*{引用的类型与 \lstinline@is_same@}
155155
先来看一下 \lstinline@num@, \lstinline@ref@ 和 \lstinline@rref@ 是不是同一个类型。
156156
\begin{lstlisting}
157157
int num, &ref {num}, &rref {ref};
158-
cout << is_same<decltype(num), decltype(ref)>::value << endl;
159-
cout << is_same<decltype(ref), decltype(rref)>::value;
158+
cout << is_same<decltype(num), decltype(ref)>::value << endl; //0
159+
cout << is_same<decltype(ref), decltype(rref)>::value; //1
160160
\end{lstlisting}
161-
这两句的输出分别是 \lstinline@0@ 和 \lstinline@1@,说明 \lstinline@ref@ 和 \lstinline@rref@ 都是同一个类型的,它们都和 \lstinline@num@ 不是同一个类型的。\par
161+
这说明,\lstinline@ref@ 和 \lstinline@rref@ 都是同一个类型的,它们都和 \lstinline@num@ 不是同一个类型的。\par
162162
接下来我们具体看一下它们三个分别是什么类型。
163163
\begin{lstlisting}
164164
cout << is_same<decltype(num), int>::value << endl;
165165
cout << is_same<decltype(ref), int&>::value << endl;
166166
cout << is_same<decltype(rref), int&>::value << endl;
167167
\end{lstlisting}
168168
这三个输出的结果全部为 \lstinline@1@,说明 \lstinline@num@ 是 \lstinline@int@ 类型的,而 \lstinline@ref@ 和 \lstinline@rref@ 是 \lstinline@int&@ 类型 的,它们并不相同。\par
169-
\lstinline@is_same@ 是一个很实用的类型判断工具,我们在后面也会用到它的\par
169+
\lstinline@is_same@ 是一个很实用的类型判断工具,我们在后面也会用到它\par

0 commit comments

Comments
 (0)