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

这一章的标题是《计算》,想法是:计算是一个过程,是处理输入得到输出的过程。也有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 pound

    std::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 Yen
    constexpr float usd_per_eur = 1.1027f; // Europe
    constexpr float usd_per_gbp = 1.3126f; // Great British Pound

    std::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 inputs
    std::istringstream iss(test_input);

    // Redirect cin to use the stringstream input
    auto cin_buff = std::cin.rdbuf();  // Save original buffer
    std::cin.rdbuf(iss.rdbuf());

    // Run the function to test
    convert_currency_to_us_dollars();

    // Restore original cin
    std::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 Yen
    constexpr float usd_per_eur = 1.1027f; // Europe
    constexpr float usd_per_gbp = 1.3126f; // Great British Pound

    if (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 inputs
    std::istringstream iss(test_input);

    // Redirect cin to use the stringstream input
    auto cin_buff = std::cin.rdbuf();  // Save original buffer
    std::cin.rdbuf(iss.rdbuf());

    // Run the function to test
    convert_currency_to_us_dollars_app();

    // Restore original cin
    std::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 的一些操作。

 本书是经典程序设计思想与C++开发实践的完美结合,是C++之父回归校园后对C++编程原理和技巧的全新阐述。书中全面地介绍了程序设计基本原理,包括基本概念、设计和编程技术、语言特性以及标准库等,教你学会如何编写具有输入、输出、计算以及简单图形显示等功能的程序。此外,本书通过对C++思想和历史的讨论、对经典实例(如矩阵运算、文本处理、测试以及嵌入式系统程序设计)的展示,以及对C语言的简单描述,为你呈现了一幅程序设计的全景图。   ·C++初学者的权威指南。无论你是从事软件开发还是其他领域的工作,本书将为你打开程序开发之门。   ·中高级程序员的必备参考。通过观察程序设计大师如何处理编程中的各种问题,使你获得新的领悟和指引。   ·全面阐释C++基本概念和技术。与传统的C++教材相比,本书对基本概念和技术的介绍更为深入,为你编写实用、正确、易维护和有效的代码打下坚实的基础。   ·强调现代C++编程风格。本书从开篇就介绍现代C++程序设计技术,并揭示了大量关于如何使用C++标准库来简化程序设计原理,使你快速掌握实用编成技巧。 图书目录   出版者的话   译者序   前言   第0章 致读者   第1章 计算机、人与程序设计   第一部分 基本知识   第2章 Hello,World!   第3章 对象、类型和值   第4章 计算   第5章 错误   第6章 编写一个程序   第7章 完成一个程序   第8章 函数相关的技术细节   第9章 类相关的技术细节   第二部分 输入和输出   第10章 输入/输出流   第11章 定制输入/输出   第12章 一个显示模型   第13章 图形类   第14章 设计图形类   第15章 绘制函数图和数据图   第16章 图形用户界面   第三部分 数据结构和算法   第17章 向量和自由空间   第18章 向量和数组   第19章 向量、模板和异常   第20章 容器和迭代器   第21章 算法和映射   第四部分 拓宽视野   第22章 理念和历史   第23章 文本处理   第24章 数值计算   第25章 嵌入式系统程序设计   第26章 测试   第27章 C语言   术语表   参考书目   第五部分 附录?   附录A C++语言概要   附录B 标准库概要   附录C Visual Studio简要入门教程   附录D 安装FLTK   附录E GUI实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值