16. 面向对象编程

一、什么是面向对象

  对象(Object)是内存中专门用来存储数据的一块区域。对象中可以存放各种数据,比如:数字、布尔值、代码等。对象由 对象的标识(id)、对象的类型(type)和 对象的值(value)三部分组成。

  C++ 支持面向对象编程。所谓的 面向对象 的语言,简单理解就是语言中所有操作都是通过对象来进行的。面向对象的编程语言,关注的是对象,而不关注过程。它将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。如果要使用某个功能,直接找到对应的对象即可。对于面向对象的语言来说,一切都是对象。面向对象 为单位,每种事物都具备自己的 属性方法/功能。面向对象这种方式编写的代码,可读性比较高,并且易于维护,可复用性比较高。但是这种方式,不太符合常规的思维,编写起来稍微麻烦一点。

  与之对应的就是 面向过程 的编程语言,面向过程的编程语言指将我们的程序的逻辑分解为一个一个的步骤,通过对每个步骤的抽象,来完成程序。但是这种编写的代码往往只适用于一个功能。如果要实现别的功能,即使功能相差极小,也往往需要重新编写代码,所以它的可复用性比较低,并且难以维护。面向过程 强调的是 功能行为,以 函数 为最小单位,考虑怎么做;这种编程方式,符合我们人类思维,编写起来相对简单。

  面向过程其实是最为实际的一种思考方式,就算是面向对象的方法也是含有面向过程的思想。可以说面向过程是一种基础的方法。它考虑的是实际地执行。一般的面向过程是从上往下步步求精。面向对象主要是把事物给对象化,对象包括属性与行为。当程序规模不是很大的时候,面向过程的方法还会体现出一种优势。因为程序的流程很清楚,按着模块与函数的方法可以很好的组织。但对于复杂而庞大的系统来说,面向过程显着就很无力了。

二、类与对象

  (Class)和 对象(Object)是面向对象的核心概念。 是对一类事物的描述,是 抽象的、概念上的定义,简单理解就相当于一个图纸;对象 是实际存在的该类事物的每个个体,是 具体的,因此也称为 实例(instance);在程序中我们根据类来创建对象。我们也称对象是类的实例。如果多个对象是通过一个类创建的,我们称这些对象是一类对象。

  面向对象程序设计的重点是 类的设计,类的设计,其实就是 类的成员的设计

  1. 创建类,设计类的内部成员(属性、方法)
  2. 创建类的对象
  3. 通过对象,调用其内部声明的属性或方法,完成相关的功能
#include <iostream>

using namespace std;

class Person
{
public:             // 公共权限
    // 属性
    string name = "Unknown";;
    int age = 0;

    // 行为
    void setName(string n)
    {
        name = n;
    }

    void setAge(int a)
    {
        if (a < 0)
        {
            cout << "年龄不合法" << endl;
            return;
        }
  
        age = a;
    }

    void showInfo(void)
    {
        cout << "{name: " << name << ", age: " << age << "}\n";
    }
};

int main(void)
{
    // 通过类创建一个具体的对象
    Person p1;
    Person p2;

    // 给对象的属性进行赋值
    p1.name = "Sakura";
    p1.age = 10;

    p2.setName("Mikoto");
    p2.setAge(14);

    // 调用对象中的方法
    p1.showInfo();
    p2.showInfo();
}

三、类的成员

3.1、成员变量和成员函数

  成员变量 用来描述具体某个对象的特征。描述的是对象的状态信息,通常以变量的形式进行定义。在类中我们定义的变量,将会成为所有实例的成员变量,所有实例都可以通过 对象.成员变量 的方式访问这些变量。

  成员函数 是类或对象行为特征的抽象,用来完成某个功能的操作。在其它编程语言中,函数 也被称为 方法过程。在类中定义的函数,将会成为所有实例的公共函数,所有该类实例都可以通过 对象.成员函数名() 的形式调用方法。在实际开发中,我们可以将重复的代码、具有独立功能的代码抽取到方法中。使用方法后,我们可以提高代码的复用性和可维护性。

  一般,我们在头文件中声明类的成员,这里我们新建一个 person.h 头文件用来保存类的声明。

