C++ 类和对象(完)

1. 再谈构造函数

1.1 构造函数体赋值 vs 初始化列表

在创建对象时,编译器通过调用构造函数给成员变量赋初值。 虽然在构造函数体内赋值可以让对象有初始值,但这被称为赋初值而非初始化。因为初始化只能进行一次,而函数体内可以多次赋值。

初始化列表:以冒号开始,接着是以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。

cpp 复制代码
class Date 
{ 
public: // 推荐:使用初始化列表 
Date(int year, int month, int day) 
: _year(year) 
, _month(month) 
, _day(day) {} 
private: 
int _year; 
int _month; 
int _day; 
};

必须使用初始化列表的情况:

  1. 引用成员变量

  2. const 成员变量

  3. 自定义类型成员(且该类没有默认构造函数时)

重要细节: 成员变量在类中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

  • 建议: 尽量使用初始化列表。对于自定义类型成员,无论是否写在列表中,编译器总会先尝试在列表中初始化它。

1.3 explicit 关键字

构造函数不仅用于初始化,对于单个参数 (或多参数但第一个参数外都有默认值)的构造函数,还具有隐式类型转换的作用。

  • 隐式转换示例: `Date d1 = 2023;` 编译器会用 2023 构造一个临时对象,再赋值给 d1。
  • explicit 的作用: 在构造函数前加上 `explicit` 关键字,可以禁止这种隐式类型转换,提高代码的可读性和安全性。

2. Static 成员 (Static Members)

2.1 概念与定义

声明为 `static` 的类成员称为类的静态成员。

  • 存储: 静态成员为所有类对象所共享,不属于某个具体对象,存放在静态区。
  • 定义: 静态成员变量必须在类外定义(初始化),类中只是声明。类外定义时不加 `static` 关键字。
  • 访问: 可以通过 类名::静态成员对象.静态成员 访问。

2.2 Static 成员函数

静态成员函数没有隐藏的 this 指针。

  • 限制: 不能访问任何非静态成员(因为没有 `this` 指针)。
  • 权限: 依然受 public/protected/private 访问限定符的限制。
cpp 复制代码
class A {
public:
    A() { ++_scount; } // 每创建一个对象,计数+1
    ~A() { --_scount; }
    static int GetACount() { return _scount; } // 静态函数获取静态变量
private:
    static int _scount; // 类内声明
};

int A::_scount = 0; // 类外定义及初始化 

3. 友元 (Friends)

友元提供了一种突破封装的方式,允许外部函数或类访问某个类的私有(private)和保护(protected)成员。虽然便利,但会增加耦合度,破坏封装,不宜滥用。

3.1 友元函数 (Friend Function)

友元函数是定义在类外部的普通函数,不属于任何类,但在类内部使用 `friend` 关键字声明。

  • 典型应用: 重载 `operator<<` 和 `operator>>`。
  • 痛点: 如果将 `<<` 重载为成员函数,`this` 指针会占据第一个参数位置,导致调用变成 `d1 << cout`,非常别扭。因此必须重载为全局函数。
  • 实现: 为了让全局函数能访问类的私有成员(如 `_year`),需要将其声明为友元。
cpp 复制代码
class Date {
    // 声明友元函数,它可以访问 Date 的私有成员
    friend ostream& operator<<(ostream& _cout, const Date& d);
    friend istream& operator>>(istream& _cin, Date& d);
public:
    Date(int y, int m, int d) : _year(y), _month(m), _day(d) {}
private:
    int _year;
    int _month;
    int _day;
};

// 类外定义,无需 friend 关键字,也无需 Date:: 限定符
ostream& operator<<(ostream& _cout, const Date& d) {
    _cout << d._year << "-" << d._month << "-" << d._day;
    return _cout;
} 

3.2 友元类 (Friend Class)

如果类 B 是类 A 的友元类,则类 B 的所有成员函数都可以访问类 A 的私有成员。

  • 单向性: B 是 A 的友元,不代表 A 是 B 的友元。
  • 无传递性: C 是 B 的友元,B 是 A 的友元,不能推断出 C 是 A 的友元。
  • 不能继承: 友元关系不能被继承。

4. 内部类 (Inner Class)

4.1 概念与特性

如果一个类定义在另一个类的内部,这个类就叫做内部类。

  • 独立性: 内部类是一个独立的类,不属于外部类。sizeof(外部类) 不包含内部类的大小。
  • 天生友元: 内部类天生就是外部类的友元。内部类可以直接访问外部类的 static 成员、枚举等,也可以通过外部类对象访问其私有成员。但反过来不行(外部类不是内部类的友元)。
  • 访问权限: 内部类可以定义在外部类的 public、protected、private 区域。
cpp 复制代码
class A {
private:
    static int k;
    int h;
public:
    class B { // B 是 A 的内部类,也是 A 的友元
    public:
    void foo(const A& a) {
            cout << k << endl;   // OK: 直接访问外部类 static
            cout << a.h << endl; // OK: 访问外部类私有成员
        }
    };
};
int A::k = 1;

int main() {
    A::B b; // 定义内部类对象
    b.foo(A());
    return 0;
}
相关推荐
wuminyu11 分钟前
HotSpot的轻量锁与膨胀后的ObjectMonitor状态重建原理
java·linux·c语言·jvm·c++
CoderYanger19 分钟前
A.每日一题:835. 图像重叠
java·开发语言·程序人生·leetcode·面试·职场和发展·学习方法
一只小透明啊啊啊啊26 分钟前
Socket、AIDL、SOME/IP
c++·mcu
需要82642 分钟前
JVM 内存区域与对象创建:一次 GC 从哪来
java·jvm·spring boot·spring·servlet·tomcat
伞伞悦读1 小时前
【第37期】Python JSON 与配置详解:序列化、反序列化、嵌套结构和配置文件
开发语言·python·json
凤山老林1 小时前
Spring Boot 应用 JVM 性能调优实战:GC 日志分析、堆内存规划与容器环境避坑
jvm·spring boot·gc·堆内存
CCCCCCCCharlie1 小时前
Linux进程控制四大核心操作
linux·开发语言
Tim_102 小时前
【LeetCode】338、比特位计数
c++·算法·leetcode
Brilliantwxx2 小时前
【STM32】 SPI Flash 驱动开发实战:阻塞模式驱动 W25Q64(含工程代码)
开发语言·stm32·单片机·嵌入式硬件
wuyk5552 小时前
【Socket 进阶之路】第 9 章 Linux 网络服务量产稳定性优化|心跳保活、TIME_WAIT、SO_LINGER、内存池、断线重连、完整异常防护框架
linux·服务器·开发语言·网络·物联网