06-封装、内存布局、堆、构造和析构

《C++文章汇总》
上一篇介绍了引用和汇编《05-汇编补充&面向对象》,本文介绍封装、内存布局和堆空间。

1.封装

#include 
using namespace std;

struct Person {
private:
    int m_age;
public:
    void setAge(int age){
        if (age < 0) {
            m_age = 1;
        }else{
            m_age = age;
        }
    }
    int getAge(){
        return m_age;
    }
};
int main(){
    Person person;
    person.setAge(10);
    cout << "age:" << person.getAge() << endl;
    return 0;
}
//输出
age:10

2.内存空间布局

◼ 每个应用都有自己独立的内存空间,其内存空间一般都有以下几大区域
代码段(代码区)
✓ 用于存放代码
数据段(全局区)
✓ 用于存放全局变量等
栈空间
✓ 每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间 ✓ 自动分配和回收
堆空间
✓ 需要主动去申请和释放

3.堆空间

◼ 在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存
◼ 堆空间的申请\释放
一一对应关系
malloc \ free
new \ delete
new [] \ delete []
x86 32bit环境,指针变量占用4个字节


image

◼注意
申请堆空间成功后,会返回那一段内存空间的地址
申请和释放必须是1对1的关系,不然可能会存在内存泄露
◼ 现在的很多高级编程语言不需要开发人员去管理内存(比如Java),屏蔽了很多内存细节,利弊同时存在
利:提高开发效率,避免内存使用不当或泄露
弊:不利于开发人员了解本质,永远停留在API调用和表层语法糖,对性能优化无从下手

堆空间初始化

void test3(){
    int *p = (int *)malloc(4);
    *p = 0;
    //将4个字节中的每一个字节都设置为1,并不是将4个字节设置为1
    int size = sizeof(int) * 10;
    int *q = (int *)malloc(size);
    //momery set
    memset(q, 0, size);
    //从p地址开始的连续4个字节中的每一个字节都设置为1
    //memset(q,1,4);
    //将4个字节设置为1
    //00000000 00000000 00000000 00000001
    //将4个字节中的每一个字节都设置为1
    //00000001 00000001 00000001 00000001
}

堆空间new的时候初始化

void test4(){
    int *p0 = new int;//Mac平台会初始化为0,Windows平台未初始化
    int *p1 = new int();
    int *p2 = new int(5);
    cout << *p0 << endl;
    cout << *p1 << endl;
    cout << *p2 << endl;
}
//输出
0
0
5
image

memset函数是将较大的数据结构(比如对象、数组等)内存清零的比较快的方法


图片.png

4.对象的内存

◼ 对象的内存可以存在于3种地方
全局区(数据段):全局变量
栈空间:函数里面的局部变量
堆空间:动态申请内存(malloc、new等)

struct Person {
    int m_age;
};
//全局区
Person g_person;
int main(){
    //栈空间
    Person person;
    //堆空间
    Person *per = new Person;
}

5.构造函数Constructor

◼ 构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作

struct Person {
    int m_age;
    
    Person(){
        m_age = 0;
        cout << "Person()" << endl;
    }
    
    Person(int age){
        m_age = age;
        cout << "Person(int age)" << endl;
    }
    void display(){
        cout << m_age << endl;
    }
};

int main(){
    Person person1;
    person1.display();
    Person person2(20);
    person2.display();
    Person person3(30);
    person3.display();
    getchar();
    return 0;
}
//输出
Person()
0
Person(int age)
20
Person(int age)
30

◼特点
函数名与类同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数
一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象,若不调用会报错

struct Person {
    int m_age;
//    Person(){
//        m_age = 0;
//        cout << "Person()" << endl;
//    } 
    Person(int age){
        m_age = age;
        cout << "Person(int age)" << endl;
    }
    void display(){
        cout << m_age << endl;
    }
};

int main(){
    Person person1;//会报错,Person()构造函数被注释了
    person1.display();
    Person person2(20);
    person2.display();
    Person person3(30);
    person3.display();
    getchar();
    return 0;
}

◼注意
通过malloc分配的对象不会调用构造函数

struct Car {
    int m_price;
    Car(){
        cout << "Car::car()" << endl;
    }
    void run(){
        cout << "Car::run " << m_price << endl;
    }
};
int main(){
    Car car;//调用构造函数
    Car *c = new Car;//调用构造函数
    delete c;
    Car * cc = (Car *)malloc(sizeof(Car));//没有调用构造函数
    cc->m_price = 10;
    cc->run();
}
//输出
Car::car()
Car::car()
Car::run 10 

◼ 一个广为流传的、很多教程\书籍都推崇的错误结论:
默认情况下,编译器会为每一个类生成空的无参的构造函数
正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数
✓ (哪些特定的情况?以后再提)

struct Teacher {
    int m_age;
//    Teacher(){
//        cout << "Teacher()" << endl;
//    }
};
int main(){
    Teacher teacher;
    teacher.m_age = 10;//不会默认生成构造函数
}

函数声明和构造函数易混淆:4个无参,3个有参,一共创建了7个Person对象,有两个是函数声明,并没有创建Person对象


image
struct Person {
    int m_age;
    
    Person(){
        m_age = 0;
        cout << "Person()" << endl;
    }
    