#pragma once
#include <iostream>

using namespace std;

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 方法的声明
    void setName(string n);
    void setAge(int a);
    void showInfo(void);
};

  新建一个 person.cpp 文件用来实现 person.h 头文件中声明的方法。

#include "person.h"

// 类中方法的实现,其中Person::指定为Person类
void Person::setName(string n)
{
    name = n;
}

void Person::setAge(int a)
{
    if (a < 0)
    {
        cout << "年龄不合法" << endl;
        return;
    }

    age = a;
}

void Person::showInfo(void)
{
    cout << "{name: " << name << ", age: " << age << "}\n";
}

  在包含 main() 函数的文件中包含刚才定义的头文件,然后使用。

#include <iostream>
#include "person.h"

using namespace std;

int main(void)
{
    // 通过类创建一个具体的对象
    Person p1;
    Person p2;

    // 给对象的属性进行赋值
    p1.name = "Sakura";
    p1.age = 10;

    p2.setName("Mikoto");
    p2.setAge(14);

    // 调用对象中的方法
    p1.showInfo();
    p2.showInfo();

    return 0;
}

  如果使用 MinGW 或 GCC 运行程序出错,报未找到类中方法的定义的错误:

E:/Software/CPP/test.cpp:16: undefined reference to `Person::setName(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
E:/Software/CPP/test.cpp:17: undefined reference to `Person::setAge(int)'
E:/Software/CPP/test.cpp:20: undefined reference to `Person::showInfo()'
E:/Software/CPP/test.cpp:21: undefined reference to `Person::showInfo()'

  请将 #include "person.h" 包含头文件路径替换为 #include "person.cpp"

3.2、构造函数和析构函数

  对象的初始化和清理是两个非常重要的安全问题。一个对象或者变量没有初始化,对其使用后果是未知的。同样的,使用完一个对象或变量,没有及时清零,也会造成一定的安全问题。

  C++ 中利用 构造函数析构函数 解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情。

  • 构造函数:主要作用在于 创建对象 时为对象成员属性赋值,构造函数由编译器自动调用,无需手动调用。
  • 析构函数:主要作用在于 对象销毁前 系统自动调用,执行一些清理工作。

  默认情况下,C++ 编译器至少会给一个类添加 3 个函数:

  • 默认构造函数(无参,函数体为空)。
  • 默认析构函数(无参,函数体为空)。
  • 默认拷贝构造函数,对属性进行值拷贝。

  构造函数的写法如下:

类名(参数列表)
{
    // 方法体
}

  构造函数没有返回值,也不用在方法名前写 void。构造函数的名称与类名一致。构造函数可以没有参数,也可以有参数,因此,构造函数可以发生重载。程序会在创建对象时自动调用构造函数,无需手动调用,而且只会调用一次。

  我们可以使用委托构造函数来简化代码。

类名(参数列表) : 构造函数名(参数列表)
{
    // 方法体
}

  我们还可以使用初始化列表对类中的属性赋初值。

类名(参数列表) : 属性(方法)
{
    // 方法体
}

 构造函数调用的规则如下:

  • 如果用户自定义有参构造函数,C++ 不再提供默认无参构造,但是会提供默认拷贝构造函数
  • 如果用户自定义拷贝构造函数,C++ 不再提供其它构造函数

  析构函数的写法如下:

~类名(void)
{
    // 方法体
}

  析构函数没有返回值,也不用在方法名前写 void。析构函数的名称与类名一致,但需要在方法名前加上符号:~。析构函数不可以有参数,因此,不可以发生重载。程序会在销毁对象前自动调用析构函数,无需手动调用,而且只会调用一次。

  我们修改 person.h 头文件有关 Person 类定义的内容:

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 无参构造函数
    Person(void);

    // 有参构造函数
    Person(string n);
    Person(string n, int a);

    // 析构函数
    ~Person(void);

    // 方法的声明
    void setName(string n);
    void setAge(int a);
    void showInfo(void);
};

  我们在 person.cpp 源文件中添加如下内容:

// 类中方法的实现,其中Person::指定为Person类
// 无参构造函数
Person::Person(void)
{
    cout << "Person()构造函数" << endl;
}

