C++語言學習(四)——類與對象

C++語言學習(四)——類與對象

1、構造函數(constructor)

一、構造函數簡介

C++語言中,構造函數是與類名相同的特殊成員函數。
在類對象建立時,自動調用構造函數,完成類對象的初始化。類對象自己是變量,在棧、堆上建立的對象,對象的成員初始化爲隨機值;在靜態存儲區建立的對象,對象的成員初始化爲0。ios

二、構造函數的定義

構造函數聲明的語法以下:
classname(parameters);
沒有參數的構造函數稱爲無參構造函數。當類中沒有定義構造函數(包括拷貝構造函數)時,編譯器默認提供一個無參構造函數,而且其函數體爲空。若是類中已經定義了構造函數(包括拷貝構造函數),編譯器不會再提供默認的無參構造函數。數組

#include <iostream>

using namespace std;

class Person
{
public:
    const char* name;
    int age;
    void print()
    {
        printf("My name is %s, I'm is %d years old.\n",name,age);
    }
};

int main(int argc, char *argv[])
{
    Person p;//編譯器提供默認的無參構造函數
    p.name = "Bauer";
    p.age = 30;
    Person p1 = p;//編譯器提供默認的拷貝構造函數
    p1.print();
    return 0;
}

上述代碼中,編譯器提供了默認的無參構造函數和拷貝構造函數。安全

#include <iostream>

using namespace std;

class Person
{
public:
    const char* name;
    int age;
    Person(const Person& another)
    {
        name = another.name;
        age = another.age;
    }
    void print()
    {
        printf("My name is %s, I'm is %d years old.\n",name,age);
    }
};

int main(int argc, char *argv[])
{
    //error: no matching function for call to 'Person::Person()'
    Person p;//編譯器不在提供默認的無參構造函數
    p.name = "Bauer";
    p.age = 30;
    Person p1 = p;
    p1.print();
    return 0;
}

上述代碼中,類中定義了一個拷貝構造函數,編譯器不會再提供默認的無參構造函數,建立Person對象時找不到構造函數調用,編譯器報錯。ide

三、構造函數的規則

構造函數的規則以下:
A、在對象建立時自動調用,完成初始化相關工做。
B、無返回值,與類名相同,默認無參,能夠重載,可以使用默認參數。
C、一旦顯示實現構造函數,C++編譯器再也不爲類實現默認構造函數。
構造函數的調用根據重載函數的匹配規則進行調用。函數

四、構造函數的參數初始化

classname::classname():member1(v1),member2(v2),...
{
    //code 
}

構造函數的初始化列表中成員的初始化順序與類中成員的聲明順序相同,與成員在初始化列表中的位置無關,初始化列表先於構造函數的函數體執行。學習

#include <iostream>

using namespace std;

class Value
{
private:
    int i;
public:
    Value(int value)
    {
        i = value;
        cout << i << endl;
    }
};

class Test
{
private:
    Value value2;
    Value value3;
    Value value1;
public:
    Test():value1(1),value2(2),value3(3)
    {}
};

int main(int argc, char *argv[])
{
    Test test;
    //2
    //3
    //1
    return 0;
}

類中定義的const變量的初始化必須在初始化列表中進行,本質是隻讀變量。
編譯器沒法直接獲得類的const成員的初始值,所以const成員沒法進行符號表。測試

#include <iostream>

using namespace std;

class Test
{
private:
    const int i;
public:
    Test():i(10)
    {}
    int getI()const
    {
        return i;
    }
    void setI(const int value)
    {
        int* p = const_cast<int*>(&i);
        *p = value;
    }
};

int main(int argc, char *argv[])
{
    Test test;
    cout << test.getI() << endl;//10
    test.setI(100);
    cout << test.getI() << endl;//100
    return 0;
}

五、類對象的構造順序

類對象根據存儲區域分爲三種,其構造順序以下:
A、局部對象的構造順序
當程序執行流到達對象的定義語句時進行構造,可是goto語句可能會跳過類對象的構造。
B、堆對象的構造順序
當程序執行流到達new語句時建立對象,使用new建立對象將自動觸發構造函數的調用。
C、全局類對象的構造順序
全局類對象的構造順序是不肯定的,不一樣的編譯器使用不一樣的規則肯定構造順序。全局類對象的構造是不肯定的,所以儘可能不使用有依賴關係的全局對象。優化

六、臨時對象

