博客 / 詳情

返回

小凱15天快速講完c語言-簡單學習第九課

0. 複習

0.1 堆的申請釋放

申請:new
釋放:delete
使用new和delete的好處:
1.使用new,new可以識別類型,申請什麼類型,返回的就是什麼類型的指針,就無需強制轉換了。
2.new會調用構造函數,delete會調用析構函數
和malloc,free有什麼區別:
malloc和free是函數,delete是運算符
//假如想要申請10個int
int* p = new int[10]{1,2,3,4,5,6};
//.....
delete []p;
p = nullptr;
//假如申請的是1個int,初始值是50
int* p = new int(50);
delete p;
p = nullptr;

0.2 函數重載

函數重載:在相同的作用域內,函數名相同,參數不同可以構成重載
用處:構成重載之後,調用函數的時候,編譯器能夠根據我們傳遞不同的參數自動的選擇調用哪一個函數。
好處:不用去記憶過多的函數名了,這個也被稱為 接口複用
名稱粉碎:編譯器根據函數名和參數,構建一個新的函數名。從編譯器看來,同名函數的不同參數實際是不同的名稱。

0.3 默認參數

我們可以給函數設置默認參數,就是默認值,不傳參的時候,自動傳遞默認值。注意事項:
1.默認參數只能從右往左設置。
2.中間不能中斷
3.一個函數如果有聲明,也有定義,默認參數應該寫在聲明中。
4.有默認參數的同時,還有這個函數的重載,容易造成二義性問題

0.4 引用

給一個變量起一個別名
對於別名的操作和對於變量的操作,此時就是一樣的。
別名和變量名公用內存空間
引用可以代替指針的一部分功能
有什麼特點呢??
1.必須初始化
2.一經引用,就不能再引用其他對象了。
3.能夠使用引用的地方,不應該使用指針。因為引用更為安全。
char cCh = 'a';
char& m = cCh;

0.5 輸入輸出

有一個命名空間的概念:
輸出:

#include<iostream>
1.  using namespace  std;
2.  using std::cout;
3.  std::cout<<"hello world";

輸入:

int  nNum = 0;
std::cin>>nNum;
輸出的時候,換行:
std::endl;  

0.6 類的基本語法

0.6.1 類的基本定義方式

XXXX.h
class 類名
{
public:         //   公有的
int  m_nNum1 ;   // 成員變量,屬性,類中的數據
void  Fun1();        // 成員函數,方法,行為
protected:   //   保護的
int  m_nNum2 ;   // 成員變量,屬性,類中的數據
void  Fun2();        // 成員函數,方法,行為
private:        //   私有的
int  m_nNum3 ;   // 成員變量,屬性,類中的數據
void  Fun3();        // 成員函數,方法,行為
};
XXXX.cpp
void  類名::Fun1()
{
        //代碼
}  
void  類名::Fun2()
{
        //代碼
}  
void  類名::Fun3()
{
        //代碼
}  

總結:
1..h中些類的聲明,成員函數的定義一般不寫在類內。
.cpp中寫成員函數的實現
2.使用的時候,包含類的頭文件即可。
3.使用類定義出來的變量,叫做對象。
4.命名規範:

a. 類名一般以C開頭
b. 成員變量的名字,一般以m_開頭

5.類的大小隻和成員變量有關。和普通的成員函數沒有關係。
6.當我們定義了一個類對象,實際上就開闢了一塊內存,裏面放置成員變量

0.6.2 this指針

圖片.png
類的大小是不包含函數的。定義類對象,裏面只有數據。
一個類的所有對象,使用的都是同一份函數。
一個函數是如何區分這麼多對象的呢???
調用成員函數的時候,會隱含着傳遞對象的地址進去。這個就是this指針。
this指針就代表着調用這個函數的對象地址。
我們看着成員函數是 3 個參數,實際上是 4 個參數,含有一個自己的地址
默認傳遞自己地址 是編譯器做的,不需要我們自己干預。
圖片.png

0.6.3 類和結構體有什麼區別???

C++中,類和結構體幾乎沒有區別的,結構體也能封裝函數,也有三種權限。但是還是依然保留着 C語言中使用結構體的習慣,結構體中只放置數據,不放置函數。
唯一的區別:
類的默認權限是 私有的
結構體的默認權限是 公有的

