目录
C/C++内存管理
1. C/C++内存分布
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);
}
选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?_静态区_ staticGlobalVar在哪里?_静态区_
staticVar在哪里?_静态区_ localVar在哪里?_栈区_
num1 在哪里?_栈区_
char2在哪里?_栈区_ *char2在哪里?_常量区_
pChar3在哪里?_栈区_ *pChar3在哪里?_常量区_
ptr1在哪里?_栈区_ *ptr1在哪里?_堆区_
【说明】1.栈又叫堆栈,里面存的是非静态的局部变量、 函数参数、 返回值等等,栈是后进先出的(压栈和出栈,栈顶是低地址栈底是高地址)。2. 内存映射段 是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。3. 堆 用于程序运行时动态内存分配,堆是可以上增长的(向着高地址存储数据)。4. 数据段 -- 存储全局数据和静态数据。5. 代码段 -- 可执行的代码 / 只读常量(const是变量而非常量故不在代码段)
2. C/C++语言中动态内存管理方式
在C语言中已经学过:
malloc是动态内存开辟,malloc(开辟空间大小(字节数)) ,返回地址
calloc是动态内存开辟的同时将其中所有字节初始化为0,calloc(数据个数,每个数据所占字节大小),返回地址
realloc是重新开辟空间大小,realloc(以前开辟的空间的起始地址,重新开辟的空间大小)。返回空
free是释放指针所对应的空间。
C++在C语言的基础上提出了自己的内存管理方式,new和delete。
3.new和delete
new 后面跟所要开辟空间中对应的类型
new int()括号表示初始化,括号里面的值表示初始化的值
new int [ ] 方括号表示所要开辟的空间的个数
delete注意对于开辟多个空间的new 也要对应在delete后面写个[ ]
例:
int main()
{
int* p1 = new int;//new 后面跟所要开辟空间对应的类型大小
int* p2 = new int(10);//括号代表初始化,其中为初始化的值
int* p3 = new int[10];//方括号代表所要申请的个数,这里就表示p3要开辟10个int空间
delete p1;
delete p2;
delete[] p3;//要和new对应
return 0;
}
new和delete在处理内置类型时其实和malloc/calloc和free是差不多的(注意new不支持realloc),但是在处理自定义类型时,delete和new不仅会开辟空间还会调用自定义类型的构造函数和析构函数
4. new和delete
new 和 delete 是用户进行 动态内存申请和释放的操作符 , operator new (这个operator和操作符重载的那个operator没有关系,只是函数名) 和 operator delete 是系统提供的全局函数 , new 在底层调用 operator new 全局函数来申请空间, delete 在底层通过 operator delete 全局函数来释放空间。其实operator new在其实现中也是调用了malloc来开辟空间的,只不过malloc如果开辟失败会返回NULL,但是operator new会接收这个消息做其他的错误报告处理。
1.内置类型
2.自定义类型
new 调用 operator new 调用 malloc 开辟出动态内存空间,之后在此空间上调用构造函数
先调用析构函数 之后 delete 调用 operator delete 调用 free 释放空间
总结:malloc free和new delete的区别:
1.malloc free是函数需要传参,new delete是操作符直接对对象进行操作
2.malloc申请空间时需要自己手动传参空间大小,new可以自动计算
3.malloc开辟的空间有时需要强转,但是new不需要,new后面跟的类型就是所开辟的空间类型
4.malloc申请失败会返回NULL,new申请资源失败会直接报错(需要捕获错误)
5. 对于自定义类型,new delete和在开辟空间之后调用构造函数 delete会在释放空间之前调用析构函数。
另外对于内存泄露:
内存泄漏是指程序未能释放或者不再使用的内存的情况。内存泄露并不是泄露,更偏向于丢失,指的是在应用程序分配某段内存之后因为涉及错误失去了这段内存区域的控制,并且忘记释放,造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄露,影响很大,比如操作系统、后台服务等等,出现内存泄露会导致响应越来越慢,最终卡死。
模板
1.泛型编程
我们为了实现一个通用的函数,之前学了函数重载
但是函数重载也有不好的地方:1.重载的函数仅仅只是类型不同,对于每一种类型重载都要写一个新的同名函数,太麻烦了。2.代码的可维护性不好,有可能因为逻辑错误,一个错而导致全错,还得一个一个去改。
而泛型编程的手段就是:编写和类型无关的通用代码,根据这个通用代码对应生成函数。
这时我们就引出了 —— 函数模板
2.函数模板
相当于活字印刷术的那个模具
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
其格式如下:
template <typename(这里也可以写成class) T(随便写的,T代表一个类型,随便写ABCD什么都行)>
void Swap (T&a , T&b)
{
T temp = a;
a = b;
b = temp;
}
在这格式中需要注意的点就是typename就是用来定义模板参数的关键字,也可以使用class来代替的(但是切记不可使用struct)
函数模板其实就是相当于一个蓝图,是编译器根据使用方式产生特定具体类型函数的模具(所以其实就是模板在教编译器帮我们写函数)
3.函数模板的实例化
函数模板的实例化:用不同的类型参数使用模板生成函数
函数模板的实例化分为 隐式实例化 和 显式实例化
隐式实例化 :让编译器根据实参推演模板参数的实际类型
例如:
template <class T>
A Add(const A&left , const A&right)
{
return left + rigtht;
}
int main()
{
int a1 = 0,a2 = 1;
double b1 = 1.2,b2 = 2.5;
Add(a1 + a2 ); //可以实现
Add(b1 + b2 ); //可以实现
Add(a1 + b1 ); //无法实现 ,编译错误 因为编译器识别不了A是什么类型
return 0;
}
注意:在模板中,编译器一般不会进行类型转换,因为他无法识别出到底该转化成谁。
那么对于上面这串代码中的Add(a1 + b1); 该怎么才能实现呢?
有两个办法:
1.强制类型转化
Add((double)a1, b1);(识别A为double)或者 Add(a1, (int)b1);(识别A为int)
2.显示实例化
Add< int > (强制让T为int)(a1 , b1);
模板参数的匹配原则 :
1.一个非模板函数可以和另一个同名模板函数同时存在,而且该函数模板还可以被实例化为这个非模板函数
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的Add版本
}
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函
数
}
4.类模板
格式:
template < class T1 , class T2 , ..., class Tn >class 类模板名{// 类内成员定义};
例 ,顺序表类的模板:
template <A>
class Seq
{
public:
Seq(int capacity = 4)
:_a(new A[capacity])
,_size(0)
,_capacity(capacity)
{}
~Seq();//演示一下类外定义
Seq(const Seq& x)
{
x._size = _size;
x._capacity = _capacity;
//没学深拷贝,地址暂时不会拷贝捏
}
private:
A* _capacity;
int _size;
int _capacity;
};
//在类外定义时,要注意格式
template <A>
Seq<A> :: ~Seq()
{
if(_a)
{
delete[] _a;
}
_size = _capacity = 0;
}
类模板实例化与函数模板实例化是不同的,类模板实例化只能显式实例化。在类模板名字后面跟一个<>,然后将实例化的类型放在<>中即可,类模板名字不算是一个类,实例化之后的结果才是类