    Person(int age){
        m_age = age;
        cout << "Person(int age)" << endl;
    }
    void display(){
        cout << m_age << endl;
    }
};
Person g_person0;//Person()
Person g_person1();//函数声明
Person g_person2(20);//Person(int age)
int main(){
    Person person0;//Person()
    Person person1();//函数声明
    Person person2(20);//Person(int age)
    
    Person *p0 = new Person;//Person()
    Person *p1 = new Person();//Person()
    Person *p2 = new Person(20);//Person(int age)
    //4个无参,3个有参,一共创建了7个Person对象,有两个是函数声明,并没有创建Person对象
    getchar();
    return 0;
}
//输出
Person()
Person(int age)
Person()
Person(int age)
Person()
Person()
Person(int age)

6.成员变量初始化

I.没有自定义构造函数的情况下:new Person()堆空间成员变量初始化为0,栈空间没有初始化成员变量,里面是中断代码cccccccc

struct Person {
    int m_age;
};
//全局区:成员变量初始化为0
Person g_person;
int main(){
    //栈空间:没有初始化成员变量,里面是cccccccc
    Person person;
    //堆空间:Windows没有初始化成员变量,Mac初始化了成员变量
    Person *p0 = new Person;
    //堆空间:成员变量初始化为0
    Person *p1 = new Person();
    
    cout << g_person.m_age << endl;
    cout << person.m_age << endl;//Windows报错
    cout << p0->m_age << endl;
    cout << p1->m_age << endl;
    
    getchar();
    return 0;
}
//输出
0
86053
0
0

II.自定义了构造函数,但没有对成员变量赋值初始化,Windows和Mac平台下,全局区依然初始化成员变量为0
Windows平台下Person *p0 = new Person堆空间没有初始化成员变量,
Windows平台下Person *p0 = new Person()堆空间没有初始化成员变量,编译器认为自定义了构造函数,会自己初始化成员变量,编译器不再帮忙初始化成员变量
Mac平台下堆空间初始化成员变量为0


图片.png

◼ 如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化

struct Person {
    int m_age;
    Person(){
        
    };
};
//全局区:成员变量初始化为0
Person g_person;
int main(){
    //栈空间:没有初始化成员变量,里面是cccccccc
    Person person;
    //堆空间:Windows没有初始化成员变量,Mac初始化了成员变量
    Person *p0 = new Person;
    //堆空间:Windows没有初始化成员变量,Mac初始化了成员变量
    Person *p1 = new Person();
    
    cout << g_person.m_age << endl;
    cout << person.m_age << endl;//Windows报错
    cout << p0->m_age << endl;
    cout << p1->m_age << endl;
    
    getchar();
    return 0;
}
//输出
0
86053
0
0

Windows下Person对象数组,没有自定义构造函数,成员变量都为0,自定义了构造函数,成员变量未初始化
Mac下均初始化为0

struct Person {
    int m_age;
    //Person(){
    //};
};
int main(){
    Person *p = new Person[3]();
    cout << p[0].m_age << endl;
}
windows下为0

struct Person {
    int m_age;
    Person(){
    };
};
int main(){
    Person *p = new Person[3]();
    cout << p[0].m_age << endl;
}
windows下为-842150451,显然没有初始化

struct Person {
    int m_age;
    //Person(){
    //};
};
int main(){
    Person *p = new Person[3]{};
    cout << p[0].m_age << endl;
}
windows下为0

struct Person {
    int m_age;
    Person(){
    };
};
int main(){
    Person *p = new Person[3]{};
    cout << p[0].m_age << endl;
}
windows下为-842150451,显然没有初始化

7.对象初始化,对象所有成员变量清零

   Person(){
        memset(this, 0, sizeof(Person));
    };

8.析构函数Destructor

◼析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作
◼特点
函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数
◼注意
通过malloc分配的对象free的时候不会调用析构函数
◼构造函数、析构函数要声明为public,才能被外界正常使用

struct Person {
    int m_age;
    Person(){
        memset(this, 0, sizeof(Person));
        cout << "Person Person()" << endl;
    };
    ~Person(){
        cout << "Person ~Person()" << endl;
    }
};
int main(){
    cout << 1 << endl;
    {
        Person person;
    }
    cout << 2 << endl;
    getchar();
    return 0;
}
//输出
1
Person Person()
Person ~Person()
2

malloc函数创建的对象不会调用析构函数

struct Person {
    int m_age;
    Person(){
        memset(this, 0, sizeof(Person));
        cout << "Person Person()" << endl;
    };
    ~Person(){
        cout << "Person ~Person()" << endl;
    }
};
int main(){
    Person * p = (Person *)malloc(sizeof(Person));
    free(p);
}
//没有打印输出

new Person会调用析构函数

struct Person {
    int m_age;
    Person(){
        memset(this, 0, sizeof(Person));
        cout << "Person Person()" << endl;
    };
    ~Person(){
        cout << "Person ~Person()" << endl;
    }
};
int main(){
    Person *p = new Person;
    delete p;
}
//输出
Person Person()
Person ~Person()

查看当前平台栈空间堆空间的地址大小

int main(){
    Person *p = new Person;
    //堆空间的地址值
    cout << p << endl;
    //栈空间的地址值
    cout << &p << endl;
    delete p;
}
//输出:栈空间的地址值0x7ffeefbff480大于堆空间地址值0x1004b6370
Person Person()
0x1004b6370
0x7ffeefbff480
Person ~Person()

你可能感兴趣的:(06-封装、内存布局、堆、构造和析构)