C++ Primer 笔记+习题解答(七)

来源:互联网 发布:民间借款记账软件 编辑:程序博客网 时间:2024/06/08 00:12

今天是第七篇笔记,主要是类相关的基础知识。其实越往后看越发觉得翻译的质量略有待考证。不知是我水平不够理解有误,还是译者笔下误。总之有的地方讲的着实混乱,看的着实头大。昨天看了些大牛的文章,自卑感油然而生。有时候,不得不承认,信息负载的确不是好事。当你坚持做一件事情时,不妨做一个井底之蛙。等你做完了事情,出来观天下吧。

若有错误 请指正 谢谢

0.引言:

  • 1.我们使用类来自定义自己的数据类型,通过自定义自己的数据类型来反映待解决问题中的各种概念。其实学到这个地方才是C++独有的地方,前面学的很多东西都在C中有迹可寻。

  • 2.数据抽象:数据抽象是一种依赖于接口和实现分离的编程设计艺术。

  • 3.类的基本思想:数据抽象和封装。

  • 4.接口:类的使用者可以执行的操作。

  • 5.实现:类的私有数据成员,负责接口实现的函数,也包括私有成员函数。

  • 6.封装:隐藏类的实现细节。

  • 7.抽象数据类型:通过数据抽象和封装实现抽象数据类型。

  • 8.隐式内联:一般来说,定义在类内部的函数是隐式内联函数。

  • 9.声明和定义:必须在类内声明,可以在体内或者体外定义。

  • 10.this指针:指向自身的指针。正常是常量指针,意味着定义的时候必须初始化,无法进行赋值操作。此外,这个指针是隐式定义的。

  • 任何自定义名为this的变量或者参数都是非法的。

  • 11.const成员函数:修改this指针的类型。由原先的常量指针修改为指向常量的常量指针。因为在const成员函数内部是无法修改普通数据成员的,当然可变数据成员是例外。

  • 12.类作用域:定自身定义了一个作用域。即使函数定义在数据成员之前也是合法的。因为类会先处理声明然后在处理函数。在类外定义函数时,要指明范围,当然如果是const成员函数,const也要指明。

  • 13.与类相关的成员函数:一般是指友元函数,属于接口的组成部分,但是不属于类,顾名思义,类的朋友吧。

  • 14.构造函数:定义一种函数对对象进行初始化。此函数无返回类型,函数名同类名相同,可以重载,但是不能是const的,理由很简单,因为肯定要对成员进行初始化或者赋值。

  • 当我们创建一个const对象时,直到构造函数完成工作才会获得const属性。

  • 如果类未定义构造函数,编译器会在合适的情况下合成一个构造函数。具体的请翻看深入探索C++对象模型专栏,讲的很详细。

  • 15.=default的含义:C++11中声明默认构造函数的方式,=default作为声明的一部分出现。

  • 16.构造函数初始值列表:形参列表后,花括号前的冒号部分。

1.访问控制与封装:

  • 1.访问权限说明符:public,private.可以出现0个或者多个。

  • 2.关键字class和struct的区别:唯一区别就是默认权限。class默认是private,struct默认是public。

  • 3.友元相关:允许其他函数或者类成为类的友元,可以访问类的私有成员。

友元只能声明在类的内部,位置不限。友元不是类的一部分,不受访问权限的控制。

若希望类的用户可以调用友元函数,那么除了在类的内部声明一次之外,还需要在类外在进行一次专门的声明,不过有的编译器不限制。原因是友元只是指定了朋友这个关系,并不是一般意义上的函数声明,故需要二次声明。

2.类的其他特性:

  • 1.可以在类内部定义类型别名,但是受到访问权限的控制。

  • 2.inline关键字:类内部外部都可以出现,但最好只在外部写上inline。

  • 3.可变数据成员:在声明之前加上mutable。顾名思义,可变的,永远不是const的,即使在const成员函数内部也可以修改其值。

  • 4.基于const的重载:const成员函数和非const函数不同,算是一种重载。

  • 5.对于公共代码建议使用私有功能函数,可以减少代码的重复。比如以前看见的在类内设置一个函数来设置数据成员,然后在构造函数中直接调用这个函数。虽然不一定比类内初始值列表好用。

  • 6.前向声明:只给出类的声明,不给定义。此时的类是不完全类型,只可以定义这个类的指针或引用类型。

  • 7.友元函数:普通的友元函数,类是友元,类的成员函数是友元。友元的关系不具有传递性。

3.类的作用域:

类自己定义了一个作用域。所以存在同名隐藏和名字查找相关内容。

名字查找:寻找与所使用的名字最佳匹配过程。

流程:先在块内寻找声明,只寻找使用之前的声明。若块内未发现,则在外层作用域中寻找。若都未发现,则报错。

类部分符合此规则,但是遵循先处理声明后处理函数的原则。

当发生同名隐藏时,仍然可以通过显式使用域作用付来使用被隐藏的变量。当形参和数据成员同名时,会覆盖隐藏,这个时候可以通过使用this指针或者域作用符来使用数据成员。

