第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++中两种特殊的类定义方式,它们提供了一种封装和组织代码的机制,特别是在处理复杂的类层次结构时非常有用。这些特性在现代C++中被广泛应用于实现各种设计模式和优化技术。

嵌套类

嵌套类是在另一个类的内部定义的类,它可以访问外部类的私有成员和保护成员,而外部类也可以访问嵌套类的所有成员。

嵌套类的底层实现

  1. 作用域和访问控制

    • 嵌套类的作用域被限制在外部类内部
    • 嵌套类可以访问外部类的所有成员(包括私有成员)
    • 外部类也可以访问嵌套类的所有成员
    • 嵌套类不自动拥有指向外部类对象的指针
  2. 内存布局

    • 嵌套类的大小和内存布局独立于外部类
    • 外部类的对象不包含嵌套类的成员
    • 嵌套类的对象可以独立创建和销毁
  3. 编译期处理

    • 嵌套类的定义在外部类的定义内部
    • 编译器会为嵌套类生成独立的类型信息
    • 嵌套类的名称需要通过外部类的名称来限定(如 Outer::Inner

嵌套类的高级应用

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// 示例1:使用嵌套类实现迭代器模式
template <typename T>
class LinkedList {
private:
// 嵌套类:节点
class Node {
public:
T data;
Node* next;
Node* prev;

Node(const T& d, Node* n = nullptr, Node* p = nullptr)
: data(d), next(n), prev(p) {}
};

Node* head;
Node* tail;
size_t size;

public:
LinkedList() : head(nullptr), tail(nullptr), size(0) {}

~LinkedList() {
clear();
}

// 嵌套类:迭代器
class Iterator {
private:
Node* current;

public:
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&
using iterator_category = std::bidirectional_iterator_tag;

Iterator(Node* node) : current(node) {}

// 运算符重载
reference operator*() const {
return current->data;
}

pointer operator->() const {
return &current->data;
}

Iterator& operator++() {
current = current->next;
return *this;
}

Iterator operator++(int) {
Iterator temp = *this;
current = current->next;
return temp;
}

Iterator& operator--() {
current = current->prev;
return *this;
}

Iterator operator--(int) {
Iterator temp = *this;
current = current->prev;
return temp;
}

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

bool operator!=(const Iterator& other) const {
return !(*this == other);
}
};

// 迭代器相关方法
Iterator begin() {
return Iterator(head);
}

Iterator end() {
return Iterator(nullptr);
}

// 其他链表方法
void push_back(const T& value) {
Node* newNode = new Node(value, nullptr, tail);
if (tail) {
tail->next = newNode;
} else {
head = newNode;
}
tail = newNode;
size++;
}

void push_front(const T& value) {
Node* newNode = new Node(value, head, nullptr);
if (head) {
head->prev = newNode;
} else {
tail = newNode;
}
head = newNode;
size++;
}

void clear() {
while (head) {
Node* temp = head;
head = head->next;
delete temp;
}
tail = nullptr;
size = 0;
}

size_t getSize() const {
return size;
}
};

// 使用示例
void useLinkedList() {
LinkedList<int> list;
list.push_back(10);
list.push_back(20);
list.push_back(30);

// 使用范围for循环(需要迭代器支持)
for (int value : list) {
std::cout << value << " ";
}
std::cout << std::endl;

// 使用迭代器
for (auto it = list.begin(); it != list.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
}

// 示例2:使用嵌套类实现策略模式
class PaymentProcessor {
private:
// 嵌套抽象类:支付策略
class PaymentStrategy {
public:
virtual ~PaymentStrategy() = default;
virtual void pay(double amount) const = 0;
};

// 嵌套类:信用卡支付策略
class CreditCardPayment : public PaymentStrategy {
private:
std::string cardNumber;
std::string expiryDate;
std::string cvv;

public:
CreditCardPayment(const std::string& num, const std::string& exp, const std::string& cv)
: cardNumber(num), expiryDate(exp), cvv(cv) {}

void pay(double amount) const override {
std::cout << "Paying " << amount << " using credit card: " << cardNumber << std::endl;
// 实际的支付处理逻辑
}
};

// 嵌套类:PayPal支付策略
class PayPalPayment : public PaymentStrategy {
private:
std::string email;

public:
PayPalPayment(const std::string& mail) : email(mail) {}

void pay(double amount) const override {
std::cout << "Paying " << amount << " using PayPal: " << email << std::endl;
// 实际的支付处理逻辑
}
};

std::unique_ptr<PaymentStrategy> strategy;

public:
void setPaymentStrategy(const std::string& type, const std::map<std::string, std::string>& details) {
if (type == "credit_card") {
strategy = std::make_unique<CreditCardPayment>(
details.at("card_number"),
details.at("expiry_date"),
details.at("cvv")
);
} else if (type == "paypal") {
strategy = std::make_unique<PayPalPayment>(details.at("email"));
}
}

void processPayment(double amount) {
if (strategy) {
strategy->pay(amount);
} else {
throw std::runtime_error("No payment strategy set");
}
}
};

嵌套类的性能优化

  1. 内联可能性

    • 嵌套类的成员函数通常定义在外部类的头文件中,更容易被编译器内联
    • 内联可以减少函数调用的开销,提高性能
  2. 缓存局部性

    • 嵌套类和外部类的定义放在一起,有助于编译器优化缓存使用
    • 相关数据和函数的紧密布局可以提高缓存命中率
  3. 模板实例化

    • 对于模板类中的嵌套类,编译器会为每个模板实例化生成独立的代码
    • 这可以提高类型安全性,同时允许编译器进行更具体的优化

局部类

局部类是在函数内部定义的类,它的作用域被限制在定义它的函数内部。

局部类的底层实现

  1. 作用域限制

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

    • 局部类可以访问函数中的静态变量和外部变量(C++11+)
    • 局部类不能访问函数中的非静态局部变量
    • 局部类的成员函数只能在类定义内部实现
  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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// 示例1:使用局部类实现RAII
void processFile(const std::string& filename) {
// 局部类:文件RAII管理器
class FileGuard {
private:
FILE* file;

public:
explicit FileGuard(FILE* f) : file(f) {}

~FileGuard() {
if (file) {
fclose(file);
std::cout << "File closed automatically" << std::endl;
}
}

FILE* get() const {
return file;
}

// 禁用拷贝和移动
FileGuard(const FileGuard&) = delete;
FileGuard& operator=(const FileGuard&) = delete;
FileGuard(FileGuard&&) = delete;
FileGuard& operator=(FileGuard&&) = delete;
};

// 使用局部类进行文件操作
FILE* file = fopen(filename.c_str(), "r");
if (!file) {
throw std::runtime_error("Failed to open file");
}

FileGuard guard(file);

// 读取文件内容
char buffer[256];
while (fgets(buffer, sizeof(buffer), guard.get())) {
std::cout << buffer;
}

// 文件会在guard销毁时自动关闭
}

// 示例2:使用局部类实现自定义比较器
void sortWithCustomComparator(std::vector<int>& numbers) {
// 局部类:自定义比较器
class CustomComparator {
public:
bool operator()(int a, int b) const {
// 按绝对值大小排序
return std::abs(a) < std::abs(b);
}
};

// 使用局部类作为比较器
std::sort(numbers.begin(), numbers.end(), CustomComparator());
}

// 示例3:使用局部类实现命令模式
void processCommands() {
// 局部类:命令接口
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
};

// 局部类:具体命令
class PrintCommand : public Command {
private:
std::string message;

public:
explicit PrintCommand(const std::string& msg) : message(msg) {}

void execute() override {
std::cout << "Print: " << message << std::endl;
}
};

class AddCommand : public Command {
private:
int& value;
int addend;

public:
AddCommand(int& val, int add) : value(val), addend(add) {}

void execute() override {
value += addend;
std::cout << "Add: " << addend << ", result: " << value << std::endl;
}
};

// 执行命令
std::vector<std::unique_ptr<Command>> commands;
int counter = 0;

commands.push_back(std::make_unique<PrintCommand>("Hello, World!"));
commands.push_back(std::make_unique<AddCommand>(counter, 5));
commands.push_back(std::make_unique<AddCommand>(counter, 10));
commands.push_back(std::make_unique<PrintCommand>("Command execution complete"));

// 执行所有命令
for (const auto& cmd : commands) {
cmd->execute();
}
}

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

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;

if (num % 2 == 0) {
evenNumbers.push_back(num);
} else {
oddNumbers.push_back(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;
std::cout << "Even numbers: ";
for (int num : evenNumbers) {
std::cout << num << " ";
}
std::cout << std::endl;
std::cout << "Odd numbers: ";
for (int num : oddNumbers) {
std::cout << num << " ";
}
std::cout << std::endl;
}

// 获取统计结果
int getSum() const { return sum; }
double getAverage() const { return average; }
int getMin() const { return min; }
int getMax() const { return max; }
const std::vector<int>& getEvenNumbers() const { return evenNumbers; }
const std::vector<int>& getOddNumbers() const { return oddNumbers; }
};

// 使用局部类处理数据
Result result(numbers);
result.display();

// 使用局部类的结果
std::cout << "Processing complete. Sum: " << result.getSum() << std::endl;
}

