C++学习:C++类型转换专栏 - 指南

        对于C/C++这种强类型语言而言,类型转换是常态。

        但是对于C++而言,类型转换又比C语言麻烦一些。

        本期我们就来简单学习一下C++类型转换相关的知识

        相关代码已经上传至作者个人gitee:楼田莉子/CPP代码学习喜欢请支持一下谢谢

目录

C语言类型转换

C++的类型转换

C++头文件

        核心组件概览

 头文件中常见函数的主要作用

        智能指针(Smart Pointers)

        std::unique_ptr - 独占所有权指针

   std::shared_ptr - 共享所有权指针

   std::weak_ptr - 弱引用指针

       内存分配器(Allocators)

        自定义内存分配

        工具函数(Utility Functions)

   std::addressof - 获取对象地址

   std::align - 内存对齐

        工厂函数

        std::make_unique 和 std::make_shared

        智能指针对比总结

        应用场景

        资源管理(RAII)

        避免循环引用

C++中的强制显式类型转换

       内存安全

        C++中4种强制类型转换运算符

RAII


C语言类型转换

        在C语⾔中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与 接收返回值类型不⼀致时等场景,就需要发⽣类型转化。

        C语⾔中总共有两种形式的类型转换:隐 式类型转换和显式强制类型转换。

隐式类型转化:编译器在编译阶段⾃动进⾏,能转就转,不能转就编译失败

  显式强制类型转化:需要⽤⼾⾃去显⽰在变量前⽤括号指定要转换的类型。

        并不是任意类型之前都⽀持转换,两个类型⽀持转换需要有⼀定关联性,也就是说转换后要有⼀定 的意义,两个毫⽆关联的类型是不⽀持转换的。

        举例说明:

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
int main()
{
	int i = 1;
	// 隐式类型转换
	// 隐式类型转换主要发⽣在整形和整形之间,整形和浮点数之间,浮点数和浮点数之间
	double d = i;
	printf("%d, %.2f\n", i, d);
	int* p = &i;
	// 显⽰的强制类型转换
	// 强制类型转换主要发⽣在指针和整形之间,指针和指针之间
	int address = (int)p;
	printf("%p, %d\n", p, address);
	// malloc返回值是void*,被强转成int*
	int* ptr = (int*)malloc(8);
	// 编译报错:类型强制转换:⽆法从“int* ”转换为“double”
	// 指针是地址的编号,也是⼀种整数,所以可以和整形互相转换
	// 但是指针和浮点数毫⽆关联,强转也是不⽀持的
	// d = (double)p;
	return 0;
}

C++的类型转换

        C++兼容C,所以C⽀持的隐式类型转换和显式强制类型转换C++都⽀持。

        单参数类型转换。下图所示的为隐式类型转换 

         C++还⽀持内置类型到⾃定义类型之间的转换,内置类型转成⾃定义类型需要构造函数的⽀持,⾃定义类型转成内置类型,需要⼀个operator类型()的函数去⽀持。

        C++还⽀持⾃定义类型到⾃定义类型之间的转换,需要对应类型的构造函数⽀持即可,⽐如A类型对 象想转成B类型,则⽀持⼀个形参为A类型的B构造函数即可⽀持。

        自定义类型转换示例:

//test.cpp
#include"list.h"
using namespace std;
void test2()
{
	Boogiepop::list lt = { 1,2,3,4,5 };
	//权限缩⼩?权限缩⼩和放⼤,仅限于const的指针和引⽤
	//这⾥不是权限缩⼩,这⾥是⾃定义类型=⾃定义类型之间的类型转换
	//具体实现看下⾯ListIterator中对应的构造函数的实现
	Boogiepop::list::const_iterator cit = lt.begin();
	while (cit != lt.end())
	{
		cout << *cit << " ";
		++cit;
	}
	cout << endl;
}
int main()
{
	test2();
	return 0;
}
//list.h
#pragma once
#include 
#include 
using namespace std;
namespace Boogiepop
{
    template
    struct ListNode
    {
        ListNode* _next;
        ListNode* _prev;
        T _data;
        ListNode(const T& data = T())
            :_next(nullptr)
            , _prev(nullptr)
            , _data(data)
        {
        }
    };
    template
    struct ListIterator
    {
        typedef ListNode Node;
        typedef ListIterator Self;
        Node* _node;
        ListIterator(Node* node)
            :_node(node)
        {
        }
        // typedef ListIterator iterator;
        // typedef ListIterator const_iterator;
        // ListIterator实例化为iterator时,这个函数是拷⻉构造
        // ListIterator实例化为const_iterator时,
        // 这个函数⽀持iterator转换为const_iterator构造函数
        ListIterator(const ListIterator& it)
            :_node(it._node)
        {
        }
        // ++it;
        Self& operator++()
        {
            _node = _node->_next;
            return *this;
        }
        // --it;
        Self& operator--()
        {
            _node = _node->_prev;
            return *this;
        }
        // it++
        Self operator++(int)
        {
            Self tmp(*this);
            _node = _node->_next;
            return tmp;
        }
        // 修正2:后置递减运算符返回Self而不是Self&
        Self operator--(int)
        {
            Self tmp(*this);
            _node = _node->_prev;
            return tmp;
        }
        Ref operator*()
        {
            return _node->_data;
        }
        Ptr operator->()
        {
            return &_node->_data;
        }
        bool operator!=(const Self& it)
        {
            return _node != it._node;
        }
        bool operator==(const Self& it)
        {
            return _node == it._node;
        }
    };
    template
    class list
    {
        typedef ListNode Node;
    public:
        //同⼀个类模板给不同参数会实例化出不同的类型
        typedef ListIterator iterator;
        typedef ListIterator const_iterator;
        iterator begin()
        {
            return iterator(_head->_next);
        }
        const_iterator begin() const
        {
            return const_iterator(_head->_next);
        }
        iterator end()
        {
            return iterator(_head);
        }
        const_iterator end() const
        {
            return const_iterator(_head);
        }
        void empty_init()
        {
            _head = new Node();
            _head->_next = _head;
            _head->_prev = _head;
        }
        list()
        {
            empty_init();
        }
        // 修正3:添加std::命名空间限定
        list(std::initializer_list il)
        {
            empty_init();
            for (const auto& e : il)
            {
                push_back(e);
            }
        }
        void push_back(const T& x)
        {
            insert(end(), x);
        }
        // 没有iterator失效
        iterator insert(iterator pos, const T& x)
        {
            Node* cur = pos._node;
            Node* newnode = new Node(x);
            Node* prev = cur->_prev;
            // prev  newnode  cur
            prev->_next = newnode;
            newnode->_prev = prev;
            newnode->_next = cur;
            cur->_prev = newnode;
            return iterator(newnode);
        }
    private:
        Node* _head;
    };
}

C++头文件<memory>

<memory> 头文件是 C++ 标准库中用于智能内存管理的核心头文件,提供了自动内存管理的工具,避免手动内存管理带来的问题。

        核心组件概览

组件功能特点
智能指针自动内存管理防止内存泄漏,自动释放资源
内存分配器自定义内存分配控制内存分配策略
工具函数内存操作辅助提供安全的内存操作工具

<memory> 头文件中常见函数的主要作用

函数名主要作用引入版本特点使用示例
std::make_unique创建 std::unique_ptr 智能指针C++14异常安全,避免直接使用 newauto ptr = make_unique<MyClass>(args);
std::make_shared创建 std::shared_ptr 智能指针C++11单次内存分配,效率更高auto ptr = make_shared<MyClass>(args);
std::make_shared_for_overwrite创建未初始化的 shared_ptrC++20避免值初始化开销auto ptr = make_shared_for_overwrite<T>();
std::make_unique_for_overwrite创建未初始化的 unique_ptrC++20避免值初始化开销auto ptr = make_unique_for_overwrite<T>();
std::allocate_shared使用自定义分配器创建 shared_ptrC++11控制内存分配策略auto ptr = allocate_shared<T>(alloc, args);
std::addressof获取对象的真实地址C++11绕过 operator& 重载T* p = addressof(obj);
std::align内存对齐调整C++11在缓冲区中对齐指针align(alignment, size, ptr, space);

        智能指针(Smart Pointers)

std::unique_ptr - 独占所有权指针

#include 
#include 
class MyClass {
public:
    MyClass(int value) : data(value) {
        std::cout << "MyClass constructed: " << data << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destroyed: " << data << std::endl;
    }
    void print() { std::cout << "Value: " << data << std::endl; }
private:
    int data;
};
void unique_ptr_example() {
    // 创建 unique_ptr
    std::unique_ptr ptr1 = std::make_unique(42);
    ptr1->print();
    // 所有权转移(移动语义)
    std::unique_ptr ptr2 = std::move(ptr1);
    if (!ptr1) {
        std::cout << "ptr1 现在为空" << std::endl;
    }
    ptr2->print();
    // 自动释放内存,无需手动 delete
}

   std::shared_ptr - 共享所有权指针

