C++特殊工具与技术(上)

一、控制内存分配

某些应用程序对内存分配有特殊需求,无法直接应用标准内存管理机制。需要自定义内存分配的细节。

1、重载 new 和 delete

void* operator new(std::size_t size) {
    // 自定义内存分配逻辑
    void* ptr = std::malloc(size);
    if (!ptr) {
        throw std::bad_alloc(); // 内存分配失败时抛出异常
    }
    return ptr;
}

上述代码中,重载了new运算符,std::size_t size参数表示要分配的字节数。在这个函数中,可以实现自己的内存分配逻辑,例如使用malloc来分配内存。如果分配失败,通常会抛出std::bad_alloc异常。

void operator delete(void* ptr) noexcept {
    // 自定义内存释放逻辑
    std::free(ptr);
}

重载了delete运算符,接受一个指向要释放内存的指针。在这个函数中,可以实现自己的内存释放逻辑,例如使用free来释放内存。

malloc 和 free 函数定义在头文件<cstdlib>中。

2、定位 new 表达式

定位 new 允许在已分配的内存块上创建对象,而不是使用默认的内存分配方式。通常,它用于在已分配的内存上构造对象,例如在内存池或特定内存区域中。

new (place_address) type 
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }

其中 place_address必须是一个指针,同时在initializers中提供一个(可能为空的)以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象。

当只传入一个指针类型的实参时,定位new表达式构造对象但是不分配内存。

#include <iostream>

class MyClass {
public:
    MyClass(int val) : value(val) {}
    void Print() {
        std::cout << "Value: " << value << std::endl;
    }
private:
    int value;
};

int main() {
    // 分配一块内存用于存储 MyClass 对象
    void* memory = ::operator new(sizeof(MyClass));
    
    // 使用定位 new 表达式在已分配的内存上创建对象
    MyClass* obj = new (memory) MyClass(42);

    // 访问对象的方法
    obj->Print();

    // 使用定位 delete 表达式释放对象
    obj->~MyClass();

    // 释放内存块
    ::operator delete(memory);

    return 0;
}

3、时类型识别

运行时类型识别(run-time type identification,RTTI)的功能由两个运算符实现:
typeid运算符,用于返回表达式的类型。
dynamic_cast运算符,用于将基类的指针或引用安全地转换成派生类的指针或引用。
当我们将这两个运算符用于某种类型的指针或引用,并且该类型含有虚函数时,运算符将使用指针或引用所绑定对象的动态类型。

4、dynamic_cast 运算符

dynamic_cast运算符的使用形式如下所示:

dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)

其中,type必须是一个类类型,并且通常情况下该类型应该含有虚函数。在第一种形式中,e必须是一个有效的指针;在第二种形式中,e必须是一个左值;在第三种形式中,e不能是左值。

dynamic_cast 运算符用于在继承层次结构中进行安全的向下转型。它可以将基类指针或引用转换为派生类指针或引用,并且在类型不匹配时返回 nullptr(对指针)或引发 std::bad_cast 异常(对引用)。

#include <iostream>

class Base {
public:
    virtual ~Base() {}
};