局部类的性能考虑

  1. 内联可能性

    • 局部类的成员函数必须在类定义内部实现,这使得它们更容易被编译器内联
    • 内联可以减少函数调用的开销,提高性能
  2. 作用域限制

    • 局部类的作用域仅限于定义它的函数内部,编译器可以进行更激进的优化
    • 编译器知道局部类不会在其他地方被使用,可以更自由地优化其实现
  3. 内存管理

    • 局部类的实例通常在栈上创建,避免了堆分配的开销
    • 栈分配和释放的速度比堆分配和释放快得多
  4. 模板实例化

    • 局部类可以在函数模板内部定义,为每个模板实例化生成独立的代码
    • 这允许编译器进行更具体的优化,提高性能

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

  1. 合理使用嵌套类

    • 当一个类只被另一个类使用时,考虑使用嵌套类
    • 嵌套类可以提高代码的封装性和可读性
    • 对于实现细节,使用私有嵌套类
  2. 谨慎使用局部类

    • 局部类适用于只在一个函数内部使用的类
    • 局部类的定义会增加函数的复杂度,应谨慎使用
    • 对于复杂的局部类,考虑将其移到函数外部作为嵌套类或独立类
  3. 性能优化

    • 对于频繁调用的嵌套类或局部类的成员函数,确保它们被内联
    • 考虑使用移动语义减少拷贝开销
    • 对于模板类中的嵌套类,注意模板实例化的开销
  4. 代码组织

    • 嵌套类的定义应放在外部类的私有部分,除非需要在外部访问
    • 局部类的定义应放在函数的开始部分,使其作用域清晰
    • 为嵌套类和局部类提供清晰的文档,说明其用途和限制
  5. 现代C++特性

    • 利用C++11+的特性,如移动语义、lambda表达式、智能指针等
    • 对于简单的局部类,考虑使用lambda表达式替代
    • 对于需要管理资源的局部类,使用RAII模式

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

  1. 嵌套类的应用场景

    • 迭代器模式:实现容器的迭代器
    • 策略模式:实现不同的算法策略
    • 状态模式:实现对象的不同状态
    • 工厂模式:实现创建对象的工厂
    • 复合模式:实现复杂的对象结构
  2. 局部类的应用场景

    • RAII:管理临时资源
    • 自定义比较器:用于排序和查找
    • 命令模式:实现临时命令
    • 适配器模式:适配不同的接口
    • 临时数据结构:处理函数内部的数据

