C语言malloc calloc和realloc的弊端
C语言的这三个函数对于内置类型的处理是还可以的,但是对自定义类型就无法处理了,因为c++自定义类型在初始化的时候是自动调用构造函数的,而C语言的这三个函数是无法初始化内置类型的,于是我们下面要介绍new和delete两个关键字,所以在用new创造空间时,会自动调用构造函数,在用delete释放空间时,会自动调用析构函数
new和delete
new和delete的用法
operator new和operator delete实际上是通过malloc和free开启空间,只是new在申请新空间的时候,对于自定义类型会调用默认构造函数,delete在释放自定义类型的空间的时候,会先调用自定义类型的析构函数,然后再free掉这块空间
此外,new和malloc不一样,malloc失败后会返回空,而new不会返回空,若申请空间失败了,如果用户有执行相应措施,则它会调用相应措施,继续申请,如果没有,它会抛异常
const int N=100;
class stack
{stack(int capacity=4){_capacity=capacity;_size=0;arr=new int[_capacity];}~stack(){free(arr);_capacity=_size=0;}
private:int* arr;int _capacity;int _size;
};
stack* func(int n=10)
{stack* pst=new stack;return pst;
}
int main()
{int capacity=N;int* arr1=new int[capacity];//开辟数组 new datatype[]的方括号里面是完成对n个对象的调用申请delete[] arr1;//stack* pst=func(N);delete pst;//析构函数先把stack里的arr释放掉,delete再释放掉pst指向的空间
}
如果你想捕获异常,可以用以下代码
int main()
{ try{char* p=new char[0x7fffffff];//可更换//即使是一个函数,也可以在异常处中断,跳到catch}catch(const exception& e){cout<<e.what()<<endl;}return 0;
}
不匹配问题
有时候我们会malloc,new,free,delete,new[ ],delete[ ]混着用,这是会出问题的
class V
{
public:stack(int a=1):_a(a){}//但一旦我加上析构函数,这里就会报错了
private:int _a;
};
int main()
{stack* pst=new stack;free(pst);//会报错,因为内存泄漏delete(pst);//正常A* pA=new A[10];//free(pA);//报错//delete pA;//报错delete[]pA;
}
为什么有了析构函数就会报错呢?因为在new 出n个空间的时候,编译器会在空间前面加四个字节,用来存储对象的个数,但是只返回cur的位置
我们在申请内存的时候不能只申请一部分,如果上面用free或者delete的话,会从cur位置开始释放,而不是从最开始位置开始释放,只有delete[ ]会往前偏移四个字节,至于为什么有析构函数编译器就会报错的问题,编译器在没有析构函数的时候会自动去优化,而有析构函数的时候不得不执行,然后就报错了
定位new
构造函数不能显式调用,析构函数可以显式调用,如果我们一定要调用构造函数的话,可以用下面这种方法
class stack
{
public:stack(int capacity=4){_capacity=capacity;_size=0;arr=new int[_capacity];}
private:int* arr;int _size;int _capacity;
};
int main()
{stack* pst1=new stack;pst1->stack();//不允许new(pst1)stack();//定位newreturn 0;
}
malloc,new,free,delete的区别
1.malloc和free是函数,而new和delete是操作符
2.malloc申请的空间不会初始化,而new开辟的空间会初始化
3.malloc申请空间的时候,需要手动计算申请空间,而new只需要在new后面加上类型,若有多个,则可以在类型后面加上[数量]
4.malloc返回的类型是void*,需要强转,而new不需要强转,因为new后面跟着的就是空间类型
5.malloc失败后会返回空,而new失败后会报错,需要捕获异常
6.malloc和free在使用的时候只会开辟空间,而new在使用的时候会先开空间,再调用构造函数,而delete会先调用析构函数,再去释放空间
泛型编程
泛型编程就是针对广泛的类型去编程,我们在实现交换函数swap的时候每次都只能实现一种类型的交换,虽然说我们用函数重载可以解决一部分的问题,但仍然存在着缺陷,因为他们内部的逻辑都是一样的,只有类型不一样,但又不得不写,就会显得代码很繁琐
void swap(int& a1,int& a2)
{int tmp=a1;a1=a2;a2=tmp;
}
void swap(char& a1,char& a2)
{char tmp=a1;a1=a2;a2=tmp;
}
void swap(double& a1,double& a2)
{double tmp=a1;a1=a2;a2=tmp;
}
如果我们有函数模板就可以解决这个问题
函数模板
template<class T>
void swap(T& a1,T& a2)
{T tmp=a1;a1=a2;a2=tmp;
}
int main()
{int a1=1,a2=2;double b1=1.0,b2=2.0;swap(a1,a2);//隐式实例化swap(b1,b2);
}
我们去用不同类型调试的时候会发现它都进入了同一个函数代码,但实际上它进去的是编译器通过T模板实例化后的函数,所以他们调用的是不一样的函数,如果你进入函数反汇编去看,你会发现这两个swap函数的汇编代码有一句不一样,一个是swap< int >(地址),另一个是swap< double >(地址)
类模板
我们在写一些类的时候,比如说栈,有时候我们栈只能写一个int类型,然后拷贝一份再写一个double类型,但内部的逻辑都是一样的,会很麻烦,所以类也引进了类模板
template<class T>
class stack
{
public:stack(int capacity=4):_size(0),_capacity(capacity){arr=new T[_capacity];}~stack(){delete[] arr;_size=_capacity=0;}
private:T* arr;int _size;int _capacity;
};
//我们在使用的时候在类的后面要加入模板类型
int main()
{stack<int> st1;stack<double> st2;//显式实例化return 0;
}