第18章 类的高级特性

友元

友元是C++中一种特殊的访问控制机制,它允许特定的函数或类访问另一个类的私有成员和保护成员。虽然友元会破坏封装性,但在某些场景下,它可以提高代码的灵活性和性能。

友元函数

友元函数是指可以访问类的私有成员和保护成员的非成员函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>

class Box {
private:
double width;
double height;
double depth;

public:
Box(double w, double h, double d) : width(w), height(h), depth(d) {}

// 声明友元函数
friend double calculateVolume(const Box& box);
friend void displayBox(const Box& box);
};

// 定义友元函数
double calculateVolume(const Box& box) {
return box.width * box.height * box.depth;
}

void displayBox(const Box& box) {
std::cout << "Width: " << box.width << std::endl;
std::cout << "Height: " << box.height << std::endl;
std::cout << "Depth: " << box.depth << std::endl;
std::cout << "Volume: " << calculateVolume(box) << std::endl;
}

int main() {
Box myBox(10.0, 20.0, 30.0);
displayBox(myBox);
return 0;
}

友元类

一个类可以声明另一个类为友元,这样友元类的所有成员函数都可以访问原类的私有成员和保护成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>

class Box {
private:
double width;
double height;
double depth;

public:
Box(double w, double h, double d) : width(w), height(h), depth(d) {}

// 声明友元类
friend class BoxCalculator;
};

class BoxCalculator {
public:
double calculateVolume(const Box& box) {
return box.width * box.height * box.depth;
}

double calculateSurfaceArea(const Box& box) {
return 2 * (box.width * box.height + box.width * box.depth + box.height * box.depth);
}

void displayBoxInfo(const Box& box) {
std::cout << "Width: " << box.width << std::endl;
std::cout << "Height: " << box.height << std::endl;
std::cout << "Depth: " << box.depth << std::endl;
std::cout << "Volume: " << calculateVolume(box) << std::endl;
std::cout << "Surface Area: " << calculateSurfaceArea(box) << std::endl;
}
};

int main() {
Box myBox(10.0, 20.0, 30.0);
BoxCalculator calculator;
calculator.displayBoxInfo(myBox);
return 0;
}

成员函数作为友元

一个类的成员函数可以被声明为另一个类的友元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>

class Box;

class BoxCalculator {
public:
double calculateVolume(const Box& box);
double calculateSurfaceArea(const Box& box);
};

class Box {
private:
double width;
double height;
double depth;

public:
Box(double w, double h, double d) : width(w), height(h), depth(d) {}

// 声明BoxCalculator的成员函数为友元
friend double BoxCalculator::calculateVolume(const Box& box);
friend double BoxCalculator::calculateSurfaceArea(const Box& box);

void display() {
std::cout << "Width: " << width << std::endl;
std::cout << "Height: " << height << std::endl;
std::cout << "Depth: " << depth << std::endl;
}
};

// 定义BoxCalculator的成员函数
double BoxCalculator::calculateVolume(const Box& box) {
return box.width * box.height * box.depth;
}

double BoxCalculator::calculateSurfaceArea(const Box& box) {
return 2 * (box.width * box.height + box.width * box.depth + box.height * box.depth);
}

int main() {
Box myBox(10.0, 20.0, 30.0);
BoxCalculator calculator;

myBox.display();
std::cout << "Volume: " << calculator.calculateVolume(myBox) << std::endl;
std::cout << "Surface Area: " << calculator.calculateSurfaceArea(myBox) << std::endl;

return 0;
}

友元的底层实现

  1. 友元的编译期处理

    • 友元关系是在编译期确定的,不是运行时
    • 编译器在处理类定义时,会记录友元声明
    • 当编译友元函数或友元类的成员函数时,会检查是否有访问权限
  2. 友元与封装性

    • 友元破坏了封装性,允许外部代码访问私有成员
    • 但友元关系是单向的,A是B的友元不意味着B是A的友元
    • 友元关系也不具有传递性,A是B的友元,B是C的友元,不意味着A是C的友元
  3. 友元与继承

    • 友元关系不会被继承,派生类不会继承基类的友元关系
    • 基类的友元不会自动成为派生类的友元

友元的性能影响

  1. 访问速度

    • 友元函数直接访问私有成员,避免了通过公共接口的函数调用开销
    • 对于频繁调用的函数,使用友元可以提高性能
  2. 内联可能性

    • 友元函数通常定义在头文件中,更容易被编译器内联
    • 内联友元函数可以进一步减少函数调用开销
  3. 内存布局

    • 友元关系不影响类的内存布局
    • 类的大小和成员变量的布局不受友元声明的影响

友元的最佳实践

  1. 最小化友元使用

    • 只在必要时使用友元,优先考虑公共接口
    • 友元应该是特例,不是常规做法
  2. 友元的作用域

    • 尽可能将友元声明限制在最小必要的范围内
    • 优先使用成员函数作为友元,而不是整个类作为友元
  3. 友元与运算符重载

    • 对于需要访问两个对象私有成员的运算符重载(如+、-等),友元函数是理想选择
    • 对于单目运算符(如++、–等),成员函数通常更合适
  4. 友元与工厂模式

    • 在工厂模式中,工厂类需要创建产品类的实例并初始化其私有成员
    • 此时,将工厂类声明为产品类的友元是合理的
  5. 友元与测试

    • 在单元测试中,可以将测试类声明为被测类的友元
    • 这样测试代码可以直接访问被测类的私有成员,进行更全面的测试

友元的高级应用

  1. 友元与模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    template <typename T>
    class Pair {
    private:
    T first;
    T second;

    public:
    Pair(T f, T s) : first(f), second(s) {}

    // 模板友元函数
    template <typename U>
    friend std::ostream& operator<<(std::ostream& os, const Pair<U>& pair);
    };

    template <typename U>
    std::ostream& operator<<(std::ostream& os, const Pair<U>& pair) {
    os << "(" << pair.first << ", " << pair.second << ")";
    return os;
    }
  2. 友元与命名空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    namespace Math {
    class Vector3D {
    private:
    double x, y, z;

    public:
    Vector3D(double x, double y, double z) : x(x), y(y), z(z) {}

    // 声明另一个命名空间中的函数为友元
    friend Vector3D Physics::calculateForce(const Vector3D&);
    };
    }

    namespace Physics {
    Vector3D calculateForce(const Math::Vector3D& velocity) {
    // 可以访问Vector3D的私有成员
    double mass = 1.0;
    return Math::Vector3D(mass * velocity.x, mass * velocity.y, mass * velocity.z);
    }
    }
  3. 友元与CRTP(奇异递归模板模式)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    template <typename Derived>
    class Base {
    private:
    int value;

    public:
    Base(int v) : value(v) {}

    void doSomething() {
    static_cast<Derived*>(this)->implementation();
    }

    // 允许派生类访问私有成员
    friend Derived;
    };

    class Derived : public Base<Derived> {
    public:
    Derived(int v) : Base<Derived>(v) {}

    void implementation() {
    // 可以访问Base的私有成员
    std::cout << "Derived implementation, value: " << value << std::endl;
    }
    };

