首页 > 编程语言 >04-c++类和对象(下)

04-c++类和对象(下)

时间:2025-01-03 21:34:29浏览次数:3  
标签:函数 04 对象 ++ c++ 运算符 Person 重载 name

一、友元

前面学习的类中,只能通过该类的公共方法访问私有数据。而如果将某个函数设置为类的友元,那么这个函数就可以直接访问该类的私有数据,破坏了类的封装性,只在某些特定的情况下使用。

友元的分类:普通全局函数作为友元、类的某个成员函数作为友元、整个类作为友元。关键字:friend。

1.全局函数作为类的友元

  • 代码演示
class Object
{
    friend void useObject(Object &ob);
private:
    char cup[32];
public:
    char pen[32];
    Object(){}
    Object(char *cup, char *pen)
    {
        strcpy(this->cup, cup);
        strcpy(this->pen, pen);
    }
};

// 定义一个全局函数
void useObject(Object &ob)
{
    cout << "小明正在使用" << ob.pen << endl;
    cout << "小明正在使用" << ob.cup << endl;
}

void test27()
{
    Object xh_obj("小红的水杯", "小红的钢笔");
    useObject(xh_obj);
}
  • 运行结果
小明正在使用小红的钢笔
小明正在使用小红的水杯
  • 说明:未设置友元的时候,全局函数直接访问对象的私有数据会报错,但是设置了友元就可以访问了。将全局函数设置友元,只需要在类的首行加上函数的声明,并在前面加上 friend 关键字即可。

2.类的成员函数作为另外一个类的友元

  • 代码演示
// 向前声明Object类
class Object;
// 要将Person类中的成员函数设置为Object的友元,先定义Person类
class Person
{
private:
    char name[32];
public:
    Person(){}
    Person(char *name)
    {
        strcpy(this->name, name);
    }
    void useCup(Object &obj);
    void usePen(Object &obj);
};

class Object
{
    friend void Person::useCup(Object &obj);
private:
    char cup[32];
public:
    char pen[32];
    Object(){}
    Object(char *cup, char *pen)
    {
        strcpy(this->cup, cup);
        strcpy(this->pen, pen);
    }
};

// 将Person的函数在两个类的下方首先,否则没法识别到Object的数据
void Person::useCup(Object &obj) {
    cout << name << "正在使用" << obj.cup << endl;
}

void Person::usePen(Object &obj) {
    cout << name << "正在使用" << obj.pen << endl;
}


void test27()
{
    Object xh_obj("小红的水杯", "小红的钢笔");
    Person xm("小明");
    xm.usePen(xh_obj);
    xm.useCup(xh_obj);
}
  • 运行结果
小明正在使用小红的钢笔
小明正在使用小红的水杯
  • 说明:假设将 A 类中的成员函数设置为 B 类的友元,其步骤如下:
  1. 先定义 A 类,并且 A 类的成员函数在类外(A 和 B 类的下方)实现;
  2. 向前声明 B 类,在 A 类前面声明 B 类;
  3. 在 B 类中使用 friend 声明 A 的成员函数为友元。

3.一个类作为另外一个类的友元

  • 代码演示
// 其它代码和上面类的成员函数作为另外一个类的友元一样
// 只是不是将单个函数作为友元,而是整个类,只修改一句代码
// 将class Object中的
friend void Person::useCup(Object &obj); // 改为
friend Person;
// 这样,如果Person类中有多个成员函数需要设置为Object的友元
// 不需要一个个函数去 friend ,只需要把整个类 friend 就行
  • 总结
    1. 友元关系不能被继承,继承后面学。即:类A 是父类,类B 是 类A 的朋友,类C 是 类A 的子类,但 类B 不一定是 类C 的朋友;
    2. 友元关系是单向的。即:类 A 是类 B 的朋 友,但类 B 不一定是类 A 的朋友;
    3. 友元关系不具有传递性。即:类 B 是类 A 的朋 友,类 C 是类 B 的朋友,但类 C 不一定是类 A 的朋友。

二、数组类

数组类,本质上是一个类,可以通过这个类创建一个个数组对象,实现数组的部分功能。

  • 代码演示
class Array
{
private:
    // 数组的实际元素个数(数组大小)
    int size;
    // 数组可容纳的元素个数(数组容量)
    int capacity;
    // 数组首元素地址
    int *array;
public:
    //  构造函数
    Array();
    Array(int capacity);
    Array(const Array &obj);
    // 析构函数
    ~Array();
    // 数组的功能函数
    // 返回数组大小和数组容量
    int getSize();
    int getCapacity();
    // 尾部插入数据
    void pushBack(int num);
    // 删除尾部数据
    void popBack();
    // 遍历数组
    void printArray();
};

