(5)自定义数据结构再探
C++Primer要求自己写一个Salse_data,并引用这个数据类型。这就有一个问题,怎么正确写出一个自己定义的可以引用的头文件(虽然也可以通过在同一个文件中定义来完成这个要求)?于是百度和看了程序案例,发现除了正常的写一个struct外,还要加上#ifndef/#define/#endif,这玩意在定义头文件中有什么用呢?百度到了这篇文章,可以先暂时看这个理解,后面再深究:
http://blog.csdn.net/abc5382334/article/details/18052757
这是我自己写的:
//用自己写的类计算卖出的一种书的总销售额、总销售数、平均价格 #include <iostream> /* *后续会详解什么是string类型,现在先说一点我们要用到的: *string类型其实就是字符的序列,操作有>>、<<、==等,功能有读入、写出字符串和比较字符串 */ #include <string> //定义一个Sales_data类型,成员分别记录一种书的ISBN、售出数量、总收入 struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; int main() { //定义存储两次交易的数据 Sales_data data1, data2; //定义变量用来记录单次交易的单价 double price = 0; //输入两次交易的所有数据:INSB、售出数量、单次交易的单价,并算出单次交易的总收入 std::cout << "plase enter The ISBN、amount of sales、unit price twice,end of space bar" << std::endl; std::cout << "请分别输入两次交易数据的ISBN、售出数量、单价,空格结束." << std::endl; std::cin >> data1.bookNo >> data1.units_sold >> price; data1.revenue = data1.units_sold*price; std::cin >> data2.bookNo >> data2.units_sold >> price; data2.revenue = data2.units_sold*price; //如果ISBN号一致,输出ISBN和合并后的总销售数、总收入、平均价格 if (data1.bookNo == data2.bookNo) { //总销售数 unsigned totalCnt = data1.units_sold + data2.units_sold; //总收入 double totalRevenue = data1.revenue + data2.revenue; //平均价格 double avPrice = totalRevenue / totalCnt; std::cout << data1.bookNo << " " << totalCnt << " " << totalRevenue << " " << avPrice << std::endl; } //ISBN不一样,输出错误提示 else { std::cout << "error data" << std::endl; } system("pause"); return 0; }
书上写的是:
#include <iostream>
#include <string>
//课本上是直接头文件引用Sales_data,这里我改成了在主函数前声明这个类
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sales_data data1, data2;
double price = 0;
std::cin >> data1.bookNo >> data1.units_sold >> price;
data1.revenue = data1.units_sold*price;
std::cin >> data2.bookNo >> data2.units_sold >> price;
data2.revenue = data2.units_sold*price;
//从这里开始和我不一样了,为什么会这样?
if (data1.bookNo == data2.bookNo)
{
unsigned totalCnt = data1.units_sold + data2.units_sold;
double totalRevenue = data1.revenue + data2.revenue;
//这行没用std::endl,为什么?
std::cout << data1.bookNo << " " << totalCnt << " " << totalRevenue << " ";
if (totalCnt != 0)
std::cout << totalRevenue / totalCnt << std::endl;
else
std::cout << "(no sales)" << std::endl;
return 0;
}
else {
//cerr、cout、clog的区别?
std::cerr << "Data must refer to the same ISBN " << std::endl;
return -1;
}
return 0;
}
如上面代码,一共有三个问题需要去解决。
1.我写的为什么和书上的不一样?
书上考虑了会有一本都没卖出去的情况,但我觉得既然都没卖出去,又怎么会输入这个交易数据呢。。。。也许考虑这种bug般的情况是应该的~以后真正写东西的时候要做到“把用户当傻逼”去看待;书上输出错误信息是用cerr而不是cout,为什么会这样后面下面会讨论;书上没有再定义一个变量来存储平均价格,我想了一下,确实不需要再多定义一个变量去存这个数字了,因为没有必要——后面再没有需要使用平均价格的地方了,而totalCnt 、totalRevenue后面都要用到,所以要特别再定义它们来存。
2.什么时候用std:endl,什么时候不需要用?这个问题下面会讨论,这里先讨论std::endl和\n的区别。
首先\n在c里是控制字符,表换行,可作为一个字符来使用;endl是C++中行结束符,只能用于输出流中。(C++继承了c的\n)
其次是endl虽然也有换行的效果,但是它比\n多了一个“刷新”流里的缓冲的flush操作,至于这个操作是什么意思,后面会说到。
3.cerr、cout、clog的区别?以及上面提到的什么是刷新缓冲的操作,什么时候要用std::endl。
首先参考这两篇文章:
https://stackoverflow.com/questions/213907/c-stdendl-vs-n
http://blog.csdn.net/garfield2005/article/details/7639833
什么是缓冲区刷新?
缓冲区的目的,就是减少刷屏的次数——比如,你的程序输出一篇文章。不带缓冲的话,就会每写一个字母,就输出一个字母,然后刷屏。有了缓冲,你将看到若干句子“同时”就出现在了屏幕上(由内存翻新到显存)
cout是在终端显示器输出,其流通常是传到显示器,但也可以被重定向到文件。cout流在内存中对应开辟了一个缓冲区,用来存放流中的数据,当向cout流插入一个endl,不论缓冲区是否漫了,都立即输出流中所有数据,然后插入一个换行符.
cerr流对象是标准错误流,和cout作用差不多,但流中的信息只能在显示器输出. ,且不经过缓冲区就直接向显示器输出信息
clog流也是标准错误流,作用和cerr一样。但clog中的信息存放在缓冲区,缓冲区满或者遇到endl时才输出.
为什么不把这些功能集中为一体呢?
首先输出错误信息是必须的,cerr之所以选择不经过缓冲区就输出,是怕有些程序bug会导致经过缓冲区的东西无法输出,比如,你的程序遇到调用栈用完了的威胁(无限,没有出口的递归),到什么地方借内存,存放你的错误信息?而如果都和cerr一样不经过缓冲区就输出,就没办法做到若干句子“同时”出现在屏幕上。
也就是说,为了能输出错误信息,不经缓冲区的操作是必须的,于是有了cerr;而为了同时输出信息,经过缓冲区是必须的,于是有了cout。
就是clog让我觉得很奇怪,似乎没有存在的必要.......除非clog是一种能把错误信息重定向到文件的操作,这样它的出现理由就是制作错误文件!现在还不懂怎么重定向到文件,留着以后检验。
最后是书里的习题解答:
//编写程序,利用自己写的Sales_data,读取两个(或多个)相同ISNBN的销售记录,输出所有记录的和(当输入不同的ISBN数据时结束程序) #include <iostream> #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; int main() { Sales_data data1, data2; //不能直接std::cin>>data1,可能和Sales_data定义没有Sales_item完整有关 if (std::cin >> data1.bookNo >> data1.units_sold >> data1.revenue) { //这里用if是不对的,if只判断一次就结束了,不能多次输入!必须用while循环 while (std::cin >> data2.bookNo >> data2.units_sold >> data2.revenue) { if (data1.bookNo == data2.bookNo) { //一开始以为用不到units_sold和revenue的和,就直接在输出语句里相加了;后来发现要把data1的数据更新才能继续输出其它记录就定义了记录两者的变量totalS和totaR
//“用到的时候再定义,且定义在最靠近第一次使用的地方!”,这就是这句话为什么正确的原因! unsigned totalS= data1.units_sold + data2.units_sold; double StotaR= data1.revenue + data2.revenue; std::cout << data1.bookNo << " " << totalS << " " << StotaR << std::endl; data1.units_sold = totalS; data1.revenue = StotaR; } else { std::clog << "data of no avail" << std::endl; //return是用来终止函数循环的,while和if都不是一个函数所以加了也不能终止程序;break倒是可以终止while这种循环语句(if是判断不是循环)。 //break作用是块作用,比如下面的放在else块里,就意味进入else后整个while就会结束;如果放在while块中就是while只循环一次就结束——这样while就没有意义了! break; } } } return 0; } /*我这个程序有个bug: *我没有定义输入的数据是无效数据的时候会发生什么。 *实际上string能存数字+字母,我想不到有什么数据是无效的,至于后面两个,和double,一旦输入无效数据,就会直接整个程序结束了,没有任何报错。怎么使其报错呢? */
39~42有疑问(吐槽下,在代码里标红色,实际查看时却并没有变红!)
//编写程序,读取多条销售记录,并统计每个ISBN有几条销售记录
//这程序有一个bug,如果连续3条或以上ISBN不一样,则输出就要重置了!即真正销售记录要把之前输出过的同ISBN的全部加起来才行。想不到办法解决这个问题了。
#include <iostream>
#include <string>
struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; int main() { Sales_data data1, data2; //这里的data1.units_sold和data1.revenue其实是没有用处的...但为了让用户输入“销售”记录而不是ISBN,所以还是保留了 if (std::cin >> data1.bookNo >> data1.units_sold >> data1.revenue) { //number用来计数,保存同ISBN卖出的数量 //这个number不能写在while里的if里,因为if一旦结束,number这个局部变量就没了,其计数功能就没了 /*同理也不能写在while里,因为一旦while里的if结束number又会被重新定义一次,且我注意到: *int a = 1; *a = a + 1; *int a = ; *这种代码会报错说是重复定义,但是: *while() *int a = 1; *这种就不会!我的理解是while每次执行int a = 1的时候实际上已经把a这个变量抹除了重新定义,所以不算重复定义 *所以要注意,不能随便在循环中定义变量。 */ //因为number在第一个if外不再有什么作用,所以定义在第一个if里和定义在main函数里其实没区别,本着用到时才定义的原则我定义在了第一个if里 int number = data1.units_sold; while (std::cin >> data2.bookNo >> data2.units_sold >> data2.revenue) { //如果ISBN一样,且不是无效数据(如果没至少卖出一本就是无效的),把卖出的书数相加输出 if (data1.bookNo == data2.bookNo && data2.units_sold>0) { number = number + data2.units_sold; std::cout << " we are sold book of " << data2.bookNo << " for " << number << std::endl; } //如果ISBN不一样且卖出至少一本 else if (data1.bookNo != data2.bookNo && data2.units_sold > 0) {
data1.bookNo = data2.bookNo;
number = data2.units_sold;
std::cout << " we are sold book of " << data1.bookNo << " for " << number << std::endl; } //其余情况 else { std::cout << "data is not valid " << std::endl; } } } return 0; } /*what is “if”、“else if”、“else”? *if() *else if() *..... *else if() *else *这段代码的意思是,满足if的就进入if,满足else if的就进入else if,这两种都不满足的就进入else */
24行有疑问
这个程序那几个判断条件我是这样想的:
//Sales_data版书店程序,输入多条销售记录,每个记录包括ISBN、售出数、总销售价格,输出同ISBN的售出数、总销售价格、平均销售价格 //这程序有一个bug,如果连续3条或以上ISBN不一样,则输出就要重置了!即真正售出数、总销售价格、平均销售价格要把之前输出过的同ISBN的全部加起来才行。想不到办法解决这个问题了。
//和上个程序基本一样,只不过输出的地方多输出几个数据而已。 #include <iostream> #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; int main() { Sales_data data1, data2; if (std::cin >> data1.bookNo >> data1.units_sold >> data1.revenue) { //需要totalSold记录销售数,totalRevenue记录总销售额 int totalSold = data1.units_sold;
//注意这里必须用double,不能乱定义一个类型! double totalRevenue = data1.revenue; while (std::cin >> data2.bookNo >> data2.units_sold >> data2.revenue) { //如果ISBN一样,且不是无效数据(如果没至少卖出一本就是无效的),输出售出数、总销售价格、平均销售价格 if (data1.bookNo == data2.bookNo && data2.units_sold>0) { totalSold = totalSold + data2.units_sold; totalRevenue = totalRevenue + data2.revenue; std::cout << " we are sold book of " << data2.bookNo << " for " << totalSold << std::endl; std::cout << " The book fetched " << totalRevenue << " and average price is " << totalRevenue/ totalSold << std::endl; } //如果ISBN不一样且卖出至少一本 else if (data1.bookNo != data2.bookNo && data2.units_sold > 0) {
//重置要放在前面不能放后面,这样就避免了重复输出之前的结果,转而输出当前的结果 data1.bookNo = data2.bookNo; totalSold = data2.units_sold; totalRevenue = data2.revenue; std::cout << " we are sold book of " << data1.bookNo << " for " << totalSold << std::endl; std::cout << "The book fetched " << totalRevenue << " and average price is " << totalRevenue/ totalSold << std::endl; } //其余情况 else { std::cout << "data is not valid " << std::endl; } } } return 0; } //深刻体会到else在if中的强大之处!一旦把必须输出的写到if和else if中后,剩下的统统丢到else里去,一旦出现新的要考虑的东西再加一个else if就好了!