直接調用構造函數將會產生一個臨時對象,臨時對象的生命週期只有一條語句的時間,臨時對象的做用域只在一條語句中。
現代C++編譯器在不影響最終結果的前提下,會盡力減小臨時對象的產生。實際工程開發中應盡力減小臨時對象。
A a = A(10);
上述代碼實際沒有產生臨時對象,也沒有調用拷貝構造函數,C++編譯器實際優化爲A a=10;this

#include <iostream>

using namespace std;

class Test
{
private:
    int i;
public:
    Test(int i)
    {
        cout << "Test(int i): " << i <<endl;
    }
    Test(const Test& another)
    {
        i = another.i;
        cout << "Test(const Test& another)" << i<< endl;
    }
    Test create(int i)
    {
        return Test(i);
    }
};

Test create(int i)
{
    return Test(i);
}

int main(int argc, char *argv[])
{
    Test test1 = Test(10);//輸出:Test(int i): 10
    //等價於Test test1 = 10;
    Test test2 = create(100);//輸出:Test(int i): 100
    //等價於Test test1 = 100;
    return 0;
}

七、構造函數的調用順序

單個對象建立時,構造函數的調用順序以下:
A、調用父類的構造過程
B、調用成員變量的構造函數(調用順序與聲明順序相同)
C、調用類自身的構造函數
析構函數的調用順序與構造函數相反。spa

#include <iostream>

using namespace std;

class Member
{
    const char* ms;
public:
    Member(const char* s)
    {
        printf("Member(const char* s): %s\n", s);

        ms = s;
    }
    ~Member()
    {
        printf("~Member(): %s\n", ms);
    }
};

class Test
{
    Member mA;
    Member mB;
public:
    Test() : mB("mB"), mA("mA")
    {
        printf("Test()\n");
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};

Member gA("gA");

int main(int argc, char *argv[])
{
    Test test;
    return 0;
}

對象定義時會申請對象空間並調用構造函數。
對象聲明告訴編譯器存在一個對象。

八、構造函數拋出異常

若是構造函數中拋出異常,構造過程當即中止,當前對象沒法生成,析構函數不會被調用,對象佔用的空間被當即收回。當構造函數中可能拋出異常時使用二階構造模式。

#include <iostream>

using namespace std;

class Test
{
public:
    Test()
    {
        cout << "Test()" << endl;
        throw 0;
    }
};

int main(int argc, char *argv[])
{
    Test* p = reinterpret_cast<Test*>(0x1);
    try
    {
        p = new Test();
    }
    catch(...)
    {
        cout << "Exception..." << endl;
    }
    cout << "p = " << p << endl;//p = 0x1
    return 0;
}

2、拷貝構造函數(copy constructor)

一、拷貝構造函數簡介

拷貝構造函數根據己存在的對象建立新對象,新對象不禁構造函數來構造,而是由拷貝構造函數來完成。

二、拷貝構造函數的定義

拷貝構造函數的聲明以下:
classname(const classname& xxx);
拷貝構造函數是參數爲const classname&的構造函數。當類中沒有定義拷貝構造函數時,編譯器會提供一個默認拷貝構造函數,其函數體內會進行成員變量的淺拷貝。

#include <iostream>

using namespace std;

class Person
{
public:
    const char* name;
    int age;
    Person()
    {
    }
    void print()
    {
        printf("My name is %s, I'm is %d years old.\n",name,age);
    }
};

int main(int argc, char *argv[])
{
    Person p;
    p.name = "Bauer";
    p.age = 30;
    Person p1 = p;//編譯器提供默認的拷貝構造函數
    p1.print();
    return 0;
}

上述代碼中,編譯器會爲類提供一個默認的拷貝構造函數。

#include <iostream>

using namespace std;

class Person
{
public:
    const char* name;
    int age;
    void print()
    {
        printf("My name is %s, I'm is %d years old.\n",name,age);
    }
};

int main(int argc, char *argv[])
{
    Person p;//編譯器提供默認的無參構造函數
    p.name = "Bauer";
    p.age = 30;
    Person p1 = p;//編譯器提供默認的拷貝構造函數
    p1.print();
    return 0;
}

上述代碼中,編譯器會爲類提供一個默認的無參構造函數和默認的拷貝構造函數。

三、拷貝構造函數的規則

拷貝構造函數的規則以下:
A、編譯器能夠提供默認的拷貝構造函數。一旦類中定義了拷貝構造函數,編譯器不會爲類再提供默認的拷貝構造函數。
B、編譯器提供的拷貝構造函數是等位拷貝,即淺拷貝。
C、要實現深拷貝,必需要自定義。
淺拷貝後對象的物理狀態相同,深拷貝後對象的邏輯狀態相同。
編譯器能夠爲類提供默認的拷貝構造函數,一旦類中定義了拷貝構造函數,編譯器將不會再爲類提供默認拷貝構造函數和默認無參構造函數,所以開發者必須在類中自定義拷貝構造函數的同時自定義構造函數。編譯器提供的默認拷貝構造器是等位拷貝,即淺拷貝,若是類中包含的數據元素所有在棧上,淺拷貝也能夠知足需求的;若是類中有堆上的數據,則會發生屢次析構行爲。
淺拷貝只是對指針的拷貝,拷貝後兩個指針指向同一個內存空間,深拷貝不只對指針進行拷貝,並且對指針指向的內容進行拷貝,經深拷貝後的指針是指向兩個不一樣地址的指針。
深拷貝構造函數的示例以下:

#include <iostream>
#include <string.h>

using namespace std;

class IntArray
{
public:
    IntArray(int len)
    {
        m_pointer = new int[len];
        for(int i=0; i<len; i++)
        {
            m_pointer[i] = 0;
        }
        m_length = len;
    }
    //深拷貝
    IntArray(const IntArray& obj)
    {
        m_length = obj.m_length;
        m_pointer = new int[obj.m_length];
        for(int i=0; i<obj.m_length; i++)
        {
            m_pointer[i] = obj.m_pointer[i];
        }
    }
    int length()
    {
        return m_length;
    }
    bool get(int index, int& value)
    {
        bool ret = (0 <= index) && (index < length());
        if( ret )
        {
            value = m_pointer[index];
        }
        return ret;
    }

