Primer C++第五版 读书笔记(一) (若有侵权请通知本人,将第一时间删文) 1.1-2.2 章节 关于C++变量初始化: 初始化不是赋值,初始化的含义是建立变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,以一个新值来替代. 定义一个名为a的int变量并初始化为0,有如下4种方法: int a = 0; int a = {0}; // 列表初始化 int a{0}; // 列表初始化 int a(0); 当列表初始化方法用于内置类型的变量时,若是初始值存在信息丢失的风险,则编译器将报错. 示例: long double ld = 3.1415926536; int a{ ld }, b{ ld }; // error C2397 从"long double"转换到"int"须要收缩转换 int c(ld), d = ld; // warning C4244: "初始化": 从"long double"转换到"int",可能丢失数据 定义变量的错误示范: double salary = wage = 9999.99; // error:没定义变量 wage std::cin >> int input_value; // 不容许 下列变量的初始值是什么? string global_str; // 全局变量,值为"" int global_int; // 全局变量,值为 0 int main() { int local_int; // 局部变量,是未定义状态,无心义数据 string local_str; // 哪怕它是局部变量,它也是string定义的,值为"" } 未初始化的变量可能引起运行时的故障. 建议初始化每个内置类型的变量.虽然并不是必须,但若是咱们不能确保初始化后程序安全,那么这么作不失为一种简单可靠的方法. 注意: 变量只能够被定义一次,但可被声明屡次. 若是想声明一外变量而非定义它,可在变量名前添加关键字 extern , 而不要显示地初始化变量,示例以下: extern int i; // 声明变量 i 而非定义它 int j; // 声明并定义变量 j 任何包含了显示初始化的声明即成为定义.咱们能给由extern关键字标记的变量赋一个初始值,但这么作就抵消了extern的做用. extern语句若是包含了初始值就再也不是声明,而变成了定义: extern double pi = 3.1415; // 定义 在函数体内部,若是试图初始化一个由extern关键字标记的变量,将引起错误. ============================================================================================ 2.3 C++复合类型: 引用: 引用(reference)为对象起了另一个名字,引用类型引用(refers to)另一种类型. int ival = 1024; int &refVal = ival; // refVal指向ival(是ival的另外一个名字) int &refVal2; // 错误!!引用必须被初始化 引用即别名:引用并不是对象,相反的,它只是为一个已经存在的对象所起的另一个名字. 为引用赋值,其实是把值赋给了与引用绑定的对象.获取引用的值,其实是获取了与引用绑定的对象的值. 同理,经引用做为初始值,其实是以与引用绑定的对象做为初始值. 由于引用自己不是一个对象,因此不能定义引用的引用. 绝大多数状况 下,引用的类型都要和与之绑定的对象严格匹配.并且,引用只能绑定到对象上,而不能与字面值或某个表达式的计算结果绑定在一块儿. int &refVal4 = 10; // 错误:引用类型的初始值必须是一个对象. const int &refVal4 = 10; // 正确 double dval = 3.14; int &reVal5 = dval; // 错误:引用类型要与与之绑定的对象严格匹配! 指针: 指针(pointer)是指向另一种类型的复合类型. 指针与引用相比有不少不一样点(重点): 0.引用是已经存在的对象的另外一个名称,而指针是一个对象,它遵循本身的使用规则. 1.指针自己就是一个对象,容许对指针赋值和拷贝,并且在指针的生命周期内它能够前后指向几个不一样的对象. 2.指针无须在定义时赋初值,和其余内置类型同样,在块做用域内定义的指针,若是没有被初始化,也将拥有一个不肯定的值.引用在定义时就必须初始化. 3.最大不一样:引用自己并不是一个对象,一旦定义了引用,就没法令其再绑定到另外的对象,以后每次使用这个引用都是访问它最初绑定的那个对象. int *ip1, *ip2; // ip1 和 ip2 都是指向 int 型对象的指针 double dp, *dp2; // dp2 是指向 double 型对象的指针,dp 是 double 型对象. 获取对象的地址: 指针存放某个对象的地址,想要获取该地址,须要使用取地址符(&): int ival = 42; int *p = &ival; // p 存放变量ival的地址,或者说p是指向变量ival的指针. 示例: double dval; double *a = &dval; // 正确:初始值是double型对象的地址 double *a2 = a; // 正确:初始值是指向double开进对象的地址 int * b = a; // 错误:指针b的类型与指针a的类型不匹配! int * b2 = &vdal; // 错误:试图把double型对象的地址赋给int型指针 由于在声明语句中指针的类型实际上被用于指定它所指向对象的类型,因此两者必须匹配.若是指针指向了一个其余类型的对象,对该对象的操做将发生错误. 指针值: 指针的值(即地址)应属下列4种状态之一: 1.指向一个对象 2.指向紧邻对象所占空间的下一个位置 3.空指针,意味着指针没有指向任何对象 4.无效指针,也就是上述状况以外的其余值. 试图拷贝或以其余方式访问无效指针的值都将引起错误.编译器并不负责检查此类错误,这一点和试图使用未经初始化的变量同样.所以程序员必须清楚任意给定的指针是否有效. 利用指针访问对象: 若是指针指向了一个对象,则容许使用解引用符(*)来访问该对象: int ival = 42; int *p = &ival; // p存放着变量ival的地址,或者说p是指向变量ival的指针. cout << *p; // 由符号*获得指针p所指向的对象,输出42. 对指针解引用会获得所指的对象,所以若是给解引用的结果赋值,实际上也就是给指针所指的对象赋值: *p = 0; cout<<*p; // 输出0. 解引用操做仅适用于那些确实指向了某个对象的有效指针. 空指针: 空指针不指向任何对象,在试图使用一个指针以前可先检查它是否为空,如下列出几个生成空指针的方法: int *p1 = nullptr; // 等价于 int *p1 = 0; 这是C++11新标准.nullptr是一种特殊类型的字面值,它能够被转化成任意其余的指针类型. int *p2 = 0; // 直接将p2初始化为字面常量0. // 下面方法须要首先#include cstdlib int *p3 = NULL; // 等价于 int *p3 = 0; 建议:初始化全部的指针!!! 建议初始化全部的指针,而且在可能的状况下,尽可能等定义了对象以后再定义指向它的指针.若是实在不清楚指针会指向何处, 就把它初始化为nullptr或者0,这样程序就能检测并知道它没有指向任何具体的对象了. 任何非0指针对应的条件值都是true.示例以下: int ival = 1024; int *pi = 0; int *pi2 = &ival; if(pi) // false if(pi2) // true void* 指针 void* 是一种特殊的指针类型,可用于存听任意对象的地址.咱们对该地址中究竟是什么类型的对象不清楚. 归纳来讲,以 void* 的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象. 问题:给定指针p,你能知道它是否指向了一个合法的对象吗?若是能?叙述判断思路,若是不能,说明缘由. 答:不能,由于须要更多的信息来肯定该指针是否有效. 定义多个变量: int* p; // 合法但容易产生误导 int *p1, p2; // p1是指向int的指针,p2是int类型 int *p1, *p2; // p1和p2都是指向int的指针(本书推荐) 指向指针的指针: int ival = 1024; int *pi = &ival; // pi指向一个int型的数 int **ppi = π // ppi指向了指针pi的地址 示例: int main() { int ival = 1024; int *pi = &ival; // pi指向一个int型的数 int **ppi = π // ppi指向了指针pi的地址 cout << "ival = " << ival << endl; // ival 1024 cout << "*pi = " << *pi << endl; // *pi = 1024 cout << "**ppi = " << **ppi << endl; // **ppi = 1024 cout << "pi = " << pi << endl; // pi = 000000F3B439F8B4 cout << "*ppi = " << *ppi << endl; // *ppi = 000000F3B439F8B4 cout << (*ppi == pi) << endl; // 1 return 0; } 指向指针的引用(难点): 引用自己不是一个对象,所以不能定义指向引用的指针.但指针是对象,因此存在对指针的引用. 示例以下: int main() // 这个示例的关键是p,r都存的是i的地址(即&i). { int i = 42; int *p = &i; // p是一个int型指针,它指向i,是变量i的地址. int *&r = p; // r是一个对指针p的引用 cout << "&r = " << &r << endl; // &r = 00000081A017FB18 cout << "&i == p == r 吗?下面开始打印: " << endl; cout << "&i = " << &i << endl; // &i = 00000081A017FAF4 cout << "p = " << p << endl; // p = 00000081A017FAF4 cout << "r = " << r << endl; // r = 00000081A017FAF4 *r = 0; // 解引用r获得i,也就是p指向的对象,将i的值改成0. cout << "*r = 0以后 i = " << i << endl; // *r = 0以后 i = 0 system("pause"); return 0; } ============================================================================================ 2.4 const限定符 const是一种类型修饰符,用于说明永不改变的对象.const对象一旦定义就没法再赋新值,因此必须初始化. const int bufSize = 512; //输入缓冲区大小 这样定义就把bufSize定义成了一个常量,任何试图为bufSize赋值的行为都将发生错误. 由于const对象一旦建立后其值就不能再改变,因此const对象必须初始化. 默认状态下,const对象仅在文件内有效. 若是想只在一个文件中定义const,而在其余多个文件中声明并使用它,须要作以下操做: 对于const变量无论是声明仍是定义都添加extern关键字,这样只须要定义一次就能够了. 注意:若是想在多个文件之间共享const对象,必须在变量的定义前添加extern关键字. const指针: 常量指针(const pointer)是一种指针,它的值永不改变. 容许把指针自己定为常量.常量指针(const pointer)必须初始化,并且一旦初始化,它的值(也就是存放在指针中的那个地址) 就不能改变了.把*放在const关键字以前用以说明指针是一个常量. 下面的定义声明哪些合法哪些不合法? int i, *const cp; // 不合法,cp必须被初始化 int *p1, *const p2; // 不合法,p2必须被初始化 const int ic, &r = ic; // 不合法,ic必须被初始化 const int *const p3; // 不合法,p3必须被初始化 const int *p; // 合法,p指针指向一个const int类型的数据 顶层const: 顶层const(top-level const)表示***指针自己是一个常量***,而底层const(low-level const)表示***指针所指的对象是一个常量***. 更通常的,顶层const能够表示任意的对象是常量,这一点对任何数据类型都适用.如算术类型,类,指针等. 底层const则与指针和引用等复合类型的基本类型部分有关.比较特殊的是,指针类型既但是顶层const,也但是底层const,这一点和其余类型区别明显: int i = 0; int *const p1 = &ri; // 不能改变p1的值,这是一个顶层const const int ci = 42; // 不能改变ci的值,这是一个顶层const const int *p2 = &ci; // 容许改变p2的值,这是一个底层const const int *const p3 = p2; // 靠右的const是顶层const,靠左的是底层const const int &r = ci; // 用于声明引用的const都是底层const 当执行拷贝时,常量是顶层const仍是底层const区别明显.其中顶层const不受什么影响: i = ci; // 正确:拷贝ci的值,ci是一个顶层cosnt,对此操做无影响 p2 = p3; // 正确:p2和p3指向的对象类型相同,p3顶层const的部分不影响. 执行拷贝操做并不会改变被拷贝对象的值,所以,拷入和拷出的对象是不是常量都没什么影响. 另外一方面,底层const的限制却不能忽视.当执行对象的拷贝操做时,拷入和拷出的对象必须具备相同的底层const资格,或者两个对象的数据类型必须能转换. 通常来讲,很是量能够转换成常量,反之则不行. int *p = p3; // 错误:p3包含底层const的定义,而p没有. p2 = p3; // 正确:p2和p3都是底层const p2 = &i; // 正确:int *能转换成const int * int &r = ci; // 错误,普通的int&不能绑定到int常量上 const int &r2 = i; // 正确:const int& 能够绑定到一个普通int上. p3既是顶层const也是底层const,拷贝p3时能够不在意它是一个顶层const,但必须清楚它指向的对象得是一个常量.所以,不能用p3去初始化p, 由于p指向的是一个普通的(很是量)整数.另外一方面,p3的值能够赋值给p2,是由于这两个指针都是底层const,尽管p3同时也是一个常量指针(顶层const), 仅就此次赋值不会有什么影响. constexpr(const expression)和常量表达式: 常量表达式(const expression)是指值不会改变而且在编译过程当中就能计算结果的表达式. const int max_files = 20; // max_files是常量表达式 const int limit = max_files+1; // limit是常量表达式 int staff_size = 27; // staff_size不是常量表达式,由于staff_size可能会被赋予其它值 const int sz = get_size(); // sz不是常量表达式,由于编译过程当中看不出get_size()是多少. constexpr变量: C++11新标准规定:容许将变量声明为constexpr类型以便由编译器来验证变量的值是不是一个常量表达式. 声明为constexpr的变量必定是一个常量,并且必须用常量表达式初始化: constexpr int mf = 20; // 20是常量表达式 constexpr int limit = mf +1; // mf+1是常量表达式 constexpr int sz = size(); // 只有当size是一个constexpr函数时才是一条正确的声明语句. 新标准容许定义一种特殊的constexpr函数,这种函数应该足够简单以使得编译时就能够计算其结果,这样就能用constexpr函数去初始化constexpr变量了. 通常来讲,若是你认定变量是一个常量表达式,那就把它声明成constexpr类型. 指针和constexpr: 必须明确一点:在constexpr声明中若是定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关: constexpr int *p = nullptr; // p是一个指向整数的常量指针.constexpr把它所定义的对象置为了顶层const. 与其余常量指针相似,constexpr指针既能够指向常量也可指向一个很是量; constexpr int *np = nullptr; // np是一个指向整数的常量指针,其值为空 int j = 0; constexpr int i = 42; // i的类型是整型常量 // i和j都必须定义在函数体以外 constexpr const int *p = &i; // p是常量指针,指向整型常量i constexpr int *p1 = &j; // p1是常量指针,指向整数j 2.5 处理类型: 类型别名: 有两种方法可用于定义类型别名,传统的方法是使用关键字typedef: typedef double wages; // wages是double的同义词 typedef wages base, *p; // base是double的同义词,p是double*的同一词 新标准规定了一种新的方法,使用别名声明(alias declaration)来定义类型的别名: using SI = Sales_item; // SI是Sales_item的同义词 指针,常量和类型别名: typedef char *pstring; // pstring是类型char *的别名 const pstring cstr = 0; // cstr是指向char的常量指针 const pstring *ps; // ps是一个指针,它的对象是指向char的常量指针 const pstring是指向char的常量指针,并不是指向常量字符的指针. auto类型说明符: auto定义的变量必须有初始值. // 由val1和val2相加的结果能够推断出item的类型 auto item = val1 + val2; // item初始化为val1和val2相加的结果 使用auto同时声明多个变量时,该语句中的全部初始基本数据类型都必须同样: auto i = 0, *p = &i; // 正确:i是整数,p是整型指针 auto sz = 0, pi = 3.14; // 错误:sz和pi的类型不一致 复合类型,常量和auto: 编译器推断出来的auto类型有时候和初始值的类型并不彻底同样,编译器会适当地改变结果类型使其更符合初始化规则. 首先,正如咱们所熟知的,使用引用实际上是使用引用的对象,特别是当引用被用做初始值时,真正参与初始化的实际上是引用对象的值. 此时编译器以引用对象的类型做为auto类型. int i = 0, &r = i; auto a = r; // a是一个整数(r是i的别名,而i是一个整数) 其次,auto通常会忽略掉顶层const,同时底层const则会保留下来,好比当初始值是一个指向常量的指针时: const int ci = i, &cr = ci; auto b = ci; // b是一个整数(ci的顶层const特性被忽略掉了) auto c = cr; // c是一个整数(cr是ci的别名,ci自己是一个顶层const) auto d = &i; // d是一个整型指针(整数的地址就是指向整数的指针) auto e = &ci; // e是一个指向整数常量的指针(对常量对象取地址是一种底层const) 若是但愿推断出的auto类型是一个顶层const,须要明确指出: const auto f = ci; // ci的推演类型是int,f是const int; 还能够将引用的类型设置为auto,此时原来的初始化规则仍然适用: auto &g = ci; // g是一个整型常量引用,绑定到ci auto &h = 42; // 错误,不能为很是量引用绑定字面值 const auto &j = 42; // 正确,能够为常量引用绑定字面值 设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留,和往常同样,若是咱们给初始值绑定一个引用,则此时的常量就不是顶层常量了. 要在一条语句中定义多个变量,切记,符号&和*只从属于某个声明符,而 非基本数据类型的一部分,所以初始值必须是同一种类型: auto k = ci, &l = i; // k是整数,l是整型引用 auto &m = ci, *p = &ci; // m是对整型常量的引用,p是指向整型常量的指针 auto &n = i, *p2 = &ci; // 错误:i的类型是int而&ci的类型是const int 示例: int main() { int i = 0, &r = i; auto a = r; // int a = 0; const int ci = i, &cr = ci; auto b = ci; // int b = 0; auto c = cr; // int c = 0; auto d = &i; // int *d = &i; auto e = &ci; // const int *e = &ci; const auto f = ci; // const int f = 0; auto &g = ci; // const int &g = 0; system("pause"); return 0; } decltype类型指示符: decltype做用是选择并返回操做数的数据类型.在此过程当中,编译器分析表达式并获得它的类型,却不实际计算表达式的值: decltype(f()) sum = x; // sum 的类型就是函数f的返回类型 编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型做为sum的类型.换句话说,编译器为sum指定的类型就是 假如f被调用的话就会返回的那个类型. decltype处理顶层const和引用的方式与auto有些许不一样.若是decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内): const int ci = 0, &cj = ci; decltype(ci) x = 0; // x的类型是const int decltype(cj) y = x; // y的类型是const int &, y绑定到变量x decltype(cj) z; // 错误:z是一个引用,必须初始化 由于cj是一个引用,decltype(cj)的结果就是引用类型,所以做为引用的z必须被初始化. 须要指出的是,引用历来都做为其所指对象的同义词出现,只有用在decltype处是一个例外. decltype和引用: 若是decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型. 有些表达式将向decltype返回一个引用类型,通常来讲当这种状况发生时,意味着该表达式的结果对象能做为一条赋值语句的左值: // decltype的结果能够是引用类型 int i = 42, *p = &i, &r = i; decltype(r+0) b; // 正确,加法的结果是int,所以b是一个(未初始化的)int decltype(*p) c; // 错误,c是int&,必须初始化. 若是表达式的内容是解引用操做,则decltype将获得引用类型. decltype和auto的另外一处重要区别是:decltype的结果类型与表达式形式密切相关.有一种状况特别要注意:对于decltype所用的表达式来讲, 若是变量名加上了一对括号,则获得的类型与不加括号会有不一样.若是decltype使用的是一个不加括号的变量, 则获得的结果就是该变量的类型,若是给变量加上一层或多层括号,编译器就会把它看成是一个表达式. 变量是一种能够做为赋值语句左值的特殊表达式,因此这样的decltype就会获得引用类型: // decltype的表达式若是是加上了括号的变量,结果将是引用 decltype((i)) d; // 错误,d是int&,必须初始化; decltype(i) e; // 正确,e是一个(未初始化的)int 切记:decltype((variable))(注意是双层括号)的结果永远是引用,而decltype(variable)的结果只有当variable自己就是一个引用时才是引用. 示例: 1.关于下面的代码,指出每一个变量的类型及程序结束时它们各自的值: int a = 3, b = 4; decltype(a) c = a; decltype((b)) d = a; ++c; ++d; c的类型是int,d的类型是int&,c与d的最终值都为4. 赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型.也就是说,若是i是int,则表达式i=x的类型就是int&. 2.指出下面的代码中每个变量的类型和值: int a = 3, b = 4; decltype(a) c = a; // c是int型. decltype(a = b) d = a; // d是int&类型 3.auto指定类型与decltype指定类型同样和不同的示例: int i = 0, &r = i; // 同样 auto a = i; // a是int型 decltype(i) b = i; // b是int型 // 不同 auto c = r; // c是int型 decltype(r) d = r; // d是int&类型 2.6 自定义数据类型 自定义数据类以关键字struct开始,紧跟着类名和类体(其中类体部分能够为空).类体由花括号包围造成一个新的做用域. 类内部定义的名字必须惟一,但能够与类外部定义的名字重复. struct Sales_data{ /*...*/ } accum, trans, *salesptr; // 与上一条语句等价,但可能更好一些 struct Sales_data { /*...*/ }; Sales_data accum, trans, *salesptr; 通常来讲,最好不要把对象的定义和类的定义放在一块儿,这么作无异于把两种不一样实体的定义混在了一条语句里.(不建议) 类数据成员: 类我是个定义类的成员,咱们的类只有数据成员(data member),类的数据成员定义了类的对象的具体内容,每一个对象有本身的一份 数据成员拷贝.修改一个对象的数据成员,不会影响其余的对象. 定义数据成员的方法和定义普通变量同样:首先说明一个基本数据类型,随后紧跟一个或多个声明符. C++11新标准规定,能够为数据成员提供一个类内初始值(in-class initializer).建立对象进,类内初始值将用于初始化数据成员. 没有初始值的成员被默认初始化. 编写本身的头文件: 类通常都不定义在函数体内,当在函数体外部定义类时,在各个指定的源文件中可能只有一处该类的定义.并且,若是要在不一样文件中使用 同一个类,类的定义就必须保持一致. 为了确保各个文件中类的定义的一致性,类一般被定义在头文件中,并且类所在头文件的名字应与类的名字同样.例如咱们应该把Sales_data类定义在名为Sales_data.h的头文件中. 头文件一般包含那些只能被定义一次的实体,如类,const和constexpr变量等.头文件也常常用到其余头文件的功能. 注意:头文件一旦改变,相关的源文件必须从新编译以获取更新过的声明. 预处理器概述: 确保头文件屡次包含仍能安全工做的经常使用技术是预处理器(preprocessor),它由C++语言从C语言继承而来.预处理器在编译以前执行一段程序, 能够部分地改变咱们所写的程序.以前已经用到了一项预处理功能#include. 当预处理器看到#include标记时就会用指定的头文件的内容代替#include. C++程序还会用到的一项预处理功能就是头文件保护符(header guard),头文件保护符依赖于预处理变量.预处理变量有两种状态:已定义和未定义. #define指令把一个名字设定为预处理变量,另外两个指令则分别检查某个指定的预处理变是否已定义: #ifdef:当且仅当变量已定义时为真, #ifndef:当且仅当变量未定义时为真. 一旦检查结果为真,则执行后续操做直至遇到#endif指令为止. 使用这些功能就能有效地防止重复包含的发生: #ifndef SALES_DATA_H #define SALES_DATA_H #include<string> struct Sale_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; #endif 第一次包含Sales_data.h时,#ifndef的检查结果为真,预处理器将顺序执行后面的操做直至遇到#endif为止. 此时,预处理变量SALES_DATA_H的值将变为已定义,并且Sales_data.h也会被拷贝到咱们的程序中来.后面若是再一次包含Sales_data.h, 则#ifndef的检查结果将为假,编译器将忽略#ifndef到#endif之间的部分. 警告:预处理变量无视C++语言中关于做用域的规则. 整个程序中的预处理变量包括头文件保护符必须惟一,一般的作法是基于头文件中类的名字来构建保护符的名字,以确保其惟一性. 为了与程序中的其余实体发生名字冲突,通常把预处理变量的名字所有大写. 头文件即便(目前还)没有被包含在任何其余头文件中,也应该设置保护符.头文件保护符很简单,程序员只要习惯性地加上就能够了,不必太在意你的程序到底需不须要. 自定义头文件,数据类型示例: 1.在当前项目的"头文件"文件夹内新建 Sales_data.h 文件,内容以下: #ifndef CH02_EX2_42_H_ #define CH02_EX2_42_H_ #include <string> #include <iostream> struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; void CalcRevenue(double price); double CalcAveragePrice(); void SetData(Sales_data data); void AddData(Sales_data data); void Print(); }; #endif 2.在当前项目的"源文件"文件夹内新建 Sales_data.cpp 文件,内容以下: #include<iostream> #include "Sales_data.h" void Sales_data::CalcRevenue(double price) { revenue = units_sold * price; std::cout << "CalcRevenue正在执行..." << "revenue = " << revenue << std::endl; } void Sales_data::SetData(Sales_data data) { bookNo = data.bookNo; units_sold = data.units_sold; revenue = data.revenue; std::cout << "SetData执行完毕..." << std::endl; } void Sales_data::AddData(Sales_data data) { if (bookNo != data.bookNo) return; units_sold += data.units_sold; revenue += data.revenue; std::cout << "AddData正在执行..." << std::endl; std::cout << "units_sold的值如今是: " << units_sold << std::endl; std::cout << "revenue的值如今是: " << revenue << std::endl; } double Sales_data::CalcAveragePrice() { if (units_sold != 0) { std::cout << "CalcAveragePrice正在执行..." << "AveragePrice的值为: " << revenue / units_sold << std::endl; return revenue / units_sold; } else return 0.0; } void Sales_data::Print() { std::cout << "Print正在执行..." << "bookNo = " << bookNo << ", " << "units_sold = " << units_sold << ", " << "revenue = " << revenue << std::endl; double averagePrice = CalcAveragePrice(); if (averagePrice != 0.0) std::cout << "averagePrice = " << averagePrice << std::endl; else std::cout << "(no sales)" << std::endl; } 3.在当前项目的"源文件"文件夹内新建 main.cpp 文件,内容以下: #include<iostream> #include"Sales_data.h" using namespace std; #endif int main() { Sales_data total; double totalPrice; std::cout << "请依次输入bookNo, units_sold, totalPrice这三个变量,以空格做为分隔符,以回车符结束: " << std::endl; if (std::cin >> total.bookNo >> total.units_sold >> totalPrice) { total.CalcRevenue(totalPrice); Sales_data trans; double transPrice; while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice) { trans.CalcRevenue(transPrice); if (total.bookNo == trans.bookNo) { total.AddData(trans); } else { total.Print(); total.SetData(trans); } } total.Print(); return 0; } else { std::cerr << "No data?!" << std::endl; return -1; // indicate failure } system("pause"); return 0; } 编译执行便可. ============================================================================================