C++ Primer学习笔记0-书店程序

273 阅读5分钟

开始

本章通过学习基础知识,编写一个简单的程序来解决书店问题。

书本信息:

ISBN售出册数单价
0-201-70353-X424.99

要求如下:

  • 定义变量
  • 进行输入和输出
  • 使用数据结构保存数据
  • 检测两条记录是否有相同的ISBN
  • 包含一个循环来处理销售档案中的每条记录

编写一个简单的C++程序

练习1:编译一个基础main程序;

练习2:改写main返回的值为-1,并观察错误标识;

初识输入和输出(IO流)

练习1:打印出Hello,World。

练习2:编写程序求输入的两个值的乘积

练习3:重写程序,将每个运算对象打印操作放在一条独立的语句中

#include <iostream>

int main(){
    std::cout << "Enter two numbers: " << std::endl;
    int v1 = 0, v2 = 0;
    std::cin >> v1 >> v2;
    std::cout << "The sum of " << v1 << "and" << v2 << "is" << v1 + v2 << std::endl;
    return 0;
}

练习4:判断下列程序片段是否合法,他会输出什么?如果不合法,原因是什么,应该如何修正

std::cout << "The sum of " << v1;
          << " and " << v2;
          << " is " << v1 + v2 << std::endl;

注释简介

练习5:编译一个包含不正确的嵌套注释的程序,观察编译器返回的错误信息;

练习6:指出下列哪些输出语句是合法;

std::cout << "/*";
std::cout << "*/";
std::cout << /* " */" */;
std::cout << /* "*/" /* "/*" */;

控制流

读取数量不定的输入数据

当我们预先不知道用户要多少个数字进行操作(书中指的是求和)时,就需要不停读取用户的输入直至没有新的输入为止,我们可以通过循环获取输入来获取:

    while (std::cin >> value)

当我们使用一个istream对象作为条件时,其效果是检测流的状态。如果流是有效的,即表示流未遇到错误,那么检测成功。当遇到文件结束符,或者遇到一个无效的输入时(输入的和写入变量类型不一致),istream对象的状态就会变成无效。处于无效状态的istream对象会使条件变为假;

练习7:编写程序,使用while循环将50到100的整数相加;

#include <iostream>

int main()
{
    // 练习7:编写程序,使用while循环将50到100的整数相加;
    std::cout << "练习7:编写程序,使用while循环将50到100的整数相加;" << std::endl;
    int val1 = 50;
    int testSevenSum = 0;
    while (val1 <= 100)
    {
        testSevenSum += val1;
        val1++;
    }
    std::cout << "50到100的整数相加和是:" << testSevenSum << std::endl;
    return 0;
}

练习8:编写程序,使用while循环递减运算符在循环中按递减顺序打印出10到0之间的整数;

#include <iostream>

int main()
{
    //练习8:编写程序,使用while循环递减运算符在循环中按递减顺序打印出10到0之间的整数;
    std::cout << "练习8:编写程序,使用while循环递减运算符在循环中按递减顺序打印出10到0之间的整数;" << std::endl;
    int val2 = 9;
    while (val2 > 0)
    {
        std::cout << val2 << std::endl;
        val2--;
    }
    return 0;
}

练习9:编写程序,提示用户输入两个整数,使用while循环打印出这两个整数所指定的范围内所有整数;

#include <iostream>

int main()
{
    //练习9:编写程序,提示用户输入两个整数,使用while循环打印出这两个整数所指定的范围内所有整数;
    std::cout << "练习9:编写程序,提示用户输入两个整数,使用while循环打印出这两个整数所指定的范围内所有整数;" << std::endl;
    int val3, val4;
    std::cout << "请输入任意一个整数:";
    std::cin >> val3;
    std::cout << "请输入一个更大的数:";
    std::cin >> val4;
    val3++;
    while (val3 < val4)
    {
        std::cout << val3 << std::endl;
        val3++;
    }
    return 0;
}

练习10:编写程序,使用for循环将50到100的整数相加;

#include <iostream>