Array::Array() {
    size = 0;
    // 将容量默认初始化为5
    capacity = 5;
    // 为数组申请堆区空间
    array = new int[capacity];
}

Array::Array(int capacity) {
    size = 0;
    this->capacity = capacity;
    // 为数组申请堆区空间
    array = new int[capacity];
}

Array::Array(const Array &obj) {
    size = obj.size;
    capacity = obj.capacity;
    // 为数组申请堆区空间
    array = new int[capacity];
    // 将旧对象的数据通过内存拷贝拷贝给新对象
    memcpy(array, obj.array, sizeof(int) * capacity);
}

Array::~Array() {
    // 释放前先判断数组是否为空
    if(array != NULL)
    {
        // 注意释放的是数组,加 []
        delete [] array;
        array = NULL;
    }
}

int Array::getSize() {
    return size;
}

int Array::getCapacity() {
    return capacity;
}

void Array::pushBack(int num) {
    // 插入前先判断数组是否已满
    if (size == capacity)
    {
        // 如果满了就为数组增加一倍的空间
        int *tempArray = new int[capacity * 2];
        // 将以前的数组数据拷贝给新开辟的数组空间
        memcpy(tempArray, array, sizeof(int) * capacity);
        capacity = capacity * 2;
        // 释放以前的数组
        delete [] array;
        // 指向新开辟的空间
        array = tempArray;
    }
    // 末尾插入数据
    array[size] = num;
    size++;
}

void Array::popBack() {
    // 先判断数组是否为空
    if (NULL == array)
    {
        cout << "数组为空" << endl;
        return;
    }
    size--;
}

void Array::printArray() {
    int i = 0;
    for (i = 0; i < size; i++)
    {
        cout << array[i] << " ";
    }
    cout << endl;
}

void test28()
{
    // 创建一个数组对象
    Array array1;
    // 给数组添加5个元素
    array1.pushBack(11);
    array1.pushBack(22);
    array1.pushBack(33);
    array1.pushBack(44);
    array1.pushBack(55);
    // 打印数组大小容量
    cout << "size = " << array1.getSize() << " capacity = " << array1.getCapacity() << endl;
    // 遍历数组
    array1.printArray();

    // 添加一个元素,打印大小和容量
    array1.pushBack(66);
    cout << "size = " << array1.getSize() << " capacity = " << array1.getCapacity() << endl;
    array1.printArray();

    // 删除一个,打印信息
    array1.popBack();
    cout << "size = " << array1.getSize() << " capacity = " << array1.getCapacity() << endl;
    array1.printArray();
}
  • 运行结果
size = 5 capacity = 5 
11 22 33 44 55        
size = 6 capacity = 10
11 22 33 44 55 66     
size = 5 capacity = 10
11 22 33 44 55 
  • 注意:
    1. 数组类中包含数组的必要数据:数组的的大小,即实际的元素个数,数组的容量,数组首元素的地址;
    2. 数组初始化,除了初始化容量以外,还得申请相应大小的堆区空间;
    3. 插入数据的时候,如果数组的大小和容量相等,代表数组已满,要插入需要先追加空间。但追加空间不是在原空间后面追加,而是新申请内存(为原来的内存加新申请的空间的大小总和),然后再将原来空间的数据,拷贝到新空间。不在后面直接追加的原因是,不确定原空间后面的空间是否存储了其它数据,后面添加可能占用非法内存。

三、运算符重载

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

关键字:operator 比如重载 + 运算符,其函数名 operator+

一元运算符:运算符需要的运算对象是一个,如i++ --i等;二元运算符:运算符需要的运算对象是两个,如+ - *等。

1.重载运算符的步骤

  1. 首先必须明确:运算符左边的运算符对象是自定义对象还是其他;
  2. 如果左边是其他,必须使用全局函数完成运算符重载,且全局函数必须是友元;
  3. 如果左边是自定义对象,可以使用成员函数或全局函数完成运算符重载,推荐用成员函数实现。如果使用成员函数,可以少一个参数,如果使用全局函数,不可以少参数,且必须把全局函数设置友元。

先大致了解重载运算符的步骤及注意事项,结合下面的案例具体学习。

2.运算符重载应用

