1. 一、地址
1.1. 1.对于32位的操作系统,地址都是32位的,前0不可以省略
例:
int *p = NULL;
cout<<p<<endl;//输出00000000,而不是0
1.2. 2.指针可以通过内存地址直接访问数据,可避免在程序中复制大量的代码。因此指针效率最高
例:
a[j]:a+j
a[i][j]:a[i]+j
2. 二、内存
2.1. 1.数据在内存中的存放形式
1)栈区:由编译器自动分配和释放
一般存放函数的参数值、局部变量的值等
2)堆区:由程序员分配及释放。若程序员不释放,程序结束后可能由OS回收
3)寄存器区:用来保存栈顶指针和指令指针
4)全局区(静态区):全局变量和静态变量是存储在一起的。初始化的和未初始化的是分开的。
程序结束后由系统释放
5)文字常量区:程序结束后由系统释放
存放常量字符串
6)程序代码区:存放函数体的二进制代码
2.2. 2.堆与栈的区别:
堆 | 栈 | |
---|---|---|
内存申请方式 | 程序员自己申请,申请时需要指明申请的大小 | 系统自己分配 |
系统响应申请的方式 | 分配算法复杂,例如: 遍历内存空闲地址链表,找到比申请的要大的堆结点,将其中申请的大小分配给程序,程序空间放入空闲链表中 |
简单,移到一下指针即可 栈的剩余空间不足时会overflow |
系统响应释放的方式 | 释放算法复杂 | 函数执行后,函数所占的空间自动释放 |
最大空间大小 | 由系统中的有效虚拟内存决定 | 2M |
执行效率 | 慢、易产生内存碎片、灵活 | 快、后进先出,无内存碎片 |
空间中对象的生命周期 | 堆上对象释放前一直可以被使用 | 函数返回后,栈上对象会被销毁。对象在栈上的空间随时可能被后续代码改写。 |
多线性编程 | 栈上对象有可能被多个线程访问,潜在有竞争问题。 内存的分配和释放通常需要加锁 |
栈空间不共享,栈上对象通常没有多线程竞争问题。 |
适合放什么样的对象 | 很大的对象 大小在编译时不确定的对象 函数需要返回对象,但又不能作为返回值的对象 |
补充:
2.2.1. (1)指针对内存数据有保护作用
栈可以为它其中的某个内存单元命名,但堆中的每个内存单元都是匿名(这是对数据的保护)的。必须先在堆中申请一个内存单元地址,然后把它的保存在一个指针中,只有该指针才可以访问该内存单元的数据。
2.2.2. (2)delete运算符只能删除堆中的空间,删除栈中的空间会导致出错
int main()
{
A a;
A *p = new A;
A *q = &a;
delete p;//正确,因为p指向堆中的未命名空间
delete q;//错误,因为q指向栈中的空间
return 0;
}
2.2.3. (3) “函数需要返回对象,但又不能作为返回值的对象”举例
Shape * create_shape(shape_type type)
{
switch (type){
case 0:
return new Circle();
case 1:
return new Triangle();
}
}
2.3. 3.内存泄漏
(1)某指针指向某堆结点,没有释放
class A
{};
int main()
{
A *p = new A;//没有释放
return 0;
}
(2)指针是一个局部变量,在释放内存之前,因为离开作用域而消失了
class A
{};
void Test()
{
A *p = new A;
}
int main()
{
//无法释放
return 0;
}
(3)用父类指向一个子类对象,析构函数没有实现多态
class A
{};
class B:public A
{
int x;
};
void Test()
{
A *p = new A;
}
int main()
{
A *p = new B;//只释放了基类所占用的空间,x所占用的空间没有释放
delete p;
return 0;
}
3. 三、堆
3.1. 1.访问堆中成员的两种方式
访问堆中成员有两种方式,它们是等价的:
A *p = new A();
1)(*p).get();
2)p->get
例:
class A
{
int x;
public:
A(int i):x(i){}
int get(){return x;}
};
int main()
{
A *p = new A(1), *q = new A(2);
cout<<(*p).get()<<' '<<q->get()<<endl;
return 0;
}
输出:1 2
4. 四、栈
4.1. 1.在函数调用时的入栈和出栈的顺序:
入栈顺序:
1)被调用函数的下一行地址
2)参数(从右往左)
3)函数的全局变量
出栈的顺序相反。
4.2. 2.栈中的对象,遇到“}”时自动析构(释放栈空间,并调用析构函数)
例:
class A
{
public:
~A(){cout<<"析构"<<endl;}
};
void Test()
{
A a;
}
int main()
{
Test();
return 0;
}
输出:析构
因此,把栈内对象的地址作为函数返回值是危险的!!!
4.3. 3. 栈展开
当异常发生时,编译器会自动插入特殊代码,以保证所有栈上对象被析构。
例:
class A
{
public:
A(){cout<<"构造"<<endl;}
~A(){cout<<"析构"<<endl;}
};
void func(int n)
{
A a;
if (n== 5)
throw "an exception"
}
int main()
{
try{
func(3);
func(5);
}
}
输出:
构造
析构
构造
/*在此处发生了异常*/
析构
/*在清理了对象之后再处理异常*/
an exception