Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 99c2cdf

Browse files
committedJan 16, 2024
Updated to Chapter 8, Section 5; finished "code_in_book" and relative parts
1 parent 5dc06cd commit 99c2cdf

38 files changed

+523
-700
lines changed
 

‎.vscode/settings.json

-3
This file was deleted.

‎.vscode/tasks.json

-28
This file was deleted.

‎README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
我为此规划了一个大致的编章结构,参见[Structure.md](https://github.com/cppHusky/cppHusky-cpp-Tutorial/blob/main/Structure.md)
1010

11-
正在疯狂赶进度,还要补充插图。现阶段争取保持每4天更新一章的速度
11+
本书中的每一份正式的示例代码都已打包并存至本项目中!详见[Code in book](https://github.com/cppHusky/cppHusky-cpp-Tutorial/tree/main/code_in_book)
1212

13-
我正在考虑把书中每一份正式的示例代码打包并存至本项目中。现在尚在研究阶段
13+
正在疯狂赶进度,还要补充插图和代码。现阶段争取保持每4天更新一章的速度
1414

1515
如果发现内容有任何疏漏和错误,欢迎来作补充和提出修改意见!可以直接提交到[Issues](https://github.com/cppHusky/cppHusky-cpp-Tutorial/issues)当中。
16+
17+
>自24年1月6日起,共编缉此项目时长为 :[![wakatime](https://wakatime.com/badge/user/018cddbf-c102-44d2-a0f3-463bcf2eef39/project/018cddc4-4445-4bc3-8a9d-c6c933978a2b.svg)](https://wakatime.com/badge/user/018cddbf-c102-44d2-a0f3-463bcf2eef39/project/018cddc4-4445-4bc3-8a9d-c6c933978a2b)

‎Structure.md

+10-24
Original file line numberDiff line numberDiff line change
@@ -571,41 +571,27 @@
571571

572572
#### `mutable`
573573

574-
### 对象数组、对象指针
575-
576-
#### `string`对象数组
577-
578-
`std::string`为例,介绍下对象数组,强调不同的下标运算符的含义可能是不同的(`strs[0][0]`这样)。
579-
580-
> 多用`typeid`,很好用的。
581-
582-
#### `valarray`与动态内存分配
583-
584-
再尝试一下用`valarray`指针来分配一批对象,理顺`valarray`指针申请的动态内存空间和`valarray`构造函数申请的动态内存空间有什么区别。
585-
586574
### 类型转换函数
587575

588-
#### 隐式类型转换
576+
类型转换可以通过转换构造函数或自定义转换函数实现,看怎么方便怎么用。
589577

590-
简单介绍下隐式类型转换的条件(不会太深入)和简单实例。
578+
#### `explicit`
591579

592-
类型转换可以通过构造函数或`operator type`实现,看怎么方便怎么用
580+
隐式类型转换可能不尽如人意,所以有些时候我们需要用显式类型转换
593581

594-
#### 显式类型转换
582+
介绍下用`explicit`来强制显式类型转换,避免不合适的隐式类型转换带来的问题。
595583

596-
隐式类型转换可能不尽如人意,所以有些时候我们需要用显式类型转换。
584+
### 复合类型与对象
597585

598-
> 本来想到一个绝佳的例子是`cout<<`一个函数指针`pf`,这里必须要用`cout<<(void*)pf`才能正常输出地址,否则`pf`会被自动类型转换成`bool`来输出。但是突然想起来,泛讲篇不讲函数指针。
599-
>
600-
> 其实问题不大,简单讲讲没什么事的。
586+
#### `string`对象数组
601587

602-
#### `explicit`
588+
`std::string`为例,介绍下对象数组,强调不同的下标运算符的含义可能是不同的(`strs[0][0]`这样)。
603589

604-
介绍下用`explicit`来强制显式类型转换,避免不合适的隐式类型转换带来的问题
590+
> 多用`typeid`,很好用的
605591
606-
#### 编程示例:`valarray``vector<int>`
592+
#### `valarray`与动态内存分配
607593

608-
通过一个简单的例子,让读者掌握在`valarray``vector<int>`之间互相转换的方法
594+
再尝试一下用`valarray`指针来分配一批对象,理顺`valarray`指针申请的动态内存空间和`valarray`构造函数申请的动态内存空间有什么区别
609595

610596
### 编程示例:简单的`string`
611597

‎code_in_book/8.1-8.2/Definition.cpp

+26-12
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#include "Header.h"
22
#include <algorithm>
33
valarri::valarri(const std::initializer_list<int>& ilist)
4-
:_cap {std::min(MAX_SIZE, ilist.size())} //_cap的初值要取二者最小值
4+
: _cap {std::min(MAX_SIZE, ilist.size())} //_cap的初值要取二者最小值
55
{
66
std::size_t counter {0}; //长度计数器,用来防止超过MAX_SIZE
77
for (int x : ilist) { //范围for循环,遍历ilist中的每个数
8-
operator[](_size++) = x; //这里可以用operator[](int)成员函数来访问
8+
operator[](_size++) = x; //注意自增是前缀还是后缀,请读者自行分析
99
if (++counter >= MAX_SIZE) //说明达到MAX_SIZE了
1010
break; //直接退出for循环
1111
}
@@ -19,17 +19,31 @@ valarri::valarri(int n, std::size_t size)
1919
_object_number++; //计数器自增
2020
}
2121
valarri::valarri(const valarri &a) //拷贝构造函数应当进行深拷贝,切记!
22-
:_cap{a._cap},
23-
_size{a._size}
22+
: _cap {a._cap},
23+
_size {a._size}
2424
{ //这里不用防_cap超过MAX_SIZE,因为其它部分共同保证了a._cap不会超过MAX_SIZE
25-
for (int i = 0; i < _size; i++)
25+
for (std::size_t i = 0; i < _size; i++)
2626
operator[](i) = a[i]; //拷贝每个值,而不是拷贝地址
2727
_object_number++; //计数器自增
2828
}
2929
valarri::~valarri() {
3030
_object_number--; //计数器自减
3131
delete[] _arr.p(); //回收_arr.p()指向的动态内存空间,注意必须要用delete[]搭配
3232
}
33+
valarri::valarri(const vecint &vec) //vecint到valarri的转换构造函数
34+
: _cap {vec.size()}, //valarri的成员对vec私有成员无访问权限,所以要用其公有成员
35+
_size {vec.size()}
36+
{
37+
for (std::size_t i = 0; i < _size; i++)
38+
operator[](i) = vec[i]; //vecint也有重载下标运算符,并且有常成员函数的重载
39+
_object_number++; //计数器自增
40+
}
41+
valarri::operator vecint()const { //valarri到vecint的自定义转换函数
42+
vecint vec(_size); //调用了vecint的构造函数,直接指定vec的大小
43+
for (std::size_t i = 0; i < _size; i++)
44+
vec[i] = operator[](i); //逐个赋值,不要用push_back成员,反复重分配太浪费
45+
return vec;
46+
}
3347
valarri& valarri::operator=(const valarri &a) { //直接赋值运算符
3448
if (&a == this) //防范自我赋值
3549
return *this;
@@ -44,19 +58,19 @@ valarri& valarri::operator=(const valarri &a) { //直接赋值运算符
4458
}
4559
valarri valarri::operator+(const valarri &a)const {
4660
valarri v(0,std::min(_cap,a._cap)); //用构造函数为v初始化,代码重用
47-
for (int i=0; i < v._size; i++)
61+
for (std::size_t i=0; i < v._size; i++)
4862
v[i] = operator[](i) + a[i];
4963
return v;
5064
}
5165
valarri valarri::operator+(int n)const {
5266
valarri v {*this}; //用拷贝构造函数为v初始化,也是代码重用
53-
for (int i = 0; i < v._size; i++)
67+
for (std::size_t i = 0; i < v._size; i++)
5468
v[i] = operator[](i) + n;
5569
return v;
5670
}
5771
valarri operator+(int n,const valarri &a) { //这是非成员函数,它是valarri的友元
5872
valarri v {a}; //同样是拷贝构造函数
59-
for (int i = 0; i < v._size; i++)
73+
for (std::size_t i = 0; i < v._size; i++)
6074
v[i] = a[i] + n;
6175
return v;
6276
}
@@ -83,27 +97,27 @@ void valarri::resize(std::size_t size, int value) { //改变数据容量
8397
if (size > MAX_SIZE) //防止size超过MAX_SIZE
8498
size = MAX_SIZE;
8599
valarri v(value, size); //注意用圆括号
86-
for (int i = 0; i < std::min(_size, v._size); i++) //注意范围
100+
for (std::size_t i = 0; i < std::min(_size, v._size); i++) //注意范围
87101
v[i] = operator[](i);
88102
swap(v); //将此对象与v交换,这样v会指向旧的内存空间并在析构时销毁
89103
}
90104
int valarri::max()const { //返回值是数组当前的最大值
91105
int maximum {operator[](0)};
92-
for (int i = 1; i < _size; i++)
106+
for (std::size_t i = 1; i < _size; i++) //注意循环的结束条件用_size,下同
93107
if (maximum < operator[](i))
94108
maximum = operator[](i);
95109
return maximum;
96110
}
97111
int valarri::min()const { //返回值是数组当前的最小值
98112
int minimum {operator[](0)};
99-
for (int i = 1; i < _size; i++)
113+
for (std::size_t i = 1; i < _size; i++)
100114
if (minimum > operator[](i))
101115
minimum = operator[](i);
102116
return minimum;
103117
}
104118
int valarri::sum()const { //返回值是当前数组中有效数据的总和
105119
int summation {0}; //先置为0
106-
for (int i = 0; i < _size; i++) //注意循环的结束条件应用_size而不是_cap
120+
for (std::size_t i = 0; i < _size; i++)
107121
summation += operator[](i); //然后把每个数都加到summation上
108122
return summation;
109123
}

‎code_in_book/8.1-8.2/Header.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#pragma once
22
#include <initializer_list>
33
#include <iostream>
4+
#include <vector> //使用vector必备的库
5+
using vecint = std::vector<int>; //别名声明
46
class valarri; //valarri类声明
57
void input_clear(std::istream& = {std::cin}); //input_clear函数声明
68
class valarri { //valarri类定义
@@ -23,15 +25,19 @@ class valarri { //valarri类定义
2325
valarri(int, std::size_t); //将<参数1>重复<参数2>次
2426
valarri(const valarri&); //拷贝构造函数
2527
~valarri(); //析构函数
28+
valarri(const vecint&); //vecint到valarri的转换构造函数
29+
operator vecint()const; //valarri到vecint的自定义转换函数
30+
explicit operator bool()const { return _size; }
31+
//valarri到bool的自定义转换函数,说明此数组中是否有数据,是常成员函数
2632
valarri& operator=(const valarri&); //直接赋值运算符
2733
valarri operator+(const valarri&)const; //可以与数组相加
2834
valarri operator+(int)const; //可以与数相加
2935
friend valarri operator+(int, const valarri&); //可以以数加之,友元
3036
valarri& operator+=(const valarri&); //可以加之以数组
3137
valarri& operator+=(int); //可以加之以数
3238
friend std::istream& operator>>(std::istream&, valarri&); //需iostream库
33-
int& operator[](int i) { return _arr.p()[i]; } //下标,非常成员函数
34-
int operator[](int i)const { return _arr.p()[i]; } //下标,常成员函数
39+
int& operator[](std::size_t i) { return _arr.p()[i]; } //非常成员函数
40+
int operator[](std::size_t i)const { return _arr.p()[i]; } //常成员函数
3541
void swap(valarri&); //完全交换两个valarri对象的成员数据
3642
void resize(std::size_t, int = {0}); //改变数据容量,如果扩大,将补<参数2>
3743
std::size_t size()const { return _size; } //返回当前的存储量大小,常成员函数

‎generalized_parts/01_welcome_to_cpp/01_start_with_a_cpp_program.tex

+1-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
\section{开始一个C++程序}
22
各种编程语言的教程都喜欢用``输出Hello World!''作为第一个编程示例,本书也不例外。\par
33
在C++中,我们可以用如下的代码来输出``Hello World!''
4-
\begin{lstlisting}[caption=\texttt{Hello\_World.cpp},label={lst:HelloWorld}]
5-
// 这是一个简单的C++程序,用于输出Hello World
6-
#include <iostream> //标准输入输出头文件
7-
using namespace std; //使用std命名空间
8-
int main() { //主函数,程序执行始于此
9-
cout << "Hello World!";//输出Hello World
10-
return 0;
11-
}
12-
\end{lstlisting}
4+
\lstinputlisting[caption=\texttt{Hello\_World.cpp},label={lst:HelloWorld}]{../code_in_book/1.1/Hello_World.cpp}\par
135
如果你使用的是集成开发环境,那么只需要用一两个选项或快捷键就可以完成编译、运行步骤,并看到程序的运行结果了。这个快捷键因软件而异,不过总得说来,可以概括成三个过程:
146
\begin{enumerate}
157
\item \textbf{编译(Compile)}:编译器将你写在文件中的源代码\footnote{源代码(Source Code),是一系列人类可读的计算机语言指令,在C++中一般指.cpp文件中的代码。}编译成目标代码\footnote{目标代码(Object Code),是源代码经编译器或汇编器处理后产生的代码,对人类来说可读性很差。机器不能直接执行源代码,只能执行目标代码。}。

‎generalized_parts/01_welcome_to_cpp/03_define_and_use_data.tex

+2-11
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ \subsection*{初始化(Initialization)}
4949
//定义一个long double浮点型数据,并初始化为3.141592653590
5050
\end{lstlisting}
5151
以上初始化语法也叫作\textbf{直接初始化(Direct Initialization)}。\par
52-
初始化的语法不只这一种。在C++11以后的语言标准中,我更推荐使用\textbf{统一初始化(Uniform Initialization)}的方法:
52+
初始化的语法不只这一种。在C++11以后的语言标准中,我更推荐使用\textbf{统一初始化(Uniform Initialization)}\footnote{有关``统一初始化''这个词,实在让人费解。C++标准中没有查到过这个词,cppreference中也没有查到这个词。但这个词却在网络中普遍存在,十分常用。笔者无法在概念问题上耗费太多时间,因此请读者留意,本书很有可能会在这里犯错。}的方法:
5353
\begin{lstlisting}
5454
<类型关键字> <名字> = {值}; //统一初始化语法
5555
<类型关键字> <名字> {值}; //另一种统一初始化语法
@@ -93,15 +93,6 @@ \subsection*{实操:让编译器充当计算器}
9393
cout << (a + b * c) / d; //输出(a+b*c)/d的值
9494
\end{lstlisting}\par
9595
这段代码不能单独存在,现在让我们``借鉴''一下\texttt{Hello\_World.cpp}的代码,把它改成这样:
96-
\begin{lstlisting}[caption=\texttt{calc.cpp}\label{lst:calc1}]
97-
// 这是一个简单的C++程序,用于简易的数值计算
98-
#include <iostream> //标准输入输出头文件
99-
using namespace std; //使用std命名空间
100-
int main(){ //主函数,程序执行始于此
101-
double a {1.5}, b {0.3}, c {0.5}, d {4.5}; //定义并初始化
102-
cout << (a + b * c) / d; //输出(a+b*c)/d的值
103-
return 0;
104-
}
105-
\end{lstlisting}
96+
\lstinputlisting[caption=\texttt{calc.cpp}\label{lst:calc1}]{../code_in_book/1.2/calc.cpp}
10697
最后的输出结果是一个近似值 \lstinline@0.366667@,与实际计算器算出的结果相近。\par
10798
你还可以修改代码中的初始值,或者是把输出的式子改成别的式子,观察一下结果。\par

‎generalized_parts/02_basic_operation_on_data/02_fundamental_types.tex

+1-16
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,7 @@ \subsection*{整型}
88
于是乎,我们只需要通过 \lstinline@sizeof@ 算出它的内存空间大小,再根据它是有符号类型还是无符号类型,就可以判断出它的数据范围是什么了。\par
99
C++还有另一种更简便的方法,那就是直接使用 \lstinline@limits@ 头文件中定义的 \lstinline@numeric_limits@ 来输出某一类型的数据上下限。\lstinline@numeric_limits@ 给出的结果正是本电脑的类型数据范围。\par
1010
以下是实现这个功能的完整示例代码:
11-
\begin{lstlisting}[caption=\texttt{Integer\_Limits.cpp}]
12-
// 这是一个简单的C++程序,用于输出一些整型数值的上/下限值
13-
#include <iostream> //标准输入输出头文件
14-
#include <limits> //标准算术类型属性头文件,numeric_limits定义于此
15-
using namespace std; //使用std命名空间
16-
int main(){ //主函数,程序执行始于此
17-
cout << numeric_limits<short>::lowest(); //输出short类型的下限值
18-
cout << endl; //输出换行,下同
19-
cout << numeric_limits<short>::max(); //输出short类型的上限值
20-
cout << endl;
21-
cout << numeric_limits<unsigned>::lowest(); //输出unsigned类型的下限值
22-
cout << endl;
23-
cout << numeric_limits<unsigned>::max(); //输出unsigned类型的上限值
24-
return 0;
25-
}
26-
\end{lstlisting}
11+
\lstinputlisting[caption=\texttt{Integer\_Limits.cpp}]{../code_in_book/2.1/Integer_Limits.cpp}
2712
在Coliru上,该程序代码的运行结果如下:\\\noindent\rule{\textwidth}{.2pt}\texttt{
2813
-32768\\
2914
32767\\

‎generalized_parts/03_control_flow/01_introduction_to_structure_process_and_order.tex

+1-14
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,7 @@ \subsubsection*{输入}
6969
\end{figure}
7070
值得注意的是回车键。在我们按下回车键之前,我们输入的内容并未实际发送给计算机,我们可以修改这些内容;而一旦按下了回车键,本行的输入内容将会发送给程序\footnote{这才是真正意义上完成了一次输入。},我们也没机会再修改了。\par
7171
那么当我们按下回车键之后,计算机将会接收到什么输入内容呢?我们可以用这个程序来检测:
72-
\begin{lstlisting}[caption=\texttt{Input\_to\_ASCII.cpp},label={lst:InputToASCII}]
73-
// 这是一个简单的程序,用来将用户所有的输入转为ASCII码
74-
// 读者无需掌握本代码的全部细节,后续会讲到
75-
#include <iostream> //标准输入输出头文件
76-
using namespace std; //使用命名空间std
77-
int main() { //主函数,程序执行始于此
78-
char c; //定义一个char变量c用以接收输入
79-
while (cin.good()) { //while循环,当cin状态正常时继续循环
80-
c = cin.get(); //通过cin.get()接收输入,并存入c
81-
std::cout << static_cast<int>(c) << std::endl;
82-
//强制类型转换为int,故以ASCII码值形式显示输入内容
83-
}
84-
}
85-
\end{lstlisting}
72+
\lstinputlisting[caption=\texttt{Input\_to\_ASCII.cpp},label={lst:InputToASCII}]{../code_in_book/3.1/Input_to_ASCII.cpp}\par
8673
读者可以自行尝试这个程序。如果你单独按下一个回车键,程序会给出输出 \lstinline@10@。\textbf{这说明,回车键也是一种输入!}当你按下回车键的时候,既有``发送输入内容''的作用,又有``输入一个回车字符''作用。这点我们现在还不必深究,读者只需有一个印象即可。\par
8774
\subsubsection*{输出}
8875
如果说输入是我们在命令行中敲下若干字符的过程的话,那么输入就是程序在命令行中敲下若干字符的过程。\footnote{这当然是不严谨的。一般来说输出流有缓冲区机制,会先把输出内容存入缓冲区,再一次性将缓冲区内容输出,以提高效率。程序输出的逻辑与人工输入并不相同!这里只为方便初学者理解而如是描述。}想象一下,在输出过程中有一个输出光标,程序每输出一个字符,光标就会后移一位;输出换行符时,程序就会换行。\par

‎generalized_parts/03_control_flow/02_choice.tex

+1-17
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,7 @@ \subsection*{\lstinline@if@-\lstinline@else@ 结构}
5252
\caption{奇偶判断程序流程图}
5353
\end{figure}
5454
最后Ctrl+V一下文件包含等等乱七八糟的代码,就是这样:
55-
\begin{lstlisting}[caption=\texttt{odd\_or\_even.cpp},label=lst:oddoreven]
56-
//这段代码可以判断输入的正整数是奇数还是偶数
57-
#include <iostream>
58-
using namespace std;
59-
int main() { //主函数,代码顺序执行
60-
int num; //也可以用unsigned
61-
cin >> num; //输入num变量
62-
const int mod {num % 2}; //num模除2的值;也可以不用const
63-
if (mod == 1) { //选择结构
64-
cout << "奇数";
65-
}
66-
else {
67-
cout << "偶数";
68-
}
69-
return 0;
70-
}
71-
\end{lstlisting}
55+
\lstinputlisting[caption=\texttt{odd\_or\_even.cpp},label=lst:oddoreven]{../code_in_book/3.2/odd_or_even.cpp}
7256
读者可以自行测试一下,例如输入\texttt{1},程序就会给出输出 \texttt{奇数};输入\texttt{2},程序就会给出输出 \texttt{偶数}。\par
7357
如果甲方有什么其它的奇怪要求,比如``如果是偶数就输出,如果是奇数就不输出'',这时我们就只需要为偶数部分写一个 \lstinline@if@ 块即可,没有必要再写 \lstinline@else@ 块,代码可以这样写:
7458
\begin{lstlisting}

0 commit comments

Comments
 (0)
Please sign in to comment.