#include 
void shared_ptr_example() {
    // 创建 shared_ptr
    std::shared_ptr ptr1 = std::make_shared(100);
    {
        std::shared_ptr ptr2 = ptr1;  // 共享所有权
        std::cout << "引用计数: " << ptr1.use_count() << std::endl;  // 2
        ptr2->print();
    }  // ptr2 离开作用域,引用计数减1
    std::cout << "引用计数: " << ptr1.use_count() << std::endl;  // 1
    ptr1->print();
    // 当最后一个 shared_ptr 销毁时,对象自动释放
}

   std::weak_ptr - 弱引用指针

#include 
void weak_ptr_example() {
    std::shared_ptr shared = std::make_shared(200);
    std::weak_ptr weak = shared;
    std::cout << "引用计数: " << shared.use_count() << std::endl;  // 1
    // 使用 weak_ptr 前需要转换为 shared_ptr
    if (auto temp = weak.lock()) {
        temp->print();  // 对象还存在
    } else {
        std::cout << "对象已被释放" << std::endl;
    }
    shared.reset();  // 释放对象
    if (auto temp = weak.lock()) {
        temp->print();
    } else {
        std::cout << "对象已被释放" << std::endl;  // 这里会执行
    }
}

       内存分配器(Allocators)

        自定义内存分配

#include 
#include 
template
class CustomAllocator {
public:
    using value_type = T;
    CustomAllocator() = default;
    template
    constexpr CustomAllocator(const CustomAllocator&) noexcept {}
    T* allocate(std::size_t n) {
        std::cout << "分配 " << n << " 个元素" << std::endl;
        return static_cast(::operator new(n * sizeof(T)));
    }
    void deallocate(T* p, std::size_t n) noexcept {
        std::cout << "释放 " << n << " 个元素" << std::endl;
        ::operator delete(p);
    }
};
void allocator_example() {
    // 使用自定义分配器的 vector
    std::vector> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
}

        工具函数(Utility Functions)

   std::addressof - 获取对象地址

#include 
class WeirdClass {
public:
    WeirdClass* operator&() {  // 重载了 & 运算符
        return nullptr;
    }
};
void addressof_example() {
    WeirdClass obj;
    WeirdClass* p1 = &obj;           // 使用重载的 &,得到 nullptr
    WeirdClass* p2 = std::addressof(obj);  // 得到真实地址
    std::cout << "重载&: " << p1 << std::endl;      // 0
    std::cout << "addressof: " << p2 << std::endl;  // 真实地址
}

   std::align - 内存对齐

#include 
void align_example() {
    char buffer[100];
    std::size_t space = sizeof(buffer);
    void* ptr = buffer;
    // 尝试在 buffer 中寻找对齐到 16 字节边界的地址
    if (std::align(16, 50, ptr, space)) {
        std::cout << "对齐成功: " << ptr << std::endl;
        std::cout << "剩余空间: " << space << std::endl;
    }
}

        工厂函数

std::make_unique 和 std::make_shared

#include 
class Resource {
public:
    Resource(int a, int b, int c)
        : x(a), y(b), z(c) {}
    void show() {
        std::cout << "Resource: " << x << ", " << y << ", " << z << std::endl;
    }
private:
    int x, y, z;
};
void factory_functions_example() {
    // C++14 引入 make_unique
    auto unique = std::make_unique(1, 2, 3);
    unique->show();
    // C++11 引入 make_shared(更高效,引用计数和对象在同一内存块)
    auto shared = std::make_shared(4, 5, 6);
    shared->show();
    // 对比传统 new
    std::shared_ptr old_way(new Resource(7, 8, 9));  // 不推荐
}

        智能指针对比总结

特性unique_ptrshared_ptrweak_ptr
所有权独占共享无所有权
拷贝不允许允许允许
移动允许允许允许
引用计数观察计数
性能接近裸指针有额外开销有额外开销
适用场景独占资源共享资源打破循环引用

        应用场景

        资源管理(RAII)

#include 
#include 
class FileHandler {
private:
    std::unique_ptr file;
public:
    FileHandler(const std::string& filename)
        : file(std::make_unique(filename)) {
        if (!file->is_open()) {
            throw std::runtime_error("无法打开文件");
        }
    }
    // 自动关闭文件,无需手动调用 close()
};
void raii_example() {
    try {
        FileHandler handler("data.txt");
        // 使用文件...
    } catch (const std::exception& e) {
        std::cout << "错误: " << e.what() << std::endl;
    }
    // 文件自动关闭
}

        避免循环引用