// 有参构造函数
Person::Person(string n) : name(n)
{
    cout << "Person(string n)构造函数" << endl;
}

// 有参构造函数,使用初始化列表进行初始化
Person::Person(string n, int a) : Person(n)
{
    if (age < 0)
    {
        cout << "年龄不合法" << endl;
        return;
    }
    age = a;
    cout << "Person(string n, int a)构造函数" << endl;
}

// 析构函数
Person::~Person(void)
{
    cout << "~Person()析构函数" << endl;
}

  修改包含 main() 函数的文件中的内容。

#include <iostream>
#include "person.cpp"

using namespace std;

int main(void)
{
    // 创建对象时,编译器自动调用Person()无参构造函数
    // 调用默认无参构造时,不要加(),因为编译器会认为是无参构造函数的声明
    Person p1;
    // 给对象的属性进行赋值
    p1.name = "Sakura";
    p1.age = 10;

    // 创建对象时,编译器自动调用Person(string, int)有参构造函数
    Person p2("Mikoto", 14);

    // 显示调用Person(string)有参构造函数
    Person p3 = Person("Shana");
    p3.setAge(15);

    // C++11中可以使用列表初始化类对象
    Person p4 = {"Kikyō", 18};

    // 调用编译器默认提供的拷贝构造函数
    Person p5 = Person(p1);

    // 调用对象中的方法
    p1.showInfo();
    p2.showInfo();
    p3.showInfo();
    p4.showInfo();
    p5.showInfo();

    return 0;
}

调用默认无参构造时,不要加 (),因为编译器会认为是无参构造函数的声明。

当初始化列表包含多个项目时,这些项目的初始化顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。

删除对象是可以释放对象本身占用的内存,但并不能释放属于对象成员指向的内存。因此,我们需要在析构函数中使用 delete 语句确保对象过期时,由构造函数使用 new 分配的内存被释放。

在构造函数中使用 new 来分配内存时,必须在相应的析构函数中使用 delete 来释放内存。如果使用 new[] 来分配内存,则应使用 delete[] 来释放内存。

3.3、this指针

  在 C++ 中,成员变量和成员函数是分开存储的。每个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。C++ 中通过提供特殊的对象指针,this 指针来区分哪个对象调用成员函数的问题。this 指针指向被调用的成员函数所属的对象。this 指针不需要程序员手动定义,直接使用即可。

  this 指针的主要用途如下:

  • 当形参与成员变量同名时,可以使用 this 指针来区分。
  • 在类的非静态的成员函数中返回对象本身,可使用 return *this

  我们修改 person.h 头文件的内容:

#pragma once
#include <iostream>

using namespace std;

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 构建函数
    Person(void);
    Person(string name);
    Person(string name, int age);

    // 析构函数
    ~Person(void);
  
    // 方法的声明
    void setName(string name);
    void setAge(int a);
    void showInfo(void);
    Person & addAge(int age);
};

  我们修改 person.c 源文件的内容:

#include "person.h"

// 无参构造函数
Person::Person(void)
{
    cout << "Person()构造函数" << endl;
}

// 有参构造函数
Person::Person(string name)
{
    // this指针指向被调用的成员函数所属的对象
    this->name = name;
    cout << "Person(string name)构造函数" << endl;
}

// 有参构造函数,使用初始化列表进行初始化
Person::Person(string name, int age) : name(name), age(age)
{
    cout << "Person(string name, int age)构造函数" << endl;
}

// 析构函数
Person::~Person(void)
{
    cout << "~Person()析构函数" << endl;
}

// 类中方法的实现,其中Person::指定为Person类
void Person::setName(string name)
{
    this->name = name;
}

void Person::setAge(int age)
{
    if (age < 0)
    {
        cout << "年龄不合法" << endl;
        return;
    }

    this->age = age;
}

Person & Person::addAge(int age)
{
    if (age < 0)
    {
        cout << "年龄不合法" << endl;
        return *this;
    }
    this->age += age;

    // 返回对象本身
    return *this;
}

void Person::showInfo(void)
{
    cout << "{name: " << name << ", age: " << age << "}\n";
}

  修改包含 main() 函数的文件中的内容。