4.构造函数:

  • 1.详情请看前面的博文。

  • 2.委托构造函数:顾名思义。

  • 3.隐式类型转换:一般发生在拷贝初始化或者赋值的过程。一般定义单参数构造函数就定义了隐式类型转换。

  • 4.禁止隐式类型转换:在声明的时候加上explicit 关键字即可。只作用于单参数构造函数,多参数构造函数不存在隐式类型转换,所以无需加上explicit.在类外定义的时候不要加上此关键字。

5.静态数据成员:

  • 1.属于类,不属于任何一个对象。受访问权限控制。

  • 2.静态成员不与任何对象绑定,不包含this指针。作为结果,静态成员函数不能是const的,而且也不在静态成员函数内使用this指针。

  • 3.一般在类外定义静态数据成员。类内也可以,但是只是在一些特殊的情况下。

  • 4.静态数据成员不属于类,故不是在类的对象创建的时候初始化的,也就是说用不到构造函数的。所以要提前定义好静态成员。静态数成员的生命周期贯穿于整个程序,静态数数据成员可以是不完全类型。

6.习题解答:

7.1

//头文件#ifndef SALES_DATA_H#define SALES_DATA_H#include <iostream>#include <ostream>#include <istream>#include <string>class Sales_data{private:std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;public:Sales_data(){}std::string  isbn() const{return this->bookNo;}Sales_data& combine(const Sales_data&);double avg_price() const;friend Sales_data add(const Sales_data&, const Sales_data&);friend std::ostream& print(std::ostream&, const Sales_data&);friend std::istream& read(std::istream&, Sales_data&);};#endif // !SALES_DATA_H//函数定义部分#include "Sales_data.h"using namespace std;double Sales_data::avg_price() const{    if (units_sold)        return revenue / units_sold;    else        return 0;}Sales_data& Sales_data::combine(const Sales_data&rhs){    this->units_sold += rhs.units_sold;    this->revenue += rhs.revenue;    this->bookNo = rhs.bookNo;    return *this;}Sales_data add(const Sales_data&rh1, const Sales_data&rh2){    Sales_data temp;    temp.units_sold = rh1.units_sold + rh2.units_sold;    temp.revenue = rh1.revenue + rh2.revenue;    temp.bookNo = rh1.bookNo;    return temp;}ostream& print(ostream& os, const Sales_data& rhs){    os << rhs.isbn() << " " << rhs.units_sold << " "        << rhs.revenue << " " << rhs.avg_price();    return os;}istream& read(istream& is, Sales_data& rhs){    double price = 0.0;    is >> rhs.bookNo >> rhs.bookNo >> price;    rhs.revenue = price*rhs.units_sold;    return is;}int main(){    Sales_data total;    if (read(cin, total)){        Sales_data trans;        while (read(cin, trans)){            if (total.isbn() == trans.isbn())                total.combine(trans);            else                print(cout, total);            total = trans;        }        print(cout, total) << endl;    }    else        cerr << "No data ?" << ndl;}
7.2 7.3

参考7.1

7.4

#ifndef Person_H#define Person_H#include <iostream>#include <string>class Person{private:string name, Address;};#endif // !Person_H
7.5

class Person{private:string name, address;public:string Get_name()const{return name;}string Get_addr() const{return address;}};应是const的,因为我只需要获得信息,无需修改信息。
7.6 7.7

参考7.1

7.8

因为读的过程涉及写值,一定定义成const,那么就无法顺序写入信息。在print函数中,我们只是输出信息,所以可以定义成const.
7.9

friend ostream& print(ostream& os, const Person& rhs){os << rhs.name << " " << rhs.address;return os;}friend istream& read(istream& is, const Person& rhs){is >> rhs.name >> rhs.address;return is;}
7.10

读取data1,data2.然后检测istream对象。
7.11

