C++语言学习(9):《C++程序设计原理与实践》第四章笔记

news/2024/12/31 0:27:42/

这一章的标题是《计算》,想法是:计算是一个过程,是处理输入得到输出的过程。也有B站网友称之为 IPO 编程:Input, Process, Output. 其中 Process 相当于是广义上的「计算」。

在这里插入图片描述

计算过程的输入

如果认为程序是以计算为目的,那么就需要考虑:「计算」这一过程的输入和输出分别是什么?输入的来源有很多:键盘、鼠标、触屏、传感器、摄像头,etc

在这里插入图片描述

避免孤立的从输入得到输出

输出的途径有很多;但是最有意思的I/O是这两个:

  • 从其他程序输入或输出
  • 从同一个程序的其他部分输入或输出
    在这里插入图片描述

在2000年前后,中小学课本用的是「信息技术」这个关键字来表示电脑课/微机课,所谓信息其实就是输入、输出的统称,甚至说更主要指代输入。

而高中、大学课本里,更多的使用「算法」这个关键字,它其实是一类程序的统称,把关注点从无限的数据,转移到了能用规则描述清楚的、有限文字或公式表述的过程上。

实现计算过程时的注意事项

注意优先级,先正确,再保持简单,最后是保持高效:
在这里插入图片描述
实现保持简单,目的是控制程序的复杂度; 单个函数超过1000行,和分解为10个100行的函数,显然后者更容易维护:
在这里插入图片描述
善于使用高质量的库,而不是完全从头写,既能保证质量,也能减少工作量:
在这里插入图片描述
良好的代码组织结构,是代码能够长期健康存活的关键;没有结构的代码犹如用泥砖盖房子,虽然能用但很凑合,无法盖起5层高楼:
在这里插入图片描述
编程时对编码结构和质量所付出的努力,可以大大简化最令人沮丧的编程工程:调试。原因:好的程序结构,一方面可以减少错误的发生,另一方面能缩短发现和改正错误的时间。
在这里插入图片描述

表达式:变量在等号左边表示对象,在等号右边表示变量的值:
在这里插入图片描述
使用常量,替代硬编码的 magic number,一方面增加了代码的可读性,另一方面增加了代码的可维护性
在这里插入图片描述
不要觉得「硬编码的魔数,含义很直白」,因为读代码的人可能从未接触过你的领域知识细节,比如 299792458,它缺乏语义信息,是「面向信息编程」,是混乱的;要改为 const int light_speed = 299792458 才有意义,才是「面向概念的编程」:
在这里插入图片描述

程序的正确性:需要检查输入的合法性,避免非法输入数据执行计算、得到错误结果,例如如下的 if/else,并没有判断 unit 不为 i 时,可能是c(合法),也可能是c和i之外的字母(非法):
在这里插入图片描述
改进方式是对输入做检查,只支持承诺的输入数据,对于不支持的则明确告知:
在这里插入图片描述

练习 - 货币转美元

在这里插入图片描述

v1

对于这个任务,我最初的代码是:

void convert_money_to_us_dollars()
{float money;std::string type;// en: japan// eu: euro// uk: uk poundstd::cin >> money >> type;float us_dollars = 0.f;if (type == "en"){us_dollars = money * 0.0068f;}else if (type == "eu"){us_dollars = money * 1.1027f;}else if (type == "uk"){us_dollars = money * 1.3126f;}std::cout << money << " " << type << " == " << us_dollars << "\n";
}

存在的问题:

  • 命名:money 需要改为 currency
  • 命名:en 需要改为 jpy(Japan Yen),eu 需要改为 Eur, uk 需要改为 gbp(Great British Pound)
  • 没处理非法输入的类型
  • 硬编码了汇率转换数据
  • 缺少输入提示
  • 缺少单元测试代码

v2