2.1全局函数重载 << 运算符

  • 代码演示
// 定义一个 Person 类
class Person
{
    friend void operator<<(ostream &out, Person &obj);
private:
    int num;
    // string 需要包含 <string> 头文件
    string name;
    float score;
public:
    Person()
    {
        num = 000;
        name = "admin";
        score = 0.0f;
    }
    Person(int num, string name, float score)
    {
        this->num = num;
        // string 中重载了 = 运算符,因此这里可以直接赋值
        this->name = name;
        this->score = score;
    }
    void showPerson()
    {
        cout << "num = " << num << " name = " << name << " score = " << score << endl;
    }
};

// 重载 << 运算符
void operator<<(ostream &out, Person &obj)
{
    out << "num = " << obj.num << " name = " << obj.name << " score = " << obj.score << endl;
}

void test29()
{
    // 创建一个对象并初始化
    Person xm(101, "小明", 88.5);
    xm.showPerson();
    // 之前我们想要查看对象信息,需要通过公共方法来访问
    // 现在可以通过 cout << 对象 直接访问
    cout << xm;
    Person xh(102, "小红", 85.0);
    cout << xh;
}
  • 运行结果
num = 101 name = 小明 score = 88.5
num = 101 name = 小明 score = 88.5
num = 102 name = 小红 score = 85
  • 说明:

    1. 上面重载了 << 运算符,首先 << 左边是一个非自定义对象,因此需要通过全局函数完成运算符重载;
    2. 因为这里是需要通过重载函数操作对象数据的,因此需要将重载函数设置为类的友元;
    3. 这里重载 << 运算符只是针对当前类创建的对象有效;
    4. operator<<为重载函数名,<< 左边的作为函数的第一个参数,右边的作为函数的第二个参数;
    5. cout << xm是编译器优化后的函数调用方式,其本质是operator<<(cout, xm)
  • 还可以完成链式操作,连续输出多个对象:

// 还是上面案例的代码
// 修改函数
ostream& operator<<(ostream &out, Person &obj)
{
    out << "num = " << obj.num << " name = " << obj.name << " score = " << obj.score << endl;
    return out;
}
// 函数修改了,记得修改类的友元
friend ostream&  operator<<(ostream &out, Person &obj);
// 在 test29 函数中添加
cout << xh << xm;
  • 运行结果
num = 102 name = 小红 score = 85  
num = 101 name = 小明 score = 88.5
  • 说明:
    1. 上面的链式操作可以一次输出多个对象的信息;
    2. 原理是将 out 返回,即 cout 的引用,那么上一次函数运算的结果 cout 继续作为 << 的左值,完成连续输出,实现链式操作。

2.2全局函数重载 >> 运算符

这里要实现通过cin >> 对象给对象成员赋值

  • 代码演示
// 还是上面的类
// 重载 >> 运算符
istream &operator>>(istream &in, Person &obj) {
    in >> obj.num >> obj.name >> obj.score;
    return in;
}
// 将全局函数设置为类的友元
friend istream &operator>>(istream &in, Person &obj);

// 调用函数运行
void test30() {
    Person jack;
    cin >> jack;
    cout << jack;
}
  • 运行结果
101 jack 88
num = 101 name = jack score = 88
  • 和上面重载 >> 运算符原理类似,不做过多解释。

2.3全局函数重载 + 运算符

这里要实现两个对象的加法,即:对象1 + 对象2

  • 代码演示
// 还是使用上面的 Person 类
// 重载 + 运算符
Person operator+(Person obj1, Person obj2)
{
    // 定义一个临时对象用于存放运算结果
    Person temp;
    temp.num = obj1.num + obj2.num;
    temp.name = obj1.name + obj2.name;
    temp.score = obj1.score + obj2.score;
    return temp;
}
// 添加友元
friend Person operator+(Person obj1, Person obj2);
// 将之前的重载 << 函数的第二个参数引用传递改为值传递
ostream &operator<<(ostream &out, Person obj) // 同样友元也记得该

// 调用函数执行    
void test31()
{
    Person jack(101, "jack", 88.5);
    Person rose(102, "rose", 77.5);
    cout << jack + rose;
}    
  • 运行结果