#include <iostream>
#include "person.cpp"

using namespace std;

int main(void)
{
    // 创建对象时,编译器自动调用Person()无参构造函数
    // 调用默认无参构造时,不要加(),因为编译器会认为是无参构造函数的声明
    Person p1;
    // 给对象的属性进行赋值
    p1.name = "Sakura";
    p1.age = 10;

    // 创建对象时,编译器自动调用Person(string, int)有参构造函数
    Person p2("Mikoto", 14);

    // 显示调用Person(string)有参构造函数
    Person p3 = Person("Shana");
    p3.setAge(15);

    // 调用编译器默认提供的拷贝构造函数
    Person p4 = Person(p1);
    p4.addAge(1).addAge(1);

    // 调用对象中的方法
    p1.showInfo();
    p2.showInfo();
    p3.showInfo();
    p4.showInfo();

    return 0;
}

3.4、静态成员

  静态成员就是在成员变量和成员函数前加上关键字 static。静态成员分为:静态成员变量静态成员函数静态成员变量 的所有属性共享同一份数据,它在编译阶段分配内存。静态成员变量在类内声明,在类外进行初始化。静态成员函数 的所有对象共享同一个函数。静态成员函数只能访问静态成员变量

#include <iostream>

using namespace std;

class A
{
public:
    // 静态成员属性
    static int count;

    static void setCount(int c)
    {
        count = c;
    }

    static void getCount(void)
    {
        cout << "count: " << count << endl;

    }
};

// 初始化静态成员属性
int A::count = 0;

int main(void)
{
    A a1;
    A a2;

    cout << "a1.count: " << a1.count << endl;
    cout << "a2.count: " << a2.count << endl;

    a1.count = 100;
    cout << "a1.count: " << a1.count << endl;
    cout << "a2.count: " << a2.count << endl;

    a2.setCount(200);
    a1.getCount();
    a2.getCount();

    return 0;
}

  静态成员不属于某个对象,所有对象共享同一份数据,因此静态成员变量有两种访问方式:通过对象进行访问通过类名进行访问

#include <iostream>

using namespace std;

class A
{
public:
    // 静态成员属性
    static int count;

    static void setCount(int c)
    {
        count = c;
    }

    static void getCount(void)
    {
        cout << "count: " << count << endl;

    }
};

// 初始化静态成员属性
int A::count = 0;

int main(void)
{
    A a;

    A::count = 100;
    cout << "A::count = " << A::count << endl;
    cout << "a.count = " << a.count << endl;

    A::setCount(200);
    A::getCount();
    a.getCount();

    return 0;
}

3.5、对象成员

  C++ 类中的成员可以是另一个类的对象,我们成该成员为对象成员。当其它类对象作为本类成员,构造时候先构造其它类对象,在构造自身。析构函数的顺序与构造函数的顺序相反。

#include <iostream>

using namespace std;

class A
{
public:
    A(void)
    {
        cout << "A()构造函数" << endl;
    }

    ~A(void)
    {
        cout << "~A()析构函数" << endl;
    }
};

class B
{
public:
    A a;

    B(void)
    {
        cout << "B()构造函数" << endl;
    }

    B(A a_a) : a(a_a)
    {
        cout << "B(A)构造函数" << endl;
    }

    ~B(void)
    {
        cout << "~B()析构函数" << endl;
    }
};

int main(void)
{
    B b;

    return 0;
}

3.6、常对象和常函数

  成员函数后加 const 后,我们称这个函数为 常函数。常函数内不可以修改成员属性,但如果成员属性加关键字 mutable 后,在常函数中依然可以修改。如果我们在对象前加 const,那么这个对象为 常对象。常对象只能调用常函数。

#include <iostream>

using namespace std;

class A
{
public:
    int a;
    mutable int count;

    // this的本质是指针常量,指针指向的值是不可以修改的,Person * const this;
    // 在成员函数后加const,修饰的是this指向,让指针指向的值也不可以修改,const Person * const this;
    void setCount(int c) const
    {   // 相当于 this->a = c;
        // 常函数不能修改普通的成员变量
        // a = c;
        count = c;
    }

    int getCount(void)
    {
        return count;
    }
};

