《C++ 基础进阶:内存开辟规则、类型转换原理与 IO 流高效使用》
前引:在 C++ 编程中,内存管理是程序稳定性与性能的基石,而类型转换与 IO 流则是数据处理和交互的核心工具。栈与堆作为内存分配的两大核心区域,其开辟方式直接决定了变量的生命周期、访问效率及内存安全 —— 错误的分配策略可能导致内存泄漏、野指针或栈溢出等致命问题。与此同时,类型转换的合理性关乎类型系统的严谨性,不当转换易引发数据截断、逻辑错误;IO 流作为数据输入输出的桥梁,其正确使用则直接影响程序与外部设备(如控制台、文件)交互的可靠性!
目录
【一】内存完美开辟
(1)栈和堆的本质区别
(2)如何只在栈上开辟空间
(3)如何只在堆上开辟空间
【二】C++的四种类型转换
(1)static_cast
(2)reinterpret_cast
(3)const_cast
(4)dynamic_cast
【三】operator类型转换
(1)奇怪的现象
(2)类型转化的本质
【一】内存完美开辟
(1)栈和堆的本质区别
栈上开辟:比如一个函数、变量、数组等不需要明确的去malloc、new等申请动态空间
堆上开辟:需要手动去开辟指定大小的空间以及释放
特性 | 栈(Stack) | 堆(Heap) |
---|---|---|
分配方式 | 编译器自动分配 / 释放 | 程序员手动分配(new )/ 释放(delete ) |
生命周期 | 随作用域(如函数、代码块)结束而销毁 | 随delete 调用而销毁(否则内存泄漏) |
大小限制 | 通常较小(如几 MB,由系统限制) | 较大(几乎等于系统可用内存) |
速度 | 极快(类似 “拿取 / 放回固定货架”) | 较慢(类似 “找空地放东西,还要记录位置”) |
使用场景 | 局部变量、函数参数等短期存在的变量 | 动态大小的数据(如数组长度运行时确定) |
(2)如何只在栈上开辟空间
栈上开辟不需要手动的去malloc、new动态内存,周期一般在该函数调用结束时自动销毁
我们知道动态开辟,比如C++的new形式开辟是调用operator new开辟堆空间的,因此解决如下:
禁用(私有)operator new这样用户就没法调用,那就只能在栈上开辟空间,实现如下:
template<class T>
class StackOnly
{
public:StackOnly(){ }private:// 将operator new设为私有,禁止堆分配void* operator new(size_t size);void operator delete(void* ptr);
};
(3)如何只在堆上开辟空间
堆上调用是明确的调用new开辟空间,而栈每次是调用构造函数,因此我们禁用(私有)构造函数,只放new出来就可以完成只在堆上开辟空间,实现如下:
class HeapOnly
{
private:// 构造函数私有,禁止栈上创建(栈上创建需要调用构造函数)HeapOnly() {}public:// 静态函数:在堆上创建对象并返回指针static HeapOnly* create() {return new HeapOnly();}~HeapOnly() {delete this; // 释放当前对象}
};
这里为什么要用static静态修饰这个create函数?
简洁解释:若不用static修饰,这个函数就属于对象,就要调用构造函数。而用static修饰之后,这个函数就属于类本身,不需要创建对象就可以调用,就可以避免先有对象再调用函数
详细解释如下:
栈上创建对象时,需要直接调用构造函数(比如
HeapOnly obj;
会触发构造函数)。但我们为了禁止栈上创建,把构造函数设为了private
(外部无法直接调用)此时,如果
create
不是静态函数:
- 非静态函数属于对象(需要先有一个
HeapOnly
对象才能调用)。- 但构造函数私有,根本无法在外部创建第一个
HeapOnly
对象,自然也无法调用非静态的create
。而 静态成员函数属于 “类本身”,不需要先创建对象就能调用(可以直接通过
类名::函数名
调用,比如HeapOnly::create()
)。这样就能绕开 “必须先有对象才能调用函数” 的限制,在create
内部用new
(调用私有构造函数)创建堆上的对象
【二】C++的四种类型转换
介绍:在 C++ 中,类型转换是程序中常见的操作,为了使转换行为更清晰、更安全,C++ 提供了四种显式类型转换运算符(也称为 “转换的类模板”),分别是static_cast
、dynamic_cast
、const_cast
和reinterpret_cast
。它们各自有明确的适用场景和特性!
(1)static_cast
static_cast一般用于相近类型的转化
比如 int 和 double,:
int a = 10;double b = static_cast<double>(a);
比如 char 和 long:
char c = 'c';long d = static_cast<long>(c);
(2)reinterpret_cast
reinterpret_cast一般用于不相关类型转化
比如:int * 和 double *
int* c = &a;double* d = reinterpret_cast<double*> (c);
比如:char* 和 double*
char* c = &c;double* d = reinterpret_cast<double*> (c);
(3)const_cast
const_cast一般用于去掉const
例如:
int x = 10; // 本质是非const对象
const int* pc = &x; // const指针指向非const对象(仅限制通过该指针修改)// 需求:需要通过指针修改x的值
int* non_const_ptr = const_cast<int*>(pc); // 移除const
(4)dynamic_cast
dynamic_cast一般用于继承上的父->子的转化
(父类的指针是可以指向子类的对象的->切片)
(子类的指针是不可以指向父类的对象的)
例如:
class A
{
public:virtual void f() {}int _x = 0;
};class B : public A
{
public:int _y = 0;
};void fun(A* pa)
{// pa是指向子类对象B的,转换可以成功,正常返回地址// pa是指向父类对象A的,转换失败,返回空指针B* pb = dynamic_cast<B*>(pa);if (pb){cout << "转换成功" << endl;pb->_x++;pb->_y++;}else{cout << "转换失败" << endl;}
}
【三】operator类型转换
(1)奇怪的现象
例如现在有一个类:
class Func
{
public:int a = 10;operator bool(){return a;}
};
现在我们创建一个Func类类型的对象A,并进行赋值:
int main()
{Func A;bool pc = A;cout << pc << endl;return 0;
}
问:一个对象怎么赋值给一个变量呢?
但是结果是可以的,我们看下面的运行结果:
(2)类型转化的本质
运算符重载的本质是重新定义运算符的行为,让自定义类型(类)能像内置类型(如
int
、double
)一样使用运算符
运算符选择:大部分 C++ 运算符都可重载(如
+
、-
、=
、<<
等),但少数不可重载(如.
、::
、sizeof
、?:
三目运算符)类型转换运算符(特殊的运算符重载):
- 用于将类对象转换为其他类型(如
bool
、int
、double
等)- 语法上没有显式的返回值类型(返回值类型由 “
operator 目标类型
” 决定),也没有参数(因为是 “将当前对象转换为目标类型”)。- 示例:
operator int()
表示 “将对象转换为int
类型”,operator double()
表示 “将对象转换为double
类型”