C++语言学习(四)——类与对象

文章来源:https://blog.51cto.com/9291927/2143145

一、构造函数(constructor)

1、构造函数简介
C++语言中,构造函数是与类名相同的特殊成员函数。
在类对象创建时,自动调用构造函数,完成类对象的初始化。类对象本身是变量,在栈、堆上创建的对象,对象的成员初始化为随机值;在静态存储区创建的对象,对象的成员初始化为0。

2、构造函数的定义
构造函数声明的语法如下:
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对象时找不到构造函数调用,编译器报错。

3、构造函数的规则
构造函数的规则如下:
A、在对象创建时自动调用,完成初始化相关工作。
B、无返回值,与类名相同,默认无参,可以重载,可使用默认参数。
C、一旦显示实现构造函数,C++编译器不再为类实现默认构造函数。
构造函数的调用根据重载函数的匹配规则进行调用。

4、构造函数的参数初始化
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;
}

5、类对象的构造顺序
类对象根据存储区域分为三种,其构造顺序如下:
A、局部对象的构造顺序
当程序执行流到达对象的定义语句时进行构造,但是goto语句可能会跳过类对象的构造。
B、堆对象的构造顺序
当程序执行流到达new语句时创建对象,使用new创建对象将自动触发构造函数的调用。
C、全局类对象的构造顺序
全局类对象的构造顺序是不确定的,不同的编译器使用不同的规则确定构造顺序。全局类对象的构造是不确定的,因此尽量不使用有依赖关系的全局对象。

6、临时对象
直接调用构造函数将会产生一个临时对象,临时对象的生命周期只有一条语句的时间,临时对象的作用域只在一条语句中。
现代C++编译器在不影响最终结果的前提下,会尽力减少临时对象的产生。实际工程开发中应尽力减少临时对象。
A a = A(10);
上述代码实际没有产生临时对象,也没有调用拷贝构造函数,C++编译器实际优化为A a=10;

#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;
}

7、构造函数的调用顺序
单个对象创建时,构造函数的调用顺序如下:
A、调用父类的构造过程
B、调用成员变量的构造函数(调用顺序与声明顺序相同)
C、调用类自身的构造函数
析构函数的调用顺序与构造函数相反。

#include

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;
}
对象定义时会申请对象空间并调用构造函数。
对象声明告诉编译器存在一个对象。

8、构造函数抛出异常
如果构造函数中抛出异常,构造过程立即停止,当前对象无法生成,析构函数不会被调用,对象占用的空间被立即收回。当构造函数中可能抛出异常时使用二阶构造模式。

#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;
}

二、拷贝构造函数(copy constructor)

1、拷贝构造函数简介
拷贝构造函数根据己存在的对象创建新对象,新对象不由构造函数来构造,而是由拷贝构造函数来完成。

2、拷贝构造函数的定义
拷贝构造函数的声明如下:
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;
}

上述代码中,编译器会为类提供一个默认的无参构造函数和默认的拷贝构造函数。

3、拷贝构造函数的规则
拷贝构造函数的规则如下:
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编译器没有异常出错)。
需要深拷贝的场景一般是对象中有成员指向系统中的资源。

4、拷贝构造函数的调用
拷贝构造函数的调用场合如下:
A、创建对象的副本,如A a=b;A c(a)。
B、函数中以对象作为参数或返回值。

三、析构函数(destructor)

1、析构函数简介
析构函数是C++语言中类的特殊的清理函数,在类对象销毁时,自动调用,完成对象的销毁。析构函数的作用,并不是删除对象,而在对象销毁前完成的一些清理工作,如释放申请的系统资源。

2、析构函数的定义
析构函数的定义语法如下:

~classname()
{

}
3、析构函数的规则
析构函数的规则如下:
A、对象销毁时,自动调用。完成销毁的善后工作。
B、无返值 ,与类名同。无参,不可以重载,不能设置默认参数

4、析构函数的调用
析构函数的调用场景如下:
A、栈对象离开其作用域。
B、堆对象被手动delete。

5、析构函数的使用
当类中自定义了构造函数,并且构造函数中使用了系统资源如堆空间申请、文件打开等,需要自定义析构函数,在析构函数体内释放使用的系统资源。

6、析构函数抛出异常
析构函数中抛出异常将导致对象使用的资源无法释放。

四、this指针

this指针是编译器在创建对象时,默认生成的指向当前对象的指针。
this指针作用如下:
A、避免构造器的参数与成员名相同。
B、基于this指针的自身引用还被广泛地应用于那些支持多重串联调用的函数中。比如连续赋值。
C、this指针是const类型的指针。

五、赋值操作符重载

1、赋值操作符重载简介
C++编译器默认为每个类重载了赋值操作符,默认的赋值操作符是浅拷贝的,因此当需要进行深拷贝时必须显示重载赋值操作符。用一个己有对象,给另外一个已有对象赋值会调用赋值操作符重载函数。

2、赋值操作符重载函数定义
赋值操作符重载函数的声明如下:
classname& operator=(const classname& another);
赋值操作符重载函数的实现如下:

classname& operator=(const classname& another)
{
if(this != &another)
{
//资源分配操作和赋值拷贝
}
return *this;
}
3、赋值操作符重载的规则
赋值操作符重载的规则如下:
A、C++编译器提供默认的赋值运算符重载。
B、C++编译器提供的赋值操作符重载函数也是等位拷贝,即浅拷贝。
C、如果需要要深拷贝,必须自定义赋值操作符。
D、自定义面临的问题有三个:自赋值、内存泄漏、重析构。
E、赋值操作符函数需要返回引用,且不能用const修饰,其目的是实现连续赋值。

4、赋值操作符重载示例

#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;
}

六、函数与类对象

1、函数传值调用
类对象作为函数参数传值调用函数时,会调用类的拷贝构造函数。

#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;
}

2、函数传引用调用
类对象作为函数参数传引用调用函数时,不会调用类的拷贝构造函数。

#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;
}
3、函数返回对象
#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拷贝到临时空间中,调用了拷贝构造函数。

4、函数返回引用
返回引用多用于产生串联应用。比如连等式。栈对象是不可以返回引用的,除非函数的调用者返回自身对象。当函数返回引用类型时,没有复制返回值,返回的是对象本身。当函数执行完毕时,将释放分配给局部对象的存储空间,对局部对象的引用就会指向不确定的内存。返回指向局部对象的指针也是一样的,当函数结束时,局部对象被释放,返回的指针就变成了不再存在的对象的悬垂指针。返回引用时,要求在函数的参数中包含有以引用方式或指针方式存在并且需要被返回的参数,即返回的引用不能是函数内部的局部栈对象。

#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全局函数返回了引用。

七、栈和堆上的对象和对象数组

1、用new 和delete生成销毁堆对象
使用new创建一个堆对象,会自动调用构造函数,delete销毁一个堆对象对自动调用析构函数。

2、栈对象数组
如果生成的数组,未初始化,则必调用无参构造函数。或手动调用带参构造函数。

3、堆对象数组
如果生成的数组,未初始化,则必调用无参构造函数。或手动调用带参构造函数。
构造函数无论是重载还是默认参数,一定要把系统默认的无参构造函数包含进来。不然生成数组的时候,可能会有些麻烦。

八、类成员函数的存储方式

1、类成员的组成
类成员包括成员变量和成员函数,每个对象所占用的存储空间只是该对象的非静态成员变量所占用的存储空间,而不包括函数代码、静态成员变量所占用的存储空间,成员函数存储于代码段,静态成员变量存储于全局数据区。

#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对象中。

2、调用原理
所有对象都调用共用的函数代码段,不同的对象调用函数时,this指针指向调用函数的对象,因此函数中对于类的数据成员的调用分别调用了对象的数据成员。
不论成员函数在类内定义还是在类外定义,成员函数的代码段都用同一种方式存储。

九、const修饰类对象
1、const修饰成员变量
const修饰类的成员变量,表示只读变量,会被分配空间,不能被修改,只能在构造函数的初始化列表中初始化。const成员变量可以被const和非const成员函数调用,但不能被修改。

2、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修饰符。

3、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用于指代当前对象。

十、类与static修饰符
1、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;
}

2、static修饰成员函数
为了管理静态成员,C++提供了静态函数,提供对外接口。静态成员函数只能访问静态成员变量。
静态成员函数的声明如下:
static Type functionname(parameters);
静态成员函数的特性如下:
A、静态成员函数的意义,不在于信息共享、数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。
B、静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用时 this指针时被当作参数传进。而静态成员函数属于类,而不属于对象,没有this指针。
C++语言学习(四)——类与对象
静态成员函数的使用如下:
A、类名::函数调用
B、类对象.函数调用


```cpp
#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;
}

3、static const修饰符
如果一个类的成员变量,既要实现共享,又要实现不可改变,可以使用static const修饰。static const修饰成员变量时,既可以在类内部进行初始化,也可以在类外进行初始化。
static const修饰成员函数,是静态成员函数。

```cpp
#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;
}

十一、指向类成员的指针
在C++语言中,可以定义一个指针,使其指向类成员或成员函数,然后通过指针来访问类的成员。包括指向成员变量的指针和指向成员函数的指针。

1、指向类成员变量的指针
指向类的成员变量的指针定义如下:

<数据类型><类名>::<指针名>

指向类的成员变量的指针的赋值与初始化如下:

<数据类型><类名>::
<指针名>[=&<类名>::<非静态数据成员>]

指向非静态数据成员的指针在定义时必须和类相关联,在使用时必须和具体的对象关联。
指向类的成员变量的指针的解引用如下:

<类对象名>.<指向非静态数据成员的指针>
<类对象指针>->
<指向非静态数据成员的指针>
由于类不是运行时存在的对象。因此,在使用这类指针时,需要首先指定类的一个对象,然后,通过对象来引用指针所指向的成员。

#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;
}

2、指向类成员函数的指针
定义一个指向非静态成员函数的指针必须在三个方面与其指向的成员函数保持一致:参数列表要相同、返回类型要相同、所属的类型要相同。
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;
}

3、指向类静态成员的指针
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;
}

十二、类成员函数的重载
1、函数重载的特点
重载函数的特点如下:
A、重载函数的本质为多个不同的函数
B、重载函数的函数名和参数列表是唯一标识符
C、函数重载必须发生在同一个作用域中

2、类成员函数重载的规则
类成员函数重载的规则如下:
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;
}

3、函数重载的意义
函数重载的意义如下:
A、通过函数名对函数功能进行提示
B、通过参数列表对函数用法进行提示
C、对已经存在的函数进行功能扩展

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页