#include 
class Node {
public:
    std::string name;
    std::shared_ptr parent;
    std::weak_ptr child;  // 使用 weak_ptr 避免循环引用
    Node(const std::string& n) : name(n) {}
    ~Node() {
        std::cout << "销毁节点: " << name << std::endl;
    }
};
void cycle_reference_example() {
    auto node1 = std::make_shared("父节点");
    auto node2 = std::make_shared("子节点");
    node1->child = node2;  // 弱引用
    node2->parent = node1; // 强引用
    // 可以正常释放,不会内存泄漏
}

C++中的强制显式类型转换

       内存安全

        类型安全是指编程语⾔在编译和运⾏时提供保护机制,避免⾮法的类型转换和操作,导致出现⼀个 内存访问错误等,从⽽减少程序运⾏时的错误。

        C语⾔不是类型安全的语⾔,C语⾔允许隐式类型转换,⼀些特殊情况下就会导致越界访问的内存错 误,其次不合理的使⽤强制类型转换也会导致问题,⽐如⼀个int*的指针强转成double*访问就会 出现越界。

        C++兼容C语⾔,⽀持隐式类型转换和强制类型转换,C++也不是类型安全的语⾔,C++提出4个显 ⽰的命名强制类型转换

static_cast/reinterpret_cast/const_cast/dynamic_cast

        就是为了让类型转 换相对⽽⾔更安全。

void insert(size_t pos, char ch)
{
	//这⾥当pos == 0时,就会引发由于隐式类型转换
	// end跟pos⽐较时,提升为size_t导致判断结束逻辑出现问题
	// 在数组中访问挪动数据就会出现越界,经典的类型安全问题
	int end = 10;
	while (end >= pos)
	{
		// ...
		cout << end << endl; --end;
	}
}
void test3()
{
	insert(5, 'x');
	//insert(0, 'x');
	// 	这⾥会本质已经出现了越界访问,只是越界不⼀定能被检查出来
	int x = 100;
	double* p1 = (double*)&x;
	cout << *p1 << endl;
	const int y = 0;//可以利用关键字volatile来避免优化
	int* p2 = (int*)&y;
	(*p2) = 1;
	// 	这⾥打印的结果是1和	0,也是因为我们类型转换去掉了const属性
	// 	但是编译器认为	y是const的,不会被改变,所以会优化编译时放到
	// 寄存器或者直接替换y为0导致的
	volatile const int z = 0;
	int* p3 = (int*)&z;
	(*p3) = 1;
	// 	这⾥打印的结果是1和	0,也是因为我们类型转换去掉了const属性
	cout << *p2 << endl;
	cout << y << endl;
	cout << *p3 << endl;
	cout << z << endl;
}

        C++中4种强制类型转换运算符

        static_cast⽤于两个类型意义相近的转换,这个转换是具有明确定义的,只要底层不包含const, 都可以使⽤static_cast

 reinterpret_cast⽤于两个类型意义不相近的转换,reinterpret是重新解释的意思,通常为运算对 象的位模式提供较低层次上的重新解释,也就是说转换后对原有内存的访问解释已经完全改变了, ⾮常的⼤胆。所以我们要谨慎使⽤,清楚知道这样转换是没有内存访问安全问题的。

const_cast⽤于const类型到⾮const类型的转换,去掉了const属性,也是⼀样的我们要谨慎使 ⽤,否则可能会出现意想不到的结果。

dynamic_cast⽤于将基类的指针或者引⽤安全的转换成派⽣类的指针或者引⽤。如果基类的指针 或者引⽤时指向派⽣类对象的,则转换回派⽣类指针或者引⽤时可以成功的,如果基类的指针指向 基类对象,则转换失败返回nullptr,如果基类引⽤指向基类对象,则转换失败,抛出bad_cast异 常。

        其次dynamic_cast要求基类必须是多态类型,也就是基类中必须有虚函数。因为dynamic_cast是 运⾏时通过虚表中存储的type_info判断基类指针指向的是基类对象还是派⽣类对象。

