学习C++ primer的关键点记录三

1:在类内部,声明成员函数是必需的,而定义成员函数则是可选的。在类内部定义的函数默认为 inline,在类外部定义的成员函数必须指明它们是在类的作用域中。Sales_item::avg_price 的定义使用作用域操作符来指明这是Sales_item 类中 avg_price 函数的定义。

将关键字 const 加在形参表之后,就可以将成员函数声明为常量:double avg_price() const;const 成员不能改变其所操作的对象的数据成员。const 必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译时错误。非const对象能够调用const成员方法,但const对象不能调用非const成员方法。

2:可以在任意的访问标号(public、protected、private)出现之前定义类成员。在类的左花括号之后、第一个访问标号之前定义成员的访问级别,其值依赖于类是如何定义的。如果类是用struct 关键字定义的,则在第一个访问标号之前的成员是公有的;如果类是用class 关键字是定义的,则这些成员是私有的。

3:类的使用者只关心它的接口。类设计者会定义直观和易用的类接口,而使用者只关心类中影响他们使用的部分实现。如果类的实现速度太慢或给类的使用者加上负担,则必然引起使用者的关注。在良好设计的类中,只有类的设计者会关心实现。在简单的应用程序中,类的使用者和设计者也许是同一个人。即使在这种情况下,保持角色区分也是有益的。设计类的接口时,设计者应该考虑的是如何方便类的使用;使用类的时候,设计者就不应该考虑类如何工作。

4:使用类型别名来简化类,除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。如果为std::string::size_type 提供一个类型别名,那么 Screen 类将是一个更好的抽象:

class Screen {
  public:
     // interface member functions
     typedef std::string::size_type index;
  private:
     std::string contents;
     index cursor;
     index height, width;
};
类所定义的类型名遵循任何其他成员的标准访问控制。将 index 的定义放在类的 public 部分,是因为希望用户使用这个名字。Screen 类的使用者不必
了解用 string 实现的底层细节。定义 index 来隐藏 Screen 的实现细节。将这个类型设为 public,就允许用户使用这个名字。

4:可以声明一个类而不定义它:class Screen; // declaration of the Screen class
这个声明,有时称为前向声明(forward declaraton),在程序中引入了类类型的 Screen。在声明之后、定义之前,类 Screen 是一个不完全类型(incompete type),即已知 Screen 是一个类型,但不知道包含哪些成员。不完全类型(incomplete type)只能以有限方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。在创建类的对象之前,必须完整地定义该类。必须定义类,而不只是声明类,这样,编译器就会给类的对象预定相应的存储空间。同样地,在使用引用或指针访问类的成员之前,必须已经定义类。

5:定义类类型的对象
定义了一个类类型之后,可以按以下两种方式使用。
• 将类的名字直接用作类型名:Sales_item item1; // default initialized object of type Sales_item 
• 指定关键字 class 或 struct,后面跟着类的名字: class Sales_item item1; // equivalent definition of item1
两种引用类类型方法是等价的。第二种方法是从 C 继承而来的,在 C++ 中仍然有效。第一种更为简练,由 C++ 语言引入,使得类类型更容易使用。

6:类的定义分号结束。分号是必需的,因为在类定义之后可以接一个对象定义列表。定义必须以分号结束:
class Sales_item { /* ... */ };
class Sales_item { /* ... */ } accum, trans;

7:可变数据成员(mutable data member)永远都不能为 const,要将数据成员声明为可变的,必须将关键字 mutable 放在成员声明之前,任意成员函数,包括 const 函数(不能改变对象的数据成员但mutable数据成员例外),都可以改变mutable数据成员 的值。

8:在定义于类外部的成员函数中,形参表和成员函数体都出现在成员名之后。这些都是在类作用域中定义,所以可以不用限定而引用其他成员,与形参类型相比,返回类型出现在成员名字前面。如果函数在类定义体之外定义,则用于返回类型的名字在类作用域之外。如果返回类型使用由类定义的类型,则必须使用完全限定名

class Screen {
   public:
   typedef std::string::size_type index;
   index get_cursor() const;
};
inline Screen::index Screen::get_cursor() const
{
   return cursor;
}
该函数的返回类型是 index,这是在 Screen 类内部定义的一个类型名。如果在类定义体之外定义 get_cursor,则在函数名被处理之前,代码在不在类作用域内。当看到返回类型时,其名字是在类作用域之外使用。必须用完全限定的类型名 Screen::index 来指定所需要的 index 是在类 Screen 中定义的名字。

9:与任何其他函数一样,构造函数具有名字、形参表和函数体。与其他函数不同的是,构造函数也可以包含一个构造函数初始化列表:
// recommended way to write constructors using a constructor initializer
Sales_item::Sales_item(const string &book):isbn(book), units_sold(0), revenue(0.0) { }