num = 203 name = jackrose score = 166
  • 说明:
    1. Person operator+(Person obj1, Person obj2)的返回值不能为引用,因为函数运行结束,临时变量 temp 就释放了,如果返回引用的话,后面 cout 输出会访问非法内存,因此,重载 << 运算符的第二个参数也改为值传递了,这样才能类型匹配;
    2. 如果返回值为非引用,那么它返回的就是具体的值,那么重载 + 运算符的第一个参数也要非引用,因为如果进行链式操作,那么会将上一次 + 运算的结果作为下一次 + 运算的左值,但上一次运算结果是具体值,不能对值取引用,因此第一个参数不能直接引用传递;
    3. 如果第一个参数一定要引用传递的话,那么要加 const 修饰为常引用,才能对值取引用。

2.4成员函数重载 + 运算符

上面的案例, + 的左值为自定义对象,因此可以通过成员函数实现运算符重载。

  • 代码演示
// 还是上面的 Person 类,只是在类中添加如下成员函数
// 成员函数重载 + 运算符
    Person operator+(Person obj)
    {
        Person temp;
        temp.num = num + obj.num;
        temp.name = name +obj.name;
        temp.score = score + obj.score;
        return temp;
    }
// 调用函数执行
void test32()
{
    Person jack(101, "jack", 88.5);
    Person rose(102, "rose", 77.5);
    Person bob(102, "bob", 66.5);
    cout << jack + rose + bob;
}
  • 运行结果
num = 305 name = jackrosebob score = 232.5
  • 说明:成员函数实现重载,函数调用本质上是:jack.operator+(rose),只是被编译器简化为了jack + rose,因此只需要传一个参数,因为对象调用函数,函数内部会自动生成保存调用该函数的对象的 this 指针,因此函数内部可以直接使用调用该函数的对象的成员数据,可以少传一个参数。

2.5成员函数重载 == 运算符

直接通过 == 比较两个对象是否相等,即实现 对象1 == 对象2的判断。

  • 代码演示
// 还是上面的 Person 类,只是在类中添加如下成员函数
// 成员函数重载 == 运算符
    bool operator==(Person &obj)
    {
        if ((num == obj.num) && (name == obj.name) && (score == obj.score))
            return true;
        return false;
    }
// 调用函数执行
void test33()
{
    Person jack(101, "jack", 88.5);
    Person rose(102, "rose", 77.5);
    Person jack2(101, "jack", 88.5);

    if (jack == jack2)
    {
        cout << "相等" << endl;
    }
    else
    {
        cout << "不相等" << endl;
    }
}
  • 运行结果
相等

2.6重载 ++ – 运算符

++ 和 – 原理相同,这里就以重载 ++ 运算符为例。

重载 ++ 运算符,有两种情况, ++ 在前和在后,但是其函数名都是 operator++ ,而且该运算符的操作数只有一个,该怎样区分两种不同的情况呢,这里就用到了函数的重载,为区分两个函数,还用到了占位参数。

  • 因此分为两种情况, ++ 前置和 ++ 后置:
    1. 当编译器看到 ++a(前置++)时,它就调用 operator++(a), 先++ 后使用;
    2. 当编译器看到 a++(后置++)时,它就调用 operator++(a, int),先使用后++。
2.6.1重载前置 ++
  • 代码演示
// 还是上面的 Person 类,只是在类中添加如下成员函数
// 成员函数重载前置 ++
    Person& operator++() {
        num++;
        name = name + name;
        score++;
        return *this;
    }
// 调用函数执行
void test34() {
    Person jack(101, "jack", 88.5);
    Person bob;
    bob = ++jack;
    cout << jack;
    cout << bob;
}
  • 运行结果
num = 102 name = jackjack score = 89.5
num = 102 name = jackjack score = 89.5
  • 说明:重载前置 ++ 的时候,函数没有占位参数。
2.6.2重载后置 ++
  • 代码演示
// 还是上面的 Person 类,只是在类中添加如下成员函数
// 成员函数重载后置 ++
    Person operator++(int) {
        Person old_obj = *this;
        num++;
        name = name + name;
        score++;
        return old_obj;
    }
// 调用函数执行
void test35() {
    Person jack(101, "jack", 88.5);
    Person bob;
    bob = jack++;
    cout << jack;
    cout << bob;
}
  • 运行结果
num = 102 name = jackjack score = 89.5
num = 101 name = jack score = 88.5
  • 说明:
    1. 重载后置 ++ 的时候,函数有占位参数;
    2. 函数的返回值不能是引用,因为返回的是一个临时局部变量,保存的对象自增前的值,如果返回引用,变量在函数调用结束后就释放了,操作释放的变量会访问非法内存。

2.7重载 () 运算符