int main(void)
{
    A a1;

    // 在对象前加const变成常对象
    const A a2 = a1;
    // 常对象不能修改普通的成员变量
    // a2.a = 10;
    a2.count = 10;

    // 常对象只能调用常函数
    // a2.getCount();
    a2.setCount(20);

    return 0;
}

四、运算符重载

  运算符重载 就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。运算符重载的语法如下:

返回值类型 operator运算符(参数列表)
{
    // 函数体
}

  我们修改 person.h 头文件的内容:

#pragma once
#include <iostream>

using namespace std;

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 构建函数
    Person(void);
    Person(string name);
    Person(string name, int age);
  
    // 运算符重载
    bool operator>(const Person & other);
};

bool operator<(const Person & p1, const Person & p2);

  我们修改 person.c 源文件的内容:

#include "person.h"

// 无参构造函数
Person::Person(void){}

// 有参构造函数
Person::Person(string name) : name(name){}

// 有参构造函数,使用初始化列表进行初始化
Person::Person(string name, int age) : name(name), age(age){}

// 成员函数重载运算符:>
bool Person::operator>(const Person & other)
{
    return this->age > other.age;
}

// 全局函数重载运算符:<
bool operator<(const Person & p1, const Person & p2)
{
    return p1.age < p2.age;
}

  修改包含 main() 函数的文件中的内容。

#include <iostream>
#include "person.cpp"

using namespace std;

int main(void)
{
    // 创建对象时,编译器自动调用Person()无参构造函数
    // 调用默认无参构造时,不要加(),因为编译器会认为是无参构造函数的声明
    Person p1;
    // 给对象的属性进行赋值
    p1.name = "Sakura";
    p1.age = 10;

    // 创建对象时,编译器自动调用Person(string, int)有参构造函数
    Person p2("Mikoto", 14);

    // 成员函数运算符重载本质:
    // cout << (p1.operator>(p2)) << endl;
    cout << (p1 > p2) << endl;
  
    // 全局函数运算符重载本质:
    // cout << operator<(p1, p2) << endl;
    cout << (p1 < p2) << endl;

    return 0;
}

使用成员函数重载运算符时,运算符左侧的对象是调用对象,运算符右侧的对象是作为参数传递的对象。

使用全局函数重载运算符时,运算符左侧的对象是第一个参数对象,运算符右侧的对象是第二个参数对象。

如果要为类重载运算符,并将非类的项作为第一个操作数,则使用全局函数重载运算符。

对于内置的数据类型的表达式的运算符是不可能进行重载的。

  这里,我们将常用的运算符进行重载。

  我们修改 person.h 头文件的内容:

#pragma once
#include <iostream>

using namespace std;

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 构建函数
    Person(void);
    Person(string name);
    Person(string name, int age);
  
    // 运算符重载
    bool operator>(const Person & other);
    Person & operator++(void);
    Person operator++(int);
    bool operator==(const Person & other);
    bool operator!=(const Person & other);
    void operator()(void);
};

bool operator<(const Person & p1, const Person & p2);

ostream & operator<<(ostream & cout, const Person & person);

  我们修改 person.c 源文件的内容:

#include "person.h"

// 无参构造函数
Person::Person(void){}

// 有参构造函数
Person::Person(string name) : name(name){}

// 有参构造函数,使用初始化列表进行初始化
Person::Person(string name, int age) : name(name), age(age){}

// 成员函数重载运算符:>
bool Person::operator>(const Person & other)
{
    return this->age > other.age;
}

// 全局函数重载运算符:<
bool operator<(const Person & p1, const Person & p2)
{
    return p1.age < p2.age;
}

// 前置递增运算符重载,实现年龄自增
// 返回引用是为了一直对一个数据进行递增操作
Person & Person::operator++(void)
{
    // 先做++运算
    this->age++;
    // 再将自身返回
    return *this;
}

// 后置递增运算符重载,实现年龄自增
// int代表占位参数,可以用于区分前置和后置递增
Person Person::operator++(int)
{
    // 先记录当前结果
    Person temp = *this;
    // 再进行++运算
    this->age++;
    // 把之前记录的结果返回
    return temp;
}