总结

类的高级特性是C++中强大的工具,它们提供了更灵活、更高效的编程方式。本章详细介绍了友元、静态成员、常量成员、类型转换、嵌套类和局部类等高级特性,包括它们的底层实现、性能影响、最佳实践和应用场景。

关键要点

  1. 友元

    • 友元允许特定的函数或类访问另一个类的私有成员和保护成员
    • 友元会破坏封装性,但在某些场景下可以提高代码的灵活性和性能
    • 应谨慎使用友元,只在必要时使用
  2. 静态成员

    • 静态成员属于类而不是对象,所有对象共享同一个静态成员
    • 静态成员变量存储在全局数据区,静态成员函数没有this指针
    • 静态成员适用于存储所有对象共享的数据和实现与对象状态无关的操作
  3. 常量成员

    • 常量成员变量必须在构造函数的初始化列表中初始化,且在对象的生命周期内不能改变
    • 常量成员函数承诺不会修改对象的状态
    • 常量正确性是C++编程中的重要概念,它可以提高代码的安全性、可读性和可维护性
  4. 类型转换

    • 转换构造函数用于将其他类型转换为类类型
    • 类型转换运算符用于将类类型转换为其他类型
    • 应优先使用explicit关键字防止意外的隐式转换
  5. 嵌套类与局部类

    • 嵌套类是在另一个类的内部定义的类,它可以访问外部类的私有成员和保护成员
    • 局部类是在函数内部定义的类,它的作用域被限制在定义它的函数内部
    • 嵌套类和局部类可以提高代码的封装性和可读性,但应谨慎使用

性能考虑

  1. 友元

    • 友元函数直接访问私有成员,避免了通过公共接口的函数调用开销
    • 友元函数通常定义在头文件中,更容易被编译器内联
  2. 静态成员

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

    • const成员函数提供了更强的不变性保证,编译器可以进行更多的优化
    • 常量对象的访问可能会被优化为直接访问内存
  4. 类型转换

    • 类型转换可能会产生开销,特别是当转换涉及到复杂的计算或内存分配时
    • 类型转换运算符和转换构造函数通常是内联的,可以减少函数调用的开销
  5. 嵌套类与局部类

    • 嵌套类和局部类的成员函数更容易被编译器内联
    • 局部类的实例通常在栈上创建,避免了堆分配的开销

现代C++特性

  1. C++11+

    • 移动语义可以减少拷贝开销,提高性能
    • lambda表达式可以替代简单的局部类
    • 智能指针可以提高代码的安全性和可靠性
  2. C++14+

    • 泛型lambda表达式提供了更灵活的编程方式
    • 变量模板允许在模板中定义变量
  3. C++17+

    • 内联变量简化了静态成员变量的定义
    • 结构化绑定使代码更简洁、更可读
  4. C++20+

    • 概念(Concepts)可以约束模板参数的类型,提高代码的安全性和可读性
    • 协程(Coroutines)提供了一种新的编程范式

类的高级特性是C++中强大的工具,正确理解和使用这些特性,对于编写高效、安全、可维护的C++代码至关重要。通过合理使用这些特性,开发者可以创建更加灵活、更加高效的C++程序,充分发挥C++的性能优势。

  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++程序员的必备知识。通过不断学习和实践,你会逐渐掌握这些特性的使用技巧,并能够设计出更加优雅和高效的类。