我们在前面调用函数的时候,都会在函数名后面加 () 表示调用函数,因此 () 又叫做函数调用运算符。重载函数调用运算符,实现通过对象加()完成函数调用。

  • 代码演示
class Print {
public:
    Print &operator()(char *str)
    {
        cout << str;
        return *this;
    }
};

void test36()
{
    Print obj1;
    obj1("hello");
    cout << endl;

    Print print;
    print("hello ")("world ")("hello ")("friend");
    cout << endl;

    Print()("hello ")("world ")("hello ")("friend");
    cout << endl;
}
  • 运行结果
hello                   
hello world hello friend
hello world hello friend
  • 说明:上面定义的类又叫仿函数。

标签:函数,04,对象,++,c++,运算符,Person,重载,name
From: https://blog.csdn.net/qq_63958145/article/details/144917731

相关文章

  • 03-C++类和对象(中)
    一、初始化列表1.对象成员对象成员即一个类的对象作为另外一个类的成员。代码演示classA{private:inta;public:A(){a=0;cout<<"A的无参构造被调用"<<endl;}A(inta1){a=a1;cout<<"A......
  • 03-C++类和对象(上)
    一、类的概述1.类的引入类的封装:将数据和方法封装在一起,加以权限区分,用户只能通过公共方法访问私有数据。为什么要将数据和方法封装在一起呢,而且还要通过公共方法才能访问私有数据?C语言中数据和方法分开可能产生的问题://定义一个狗结构体structDog{charname[......
  • B4004 [GESP202406 三级] 寻找倍数
    题目描述小杨有一个包含 ......
  • P9041 [PA2021] Fiolki 2
    P9041[PA2021]Fiolki2题意给一个\(n\)个点\(m\)条边的DAG和一个常数\(k\)。定义\(f(l,r)\)表示最多选择不相交路径条数,满足起点\(s\in[1,k]\),终点\(t\in[l,r]\)。对所有的\(x\in[0,k]\),求出有多少\([l,r]\subseteq(k,n]\)使得\(f(l,r)=x\)。\(n\le10^5,m......
  • C++程序运行的三种方式
    一、例程编一个程序,计算机随机产生一个整数(1至5),自己输入一个整数,若两数相同,则输出“恭喜你,中奖了!奖金10元”,否则输出“没中奖,请付费2元”;同时公布中奖号码。#include<iostream>#include<cstdlib>//要用到定义在其中的rand()、srand()函数#include<ctime>//......
  • Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现15
    前言2009年9月Spring3.0RC1发布后,Spring就引入了SpEL(SpringExpressionLanguage)。对于开发者而言,引入新的工具显然是令人兴奋的,但是对于运维人员,也许是噩耗的开始。类比Struts2框架,会发现绝大部分的安全漏洞都和ognl脱不了干系。尤其是远程命令执行漏洞,占据了多少甲方乙方......
  • Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现13
    前言2009年9月Spring3.0RC1发布后,Spring就引入了SpEL(SpringExpressionLanguage)。对于开发者而言,引入新的工具显然是令人兴奋的,但是对于运维人员,也许是噩耗的开始。类比Struts2框架,会发现绝大部分的安全漏洞都和ognl脱不了干系。尤其是远程命令执行漏洞,占据了多少甲方乙方......
  • Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现1
    前言2009年9月Spring3.0RC1发布后,Spring就引入了SpEL(SpringExpressionLanguage)。对于开发者而言,引入新的工具显然是令人兴奋的,但是对于运维人员,也许是噩耗的开始。类比Struts2框架,会发现绝大部分的安全漏洞都和ognl脱不了干系。尤其是远程命令执行漏洞,占据了多少甲方乙方......
  • Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现4
    前言2009年9月Spring3.0RC1发布后,Spring就引入了SpEL(SpringExpressionLanguage)。对于开发者而言,引入新的工具显然是令人兴奋的,但是对于运维人员,也许是噩耗的开始。类比Struts2框架,会发现绝大部分的安全漏洞都和ognl脱不了干系。尤其是远程命令执行漏洞,占据了多少甲方乙方......
  • C++期末总复习last day 20250103
    内容主体来自于QJH先生,向其表示敬意。C++高级程序设计题目类型简述题(5题、25分)什么是数据抽象与封装?相比于过程抽象与封装,数据抽象与封装有什么好处?……(不用死记硬背)程序分析题(5题、40分)指出下面程序的错误和错误原因写出下面程序的运行结果。写出下面程序的运行......