版权声明:本文为博主原创文章,未经博主允许不得转载。 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;
}