C++ Primer 【第四版】第十二章 类和数据抽象

来源:互联网 发布:淘宝怎么批量修改详情 编辑:程序博客网 时间:2024/05/16 06:35
12.1编写一个名为person的类,表示人的名字和地址,使用string来保存每个元素。

答:

class person

{

      public:

      person( string pName, string pAddress )

{

             name =pName;

             address= pAddress;

      }

private:

             stringname;

             stringaddress;

};

 

12.2person提供一个接收两个string参数的构造函数。

见第一题。

 

12.3提供返回名字和地址的操作。这些函数应为const吗?解释你的选择。

public里添加成员函数:

      string get_name() const

      {

             return name;

      ]

      string get_address() const

      {

             return address;

      ]

这两个成员函数不应该修改其操作的对象的数据成员的值,应该声明为const类型。

 

12.4指明person的哪个成员应声明为public,哪个成员应声明为private。解释。

数据成员nameaddress应为private,保证只能被类的成员所用,外界不可访问。成员函数get_name()get_address()应声明为public,为外界提供接口访问类的数据成员。构造函数也应声明为public,以便初始化类的对象。

 

12.5 C++类支持哪些访问标号?在每个访问标号之后应定义哪种成员?如果有的话,在类的定义中,一个访问标号可以出现在何处以及可出现多少次?约束条件是什么?

public, private, protectpublic后定义可被外界访问的接口,private后定义只能被本类成员函数使用的成员;protect后定义的成员称为受保护成员,只能由本类及本类的子类访问。

      访问标号可以出现在任意成员定义之前且次数没有限制。

      约束条件是:每个访问标号指定了随后的成员定义级别,这个级别持续有效,直到下一个访问标号出现,或者看到类定义体的右花括号为止。

12.6class关键字定义的类和用struct定义的类有什么不同。

      默认访问标号不同,用struct关键字定义的,在第一个访问标号之前的成员是共有的,如果是用class关键字定义的,在第一个访问标号之前的成员是private成员。

12.7什么事封装?为什么封装是有用的?

      封装是一种将低层次的元素组合起来形成新的、高层次实体的技术。例如,函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中。被封装的元素隐藏了它们的实现细节,可以调用一个函数但不能访问它所执行的语句,同样类也是一个封装的实体:它代表若干成员的聚集,大多数类类型隐藏了实现该类型的成员。

      封装隐藏了内部元素的实现细节,提供了优点:避免类内部出现无意的可能破坏对象状态的用户级错误;在修改类的实现时不需要修改用户级代码,这些都很有用。

 

12.8sales_item::avg_price定义为内联函数。

      inline double sales_item::avr_price()const

      {

             if ( units_sole )

                    return revenue/units_sold;

             else return 0;

      }

 

12.9修改本节中给出的screen类,给出一个构造函数,根据屏幕的高度、宽度和内容的值来创建screen

      class Screen

{

public:

      typedef std::string::size_type index;

      Screen( index hei, index wid , stringcontent )

      {

             contents = content;

             height = hei;

             width = wid;

      }

private:

      std::string contents;

      index cursor;

      index height, width;

};

 

 

12.10解释下述类中的每个成员:

      classRecord {

             typedefstd::size_t size;

             Record():byte_count(0) {}

             Record(sizes): byte_count(s) { }

             Record(std::stirngs): name(s), byte_count(0) {}

             sizebyte_count;

             std::stringname;

      public:

             sizeget_count() const { return byte_count; }

             std::stringget_name() const { return name; }

      };

      三个Record()函数是重载的三个构造函数,size byte_count; std::string name;这是两个private的数据成员, size get_count() const std::stringget_name() const这是两个public成员函数。

 

12.11定义两个类XYX中有一个指向Y的指针,Y中有一个X类型的对象。

class Y;  

class X

{  

      Y *p;

};

class Y {

      X xobj;

};

 

12.12解释类声明与类定义之间的差异。何时使用类声明?何时使用类定义?

      类声明是不完全类型,只能以有限方式使用,不能定义该类型的对象,只能用于定义指向该类型的指针及引用,或者声明使用该类型作为形参类型或返回类型的函数。

      类定义,一旦类被定义,我们就可以知道所有类的成员,以及存储该类的对象所需的存储空间。

      在创建类的对象之前或者使用引用或指针访问类的成员之前必须定义类。

