1. 程式人生 > >c++類和物件的詳細介紹(二)

c++類和物件的詳細介紹(二)

一,物件的動態建立和釋放

1.什麼是物件的動態建立和釋放

  通常我們建立的物件都是由C++編譯器為我們在棧記憶體中建立的,我們無法對其進行生命週期的管理。所以我們需要動態的去建立該物件,因此我們需要在堆記憶體中建立物件和釋放物件。在C語言中為我們提供了malloc()函式和free()函式來為我們提供在堆記憶體中分配變數的方式,但是在C++中引入了new和delete關鍵字來讓我們動態的建立和釋放變數。

2.new和delete關鍵字

new關鍵字是用來在堆記憶體中建立變數的,格式為:Type * ptr = new Type(常量/表示式); 其引數列表中的常量/表示式可以用來給變數初始化,也可以省略不寫。其返回結果為該型別的指標。如果記憶體分配失敗則返回空指標。
delete關鍵字是用來釋放用new關鍵字建立的記憶體,格式為delete ptr(釋放陣列必須需要加中括號,delete [] ptr)。

3.new和delete關鍵字與malloc和free的區別

new關鍵字在分配記憶體的時候,會根據其建立的引數呼叫相應的類的建構函式。delete關鍵字會在釋放記憶體之前,會首先呼叫類的解構函式釋放物件中定義的記憶體。
malloc和free關鍵字不會去呼叫類的建構函式和解構函式。

4.new和delete關鍵字示例

# define _CRT_SECURE_NO_WARNINGS
# include<iostream>

using namespace std;

class Teacher
{
public:
    char * name;
    int age;
public:
    /* 無參建構函式 */
    Teacher()
    {
        name = NULL;
        age = 0;
        cout << "無參建構函式被執行..." << endl;
    }
    /* 有參建構函式 */
    Teacher(char * name, int age)
    {
        /* 在建構函式中分配堆記憶體 */
        this->name = new char[sizeof(name) + 1];
        /* 初始化成員變數 */
        strcpy(this->name, name);
        this->age = age;
        cout << "有參建構函式被執行..." << endl;
    }
    /* 拷貝建構函式 */
    Teacher(const Teacher &student)
    {
        /* 重新分配記憶體 */
        this->name = new char[sizeof(name) + 1];
        /* 初始化成員變數 */
        strcpy(this->name, name);
        this->age = age;
        cout << "拷貝建構函式被執行..." << endl;
    }
    /* 解構函式 */
    ~Teacher()
    {
        if (this->name != NULL)
        {
            delete [] this->name;
            this->name = NULL;
            this->age = 0;
        }
        cout << "解構函式被執行..." << endl;
    }
};

int main()
{
    /* 建立int變數,並釋放 */
    int * a = new int;
    int * b = new int(100);
    delete a;
    delete b;
    /* 建立double變數,並釋放 */
    double * c = new double;
    double * d = new double(10.1);
    delete c;
    delete d;
    /* 建立陣列並釋放 */
    char * e = new char[100];
    delete [] e;
    /* 建立物件並釋放 */
    Teacher * stu1 = new Teacher("王剛",22);
    cout << "姓名:" << stu1->name << ",年齡:" << stu1->age << endl;
    Teacher * stu2 = new Teacher();
    delete stu1;
    delete stu2;
    /* 利用malloc和free建立物件,無法呼叫其構造和解構函式*/
    Teacher * stu3 = (Teacher *)malloc(sizeof(Teacher));
    free(stu3);
}

二,靜態成員變數和靜態成員函式

1.static關鍵字

  static關鍵字用來宣告類中的成員為靜態屬性。當用static關鍵字修飾成員後,該類所建立的物件共享static成員。無論建立了多少個物件,該成員只有一份例項。靜態成員是與類相關的,是類的一種行為,而不是與該類的物件相關。

2.靜態成員的概念

  靜態成員是類所有的物件的共享成員,而不是某個物件的成員,它在物件中不佔用儲存空間,這個成員屬於整個類,而不屬於具體的一個物件,所以靜態成員變數無法在類的內部進行初始化,必須在類的外部進行初始化。比如定義一個學生類,那麼學生物件總數可以宣告為static,在構造方法中,對該變數進行加1,從而統計學生物件的數量。

3.靜態成員變數總結

靜態成員變數可以用static關鍵字定義,但是初始化必須在類的外面進行初始化。
靜態成員變數可以被類及類的物件所訪問和修改。
靜態成員變數遵循類的訪問控制原則,如果為private修飾,則只可以在類的內部和在類外面初始化的時候訪問,不會再被其他方式訪問。

4.靜態成員函式總結