int main()
{
    //练习10:编写程序,使用for循环将50到100的整数相加;
    std::cout << "/练习10:编写程序,使用for循环将50到100的整数相加;" << std::endl;
    int testTenSum = 0;
    for (int i = 50; i <= 100; i++)
    {
        testTenSum += i;
    }
    std::cout << "50到100的整数相加和是:" << testTenSum << std::endl;

    return 0;
}

练习11:编写程序,使用for循环递减运算符在循环中按递减顺序打印出10到0之间的整数;

#include <iostream>

int main()
{
    // 练习11:编写程序,使用for循环递减运算符在循环中按递减顺序打印出10到0之间的整数;
    std::cout << "练习11:编写程序,使用for循环递减运算符在循环中按递减顺序打印出10到0之间的整数;" << std::endl;
    for (int i = 10 - 1; i > 0; i--)
    {
        std::cout << i << std::endl;
    }

    return 0;
}

练习12:编写程序,提示用户输入两个整数,使用for循环打印出这两个整数所指定的范围内所有整数;

#include <iostream>

int main()
{
    // 练习12:编写程序,提示用户输入两个整数,使用for循环打印出这两个整数所指定的范围内所有整数;
    std::cout << "练习12:编写程序,提示用户输入两个整数,使用for循环打印出这两个整数所指定的范围内所有整数;" << std::endl;
    int val5, val6;
    std::cout << "请输入任意一个整数:";
    std::cin >> val5;
    std::cout << "请输入一个更大的数:";
    std::cin >> val6;
    val5++;
    for (val5; val5 < val6; val5++)
    {
        std::cout << val5 << std::endl;
    }
    return 0;
}

练习13:编写程序,从cin读取一组数,并求和输出

#include <iostream>

int main()
{
    //练习13:编写程序,从cin读取一组数,并求和输出
    int testThirteenSum = 0, thirteenVal = 0;
    std::cout << "请输入你需要求和的数(结束时请输入end):";
    while (std::cin >> thirteenVal)
    {
        testThirteenSum += thirteenVal;
    }
    std::cout << "练习13的和为:" << testThirteenSum << std::endl;
    return 0;
}

练习14:编译并运行下列代码,分别尝试输入全部重复的值和全部不重复的值

#include <iostream>

int main()
{
    int currVal = 0, val = 0;
    if (std::cin >> currVal)
    {
        int cnt = 1;
        while (std::cin >> val)
        {
            if (val == currVal)
            {
                ++cnt;
            }
            else
            {
                std::cout << currVal << " occurs " << cnt << " times " << std::endl;
                currVal = val;
                cnt = 1;
            }
        }
        std::cout << currVal << " occurs " << cnt << " times " << std::endl;
    }
    return 0;
}

这个程序的输入逻辑是要先把相同的数字都输入,这种情况下才能正常的统计,如果用户是随机输入,则无法正常统计

练习15:修改【练习9】和【练习12】的代码,使其兼容用户输入第二个数比第一个值小的情况

#include <iostream>

int main()
{
    // 练习15:修改【练习9】和【练习12】的代码,使其兼容用户输入第二个数比第一个值小的情况
    std::cout << "魔改练习9" << std::endl;
    int val1, val2;
    std::cout << "请输入任意一个整数:";
    std::cin >> val1;
    std::cout << "请在输入任意一个整数:";
    std::cin >> val2;
    if (val1 > val2)
    {
        val2++;
        while (val2 < val1)
        {
            std::cout << val2 << std::endl;
            val2++;
        }
    }
    else
    {
        val1++;
        while (val2 > val1)
        {
            std::cout << val1 << std::endl;
            val1++;
        }
    }

    std::cout << "魔改练习12" << std::endl;
    int val3, val4;
    std::cout << "请输入任意一个整数:";
    std::cin >> val3;
    std::cout << "请在输入任意一个整数:";
    std::cin >> val4;
    if (val3 > val4)
    {
        val4++;
        for (val4; val4 < val3; val4++)
        {
            std::cout << val4 << std::endl;
        }
    }
    else
    {
        val3++;
        for(val3;val3<val4;val3++){
            std::cout << val3 << std::endl;
        }
    }
    return 0;
}

类简介

类机制是C++最重要的特性之一。 为了使用类,我们需要了解以下三件事情:

  • 类名是什么?
  • 类是在哪里定义的?
  • 它支持什么操作?

