c++ primer 笔记[20190405]

292 阅读9分钟

P246 流对象不允许赋值或复制操作

因为某些原因(比如涉及缓冲区的指针,那么就会涉及深浅拷贝问题,以及流对象打开文件,由此导致的复制产生的文件句柄会很麻烦等原因),流对象不允许做赋值或复制操作。 那么就会由此引发两个层次的问题:

  • 因为只有支持复制的元素类型可以存储在vector或其他容器类型里,而流对象不能复制,因此不能存储在vector或其他容器中。
  • 函数形参或返回类型也不能为流对象。如果需要传递或返回IO对象,则必须传递或返回指向该对象的指针或引用,避免了复制。
ofstream out;
ofstream& print(ofstream&);  // ok: takes a reference, no copy
while(print(out)) {...}      // ok: pass reference to out

一般情况下,如果要传递IO对象以便对他进行读写,可用非const引用的方式传递这个流对象。对IO对象的读写会改变它的状态,因此引用必须是非const的。

P249 输出缓冲区的刷新

输出缓冲区的管理
每个IO对象管理一个缓冲区,用于存储程序读写的数据,下面几种情况会导致缓冲区的内容被刷新,即写入到真实的输出设备或文件:

  • 程序正常结束。作为main返回工作的一部分,将清空所有输出缓冲区。
  • 在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会在写入下一个值之前刷新。
  • 用endl操纵符显示地刷新缓冲区,还会输出换行。flush也可以,但不在输出中添加任何字符。还有ends,这个操纵符在缓冲区中插入空字符null,然后刷新它。
  • 每次输出操作执行完后,用unitbuf操作符设置流的内部状态,从而清空缓冲区。

如果程序不正常结束,输出缓冲区不会刷新。

P250 输入流和输出流关联

当输入流和输出流绑在一起时,任何读输入流的尝试都将首先刷新其输出流关联的缓冲区,标准库cout和cin绑在一起,因此: cin >> val; 将导致cout关联的缓冲区被刷新。

P373 inline

在类内部定义的成员函数,将自动作为inline处理,也就是说当它们被调用时,编译器将试图在同一行内扩展该函数。也可以显示地将成员函数声明为inline。
另外,可以在类定义体内指定一个成员为inline,作为其声明的一部分。或者,也可以在类定义体外部的函数定义上指定inline。在声明和定义处指定inline都是合法的。

P374 类的声明

class Bird; 这叫声明一个类,也叫前向声明。在声明之后,定义之前,类Bird是一个不完全类型,即只知道Bird是一个类型,但不知道包含哪些成员。
不完全类型只能以有限方式使用,不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明使用该类型作为形参类型或返回类型的函数。

class Bird {
public:
    Bird();
    ~Bird();

public:
    Run();
private:
    string name;
}

这称为类的定义,标明了内部的成员函数和成员变量。

P377 this指针

在普通的非const成员函数中,this的类型是一个指向类类型的const指针,可以改变this所指向的值,但不能改变this所保存的地址。在const成员函数中,this的类型是一个指向const类类型对象的const指针,既不能改变this所指向的对象,也不能改变this所保存的地址。

对于mutable
const成员函数可以改变mutable成员变量,要将数据成员声明为可变的,必须将关键字mutable放在成员声明之前:

class Screen {
private:
    void do_display(ostream& os) const
    {
        os << contents;
    }
private:
    mutable int access_count_;
};

void Screen::do_display(ostream& os) const
{
    ++access_count_;
    os << contents;
}

尽管do_display是const,它也可以增加access_count_。该成员是可变成员,任意成员函数,包括const函数,都可以改变其值。

P388 构造函数及初始化列表

构造函数是分两个阶段执行的:

  1. 初始化阶段,即初始化列表
  2. 普通的计算阶段,该阶段由构造函数函数体中的所有语句组成

执行构造函数初始化列表 和 在构造函数的函数体内对数据成员赋值 的本质区别是:
使用构造函数初始化列表的版本初始化数据成员,没有定义初始化列表的构造函数版本在构造函数函数体中对数据成员赋值。

有些情况必须使用构造函数初始化列表:

  • 没有默认构造函数的类类型的成员
  • const或引用类型的成员,初始化const或引用类型数据成员的唯一机会是在构造函数初始化列表中。

P392 建议不要依赖于合成的默认构造函数

