面向对象03(深拷贝、组合类、静态成员与对象高级操作)

深拷贝和浅拷贝

1. 浅拷贝
当类中没定义拷贝构造函数的时候,系统会默认提供一个拷贝构造函数 ,默认的拷贝构造函数
就是将参数对象的属性值赋值给新的对象。 只是进行简单的复制,这种赋值叫做浅复制,对于含有指针属性的类,浅复制通常不能满足需 要。
2. 深拷贝
对于带指针的属性,需要重新开辟内存空间来存储
示例:
#include 
using namespace std;
class Student
{
private:
    int m_nNumber;
    char* m_cName;
public:
    Student(int nNum, const char* cName);
    void show();
    void setNumber(int nNum);
    void setName(const char* cName);

    // 程序默认的拷贝构造函数,是浅拷贝
    //Student(const Student& s)
    //{
    //    m_nNumber = s.m_nNumber;
    //    m_cName = s.m_cName;
    //}
    
    // 总结:
    // 浅拷贝只能复制基本的数据类型,而且不会相互影响
    // 如果是带指针的属性,就需要深拷贝来完成,要自定义一个拷贝构造函数,给带指针的内容重新开辟空间
    
    // 声明自定义的拷贝构造函数来实现深拷贝
    Student(const Student& s);
};
Student::Student(int nNum, const char* cName)
{
    m_nNumber = nNum;
    // 申请动态内存来保存字符串
    m_cName = new char[strlen(cName) + 1];
    strcpy_s(m_cName, strlen(cName) + 1, cName);
}
Student::Student(const Student& s)
{
    m_nNumber = s.m_nNumber; // 普通变量直接复制即可
    // 实现拷贝对象的时候重新开辟空间来存储字符串,保证源对象和当前对象字符串的地址不同
    m_cName = new char[strlen(s.m_cName) + 1];  
    strcpy_s(m_cName, strlen(s.m_cName) + 1, s.m_cName);
}
void Student::setNumber(int nNum)
{
    m_nNumber = nNum;
}

void Student::setName(const char* cName)
{
    strcpy_s(m_cName, strlen(cName)+1, cName);
}
void Student::show()
{
    if (m_cName != NULL)
    {
        cout << "学号:" << m_nNumber << "\t";
        cout << "姓名:" << m_cName << endl;
    }
 }
int main()
{
    Student s1(202401, "gaiya");
    Student s2(s1); // 如果定义了拷贝构造函数就用定义的,如果没定义就用默认的
    s1.show();
    s2.show();

    cout << "-----------------------" << endl;
    s2.setNumber(202402);
    s2.setName("yousi");
    s1.show();
    s2.show();
    return 0;
}

类的组合

类的属性不仅可以是基本数据类型,也可以是类的对象, 类的组合就是在一个类中内嵌其他类对象作为成员, 当我们创建该对象的时候,内嵌对象也会被同时创建。

组合类的构造过程

如果内嵌对象的类有无参的构造函数,就在组合类构造函数的初始化表中可以不提供对该内嵌对象的初始化参数,编译器会自动调用无参构造函数来创建内嵌对象。如果内嵌对象的类有带参的构造函数,就在组合类构造函数的初始化表中必须提供对该内嵌对象的初始化参数,如果类中有多个内嵌对象,则组合类构造函数的执行顺序如下:

1.按内嵌对象的声明顺序依次调用内嵌对象的构造函数

2.执行组合类本身的构造函数

示例

#include 
using namespace std;

// 定义一个圆类
class Point
{
private:
    int x;
    int y;
public:
    Point(int x = 0, int y = 0); // 构造函数
    Point(const Point& p); // 拷贝构造
    int getX();
    int getY();
};

Point::Point(int x1, int y1)
{
    x = x1;
    y = y1;
    cout << x << "," << y << "正在构造" << endl;
}

Point::Point(const Point& p)
{
    x = p.x;
    y = p.y;
}

int Point::getX()
{
    return x;
}
int Point::getY()
{
    return y;
}

class Circle
{
private:
    const double PI; // 圆周率
    Point center; // 圆心   嵌套圆类即类的组合
    double radius; // 半径
public:
    // 构造函数
    Circle(const Point& p, double r);
    // 拷贝构造
    Circle(const Circle& c);
    void show(); // 展示圆心坐标和半径
};

// 常量成员只能使用列表初始化的方式来进行赋值
Circle::Circle(const Point& p, double r) :PI(3.1415), center(p)
{
    radius = r;
}
Circle::Circle(const Circle& c):PI(c.PI)
{
    radius = c.radius;
    center = c.center;
}