12.13扩展screen类以及包含move.setdisplay操作。通过执行如下表达式来测试类:

      myScreen.move(4, 0 ).set ( ‘#’ ).display( cout );

 

//12.13_Screen.cpp :定义控制台应用程序的入口点。

//

 

#include"stdafx.h"

#include<iostream>

#include<string>

 

usingnamespace std;

 

classScreen

{

public:

        typedefstd::string::size_typeindex;

        Screen(indexhei, index wid , const string &content =" " )

        {

                  cursor= 0;

                  contents= content;

                  height= hei;

                  width= wid;

        }

        // add 3 newmembers

        Screen& move(indexr,index c)

        {

                  indexrow =r * width;

                  cursor= row +c;

                  return*this;

        }

        Screen& set(charc )

        {

                  contents[cursor] =c;

                  return*this;

        }

        Screen& display(ostream&os )

        {

                  do_display(os);

                  return*this;

        }

        const Screen &display(ostream &os )const

        {

                           do_display(os);

                           return *this;

        }

private:

        std::stringcontents;

        index cursor;

        index height,width;

        void do_display(ostream&os) const

        {

                  os<< contents;

        }

};

 

int_tmain(int argc, _TCHAR* argv[])

{       

         Screen myscreen( 5,5, "bbbbbbbbbb\ngggg\nfffffff\niiiiiiiiiiiiiiiiii\nmmmmmmmm\n");

         myscreen.move(4,0).set('#').display(cout);

         system("pause");

        return0;

}

12.14通过this指针引用成员虽然合法,但却是多余的。讨论显示地使用this指针访问成员的优缺点。

      优点:当需要将一个对象作为整体引用而不是引用对象的一个成员时,使用this,则该函数返回对调用该函数的对象的引用。

      可以非常明确地指出访问的是调用该函数的对象的成员,且可以在成员函数中使用与数据成员同名的形参。

      缺点:不必要使用,代码多余。

 

12.15列出在类作用域中的程序文本部分。

      类的定义体;在类外定义的成员函数:形参表、成员函数体,但不包含函数的返回类型。

 

12.16如果定义get_cursor时,将会发生什么?

      IndexScreen::get_cursor() const {  returncursor; }

      编译错误,index是在Screen类的作用域之外的,在类外没有定义index类型。

 

 

12.17如果将Screen类中的类型别名放到类中的最后一行,将会发生什么?

      发生编译错误,因为Index的使用出现在了其定义之前。

 

 

12.18解释下述代码,指出每次使用TypeinitVal时用到的是哪个名字定义。如果存在错误,说明如何改正。

Typedef string type;

Type initVal();

 

class Exercise {

public:

      //...

typedef double Type;

Type setVal(Type);

Type initVal();

private:

      intval;

};

Type Exercise::setVal(Type parm) {

      val= parm + initVal();

}

成员函数setVal的定义有错。进行必要的修改以便类Exercise使用全局的类型别名Type和全局函数initVal.

答:在类的定义体内,两个成员函数的返回类型和setVal的形参使用的Type都是类内部定义的类型别名Type;

      在类外的成员函数setVal的定义体内,形参的类型TypeExercise类内定义的类型别名Type,返回类型的Type使用的是全局的类型别名Type

      steVal的定义体内使用的initVal函数,使用的是Exercise类的成员函数。

 SetVal的函数定义有错:1.缺少返回类型;2.此函数的返回类型是string类型的,而类中声明的函数返回类型是double类型的,不合法的函数重载,可以改为:

      Exercise::Type Exercise::setVal(Typeparm) {

             val = parm + initVal();

             return val;

      }

 

setVal修改为如下,可保证类Exercise使用全局的类型别名Type和全局函数initVal.

Typedef string type;

Type initVal();

class Exercise {

public:

      // ...

Type setVal(Type);

private:

      Type val;

};

TypeExercise::setVal(Type parm)

{

      val = parm + initVal();

      return val;

}

 

 

12.1 9提供一个或多个构造函数,允许该类的用户不指定数据成员的初始值或指定所有数据成员的初始值:

      classNoName {

      public:

             //constructor(s) go here ...

      private:

             std::string*pstring;

             int  ival;

             doubledval;

      };

      解释如何确定需要多少个构造函数以及它们应该接受什么样的形参。

实参决定调用哪个构造函数,所以实参的种类数决定构造函数的数目,因此此题至少需要两个构造函数,一个是默认没有参数的构造函数,一个是带有三个形参的构造函数,类型要与三个数据成员的类型匹配。

构造函数:

class NoName {

      public:

             //constructor(s) go here ...

             NoName() {}

             NoName( std::string *ps, int iv,double dv )

             {

                    pstring = ps;

                    ival = iv;

                    dval = dv;

             }

      private:

             std::string*pstring;

             int  ival;

             doubledval;

      };

 

 

12.20从下述抽象中选择一个(或一个自己定义的抽象),确定类中需要什么数据,并提供适当的构造函数集。解释你的决定:

      (a)Book  (b) Date       (c) Employee

      (d)Vehicle (e) Object    (f) Tree

(b) Date:需要年:intyear; 月:int month;日:intday

构造两个构造函数,一个默认构造函数,用于创建空的Date对象,一个带有三个形参,用于创建指定日期的对象。

构造函数为:

class Date

{

public:

      Date() {}

      Date( int y, int m, int d )

      {

             year = y;   month = m; day = d;
       }

private:

      int year, month, day;

};

 

 

12.21使用构造函数初始化列表编写类的默认构造函数,该类包含如下成员:一个const string,一个int,一个double*和一个ifstream &。初始化string来保存类的名字。

 

class X

{

public:

      X( ): cStr(“ class name” ), iVal(0),pd(0)  { }

private:

      const string cStr;

      int iVal;

      double *pd;

      ifstream & iFs;

};

 

 

12.22下面的初始化式有错误。找出并改正错误。

      structX {

             X( int i, int j): base(i), rem(base % j) { }

             intrem, base;

             };

struct X的定义中,rem先被定义,先被初始化,在构造函数初始化列表中的初始化顺序是先初始化rem,而此时rem用还没有被初始化的base来初始化rem,会产生runtime error.

改正为:

struct X {

      X( int i, int j ): rem( i % j ), base( i) { }

 

 

12.23假定有个命名为NoDefault的类,该类有一个接受一个int的构造函数,但没有默认构造函数。定义有一个NoDefault类型成员的类C。为类C定义默认构造函数。

class c {

public:

      c() : i(0), Ndf(i) { }

private:

      int i;

      NoDefault Ndf;

};

12.24上面的Sales_item定义了两个构造函数,其中之一有一个默认实参对应其单个string形参,使用该Sales_item版本,确定用哪个构造函数来初始化下述的每个变量,并列出对象中数据成员的值。

      Sales_itemfirst_item(cin);

 用的是第二个即:Sales_item( std::istream &is ); 对象的数据成员的值为:isbn的值为cin输入的字符串。

 

      intmain() {

      Sales_itemnext;

      Sales_itemlast ( “9-999-99999-9”);

      }

这两个用的是第一个即:有默认实参的构造函数,next对象的数据成员的值为:isbn空串,units_sold值为0revenue值为0.0

last对象的数据成员的值为:isbn”9-999-99999-9”units_sold值为0revenue值为0.0

 

 

12.25逻辑上讲,我们可能希望将cin作为默认实参提供给接受一个istream&形参的构造函数。编写使用cin作为默认实参的构造函数声明。

Sales_item(std::istream &is = std::cin );

 

 

12.26接受一个string和接受一个istream&的构造函数都具有默认实参是合法的吗?如果不是,为什么?

不合法,如果二者都具有默认实参,则造成了默认构造函数的重复定义,当定义一个Sales_item的对象而没有给出用于构造函数的实参时,将因为无法确定使用哪个构造函数而出错。

 

 

 

12.27下面的陈述中哪个是不正确的(如果有的话)?为什么?

      (a)类必须提供至少一个构造函数。

      (b)默认构造函数的形参列表中没有形参。

      (c)如果一个类没有有意义的默认值,则该类不应该提供默认构造函数。

      (d)如果一个类没有定义默认构造函数,则编译器会自动生成一个,同时将每个数据成员初始化为相关类型的默认值。

 (a)不提供构造函数时,由编译器合成一个默认构造函数。

(b)错了,为所有形参提供了默认实参的构造函数也定义了默认构造函数,而这样的构造函数形参列表中是有形参的。

(c)不正确,如果一个类没有默认的构造函数,而只有自己写的构造函数,在编译器需要隐式使用默认构造函数时,这个类就不能使用,编译不通过,所以如果一个类定义了其他的构造函数,通常也应该提供一个默认的构造函数。

(d)不正确,1,当一个类中自己定义了构造函数时,编译器就不会自动合成一个默认构造函数了,2,编译器不是将每个数据成员初始化为相关类型的默认值,而是使用与变量初始化相同的规则来初始化成员:类类型的成员执行各自的默认构造函数进行初始化,内置和复合类型的成员,只对定义在全局作用域中的对象才初始化。

 

 

12.28解释一下接受一个stringSales_item构造函数是否应该为explicit.将构造函数设置为explicit的好处是什么?缺点是什么?

应该声明为explicit,如果不声明为explicit,编译器可以使用此构造函数进行隐式类型转换,即将一个string类型的对象转换为Sales_item对象,这种行为时容易发生语义错误的。

      好处是:可以防止构造函数的隐式类型转换而带来的错误,缺点是,当用户确实需要进行相应的类型转换时,不能依靠隐式类型转换,必须显示地创建临时对象。

 

 

12.29解释在下面的定义中所发生的操作。

      stringnull_isbn = “9-999-99999-9”;

      Sales_itemnull1(null_isbn);

      Sales_itemnull(“9-999-99999-9”);

      1,调用接收一个C风格字符串形参的string构造函数,创建一个临时的string对象,然后调用string类的复制构造函数,将null_isbn初始化为该该临时对象的副本。

      2,使用string的对象null_isbn为实参,调用Sales_item类的构造函数创建Sales_item对象null1.

      3,使用接受一个C风格字符串形参的string类的构造函数,生成一个临时string对象,然后用这个临时对象作为实参,调用Sales_item类的构造函数来创建Sales_item类的对象null.

 

12.30编译如下代码:

      f(constvector<int>&);

      intmain()  {

             vector<int>v2;

             f(v2);  // shoule be ok

             f(42);  // shoule be an error

             return0;

      }

      基于对f的第二个调用中出现的错误,我们可以对vector构造函数做出什么推断?如果该调用成功了,那么你能得出什么结论?

可以做出以下推断:vector中没有定义接受一个int型参数的构造函数,或者即使定义了接受一个int型参数的构造函数,该函数也被声明为了explicit

如果调用成功了,则说明vector中定义了可以接受一个int型参数的构造函数。

 

 

12.31 pair的数据成员为Public,然而下面这段代码却不能编译,为什么?      

      pair<int, int > p2 = (0, 42); // doesn’t compile, why?

可能是因为pair类定义了构造函数,此时只能使用构造函数初始化成员,可改为:

      pair<int,int> p2( 0, 42 );

 

 

 

12.32什么是友元函数?什么是友元类?

被指定为某类的友元的函数称为该类的友元函数。

被指定为某类的友元的类称为授予友元关系的那个类的友元类。

 

12.33什么时候友元是有用的?讨论使用友元的优缺点。

在需要允许某些特定的非成员函数访问一个类的私有成员,而同时仍阻止一般的访问的情况下,友元机制是个有用的东西。

优点:可以灵活地实现需要访问若干类的私有或受保护成员才能完成的任务,便于与其他不支持类的语言进行混合编程;通过使用友元函数重载可以更自然第使用C++语言的I/O流库。

缺点:一个类将对非公有成员的访问权授予其他的函数或类,会破坏该类的封装性,降低该类的可靠性和可维护性。

 

12.34定义一个将两个Sales_item对象相加的非成员函数。

首先将此函数定义:

Sales_item add2obj(const Sales_item &obj1, const Sales_item &obj2 )

{

      if ( !obj1.same_isbn(obj2))

             return obj1;

      Sales_item temp;

      temp.isbn = obj1.isbn;

      temp.units_sold = obj1.units_sold +obj2.units_sold;

      temp.revenue = obj1.revenue +obj2.revenue;

      return temp;

}

然后再类Sales_item中将此函数声明为友元:

friend Sales_itemadd2obj( const Sales_item&, const sales_item&);

 

12.35定义一个非成员函数,读取一个istream并将读入的内容存储到一个Sales_item中。

stream& read( istream &is, Sales_item &obj )

{

      double price;

      is >> obj.isbn >>obj.units_sold >> price;

      if ( is )

             obj.revenue = obj.units_sold *price;

      return is;

}

然后将该函数指定为Sales_item的友元。

 

 

12.36什么是static类成员? static成员的优点是什么?它们与普通成员有什么不同?

类成员声明前有关键字static的类成员,static类成员不是任意对象的组成部分,但是由该类的全体对象所共享。

优点:1static成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突;2,可以实施封装,static成员可以是私有成员,而全局对象不可以。3,通过阅读程序看出static成员是与特定的类关联的,这种可见性可清晰地显示程序员的意图。

不同点:普通成员是与对象相关联的,是某个对象的组成部分,而static成员与类相关联,由该类的全体对象所共享,不是任意对象的组成部分。

 

12.37编写自己的Account类版本。

class Account

{

public:

      Account( string own, double am )

      {

             owner = own;

             amount = am;

      }

 

      void applyint()

      {

             amount += amount*interestRate;

      }

 

      static double rate()

      {

             return interestRate;

      }

 

      static void rate(double newRate)

      {

             interestRate = newRate;

      }

private:

      std::string owner;

      double amount;

      static double interestRate;

};

doubleAccount::interestRate = 0.016;

 

 

12.38定义一个命名为Foo的类,具有单个int型数据成员。为该类定义一个构造函数,接受一个int值并用该值初始化数据成员。为该类定义一个函数,返回其数据成员的值。

class Foo

{

private:

             int iVal;

public:

      Foo( int iV ): iVal( iV ) { }

      int get_val()

      {

             return iVal;

      }

};

 

12.39给定上题中定义的Foo类定义另一个Bar类。Bar类具有两个static数据成员:一个为int型,另一个为Foo类型。

class Bar

{

      private:

             static int i;

             static Foo f;

};

 

12.40使用上面两题中定义的类,给Bar类增加一对成员函数:第一个成员命名为FooVal,返回Bar类的Foo类型static成员的值;第二个成员命名为callsFooVal,保存xval被调用的次数。

class Bar

{

public:

      Foo FooVal()

      {

             callsFooVal++;

             return f;

      }

 

private:

      static int callsFooVal;

      static int i;

      static Foo f;

};

static成员进行初始化:

      int Bar::i = 42;

      Foo Bar::f( 0 );

      int Bar::callsFooVal = 0;

 

12.41利用12.6.1节的习题中编写的类FooBar,初始化Foostatic成员。将int成员初始化为20,并将Foo成员初始化为0

int Bar::i = 20;

Foo Bar::f( 0 );

 

 

12.42下面的static数据成员声明和定义哪些是错误的(如果有的话)?解释为什么。

      //example.h

class Example {

public:

      staticdouble rate = 6.5; 错误

      staticconst int vecSize = 20;

      staticvector<double> vec(vecSize); 错误

};

 

// example C

#include “example.h”

double Example::rate; 错误

vector<double> Example::vec; 错误

 

因为非const static成员的初始化必须放在类定义体的外部。

 

下边对static成员ratevec的定义也是错误的,因为此处必须给出初始值。

更正为:

class Example {

public:

      static double rate;

      static const int vecSize = 20;

      static vector<double> vec;

};

 

// example C

#include “example.h”

double Example::rate=6.5; 

vector<double>Example::vec(vecSize); 

0 0
原创粉丝点击