友元的替代方案

  1. 公共接口

    • 优先提供公共getter和setter方法
    • 虽然有函数调用开销,但保持了封装性
  2. 委托

    • 将需要访问私有成员的操作委托给类的成员函数
    • 外部代码调用公共成员函数,由它内部处理私有成员
  3. 内部类

    • 在类内部定义一个辅助类,处理需要访问私有成员的操作
    • 内部类自动具有访问外部类私有成员的权限
  4. PIMPL(指针to实现)模式

    • 将私有成员放在一个单独的实现类中
    • 公共类持有实现类的指针
    • 这种模式可以减少编译依赖,同时保持封装性

友元是C++中一种强大但有争议的特性。正确使用友元可以提高代码的灵活性和性能,但过度使用会破坏封装性,使代码难以维护。在实际开发中,应该谨慎使用友元,权衡其利弊,并遵循最佳实践。

类的静态成员

静态成员是C++中一种特殊的成员,它属于类而不是对象,所有对象共享同一个静态成员。静态成员在面向对象编程中有着广泛的应用,如计数器、配置信息、单例模式等。

静态成员变量

静态成员变量是类的所有对象共享的变量,不属于任何单个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>

class Counter {
private:
static int count; // 静态成员变量声明
int id;

public:
Counter() {
id = ++count;
}

int getID() const {
return id;
}

static int getCount() {
return count;
}
};

// 静态成员变量定义和初始化
int Counter::count = 0;

int main() {
std::cout << "Initial count: " << Counter::getCount() << std::endl;

Counter c1;
std::cout << "c1 ID: " << c1.getID() << std::endl;
std::cout << "Count after c1: " << Counter::getCount() << std::endl;

Counter c2;
std::cout << "c2 ID: " << c2.getID() << std::endl;
std::cout << "Count after c2: " << Counter::getCount() << std::endl;

Counter c3;
std::cout << "c3 ID: " << c3.getID() << std::endl;
std::cout << "Count after c3: " << Counter::getCount() << std::endl;

return 0;
}

静态成员函数

静态成员函数属于类而不是对象,可以在没有对象的情况下调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include <string>

class Student {
private:
std::string name;
int id;
static int nextID; // 静态成员变量,用于生成唯一ID

public:
Student(const std::string& n) : name(n), id(nextID++) {}

void display() const {
std::cout << "Name: " << name << ", ID: " << id << std::endl;
}

static int getNextID() {
return nextID;
}

static void resetID() {
nextID = 1;
}
};

// 静态成员变量初始化
int Student::nextID = 1;

int main() {
std::cout << "Next ID before creating students: " << Student::getNextID() << std::endl;

Student s1("Alice");
s1.display();

Student s2("Bob");
s2.display();

Student s3("Charlie");
s3.display();

std::cout << "Next ID after creating students: " << Student::getNextID() << std::endl;

Student::resetID();
std::cout << "Next ID after reset: " << Student::getNextID() << std::endl;

Student s4("David");
s4.display();

return 0;
}

静态成员的底层实现

  1. 静态成员变量的存储

    • 静态成员变量存储在全局数据区,而不是对象的内存空间中
    • 它们在程序启动时分配内存,程序结束时释放内存
    • 所有对象共享同一个静态成员变量的实例
  2. 静态成员函数的实现

    • 静态成员函数不包含this指针
    • 它们在内存中只有一份拷贝,与普通函数类似
    • 静态成员函数不能访问非静态成员,因为没有this指针
  3. 静态成员的初始化

    • 静态成员变量必须在类外部定义和初始化
    • 初始化顺序在同一个编译单元内是确定的,但在不同编译单元间是不确定的
    • C++17引入了内联静态成员变量,可以在类内部初始化

静态成员的线程安全性

  1. 静态成员变量的线程安全问题

    • 多个线程同时访问和修改静态成员变量可能导致竞态条件
    • 需要使用互斥锁或其他同步机制来保护
  2. 线程安全的静态成员

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #include <mutex>

    class ThreadSafeCounter {
    private:
    static int count;
    static std::mutex mtx;

    public:
    ThreadSafeCounter() {
    std::lock_guard<std::mutex> lock(mtx);
    ++count;
    }

    ~ThreadSafeCounter() {
    std::lock_guard<std::mutex> lock(mtx);
    --count;
    }

    static int getCount() {
    std::lock_guard<std::mutex> lock(mtx);
    return count;
    }
    };

    // 初始化静态成员
    int ThreadSafeCounter::count = 0;
    std::mutex ThreadSafeCounter::mtx;
  3. C++11+的线程安全初始化

    • C++11标准保证静态局部变量的初始化是线程安全的
    • 这使得Meyer’s单例模式成为实现线程安全单例的最佳选择

静态成员的内存布局

  1. 类的内存布局

    • 静态成员变量不占用类的内存空间
    • 类的大小由非静态成员变量决定
    • 静态成员函数也不占用类的内存空间
  2. 静态成员的地址

    • 静态成员变量有固定的内存地址,在程序启动时分配
    • 可以通过&ClassName::staticMember获取静态成员变量的地址
    • 静态成员函数的地址可以通过&ClassName::staticMemberFunction获取
  3. 静态成员与虚函数

    • 静态成员函数不能是虚函数
    • 因为虚函数的调用需要this指针,而静态成员函数没有this指针