void Circle::show()
{
    cout << "圆心:" << center.getX() << "," << center.getY() << endl;
    cout << "半径:" << radius << endl;
}


class Triangle
{
    // 如果出现多个内嵌对象,构造的时候按照声明的顺序来完成构造,不论初始化的顺序是否相同,不影响构造顺序
private:
    Point point2;
    Point point1;
    Point point3;
public:
    Triangle(const Point& p1, const Point& p2, const Point& p3);
    Triangle(int a, int b, int c, int d, int e, int f);
    void show();
};
// 如果传入的是对象,在传入之前就已经构造完成了,和类中的对象声明顺序就没关系了
Triangle::Triangle(const Point& p1, const Point& p2, const Point& p3):point3(p1),point2(p2),point1(p3)
{
    cout << "Triangle正在构造" << endl;
}
// 如果传入的是值,在传入值之后才进行对象的创建,那么就按照对象的声明顺序来进行初始化
Triangle::Triangle(int a, int b, int c, int d, int e, int f):point3(e,f), point2(c,d), point1(a,b)
{
    cout << "Triangle正在构造" << endl;
}
void Triangle::show()
{
    cout << "第一个点:" << point1.getX() << "," << point1.getY() << endl;
    cout << "第二个点:" << point2.getX() << "," << point2.getY() << endl;
    cout << "第三个点:" << point3.getX() << "," << point3.getY() << endl;
}
int main()
{
    Triangle t(20, 30, 40, 50, 60, 70);
    t.show();

    //Point p1(20, 30);
    //Point p2(30, 40);
    //Point p3(50, 60);
    //Triangle t(p1, p2, p3);
    //t.show();
    return 0;
}

静态成员

声明格式:

类中声明:static 类型标识符 静态数据成员名
类外定义:类型标识符 类名::静态数据成员名 = 初始值;
使用类名访问静态属性:类名::静态数据成员名

静态成员函数:

特点:

  • 对于公有的静态成员函数,可以通过类名或者对象名来调用,而一般的非静态成员函数只能通过对象名来调用
  • 静态成员函数可以访问该类的静态数据成员和静态成员函数,不能访问非静态的数据成员和成员函数。
  • 静态成员函数可以由类名加作用域符来直接调用

代码实现

#include 
using namespace std;

// 封装一个类,成员数据有学生的编号、姓名、年龄、学生总计
// 编号要按照2024001 2024002 2024003,姓名是字符数组类型,年龄int类型
// 编号自动生成,创建对象的时候只提供姓名和年龄
// 使用getCount() 方法可以获取学生的总数
class Student
{
private:
    // 定义静态数据成员来计算对象的个数
    static int  count;
    int number; // 学号
    char name[20]; // 姓名
    int age; // 年龄
public:
    Student(const char* n, int a);
    int getNumber()
    {
        return number;
    }
    char* getName()
    {
        return name;
    }
    int getAge()
    {
        return age;
    }
    //int getCount()
    //{
    //    return count;
    //}
    // 将getCount修改为静态成员函数
    static int getCount()
    {
        // 静态成员函数不能访问非静态成员
        // cout << number;
        return count;
    }
};
// 在类外对静态数据成员初始化
int Student::count = 0;

Student::Student(const char* n, int a)
{
    age = a;
    strcpy_s(name, n);
    count++;
    number = 2024000 + count;
}
int main()
{
    // 学号要自动生成,创建对象的时候需要提供姓名和年龄
    cout << Student::getCount() << endl;
    Student s1("张三", 19);
    cout << Student::getCount() << endl;
    Student s2("李四", 20);
    cout << Student::getCount() << endl;
    Student s3("王五", 21);
    cout << Student::getCount() << endl;

    
    cout << s1.getNumber() << "\t" << s1.getName() << "\t" << s1.getAge() << endl;
    cout << s2.getNumber() << "\t" << s2.getName() << "\t" << s2.getAge() << endl;
    cout << s3.getNumber() << "\t" << s3.getName() << "\t" << s3.getAge() << endl;
    //cout << "学生的总数:" << s1.getCount() << "," << s2.getCount() << "," << s3.getCount() << endl;
    return 0;
}

常对象和常成员函数:

  • 常对象的定义方式:const 类名 对象名;
  • 常成员函数的定义方式:函数类型 函数名(形参列表) const;

使用常成员函数的注意点:

  • const是函数的一部分,在声明和定义的部分要加const关键字
  • 常成员函数中,不允许修改对象的属性值,也不能调用该类的普通函数
  • 常对象只能调用常成员函数,普通对象可以调用普通函数也可以调用常函数,如果出现了同名情况会优先调用普通成员函数。

