1、引言
学习一门新的语言最好的方法,就是练习编程。在这里,我们将编写一个程序去解决简单的书店问题。
问题如下:
我们的书店保存所有销售记录的档案,每条记录保存某本书一次的销售信息(一册或者多册)。
每天记录包括三个数据项: 0-201-70353-X 4 24.99
第一项是书的ISBN号(一本书的唯一标识),第二本书是售出的册数,第三项是书的单价。
任务:
书店老板需要查询此档案,计算每本书的销售量、销售额以及平均售价。
虽然我们还没有编写这个程序,但显然它必须满足如下要求:
- 定义变量
- 进行输入和输出
- 使用数据结构保存数据
- 检测两条记录是否有相同的ISBN
- 使用一个循环来处理销售档案中的每一条数据
1.1 编写一个简单的C++程序
每个C++程序至少有一个或多个函数,其中一个必须命名为main,OS(operation system)是通过调用main函数来运行整个C++程序。
函数的定义包含四个部分:
- 返回类型
- 函数名
- 括号中的形参列表
- 函数体
下面是一个简单的main函数示例:
作用:
什么也不干,只是给操作系统返回一个值。
int main()
{
return 0;
}
注释:
- main 函数的形参列表是空的(但是也有其他其他的形参类型,后面我们会介绍)
- main 函数的返回类型必须为 int(当 return 语句包含一个值的时候,此返回值的类型必须与函数的返回类型保持一致)
重要概念:类型
一种类型不仅定义数据元素的内容,还定义了这种类型上可以进行的运算。程序所处理的数据都保存在变量中,而每一个变量都有其自己的数据类型。
1.1.1 编译、运行程序
略
1.1练习
- 查阅你使用的编译器文档,确定它所使用的文件命名约定。编译并允许main函数
- 改写程序,让main函数返回-1。返回-1通常是程序错误的标识。重新编译并允许你的程序,观察你的系统如何处理main返回的错误标识
1.2 初识输入输出
C++并未定义任何输入输出(I/O)的语句,取而代之,是一个全面的标准库来提供I/O机制。
常用头文件的解释:
#include<iostream>
注释:
- 这里使用了iostream库
- iostream 库中包含两个基础类型istream和ostream,分别表示输入流和输出流。一个流(随着时间的推移,字符是顺序生成的或者顺序消耗的)就是一个字符序列,是从I/O设备读出或者写入I/O设备的。
- #include是告诉编译器我们想要使用iostream库
- #include指令必须出现在所有的函数之外
1.2.1 标准输入输出对象:
C++提供了4个I/O对象:
- cin,标准输入对象,类型是istream
- cout,标准输出对象,类型是ostream
- cerr,标准错误(用来输出警告和错误信息),类型是ostream
- clog,输出程序运行时的一般信息
仔细思考:
系统将程序所运行的窗口和这些对象关联起来。当我们读取cin的时候,数据将从程序正在运行的窗口读入,当我们向cout、cerr、clog写入数据时,将会写到同一窗口。
1.2.2 像流中写入数据
std::cout<<"Enter two numbers"<<std::endl;
注释:
- 使用输出运算符(<<) 在标准输出上打印消息
- (<<) 运算符接受两个运算对象:左侧的运算对象必须是一个ostream类型的对象,右侧的运算对象是要打印的值。
- 输出语句中使用了两次(<<)运算符,因为此运算符返回左侧的运算对象,因此第一个运算的输出结果就成为左侧运算对象。
- endl 是称为操纵符的特殊值。作用是“结束当前行”,并将与设备关联的缓冲区中的内容刷到设备中。缓冲刷新操作可以保证到目前位置的所有输出都真正写入输出流中,而不是仅停留在内存等待写入流。
- std::cout 和 std::endl,前缀std:: 指出了名字cout和endl是定义在名为std的命名空间中。当我们需要使用标准库的一个名字时,必须通过作用域运算符(::)显示的说明来自命名空间std的名字。
警告:
程序员在调试时添加打印语句,这类语句要保证“一直刷新流”。否则,如果程序崩溃,输出还可能留在缓冲区,从而导致程序崩溃位置的错误推断。
1.2.3 从流中读取数据
int v1=0;
int v2=0;
std::cin>>v1>v2;
注释:
- 输入运算符(>>)与输出运算符类似,它接受一个istream作为其左侧运算对象,接受一个对象作为其右侧的运算对象。从给定的istream读入数据,并存入给定对象中。与输出运算符类似,输入运算符返回其左侧运算对象作为其运算结果。
1.3 注释简介
1.3.1 C++注释的种类
- 单行注释:以双斜线(//)开始,以换行符结束。
- 界定符对注释:以(/*)开始,以(*/)结束。编译器将落在/*和*/之内的所有内容当作注释
注意:注释不要嵌套
1.4 控制流
语句一般都是顺序执行的,语句块的第一条语句首先执行,然后是第二条语句,以此类推。但是C++程序设计语言,提供了多种不同的控制流语句,允许我们写出更为复杂的执行路径。
1.4.1 while语句
while语句会反复的执行一段代码,直到给定条件为假为止。
#include<iostream>
using namespace std;
int main()
{
int sum = 0, val = 1;
while (val <= 10)
{
sum += val;
++val;
}
cout << "Sum of 1 to 10 inclusive is " << sum << endl;
return 0;
}
while语句的形式:
while(condition)
statement;
注释:
- 复合赋值运算符(+=),将右侧的运算对象加到左侧上,将结果保存在左侧的运算对象中
- 前缀递增运算符(++),将运算对象的值增加1。
1.4.2 for语句
#include<iostream>
using namespace std;
int main()
{
int sum = 0;
for (int val = 1; val <= 10; ++val)
{
sum += val;
}
cout << "Sum of 1 to 10 inclusive is " << sum << endl;
return 0;
}
for语句的形式:循环头和循环体。
循环头:控制循环体的执行次数,由三个部分组成:一个初始化语句,一个循环条件以及一个表达式。
循环体:执行具体的语句。
注释:
- 初始化语句:int val=1;
- 循环条件:val<=10;
- 表达式:++val;
- 循环体在每次执行前都会先检查循环条件,只要条件满足就会执行循环体,表达式在循环体执行之后再执行。直到检查条件不满足,就会退出循环。
1.4.3 读入数量不定的输入语句
#include<iostream>
using namespace std;
int main()
{
int sum = 0;
int value = 0;
while (cin >> value)
{
sum += value;
}
cout << "Sum is : " << sum << endl;
return 0;
}
注释:
- 当我们使用一个istream对象作为条件,其效果就是检测流的状态。如果流是有效的,即流未遇到错误,那么检测成功;当遇到文件结束符,或者遇到一个无效的输入(这里指的是输入的值不是一个整数),istream对象的状态会变成无效,istream对象会使循环条件为假。
1.4.4 if语句
#include<iostream>
using namespace std;
int main()
{
int currVal = 0, val = 0;
int cnt = 0;
if (cin >> currVal)
{
cnt = 1;
while (cin >> val)
{
if (val == currVal)
{
++cnt;
}
else
{
cout << currVal << " occurs " << cnt << " times " << endl;
currVal = val;
cnt = 1;
}
}
cout << currVal << " occurs " << cnt << " times " << endl;
}
return 0;
}
注释:
- 赋值运算符(=)和相等运算符(==),所使用的场合是完全不同的。
1.5 类简介
说明:在解决书店程序之前,我们还需要了解C++的重要特性,就是如何定义一个数据结构来表示自己的销售。在C++中,通常定义一个类来定义自己的数据结构。一个类定义了一种类型,以及在此类型上的一组操作。
1.5.1 Sales_item类
Sales_item类的作用就是表示一本书的总销售额,售出的册数,以及平均售价。我们现在不关心这些数据如何存储,如何计算。为了使用一个类,我们不必关心它是如何实现的,只需要知道类对象可以执行什么操作。
每一个类实际上都是定义了一个新的数据类型,类型名就是类名。和内置数据类型一样,我们可以定义类类型的变量。
Sales_item类可以执行的操作:
- 定义一个Sales_item类型的变量;
- 调用一个名为isbn的函数从一个Sales_item对象中提取ISBN书号;
- 用输入运算符(>>)和输出运算符(<<)读写Sales_item类型的对象;
- 用赋值运算符(=) 将一个Sales_item对象的值赋给另一个Sales_item;
- 用加法运算符(+)将两个Sales_item对象相加。两个对象必须表示同一本书(相同的ISBN)。加法的结果是一个新的Sales_item对象,其ISBN与两个运算对象相同,而总销售额和售出册数则是两个运算对象的对应值之和。
- 使用复合赋值运算符(+=)将一个Sales_item对象加到另一个对象上;
#include<iostream>
#include"Sales_item.h"
using namespace std;
int main()
{
Sales_item book;
cin >> book;
cout << book << endl;
return 0;
}
#pragma once
#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
注释:
- 使用来自非标准库的头文件时应使用("")
- 在创建过工程时应,应该添加一个Sales_item.h开头的头文件,并且在cpp文件中包含此文件。
#include<iostream>
#include"Sales_item.h"
using namespace std;
int main()
{
Sales_item item1, item2;
cin >> item1 >> item2;
if (item1.isbn == item2.isbn)//这里是定义公共成员变量:string isbn;
{
cout << item1 + item2 << endl;
return 0;
}
else
{
cerr << "Data must refer to same ISBN" << endl;
return -1;
}
}
注释:
- 书本上关于成员函数说法有误,Sales_item.h头文件中包含的是string类型的公共成员变量isbn,不是内部的成员函数。
- 点运算符(.),只能用于类类型的对象,其左侧的运算对象必须是类类型的对象,右侧的运算对象必须是该类型的一个成员名,运算结果为右侧运算对象指定的成员。
1.6 书店程序
问题如下:
程序会将每个ISBN的所有数据合并起来,存入名为total的变量之中。使用一个名为trans的变量保存读取的每条销售记录。如果trans和total指向相同的ISBN,我们会更新total的值。否则,我们就会打印total的值,并将其重置为刚刚读取的数据(trans):
#include<iostream>
#include"Sales_item.h"
using namespace std;
int main()
{
Sales_item total;//保存下一条交易记录的变量
if (cin >> total)
{
Sales_item trans;
while (cin >> trans)
{
if (total.isbn == trans.isbn)
{
total += trans;
}
else
{
cout << total << endl;
total = trans;
}
cout << total << endl;
}
}
else
{
cerr << "No data?!" << endl;
return -1;
}
return 0;
}