void test1()
{
	// 对应隐式类型转换--数据的解释意义没有改变
	double d = 12.34;
	int a = static_cast(d);
	cout << a << endl;
	int&& ref = static_cast(a);
	// 对应强制类型转换--数据的解释意义已经发⽣改变
	int* p1 = reinterpret_cast(a);
	// 对应强制类型转换中有⻛险的去掉const属性
	// 所以要注意加volatile
	volatile const int b = 0;
	int* p2 = const_cast(&b);
	*p2 = 1;
	cout << b << endl;
	cout << *p2 << endl;
	// 对应类型名查询
	cout << typeid(d).name() << endl;
	cout<

        结果为:

void fun1(A* pa)
{
	// 指向⽗类转换时有⻛险的,后续访问存在越界访问的⻛险
		// 指向⼦类转换时安全
	B* pb1 = (B*)pa;
	cout << "pb1:" << pb1 << endl;
	cout << pb1->_a << endl;
	cout << pb1->_b << endl;
	pb1->_a++;
	pb1->_b++;
	cout << pb1->_a << endl;
	cout << pb1->_b << endl;
}
void fun2(A* pa)
{
	// dynamic_cast会先检查是否能转换成功(指向⼦类对象),能成功则转换,
	// (指向⽗类对象)转换失败则返回nullptr
	B* pb1 = dynamic_cast(pa);
	if (pb1)
	{
		cout << "pb1:" << pb1 << endl;
		cout << pb1->_a << endl;
		cout << pb1->_b << endl;
		pb1->_a++;
		pb1->_b++;
		cout << pb1->_a << endl;
		cout << pb1->_b << endl;
	}
	else
	{
		cout << "转换失败" << endl;
	}
}
void fun3(A& pa)
{
	// 转换失败,则抛出bad_cast异常
	try
	{
		B& pb1 = dynamic_cast(pa);
		cout << "转换成功" << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
}
void test2()
{
	A a;
	B b;
	//fun1(&a);
	//fun1(&b);
	fun2(&a);
	fun2(&b);
	fun3(a);
	fun3(b);
}

RAII

        RTTI的英⽂全称是"RuntimeTypeIdentification",中⽂称为"运⾏时类型识别",它指的是程序在 运⾏的时候才确定需要⽤到的对象是什么类型的。⽤于在运⾏时(⽽不是编译时)获取有关对象的 信息。

       RTTI主要由两个运算符实现,typeid和dynamic_cast;typeid主要⽤于返回表达式的类型, dynamic_cast前⾯已经讲过了,主要⽤于将基类的指针或者引⽤安全的转换成派⽣类的指针或者 引⽤。

        typeid(e)中e可以是任意表达式或类型的名字,typeid(e)的返回值是typeinfo或typeinfo派⽣类对 象的引⽤,typeinfo可以只⽀持⽐较等于和不等于,name成员函数可以返回C⻛格字符串表⽰对象 类型名字,typeinfo的精确定义随着编译器的不同⽽略有差异,也就以为着同⼀个e表达式,不同 编译器下,typeid(e).name()返回的名字可能是不⼀样的。

        typeinfo的⽂档如下:<typeinfo> - C++参考

        typeid(e)时,当运算对象不属于类类型或者是⼀个不包含任何虚函数的类时,typeid返回的是运算 对象的静态类型,当运算对象是定义了⾄少⼀个虚函数的类的左值时,typeid的返回结果直到运⾏ 时才会求得。

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 
#include
using namespace std;
int main()
{
	int a[10];
	int* ptr = nullptr;
	cout << typeid(10).name() << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(ptr).name() << endl;
	cout << typeid(string).name() << endl;
	cout << typeid(string::iterator).name() << endl;
	cout << typeid(vector).name() << endl;
	cout << typeid(vector::iterator).name() << endl;
	return 0;
}

        vs2022下结果为:

        g++编辑器下运行结果为:

using namespace std;
class A
{
public:
    virtual void func()
    {
    }
protected:
    int _a1 = 1;
};
class B : public A
{
protected:
    int _b1 = 2;
};
int main()
{
    try
    {
        B* pb = new B;
        A* pa = (A*)pb;
        if (typeid(*pb) == typeid(B))
        {
            cout << "typeid(*pb) == typeid(B)" << endl;
        }
        // 如果 A 和  B不是继承关系,则会抛 bad_typeid异常
        if (typeid(*pa) == typeid(B))
        {
            cout << "typeid(*pa) == typeid(B)" << endl;
        }
        // 这⾥ pa 和 pb是 A* 和 B*,不是类类型对象,他会被当做编译是求值的静态类型运算
        // 所以这⾥始终是不相等的
            if (typeid(pa) == typeid(pb))
            {
                cout << "typeid(pa) == typeid(B)" << endl;
            }
    }
    catch (const std::exception& e)
    {
        cout << e.what() << endl;
    }
    return 0;
}

        结果为:

        本期内容就到这里了,喜欢请点个赞谢谢

封面图自取:

posted @ 2025-09-30 09:20  yxysuanfa  阅读(7)  评论(0)    收藏  举报