class Derived : public Base {
public:
    void DerivedFunction() {
        std::cout << "DerivedFunction called." << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived;

    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
    if (derivedPtr) {
        derivedPtr->DerivedFunction();
    } else {
        std::cout << "无法进行向下转型。" << std::endl;
    }

    delete basePtr;

    return 0;
}

//dynamic_cast 运算符用于将 basePtr 转换为 Derived* 类型的指针,如果转换成功,就可以调用 Derived 类的成员函数。

5、typeid 运算符

typeid表达式的形式是typeid(e),其中e可以是任意表达式或类型的名字。
typeid操作的结果是一个常量对象的引用,该对象的类型是标准库类型type_info或者type_info的公有派生类型。

typeid运算符可以作用于任意类型的表达式。

当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid运算符指示的是运算对象的静态类型。而当运算对象是定义了至少一个虚函数的类的左值时,typeid的结果直到运行时才会求得。

typeid 运算符允许获取对象的实际类型信息,以便在运行时判断对象的类型。它通常与 std::type_info 类一起使用。

#include <iostream>
#include <typeinfo>

class Base {
public:
    virtual ~Base() {}
};

class Derived : public Base {
};

int main() {
    Base* basePtr = new Derived;

    if (typeid(*basePtr) == typeid(Derived)) {
        std::cout << "basePtr指向的对象是Derived类型" << std::endl;
    } else if (typeid(*basePtr) == typeid(Base)) {
        std::cout << "basePtr指向的对象是Base类型" << std::endl;
    }

    delete basePtr;

    return 0;
}

6、使用 RTTI

运行时类型识别(RTTI)可以用来获取对象的实际类型信息,并在运行时根据对象的类型进行不同的处理。

#include <iostream>
#include <typeinfo>

class Animal {
public:
    virtual void Speak() const {
        std::cout << "Animal speaks." << std::endl;
    }
};

class Dog : public Animal {
public:
    void Speak() const override {
        std::cout << "Dog barks." << std::endl;
    }
};

class Cat : public Animal {
public:
    void Speak() const override {
        std::cout << "Cat meows." << std::endl;
    }
};

int main() {
    Animal* animals[3];
    animals[0] = new Dog();
    animals[1] = new Cat();
    animals[2] = new Animal();

    for (int i = 0; i < 3; i++) {
        const std::type_info& typeInfo = typeid(*animals[i]);
        
        if (typeInfo == typeid(Dog)) {
            std::cout << "This is a Dog. ";
        } else if (typeInfo == typeid(Cat)) {
            std::cout << "This is a Cat. ";
        } else {
            std::cout << "This is an unknown animal. ";
        }
        
        animals[i]->Speak();
    }

    for (int i = 0; i < 3; i++) {
        delete animals[i];
    }

    return 0;
}

//在上述示例中,我们创建了一个基类Animal和两个派生类Dog和Cat。然后,我们创建了一个包含不同类型的Animal指针的数组,
//并使用typeid运算符获取对象的类型信息。最后,我们根据类型信息执行不同的操作。

//要注意的是,typeid返回的是std::type_info类型,可以用来进行类型比较。
//另外,为了使类型比较有效,Animal类中的Speak函数被声明为虚函数,以启用多态性。

尽管RTTI可以实现运行时类型检查和操作,但通常应该避免使用它,因为它可能会引入性能开销,并且有时更好的做法是使用多态性和虚函数来避免需要RTTI。 RTTI通常在某些特定的情况下才会用到。

7、type_info 类

type_info类的精确定义随着编译器的不同而略有差异。
它用于表示类型信息。std::type_info 的对象通常由运行时类型识别(RTTI)操作 typeid 返回。它包含有关一个类型的信息,例如类型的名称或类型的标识。

1.获取类型信息:std::type_info 通常通过 typeid 运算符来获取类型信息。

const std::type_info& typeInfo = typeid(MyClass);

2.比较类型信息:可以使用 std::type_info 对象来比较类型信息。

const std::type_info& typeInfo1 = typeid(MyClass1);
const std::type_info& typeInfo2 = typeid(MyClass2);

if (typeInfo1 == typeInfo2) {
    // 类型相同
} else {
    // 类型不同
}

3.获取类型名称:std::type_info 对象可以用于获取类型的名称。

const std::type_info& typeInfo = typeid(MyClass);
std::cout << "Type name: " << typeInfo.name() << std::endl;

4.比较类型信息的安全性:std::type_info 对象的比较是类型安全的,即使在继承层次结构中也可以正常工作。这对于执行基于多态性的类型检查非常有用。

Base* basePtr = new Derived;
const std::type_info& baseTypeInfo = typeid(Base);
const std::type_info& derivedTypeInfo = typeid(*basePtr);

if (derivedTypeInfo == baseTypeInfo) {
    // 此代码块不会执行,因为类型不同
}

二、枚举类型

枚举类型可以将一组整型常量组织在一起。和类一样,每个枚举类型定义了一种新的类型。枚举属于字面值常量类型。

C++包含两种枚举:限定作用域的和不限定作用域的。

定义限定作用域的枚举类型:首先关键字 enum class(或等价使用 enum struct),随后是枚举类型名字以及用花括号括起来以逗号分隔的枚举成员列表,最后是一个分号:enum class open_modes {input,output,append) ;

定义不限定作用域的枚举类型:省略掉关键字 class(或 struct),枚举类型的名字是可选的:

enum color{red, yellow, green};	//不限定作用域的枚举类型
//未命名的、不限定作用域的枚举类型
enum (floatPrec = 6, doublePrec = 10,double_doublePrec = 10);

1、枚举成员

在限定作用域的枚举类型中,枚举成员的名字遵循常规的作用域准则,并且在枚举类型的作用域外是不可访问的。
与之相反,在不限定作用域的枚举类型中,枚举成员的作用域与枚举类型本身的作用域相同。

默认情况下,枚举值从0开始,依次加1。不过也能为一个或几个枚举成员指定专门的值。

枚举成员是const,因此在初始化枚举成员时提供的初始值必须是常量表达式。
可以定义枚举类型的constexpr变量:

constexpr intTypes charbits = intTypes::charTyp;

switch 语句中 case 标签的值必须是常量表达式,可以用枚举成员做 case 标签。

2、和类一样,枚举也定义新的类型

只要enum有名字,我们就能定义并初始化该类型的成员。要想初始化enum对象或者为enum对象赋值,必须使用该类型的一个枚举成员或者该类型的另一个对象。

一个不限定作用域的枚举类型的对象或枚举成员自动地转换成整型。

3、枚举类型的前置声明

可以提前声明 enum,但是不限定作用域的枚举类型在声明时必须指定成员类型。
enum 的声明和定义的成员类型必须匹配。

enum class Color1;    // 前置声明 Color1,成员类型默认为 int
enum uid:long long;   // 前置声明 uid,必须指定成员类型

三、类成员指针

成员指针是指可以指向类的非静态成员的指针。一般情况下,指针指向一个对象,但是成员指针指示的是类的成员,而非类的对象。
类的静态成员不属于任何对象,因此无须特殊的指向静态成员的指针,指向静态成员的指针与普通指针没有什么区别。

1、数据成员指针

class MyClass {
public:
    int data;
    void Print() {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    int MyClass::*memberPtr = &MyClass::data;

    MyClass obj;
    obj.*memberPtr = 42;
    std::cout << obj.data << std::endl;

    return 0;
}

//`定义了一个指向 MyClass 类的 data 数据成员的类成员指针 int MyClass::*memberPtr。
//然后,创建了一个 MyClass 对象 obj,并使用类成员指针来访问和修改 data 数据成员。`

2、成员函数指针

class MyClass {
public:
    void PrintHello() {
        std::cout << "Hello from MyClass" << std::endl;
    }
};

int main() {
    void (MyClass::*memberFunctionPtr)() = &MyClass::PrintHello;

    MyClass obj;
    (obj.*memberFunctionPtr)();

    return 0;
}

//定义了一个指向 MyClass 类的 PrintHello 成员函数的类成员指针 void (MyClass::*memberFunctionPtr)()。
//然后,创建了一个 MyClass 对象 obj,并使用类成员指针来调用 PrintHello 成员函数。

类成员指针的语法可以看起来比较复杂,但它们提供了一种强大的机制,用于在运行时动态选择要调用的成员函数或访问的数据成员,这在某些高级编程场景中非常有用。需要注意的是,类成员指针的类型与要指向的成员的类型有关,因此需要确保它们的类型匹配。

3、嵌套类

一个类可以定义在另一个类的内部,称之为嵌套类。
嵌套类可以访问外部类的私有成员,并且通常用于实现一种封装和组织的方式,以将相关联的类放在一起。嵌套类的一个常见用途是作为外部类的辅助类,用于实现某些功能。

嵌套类是一个独立的类,与外层类基本没什么关系。特别是,外层类的对象和嵌套类的对象是相互独立的。在嵌套类的对象中不包含任何外层类定义的成员:类似的,在外层类的对象中也不包含任何嵌套类定义的成员。

嵌套类的名字在外层类作用域中是可见的,在外层类作用域之外不可见。和其他嵌套的名字一样,嵌套类的名字不会和别的作用域中的同一个名字冲突。

#include <iostream>

class OuterClass {
public:
    // 外部类的构造函数
    OuterClass(int value) : data(value) {}

    // 嵌套类的定义
    class NestedClass {
    public:
        NestedClass(int nestedValue) : nestedData(nestedValue) {}

        void Display() {
            std::cout << "Nested Data: " << nestedData << std::endl;
        }
    private:
        int nestedData;
    };

    void AccessNestedClass() {
        NestedClass nested(42);
        nested.Display();
    }

private:
    int data;
};

int main() {
    OuterClass outer(10);
    outer.AccessNestedClass();

    return 0;
}

//定义了一个外部类 OuterClass 和一个嵌套类 NestedClass。嵌套类 NestedClass 可以访问外部类 OuterClass 的私有成员,例如 data。在 AccessNestedClass 成员函数中,创建了一个 NestedClass 的对象并访问了它的成员函数。

在嵌套类在其外层类之外完成真正的定义之前,它都是一个不完全类型.

猜你喜欢

转载自blog.csdn.net/asuf1364/article/details/132678035