    bool set(int index, int value)
    {
        bool ret = (0 <= index) && (index < length());
        if( ret )
        {
            m_pointer[index] = value;
        }
        return ret;
    }
    ~IntArray()
    {
        delete [] m_pointer;
    }
private:
    int m_length;
    int* m_pointer;
};

int main(int argc, char *argv[])
{
    IntArray array1(10);
    for(int i = 0; i < 10; i++)
    {
        array1.set(i,i*i);
    }
    IntArray array2 = array1;
    for(int i = 0; i < 10; i++)
    {
        int value;
        array2.get(i,value);
        cout << value << endl;
    }
    return 0;
}

類中未定義拷貝構造函數時,編譯器會提供淺拷貝的默認拷貝構造函數:

#include <iostream>
#include <string.h>

using namespace std;

class IntArray
{
public:
    IntArray(int len)
    {
        m_pointer = new int[len];
        for(int i=0; i<len; i++)
        {
            m_pointer[i] = 0;
        }
        m_length = len;
        cout << "Constructor" << endl;
    }

    int length()
    {
        return m_length;
    }
    bool get(int index, int& value)
    {
        bool ret = (0 <= index) && (index < length());
        if( ret )
        {
            value = m_pointer[index];
        }
        return ret;
    }

    bool set(int index, int value)
    {
        bool ret = (0 <= index) && (index < length());
        if( ret )
        {
            m_pointer[index] = value;
        }
        return ret;
    }