void convert_currency_to_us_dollars()
{float money;std::string type;std::cout << "Please input currency value and type(one of: jpy, eur, gbp)\n";constexpr float usd_per_jpy = 0.0068f; // Japan Yenconstexpr float usd_per_eur = 1.1027f; // Europeconstexpr float usd_per_gbp = 1.3126f; // Great British Poundstd::cin >> money >> type;float us_dollars = 0.f;if (type == "jpy"){us_dollars = money * usd_per_jpy;}else if (type == "eu"){us_dollars = money * usd_per_eur;}else if (type == "uk"){us_dollars = money * usd_per_gbp;}else{std::cout << "error: invalid currency type. valid types: jpy, eur, gbp\n";return;}std::cout << money << " " << type << " == " << us_dollars << " usd\n";
}// Test function
void test_convert_currency_to_us_dollars()
{std::string test_input = "100 jpy\n"; // Change this to test other inputsstd::istringstream iss(test_input);// Redirect cin to use the stringstream inputauto cin_buff = std::cin.rdbuf();  // Save original bufferstd::cin.rdbuf(iss.rdbuf());// Run the function to testconvert_currency_to_us_dollars();// Restore original cinstd::cin.rdbuf(cin_buff);
}

这一版代码,整体有改进,但是仍存在问题:

  • 输入:是通过 cin 给出的,需要改为函数参数; 提示信息应当作为应用程序(测试函数)的输入,而不是核心转换函数的输入
  • 输出:是通过 cout 给出的, 需要改为返回值; 错误输入时的报错信息,可以让应用程序给出

v3

float convert_currency_to_us_dollars(float currency, const std::string& type)
{constexpr float usd_per_jpy = 0.0068f; // Japan Yenconstexpr float usd_per_eur = 1.1027f; // Europeconstexpr float usd_per_gbp = 1.3126f; // Great British Poundif (type == "jpy"){return currency * usd_per_jpy;}else if (type == "eu"){return currency * usd_per_eur;}else if (type == "uk"){return currency * usd_per_gbp;}else{return -1.0;}
}void convert_currency_to_us_dollars_app()
{std::cout << "Please input currency value and type (one of: jpy, eur, gbp)\n";float currency;std::string type;std::cin >> currency >> type;float usd = convert_currency_to_us_dollars(currency, type);if (usd == -1.0f){std::cout << "error: invalid currency type: " << type << ". valid types: jpy, eur, gbp\n";return;}std::cout << currency << " " << type << " == " << usd << " usd\n";
}// Test function
void test_convert_currency_to_us_dollars()
{std::string test_input = "100 jpy\n"; // Change this to test other inputsstd::istringstream iss(test_input);// Redirect cin to use the stringstream inputauto cin_buff = std::cin.rdbuf();  // Save original bufferstd::cin.rdbuf(iss.rdbuf());// Run the function to testconvert_currency_to_us_dollars_app();// Restore original cinstd::cin.rdbuf(cin_buff);
}

这一版的代码,从原来的2级调用,改为3级。最内层的函数 convert_currency_to_us_dollars() 只负责转换,中间一层的函数 convert_currency_to_us_dollars_app() 则负责给出输入、输出的打印信息, 最外层的 test_convert_currency_to_us_dollars() 则负责测试。

对于C++11,基本上就这样了;如果是C++17,可以用 std::optional 作为返回值类型。。。

switch-case 语句, 可以一定程度上改进 if/else 语句,但是如果 case 中忘记写 break, 编译器没有报错,也没有警告:

在这里插入图片描述
在这里插入图片描述
不过, clang-tidy 可以会给出提示
在这里插入图片描述

4.5.1 使用函数

初学者,大概会写出如下代码:

void print_squares_v1(){for(int i=0;i<100;i++)cout<<i<<'\t'<<i*i<<'\n';
}

大概写了一年,注意到空格缩紧格式,会改进为:

void print_squares_v2()
{for (int i = 0; i < 100; i++)cout << i << '\t' << i * i << '\n';
}

但是,把什么内容都放在单个函数里,就缺乏了抽象层级;大概从业3年~10年,会意识到,要使用函数则可以把独立的任务加以封装,调用这个函数的地方,整体代码的逻辑更加清晰:

void print_square(int v)
{cout << v << '\t' << v * v << '\n';
}void print_squares_v3()
{for (int i = 0; i < 100; i++)print_square(i);
}

在这里插入图片描述

4.6 vector

定义变量: vector<T>(n) v 指定了长度为n,类型为T。

.push_back() 增加元素.

.size() 获取 vector 的长度。