0.6.4 面向對象

根據程序需要,抽象出一個,類中設計成員變量和成員函數。設計完畢之後,他是一個數據類型。
我們使用類定義出來的變量,就是對象
面向對象有三大特性:封裝,繼承,多態。
使用成員的方式:

a. 通過對象使用    要受到權限控制的
         obj.m_nNum = 100;
         obj.Fun();
b. 在類自己的成員函數中使用自己的成員  不受權限控制
i. this->m_nNum = 200;
ii. this->Fun();

1.1 構造函數

1.1.1 構造的基本語法和調用規則

特點:

a. 構造函數的作用,是用於編寫初始化規則的
b. 構造函數名和類名是一致的
c. 構造函數沒有返回值,也不需要寫返回值類型
d. 構造函數可以有參數,一個類可以實現多個構造函數,他們是可以重載的
e. 構造函數是自動調用的,在定義對象的時候,就會自動調用,什麼算是定義對象呢??
i. 局部對象
ii. 全局對象,靜態局部對象
iii. 堆對象
#include <iostream>class CStudent {
class CStudent {
public:
    CStudent()
    {
        std::cout << "我是無參構造函數" << std::endl;
        strcpy_s(this->m_szName, "");
        this->m_nId = 0;
        this->m_nScore = 0;
    }
    CStudent(const char* szName,int nId,int nScore)
    {
        std::cout << "我是有參構造函數" << std::endl;
        strcpy_s(this->m_szName, szName);
        this->m_nId = nId;
        this->m_nScore = nScore;
    }
    void PrintfStu();
    void SetStu(const char* szName, int nId, int nScore);
private:
    char  m_szName[20]; //姓名
    int m_nId;          //學號
    int m_nScore;       //分數
};
void CStudent::PrintfStu()
{
    printf("%s ", this->m_szName);
    printf("%d ", this->m_nId);
    printf("%d ", this->m_nScore);
}
void CStudent::SetStu(const char* szName, int nId, int nScore)
{
    //pstu->szName = szName
    strcpy_s(this->m_szName, szName);
    this->m_nId = nId;
    this->m_nScore = nScore;
}

//定義了全局對象,自動調用無參構造
//全局對象的構造函數調用,在main函數之前就會調用
CStudent g_obj;
int main()
{
    //定義了局部對象,自動調用無參構造
    CStudent obj1;
    //定義了局部對象,自動調用有參構造
    CStudent obj2("xiaoming", 19, 20);
    //定義了5個局部對象,2個有參,3個無參
    CStudent arr[5] = { {"xiaobai",20,79},{"xiaohei",21,80} };//這裏是否定義了對象,是否調用構造??
    //定義了堆中的對象,自動調用無參構造,new會調用構造函數
    CStudent* p1 = new  CStudent;//這裏是否定義了對象,是否調用構造??

    //這裏就只申請了空間,malloc不會調用構造函數
    CStudent* p2 = (CStudent*)malloc(sizeof(CStudent));//這裏是否定義了對象,是否調用構造??

    //定義了10個堆中的對象,自動調用2個有參構造,8個無參構造,new會調用構造函數
    CStudent* p3 = new  CStudent[10]{//這裏是否定義了對象,是否調用構造??
    {"xiaobai",20,79},
    {"xiaohei",21,80}
    };
}

1.1.2 關於初始化的問題

圖片.png

怎麼算式初始化呢???
需要寫一個初始化列表

#include <iostream>
class CNoteBook
{
public:
    CNoteBook(double fPrice, int nType):m_fPrice(fPrice), m_nType(nType)
    {

    }
private:
    double m_fPrice;
    int m_nType;
};