// ==运算符重载,实现两个对象比较
bool Person::operator==(const Person & other)
{
    return (this->name == other.name && this->age == other.age);
}

// !=运算符重载,实现两个对象比较
bool Person::operator!=(const Person & other)
{
    return !(this->name == other.name && this->age == other.age);
}

// 函数调用运算符重载
void  Person::operator()(void)
{
    cout << "{name: " << this->name << ", age: " << this->age << "}\n";
}

// 左移运算符重载,输出自定义数据
// 不会利用成员运算符重载<<运算符,因此无法实现cout在左侧
// 因此,只能用全局函数重载<<运算符
ostream & operator<<(ostream & cout, const Person & person)
{
    cout << "{name: " << person.name << ", age: " << person.age << "}";
    return cout;
}

  修改包含 main() 函数的文件中的内容。

#include <iostream>
#include "person.cpp"

using namespace std;

int main(void)
{
    Person p1("Sakura", 10);
    Person p2("Mikoto", 14);

    cout << "p1==p2: " << (p1==p2) << endl;
    cout << "p1!=p2: " << (p1!=p2) << endl;

    p1();
    p2();

    // 前置递增
    cout << (++p1) << endl;
    cout << p1 << endl;

    // 后置递增
    cout << (p1++) << endl;
    cout << p1 << endl;
}

运算符重载也可以发生函数重载。

五、类的自动类型转换

  在 C++ 中接受一个参数的构造函数为将类型与参数相同的值转换为类提供了蓝图。一般而言,我们只把接受一个参数的构造函数作为转为转换函数。但如果接受多个参数,其他参数都提供了默认值,也可以作为转换函数。如果我们想要关闭这种特性,可以使用 C++ 提供的 explicit 关键字。

  修改包含 main() 函数的文件中的内容。

#include <iostream>
#include "person.cpp"
#include "cstring"

using namespace std;

int main(void)
{
    string name = "Sakura";
    Person person = name;

    cout << person << endl;
}

六、转换函数

  构造函数只用于从某种类型到类类型的转换。要进行相反的转换,必须使用 C++ 运算符函数 —— 转换函数。转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。转换函数的格式如下:

operator 类型名(void);

  要使用转换函数,要注意以下几点:

  • 转换函数必须是类方法。
  • 转换函数不能指定返回类型。
  • 转换函数不能有参数。

  类型名已经指出了要转换的类型,因此不需要指定返回类型。转换函数是类方法意味着:它必须通过类对象来调用,从而告知函数要转换的值,因此,函数不需要参数。

  我们修改 person.h 头文件的内容:

#pragma once
#include <iostream>

using namespace std;

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 构建函数
    Person(void);
    Person(string name);
    Person(string name, int age);
  
    // 运算符重载
    bool operator>(const Person & other);
    Person & operator++(void);
    Person operator++(int);
    bool operator==(const Person & other);
    bool operator!=(const Person & other);
    void operator()(void);

    // 转换函数
    operator string(void);
};

bool operator<(const Person & p1, const Person & p2);

ostream & operator<<(ostream & cout, const Person & person);

  我们修改 person.c 源文件的内容:

#include "person.h"

// 无参构造函数
Person::Person(void){}

// 有参构造函数
Person::Person(string name) : name(name){}

// 有参构造函数,使用初始化列表进行初始化
Person::Person(string name, int age) : name(name), age(age){}

// 成员函数重载运算符:>
bool Person::operator>(const Person & other)
{
    return this->age > other.age;
}

// 全局函数重载运算符:<
bool operator<(const Person & p1, const Person & p2)
{
    return p1.age < p2.age;
}

// 前置递增运算符重载,实现年龄自增
// 返回引用是为了一直对一个数据进行递增操作
Person & Person::operator++(void)
{
    // 先做++运算
    this->age++;
    // 再将自身返回
    return *this;
}

// 后置递增运算符重载,实现年龄自增
// int代表占位参数,可以用于区分前置和后置递增
Person Person::operator++(int)
{
    // 先记录当前结果
    Person temp = *this;
    // 再进行++运算
    this->age++;
    // 把之前记录的结果返回
    return temp;
}