sort(v.begin(), v.end() 展示了 begin() 和 end() 成员函数用法。

总结

4.2 这一章节「目标和工具」,着重强调了代码结构和质量的重要性,值得仔细品味。

4.3.1 常量表达式举出了 pi 和 299792458 这两个例子,对代码可读性和可维护性的重要性做了展示。「面向概念编程」是我自创的词语,是和「面向信息编程」这种低效率、混乱代码相对立的。它也类似于「数学分析课本中注重概念,从而注重证明」,和「高等数学课本中注重计算」相对立;它也类似于人们说的「先理解再记忆」。

有了结构的代码,是有「二次生命」的代码;没有结构的代码,是「一次性的代码」,无法被拿去复用。对于具备规模的软件,它的代码应当是具有「二次生命」的代码。

4.4.1 通过展示 if/else 语句,表达了程序要检查输入合法性的思想,非法的输入会得到非法输出(GIGO, Garbage In, Garbage Out); 以及,程序只应当提供它承诺的功能,对于没承诺的功能,需要明确告知使用者「不支持」、「输入非法」。

通过编写 convert_currency_to_us_dollars() 函数,并多次重构,得到了不那么糟糕的实现。

4.4.1.3 给出了 switch/case 语句,强调了 case 缺少 break 时编译器没有错误和警告的问题,写的时候要注意。

4.5.1 通过 squares 的例子,给出了糟糕代码到好代码的改进。

4.6 是 vector 的一些操作。


http://www.ppmy.cn/news/1534501.html

相关文章

内存占用估算方法

优质博文&#xff1a;IT-BLOG-CN 通过掌握每种数据类型的大小&#xff0c;就可以更准确地预测对象和数据的内存消耗。 一、基础数据类型 Java基础数据类型结构&#xff0c;在64位系统开启指针压缩情况下的内存占用字节数&#xff1a; booleanbytecharshortintlongfloatdoub…

Stable Diffusion绘画 | 插件-Deforum:动态视频生成(上篇)

Deforum 与 AnimateDiff 不太一样&#xff0c; AnimateDiff 是生成丝滑变化视频的&#xff0c;而 Deforum 的丝滑程度远远没有 AnimateDiff 好。 它是根据对比前面一帧的画面&#xff0c;然后不断生成新的相似图片&#xff0c;来组合成一个完整的视频。 Deforum 的优点在于可…

github/git密钥配置与使用

零、前言 因为要在ubuntu上做点东西&#xff0c;发现git clone 的时候必须输账户密码&#xff0c;后来发现密码是token&#xff0c;但是token一大串太烦了&#xff0c;忙了一天发现可以通过配置 公钥 来 替代 http 的 部署方式。 一、生成 ssh 密钥对 我们先测试下能不能 连接…

ETLCloud携手ClickHouse:高效的数据查询效率

自从大数据处理技术走进大众视野、开源项目Hadoop的出现&#xff0c;以前受制于数据库处理能力的大数据技术蓬勃发展&#xff0c;传统关系型数据库所构建的数据仓库&#xff0c;被以Hive为代表的大数据技术所取代&#xff0c;随着技术不断发展&#xff0c;Hadoop虽然带来了诸多…

Object.values() 、 Object.keys()

拿到当前对象里面的value值 // 假设你有一个对象 const myObject {name: Kimi,age: 30,country: Moon };// 获取对象的所有值 const values Object.values(myObject);// 输出值数组 console.log(values); // ["Kimi", 30, "Moon"] 如果你需要在 Vue 组…

使用 Vue3 和 Axios 实现 CRUD 操作

文章目录 1、准备工作2、创建 Vue 3 项目3、项目结构4、实现 CRUD 操作5、运行项目6、小结在当今的前端开发中,Vue.js 作为一款流行的 JavaScript 框架,正在被越来越多的开发者所青睐。尤其是 Vue 3 引入了 Composition API 和更优雅的响应式处理,使得模板编写和状态管理变得…

Build a Large Language Model (From Scratch)学习汇总

目录 中文理解代码电子书全文中文翻译第1章&#xff1a;理解大型语言模型第2章&#xff1a;处理文本数据第3章&#xff1a;编码Attention机制第4章&#xff1a;从零实现GPT模型第5章&#xff1a;在未标记数据上进行预训练第6章&#xff1a;用于文本分类的微调第7章&#xff1a;…

行为设计模式 -策略设计模式- JAVA

策略设计模式 一 .简介二. 案例2.1 抽象策略&#xff08;Strategy&#xff09;类2.2 具体策略&#xff08;Concrete Strategy&#xff09;类2.3 环境&#xff08;Context&#xff09;类2.4 测试 三. 结论3.1 优缺点3.2 使用场景 前言 这是我在这个网站整理的笔记,有错误的地方请…