class CStudent {
public:
    CStudent() 
        :m_nId(0), m_nScore(0), m_szName{""}, 
        m_nNum(58),m_nNum2(m_nId),m_objNoteBook(5000.38,3)//這三個是放在初始化列中比較合適
    {
        std::cout << "我是無參構造函數" << std::endl;
    }
    CStudent(const char* szName, int nId, int nScore)
        :m_nId(nId), m_nScore(nScore), m_szName{""},
        m_nNum(58), m_nNum2(m_nId), m_objNoteBook(5000.38, 3)
    {
        std::cout << "我是有參構造函數" << std::endl;
        strcpy_s(this->m_szName, szName);
    }
    void PrintfStu();
    void SetStu(const char* szName, int nId, int nScore);
private:
    char  m_szName[20]; //姓名
    int m_nId;          //學號
    int m_nScore;       //分數
    CNoteBook m_objNoteBook ;// 成員是一個對象,這個對象沒有無參構造,就必須傳參
    const int m_nNum;       //const 也必須初始化
    int& m_nNum2;           //引用必須初始化
};
void CStudent::PrintfStu()
{
    printf("%s ", this->m_szName);
    printf("%d ", this->m_nId);
    printf("%d ", this->m_nScore);
}
void CStudent::SetStu(const char* szName, int nId, int nScore)
{
    //pstu->szName = szName
    strcpy_s(this->m_szName, szName);
    this->m_nId = nId;
    this->m_nScore = nScore;
}

int main()
{
    CStudent obj1;
    CStudent obj2("xiaoming", 19, 21);
}

總結:
1.在初始化列表中初始化,才算是初始化
2.引用成員,const成員,沒有無參構造的 類對象成員 都需要在初始化列表中去初始化。

1.2 析構函數

1.2.1 析構函數基本語法和調用規則

特點:

a.析構函數,用於對象去釋放資源的
b.析構函數的函數名:  ~  類名
c.析構沒有返回值,也不需要寫返回值類型
d.析構函數不能有參數,析構函數不能重載,一個類只能有1個析構函數
e.析構函數在對象被銷燬的時候,自動調用
i.局部對象,離開作用域的時候,自動銷燬,參數是對象的話,等同於局部對象
ii.全局對象,靜態局部對象  程序結束的時候,會自動銷燬
iii.堆對象,delete釋放堆的時候,會自動銷燬
iv.只要銷燬,就會要調用析構函數
#include <iostream>class CStudent {
class CStudent {
public:
    CStudent()
    {
        std::cout << "我是無參構造函數" << std::endl;
        strcpy_s(this->m_szName, "");
        this->m_nId = 0;
        this->m_nScore = 0;
    }
    CStudent(const char* szName, int nId, int nScore)
    {
        std::cout << "我是有參構造函數" << std::endl;
        strcpy_s(this->m_szName, szName);
        this->m_nId = nId;
        this->m_nScore = nScore;
    }
public://析構函數
    ~CStudent()
    {
        std::cout << "我是析構函數" << std::endl;
    }


private:
    char  m_szName[20]; //姓名
    int m_nId;          //學號
    int m_nScore;       //分數
};

//定義了全局對象,自動調用無參構造
//全局對象的構造函數調用,在main函數之前就會調用
CStudent g_obj;


void Fun()
{
    //定義了局部對象,自動調用無參構造
    CStudent obj1;
    //定義了局部對象,自動調用有參構造
    CStudent obj2("xiaoming", 19, 20);
    //定義了5個局部對象,2個有參,3個無參
    CStudent arr[5] = { {"xiaobai",20,79},{"xiaohei",21,80} };//這裏是否定義了對象,是否調用構造??
    //定義了堆中的對象,自動調用無參構造,new會調用構造函數
    CStudent* p1 = new  CStudent;//這裏是否定義了對象,是否調用構造??

    //這裏就只申請了空間,malloc不會調用構造函數
    CStudent* p2 = (CStudent*)malloc(sizeof(CStudent));//這裏是否定義了對象,是否調用構造??

    //定義了10個堆中的對象,自動調用2個有參構造,8個無參構造,new會調用構造函數
    CStudent* p3 = new  CStudent[10]{//這裏是否定義了對象,是否調用構造??
    {"xiaobai",20,79},
    {"xiaohei",21,80}
    };
    delete p1;
    free(p2);
    delete []p3;
}
int main()
{
    Fun();
}

1.2.2 析構函數的用處

可以用來,釋放需要釋放的資源