构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。这个构造函数将 isbn成员初始化为 book 形参的值,将 units_sold 和 revenue 初始化为 0。与任意的成员函数一样,构造函数可以定义在类的内部或外部。构造函数初始化只在构造函数的定义中而不是声明中指定。

有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用。没有默认
构造函数的类类型的成员,以及 const 或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。

class ConstRef {
public:
  ConstRef(int ii);
private:
  int i;
   const int ci;
   int &ri;
};
// no explicit constructor initializer: error ri is uninitialized
ConstRef::ConstRef(int ii)
{  // assignments:
   i = ii; // ok
   ci = ii; // error: cannot assign to a const
   ri = i; // assigns to ri which was not bound to an object
}
记住,可以初始化 const 对象或引用类型的对象,但不能对它们赋值。在开始执行构造函数的函数体之前,要完成初始化。初始化 const 或引用类型数据成员的唯一机会是构造函数初始化列表中。编写该构造函数的正确方式为
// ok: explicitly initialize reference and const members
ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }

10:如果类定义了一个构造函数,则编译器将不合成默认构造函数。实际上,如果定义了其他构造函数,则提供一个默认构造函数几乎总是对的。通常,在默认构造函数中给成员提供的初始值应该指出该对象是“空”的。

11:错误调用默认构造函数初始化对象:
// error! declares a function, not an object
Sales_item myobj();

使用默认构造函数定义一个对象的正确方式是去掉最后的空括号:
// ok: defines a class object ...
Sales_item myobj;
另一方面,下面这段代码也是正确的:
// ok: create an unnamed, empty Sales_itemand use to initialize myobj
Sales_item myobj = Sales_item();

12:可以用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。

让我们看看定义了两个构造函数的 Sales_item 版本:
class Sales_item {
public:
    // default argument for book is the empty string
    Sales_item(const std::string &book = ""):isbn(book), units_sold(0), revenue(0.0) { }
    Sales_item(std::istream &is);
    // as before
};
这里的每个构造函数都定义了一个隐式转换。因此,在期待一个 Sales_item类型对象的地方,可以使用一个 string 或一个 istream:
string null_book = "9-999-99999-9";
// ok: builds a Sales_itemwith 0 units_soldand revenue from
// and isbn equal to null_book
item.same_isbn(null_book);//形参为Sales_item对象,但此时传递的实参是string对象,根据定义的单形参构造函数发生隐式类类型转换
这段程序使用一个 string 类型对象作为实参传给 Sales_item 的same_isbn 函数。该函数期待一个 Sales_item 对象作为实参。编译器使用接受一个 string 的 Sales_item 构造函数从 null_book 生成一个新的Sales_item 对象。新生成的(临时的)Sales_item 被传递给 same_isbn。

解决方法1:可以通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中使用构造函数
class Sales_item {
public:
       // default argument for book is the empty string
explicit Sales_item(const std::string &book = ""):isbn(book), units_sold(0), revenue(0.0) { }
explicit Sales_item(std::istream &is);
// as before
};
explicit 关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不再重复它

解决方法2:显示调用构造函数:item.same_isbn(Sales_item(null_book));

通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为 explicit。将构造函数设置为explicit 可以避免错误,并且当转换有用时,用户可以显式地构造对象。

13:static 关键字只能用于类定义体内部,外部定义不能标示为static。

static成员函数可在类内部也可在外部定义。

static 成员数据必须在类定义体的外部定义(正好一次)。

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

注意1:static 成员是类的组成部分但不是任何对象的组成部分,因此,static 成员函数没有 this 指针。

注意2:因为 static 成员不是任何对象的组成部分,所以 static 成员函数不能被声明为 const。毕竟,将成员函数声明为 const 就是承诺不会修改该函数所属的对象。

注意3:static 成员函数也不能被声明为虚函数。

14:特殊的整型 const static 成员
一般而言,类的 static 成员,像普通数据成员一样,不能在类的定义体中初始化。相反,static 数据成员通常在定义时才初始化。
这个规则的一个例外是,只要初始化式是一个常量表达式,整型 const static 数据成员就可以在类的定义体中进行初始化:
class Account {
public:
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
static const int period = 30; // interest posted every 30 days
double daily_tbl[period]; // ok: period is constant expression
};
用常量值初始化的整型 const static 数据成员是一个常量表达式。同样地,它可以用在任何需要常量表达式的地方,例如指定数组成员 daily_tbl 的维。const static 数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义。
在类内部提供初始化式时,成员的定义不必再指定初始值:
// definition of static member with no initializer;
// the initial value is specified inside the class definition
const int Account::period;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值