C++ Prime 第二章 变量和基本类型
2022-11-23~2022-12-25
2.1 基本内置类型
- 算数类型:字符、整型数、布尔值、浮点数
- 空类型:不对应具体的值,当函数不返回任何值时使用空类型作为返回类型
2.1.1 算数类型
- 算数类型分为: 整型(bool、char、wchar_t、char16_t、char32_t、short、int、long、longlong) 浮点型(float、double、long double)
- 字节:可寻址的最小内存(通常由8比特构成)
- 字:存储的基本单元(通常由几个字节组成,通常由4字节、8字节构成)
- 带符号类型: int、short、long、longlong
2.1.1节练习
- 练习2.1:short(2)与int(4)、long(4)、longlong(8)所占的字节数不同,无符号类型:仅能表示大于0的值,带符号类型:能表示0、负数、整数。float(4)和double(8)所占字节数不同,保留的有效位数也不同(7和16位)
- 练习2.2:利率、付款使用float或double,本金使用int或者long
2.1.2 类型转化
- 将无符号类型与有符号类型进行算数运算时,有符号类型会转化为无符号类型
2.1.2 节练习
- 练习2.3
32、 4,294,967,264、 32、 -32、 0、 0
- 练习 2.4:done
2.1.3 字面值常量
- 十进制:20
- 八进制:024
- 十六进制:0x14
- 字符字面值:'a'
- 字符串字面值:'Hello World!'
- 字符串字面值:由常量字符构成的数组array,编译器在字符串的结尾处添加一个空字符'\0',所以字符串字面值的实际长度要比它的内容多1
- 浮点型:表现为一个小数或以科学计数法表述的指数如下:3.14159、 3.14159E0、0.、0e0、.001,默认浮点型字面值是一个double
2.1.3 节练习
- 练习 2.5: (a):字符字面值、宽字符字面值、字符串字面值、宽字符串字面值 (b):十进制整型、十进制无符号整型、十进制长整型、 十进制无符号长整型、 八进制整型、 十六进制整型 (c):double、 float、 long double (d):十进制整型、 十进制无符号整型、 double、 double
- 练习 2.6:第一组正常的十进制数、第二组存在不正常的八进制数
- 练习 2.7: (a):"Who goes with Fergus?\n", 字符串类型 (b):31.4, long double (c):无效,后缀f只能用于浮点型 (d):3.14 long double
- 练习 2.8:
int main ()
{
cout << "\62\115\012" << endl;
cout << "\62\t\115\012" << endl;
system("pause");
return 0;
}
2.2 变量
- 变量具有数据类型,数据类型决定变量所占空间大小和布局方式、该空间能存储值的范围、以及变量能够参与的运算
2.2.1 变量的定义
- 类型说明符 + 一个或多个变量名组成的列表,变量名以逗号分隔,以分号结尾 int a, b, c;
- 初始化列表: int a {1}; 其中初始化列表中的值不能存在丢失信息风险不然会报错,如下:
using namespace std;
#include<iostream>
int main ()
{
long double ld = 3.1415926536;
int a{ ld }, b = { ld }; // 从long double类型转化到int类型存在丢失信息风险
system("pause");
return 0;
}
- 初始化不等于赋值
- 初始化:创建变量时赋予一个初始值
- 赋值:1、擦除当前变量的值 2、以一个新值来替代
- 默认初始化:如果定义变量时没有指定初值,变量将被默认初始化 1、如果变量(内置类型)被定义在任何函数体之外,变量被初始化为0 2、如果变量(内置类型)被定义在函数体内,变量将不会被初始化,它的值是未定义的。 对于类而言:每个类都有各自的初始化方式,绝大多数类无需显示初始化就定义对象。如string
using namespace std;
#include<iostream>
int main ()
{
string empty;
cout << empty << endl;// 输出一个空字符串
system("pause");
return 0;
}
2.2.1 节练习
- 练习 2.9: (a)需要先定义再使用
using namespace std;
#include<iostream>
int main ()
{
int input_value;
cin >> input_value;
system("pause");
return 0;
}
(b)存在信息丢失风险 double i = {3.14}; (c)double salary, wage = 9999.99; (d)不报错,但是小数点后面的数据将丢失,可以改成浮点数类型 double i = 3.14;
- 练习 2.10: global_string是空字符串、global_int = 0、local_int是未定义的、local_str是空字符串
2.2.2 变量声明和定义的关系
声明:让变量的名字被程序知道 定义:负责创建与名字相关的实体
- 声明一个变量但不定义它,使用extern关键字
- 在函数体内部初始化一个extern标记的变量时会报错
using namespace std;
#include<iostream>
int a; // 1、声明且定义为0
extern int c = 1; //3、声明且定义extern关键字无效
int main ()
{
extern int b; // 2、声明一个变量但不定义
//extern int d = 2; // 在函数体内部初始化一个extern标记的变量时会报错
cout << a << endl;
cout << c << endl;
system("pause");
return 0;
}
2.2.2 节练习
- 练习2.11: (a)在函数体外属于定义,在函数体内会报错 (b)在函数体内属于声明,在函数体外属于定义(初始化为0) (c)声明
2.2.3 标识符
- 必须使用字母或者下划线开头
- 有字母、下划线、数字组成
- 变量名用小写
- 自定义类名,大写开头
- 对多个单词组成的标识符单词之间需要有明显区分
2.2.3 节练习
- 练习 2.12 (a)使用C++的关键字作为标识符 (c)使用C++的关键字作为标识符,并且变量未经定义就使用 (d)不能使用数字作为开头
2.2.4 名字的作用域
- 嵌套的作用域中,内层可以访问外层的变量、也可以在内层重新定义该变量。如下
using namespace std;
#include<iostream>
int resued = 42;
int main ()
{
cout << resued << endl; // 访问外层作用域的变量
int resued = 10; // 在内层定义与外层作用域名字相同的变量
cout << resued << endl; // 访问局部变量
cout << ::resued << endl; // 访问外层变量
system("pause");
return 0;
}
2.2.4 节练习
- 练习2.13:j值=100
- 练习2.14:合法
2.3.1 引用
- 引用必须被初始化 变量初始化和引用初始化区别:
- 变量:将初始值拷贝到新建的对象中
- 引用:将初始值与引用绑定到一起,并且引用无法重新绑定,所以必须初始化
- 引用的初始值必须是一个对象而非字面值
2.3.1 节练习
- 练习 2.15: (b)不合法,引用的初始值必须是一个变量而非字面值 (c)不合法,ival没有被定义 (d)不合法,引用必须初始化
- 练习 2.16 都合法
- 练习 2.17 10, 10
2.3.2 指针
- 指针本身是一个对象,允许对指针进行赋值和拷贝
- 指针无需初始化,如果在块作用域内指针没有被初始化,也将拥有一个不确定的值
- 指针的值只属于4种状态:1、指向一个对象 2、指向紧邻对象所占空间的下一个位置 3、空指针 4、无效指针
- 空指针:
int *p1 = nullptr; // 第一种,C++11推荐,等价于int *p1 = 0;
int *p2 = 0; // 第二种,直接赋值给字面量0
int *p3 = NULL; // 第三种, 等价于int *p3 = 0;
- 赋值和指针: 赋值永远改变的是等号左侧的对象,看等号左侧的对象是什么,是地址就是改变指向、是值就是改变值
- 对指针进行比较:对于两个类型相同的指针可以使用==或者!=来比较他们
- void* 指针:可以存放任意对象的地址,不要直接操作void* 所指向的对象,因为我们不知道这个对象到底是什么类型的对象就无法确定能在这个对象上进行哪些操作
2.3.2 节练习
- 练习 2.18:
using namespace std;
#include<iostream>
int main ()
{
int a = 10, b = 20;
int* p = &a;
p = &b; // 改变指针的值
*p = 30; // 改变指针所指对象的值
system("pause");
return 0;
}
- 练习 2.19: 1、指针无需初始化、引用必须初始化 2、指针可以指向任意对象,但是引用一旦初始化后就无法重新绑定地址 3、引用是一个对象的别名,但是指针本身就是一个对象
- 练习 2.20:
using namespace std;
#include<iostream>
int main ()
{
int i = 42; // 定义一个变量,并初始化
int* p1 = &i; // 定义一个指针,并初始化
*p1 = *p1 * *p1; // 修改指针所指向对象的值
system("pause");
return 0;
}
- 练习 2.21: (a)非法,指针类型与初始化对象的数据类型不一致 (b)非法,指针初始化时必须接受一个地址类型的对象进行初始化
- 练习 2.22: 如果p是一个空指针那么... 如果p指向的对象的值不等于0,那么...
- 练习2.23: 不能,因为首先要确定这个指针是不是合法的,才能判断它所指向的对象是不是合法的。
- 练习2.24: lp的指针类型是long与初始化对象的类型不一致,void* 可以接受任意对象的地址所以合法。
- 指针中的* 修饰的是变量标识符,并不是数据类型
- 指向指针的引用:引用本身不是一个对象,因此不能定义指向引用的指针。但是指针是对象,所以可以创建指向指针的引用
using namespace std;
#include<iostream>
int main ()
{
int* p; // 定义一个指针
int*& r = p; // 定义一个指向指针的引用
int a = 10;
r = &a; // 让p指向a
cout << *r << endl;
cout << *p << endl;
system("pause");
return 0;
}
2.3.3 节练习
- 练习 2.25: (a)ip是指向int类型的指针,i是一个整型变量,r是i引用 (b)i是一个int类型的变量,ip是指向int类型的空指针 (c)ip是指向int类型的指针,ip2是一个整型变量
2.4 const限定符
-
const关键字修饰的变量无法被修改,const修饰的变量必须初始化
-
八股:默认状态下const修饰的对象仅在文件内生效,不同文件下的同名const变量等于不同的独立变量。 const修饰的对象仅仅在文件内生效的原因:编译器在编译程序代码的过程中会把const修饰的变量全部替换成const变量初始化是设定的值,如果程序代码包含了多个文件,为了保证这个替换过程的顺利执行,必须在每一个使用过const修饰变量的文件中都要有这个const修饰变量的定义,这时如果const修饰的作用域不是仅作用于单个文件中,那么就是触发重复定于变量的错误,(c++中一个变量只能允许定义一次)所以const修饰变量的作用域是仅在单个文件中生效。 如果在const修饰的变量的初始值不是一个常量表达式,并且我们希望它能够在文件间被全局共享,关键字extern可以解决这个问题,不论声明还是定义const变量时都加上extern关键字
extern const int bufsize = fcn();
extern const int bufsize; // 使用file_1.cc中定义的bufsize变量
2.4 节练习
- 练习2.26: (a)不合法,const修饰的变量必须进行初始化 (b)合法 (c)合法(默认认为cnt变量: int cnt = 0;) (d)不合法,++cnt合法,++sz不合法,sz是一个int类型的常量对象,常量不允许被修改
2.4.1 const引用
- 可以把引用绑定到const对象上,与普通常量一样,常量引用的值也不允许被改变
const int ci = 1024;
const int &r1 = ci; // 定义一个常量引用
r1 = 20; // 错误,常量不允许被修改
int &r2 = ci; // 错误,非int类型的常量引用无法接受一个int类型常量对象,反推:如果语句成立那么r2是一个普通引用即可以修改引用对象的值,即可以修改常量对象
- 引用的类型必须与与之绑定的对象类型相同(初始化常量引用时除外)
- 引用必须绑定一个对象而不是一个字面值或者一个计算的数值结果(初始化常量引用时除外)
- 常量引用可能引用了一个非常量的对象(通过常量引用初始化时进行绑定)
2.4.2 指针和const
- 指针与普通变量
- 指向常量的指针
- 允许一个指向常量的指针指向一个非常量对象
- 常量指针
- 允许常量指针指向一个非常量对象
- 指向常量的常量指针
2.4.2 节练习
- 练习 2.27: (a)不合法,引用初始化不合法,可修改成 int a = -1, &r = a; (b)不合法,引用i2未定义 (c)合法 (d)不合法,引用i2未定义 (e)不合法,引用i2未定义 (f)不合法,指向常量的常量引用必须初始化 (g)i如果=-1合法,如果不等于-1则i未定义就不合法
- 练习 2.28: (a)不合法,声明了一个int类型的变量i,定义了一个int类型的常量指针cp但未初始化,所以不合法 (b)不合法,声明了一个int类型指针p1,定义了一个int类型的常量指针p2但未初始化,所以不合法 (c)不合法,声明了一个int类型的常量ic但未加上extern关键字所以不合法,定义了一个常量引用r与ic绑定,不合法 (d)不合法,定义了一个指向常量的常量指针p3但未初始化,所以不合法 (e)合法,定义了一个指向常量的指针p
- 练习 2.29: (a)合法,i是一个int类型的变量可以接收一个常量变量(ic) (b)不合法,p1是一个int类型的指针不可以接收一个指向常量的对象的int类型的常量指针(p3) (c)不合法,p1是一个int类型的指针不可以接受一个int类型的常量地址(&ic) (d)不合法,p3是一个指向常量对象的int类型的常量指针,仅在初始化时才可以接受一个int类型的常量地址(&ic) (e)不合法,p2是一个int类型的常量指针,仅在初始化时才可以接受一个int类型的指针(p1 ) (f)不合法,ic是一个常量变量初始化后就不可以修改
2.4.3 顶层const
- 顶层const:表示任意对象是一个常量(对象本身的地址能被改变的都不是顶层const)
- 底层const:表示任意对象的数据的基本类型有关
- 对底层const进行拷贝操作时,拷贝与被拷贝的对象必须有相同的底层const资格,或者两个对象的数据类型可以进行转换
2.4.3 节练习
- 练习 2.30:
const int v2 = 0; //顶层const
int v1 = v2; //无
int *p1 = &v1, &r1 = v1; //p1是底层const,r1是顶层const
const int *p2 = &v2, *const p3 = &i, &r2 = v2; //p2底层cosnt,p3右边顶层const,左边是底层const,r2用于声明的const是底层const,r2本身是一个顶层const
- 练习 2.31: r1 = r2; 合法,r1是一个顶层const,所以r1的值是可以改变 p1 = p2; 不合法,p2拥有底层const,p1没有底层const,const类型不相同 p2 = p1; 合法,p2是一个底层const,所以p2的地址可以改变 p1 = p3; 不合法,两者的底层const类型不相同 p2 = p3; 合法,两个拥有相同的底层const(在练习2.30中i的定义没有给出,p3本身其实是不合法的)
2.4.4 constexpr和常量表达式
- 常量表达式:值不会改变并且在编译过程就能得到计算结果的表达式,是不是常量表达式由它的数据类型和初始值共同决定 const int max_files = 20; // 是一个常量表达式 const int limit = max_files + 1; // limit是一个常量表达式 int staff_size = 27; // staff_size不是一个常量表达式,非const int const int sz = get_size() // sz不是一个常量表达式,具体数值需要等运行时才知道而非编译
- constexpr声明的变量一定是一个常量,并且必须使用常量表达式初始化 constexpr int mf = 20; constexpr int limit = mf + 1;
- constexpr修饰指针时,指针的初始值必须时nullptr或者0,或者是存储与某个固定地址中的对象
- constexpr修饰指针时,仅修饰指针不修饰数据类型 const int* p = nullptr; // 指向整型常量的指针 constexpr int* p = nullptr; // 指向整数的常量指针
2.4.4 节练习
- 练习 2.32: 不合法
int null1 = 0, * p = &null1;
2.5.1 类型别名
- 使用关键字typedef来定义类型别名 typedef double wages; -->wages是double的同义词 typedef wages base; -->base是double的同义词 typedef base * p; -->p是double* 的同义词
- 使用别名声明来定义类型别名 using Si = Sales_item;
- 类型别名指代的是复合类型或常量时并且使用类型别名进行声明时要注意,不要把类型别名还原成原来的样子进行理解
typedef char * pstring;
const pastring cstr = 0;-->const *char ctr = 0; 而不是const char *ptr = 0;
2.5.2 auto类型说明符
- 当变量的数据类型不确定时可以使用auto关键字让编译自动分析初始值来推算变量的所属的数据类型,auto定义的变量必须有初始值 auto i = 2.14; auto j = 3.14 + 3;
- 当auto声明多个变量时,一条声明语句中只能有一个基本的数据类型
auto i = 0, *p = &i; // int和int类型指针
auto sz =0, pi = 3.14; // 错误,sz是int,pi是double
- auto根据初始值进行数据类型推导时会忽略初始值的顶层const,保留底层const
- 对常量对象进行取址操作是一种底层const
int i = 0, &r = i;
auto a = r;
const int ci = i;
const int &cr = ci;
auto b = ci; // b是一个整数类型,进行推导时忽略了ci的顶层const
auto c = cr; // c是一个整数类型,cr是ci的别名,进行推导时忽略的ci的顶层const
auto d = &i; // d是一个整型指针
auto e = &ci; // 对常量对象取址操作是一种底层const,&ci-->const int* ci,推导时底层const被保留,d是一个指向int类型常量的指针
- 设置一个类型是auto的引用时,对象的顶层const被保留
auto &g = ci; // g是一个常量引用
- (auto)在一条语句中声明多个变量时,符号&、* 只从属于某个声明符,而非基本数据类型的一部分,因此初始值必须是同一种类型
2.5.2 节练习
- 练习 2.33: a是int,a=42通过 b是int,b=42通过 c是int,c=42通过 d是整型指针,d=42错误 e是指向常量的指针,e=42错误 g是一个常量应用,g=42错误
- 练习 2.34:都正确
using namespace std;
#include<iostream>
int main()
{
int i = 0, & r = i;
auto a = r;
const int ci = i;
const int& cr = ci;
auto b = ci; // b是一个整数类型,进行推导时忽略了ci的顶层const
auto c = cr; // c是一个整数类型,cr是ci的别名,进行推导时忽略的ci的顶层const
auto d = &i; // d是一个整型指针
auto e = &ci; // 对常量对象取址操作是一种底层const,&ci-->const int* ci,推导时底层const被保留,d是一个指向int类型常量的指针
auto& g = ci; // g是一个常量引用
cout << a << endl;
a = 42;
cout << a << endl;
cout << b << endl;
b = 42;
cout << b << endl;
cout << c << endl;
c = 42;
cout << c << endl;
cout << d << endl;
d = 42; // 报错
cout << d << endl;
cout << e << endl;
e = 42; // 报错
cout << e << endl;
cout << g << endl;
g = 42; // 报错
cout << g << endl;
system("pause");
return 0;
}
- 练习 2.35:
using namespace std;
#include<iostream>
int main()
{
const int i = 42; // 整型常量
auto j = i; // auto推导时忽略i顶层const,所以j是整型
const auto& k = i; // auto推导时忽略i顶层const,k是一个整型常量引用
auto* p = &i; // i是一个整型常量,对一个常量对象进行取址操作时一个底层const,auto推导时保留底层const,p是一个指向常量的整型指针
const auto j2 = i, & k2 = i;// auto推导时忽略i顶层const,auto推导出为整型,加上const,j2时一个整型常量。k2是一个常量引用
j = 42;
cout << j << endl;
k = 10; // 报错
i = 20; // 报错
*p = 20; // 报错
p = &j;
j2 = 100; // 报错
k2 = 10; // 报错
i = 20; // 报错
system("pause");
return 0;
}
2.5.3 decltype类型指示符
- 希望从表达式的类型推断出要定义的变量的类型但不想要该表达式的值作为初始值
const int ci = 0, &cj = ci;
decltype(ci) x = 0; //decltype返回的数据类型是const int,所以x的类型是const int
- 引用从来都是作为所指对象的同义词出现的(代表所绑定的对象的值),只有用在decltype处时代表的是引用本身
const int ci = 0, &cj = ci;
decltype(cj) x = 1; // cj是一个引用,所以decltype返回的数据类型是const int&,所以x的类型是const int&, x绑定到0
- 如果decltype使用的表达式不是一个变量,那decltype返回的数据类型=表达式结果的数据类型
int i = 42, *p = &i, &r = i;
decltype(r + 0) x; // r+0的结果是42,所以decltype返回的数据类型是int,x的数据类型是int
- 如果decltype使用的表达式是一个解引用,那decltype返回的数据类型是引用本身
const int i = 42, *p = &i, &r = i;
decltype(*p) x = 20; // *p是一个解引用,所以decltype返回的是引用本身即const int&
- 如果decltype使用的表达式是一个指针,那decltype返回的指针本身的数据类型
int i = 42, *p = &i, &r = i;
decltype(p) x; // p是一个指针,所以decltype返回的是指针本身即int*
- 对于decltype的表达式来说如果变量加上了(),那么编译器认为这个变量是一个表达式,对于一个变量来说,变量是可以作为赋值语句左值的特殊表达式,相当于这个变量成为了另一个对象的别名(即产生了引用),所以decltype((x))转化成decltype(引用),x的类型即引用的类型
2.5.3 节练习
- 练习 2.36: a是int,值等于4 b是int,值等于4 c是int,值等于4 d是int&,值等于4
- 练习 2.37: a是int,值等于3 // decltype(a = b)中,a=b并没有执行,或者a=b被执行了但是a、b是都是临时变量并不是外部的a、b b是int,值等于3 // decltype(a = b)中,a=b并没有执行,或者a=b被执行了但是a、b是都是临时变量并不是外部的a、b c是int,值等于4 d是int&,值等于3 // decltype(a = b)中,a=b并没有执行,或者a=b被执行了但是a、b是都是临时变量并不是外部的a、b
- 练习2.38: auto是根据初始值的数据类型来进行变量的数据类型的推导 decltype是根据表达式的数据类型进行变量的数据类型的推导 一致: 不一致:
2.6.1 定义Sales_date类型
- 使用stuct定义一个类
struct Sales_date {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
}
- 数据成员:每个对象都有自己的一份数据成员拷贝,数据成员未提供初始值的都将被默认初始化为0,string类型被初始化成空字符串
2.6.1 节练习
-
练习2.39: 报错,提示输入分号
-
练习2.40: 暂无不同,同上定义
2.6.2 节练习
- 练习2.41:
- 重写练习1.21:
using namespace std;
#include<iostream>
#include"Sales_date.h"
using namespace std;
int main()
{
Sales_date data1, data2;
double price;
cin >> data1.bookNo >> data1.units_sold >> price;
data1.revenue = data1.units_sold * price;
cin >> data2.bookNo >> data2.units_sold >> price;
data2.revenue = data2.units_sold * price;
if (data1.bookNo == data2.bookNo)
{
int totalSold = data1.units_sold + data2 .units_sold;
if (totalSold != 0)
{
// 输入ISBN、总销售量、总销售额、平均价格
double totalRevnue = data1.revenue + data2.revenue;
double avgPrice = totalRevnue / totalSold;
cout << data1.bookNo << " " << totalSold << " " << totalRevnue << " " << avgPrice << endl;
}
else
{
cout << "no sales..." << endl;
}
}
system("pause");
return 0;
}
- 重写练习1.22-1.24:
using namespace std;
#include<iostream>
using namespace std;
struct Sales_date {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sales_date data1;
double price;
if (cin >> data1.bookNo >> data1.units_sold >> price)
{
data1.revenue = data1.units_sold * price;
Sales_date data2;
while (cin >> data2.bookNo >> data2.units_sold >> price)
{
if (data1.bookNo == data2.bookNo)
{
data1.units_sold += data2.units_sold;
data1.revenue += (price * data2.units_sold);
}
else
{
cout << "ISBN: " << data1.bookNo << " total_sold: " << data1.units_sold
<< " total_revenue: " << data1.revenue << " avgPrice: " << data1.revenue / data1.units_sold << endl;
data1.bookNo = data2.bookNo;
data1.units_sold = data2.units_sold;
data1.revenue = data2.units_sold * price;
}
}
cout << "ISBN: " << data1.bookNo << " total_sold: " << data1.units_sold
<< " total_revenue: " << data1.revenue << " avgPrice: " << data1.revenue / data1.units_sold << endl;
}
system("pause");
return 0;
}
2.6.3 编写自己的头文件
- 预处理器作用:确保头文件多次包含仍能安全工作。预处理器在编译之前执行的一段程序,可以部分的改变我们所写的程序
- 预处理功能:#include:当预处理器看到#include时会使用指定的头文件内容替代#include
- 预处理功能:头文件保护符:头文件保护符依赖于预处理变量: #define:定义一个预处理变量 #ifdef:检查某个指定的预处理变量是否已定义,若已定义则判断为真,执行#indef到#endif这段内容代码,如果为否就不执行 #ifndef:检查某个指定的预处理变量是否未定义,若未定义则判断为真,执行#ifndef到#endif这段内容代码,如果为否就不执行
- 头文件保护符必须唯一,一般做法:基于头文件中的类名称来构建保护符的名字(大写)
2.6.3 节练习
- 练习: 观察21页的书店程序发现: if (std::cin >> total);-->1、需要重载右移运算符 total.isbn();-->2、需要实现isbn公共方法 total += trans; -->3、需要重载+=运算符 std::cout << total << std::endl;-->4、需要重载左移动运算符 重写后的Sales_date.h头文件:
#ifndef SALES_ITEM_H
#define SALES_ITEM_H
#include <string>
#include <iostream>
using namespace std;
struct Sales_date {
// 1、需要重载右移运算符
friend istream& operator>>(istream& cin, Sales_date& s);
// 4、需要重载左移动运算符
friend ostream& operator<<(istream& cin, const Sales_date& s);
// 2、需要实现isbn公共方法
string isbn() {
return this->bookNo;
}
// 3、需要重载+=运算符
Sales_date& operator+=(const Sales_date& s) {
this->units_sold += s.units_sold;
this->revenue += s.revenue;
return *this;
};
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// 重载右移运算符
istream& operator>>(istream& cin, Sales_date& s) {
double price;
cin >> s.bookNo >> s.units_sold >> price;
s.revenue = price * s.units_sold;
return cin;
}
// 重载左移运算符
ostream& operator<<(ostream& cout, const Sales_date& s) {
cout << "ISBN: " << s.bookNo << " total_sold: " << s.units_sold
<< " total_revenue: " << s.revenue << " avgPrice: " << s.revenue / s.units_sold << endl;
return cout;
}
// 简单测试重载右移运算符
void test01() {
Sales_date data1;
cin >> data1;
cout << data1.bookNo << data1.units_sold << data1.revenue << endl;
}
// 简单测试isbn方法
void test02() {
Sales_date data1;
data1.bookNo = "ISBN:123456789";
data1.isbn();
}
// 简单测试重载+=
void test03() {
Sales_date data1, data2;
data1.revenue = 100;
data1.units_sold = 100;
data2.revenue = 200;
data2.units_sold = 200;
data1 += data2;
cout << data1.revenue << data1.units_sold << endl;
}
// 简单测试左移运算符
void test04() {
Sales_date data1;
cin >> data1;
cout << data1 << endl;
}
#endif
可以正常运行21页的书店程序:
#include "Sales_date.h"
int main()
{
//test01();
//test02();
//test03();
//test04();
Sales_date total;
if (cin >> total)
{
Sales_date trans;
while (cin >> trans)
{
if (total.isbn() == trans.isbn())
{
total += trans;
}
else
{
cout << total << endl;
total = trans;
}
}
cout << total << endl;
}
system("pause");
return 0;
}
C++Prime学习笔记 文章被收录于专栏
勇敢和愚蠢只有一剑之差