如果类包含内置或复合类型的成员,则该类不应该依赖于合成的默认构造函数。它应该定义自己的构造函数来初始化这些成员。
每个构造函数应该为每个内置或复合类型的成员提供初始化式。没有初始化内置或复合类型成员的构造函数,将使那些成员处于未定义的状态。除了作为赋值的目标之外,以任何方式使用一个未定义的成员都是错误的。

类通常应定义一个默认构造函数。

在某些情况下,默认构造函数是由编译器隐式应用的。如果类没有默认构造函数,则该类就不能用在这些环境中。例如:NoDefault类没有默认构造函数,有一个接受string实参的构造函数。

  1. 具有NoDefault成员的类的每个构造函数,必须通过传递一个初始的string值给NoDefault构造函数来显示初始化NoDefault成员。
  2. 编译期不会为具有NoDefault类型成员的类合成默认构造函数。如果这样的类希望提供默认构造函数,就必须显式定义,并且默认构造函数必须显式初始化其NoDefault成员。
  3. NoDefault类型不能用作动态分配数组的元素类型。
  4. NoDefault类型的静态分配数组必须为每个元素提供一个显示的初始化。
  5. 如果有一个保存NoDefault对象的容器,如vector,就不能使用接受容器大小而没有同时提供一个元素初始化式的构造函数。

注意:通常如果定义了其他构造函数,则提供一个默认构造函数总是对的。在默认构造函数中给成员提供的初始值应该指出该对象是“空”的。

理解这句话:为转换而显示地使用构造函数。尽量避免-隐式类类型转换
通常,除非有明显的理由想要定义隐式转换,否则,单行参构造函数应该为explicit。将构造函数设置为explicit可以避免错误,并且当转换有用时,用户可以显示地构造对象。

隐式类类型转换理解

可以用 单个形参来调用 的构造函数定义了从 形参类型 到 该类类型 的一个隐式转换。
这里应该注意的是,“可以用单个形参进行调用”并不是指构造函数只能有一个形参,而是它可以有多个形参,但那些形参都是有默认实参的。
那么,什么是“隐式转换”呢? 上面这句话也说了,是从 构造函数形参类型 到 该类类型 的一个编译器的自动转换。

class BOOK  //定义了一个书类
{
private:
    string _bookISBN ;  //书的ISBN号
    float _price ;    //书的价格

public:
    //定义了一个成员函数,这个函数即是那个“期待一个实参为类类型的函数”
    //这个函数用于比较两本书的ISBN号是否相同
    bool isSameISBN(const BOOK & other )
    {
        return other._bookISBN == _bookISBN;
    }

    // 类的构造函数,即那个“能够用一个参数进行调用的构造函数”
    // (虽然它有两个形参,但其中一个有默认实参,只用一个参数也能进行调用)
    BOOK(string ISBN, float price=0.0f) : _bookISBN(ISBN), _price(price) {}
};

int main()
{
    BOOK A("A-A-A");
    BOOK B("B-B-B");

    cout<<A.isSameISBN(B)<<endl;   //正经地进行比较,无需发生转换

    //此处即发生一个隐式转换:string类型-->BOOK类型,借助BOOK的构造函数
    //进行转换,以满足isSameISBN函数的参数期待。
    cout<<A.isSameISBN(string("A-A-A"))<<endl; 
    cout<<A.isSameISBN(BOOK("A-A-A"))<<endl;  //显式创建临时对象,也即是编译器干的事情。
}

P399 static成员

static数据成员

类的static数据成员独立于该类的任意对象而存在,因此static成员的名字在类的作用域中,可以避免与其他类的成员或全局对象名字冲突。
static数据成员必须在类定义体的外部定义。但static关键字只能用于类定义体内部的声明中,定义不能带着static关键字。如:

class Bird {
    static int type;
};

int Bird::type = 10;

对于上诉情况有一个特例:
只要初始化式是一个常量表达式,整型const static数据成员就可以在类的定义体中进行初始化:

class Account{
private:
    static const int period = 30;
    double daily_tbl[period];
};

但记得:const static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义,但不必在指定初始值:

const int Account::period;

static成员函数

类的static成员函数没有this形参,它可以访问所属类的static成员,但不能直接使用非static成员。因为没有this指针,因此:

  • static成员函数不能被声明为const,毕竟,将成员函数声明为const就是承诺不会修改该函数所属的对象。
  • static成员函数不能被声明为虚函数。