提到代码,我们往往会有一个疑问:计算机中的内存有划分之说吗?如果有,那代码存储在计算机中的哪些区域?就像日常生活中我们对于土地的划分一样,根据外部环境、市场、交通等因素,不同的区域放置不同的资源。计算机也是一样,有着自己的资源分配规则,不同的区域放置不同的资源(变量)。那我们如何通过编程语言去合理利用这些资源呢?这篇文章会告诉你内存的栈区、堆区、常量区...存储何种变量,并且从C和C++这两种编程语言去讲解如何开辟与释放空间以及进一步了解C++中 操作符:new && delete,函数:operator new && operator delete,还有模板的讲解、使用方法。
目录
1.C内存管理方式:malloc、calloc、realloc -- free
(2)函数operator new &&operator delete:
一、C/C++内存分布
1.内存分布精准打击:
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
2.解析+内存分布图:
①globalVar(全局)、staticGlobalVar(静态变量)、staticVar(静态变量)、localVar(局部变量)、num1(数组首元素地址 -- 指针)
②char2(指针)、*char2(字符串首元素,字符串存储栈区中)、pChar3(指针)、*pChar3(字符串首元素,字符串存储常量区中)、ptr1(指针)
③sizeof计算大小、strlen计算字符串长度
二、C/C++内存管理方式
1.C内存管理方式:malloc、calloc、realloc -- free
2.C++内存管理方式:
按道理来说,C语言的内存管理方式C++也可以适用,但是既然C++存在那么就一定有更优越于C语言的地方,所以,操作符new、delete横空出世
(1)操作符new && delete:
*使用方法:①指针变量 = new 类型
②指针变量 = new 类型(对应参数) -------- 初始化
delete 指针变量
③指针变量 = new 类型[ 数量 ] ----- --- 开辟连续空间(等同于数组)
delete[] 指针变量
*范例:
①内置类型:
void test()
{
//创建指针
int* ptr4 = new int;
int* ptr5 = new int(2);
int* ptr6 = new int[6];
//释放空间
delete pt4;
delete pt5;
delete[] pt6;
}
②自定义类型:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout<<”A()”<<endl;
}
~A()
{
cout<<”~A()”<<endl;
}
private:
int _a;
}
int main()
{
A* ptr3 = (A*)malloc(sizeof(A));
A* ptr4 = new A(1);
free(ptr3);
delete ptr4;
A* ptr1 = (A*) malloc(sizeof(A) * 10);
A* ptr2 = new A[10];
free(ptr1);
delete[] ptr2;
return 0;
}
补充说明:在使用操作符new和delete 操作符 开辟/释放 空间时,会调用相对应的 构造/析构函数,而malloc和free则不会。
(2)函数operator new &&operator delete:
*operator new 和 operator delete 是2个全局函数,上文提到的new和delete2个操作符就是在底层通过调用这2个函数来实现 申请空间 && 释放空间
*operator new 和 operator delete 实际上也是通过 malloc 和 free 来 申请 和 释放空间的,区别就是:malloc申请失败则返回NULL,operator new 申请失败则会执行用户提供的空间不足对应措施,若该措施存在则继续执行,否则抛异常。operator delete 则是通过free来释放空间。
3.进一步理解new 和 delete:
通过上面的学习,我们已经大概知道这2个操作符和malloc、free的区别了,但是,上述的区别实在是让人觉得没有说服力,难道仅仅是因为使用时稍微简单、方便点就创造了这2个操作符?亦或者:难道就因为 operator new 申请空间失败时可以执行对应措施就创建了这个函数?请听我针对空间存储的数据类型来解释:
(1)内置类型(char、int、long ....):
对于内置类型来说,使用malloc 还是new开辟空间存放并无区别,因为这些类型不会再衍生出其他空间,使用free可以直接将其开辟的空间释放。
(2)自定义类型:
我们知道自定义类型的基本构造:构造函数、析构函数 是必须存在的!问题的关键恰恰就在这里,使用new和delete可以调用 构造函数和析构函数,而malloc和free并不可以,从开辟空间的角度来说new和malloc 并无二异,但是如果释放空间就会显现出异常了:free仅仅会释放指向若干个存储自定义类型的空间,并不能将自定义类型内成员函数开辟的空间释放,如果是用free释放的话会有内存泄漏的风险!而delete会调用析构函数,因此会将自定义类型内成员函数开辟的空间释放。
上述就是new && delete 与 malloc && free 比较核心的区分,除此之外,它们之间还有使用方法上的不同:
如:
开辟数组:①new:类型* ptr1 = new[n];
②malloc:类型* ptr2 = (类型*)malloc(所需开辟空间的大小)
使用操作符new只需要知道开辟空间的类型,而使用malloc则需要知道开辟空间的大小!!!
三、定位new表达式
1.概念:
定位new 是在已分配的空间上调用构造函数初始化对象
2.用法:
new(place_address)type / new(place_address)type((initializer-list)
3.代码举例:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout<<”A()”<<endl;
}
~A()
{
cout<<”~A()”<<endl;
}
private:
int _a;
}
int main()
{
//首先开辟一个A类型大小的空间赋值给指针ptr1,此时ptr1还不是一个对象
A* ptr1 = (A*)malloc(sizeof(A));
//使用定位new在ptr1指向的空间上创建一个对象
new(ptr1)A; //若调用析构函数需要传参,则此处要传递参数
//释放空间
Ptr1->~A();
free(ptr1);
//构造函数需要传参
A* ptr2 = (A*)malloc(sizeof(A));
new(ptr2)A(2);
//释放
ptr2->~A();
free(ptr2);
}
四、模板
大家一定遇到过这样一种情况:使用一个函数达到某种效果,但是当遇到想让函数实现的效果相同但参数类型不同这种情况时,大家只能复制写好的函数,然后逐个修改类型。这样做看似是重复性的机械劳动,但是又不可省略。那么问题来了:如何省略这样机械性的重复呢?答案是:不可能!但是!人可以省略这个重复性的过程,让编译器去完成!这就是模板。
1.函数模板:
(1)概念:
函数模板代表的 效果、参数个数 相同的所有函数,在使用时会被参数化,根据传入的参数创造对应的类型版本。
(2)格式:
tymplate<typename T1, typename T2.....>
返回值 函数名(参数列表)
{
}
实例:
tymplate<typename T>
Void Swap(T& p1,T& p2)
{
T tmp;
tmp = p1;
p1 = p2;
p2 = tmp;
}
(typename 也可以用 class 代替,但是不能用struct来代替class!!!)
(3)详解:
函数模板其实并没有省略掉创建函数这个环节,只是不需要我们认为的去编写只是参数类型不同的函数了,编译器会替我们完成这个环节。
(4)函数模板的实例化:
函数模板分为 隐式实例化 和 显式实例化
①隐式实例化:
编译器通过对参数类型的判定去选择创造对应的函数,但是不能产生歧义!如下:
tymplate<typename T>
Void Swap(T& p1,T& p2)
{
T tmp;
tmp = p1;
p1 = p2;
p2 = tmp;
}
int main()
{
int a = 10;
double b = 5.2;
Swap(a,b); // 这样是错误的,因为编译器首先读取参数a,判定参数类型//为 int ,
//但是读取参数b时,判定参数又为double,因此编//译不通过解决办法:
// ①自行强转 ②显式实例化(见下文)
//用户自行强转
Swap(a,(int)b)
return 0;
}
②显式实例化:
在函数名后的<>中说明参数类型
int main()
{
int a = 10;
double b = 5.2;
//显式实例化
Swap<int>(a,b)
return 0;
}
(5)函数模板的匹配原则:
对于非模板函数和同名函数模板,如果其他条件都相同,那么将不会通过函数模板产生一个实例,而是直接调用此函数。如果函数模板更符合条件,则通过函数模板产生实例(择优录取)
(模板函数不会进行类型自动转换,但是普通函数可以!)
2.类模板:
(1)概念:
既然存在有函数模板,那么也就一定存在类模板,所谓的类模板就是说:不同的类除了类中参数的类型不同外,其他全部一样。这时就可以使用类模板。
(2)格式:
template(typename T1, typename T2...)
class 类模板名
{
...
}
(3)例子:
//类模板(顺序表)
template<class T>
class Vector
{
public:
//构造函数
Vector(size_t capacity = 10)
:_pData(new T[_capacity])
, _size(0)
, _capacity(capacity)
{
cout << "Vector()" << endl;
}
//析构函数 (类中声明 类外定义)
~Vector();
private:
T* _pData;
size_t _size;
size_t _capacity;
};
//析构函数 (需要先写出模板参数列表)
template<class T>
Vector<T>::~Vector()
{
if (_pData)
{
delete[] _pData;
_size = _capacity = 0;
}
cout << "~Vector()" << endl;
}
int main()
{
//类模板正确使用方法
Vector<int> S1;
Vector<double> S2;
}
(4)类模板的实例化:
我们上述所编写的 Vector 其实并不是类型,它只是类名,正确将它改为类型还需要在后面加 <实例化类型>,这时,Vector<实例化类型> 才是一个类型,才可以创建对象。
以上就是本篇文章的全部内容啦!希望这篇文章可以帮助你更好的学习与了解 内存分布,内存管理方式:new、delete && malloc、free等。