int main(){Sales_data item1;//默认构造函数调用Sales_data item2("Hello");//const string& 的构造函数调用。Sales_data item3("Word", 10, 3.2);//三个参数的构造函数调用。read(cin, item1);Sales_data(cin);system("pause");return 0;}

7.12

Sales_data(istream& is){read(is, *this);}
7.13

参考7.1

7.14

class Temp{private:int number = 0;public:Temp(int n=0) :number(n){}};
7.15

Person(string n_name="", string a_addr="") :name(n_name), address(a_addr){}
7.16

无次数为位置限定。接口部分应该定义在public 下。实现部分应该定义在private下。
7.17

无大区别。唯一的区别在于默认权限。
7.18

隐藏类的实现细节部分。
7.19

把数据成员声明为private 的。把接口函数声明为public的。原因:略。参见实现和接口的含义。
7.20

普通函数或类想要访问类的私有成员。坏处就是破坏封装性了。
7.21

参考7.1

7.22

参考前面习题。

7.23 7.24

#include <iostream>#include <string>using std::string;class Screen{public:using pos =string::size_type;private:pos cursor=0;pos height=0, width=0;std::string content;public:Screen(){}Screen(pos w_width, pos h_height) :height(h_height), width(w_width){content=string(w_width*h_height, ' ');}Screen(pos w_width, pos h_height, char c) :height(h_height), width(w_width), content(h_height*w_width, c){}};
7.25

可以安全依赖。因为都存在类内初始值,而且string 是存在默认构造函数的。
7.26

提高效率吧。。。我喜欢。。
7.27

7.28

会发生临时对象调用成员函数的事情。。
7.29

不正确。
7.30

缺点:多打了几个字符。优点:同名隐藏时可以显式调用,其余也就没什么了吧。
7.31

class Y;  //前向声明。class X{private:Y* ptr;};class Y{X itex;};
7.32

略。
7.33

暂时未发现会导致什么情况。只不过是const成员函数而已,无法在函数体内修改成员。
7.34

标识符未定义。
7.35

根据名字查找原则,同名覆盖。使用的都是类内部定义的别名和函数。
7.36

初始化列表的执行顺序是按照数据成员的声明顺序执行的。修改:struct X{  x(int i,int j):base(i){    rem(base%j);  }} 
7.37

第一个是istream对象相关的构造函数。第二个是默认构造函数调用第三个是调用string 相关的构造函数。
7.38

没看懂题目的意思。istream对象允许拷贝呢?
7.39

同上。如何为istream对象提供默认实参?可以拷贝?string对象倒是没问题。
7.40

日期的构成:年日月。可以考虑用无符号整形表示吧。此时的构造函数很简单吧。也可以用string 表示。此时构造函数提供一个string就可以了。
7.41

Sales_data(std::string temp_book, unsigned temp_solds, double temp_rev) :bookNo(temp_book), units_sold(temp_solds), revenue(temp_rev){cout << "Call the three arguments Cstor" << endl;}Sales_data() :Sales_data("", 0, 0){cout << " Call the default Cstor " << endl;}Sales_data(istream& is):Sales_data(){cout << "Call the istream Csotr" << endl;read(is, *this);}Sales_data(const string& s) :Sales_data(s,0,0) { cout << "Call the string Cstor" << endl;}委托构造函数至少有一个是要用来被委托的,而且我感觉用处不是很大啊。类似于函数里面调用其他构造函数进行初始化工作。
7.42

略。
7.43

C(int x=0){  NoDefault(x)  //...}
7.44

不一定合法。如果NoDefault中没用默认构造函数,那么10个对象怎么办?所以要视情况而定。
7.45

合法。因为我有默认构造函数。10个对象我有办法处理。
7.46

a)不正确,因为我们可以让编译器提供。b) 不是,是不用提供实参就可以调用的构造函数。c)理论是不应该,但是要控制对象的初始化过程。d)错误。详情可以C++对象模型的博文,其中一张深入探讨了构造函数的相关问题。
7.47

定义成显式的比较好。如果当我们用C风格字符串进行拷贝初始化对象时,要进行两步转换,两次转换是不合法行为。不如直接禁止隐式类型转换发生。
7.48

第一个进行隐式类型转换,会产生一个临时对象。第二个调用string相关的构造函数。第三个会产生隐式类型转换,把C字符传转换成string。
7.49

a)s先隐式转换成对象类型。然后拷贝给形参b)同样是先隐式转换,然后直接引用那个临时对象,然后报错,因为普通引用类型无法引用const对象。c)正好同b互补。因为是const的,所以调用成功。
7.50

这个翻译的我也是醉了。原题:确定在你的Person类中是否有一些构造函数应是explicit的。为嘛我总觉得读起来怪怪的。我语文已经差到这个地步了嘛?关于构造函数是否是Explicit的。关键在于自己是否想禁止隐式类型转换。
7.51

我理解是可能是二义性问题。一个单参数如何理解?是表示数量还是一个元素的值?但是string就不会产生这个问题吧。比较C风格字符串同string还是有关联的。
7.52

列表初始化。等价于写成:Sales_data item("978-xxxxxxxx",25,1.99);前提是你要有相应的构造函数。
7.53

由于这部分比较偏僻,带的需要的时候在来仔细研究吧。

7.54

同7.53

7.55

不是字面值常量类。因为字面值常量类至少要存在一个constexpr类型的构造函数。
7.56

最直观的感受就是属于所有对象,但是不属于任何一个对象。共有的东西。其次就是生命周期是贯穿程序的。优点可能具体情况讨论比较好。毕竟优点缺点是比较出来的。
7.57

#include<iostream>using namespace std;class Account{ public:static double rate() { return interestRate;}static void rate(double);private:static constexpr int period=30;double daily_tbl[period];}
7.58

有错误。首先在类内部初始化了普通静态数据成员。其次vecsize 不可以用了vec的大小。


后记:

反正我感觉这个题目出的简直操蛋啊,从来不写一个程序。而且有的题目意思理解起来坑爹啊。所以有的题目,有的知识比较偏僻的我直接就省略了。希望下面的习题千万不能出成这个样子,其实我更希望割裂习题间的联系,因为来回翻看前面的也是比较烦心的事情啦。

其实对比前面的博文,我发现这个篇章我写的的确比较少。其实我也是在告诫我自己,你要做的是学会知识,而不是把书抄下来。

End



0 0