Sales_item类(知道怎么用就好,不需要纠结源码)

使用书本中提供的网址无法下载这个类,使用了一下万能的度娘就找到了:

// Sales_item.hpp
#ifndef SALESITEM_H
#define SALESITEM_H
#include <iostream>
#include <string>

class Sales_item
{
public:
    Sales_item(const std::string &book) : isbn(book), units_sold(0), revenue(0.0) {}
    Sales_item(std::istream &is) { is >> *this; }
    friend std::istream &operator>>(std::istream &, Sales_item &);
    friend std::ostream &operator<<(std::ostream &, const Sales_item &);

public:
    Sales_item &operator+=(const Sales_item &);

public:
    double avg_price() const;
    bool same_isbn(const Sales_item &rhs) const
    {
        return isbn == rhs.isbn;
    }
    Sales_item() : units_sold(0), revenue(0.0) {}

public:
    std::string isbn;
    unsigned units_sold;
    double revenue;
};

using std::istream;
using std::ostream;
Sales_item operator+(const Sales_item &, const Sales_item &);

inline bool operator==(const Sales_item &lhs, const Sales_item &rhs)
{
    return lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue && lhs.same_isbn(rhs);
}

inline bool operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
    return !(lhs == rhs);
}

inline Sales_item &Sales_item::operator+=(const Sales_item &rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

inline Sales_item operator+(const Sales_item &lhs, const Sales_item &rhs)
{
    Sales_item ret(lhs);
    ret += rhs;
    return ret;
}

inline istream &operator>>(istream &in, Sales_item &s)
{
    double price;
    in >> s.isbn >> s.units_sold >> price;
    if (in)
        s.revenue = s.units_sold * price;
    else
        s = Sales_item();
    return in;
}

inline ostream &operator<<(ostream &out, const Sales_item &s)
{
    out << s.isbn << "\t" << s.units_sold << "\t" << s.revenue << "\t" << s.avg_price();
    return out;
}

inline double Sales_item::avg_price() const
{
    if (units_sold)
        return revenue / units_sold;
    else
        return 0;
}
#endif

include

如果include的是标准库,则使用尖括号包围头文件,如:

# include <iostream>

如include的不是标准库,则使用双引号包围头文件,如:

# include "Sales_item.hpp"

Sales_item 加法

Sales_item支持两个对象进行相加,会将相同的ISBN号进行归类,统计售出的册数和价钱

使用文件重定向

当你测试程序时,反复从键盘输入这些销售记录是一件很乏味的事情,大多数的操作系统都支持文件重定向。这种机制允许我们将标准输入和标准输出与命名文件关联起来:

$ addItems <infile >outfile

假定 $ 是操作系统提示符,我们的假发车光绪已经编名为addItems.exe 的可执行文件中(Unix是addItems),则上述命令会从一个名为infile的文件读取销售记录,并将结果写入到名为outfile的文件中,两个文件都会位于当前目录

注:本人在这里研究了好久,上面的命令的尖括号有奥妙,指向可执行文件的尖括号是使用尖括号后面的文件作为输入内容被执行文件读取,而右尖括号则是将编译文件的数据写入文件中,两者可以单独使用,如:

$ addItems <infile
$ addItems >outfile

练习15:编写程序:读取两个ISBN相同的Sales_item对象,输出它们的和。

#include <iostream>
#include "Sales_item.hpp"

int main()
{
    // 练习15:编写程序:读取两个ISBN相同的Sales_item对象,输出它们的和。
    Sales_item item1,item2;
    std::cin >> item1 >> item2;
    std::cout << item1 + item2 << std::endl;
    return 0;
}

执行方法:

先来到源文件的路径中,输入:

$ g++ test1_6.cpp -o test1_6

这时会得到一个名为test1_6的编译文件,然后创建一个名为test15.txt的文件,写入以下数据,并保存:

0-201-70353-x 3 24.99

0-201-70353-x 3 24.99

最后,在bash中输入命令即可:

$ ./test1_6 <test15.txt

练习16:编写程序:读取多个具有相同Sales_item的销售记录,并输出所有记录的和

#include <iostream>
#include "Sales_item.hpp"

int main()
{
    // 练习16:编写程序:读取多个具有相同Sales_item的销售记录,并输出所有记录的和
    Sales_item item1, itemSum;
    while (std::cin >> item1)
    {
        itemSum += item1;
    }
    std::cout << itemSum << std::endl;
    return 0;
}

不知道为什么,结果不会显示isbn号...然后上网看了一下别人的写法:

#include <iostream>
#include "Sales_item.hpp"

int main()
{
    // 练习16:编写程序:读取多个具有相同Sales_item的销售记录,并输出所有记录的和
    Sales_item item1, item2;
    std::cin >> item1;
    while (true)
    {
        std::cin >> item2;
        if (item2.isbn == "")
        {
            std::cout << item1 << std::endl;
            return 0;
        }
        else
        {
            item1 += item2;
        }
    }
    return 0;
}

这样写就完全没问题,估计是直接循环cin的问题,尝试修改一下:

int main()
{
    // 练习16:编写程序:读取多个具有相同Sales_item的销售记录,并输出所有记录的和
    Sales_item item1, item2;
    std::cin >> item1;
    while (std::cin >> item2)
    {
        item1 += item2;
    }
    std::cout << item1 << std::endl;
    return 0;
}

完成~~

成员函数

#include <iostream>
#include "Sales_item.hpp"

int main()
{
    // 本小节代码
    Sales_item item1, item2;
    // 继续选择读取文件的方式
    std::cin >> item1 >> item2;
    // 检查输入是否为同一本书
    if (item1.isbn == item2.isbn)
    {
        std::cout << item1 + item2 << std::endl;
        return 0;
    }
    else
    {
        // cerr是打印异常log
        std::cerr << "Data must refer to same ISBN" << std::endl;
        return -1;
    }
    return 0;
}

练习17&18:编写程序,读取多条销售记录,并统计每个ISBN有几条销售记录(每个ISBN的记录应该聚在一起)。

#include <iostream>
#include "Sales_item.hpp"

int main()
{
    // 练习17&18:编写程序,读取多条销售记录,并统计每个ISBN有几条销售记录(每个ISBN的记录应该聚在一起)。
    Sales_item item1, item2;
    int countNum = 1;
    // 处理第一条写入的数据
    if (std::cin >> item1)
    {
        while (std::cin >> item2)
        {
            // 如果本次录入的数据和当前的ISBN号一致,则增加记录的数量
            if (item1.isbn == item2.isbn)
            {
                countNum++;
            }
            else
            {   
                std::cout << item1.isbn << " 共有" << countNum << "条数据" << std::endl;
                // 统计完毕后,初始化item1和countNum
                item1 = item2;
                countNum = 1;
            }
        }
        std::cout << item1.isbn << " 共有" << countNum << "条数据" << std::endl;
    }
    else
    {
        std::cerr << "Fail,Data is not true." << std::endl;
        return -1;
    }
    return 0;
}

书店程序

现在我们已经准备好完成书店程序了。我们需要从一个文件读取销售记录,生成每本书的销售报告,显示售出册数、总销售额和平均售价。我们假定每个ISBN书号的所有销售记录在文件中都是聚在一起保存的(相同的ISBN号连着排序)

我们的程序会将每个ISBN的所有数据合并起来,存入名为total的变量中。我们使用另一个名为trans的变量保存读取的每条销售记录。如果trans和total指向相同的ISBN,我们会更新total的值。否则,我们会打印total的值,并重置为刚刚读取的数据;

练习19:借助Sales_item.hpp文件,编译并运行本节的代码;

#include <iostream>
#include "Sales_item.hpp"

int main()
{
    Sales_item total;
    if (std::cin >> total){
        Sales_item trans;
        while (std::cin >> trans)
        {
            if (total.isbn == trans.isbn){
                total += trans;
            }else{
                std::cout << total << std::endl;
                total = trans;  //初始化
            }
        }
        std::cout << total << std::endl;
    } else{
        std::cerr << "No data?!" << std::endl;
        return -1;
    }
    return 0;
}

输出:

$ ./bookstore <bookstore.txt 
0-201-70353-x   45      1124.55 24.99
0-201-70352-x   32      799.68  24.99
0-201-70333-x   20      499.8   24.99