    ~IntArray()
    {
        cout << "DeConstructor" << endl;
        cout << m_pointer << endl;
        delete [] m_pointer;
        m_pointer = NULL;
    }
private:
    int m_length;
    int* m_pointer;
};

int main(int argc, char *argv[])
{
    IntArray array1(10);
    for(int i = 0; i < 10; i++)
    {
        array1.set(i,i*i);
    }
    IntArray array2(array1);
    for(int i = 0; i < 10; i++)
    {
        int value;
        array2.get(i,value);
        cout << value << endl;
    }

    return 0;
}

上述代碼中,編譯器提供的默認構造函數爲淺拷貝,只會進行值拷貝。在對象銷燬時調用析構函數,會形成釋放同一內存空間2次,致使程序異常(經測試,Linux G++編譯器編譯後程序異常出錯,Windows下QtCreator+MinGW編譯器沒有異常出錯)。
須要深拷貝的場景通常是對象中有成員指向系統中的資源。

四、拷貝構造函數的調用

拷貝構造函數的調用場合以下:
A、建立對象的副本,如A a=b;A c(a)。
B、函數中以對象做爲參數或返回值。

3、析構函數(destructor)

一、析構函數簡介

析構函數是C++語言中類的特殊的清理函數,在類對象銷燬時,自動調用,完成對象的銷燬。析構函數的做用,並非刪除對象,而在對象銷燬前完成的一些清理工做,如釋放申請的系統資源。

二、析構函數的定義

析構函數的定義語法以下:

~classname()
{

}

三、析構函數的規則

析構函數的規則以下:
A、對象銷燬時,自動調用。完成銷燬的善後工做。
B、無返值 ,與類名同。無參,不能夠重載,不能設置默認參數

四、析構函數的調用

析構函數的調用場景以下:
A、棧對象離開其做用域。
B、堆對象被手動delete。

五、析構函數的使用

當類中自定義了構造函數,而且構造函數中使用了系統資源如堆空間申請、文件打開等,須要自定義析構函數,在析構函數體內釋放使用的系統資源。

六、析構函數拋出異常

析構函數中拋出異常將致使對象使用的資源沒法釋放。

4、this指針

this指針是編譯器在建立對象時,默認生成的指向當前對象的指針。
this指針做用以下:
A、避免構造器的參數與成員名相同。
B、基於this指針的自身引用還被普遍地應用於那些支持多重串聯調用的函數中。好比連續賦值。
C、this指針是const類型的指針。

5、賦值操做符重載

一、賦值操做符重載簡介

C++編譯器默認爲每一個類重載了賦值操做符,默認的賦值操做符是淺拷貝的,所以當須要進行深拷貝時必須顯示重載賦值操做符。用一個己有對象,給另一個已有對象賦值會調用賦值操做符重載函數。

二、賦值操做符重載函數定義

賦值操做符重載函數的聲明以下:
classname& operator=(const classname& another);
賦值操做符重載函數的實現以下:

classname& operator=(const classname& another)
    {   
    if(this != &another)
{
//資源分配操做和賦值拷貝
}
        return *this;
    }

三、賦值操做符重載的規則

賦值操做符重載的規則以下:
A、C++編譯器提供默認的賦值運算符重載。
B、C++編譯器提供的賦值操做符重載函數也是等位拷貝,即淺拷貝。
C、若是須要要深拷貝,必須自定義賦值操做符。
D、自定義面臨的問題有三個:自賦值、內存泄漏、重析構。
E、賦值操做符函數須要返回引用,且不能用const修飾,其目的是實現連續賦值。

四、賦值操做符重載示例

#include <iostream>

using namespace std;

class IntArray
{
public:
    IntArray(int num)
    {
        m_length = num;
        m_array = new int[num];
        for(int i = 0; i < num; i++)
        {
            m_array[i] = 0;
        }
    }
    ~IntArray()
    {
        if(m_array != NULL)
        {
            delete [] m_array;
            m_array = NULL;
        }
    }
    //拷貝構造函數(深拷貝)
    IntArray(const IntArray& another)
    {
        cout << "copy constructor." << endl;
        if(this != &another)
        {
            m_length = another.length();
            m_array = new int[another.length()];
            for(int i = 0; i < another.length(); i++)
            {
                m_array[i] = another.getValue(i);
            }
        }
    }
    //賦值操做符重載函數(深拷貝)
    IntArray& operator = (const IntArray& another)
    {
        cout << "assign function." << endl;
        if(this != &another)
        {
            delete m_array;
            m_length = another.length();
            m_array = new int[another.length()];
            for(int i = 0; i < another.length(); i++)
            {
                m_array[i] = another.getValue(i);
            }
        }
        return *this;
    }
    int length()const
    {
        return m_length;
    }
    int getValue(int index)const
    {
        return m_array[index];
    }
    void setValue(int index, int value)
    {
        m_array[index] = value;
    }
private:
    int* m_array;
    int m_length;
};

int main(int argc, char *argv[])
{
    IntArray array1(10);
    for(int i = 0; i < array1.length(); i++)
    {
        array1.setValue(i, i*i);
    }
    IntArray array2(5);
    array2 = array1;//賦值操做符
    for(int i = 0; i < array2.length(); i++)
    {
        cout << array2.getValue(i) <<endl;
    }
    IntArray array3 = array1;//拷貝構造函數
    for(int i = 0; i < array3.length(); i++)
    {
        cout << array3.getValue(i) <<endl;
    }
    return 0;
}

6、函數與類對象

一、函數傳值調用

類對象做爲函數參數傳值調用函數時,會調用類的拷貝構造函數。

#include <iostream>

using namespace std;

class Test
{
public:
    Test(int a = 0, int b = 0)
    {
        cout << "call constructor." << endl;
        this->a = a;
        this->b = b;
    }
    Test(const Test& another)
    {
        cout << "copy constructor." << endl;
        if(this != &another)
        {
            a = another.a;
            b = another.b;
        }
    }
    void print()
    {
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
    }
private:
    int a;
    int b;
};

void print(const Test obj)
{
    const_cast<Test&>(obj).print();
}

int main(int argc, char *argv[])
{
    Test test;//call constructor.
    print(test);//copy constructor.

    return 0;
}

二、函數傳引用調用

類對象做爲函數參數傳引用調用函數時,不會調用類的拷貝構造函數。

#include <iostream>

using namespace std;

class Test
{
public:
    Test(int a = 0, int b = 0)
    {
        cout << "call constructor." << endl;
        this->a = a;
        this->b = b;
    }
    Test(const Test& another)
    {
        cout << "copy constructor." << endl;
        if(this != &another)
        {
            a = another.a;
            b = another.b;
        }
    }
    void print()
    {
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
    }
private:
    int a;
    int b;
};

void print(const Test& obj)
{
    const_cast<Test&>(obj).print();
}

int main(int argc, char *argv[])
{
    Test test;//call constructor.
    print(test);

    return 0;
}

三、函數返回對象

#include <iostream>

using namespace std;

class Test
{
public:
    Test(int a = 0, int b = 0)
    {
        cout << "call constructor." << endl;
        this->a = a;
        this->b = b;
    }
    Test(const Test& another)
    {
        cout << "copy constructor." << endl;
        if(this != &another)
        {
            a = another.a;
            b = another.b;
        }
    }
    void print()
    {
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
    }
private:
    int a;
    int b;
};

void print(const Test& obj)
{
    const_cast<Test&>(obj).print();
}

Test func(const Test& obj)
{
    return const_cast<Test&>(obj);
}

int main(int argc, char *argv[])
{
    Test test(1,2);//call constructor.
    func(test);//copy constructor.

    return 0;
}

上述代碼中,在調用func函數的棧上創建臨時空間,將test拷貝到臨時空間中,調用了拷貝構造函數。

四、函數返回引用

返回引用多用於產生串聯應用。好比連等式。棧對象是不能夠返回引用的,除非函數的調用者返回自身對象。當函數返回引用類型時,沒有複製返回值,返回的是對象自己。當函數執行完畢時,將釋放分配給局部對象的存儲空間,對局部對象的引用就會指向不肯定的內存。返回指向局部對象的指針也是同樣的,當函數結束時,局部對象被釋放,返回的指針就變成了再也不存在的對象的懸垂指針。返回引用時,要求在函數的參數中包含有以引用方式或指針方式存在而且須要被返回的參數,即返回的引用不能是函數內部的局部棧對象。

#include <iostream>

using namespace std;

class Test
{
public:
    Test(int a = 0, int b = 0)
    {
        cout << "call constructor." << endl;
        this->a = a;
        this->b = b;
    }
    Test(const Test& another)
    {
        cout << "copy constructor." << endl;
        if(this != &another)
        {
            a = another.a;
            b = another.b;
        }
    }

