C++ 学习笔记(7)类、友元、默认构造函数(default)、可变数据成员(mutable)、前向声明和不完全类型、聚合类、字面值常量类

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l773575310/article/details/79131446

C++ 学习笔记(7)类、友元、默认构造函数(default)、可变数据成员(mutable)、前向声明和不完全类型、聚合类、字面值常量类

参考书籍:《C++ Primer 5th》


7.1 定义抽象数据类型

7.1.2 定义改进的Sales_data类

  • 定义在类内部的函数是隐式的inline函数。
  • 所有成员必须在类内部声明,但是成员函数体可以定义在类内,也可以定义在类外。
  • this默认是一个常量指针,指向对象本身。
  • 在函数参数列表后面加上const关键字时,隐式修改了函数体内this的类型:this是常量指针,指向常量。这样的函数称为常量成员函数。
  • 常量对象、常量对象的引用、指针都只能调用常量成员函数。
class Person
{
    string name = "dick";
    int age = 66;
    int GetAge() { return this->age; }          // this是 Person *const 类型。即可以修改this指向的对象。
    string GetName() const { return this->name; }   // this是 const Person *const 类型。不能修改this指向的对象。
};

7.1.3 定义类相关的非成员函数

  • 如果非成员函数是类接口的组成部分,则这些函数声明应该和类在同一个头文件内。
  • IO类属于不能拷贝的类型,因此只能通过引用(作为参数)来传递它们。

7.1.4 构造函数

  • 构造函数不能被声明成const的,在创建一个类的const对象时,直到构造函数完成,对象才真正变成const。(即可以在构造函数中写值。)
  • 如果没有显示定义默认构造函数,编译器会创建:合成的默认构造函数(synthesized default constructor)
  • 使用=default来要求编译器生成默认构造函数。
class Person
{
public:
    Person() = default;     // 提供默认构造函数。
}

7.2 访问控制与封装

  • class 和 struct 两则区别只有默认访问权限:class默认是private。而struct默认是public。

7.2.1 友元

  • 友元声明(friend)只能出现在类定义的内部。友元不是类的成员也不受它所在区域访问控制级别的约束。

7.3 类的其他特性

7.3.1 类成员再探

  • 可变数据成员(mutable):永远不会是const,即使它是const对象成员。在const函数内也可以改变其值。
class A
{
    mutable int b;
    void add() const
    {
        ++b;        // 成立,因为是mutable。
    }
}

7.3.3 类类型

  • 类声明和定义分开时,这种声明称作前向声明(forward declartion),在声明之后和定义之前是一个不完全类型(incomplete type),即知道是一个类,但不知道它包含那些成员。
class A;        // 仅声明A类

// 在这段区间,A是一个不完全类型。

class A     // 定义A类
{
    //...
}

7.3.4 友元再探

  • 类之间的友元关系:
class A
{
    friend class B;     // B可以访问A的私有部分。
}
  • 成员函数作为友元:
class A
{
    friend void B::b();     // B类的b函数,可以访问A的私有部分。
}
  • 友元声明和作用域:
struct X
{
    friend void f() {}      // 友元函数在类内部定义。但却不算声明。
    X() { f(); }            // 运行错误:f未声明。
    void g();
    void h();
};

void X::g() { return f(); } // 运行错误:f未3声明。
void f();                   // 声明f()。
void X::h() { return f(); } // 正确:f声明在作用域了。

7.4 类的作用域

7.4.1 名字查找与类的作用域

  • 编译器处理完类中的全部声明后,才会处理成员函数的定义。

7.5 构造函数再探

7.5.1 构造函数初始值列表

  • 对于常量对象或者引用,都必须初始化,在初始化列表中执行。
class C
{
    int i;
    const int ci;
    int &ri;

    C() : i(1), ci(i), ri(i) {}     // 正确的初始化方式。
};
  • 成员初始化的顺序和类中定义的出现顺序一致,所以初始化时应该按照成员定义顺序。
  • 如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。

7.5.2 委托构造函数

  • 先执行被委托的构造函数,再执行自己的构造函数。
class A
{
    A() : A(0) {}       调用该构造函数时,会先调用A(int i)构造函数,后执行自己函数体。
    A(int i) {}
}

7.5.4 隐式的类类型转换

  • 如果构造函数只接收一个实参,那它实际上定义了转换为此类型的隐式转换机制。
class C
{
public:
    C() = default;
    C(int i) { this->i = i; };

    int i;
};

void Output(C c)
{
    cout << endl << c.i << endl;
}

void main()
{
    Output(5);      // 实际创建了一个临时C类对象,使用了构造函数C(3),作为函数实参。
}
  • 可以通过将构造函数声明为explicit,阻止隐式转换。只能在类内声明构造函数时使用explict关键字。
  • explicit 构造函数只能用于直接初始化。即不能用于拷贝形式初始化。

7.5.5 聚合类(aggregate class)

  • 聚合类:用户可以直接访问其成员,具有特殊的初始化语法形式。类似C#的结构体。满足条件:
    • 所有成员都是public。
    • 没有定义任何构造函数。
    • 没有类内初始值。
    • 没有基类、没有virtual函数。

7.5.6 字面值常量类

  • 字面值常量类:数据成员都是字面类型的聚合类。满足条件:
    • 数据成员都必须是字面值类型。
    • 类必须至少含有一个constexpr构造函数。
    • 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是常量表达式;或者类自己的constexpr构造函数。
    • 类必须使用析构函数的默认定义。
class Debug
{
public:
    constexpr Debug(bool b) {}
};

7.6 类的静态成员

  • 静态成员函数不能声明成const的。
  • static关键字只能在类内部声明。
  • 因为静态成员不属于任何一个对象,所以不能在类内部初始化一个静态成员,必须在类的外部定义和初始化每个静态成员。除非是常量表达式或为const。
  • 静态数据成员的类型可以是其所属的类类型(不完全类型)。
class C
{
    static double rate = 6.4;       // 错误,不能在类内部初始化静态成员。
    static const int vecSize = 20;  // 正确,是const。
}

class D
{
    static D mem1;  // 正确,静态成员可以是不完全类型。
    D *mem2;        // 正确,指针也可以是。(引用也可以)
    D mem3;         // 错误,数据成员必须是完全类型。
};
  • 可以使用静态成员作为默认实参。
class E
{
    E init(int i = index);      // 正确。可以使用静态成员作为默认实参。
    static const int index;
}

猜你喜欢

转载自blog.csdn.net/l773575310/article/details/79131446