Snowming04's Blog
一颗红❤
Toggle navigation
Snowming04's Blog
主页
Cobalt Strike
Accelerated C++
区块链安全
友链
关于我
常用工具
代码积累
归档
标签
【Accelerated C++】课时3:使用批量数据
? C++ ?
2020-03-22 03:39:55
319
0
0
snowming
? C++ ?
# 0x01 一个程序 本章讲述处理批量数据的方法,如何把未知数目的字符放进一个对象(字符串)中。  # 0x02 程序解析 **<u>预处理器</u>** 首先为程序要用到的库工具编写 #include 指令和 using 声明。 此程序中有两个之前没用过的: - `<iomanip>` - `<ios>` 头文件`<iomanip>`定义了控制器`setprecision`,这个控制器可以让我们指明输出所包含的有效位数。 头文件`<ios>`定义了一个类型`streamsize`,输入/输出库就是用这个类型来表示长度的。 >常见形式:<br> endl(标准输出)<< 变量/表达式 << 控制器 >- endl 也是一个控制器(换行),在头文件`<iostream>`中。 **<u>setprecision</u>** `setprecision` 也是一个控制器。这个控制器为流的后继输出设置了一个特定的有效位数,这样它就可以对流进行控制了。编写了 `setprecision(3)` 后,我们就可以让系统环境输出具有三位有效位的成绩了,输出的形式通常是(十进制)小数点前有2位有效数,小数点后则有1位。 **<u>重置精度</u>** 有两种方法重置精度。 方法1:使用`setprecision`控制器 ``` streamsize prec = cout.precision(); cout << "Your final grade is "<<setprecision(3) << 0.2*midterm + 0.4*finla + 0.4*sum/count << setprecision(prec) << endl; ``` 方法2:使用`precision`函数 ``` //把精度设为3,返回原先的精度 streamsize prec = cout.precision(3); cout << "Your final grade is " << 0.2*midterm + 0.4*final+0.4*sum/count << endl; //把精度重新设置为它的原始值 cout.precision(prec); ``` 方法1中,第①行`stream prec = cout.precision();`,调用了cout中的一个名为precision的成员函数。使用了这个函数之后就可以获取流在进行浮点数输出时所使用的精度。 通过使用`setprecision`可以改变出现在cout中的所有后继输出的精度。方法1的第②行`setprecision(3)`把`0.2*midterm + 0.4*finla + 0.4*sum/count`计算结果的精度设置为3,也就是xx.x的格式。然后再通过`setprecision(prec)`把所有后继输出的精度重置为从`prec`那里得到的值。 方法2中,第②行`streamsize prec = cout.precision(3);`通过调用cout中的成员函数`precision()`,给其传入实参3,副作用是把后继浮点数输出的精度设为了3,也就是xx.x的格式。其返回值赋值给prec,也就是默认初始浮点输出精度。 然后第⑥行`cout.precision(prec);`给precision()函数传入实参prec,这样就把之后输出的精度重置为了初始值。 偏向使用第1种方法:使用`setprecision`控制器来重置精度,因为这样的话,可以把设置精度的那一部分程序代码最小化,让代码更短。 - `注意`:streamsize 类型的变量保存初始精度。头文件 `<ios>` 定义了一个类型 streamsize,输入/输出库就是用这个类型来表示长度的。 **<u>istream</u>** 现在来分析这一段程序: ``` while(cin>>x){/*...*/} ``` - `>>`运算符会返回它的左操作数,因此,请求`cin>>x`的值等价于先执行`cin>>x`然后请求cin的值。所以下两段代码等价: ``` if (cin >> x){/*...*/} ``` ``` cin>>x; //把一个值读到x中 if (cin){/*...*/} ``` - 因为cin的类型是`istream`,而`istream`是标准库的一部分。类`istream`提供了一个转换来把cin转换成一个可以在条件中使用的值。因此,用cin来作为条件等价于检测最近一次从cin读数据的尝试是否成功。 # 0x03 升级设计 现在程序在计算家庭作业这一块的分数时候使用的是平均数,要求平均数,程序在读进家庭作业成绩之后就把这个成绩丢掉,这是没问题的,只需要存储数据的个数和读到的数据项的总和。 但是现在想改用中数来表示这一块的成绩,因为这样可以较少受到异常值的干扰。查找中数的简单方法是:把数值按递增(或递减)顺序排列起来,然后找出中间的1到2个数。所以现在就涉及到一个问题:如果换成中数的话我们需要存储每个读进来的数据。 如何实现? 捋一捋需求:为了计算中值,我们需要读取并存储所有的家庭作业成绩,然后对它们进行排序,最后还得取出中间的一个数(或两个)。 所以我们需要一种方法来实现: 1. 存储未知数目的数值→动态 2. 在读取了所有的数值之后对它们进行排序→希望这种数据结构自带排序方法 3. 高效地获取中间的1到2个数值→便于查询并进行返回 # 0x04 向量类型 C++的标准库提供了向量(Vector)类型,这种类型的特点有: - 大小可以根据需要增长以容纳添加的数值(类似于动态数组) - 可以直接索引到每一个独立的数值(快速访问) 所以现在用向量类型来改写程序,把家庭作业成绩存进一个向量中。 原程序: ``` int count = 0 double sum = 0.00; double x; //不变式 //到目前为止,我们已经读到了count个家庭作业成绩,而且sum等于头count个成绩的总和 while (cin >> x) { ++count; sum += x; } ``` 优化程序: ``` double x; vector<double> homework; //不变式:到目前为止,homework包含了所有读到的家庭作业成绩 while (cin >> x) homework.push_back(x); //每次1个地把数值读到x中,直到遇到了文件结束标志或无效输入为止 ``` - 在定义homework的时候,我们使用的数据是`vector<double>`,向量是一个存储数值集合的容器。在一个向量中的所有数值都具有相同的类型,但是不同的向量可以不同类型的对象。无论何时,只要我们定义了一个向量,我们就必须指定向量所保存的数值的类型。从`vector<double> homework;`这个定义可以看出:homework是一个向量,而且这个向量将保存double类型的值。 - 在定义向量类型的时候,我们使用了「模板类」的语言特征。我们可以把一个向量和这个向量所保存的特定类型的对象分隔开来。我们在尖括号内指定了对象的类型。例如:`vector<double>`类型的对象是向量,这些向量保存了double类型的变量;`vector<string>`类型的对象则保存了字符串。 **<u>push_back()成员函数</u>** 和`greeting.size()`一样,我们可以把`push_back()`看作一个成员函数,这个函数被定义成vector类型的一部分,而且我们用这个函数来对homework对象进行操作。我们调用了这个函数并把x传递给它。`push_back`函数所做的是添加一个新的元素到向量的末尾,它会给新元素一个值,这个值就是我们传递给它的那个参数。因此,push_back会把它的参数压进向量的尾部。作为副作用,它会把向量的长度加1。 **<u>把长度存储在局部变量</u>** ``` typedef vector<double>::size_type vec_sz; //vec_sz是一个类型名的别名 vec_sz size = homework.size(); ``` 类型`size_type`是一个unsigned类型,它必须大到可以用来保存向量可以容纳的最大长度。`size()`则返回了一个size_type类型的值,我们用这个值来表示向量中的元素个数。 局部变量size用来保存向量的长度,不同的系统环境使用了不同的类型来表示长度。如果我们希望保持系统环境的独立性,就不能直接编写恰当的类型。由于这个原因,我们应该养成使用库定义的`size_type`来表示容器的长度的良好编程习惯。所以在声明`size`这个局部变量的时候,它的类型就是size_type。 > 划重点:使用库定义的`size_type`表示容器的长度。 `typedef`是一种语言工具。如果定义包含`typedef`,说明所定义的名称是特定类型的一个替代名,而不是这种类型的一个变量。通过typedef定义的名称会具有跟其他所有名称一样的作用域。在这里我们把`vec_sz`定义为了size_type这种类型名的别名,所以说`typedef`不仅可以给函数名设定别名,也可以给类型名、变量名等设定别名。 > 划重点:typedef不仅可以给函数名设定别名,也可以给类型名、变量名等设定别名。 **<u>main函数返回1</u>** ``` if (size == 0) { cout << endl << "You must enter your grades. " "Please try again." << endl; return 1; } ``` 代码逻辑是:检查size是否为0,如果size为0,那么发出错误并终止程序。为此我们返回1来表示失败。系统假定,如果main函数返回0,则程序运行成功。返回其他的任何值都会表示一个系统环境自定义的意义。不过,大部分的系统环境都把所有的非0值看作是失败。 **<u>排序</u>** 计算中值的第一步是对数据进行排序,为此调用了库函数`sort()`。 ``` sort(homework.begin(),homework.end()); ``` sort 函数定义在头文件`<algorithm>`中,它把容器中的数据重新排序成非递减序列。 sort 函数的参数指定了被排序的元素的范围。vector类为这个用法提供了两个成员函数`begin()`和`end()`。begin()指示了向量中的第一个元素,end()指向紧跟在homework的最后一个元素之后的位置。 就这样就完成了排序。 **<u>寻找中间元素</u>** 在完成排序之后: ``` vec_sz mid = size/2; //mid也是typesize类型的,因为size是typesize类型的,它是1/2size double median; median = size % 2 == 0 ? (homework[mid] + homework[mid-1]) / 2 : homework[mid]; ``` 上面的这个条件运算符经常被称作`?:运算符`。 **<u>:?运算符</u>** `:?运算符`和&&、||类似,也是短路取值。运算符首先计算它的最左操作数,以这个计算所得到的结果值为基础,接下来它会计算且只计算其他操作数中的一个。 **<u>索引</u>** 对homework[mid]和homework[mid-1]的引用为我们展示了访问向量元素的一种方法。每个向量中的任意一个元素都有一个被称为`索引`的整数与之相联。例如,homework[mid]是homework中索引为mid的元素。 homework向量的第一个元素是homework[0],最后一个元素则是homework[size-1]。 中值的计算取决于在只知道元素索引的情况下我们对这个元素进行访问的能力。 因为使用了向量,homework向量会根据需要而自动扩容——这样它就可以容纳得下数量在学生可接受范围之内的家庭作业的成绩,我们的程序也不需要考虑如何获得内存来存储所有的这些成绩,因为标准库已经自动为我们做了所有的这些工作了(vector是标准库提供的类型)。 # 0x05 完整的程序  如果`size==0`(homework.size())时候的执行结果:  # 0x06 一些更为深入的观察 **<u>size_type是无符号整数</u>** 像所有的标准库长度类型一样,`vector<double>::size_type`是无符号整数类型。这样的类型是根本不能用来存储负数值的;相反,它们所存储的值以2^n为模(n的大小取决于不同的系统环境)。例如,在程序中我们永远不会检查homework.size()<0是否成立,因为这个不等式总是产生false值。 此外,每当普通整数和无符号整数在表达式中结合在一起时,普通整数就要被转换成无符号整数。因此,诸如`homework.size-100`这样的表达式将会产生无符号的结果,这也意味着结果不能小于0——即使是homework.size()<100。 **<u>对于向量的一些操作</u>**  **<u>其他库工具</u>** 
上一篇:
【Accelerated C++】课时4:组织程序和数据
下一篇:
【C基础】课时13:一个玄学问题的解决
0
赞
319 人读过
新浪微博
微信
腾讯微博
QQ空间
人人网
提交评论
立即登录
, 发表评论.
没有帐号?
立即注册
0
条评论
More...
文档导航
没有帐号? 立即注册