静态成员的高级应用

  1. 内联静态成员变量(C++17+)

    1
    2
    3
    4
    5
    6
    class Config {
    public:
    // C++17+:内联静态成员变量,可以在类内部初始化
    inline static const std::string version = "1.0.0";
    inline static const int maxConnections = 100;
    };
  2. 静态成员与模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    template <typename T>
    class Array {
    private:
    static size_t objectCount;
    T* data;
    size_t size;

    public:
    Array(size_t s) : size(s), data(new T[s]) {
    ++objectCount;
    }

    ~Array() {
    delete[] data;
    --objectCount;
    }

    static size_t getObjectCount() {
    return objectCount;
    }
    };

    // 为每个模板实例化定义静态成员变量
    template <typename T>
    size_t Array<T>::objectCount = 0;
  3. 静态成员与单例模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    class Singleton {
    private:
    static Singleton* instance;
    int value;

    // 私有构造函数,防止外部创建实例
    Singleton() : value(0) {}

    // 防止拷贝和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    public:
    static Singleton* getInstance() {
    if (instance == nullptr) {
    instance = new Singleton();
    }
    return instance;
    }

    static void destroyInstance() {
    delete instance;
    instance = nullptr;
    }

    void setValue(int v) {
    value = v;
    }

    int getValue() const {
    return value;
    }
    };

    // 初始化静态成员变量
    Singleton* Singleton::instance = nullptr;
  4. Meyer’s 单例模式(线程安全)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Singleton {
    private:
    int value;

    // 私有构造函数
    Singleton() : value(0) {}

    public:
    // 静态成员函数返回静态局部变量
    static Singleton& getInstance() {
    static Singleton instance; // C++11+保证线程安全的初始化
    return instance;
    }

    void setValue(int v) {
    value = v;
    }

    int getValue() const {
    return value;
    }

    // 防止拷贝和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    };

静态成员的性能考虑

  1. 内存使用

    • 静态成员变量在全局数据区只存储一份,节省内存
    • 对于频繁使用的常量,使用静态成员可以避免重复创建
  2. 访问速度

    • 静态成员变量的访问速度与全局变量相当,比成员变量稍快
    • 静态成员函数的调用速度与普通函数相当,比成员函数稍快(因为没有this指针)
  3. 初始化开销

    • 静态成员变量在程序启动时初始化,增加了程序的启动时间
    • 对于大型静态对象,初始化开销可能较大

静态成员的最佳实践

  1. 合理使用静态成员

    • 对于所有对象共享的数据,使用静态成员变量
    • 对于与对象状态无关的操作,使用静态成员函数
    • 避免过度使用静态成员,以免破坏面向对象的封装性
  2. 静态成员的命名约定

    • 使用大写字母或特定前缀标识静态成员
    • 提高代码的可读性和可维护性
  3. 静态成员的初始化

    • 确保静态成员变量在使用前正确初始化
    • 对于依赖于其他静态成员的情况,注意初始化顺序
    • 优先使用C++17的内联静态成员变量,简化代码
  4. 静态成员与继承

    • 静态成员在继承层次结构中是共享的
    • 派生类可以访问基类的静态成员
    • 派生类可以隐藏基类的静态成员,但不能覆盖它们
  5. 静态成员的测试

    • 静态成员的状态会在测试之间保持
    • 在单元测试中,需要确保静态成员的状态不会影响其他测试
    • 考虑在测试前后重置静态成员的状态

静态成员的常见应用场景

  1. 计数器:跟踪类的实例数量
  2. 配置信息:存储应用程序的配置参数
  3. 常量定义:定义类级别的常量
  4. 单例模式:确保类只有一个实例
  5. 工厂模式:创建对象的工厂方法
  6. 工具函数:与对象状态无关的工具方法
  7. 缓存:存储共享的缓存数据
  8. 全局状态:管理应用程序的全局状态

静态成员是C++中一种强大的特性,它提供了一种在类级别共享数据和行为的机制。正确使用静态成员可以提高代码的效率和可维护性,但过度使用可能会导致代码难以理解和测试。在实际编程中,应该根据具体情况权衡使用静态成员的利弊。

类的常量成员

常量成员是C++中实现常量正确性(Const Correctness)的重要机制,它确保了对象在不同上下文中的正确使用。常量成员包括常量成员变量和常量成员函数。

常量成员变量

常量成员变量必须在构造函数的初始化列表中初始化,且在对象的生命周期内不能改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>

class Circle {
private:
const double PI; // 常量成员变量
double radius;

public:
// 构造函数初始化列表
Circle(double r) : PI(3.14159), radius(r) {}

double getArea() const {
return PI * radius * radius;
}

double getCircumference() const {
return 2 * PI * radius;
}

void setRadius(double r) {
radius = r;
}

double getRadius() const {
return radius;
}
};

int main() {
Circle c(5.0);
std::cout << "Radius: " << c.getRadius() << std::endl;
std::cout << "Area: " << c.getArea() << std::endl;
std::cout << "Circumference: " << c.getCircumference() << std::endl;

c.setRadius(10.0);
std::cout << "\nAfter changing radius: " << std::endl;
std::cout << "Radius: " << c.getRadius() << std::endl;
std::cout << "Area: " << c.getArea() << std::endl;
std::cout << "Circumference: " << c.getCircumference() << std::endl;

return 0;
}

常量成员函数

常量成员函数承诺不会修改对象的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <string>

class Person {
private:
std::string name;
int age;

public:
Person(const std::string& n, int a) : name(n), age(a) {}

// 常量成员函数
void display() const {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}

// 非常量成员函数
void setName(const std::string& n) {
name = n;
}

void setAge(int a) {
age = a;
}

// 常量成员函数
std::string getName() const {
return name;
}

int getAge() const {
return age;
}
};

int main() {
Person p1("Alice", 30);
p1.display();

p1.setName("Bob");
p1.setAge(25);
p1.display();

// 常量对象
const Person p2("Charlie", 35);
p2.display(); // 可以调用常量成员函数
// p2.setName("David"); // 错误:不能调用非常量成员函数
// p2.setAge(40); // 错误:不能调用非常量成员函数

std::cout << "p2's name: " << p2.getName() << std::endl;
std::cout << "p2's age: " << p2.getAge() << std::endl;

return 0;
}

常量成员的底层实现

  1. 常量成员变量的存储

    • 常量成员变量与普通成员变量存储在同一内存区域
    • 它们占用对象的内存空间,增加对象的大小
    • 编译器会在编译期检查对常量成员变量的修改
  2. 常量成员函数的实现

    • 常量成员函数的this指针类型是const ClassName* const
    • 非常量成员函数的this指针类型是ClassName* const
    • 这种类型差异使得常量成员函数无法修改对象的状态
  3. mutable关键字

    • mutable关键字允许在常量成员函数中修改特定的成员变量
    • mutable修饰的成员变量不受常量成员函数的限制
    • 通常用于缓存、计数等不影响对象逻辑状态的成员

常量的传递性

  1. const与函数参数

    • 常量引用参数可以接受常量和非常量实参
    • 非常量引用参数只能接受非常量实参
    • 按值传递的参数不受const修饰符的影响
  2. const与函数返回值

    • 返回常量值可以防止返回值被意外修改
    • 返回常量引用可以防止通过引用修改原对象
    • 对于内置类型,返回const值没有实际意义
  3. const与成员函数链

    • 常量成员函数只能调用其他常量成员函数
    • 非常量成员函数可以调用常量和非常量成员函数
    • 这确保了常量对象的状态不会被修改

常量成员的高级应用

  1. const与运算符重载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class Vector2D {
    private:
    double x, y;

    public:
    Vector2D(double x = 0, double y = 0) : x(x), y(y) {}

    // 常量成员函数重载运算符+
    Vector2D operator+(const Vector2D& other) const {
    return Vector2D(x + other.x, y + other.y);
    }

    // 非常量成员函数重载运算符+
    Vector2D& operator+=(const Vector2D& other) {
    x += other.x;
    y += other.y;
    return *this;
    }

    // 常量成员函数重载运算符==
    bool operator==(const Vector2D& other) const {
    return x == other.x && y == other.y;
    }
    };
  2. const与指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Data {
    private:
    int value;

    public:
    Data(int v) : value(v) {}

    // 返回常量指针
    const int* getValuePtr() const {
    return &value;
    }

    // 返回非常量指针
    int* getValuePtr() {
    return &value;
    }
    };
  3. const与智能指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Resource {
    private:
    int value;

    public:
    Resource(int v) : value(v) {}

    int getValue() const {
    return value;
    }

    void setValue(int v) {
    value = v;
    }
    };

    // 指向常量对象的智能指针
    std::unique_ptr<const Resource> constResource = std::make_unique<Resource>(42);
    // constResource->setValue(100); // 错误:不能修改常量对象
    std::cout << constResource->getValue() << std::endl; // 允许

    // 常量智能指针
    const std::unique_ptr<Resource> resourcePtr = std::make_unique<Resource>(42);
    // resourcePtr = std::make_unique<Resource>(100); // 错误:不能修改常量智能指针
    resourcePtr->setValue(100); // 允许:可以修改指向的对象

常量成员的性能考虑

  1. 编译器优化

    • const成员函数提供了更强的不变性保证
    • 编译器可以进行更多的优化,如常量折叠、函数内联等
    • 常量对象的访问可能会被优化为直接访问内存
  2. 线程安全

    • 常量成员函数通常是线程安全的(如果没有修改mutable成员)
    • 多个线程可以同时调用常量成员函数
    • 这使得常量成员函数成为多线程编程中的理想选择
  3. 代码可读性

    • const限定符明确了函数的行为
    • 提高了代码的可读性和可维护性
    • 使代码的意图更加清晰

常量成员的最佳实践

  1. 尽可能使用const

    • 对于不修改对象状态的成员函数,添加const限定符
    • 对于不修改参数的函数参数,添加const限定符
    • 对于不修改返回值的函数,考虑返回const值
  2. const与重载

    • 为const和非const对象提供适当的重载版本
    • 确保const版本和非const版本的行为一致
    • 优先实现const版本,然后让非const版本调用const版本
  3. 合理使用mutable

    • 只对真正需要在const成员函数中修改的成员变量使用mutable
    • 通常用于缓存、计数等不影响对象逻辑状态的成员
    • 避免过度使用mutable,以免破坏常量正确性
  4. const与引用传递

    • 对于大对象,优先使用const引用传递
    • 避免不必要的拷贝,同时确保对象不被修改
    • 这是一种高效且安全的参数传递方式
  5. const与移动语义

    • 注意const对象不能被移动,只能被拷贝
    • 移动语义需要修改源对象,与const语义冲突
    • 对于需要移动的对象,不要声明为const

常量正确性的重要性

  1. 编译期错误检测

    • const限定符可以在编译期检测到潜在的错误
    • 避免了运行时错误的可能性
    • 提高了代码的安全性和可靠性
  2. 接口清晰性

    • const限定符明确了接口的契约
    • 告诉用户哪些函数会修改对象,哪些不会
    • 提高了代码的可理解性和可维护性
  3. 优化机会

    • const限定符为编译器提供了更多的优化机会
    • 可以生成更高效的机器代码
    • 提高了程序的性能
  4. 线程安全性

    • const成员函数天然具有更好的线程安全性
    • 减少了多线程编程中的同步开销
    • 提高了程序的并发性能

常量正确性是C++编程中的重要概念,它通过编译期检查确保了对象的正确使用,提高了代码的安全性、可读性和可维护性。正确理解和使用常量成员,对于编写高效、安全、可维护的C++代码至关重要。

类的类型转换

类的类型转换是C++中一种强大的特性,它允许在不同类型之间进行转换,包括从其他类型转换为类类型(转换构造函数)和从类类型转换为其他类型(类型转换运算符)。

转换构造函数

转换构造函数是一个参数的构造函数,用于将其他类型转换为类类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <string>

class Distance {
private:
double meters;

public:
// 默认构造函数
Distance() : meters(0) {}

// 转换构造函数(从double转换为Distance)
Distance(double m) : meters(m) {
std::cout << "Conversion constructor called" << std::endl;
}

// 显式转换构造函数(C++11+)
explicit Distance(int km) : meters(km * 1000) {
std::cout << "Explicit conversion constructor called" << std::endl;
}

double getMeters() const {
return meters;
}

void display() const {
std::cout << "Distance: " << meters << " meters" << std::endl;
}
};

void printDistance(Distance d) {
d.display();
}

int main() {
// 使用默认构造函数
Distance d1;
d1.display();

// 直接初始化
Distance d2(100.5);
d2.display();

// 隐式转换(从double到Distance)
Distance d3 = 200.75; // 调用转换构造函数
d3.display();

// 函数参数的隐式转换
printDistance(300.25); // 调用转换构造函数

// 显式转换(从int到Distance)
Distance d4 = Distance(5); // 调用显式转换构造函数
d4.display();

// 错误:不能隐式转换,因为构造函数是explicit的
// Distance d5 = 10; // 编译错误

return 0;
}

类型转换运算符

类型转换运算符用于将类类型转换为其他类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>

class Temperature {
private:
double celsius;

public:
Temperature(double c) : celsius(c) {}

double getCelsius() const {
return celsius;
}

// 类型转换运算符:转换为double(华氏度)
operator double() const {
return celsius * 9.0 / 5.0 + 32.0;
}

// 类型转换运算符:转换为bool
explicit operator bool() const {
return celsius > 0;
}
};

int main() {
Temperature t(25.0);
std::cout << "Celsius: " << t.getCelsius() << std::endl;

// 隐式转换为double(华氏度)
double fahrenheit = t;
std::cout << "Fahrenheit: " << fahrenheit << std::endl;

// 显式转换为bool
if (static_cast<bool>(t)) {
std::cout << "Temperature is above freezing" << std::endl;
} else {
std::cout << "Temperature is at or below freezing" << std::endl;
}

// 测试低于冰点的温度
Temperature t2(-5.0);
std::cout << "\nCelsius: " << t2.getCelsius() << std::endl;
fahrenheit = t2;
std::cout << "Fahrenheit: " << fahrenheit << std::endl;

if (static_cast<bool>(t2)) {
std::cout << "Temperature is above freezing" << std::endl;
} else {
std::cout << "Temperature is at or below freezing" << std::endl;
}

return 0;
}

类型转换的底层实现

  1. 转换构造函数的实现

    • 转换构造函数是一个特殊的构造函数,只有一个参数
    • 当编译器需要将其他类型转换为类类型时,会调用转换构造函数
    • 转换构造函数的调用是在编译期决定的
  2. 类型转换运算符的实现

    • 类型转换运算符是一个特殊的成员函数,没有返回类型
    • 当编译器需要将类类型转换为其他类型时,会调用类型转换运算符
    • 类型转换运算符的调用也是在编译期决定的
  3. 隐式转换与显式转换

    • 隐式转换:编译器自动进行的转换
    • 显式转换:使用static_cast、dynamic_cast等运算符进行的转换
    • explicit关键字可以防止隐式转换,只允许显式转换

类型转换的性能考虑

  1. 转换开销

    • 类型转换可能会产生开销,特别是当转换涉及到复杂的计算或内存分配时
    • 应尽量减少不必要的类型转换
    • 对于频繁执行的代码路径,应考虑避免类型转换
  2. 内联可能性

    • 类型转换运算符和转换构造函数通常是内联的
    • 内联可以减少函数调用的开销
    • 对于简单的转换,内联可以完全消除转换开销
  3. 移动语义

    • 类型转换时可以使用移动语义来减少拷贝开销
    • 例如,在转换构造函数中使用移动构造来初始化成员变量

类型转换的最佳实践

  1. 使用explicit关键字

    • 对于转换构造函数和类型转换运算符,优先使用explicit关键字
    • 这可以防止意外的隐式转换,提高代码的安全性
    • 只在确实需要隐式转换的情况下省略explicit关键字
  2. 避免歧义转换

    • 确保类型转换不会产生歧义
    • 避免定义多个可能导致歧义的转换路径
    • 使用explicit关键字可以减少歧义的可能性
  3. 转换的语义正确性

    • 类型转换应该具有直观的语义
    • 转换后的结果应该符合用户的预期
    • 避免定义语义不明确的类型转换
  4. 性能优化

    • 对于频繁执行的类型转换,应优化转换的性能
    • 考虑使用内联、移动语义等技术减少转换开销
    • 对于复杂的转换,考虑缓存转换结果

类型转换的高级应用

  1. 转换构造函数与运算符重载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class Complex {
    private:
    double real;
    double imag;

    public:
    // 转换构造函数
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // 转换构造函数(从int转换)
    Complex(int r) : real(r), imag(0) {}

    // 运算符重载
    Complex operator+(const Complex& other) const {
    return Complex(real + other.real, imag + other.imag);
    }

    // 类型转换运算符
    operator double() const {
    return real; // 返回实部
    }
    };

    // 使用
    Complex c1 = 5; // 隐式转换:int -> Complex
    Complex c2 = 3.14; // 隐式转换:double -> Complex
    Complex c3 = c1 + c2; // 运算符重载
    double realPart = c3; // 隐式转换:Complex -> double
  2. 类型转换与模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    template <typename T>
    class Wrapper {
    private:
    T value;

    public:
    Wrapper(const T& v) : value(v) {}

    // 转换构造函数:从其他Wrapper类型转换
    template <typename U>
    Wrapper(const Wrapper<U>& other) : value(static_cast<T>(other.getValue())) {}

    T getValue() const {
    return value;
    }

    // 类型转换运算符:转换为其他类型
    template <typename U>
    operator U() const {
    return static_cast<U>(value);
    }
    };
  3. 安全的布尔转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    class SafeBool {
    private:
    bool value;

    // 私有类型,用于安全的布尔转换
    struct BoolType {
    int dummy;
    };

    public:
    SafeBool(bool v) : value(v) {}

    // 安全的布尔转换运算符
    operator BoolType*() const {
    return value ? reinterpret_cast<BoolType*>(1) : nullptr;
    }

    // 防止与其他类型的比较
    bool operator!() const {
    return !value;
    }
    };

    // 使用
    SafeBool b1(true);
    SafeBool b2(false);

    if (b1) {
    std::cout << "b1 is true" << std::endl;
    }

    if (!b2) {
    std::cout << "b2 is false" << std::endl;
    }

    // 错误:不能与整数比较
    // if (b1 == 1) {}

类型转换的陷阱

  1. 意外的隐式转换

    • 隐式转换可能会导致意外的行为
    • 例如,一个接受double参数的函数可能会意外地接受一个自定义类型的对象
    • 使用explicit关键字可以防止这种情况
  2. 转换歧义

    • 当存在多个可能的转换路径时,会产生歧义
    • 例如,一个类同时有转换构造函数和类型转换运算符
    • 应避免定义可能导致歧义的转换
  3. 转换链

    • 编译器最多只会进行一次用户定义的类型转换
    • 例如,A -> B -> C 的转换不会自动进行
    • 需要显式地进行每一步转换
  4. 性能问题

    • 频繁的类型转换可能会影响性能
    • 特别是当转换涉及到复杂的计算或内存分配时
    • 应尽量减少不必要的类型转换

类型转换与现代C++

  1. C++11的显式转换运算符

    • C++11引入了explicit关键字用于类型转换运算符
    • 这使得类型转换运算符的行为与转换构造函数一致
    • 推荐使用explicit关键字来防止意外的隐式转换
  2. C++11的移动语义

    • 移动语义可以用于类型转换,减少拷贝开销
    • 例如,在转换构造函数中使用移动构造来初始化成员变量
  3. C++17的保证拷贝省略

    • C++17引入了保证拷贝省略,减少了类型转换中的拷贝开销
    • 这使得返回值优化更加可靠

类型转换的设计原则

  1. 最小惊讶原则

    • 类型转换的行为应该符合用户的预期
    • 避免定义令人惊讶的类型转换
  2. 显式优于隐式

    • 优先使用显式转换,而不是隐式转换
    • 这可以提高代码的可读性和安全性
  3. 单一职责原则

    • 类型转换应该只负责转换,不应该执行其他操作
    • 避免在类型转换中执行复杂的计算或副作用
  4. 性能考虑

    • 类型转换应该尽可能高效
    • 对于频繁执行的转换,应优化其性能

类型转换是C++中一种强大的特性,它可以使代码更加灵活和直观。然而,类型转换也可能导致意外的行为和性能问题。正确理解和使用类型转换,对于编写高效、安全、可维护的C++代码至关重要。

类的嵌套和局部类

类的嵌套和局部类是C++中两种特殊的类定义方式,它们提供了一种封装和组织代码的机制,特别是在处理复杂的类层次结构时非常有用。

嵌套类

嵌套类是在另一个类的内部定义的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <iostream>
#include <vector>

class Stack {
private:
// 嵌套类:节点
class Node {
public:
int data;
Node* next;

Node(int d, Node* n = nullptr) : data(d), next(n) {}
};

Node* top;
int size;

public:
Stack() : top(nullptr), size(0) {}

~Stack() {
while (!isEmpty()) {
pop();
}
}

void push(int value) {
top = new Node(value, top);
size++;
}

int pop() {
if (isEmpty()) {
throw std::runtime_error("Stack underflow");
}

int value = top->data;
Node* temp = top;
top = top->next;
delete temp;
size--;

return value;
}

int peek() const {
if (isEmpty()) {
throw std::runtime_error("Stack is empty");
}
return top->data;
}

bool isEmpty() const {
return top == nullptr;
}

int getSize() const {
return size;
}

void display() const {
if (isEmpty()) {
std::cout << "Stack is empty" << std::endl;
return;
}

std::cout << "Stack contents (top to bottom): " << std::endl;
Node* current = top;
while (current != nullptr) {
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
}
};

int main() {
Stack s;

s.push(10);
s.push(20);
s.push(30);
s.push(40);

std::cout << "Stack size: " << s.getSize() << std::endl;
s.display();

std::cout << "Top element: " << s.peek() << std::endl;

std::cout << "Popped: " << s.pop() << std::endl;
std::cout << "Popped: " << s.pop() << std::endl;

std::cout << "Stack size after pops: " << s.getSize() << std::endl;
s.display();

s.push(50);
s.push(60);

std::cout << "Stack size after pushes: " << s.getSize() << std::endl;
s.display();

return 0;
}

局部类

局部类是在函数内部定义的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <vector>

void processNumbers(const std::vector<int>& numbers) {
// 局部类:用于处理和存储结果
class Result {
private:
int sum;
double average;
int min;
int max;

public:
Result(const std::vector<int>& nums) {
if (nums.empty()) {
sum = 0;
average = 0;
min = 0;
max = 0;
return;
}

sum = 0;
min = nums[0];
max = nums[0];

for (int num : nums) {
sum += num;
if (num < min) min = num;
if (num > max) max = num;
}

average = static_cast<double>(sum) / nums.size();
}

void display() const {
std::cout << "Sum: " << sum << std::endl;
std::cout << "Average: " << average << std::endl;
std::cout << "Minimum: " << min << std::endl;
std::cout << "Maximum: " << max << std::endl;
}
};

// 创建局部类的对象
Result result(numbers);
result.display();
}

int main() {
std::vector<int> nums = {10, 20, 30, 40, 50};
std::cout << "Processing numbers: 10, 20, 30, 40, 50" << std::endl;
processNumbers(nums);

std::vector<int> emptyNums;
std::cout << "\nProcessing empty vector" << std::endl;
processNumbers(emptyNums);

return 0;
}

嵌套类的底层实现

  1. 嵌套类的作用域

    • 嵌套类的作用域被限制在外部类内部
    • 外部类可以直接访问嵌套类的成员,包括私有成员
    • 嵌套类不能直接访问外部类的非静态成员,因为它没有外部类的实例
  2. 嵌套类的内存布局

    • 嵌套类是一个独立的类,有自己的内存布局
    • 嵌套类的大小不影响外部类的大小
    • 嵌套类的对象存储在独立的内存空间中
  3. 嵌套类的访问控制

    • 嵌套类的访问权限由其在外部类中的声明位置决定
    • 如果嵌套类声明在外部类的private部分,那么只有外部类可以访问它
    • 如果嵌套类声明在外部类的public部分,那么任何代码都可以通过外部类访问它

局部类的底层实现

  1. 局部类的作用域

    • 局部类的作用域被限制在定义它的函数内部
    • 局部类不能在函数外部被访问
    • 局部类的名称在函数外部不可见
  2. 局部类的限制

    • 局部类不能有静态成员
    • 局部类只能访问函数中的静态变量和外部变量
    • 局部类的成员函数必须在类内部定义
  3. 局部类的内存管理

    • 局部类的对象在函数内部创建,存储在栈上
    • 局部类的对象在函数返回时被销毁
    • 局部类的析构函数会在对象销毁时被调用

嵌套类的高级应用

  1. 嵌套类与封装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    class LinkedList {
    private:
    // 嵌套的节点类
    class Node {
    public:
    int data;
    Node* next;

    Node(int d) : data(d), next(nullptr) {}
    };

    Node* head;

    public:
    LinkedList() : head(nullptr) {}

    void add(int data) {
    Node* newNode = new Node(data);
    if (!head) {
    head = newNode;
    } else {
    Node* current = head;
    while (current->next) {
    current = current->next;
    }
    current->next = newNode;
    }
    }

    void display() const {
    Node* current = head;
    while (current) {
    std::cout << current->data << " ";
    current = current->next;
    }
    std::cout << std::endl;
    }

    ~LinkedList() {
    while (head) {
    Node* temp = head;
    head = head->next;
    delete temp;
    }
    }
    };
  2. 嵌套类与迭代器模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    class Vector {
    private:
    int* data;
    size_t size;
    size_t capacity;

    public:
    Vector() : data(nullptr), size(0), capacity(0) {}

    void push_back(int value) {
    if (size >= capacity) {
    size_t newCapacity = capacity == 0 ? 1 : capacity * 2;
    int* newData = new int[newCapacity];
    for (size_t i = 0; i < size; ++i) {
    newData[i] = data[i];
    }
    delete[] data;
    data = newData;
    capacity = newCapacity;
    }
    data[size++] = value;
    }

    size_t getSize() const { return size; }

    // 嵌套的迭代器类
    class Iterator {
    private:
    int* ptr;

    public:
    Iterator(int* p) : ptr(p) {}

    int& operator*() const { return *ptr; }

    Iterator& operator++() {
    ++ptr;
    return *this;
    }

    bool operator!=(const Iterator& other) const {
    return ptr != other.ptr;
    }
    };

    Iterator begin() const { return Iterator(data); }
    Iterator end() const { return Iterator(data + size); }

    ~Vector() {
    delete[] data;
    }
    };
  3. 嵌套类与策略模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    class SortStrategy {
    public:
    virtual void sort(int* data, size_t size) = 0;
    virtual ~SortStrategy() {}
    };

    class Sorter {
    private:
    // 嵌套的排序策略实现
    class BubbleSort : public SortStrategy {
    public:
    void sort(int* data, size_t size) override {
    for (size_t i = 0; i < size - 1; ++i) {
    for (size_t j = 0; j < size - i - 1; ++j) {
    if (data[j] > data[j + 1]) {
    std::swap(data[j], data[j + 1]);
    }
    }
    }
    }
    };

    class QuickSort : public SortStrategy {
    private:
    void quickSort(int* data, int low, int high) {
    if (low < high) {
    int pivot = partition(data, low, high);
    quickSort(data, low, pivot - 1);
    quickSort(data, pivot + 1, high);
    }
    }

    int partition(int* data, int low, int high) {
    int pivot = data[high];
    int i = low - 1;
    for (int j = low; j < high; ++j) {
    if (data[j] <= pivot) {
    ++i;
    std::swap(data[i], data[j]);
    }
    }
    std::swap(data[i + 1], data[high]);
    return i + 1;
    }

    public:
    void sort(int* data, size_t size) override {
    quickSort(data, 0, size - 1);
    }
    };

    SortStrategy* strategy;

    public:
    enum StrategyType {
    BUBBLE_SORT,
    QUICK_SORT
    };

    Sorter(StrategyType type) {
    switch (type) {
    case BUBBLE_SORT:
    strategy = new BubbleSort();
    break;
    case QUICK_SORT:
    strategy = new QuickSort();
    break;
    default:
    strategy = new BubbleSort();
    }
    }

    void sort(int* data, size_t size) {
    strategy->sort(data, size);
    }

    ~Sorter() {
    delete strategy;
    }
    };

嵌套类和局部类的性能考虑

  1. 嵌套类的性能

    • 嵌套类的成员函数调用开销与普通类相同
    • 嵌套类的对象创建和销毁开销与普通类相同
    • 嵌套类不会引入额外的性能开销
  2. 局部类的性能

    • 局部类的成员函数通常是内联的,减少了函数调用开销
    • 局部类的对象存储在栈上,创建和销毁开销很小
    • 局部类适合用于临时的、小范围的任务
  3. 内存使用

    • 嵌套类的对象存储在独立的内存空间中
    • 局部类的对象存储在函数的栈帧中
    • 两者都不会增加额外的内存开销

嵌套类和局部类的最佳实践

  1. 嵌套类的最佳实践

    • 封装实现细节:使用嵌套类来封装外部类的实现细节
    • 逻辑关联:当一个类只与另一个类相关时,使用嵌套类
    • 访问控制:根据需要选择适当的访问权限
    • 命名空间污染:使用嵌套类来避免命名空间污染
  2. 局部类的最佳实践

    • 临时使用:只在函数内部临时需要一个类时使用局部类
    • 小范围:局部类的作用域应限制在最小必要的范围内
    • 简单实现:局部类应该保持简单,避免复杂的实现
    • 避免静态成员:记住局部类不能有静态成员
  3. 代码组织

    • 清晰的层次结构:使用嵌套类来创建清晰的层次结构
    • 逻辑分组:将相关的类组织在一起
    • 可读性:确保嵌套和局部类的使用不会降低代码的可读性
  4. 性能优化

    • 内联成员函数:对于简单的成员函数,使用内联来提高性能
    • 避免动态内存分配:对于局部类,优先使用栈上存储
    • 最小化开销:确保嵌套和局部类的使用不会引入不必要的开销

嵌套类和局部类的设计原则

  1. 单一职责原则

    • 每个嵌套类和局部类应该有单一的职责
    • 避免创建过于复杂的嵌套类层次结构
  2. 封装原则

    • 使用嵌套类来封装实现细节
    • 只暴露必要的接口给外部代码
  3. 最小惊讶原则

    • 嵌套类和局部类的行为应该符合用户的预期
    • 避免创建令人惊讶的嵌套类层次结构
  4. 代码重用

    • 当多个函数需要相同的局部类时,考虑将其提升为嵌套类
    • 当多个类需要相同的嵌套类时,考虑将其提升为独立的类

嵌套类和局部类的应用场景

  1. 嵌套类的应用场景

    • 迭代器实现:为容器类实现迭代器
    • 策略模式:实现不同的策略算法
    • 工厂模式:实现对象创建的工厂方法
    • 适配器模式:实现接口适配器
    • 内部数据结构:实现内部使用的数据结构
  2. 局部类的应用场景

    • 临时数据处理:处理函数内部的临时数据
    • 回调对象:创建临时的回调对象
    • 异常处理:创建临时的异常类
    • 测试代码:在测试函数中创建测试用的类

嵌套类和局部类与现代C++

  1. C++11的lambda表达式

    • lambda表达式提供了一种更简洁的方式来创建临时的函数对象
    • 对于简单的场景,lambda表达式可以替代局部类
    • 对于复杂的场景,局部类仍然是必要的
  2. C++11的嵌套类改进

    • C++11允许在模板类中定义嵌套类模板
    • C++11允许在嵌套类中使用外部类的类型参数
  3. C++14的泛型lambda

    • 泛型lambda提供了更灵活的类型推导
    • 进一步减少了对局部类的需求

嵌套类和局部类的陷阱

  1. 嵌套类的陷阱

    • 过度使用:避免过度使用嵌套类,以免使代码变得复杂
    • 循环依赖:避免嵌套类和外部类之间的循环依赖
    • 访问权限:注意嵌套类的访问权限,确保它们被正确使用
  2. 局部类的陷阱

    • 作用域限制:记住局部类的作用域被限制在函数内部
    • 静态成员:局部类不能有静态成员
    • 外部变量访问:局部类只能访问函数中的静态变量和外部变量
  3. 性能陷阱

    • 过度内联:避免在局部类中定义过于复杂的内联函数
    • 栈溢出:避免在局部类中创建过大的对象,以免导致栈溢出

嵌套类和局部类是C++中强大的特性,它们提供了一种封装和组织代码的机制。正确使用嵌套类和局部类可以提高代码的可读性、可维护性和安全性。然而,过度使用或不当使用它们可能会使代码变得复杂和难以理解。在实际编程中,应该根据具体情况权衡使用嵌套类和局部类的利弊。

类的模板

类模板的基本概念

类模板允许我们创建通用的类,能够处理不同类型的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>

// 类模板
template <typename T>
class Box {
private:
T value;

public:
Box(T v) : value(v) {}

T getValue() const {
return value;
}

void setValue(T v) {
value = v;
}

void display() const {
std::cout << "Value: " << value << std::endl;
}
};

int main() {
// 使用int类型实例化Box
Box<int> intBox(100);
intBox.display();

// 使用double类型实例化Box
Box<double> doubleBox(3.14);
doubleBox.display();

// 使用std::string类型实例化Box
Box<std::string> stringBox("Hello");
stringBox.display();

// 修改值
intBox.setValue(200);
intBox.display();

doubleBox.setValue(6.28);
doubleBox.display();

stringBox.setValue("World");
stringBox.display();

return 0;
}

类模板的特化

类模板特化允许我们为特定类型提供自定义实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <string>

// 主模板
template <typename T>
class Storage {
private:
T value;

public:
Storage(T v) : value(v) {}

void display() const {
std::cout << "Generic Storage: " << value << std::endl;
}
};

// 特化版本:针对bool类型
template <>
class Storage<bool> {
private:
bool value;

public:
Storage(bool v) : value(v) {}

void display() const {
std::cout << "Boolean Storage: " << (value ? "true" : "false") << std::endl;
}
};

// 特化版本:针对std::string类型
template <>
class Storage<std::string> {
private:
std::string value;

public:
Storage(const std::string& v) : value(v) {}

void display() const {
std::cout << "String Storage: '" << value << "'" << std::endl;
std::cout << "Length: " << value.length() << std::endl;
}
};

int main() {
// 使用主模板
Storage<int> intStorage(42);
intStorage.display();

Storage<double> doubleStorage(3.14);
doubleStorage.display();

// 使用特化版本
Storage<bool> boolStorage(true);
boolStorage.display();

Storage<std::string> stringStorage("Hello, C++");
stringStorage.display();

return 0;
}

类的继承和多态高级特性

虚析构函数

虚析构函数确保在删除指向派生类对象的基类指针时,能够正确调用派生类的析构函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>

class Base {
public:
Base() {
std::cout << "Base constructor called" << std::endl;
}

// 虚析构函数
virtual ~Base() {
std::cout << "Base destructor called" << std::endl;
}

virtual void display() {
std::cout << "Base display" << std::endl;
}
};

class Derived : public Base {
private:
int* data;

public:
Derived(int value) {
std::cout << "Derived constructor called" << std::endl;
data = new int(value);
}

~Derived() {
std::cout << "Derived destructor called" << std::endl;
delete data;
}

void display() override {
std::cout << "Derived display, value: " << *data << std::endl;
}
};

int main() {
// 使用基类指针指向派生类对象
Base* ptr = new Derived(42);
ptr->display();

// 删除基类指针
delete ptr;

return 0;
}

纯虚函数和抽象类

纯虚函数是没有实现的虚函数,包含纯虚函数的类是抽象类,不能实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <iostream>
#include <string>

// 抽象类:包含纯虚函数
class Shape {
public:
virtual ~Shape() {}

// 纯虚函数
virtual double calculateArea() const = 0;
virtual double calculatePerimeter() const = 0;
virtual void display() const = 0;
};

class Circle : public Shape {
private:
double radius;

public:
Circle(double r) : radius(r) {}

double calculateArea() const override {
return 3.14159 * radius * radius;
}

double calculatePerimeter() const override {
return 2 * 3.14159 * radius;
}

void display() const override {
std::cout << "Circle - Radius: " << radius << std::endl;
std::cout << "Area: " << calculateArea() << std::endl;
std::cout << "Perimeter: " << calculatePerimeter() << std::endl;
}
};

class Rectangle : public Shape {
private:
double width;
double height;

public:
Rectangle(double w, double h) : width(w), height(h) {}

double calculateArea() const override {
return width * height;
}

double calculatePerimeter() const override {
return 2 * (width + height);
}

void display() const override {
std::cout << "Rectangle - Width: " << width << ", Height: " << height << std::endl;
std::cout << "Area: " << calculateArea() << std::endl;
std::cout << "Perimeter: " << calculatePerimeter() << std::endl;
}
};

int main() {
// 不能实例化抽象类
// Shape shape; // 错误

// 使用基类指针指向派生类对象
Shape* shapes[2];
shapes[0] = new Circle(5.0);
shapes[1] = new Rectangle(4.0, 6.0);

// 多态调用
for (int i = 0; i < 2; i++) {
shapes[i]->display();
std::cout << std::endl;
}

// 释放内存
for (int i = 0; i < 2; i++) {
delete shapes[i];
}

return 0;
}

虚函数和多态

虚函数允许派生类重写基类的方法,实现多态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include <string>

class Animal {
protected:
std::string name;

public:
Animal(const std::string& n) : name(n) {}

virtual ~Animal() {
std::cout << "Animal destructor called" << std::endl;
}

// 虚函数
virtual void makeSound() {
std::cout << "Animal makes a sound" << std::endl;
}

virtual void eat() {
std::cout << "Animal eats" << std::endl;
}

void sleep() {
std::cout << "Animal sleeps" << std::endl;
}
};

class Dog : public Animal {
public:
Dog(const std::string& n) : Animal(n) {}

~Dog() {
std::cout << "Dog destructor called" << std::endl;
}

// 重写虚函数
void makeSound() override {
std::cout << name << " barks: Woof! Woof!" << std::endl;
}

void eat() override {
std::cout << name << " eats bones" << std::endl;
}
};

class Cat : public Animal {
public:
Cat(const std::string& n) : Animal(n) {}

~Cat() {
std::cout << "Cat destructor called" << std::endl;
}

// 重写虚函数
void makeSound() override {
std::cout << name << " meows: Meow! Meow!" << std::endl;
}

void eat() override {
std::cout << name << " eats fish" << std::endl;
}
};

int main() {
// 使用基类指针指向派生类对象
Animal* animals[2];
animals[0] = new Dog("Buddy");
animals[1] = new Cat("Whiskers");

// 多态调用
for (int i = 0; i < 2; i++) {
animals[i]->makeSound();
animals[i]->eat();
animals[i]->sleep(); // 调用基类的非虚函数
std::cout << std::endl;
}

// 释放内存
for (int i = 0; i < 2; i++) {
delete animals[i];
}

return 0;
}

类的设计原则

封装

封装是将数据和操作数据的方法组合在一起,对外部隐藏实现细节。

继承

继承允许我们创建基于现有类的新类,重用代码并扩展功能。

多态

多态允许我们使用基类指针或引用指向派生类对象,调用派生类的方法。

抽象

抽象是通过抽象类和纯虚函数,定义接口而不提供实现。

组合

组合是将一个类的对象作为另一个类的成员,实现代码重用。

聚合

聚合是一种特殊的组合,其中成员对象的生命周期独立于容器对象。

最小特权原则

只授予类和函数必要的访问权限,避免不必要的暴露。

单一职责原则

一个类应该只有一个引起它变化的原因,专注于单一功能。

开放/封闭原则

类应该对扩展开放,对修改封闭。

里氏替换原则

派生类应该可以替换其基类,而不破坏程序的正确性。

接口隔离原则

客户端不应该依赖它不使用的接口。

依赖倒置原则

高层模块不应该依赖低层模块,两者都应该依赖抽象。

总结

本章介绍了类的高级特性,包括:

  1. 友元:友元函数、友元类和成员函数作为友元
  2. 静态成员:静态成员变量和静态成员函数
  3. 常量成员:常量成员变量和常量成员函数
  4. 类型转换:转换构造函数和类型转换运算符
  5. 嵌套和局部类:嵌套类和局部类的定义和使用
  6. 类模板:类模板的基本概念和特化
  7. 继承和多态高级特性:虚析构函数、纯虚函数和抽象类
  8. 类的设计原则:封装、继承、多态、抽象、组合、聚合等设计原则

这些高级特性使C++的类更加灵活和强大,能够适应各种复杂的编程需求。通过合理使用这些特性,你可以设计出更加模块化、可维护和可扩展的代码。

类的高级特性是C++的重要组成部分,也是成为优秀C++程序员的必备知识。通过不断学习和实践,你会逐渐掌握这些特性的使用技巧,并能够设计出更加优雅和高效的类。