C/C++程序编译时内存分为5大存储区:堆区、栈区、全局区、文字常量区、程序代码区
C++ 程序中的内存分为两个部分:
递归函数需要栈空间,而栈空间取决于递归的深度
堆 | 栈 | |
---|---|---|
申请方式 | 程序员申请和释放 | 系统自动分配 |
分配方式 | 动态分配 | 静态分配、动态分配 |
空间特点 | 不连续内存区域 堆大小受限于系统中有效的虚拟内存大小 向高地址方向扩充 |
连续内存区域 大小由OS预设好 向低地址方向扩充 |
内存管理 | 系统维护一个记录空闲内存地址的链表 (当系统收到程序申请时,遍历该链表,寻找第一个空间大于申请空间的堆结点,将该结点空间分配给程序,并删除链表中结点。大多数系统还会在这块内存空间的首地址记录本次分配的大小,同时将多余部分重新放入空闲链表中。) |
只要栈的剩余空间大于所申请空间,系统为程序提供内存,否则报异常提示栈溢出 |
分配效率 | 速度慢,会有碎片 (操作是由C/C++函数库提供,分配堆内存的时候需要算法寻找合适大小的内存,获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存) |
高 (栈是系统提供的数据结构,计算机在底层对栈提供支持,分配专门 寄存器存放栈地址,栈操作有专门指令,出入栈操作也很简单) |
思路【源自网络】
栈就像去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用)。吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作。快捷,但是自由度小。
堆就像是自己动手做喜欢吃的菜肴,比较麻烦,但是符合自己的口味,而且自由度大。
堆内存泄露
避免方式:
在程序中重复使用已有的对象,避免频繁地创建新对象,提高程序的运行效率,减少内存分配和回收的开销,避免产生内存碎片
都可用于内存的动态申请和释放
new / delete | malloc / free |
---|---|
C++关键字(编译器) 支持重载 |
C/C++语言标准库函数(头文件) 支持覆盖 |
自动计算要分配的空间的大小 | 手工计算 |
类型安全 返回与构造对象类型相同的指针 |
不支持 分配成功返回void*指针,后续需要进行强制类型转换 |
new:
delete: 简单类型调用free函数 复杂类型:
|
malloc:仅分配内存空间 free:仅回收内存空间 不能执行构造函数和析构函数 操作对象必须是明确大小的 (被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。) |
分配失败抛出bac_alloc异常 | 分配失败返回NULL |
申请内存,但不初始化对象,当需要的时候才进行初始化操作
malloc申请的空间的值随机初始化,calloc申请的空间的值初始化为0
给动态分配的空间分配额外的空间,用于扩充容量
重载是水平关系,覆盖是垂直关系
函数名相同,参数类型和数目不同
在派生类中覆盖基类中的同名函数,重写函数体。要求基类函数必须为虚函数,有相同的参数类型、个数和返回值类型
派生类中的函数屏蔽了基类中的同名函数
均拥有成员函数,可以设置访问修饰符
class | struct |
---|---|
成员默认为private | 成员默认为public |
默认private继承 | 默认public继承 |
基类 | 派生类 | |
---|---|---|
public 继承 | public | public |
protected | protected | |
pivate | 不可见 | |
protected 继承 | public | protected |
protected | protected | |
pivate | 不可见 | |
private 继承 | public | private |
protected | private | |
pivate | 不可见 |
#include
using namespace std;
struct S{
int x;
char y;
}
int main(){
cout << offsetof(S, x);
return 0;
}
const_cast:将const类型转换为非const类型
使头文件只被编译一次
不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被同时包含
底层实现机制不同:
原先从键盘(标准输入的默认设备)接受的输入,变为从文件读取
调用拷贝构造函数,首先用构造函数创建一个临时对象,然后用拷贝构造函数将该临时对象拷贝到正在创建的对象
// 拷贝初始化:先为字符串“hello world”创建临时对象,再把临时对象作为参数,使用拷贝构造函数构造str1
string str1 = "hello world";
// 隐式调用拷贝构造函数
string str2 = str1;
直接调用与实参匹配的构造函数
string str1("hello world");
// 调用拷贝构造函数对str2进行初始化
string str2(str1);
浅拷贝只是拷贝一个指针,并没有新开辟一个地址,拷贝的指针和原来的指针指向同一块地址
深拷贝不仅拷贝值,还开辟出一块新的空间用来存放新的值。即使原先的对象被析构掉,释放内存了也不会影响到深拷贝得到的值。
解决多继承造成的菱形继承问题,通过在每个派生类与共同基类之间建立虚拟基类来实现
在多重继承的情况下,如果一个派生类从两个或更多的基类派生,而这些基类共同派生自同一个基类时,就会产生所谓的“菱形继承”问题
例如,如果有两个类B和C都从类A继承了一些属性和方法,然后又定义了一个类D,从B和C中分别继承了属性和方法,那么在D中就会包含两份从A继承的成员。这种情况下,如果使用D的对象调用从A中继承的成员,就会导致歧义、冗余和效率浪费等问题。
虚拟继承机制使得从共同基类继承来的成员在派生类中只保留一份,从而避免了重复继承的问题。
C++调用C函数
// xx.h
extern int add(...)
// xx.c
int add(){
}
// xx.cpp
extern "C" {
#include "xx.h"
}
C调用C++
// xx.h
extern "C"{
int add();
}
// xx.cpp
int add(){
}
// xx.c
extern int add();
没有被初始化过的指针
指针最初指向的内存已经被释放
#include
using namespace std;
int main()
{
cout << __cplusplus << endl;
return 0;
}
199711 | C++ 98 |
201103 | C++ 11 |