// ==运算符重载,实现两个对象比较
bool Person::operator==(const Person & other)
{
    return (this->name == other.name && this->age == other.age);
}

// !=运算符重载,实现两个对象比较
bool Person::operator!=(const Person & other)
{
    return !(this->name == other.name && this->age == other.age);
}

// 函数调用运算符重载
void  Person::operator()(void)
{
    cout << "{name: " << this->name << ", age: " << this->age << "}\n";
}

// 左移运算符重载,输出自定义数据
// 不会利用成员运算符重载<<运算符,因此无法实现cout在左侧
// 因此,只能用全局函数重载<<运算符
ostream & operator<<(ostream & cout, const Person & person)
{
    cout << "{name: " << person.name << ", age: " << person.age << "}";
    return cout;
}

// 转换函数
Person::operator string(void)
{
    string str;
    str = "{name: " + this->name + ", age: " + to_string(this->age) + "}";
    return str;
}

  修改包含 main() 函数的文件中的内容。

#include <iostream>
#include "person.cpp"
#include "cstring"

using namespace std;

int main(void)
{
    string str = "Sakura";
    Person person("Sakura", 10);
    str = person;

    cout << str << endl;
}

七、嵌套类

  在 C++ 中,可以将类声明放在另一个类中。在另一个类中声明的类被称为 嵌套类。它通过提供新的类型类作用域来避免名称混乱。包含类成员函数可以创建被嵌套类的对象。而仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用作用域解析运算符。

  对类进行嵌套与包含并不同。包含意味着将类对象作为另一个类的成员,而对了进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效。对类进行嵌套,通常是为了帮助实现另一个类,并避免名称冲突。

  嵌套类的声明位置决定了嵌套类的作用域,即它决定了程序哪些部分可以创建这种类对象。其次,和其它类一样,嵌套类的共有部分、保护部分和私有部分控制了对类成员的访问。如果嵌套类是在另一个类的私有部分声明的,则只有后者知道它。如果嵌套类是在另一个类的保护部分声明的,则对于后者来说他是可见的,但对于外部世界则是不可见的。然而,在这种情况下,派生类将知道嵌套类,并可以直接创建这种类型的对象。如果嵌套类是在另一个类的公有部分声明的,则允许后者、派生类以及外部世界使用它。然而,由于嵌套类的作用域为包含它的类,因此在外部世界使用它是,必须使用类限定符。

#include <iostream>

using namespace std;

class Outer
{
private:
    int a;
protected:
    int b;
public:
    int c;

    Outer(void) {}
    Outer(int a, int b, int c) : a(a), b(b), c(c) {}
  
    // 声明内部类
    class Inner
    {
    private:
        int a;
    protected:
        int b;
    public:
        int c;

        Inner(void) {}
        Inner(int a, int b, int c) : a(a), b(b), c(c) {}

        // 外部类对象指针
        Outer * outer;

        // 内部类访问外部类的成员
        void vistedOuter(void)
        {
  
            cout << "Outer --> a = " << outer->a << endl;
            cout << "Outer --> b = " << outer->b << endl;
            cout << "Outer --> c = " << outer->c << endl;
        }
    };

    // 外部类定义嵌套类的对象
    Inner inner;
  
    // 外部类访问内部类的成员
    void vistedInner(void)
    {
        // 嵌套类的共有部分、保护部分和私有部分控制了对类成员的访问
        // cout << "Inner --> a = " << inder.a << endl;
        // cout << "Inner --> b = " << inner.b << endl;
        cout << "Inner --> c = " << inner.c << endl;
    }
};

int main(void)
{
    // 创建外部类对象
    Outer outer = {1, 2, 3};
    // 创建内部类对象
    Outer::Inner inner = {4, 5, 6};

    // 为外部类的innner属性赋值
    outer.inner = inner;
    // 为内部类的outer属性赋值
    inner.outer = &outer;

    // 外部类调用成员方法访问内部类的成员
    outer.vistedInner();
    // 外部类调用成员方法访问内部类的成员
    inner.vistedOuter();

    return 0;
}
posted @ 2023-04-28 20:46  星光樱梦  阅读(148)  评论(0编辑  收藏  举报