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== 5throw "an exception"
}

int main()
{
    try{
        func(3);
        func(5);
    }
}

输出:

构造
析构
构造
/*在此处发生了异常*/
析构
/*在清理了对象之后再处理异常*/
an exception

results matching ""

    No results matching ""