    Test& operator =(const Test& another)
    {
        if(this != &another)
        {
            a = another.a;
            b = another.b;
        }
        return *this;
    }
    void print()
    {
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
    }
private:
    int a;
    int b;
    friend Test& func(int a, int b, Test& obj);
};

Test& func(int a, int b, Test& obj)
{
    obj.a = a;
    obj.b = b;
    return obj;
}

int main(int argc, char *argv[])
{
    Test test(1,2);//call constructor.
    Test test1;//call constructor.
    test1 = test;
    test1.print();
    return 0;
}

上述代碼中,類的賦值操做符重載函數和func全局函數返回了引用。

7、棧和堆上的對象和對象數組

一、用new 和delete生成銷燬堆對象

使用new建立一個堆對象,會自動調用構造函數,delete銷燬一個堆對象對自動調用析構函數。

二、棧對象數組

若是生成的數組,未初始化,則必調用無參構造函數。或手動調用帶參構造函數。

三、堆對象數組

若是生成的數組,未初始化,則必調用無參構造函數。或手動調用帶參構造函數。
構造函數不管是重載仍是默認參數,必定要把系統默認的無參構造函數包含進來。否則生成數組的時候,可能會有些麻煩。

8、類成員函數的存儲方式

一、類成員的組成

類成員包括成員變量和成員函數,每一個對象所佔用的存儲空間只是該對象的非靜態成員變量所佔用的存儲空間,而不包括函數代碼、靜態成員變量所佔用的存儲空間,成員函數存儲於代碼段,靜態成員變量存儲於全局數據區。

#include <iostream>

using namespace std;

class Test
{
public:
    Test(int a = 0, int b = 0, int c = 0)
    {
        this->a = a;
        this->b = b;
        this->c = c;
    }
    void print()
    {
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
        cout << "c = " << c << endl;
    }
private:
    int a;
    int b;
    static int c;
};

int Test::c = 0;

int main(int argc, char *argv[])
{
    Test test(1,2,3);
    test.print();
    cout << "test has " << sizeof(test) << " bytes." << endl;//8

    return 0;
}

上述代碼中,一個Test對象佔用空間由int類型的成員變量a和b決定,靜態成員變量c所佔用空間不在Test對象中。

二、調用原理

全部對象都調用共用的函數代碼段,不一樣的對象調用函數時,this指針指向調用函數的對象,所以函數中對於類的數據成員的調用分別調用了對象的數據成員。
不論成員函數在類內定義仍是在類外定義,成員函數的代碼段都用同一種方式存儲。

9、const修飾類對象

一、const修飾成員變量

const修飾類的成員變量,表示只讀變量,會被分配空間,不能被修改,只能在構造函數的初始化列表中初始化。const成員變量能夠被const和非const成員函數調用,但不能被修改。

二、const修飾成員函數

const成員函數的定義:
Type classname::functioname(Type p)const;
const成員函數的聲明和實現必需要使用const關鍵字限制。
const成員函數內部只能調用const成員函數,不能修改爲員變量的值。
const修飾函數構成重載時使用規則:
A、若是const函數構成函數重載,const對象只能調用const函數,非const對象優先調用非const函數。
B、const函數只能調用const函數。非const函數能夠調用const函數。
C、類體外定義的const成員函數,在定義和聲明處都須要const修飾符。

三、const修飾類對象

const對象是隻讀對象,只能調用const成員函數。可訪問const或非const的成員變量,但不能修改。const只讀對象是編譯時概念,運行時無效。

#include <iostream>

using namespace std;

class Test
{
public:
    int data;
public:
    Test(int i)
    {
        data = i;
        cout << "Test(int i): " << i << endl;
    }
    int getData()const
    {
        return data;
    }
    int getValue()
    {
        return data;
    }
    void setData(const int value)
    {
        data = value;
    }
    ~Test()
    {
       cout << "~Test()" << endl;
    }
};

int main(int argc, char *argv[])
{
    const Test ct(10);
    ct.getData();
    //ct.getValue();//error
    //error: passing 'const Test' as 'this' argument of 'void Test::getValue()' discards qualifiers
    int* p = const_cast<int*>(&ct.data);
    *p = 100;
    cout << ct.getData() << endl;//100
    return 0;
}

對象由成員變量和成員函數構成,成員變量能夠位於棧、堆和全局數據區,成員函數只能位於代碼段。每個對象擁有本身的成員變量,全部的對象共享類的成員函數,成員函數能直接訪問對象的成員變量。成員函數中隱藏this用於指代當前對象。

10、類與static修飾符

一、static修飾成員變量

在C++類中,能夠定義靜態成員變量。使用static關鍵字對類的成員變量進行修飾時,能夠獲得類的靜態成員變量。
類的靜態成員變量的聲明以下:
static Type varname; //在類的內部
類的靜態成員變量的初始化以下:
Type classname::varname = initvalue; //在類的外部
類的靜態成員變量的使用方法以下:
A、類名::靜態數據成員
B、類對象.靜態數據成員
靜態成員變量的特性以下:
A、定義時使用static關鍵字修飾
B、靜態成員變量在類外單獨分配空間,類對象的大小不包括靜態成員變量
C、靜態成員變量在程序內部位於全局數據區
靜態成員變量屬於整個類,其生命週期不依賴於任何對象,能夠經過類名直接訪問類的公有靜態成員變量,類的全部對象共享類的靜態成員變量,能夠經過對象名訪問類的靜態成員變量。類的靜態成員變量只存儲一份供全部對象共用,在全部對象中均可以共享它。使用靜態成員變量實現多個對象之間的數據共享不會破壞隱藏(相比全局變量的優勢)的原則,保證了安全性還能夠節省內存。
類的靜態成員,屬於類,也屬於對象,但終歸屬於類。

#include <iostream>

using namespace std;

class Test
{
private:
    static int data;
public:
    Test()
    {
        data++;
    }
    int count()const
    {
        return data;
    }
    ~Test()
    {
       data--;
    }
};

//在全局數據區分配靜態成員變量空間並初始化
int Test::data = 0;

int main(int argc, char *argv[])
{
    Test test1;
    Test test2;
    cout << test1.count() << endl;
    return 0;
}

二、static修飾成員函數

爲了管理靜態成員,C++提供了靜態函數,提供對外接口。靜態成員函數只能訪問靜態成員變量。
靜態成員函數的聲明以下:
static Type functionname(parameters);
靜態成員函數的特性以下:
A、靜態成員函數的意義,不在於信息共享、數據溝通,而在於管理靜態數據成員,完成對靜態數據成員的封裝。
B、靜態成員函數只能訪問靜態數據成員。緣由:非靜態成員函數,在調用時 this指針時被看成參數傳進。而靜態成員函數屬於類,而不屬於對象,沒有this指針。
C++語言學習(四)——類與對象
靜態成員函數的使用以下:
A、類名::函數調用
B、類對象.函數調用

#include <iostream>

using namespace std;

class Test
{
private:
    static int data;
public:
    Test()
    {
        data++;
    }
    //靜態成員函數
    static int count()
    {
        return data;
    }
    ~Test()
    {
       data--;
    }
};

//在全局數據區分配靜態成員變量空間並初始化
int Test::data = 0;

int main(int argc, char *argv[])
{
    cout << Test::count() << endl;//0
    Test test;
    cout << test.count() << endl;//1
    return 0;
}

三、static const修飾符

若是一個類的成員變量,既要實現共享,又要實現不可改變,可使用static const修飾。static const修飾成員變量時,既能夠在類內部進行初始化,也能夠在類外進行初始化。
static const修飾成員函數,是靜態成員函數。

#include <iostream>

using namespace std;

class Test
{
public:
    static const int data;
//static const int data = 0;
public:
    Test()
    {
    }
    //靜態成員函數
    static const int count()
    {
        return data;
    }
    ~Test()
    {
    }
};
const int Test::data = 100;

int main(int argc, char *argv[])
{

    cout << Test::count() << endl;//100
    Test test;

    cout << test.count() << endl;//100
    cout << Test::data << endl;//100
    cout << test.data << endl;//100
    return 0;
}

11、指向類成員的指針

在C++語言中,能夠定義一個指針,使其指向類成員或成員函數,而後經過指針來訪問類的成員。包括指向成員變量的指針和指向成員函數的指針。

一、指向類成員變量的指針

指向類的成員變量的指針定義以下:
<br/>&lt;數據類型&gt;&lt;類名&gt;::*&lt;指針名&gt;<br/>
指向類的成員變量的指針的賦值與初始化以下:
<br/>&lt;數據類型&gt;&lt;類名&gt;::*&lt;指針名&gt;[=&&lt;類名&gt;::&lt;非靜態數據成員&gt;]<br/>
指向非靜態數據成員的指針在定義時必須和類相關聯,在使用時必須和具體的對象關聯。
指向類的成員變量的指針的解引用以下:

<類對象名>.*<指向非靜態數據成員的指針>
<類對象指針>->*<指向非靜態數據成員的指針>

因爲類不是運行時存在的對象。所以,在使用這類指針時,須要首先指定類的一個對象,而後,經過對象來引用指針所指向的成員。

#include <iostream>

using namespace std;

class Student
{
public:
    Student(string n, int nu):name(n),num(nu){}
    string name;
    int num;
};

int main(int argc, char *argv[])
{
    Student s("zhangsan",1002);
    Student s2("lisi",1001);
    string Student::*ps = &Student::name;
    int Student::*pi = &Student::num;
    cout<<s.*ps<<endl;
    cout<<s.*pi<<endl;
    cout<<s2.*ps<<endl;
    cout<<s2.*pi<<endl;
    Student *pp = new Student("wangwu",1003);
    cout<<pp->*ps<<endl;
    cout<<pp->*pi<<endl;
    return 0;
}

二、指向類成員函數的指針

定義一個指向非靜態成員函數的指針必須在三個方面與其指向的成員函數保持一致:參數列表要相同、返回類型要相同、所屬的類型要相同。
A、定義

<數據類型>(<類名>::*<指針名>)(<參數列表>)

B、賦值與初始化

<數據類型>(<類名>::*<指針名>)(<參數列表>)[=&<類名>::<非靜態成員函數>]

C、解引用

(<類對象名>.*<指向非靜態成員函數的指針>)(<參數列表>)
(<類對象指針>->*<指向非靜態成員函數的指針>)(<參數列表>)

因爲類不是運行時存在的對象。所以,在使用這類指針時,須要首先指定類的一個對象,而後,經過對象來引用指針所指向的成員。
指向非靜態成員函數時, 必須用類名做限定符, 使用時則必須用類的實例做限定符。指向靜態成員函數時,則不須要使用類名做限定符。

#include <iostream>

using namespace std;

class Student
{
public:
    Student(string n, int nu)
    {
        name = n;
        num = nu;
    }
    void print()
    {
        cout << "name :" << name <<endl;
        cout << "num: " << num << endl;
    }
private:
    string name;
    int num;
};

int main(int argc, char *argv[])
{
    Student s1("zhangsan",1002);
    Student s2("lisi",1001);
    Student *s3 = new Student("wangwu",1003);
    void (Student::*pfunc)() = &Student::print;
    (s1.*pfunc)();
    (s2.*pfunc)();
    (s3->*pfunc)();
    return 0;
}

三、指向類靜態成員的指針

A、指向類靜態數據成員的指針
指向靜態數據成員的指針的定義和使用與普通指針相同,在定義時無須和類相關聯,在使用時也無須和具體的對象相關聯。
B、指向類靜態成員函數的指針
指向靜態成員函數的指針和普通指針相同,在定義時無須和類相關聯,在使用時也無須和具體的對象相關聯。

#include <iostream>

using namespace std;

class Test
{
public:
    Test()
    {
        count++;
    }
    static void print()
    {
        cout << "count: " << count << endl;
    }
    ~Test()
    {
        count--;
    }
    static int count;
};

int Test::count = 0;

int main(int argc, char *argv[])
{
    int* pc = &Test::count;
    void (*pfunc)() = &Test::print;
    cout << *pc << endl;
    pfunc();
    Test test1;
    Test test2;
    cout << *pc << endl;
    pfunc();
    return 0;
}

12、類成員函數的重載

一、函數重載的特色

重載函數的特色以下:
A、重載函數的本質爲多個不一樣的函數
B、重載函數的函數名和參數列表是惟一標識符
C、函數重載必須發生在同一個做用域中

二、類成員函數重載的規則

類成員函數重載的規則以下:
A、類的成員函數只能與類內的成員函數構成重載
B、類的成員函數不能與全局函數或其它類的成員函數構成重載

#include <iostream>

using namespace std;

class Test
{
private:
    int i;
    int j;
public:
    Test(int i = 0,int j = 0)
    {
        this->i = i;
        this->j = j;
    }
    //成員函數重載
    int sum()
    {
        return i + j;
    }
    static int sum(int i, int j)
    {
        return i + j;
    }
    static double sum(double i, double j)
    {
        return i + j;
    }
    ~Test()
    {
    }
};
//全局函數的重載
int sum(int a, int b)
{
    return a + b;
}
double sum(double a, double b)
{
    return a + b;
}

int main(int argc, char *argv[])
{
    cout << "member function overload." << endl;
    cout << Test::sum(2,10) << endl;
    cout << Test::sum(2.14,10.0) << endl;
    Test test1;
    cout << test1.sum() << endl;
    Test test2(100,200);
    cout << test2.sum() << endl;
    cout << test2.sum(1,9) << endl;
    cout << test2.sum(2.1,3.14) << endl;
    cout << "global function overload." << endl;
    cout << sum(10, 12) <<endl;
    cout << sum(3.14, 3.14) <<endl;

    return 0;
}

三、函數重載的意義

函數重載的意義以下:A、經過函數名對函數功能進行提示B、經過參數列表對函數用法進行提示C、對已經存在的函數進行功能擴展

相關文章
相關標籤/搜索