靜態成員函式用static關鍵字定義,在靜態成員函式中可以訪問靜態成員變數和靜態成員函式,但不允許訪問普通的成員變數和成員函式,因為普通的成員屬於物件而不屬於類。層次不一樣。但是在普通成員中可以訪問靜態成員。
當靜態成員函式在類中定義,但是在類的外面實現的時候,不需要再加static關鍵字。
靜態成員函式沒有this指標。

5.靜態成員重點歸納

靜態成員是類和類的物件的所有者,因此靜態成員變數不能在類的內部進行初始化,必須在類的外部進行初始化。
靜態成員依舊遵循private,protected,public的訪問控制原則。
靜態成員函式中沒有this指標,不能訪問普通的成員變數和成員函式,可以訪問靜態成員變數和成員函式,但是可以通過傳遞物件的方式訪問普通成員。

6.靜態成員變數演示

# include<iostream>

using namespace std;

class MyStudent
{
private:
    static int count;/* 學生物件總數 */
    char name[64];
    int age;
public:
    static int n;
public:
    MyStudent(char * name,int age)
    {
        strcpy(this->name, name);
        this->age = age;
        MyStudent::count++;/* 學生數量加1 */
    }
    void getCount()/* 普通成員函式訪問靜態成員變數 */
    {
        cout << "學生總數:" << MyStudent::count << endl;
    }
};
/* 靜態成員變數初始化 */
int MyStudent::count = 0;
int MyStudent::n = 10;

int main()
{
    /* 測試靜態成員變數 */
    MyStudent student1("王剛",22);
    student1.getCount();
    /* 物件和類方式訪問靜態成員變數 */
    student1.n = 100;
    MyStudent::n = 200;
}
複製程式碼
7.靜態成員函式演示

複製程式碼
# include<iostream>

using namespace std;

class Test
{
private:
    int m;
public:
    static int n;
public:
    void setM(int m)
    {
        this->m = m;
        /* 訪問靜態成員函式 */
        test();
    }
public:
    static void xoxo();
    static void test()
    {
        n = 100;
        // m = 10; 不允許訪問普通成員變數
        // int c = getM(); 不允許訪問普通成員函式
        // this->m = 1000; this指標不存在
        cout << "static void test()函式..." << endl;
    }
};
/* 初始化靜態成員 */
int Test::n = 10;
/* 類中宣告,類外實現 */
void Test::xoxo()
{
    cout << "static void Test::xoxo" << endl;
}
int main()
{
    Test t;
    /* 普通成員函式訪問靜態成員函式 */
    t.setM(10);
    /* 成員函式的呼叫方式 */
    t.test();
    Test::test();
}

三,友元函式和友元類

1.友元函式

  當我們定義類的時候,使用private關鍵字修飾成員變數(成員函式),這樣做到了訪問控制。有些時候,我們需要讓一些函式來訪問物件的私有成員(屬性或方法),C++為我們提供了友元函式這個概念,所謂的友元函式就是指這個函式是這個類的好朋友,允許讓這個函式訪問這個類建立的物件的私有屬性和私有方法。友元函式用friend函式來宣告,友元函式的宣告必須在類的內部,友元函式的實現必須要在類的外部(如果友元函式的實現也在內部,那還要用友元函式幹什麼?),友元函式的宣告位置與訪問控制符無關。

2.友元函式示例

# include<iostream>
using namespace std;

/* 定義點類 */
class Point
{
private:
    int x;
    int y;
    /* 友元函式的定義:求兩點的距離 */
    friend int distance(Point &p1, Point &p2);
public:
    Point(int x, int y)
    {
        this->x = x;
        this->y = y;
    }
};
/* 友元函式的實現 */
int distance(Point &p1, Point &p2)
{
    int dx = p1.x - p2.x;
    int dy = p1.y - p2.y;
    return sqrt(dx*dx + dy*dy);
}

int main()
{
    Point p1(3, 4);
    Point p2(0, 0);
    int dis = distance(p1, p2);
    cout << "點(3,4)到原點的距離為:" << dis << endl;
}

3.友元類

若B類是A類的友元類,則B類的所有成員函式都是A類的友元函式。類B可以訪問類A的所有私有屬性和方法。
友元類通常被設計為一種對資料操作或者類之間傳遞訊息的輔助類。

4.友元類示例

# include<iostream>
using namespace std;

/* 定義類A */
class A
{
private:
    int x;
    friend class B;/* 定義類B為類A的友元類 */
private:
    void setX(int x)
    {
        this->x = x;
    }
};

/* 定義類B */
class B
{
private:
    A AObj;
public:
    /* 類B的所有成員函式都是類A的友元函式,因此都可以訪問類A的私有屬性和方法 */
    void operater(int tmp)
    {
        AObj.setX(tmp);
    }
    void display()
    {
        cout << "類A的私有屬性x = " << AObj.x << endl;
    }
};

int main()
{
    B b;
    b.operater(100);
    b.display();

    return 0;
}

四,C++中引用的基礎知識