#include <iostream>
class CStudent {
public:
    CStudent()
        :m_nId(0), m_nScore(0), m_pName( nullptr)
    {
        std::cout << "我是無參構造函數" << std::endl;
        this->m_nId = 0;
        this->m_nScore = 0;
    }
    CStudent(const char* szName, int nId, int nScore)
        :m_nId(nId), m_nScore(nScore), m_pName(nullptr)
    {        
        int nLenth = strlen(szName) + 1;
        m_pName = new char[nLenth] {0};
        strcpy_s(m_pName, nLenth, szName);
    }

public://析構函數
    ~CStudent()
    {
        std::cout << "我是析構函數" << std::endl;
        if (m_pName!=nullptr)
        {
            delete[]m_pName;
            m_pName = nullptr;
        }
    }
private:
    char*  m_pName; //姓名
    int m_nId;          //學號
    int m_nScore;       //分數
};
int main()
{
    CStudent objl("xiaoming",20,90);

}

1.3 需要注意的地方

1.一個類必須有一個構造函數,必須有一個析構函數。
2.如果我們不實現的話,編譯器會自動提供構造函數與析構函數。沒有功能
3.如果我們自己寫了構造函數析構,編譯器就不會提供了
4.構造函數的作用:用來初始化成員變量的數據。
5.析構函數的作用:一般用來清理資源。比如我們可以在析構中釋放堆空間。
6.這兩個函數的主要特點:他們都是自動調用的
7.構造函數和析構的調用順序,一般是相反的。

1.4 拷貝構造函數(複製構造函數)

拷貝構造函數,它的作用是一個對象去初始化另外一個對象,此時會調用拷貝構造函數
圖片.png

拷貝構造函數,如果我們不實現的話,編譯器也會默認實現一個。我們剛才實現的拷貝構造函數的功能和編譯器默認實現的功能是一樣的。
既然,編譯器提供了一個,我們還有必要自己實現麼????
有必要,編譯器實現的是 淺拷貝
有些時候,比如成員中有指針指向堆空間的時候,我們需要實現一個 深拷貝

圖片.png

#include <iostream>
class CStudent {
public:
    char* m_pName; //姓名
    int m_nId;          //學號
    int m_nScore;       //分數
public:
    CStudent()
        :m_nId(0), m_nScore(0), m_pName(nullptr)
    {
        std::cout << "我是無參構造函數" << std::endl;
        this->m_nId = 0;
        this->m_nScore = 0;
    }
    CStudent(const char* szName, int nId, int nScore)
        :m_nId(nId), m_nScore(nScore), m_pName(nullptr)
    {
        int nLenth = strlen(szName) + 1;
        this->m_pName = new char[nLenth] {0};
        strcpy_s(this->m_pName, nLenth, szName);
    }
public://析構函數
    ~CStudent()
    {
        std::cout << "我是析構函數" << std::endl;
        if (this->m_pName != nullptr)
        {
            delete[] this->m_pName;
            this->m_pName = nullptr;
        }
    }
    //這就是拷貝構造函數
    CStudent(CStudent& other)
    {
        //this->m_pName = other.m_pName;
        int nLenth = strlen(other.m_pName) + 1;
        this->m_pName = new char[nLenth] {0};
        strcpy_s(this->m_pName, nLenth, other.m_pName);


        this->m_nId = other.m_nId;
        this->m_nScore = other.m_nScore;
    }
};
int main()
{
    int a = 10;
    int b = a;//這叫做用一個整型變量初始化另外一個整型變量
    CStudent obj1("xiaoming",10,90);
    CStudent obj2 = obj1;//這就叫做用一個對象初始化另外一個對象
    obj1.m_pName[0] = 'n';
}

總結:當我們需要實現深拷貝的時候,需要實現拷貝構造函數。
拷貝構造函數什麼時候,會被調用呢???
一個對象初始化,另外一個對象的時候、
直接初始化
函數傳參的時候,也是一個對象初始化另外一個對象
圖片.png

轉換構造(瞭解即可)

只有一個參數的構造函數,也叫做轉換構造

class CNoteBook
{
public:
    //轉換構造有的時候比較方便,但是更多的時候,容易引起歧義,容易讓人誤以為notebook2是 double類型
    //可以通過explicit禁止轉換
    explicit CNoteBook(double fPrice) :m_fPrice(fPrice)
    {

    }
private:
    double m_fPrice;
};
int main()
{
    CNoteBook notebook1(10.5);

    CNoteBook notebook2 = 20.8;

    notebook2 = 50;

}
user avatar mrbone11 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.