Cpp-02-类

构造函数

类可以包含多个构造函数,和其他重载函数差不多,不同的构造函数之间必须在参数数量或参数类型上有所区别。 不同于其他成员函数,构造函数不能被声明成const的。 当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数在const对象的构造过程中可以向其写值。 类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数(default constructor)。默认构造函数无须任何实参。 默认构造函数在很多方面都有其特殊性。其中之一是,如果我们的类没有显式地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数。编译器创建的构造函数又被称为合成的默认构造函数(synthesized default constructor)。对于大多数类来说,这个合成的默认构造函数将按照如下规则初始化类的数据成员: · 如果存在类内的初始值,用它来初始化成员。 · 否则,默认初始化该成员。
对于一个普通的类来说,必须定义它自己的默认构造函数,原因有三: 第一个原因就是编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认的构造函数。一旦我们定义了一些其他的构造函数,默认情况下类将没有默认构造函数。这条规则的依据是,如果一个类在某种情况下需要控制对象初始化,那么该类很可能在所有情况下都需要控制。 只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数。 第二个原因是对于某些类来说,合成的默认构造函数可能执行错误的操作。回忆我们之前介绍过的,如果定义在块中的内置类型或复合类型(比如数组和指针)的对象被默认初始化,则它们的值将是未定义的。 如果类包含有内置类型或者复合类型的成员,则只有当这些成员全都被赋予了类内的初始值时,这个类才适合于使用合成的默认构造函数 第三个原因是有的时候编译器不能为某些类合成默认的构造函数。例如,如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。对于这样的类来说,我们必须自定义默认构造函数,否则该类将没有可用的默认构造函数。

  • 只能在构造函数初始化:如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
  • 隐式类型转化:如果构造函数只有一个实参,那么包含了转换为此类类型的隐式转换机制,比如函数接受一个对象,对于一个string类型传参,编译器会自动转为临时对象进行传参。
  • 聚合类:当一个类所有成员都是public,没有定义任何构造函数,没有类内初始值,没有基类,也没有virtual函数就是聚合类,聚合类可以直接访问其成员,可以使用{"value1","value2"}直接初始化(顺序必须与声明一致)。
  • constexpr构造函数:字面值常量类的构造函数可以是constexpr函数。事实上,一个字面值常量类必须至少提供一个constexpr构造函数,constexpr构造函数就必须既符合构造函数的要求不能有返回语句,所以要用参数列表的方式初始化类,一般来说constexpr构造函数体应该是空的。
    • 数据成员都必须是字面值类型。·
    • 类必须至少含有一个constexpr构造函数。
    • 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式
    • 类必须使用析构函数的默认定义,该成员负责销毁类的对象。

类的访问控制

  • 链式调用:返回*this可以支持链式调用,但是如果制定const函数可能出现指针不匹配,这个时候可以通过重载解决。
void Test::test(ostream &os) const{
    os << "Hello World!" << endl;
}
const Test &Test::test01(ostream &os) const {
    test(os);
    return *this;
}
Test &Test::test01(ostream &os) {
    test(os);
    return *this;
}
  • 不完全声明(前向声明):提前class Test;进行类的声明,但是这种只能有限的使用,比如定义指向这种类型的指针或引用,也可以声明(但是不能定义)以不完全类型作为参数或者返回类型的函数。我们必须首先完成类的定义,然后编译器才能知道存储该数据成员需要多少空间。
  • 友元:声明为友元可以访问private成员,可以是类,函数,成员函数。

静态成员

通过在成员的声明之前加上关键字static声明静态成员,静态成员可以是public或者private,可以是常量、引用、指针、类类型等。对于静态成员函数而言,它不能使用this指针,不能声明为const,需要在非内联文件(类的cpp文件)中被定义。
可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr,初始值必须是常量表达式,因为这些成员本身就是常量表达式。

IO流

  • 流的状态
    • C++流包括istream, ostream,基于istream继承实现了istringstream和ifstream,基于ostream继承实现了ostringstream和ofstream。 由于不能拷贝IO对象,因此我们也不能将形参或返回类型设置为流类型,进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。 IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。 IO库定义了4个iostate类型的constexpr值,表示特定的位模式。这些值用来表示特定类型的IO条件,可以与位运算符一起使用来一次性检测或设置多个标志位。
    • badbit表示系统级错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法再使用了 。在发生可恢复错误后,failbit被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。 如果到达文件结束位置,eofbit和failbit都会被置位。 goodbit的值为0,表示流未发生错误。 如果badbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。
    • 流对象的rdstate成员返回一个iostate值,对应流的当前状态。 setstate操作将给定条件位置位,表示发生了对应错误。 clear成员是一个重载的成员:它有一个不接受参数的版本,而另一个版本接受一个iostate类型的参数。 clear不接受参数的版本清除(复位)所有错误标志位。执行clear()后,调用good会返回true。
  • 管理输出缓冲:如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。它告诉流在接下来的每次写操作之后都进行一次flush操作。而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制。
//所有输出操作都立即刷新缓冲区
cout << unitbuf;
//恢复到正常模式
cout << nounitbuf;
  • 关联输入输出流:tie有两个重载的版本:一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream。即,x.tie(&o)将流x关联到输出流o。
//将cin和cout绑定在一起
cin.tie(&cout);
// old_tie指向当前关联到cin的流(如果有的话)
// cin不再与其他流关联
ostream *old_tie = cin.tie(nullptr);
//将cin和cerr关联
//读取cin会刷新cerr
cin.tie(&cerr);
//重建cin和cout的关联
cin.tie(old_tie);
  • 文件流: ofstream: 写文件, ifstream: 读文件。
// in: 读
// out: 写
// app: 写文件末尾
// ate: 读文件末尾
// trunc: 截断文件
// binary: 二进制IO
ofstream app("file", ofstream::app); //隐含为输出模式打开
//等价于
ofstream app1("file", ofstream::app | ofstream::out);
  • 字符流: istringstream: 读数据, ostringstream: 写数据, 头文件stringstream: 读写都可以
最后修改于:2025年01月22日 09:19

添加新评论