1.引用的基本概念

  1.所謂的引用其實就是對變數起“別名”。引用和變數對應得是相同的記憶體,修改引用的值,變數的值也會改變,和指標類似。

  2.引用在定義的時候必須要初始化,初始化後就用引用的物件繫結在一起了。

  3.引用本身不是物件,不能定義引用的引用。

2.引用的意義

  1.引用作為其他變數的別名存在,因此在一些場合可以用來替代指標。

  2.引用相對於指標來說具有更好的可讀性和實用性。

3.引用的定義方式

  資料型別 &引用名稱 = 變數;

# include<iostream>
using namespace std;

int main()
{
    // 定義變數a
    int a = 10;
    // 定義引用b
    int &b = a;

    return 0;
}

二,引用的本質

1.引用的本質剖析

# include<iostream>
using namespace std;

int main()
{
    // 定義變數a
    int a = 10;
    // 定義引用b
    int &b = a;
    // 引用一旦初始化,就不再改變本身所繫結的物件,因此引用很想一個const修飾的常量
    cout << "&a = " << &a << endl;
    cout << "&b = " << &b << endl;
    // 通過列印地址,發現變數和引用的地址相同,因此引用本質是一個指標

    return 0;
}

2.引用的本質分析

  1.引用的初始化之後不允許被修改,因此引用是一個常量。

  2.引用的地址和變數本身的地址一樣。因此引用是一個指標。

  3.引用的本質是一個常量指標,只是C++編譯器幫助我們進行了自動取地址操作和解引用操作。

  4.上述的案例的本質:int &b = a; 相當於 int * const b = &a;

三,引用的重點

1.引用作函式引數

  引用作函式引數可以用來替代指標,在函式內部操作引用,就可以修改函式外部變數的值。

2.引用作函式返回值(此處涉及類的拷貝建構函式)

  引用作函式返回值,如果返回的是棧變數,則實際返回的是該變數的一份拷貝。

3.指標引用

  引用是指標型別的,例如:Teacher teacher = {“王剛”,21};Teacher * &t = &teacher;

4.常引用

  使用變數初始化引用,使引用具有隻讀屬性,保護了引用所指向的物件,防止被函式內部修改。

  使用字面量初始化引用,例如:const int &a = 10;是可以的,這裡會在編譯期間對字面量常量分配記憶體空間。

五,名稱空間(namespace)的基本概念以及由來

1.什麼是識別符號

  在C++中,識別符號可以是基本的變數,類,物件,結構體,函式,列舉,巨集等。

2.什麼是名稱空間

  所謂的名稱空間是指識別符號的可見範圍。C++標準庫中的所有的識別符號都被定義在一個名為std的名稱空間中。

3.C語言的名稱空間

  在C語言中只有一個全域性作用域,因此在C語言中所有的識別符號共享一個名稱空間,因此隨著程式碼量的增大,識別符號之間可能會重名。由此會造成一些命名問題。

4.針對C語言的問題,在C++中提出了名稱空間的概念

  1.名稱空間將全域性作用域劃分成不同的的部分。

  2.不同的名稱空間中的識別符號可以重名而不會發生衝突。

  3.全域性作用域也叫做預設名稱空間。

  4.名稱空間之間可以相互巢狀。

二,名稱空間(namespace)的定義以及使用

1.名稱空間的定義:

namespace 名稱空間名稱
{
……
}
2.名稱空間的使用:

using namespace name; // 使用name這個名稱空間
using namespace name::variable; // 使用name這個名稱空間下的variable
std::cout << “Hello,NameSpace” << std::endl; // 直接使用這個名稱空間中的識別符號
::tmp = 0; // 使用預設名稱空間的變數,預設情況下可以直接使用預設名稱空間的所有識別符號
3.域作用符::

  域作用符用兩個冒號(::)表示,用來訪問名稱空間下的識別符號。

三,名稱空間(namespace)實際案例

# include<iostream>

/* 定義名稱空間n1 */
namespace n1
{
    /* 定義學生類 */
    class Student
    {
    public:
        char name[64];
        int    age;
    };
    /* 定義函式 */
    void printN1()
    {
        std::cout << "我是n1名稱空間的printN1()" << std::endl;
    }
    /* 定義名稱空間n2 */
    namespace n2
    {
        /* 定義變數n */
        int n = 0;
    }
}

// 在預設名稱空間下定義變數a
int a = 100;

int main()
{
    // 使用名稱空間n1,曝光n1的所有識別符號,因此可以呼叫printN1()函式
    using namespace n1;
    printN1();
    // 使用名稱空間n2,曝光n2的所有識別符號,因此可以使用變數n
    using namespace n1::n2;
    // 直接使用指定名稱空間下的識別符號
    std::cout << n << std::endl;
    // 使用域作用符訪問預設名稱空間下的識別符號
    ::a = -100;
}