06-封裝、內存布局、堆、構造和析構

《C++文章匯總》
上一篇介紹了引用和匯編《05-匯編補充&面向對象》,本文介紹封裝、內存布局和堆空間。

1.封裝

#include <iostream>
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()
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容