代码实现:

#include 
using namespace std;
class Student
{
private:
    // 定义静态数据成员来计算对象的个数
    static int  count;
    int number; // 学号
    char name[20]; // 姓名
    int age; // 年龄
public:
    Student(const char* n, int a);
    int getNumber()
    {
        return number;
    }
    char* getName()
    {
        return name;
    }
    int getAge()
    {
        return age;
    }
    static int getCount()
    {
        return count;
    }

    // 普通成员不能被常对象访问
    //void output()
    //{
    //    cout << "执行成员函数" << endl;
    //    cout << "这个对象的属性有:" << number << "\t" << name << endl;
    //}

    // 常成员函数可以被常对象访问
    void output() const;
};
// 在类外对静态数据成员初始化
int Student::count = 0;
void Student::output() const {
    cout << "执行成员函数" << endl;
    cout << "这个对象的属性有:" << number << "\t" << name << endl;
}
Student::Student(const char* n, int a)
{
    age = a;
    strcpy_s(name, n);
    count++;
    number = 2024000 + count;
}
int main()
{
    // 定义了一个常对象
    const Student s1("星星队长", 99);
    // 常对象不能调用普通成员函数
    // s1.output();
    // 常对象可以调用静态成员函数
    cout << s1.getCount() << endl;
    s1.output();
    return 0;
}

对象数组和对象指针:

对象数组:
    定义:类名 数组名[常量表达式];
    访问成员:数组名[下标].成员名

对象指针:
    定义:类名 *对象指针名

代码实现:

#include 
using namespace std;
class Student
{
private:
    int number;
    char name[10];
    int age;
public:
    Student(int id, const char* s_name, int a)
    {
        number = id;
        strcpy_s(name, s_name);
        age = a;
    }

    int getAge()
    {
        return age;
    }
    void show()
    {
        cout << number << "\t" << name << "\t" << age << endl;
    }
};
int main()
{
     方式1:先创建对象,再添加到数组中
    //Student s1(10001, "张三", 20);
    //Student s2(10002, "张四", 21);
    //Student s3(10003, "张五", 22);
    //Student s[3] = { s1,s2,s3 };

     对象数组
    //Student s[3] = {
    //    Student(10001, "黑猫警长", 18),
    //    Student(10002, "霹雳火", 16),
    //    Student(10003, "白鸽警官", 18)
    //};

    // 对象指针
    //Student *p = new Student(10001, "黑猫警长", 18);
    //Student s1(10002, "霹雳火", 16);
    //p = &s1;

    // 定义一个Student的指针数组来存放Student对象的地址
    Student* p[3];
    p[0] = new Student(10001, "黑猫警长", 18);
    p[1] = new Student(10002, "霹雳火", 16);
    p[2] = new Student(10003, "白鸽警官", 18);
    double sum = 0;
    for (int i = 0; i < 3; i++)
    {
        // []和.的优先级比较高,*的优先级在第二级,所以会先执行[]和. , 要将(*p[i]) 当做整体来对待,解引用之后获取对象再用. 来调用成员
        // 建议直接用->来获取元素
        sum += (*p[i]).getAge();
        p[i]->show();
    }
    cout << "平均年龄:" << sum / 3 << endl;

    for (int i = 0; i < 3; i++)
    {
        delete p[i];
        p[i] = NULL;
    }
    return 0;
}

this指针

  • 类中两种类型的成员函数,普通成员函数和静态成员函数,前者比后者多了一个参数this,this是普通成员函数的第一个参数,该参数的类型为指向当前对象的const指针(指针常量)。
  • 每个普通成员函数都有一个this指针,谁调用这个函数,this就指向谁。静态成员没有this。
  • 当t1对象调用show()函数的时候,就是将t1对象地址传递给了this指针,show函数的函数原型是void show(ThisClass *this),但是这个形参是隐藏的,this是时时刻刻指向当前调用它的对象的。

代码实现:

#include 
using namespace std;
class ThisClass
{
    int x;
    int y;
public:
    ThisClass(int x = 0, int y = 0)
    {
        // this代表指向当前对象的x和y成员,后面的x和y才是形参
        this->x = x;
        this->y = y;
    }
    void show()
    {
        cout << x << "," << y << endl;
    }
};
int main()
{
    ThisClass t1;
    t1.show();
    return 0;
}

你可能感兴趣的:(面向对象编程,c++,面向对象编程进阶,深拷贝,组合类,面向对象高级操作,静态成员)