C语言的内存管理:
void Test()
{
int* p1 = (int*)malloc(sizeof(int)); // 申请一个int类型的空间
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
// 这里需要free(p2)吗?
free(p3);
// 不需要
// 当realloc成功调整内存大小时,原内存块p2会被自动释放。
}
malloc:
1. 只负责分配内存,不初始化内存(内存中的值是随机值)
2. 参数是 “总字节数”
3.返回值的类型是void*
calloc:
1. 分配内存后,会将内存初始化为 0
2. 参数是 “元素个数” 和 “单个元素大小(单位:字节)”
3.返回值的类型是void*
realloc:
1. 用于 “扩容” 或 “缩容” 已有的动态内存
2. 若原内存块后有足够空间,直接在原地址扩展;否则会分配新内存并拷贝原数据,原内存块自动释放
3. 若这里的参数p2
为NULL
,则等价于malloc(size)
4.返回值的类型是void*
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
void test()
{
// C语言中申请空间
int* p1 = (int*)malloc(sizeof(int)); // 申请一个int类型的空间
int* p3 = (int*)malloc(sizeof(int) * 10); // 申请10个int类型的空间
// 释放空间
free(p1);
free(p3);
p1 = p3 = nullptr;
// C++中申请空间
int* p2 = new int; // 申请一个int类型的空间
// int* p2 = new int(10); // 申请一个int类型的空间,并且初始化为10
int* p4 = new int[10]; // 申请10个int类型的空间
// 释放空间
delete p2;
delete[] p4;
}
既然已经有了malloc和free,new和delete的意义何在?
1. 对于内置类型,他们的效果是一样的
2. 对于自定义类型,效果就不一样,malloc只申请空间;new申请空间+调用构造函数初始化
3. free只是释放空间,delete调用析构函数+释放空间
class A
{
friend ostream& operator<<(ostream& out, const A& a);
public:
A(int a = 0)
:_a(a)
{
cout << "A()" << endl;
}
void print()
{
cout << _a << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
//ostream& operator<<(ostream& out,const A& a)
//{
// out << a._a << endl;
// return out;
//}
// 既然已经有了malloc和free,new和delete的意义何在?
// 1.对于内置类型,他们的效果是一样的
// 2.对于自定义类型,效果就不一样,malloc只申请空间;new申请空间+调用构造函数初始化
// free只是释放空间 delete调用析构函数+释放空间
int main()
{
// C语言和C++在内置类型的申请空间是一样的
// 对于自定义类型是不一样的
A* p1 = (A*)malloc(sizeof(A)); // malloc只申请空间
A* p2 = new A; // new申请空间+调用构造函数初始化
//A* p2 = new A(2);
p2->print();
//cout << &p2 << endl;
free(p1); // free只是释放空间
p1 = nullptr;
delete p2; // delete调用析构函数+释放空间
return 0;
}
new 比起 malloc不一样的地方:1、调用构造函数初始化 ;2、失败了抛异常
delete 比起free不一样的地方:1、调用析构函数清理
malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地 方是:
1. malloc和free是函数,new和delete是操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[ ]中指定对象个数即可
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new 在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
什么是内存泄漏,内存泄漏的危害
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内 存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。
函数模板
void Swap(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
void Swap(double& x, double& y)
{
double temp = x;
x = y;
y = temp;
}
void Swap(char& x, char& y)
{
char temp = x;
x = y;
y = temp;
}
// ......
观察上面的代码使用函数重载虽然可以实现多种类型数据的交换,但是有一下几个不好的地方:
1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
2. 代码的可维护性比较低,一个出错可能所有的重载均出错 。
C++的函数模板能够解决这个问题
函数模板概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
函数模板格式 :
template<typename T1,typename T2,……,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>
void Swap(T& x, T& y)
{
T temp = x;
x = y;
y = temp;
}
int main()
{
int a = 1;
int b = 2;
Swap(a,b);
cout << "a = " << a <<" " << "b = " << b << endl;
// 显式实例化
//Swap<int>(a,b); 函数变为void Swap(int& x, int& y); // T被显式指定为int,参数为int&类型
double c = 1.11;
double d = 2.22;
Swap(c, d);
cout << "c = " << c << " " << "d = " << d << endl;
char e = 'a';
char f = 'b';
Swap(e, f);
cout << "e = " << e << " " << "f = " << f << endl;
return 0;
}
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供 调用。比如:当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然 后产生一份专门处理int类型的代码,对于字符类型也是如此。
template<class T>
T Add( T& x, const T& y) // const int& 可以接收 double 类型的 c(允许隐式类型转换为 int,生成临时变量,而 const 引用可以绑定临时变量)。
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
double c = 1.11;
int e = (int)c;
double d = 2.22;
Add(a, b);
Add(c, d);
// (int)c是临时变量,强制类型转换产生的临时变量(右值)无法绑定到非 const 的左值引用(T&)
Add(a, (int)c); // 函数模板中参数不加 const则此句报错
Add(a, e);
Add<int>(a, c); // 函数模板中参数不加 const则此句报错
return 0;
}