C++学习笔记——友元及重载运算符

news2024/11/14 17:35:40

目录

一、友元

1.1声明友元函数

1.2声明友元类

二、运算符重载

2.1重载加号运算符

2.2重载流插入运算符

三、一个简单的银行管理系统

四、 详细的介绍


一、友元

在 C++ 中,友元是一个函数或类,它可以访问另一个类的私有成员或保护成员。通常情况下,我们在类定义中声明友元函数或友元类,以便它们能够访问类的私有成员。友元可以是一个函数、类、或整个命名空间。

1.1声明友元函数

首先,我们来看一下如何声明友元函数。假设我们有一个 Box 类,它表示一个三维立方体:

class Box {
public:
    Box(double l, double w, double h) : length(l), width(w), height(h) {}

private:
    double length;
    double width;
    double height;
};

我们想要计算一个 Box 对象的体积,但是 lengthwidthheight 都是私有成员,无法在类外直接访问。这时候,我们可以使用友元函数来实现。我们可以在类定义中声明一个友元函数,并将其定义为一个全局函数:

class Box {
public:
    Box(double l, double w, double h) : length(l), width(w), height(h) {}

    friend double getVolume(Box box);

private:
    double length;
    double width;
    double height;
};

double getVolume(Box box) {
    return box.length * box.width * box.height;
}

在上面的代码中,我们在 Box 类定义中声明了一个友元函数 getVolume(),并将其定义为一个全局函数。在 getVolume() 函数中,我们可以直接访问 Box 类的私有成员 lengthwidthheight,计算出它的体积并返回。

1.2声明友元类

除了友元函数,我们还可以声明友元类。假设我们有一个 Stack 类,它表示一个栈:

template <typename T>
class Stack {
public:
    Stack() : top(-1) {}

private:
    T data[10];
    int top;

    friend class StackIterator<T>;
};

我们想要实现一个迭代器类来遍历栈中的元素。但是,由于 datatop 都是私有成员,无法在迭代器类中直接访问。这时候,我们可以声明 StackIterator 类为 Stack 类的友元类:

template <typename T>
class StackIterator {
public:
    StackIterator(Stack<T>& s) : stack(s), index(s.top) {}

    T next() {
        return stack.data[index--];
    }

private:
    Stack<T>& stack;
    int index;
};

template <typename T>
class Stack {
public:
    Stack() : top(-1) {}

    friend class StackIterator<T>;

private:
    T data[10];
    int top;
};

在上面的代码中,我们声明了一个友元类 StackIterator,并在 Stack 类定义中将其声明为友元类。在 StackIterator 类中,我们可以直接访问 Stack 类的私有成员 datatop,实现了对栈的迭代。

二、运算符重载

C++ 中有一些运算符是可以被重载的,比如加号、减号、乘号、除号等等。通过重载运算符,我们可以改变它的行为,使其能够作用于我们自定义的类型。

2.1重载加号运算符

假设我们有一个 Complex 类,它表示一个复数:

class Complex {
public:
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

private:
    double real;
    double imag;
};

我们想要实现复数的加法运算,但是加号运算符无法直接作用于自定义的类型。这时候,我们可以重载加号运算符 +,使其能够对两个 Complex 对象进行相加,并返回一个新的 Complex 对象:

class Complex {
public:
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }

private:
    double real;
    double imag;
};

在上面的代码中,我们重载了加号运算符 +,并定义了一个成员函数 operator+()。在函数中,我们定义了一个新的 Complex 对象,其实部为两个复数的实部相加,虚部为两个复数的虚部相加,并将其返回。

2.2重载流插入运算符

除了加号运算符,我们还可以重载其他运算符。比如,我们可以重载流插入运算符 <<,使其能够输出我们自定义的类型。假设我们有一个 Person 类,它表示一个人:

#include <iostream>
#include <string>

class Person {
public:
    Person(std::string n, int a) : name(n), age(a) {}

    friend std::ostream& operator<<(std::ostream& os, const Person& p);

private:
    std::string name;
    int age;
};

std::ostream& operator<<(std::ostream& os, const Person& p) {
    os << "Name: " << p.name << ", Age: " << p.age;
    return os;
}

int main() {
    Person p("Alice", 20);
    std::cout << p << std::endl;
    return 0;
}

在上面的代码中,我们重载了流插入运算符 <<,并将其定义为一个友元函数。在函数中,我们将 Person 对象的姓名和年龄输出到标准输出流 os 中,然后返回该流。在主函数中,我们创建了一个 Person 对象 p,并使用 << 运算符将其输出到标准输出流中。

需要注意的是,在重载运算符时需要遵循一些规则。比如,我们必须使用正确的参数类型和返回值类型,以及保证运算符的语义符合预期。此外,C++ 中还存在一些运算符是无法被重载的,比如条件运算符 ?:,作用域运算符 :: 等等。

三、一个简单的银行管理系统

#include <iostream>
#include <string>
#include <vector>

class Account {
public:
    Account(std::string name, std::string accountNumber, double balance)
        : name(name), accountNumber(accountNumber), balance(balance) {}

    void deposit(double amount) {
        balance += amount;
        std::cout << "Deposit successful. New balance: " << balance << std::endl;
    }

    void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
            std::cout << "Withdrawal successful. New balance: " << balance << std::endl;
        } else {
            std::cout << "Insufficient balance." << std::endl;
        }
    }

    void displayInfo() {
        std::cout << "Name: " << name << std::endl;
        std::cout << "Account Number: " << accountNumber << std::endl;
        std::cout << "Balance: " << balance << std::endl;
    }

private:
    std::string name;
    std::string accountNumber;
    double balance;
};

class Bank {
public:
    void createAccount(std::string name, std::string accountNumber, double balance) {
        Account account(name, accountNumber, balance);
        accounts.push_back(account);
        std::cout << "Account created successfully." << std::endl;
    }

    void deposit(std::string accountNumber, double amount) {
        Account* account = findAccount(accountNumber);
        if (account != nullptr) {
            account->deposit(amount);
        } else {
            std::cout << "Account not found." << std::endl;
        }
    }

    void withdraw(std::string accountNumber, double amount) {
        Account* account = findAccount(accountNumber);
        if (account != nullptr) {
            account->withdraw(amount);
        } else {
            std::cout << "Account not found." << std::endl;
        }
    }

    void displayAllAccounts() {
        for (const auto& account : accounts) {
            account.displayInfo();
            std::cout << "---------------------------" << std::endl;
        }
    }

private:
    std::vector<Account> accounts;

    Account* findAccount(std::string accountNumber) {
        for (auto& account : accounts) {
            if (account.getAccountNumber() == accountNumber) {
                return &account;
            }
        }
        return nullptr;
    }
};

int main() {
    Bank bank;
    int choice;

    do {
        std::cout << "1. Create Account" << std::endl;
        std::cout << "2. Deposit" << std::endl;
        std::cout << "3. Withdraw" << std::endl;
        std::cout << "4. Display All Accounts" << std::endl;
        std::cout << "5. Exit" << std::endl;
        std::cout << "Enter your choice: ";
        std::cin >> choice;

        if (choice == 1) {
            std::string name, accountNumber;
            double balance;
            std::cout << "Enter name: ";
            std::cin >> name;
            std::cout << "Enter account number: ";
            std::cin >> accountNumber;
            std::cout << "Enter initial balance: ";
            std::cin >> balance;
            bank.createAccount(name, accountNumber, balance);
        } else if (choice == 2) {
            std::string accountNumber;
            double amount;
            std::cout << "Enter account number: ";
            std::cin >> accountNumber;
            std::cout << "Enter amount to deposit: ";
            std::cin >> amount;
            bank.deposit(accountNumber, amount);
        } else if (choice == 3) {
            std::string accountNumber;
            double amount;
            std::cout << "Enter account number: ";
            std::cin >> accountNumber;
            std::cout << "Enter amount to withdraw: ";
            std::cin >> amount;
            bank.withdraw(accountNumber, amount);
        } else if (choice == 4) {
            bank.displayAllAccounts();
        }

    } while (choice != 5);

    return 0;
}

在上面的代码中,我们定义了两个类:AccountBankAccount 类代表一个银行账户,具有姓名、账号和余额属性,以及存款、取款和展示信息的方法。Bank 类代表整个银行系统,具有创建账户、存款、取款和显示所有账户的方法。其中,存款和取款的操作会调用 Account 类相应的方法。

在主函数中,我们通过简单的菜单来模拟用户与银行系统的交互。用户可以选择创建账户、存款、取款或显示所有账户的操作,直到选择退出。

四、 详细的介绍

以下是代码中每个部分的更详细解释:

class Account {
public:
    Account(std::string name, std::string accountNumber, double balance)
        : name(name), accountNumber(accountNumber), balance(balance) {}

Account 类的构造函数中,我们使用传入的参数来初始化私有成员变量 nameaccountNumberbalance

    void deposit(double amount) {
        balance += amount;
        std::cout << "Deposit successful. New balance: " << balance << std::endl;
    }

deposit 方法用于存款操作。它接受一个 double 类型的参数 amount,代表要存入的金额。方法内部,我们将传入的金额加到余额上,并输出新的余额。

    void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
            std::cout << "Withdrawal successful. New balance: " << balance << std::endl;
        } else {
            std::cout << "Insufficient balance." << std::endl;
        }
    }

withdraw 方法用于取款操作。它接受一个 double 类型的参数 amount,代表要取出的金额。方法内部,我们首先检查账户余额是否足够,如果足够就从余额中减去取款金额,并输出新的余额。如果余额不足,则输出错误信息。

    void displayInfo() {
        std::cout << "Name: " << name << std::endl;
        std::cout << "Account Number: " << accountNumber << std::endl;
        std::cout << "Balance: " << balance << std::endl;
    }

displayInfo 方法用于展示账户的信息。它输出账户的姓名、账号和余额。

class Bank {
public:
    void createAccount(std::string name, std::string accountNumber, double balance) {
        Account account(name, accountNumber, balance);
        accounts.push_back(account);
        std::cout << "Account created successfully." << std::endl;
    }

createAccount 方法用于创建一个新的账户。它接受三个参数:姓名、账号和初始余额。方法内部,我们创建一个新的 Account 对象,并将其添加到 accounts 列表中。然后,我们输出成功创建账户的消息。

    void deposit(std::string accountNumber, double amount) {
        Account* account = findAccount(accountNumber);
        if (account != nullptr) {
            account->deposit(amount);
        } else {
            std::cout << "Account not found." << std::endl;
        }
    }

deposit 方法用于给指定账户存款。它接受两个参数:账户号码和存款金额。方法内部,我们通过调用 findAccount 方法来查找要操作的账户。如果找到了账户,则调用该账户的 deposit 方法执行存款操作。如果未找到账户,则输出错误信息。

    void withdraw(std::string accountNumber, double amount) {
        Account* account = findAccount(accountNumber);
        if (account != nullptr) {
            account->withdraw(amount);
        } else {
            std::cout << "Account not found." << std::endl;
        }
    }

withdraw 方法用于给指定账户取款。它接受两个参数:账户号码和取款金额。方法内部,我们通过调用 findAccount 方法来查找要操作的账户。如果找到了账户,则调用该账户的 withdraw 方法执行取款操作。如果未找到账户,则输出错误信息。

    void displayAllAccounts() {
        for (const auto& account : accounts) {
            account.displayInfo();
            std::cout << "---------------------------" << std::endl;
        }
    }

displayAllAccounts 方法用于展示银行中所有账户的信息。它遍历 accounts 列表,并对每个账户调用 displayInfo 方法来输出信息。在每个账户信息输出之后,我们打印分隔线以区分不同的账户。

int main() {
    Bank bank;
    int choice;

    do {
        std::cout << "1. Create Account" << std::endl;
        std::cout << "2. Deposit" << std::endl;
        std::cout << "3. Withdraw" << std::endl;
        std::cout << "4. Display All Accounts" << std::endl;
        std::cout << "5. Exit" << std::endl;
        std::cout << "Enter your choice: ";
        std::cin >> choice;

        if (choice == 1) {
            std::string name, accountNumber;
            double balance;
            std::cout << "Enter name: ";
            std::cin >> name;
            std::cout << "Enter account number: ";
            std::cin >> accountNumber;
            std::cout << "Enter initial balance: ";
            std::cin >> balance;
            bank.createAccount(name, accountNumber, balance);
        } else if (choice == 2) {
            std::string accountNumber;
            double amount;
            std::cout << "Enter account number: ";
            std::cin >> accountNumber;
            std::cout << "Enter amount to deposit: ";
            std::cin >> amount;
            bank.deposit(accountNumber, amount);
        } else if (choice == 3) {
            std::string accountNumber;
            double amount;
            std::cout << "Enter account number: ";
            std::cin >> accountNumber;
            std::cout << "Enter amount to withdraw: ";
            std::cin >> amount;
            bank.withdraw(accountNumber, amount);
        } else if (choice == 4) {
            bank.displayAllAccounts();
        }

    } while (choice != 5);

    return 0;
}

main 函数中,我们使用一个循环来模拟用户与银行系统的交互。我们显示一个简单的菜单,让用户选择不同的操作。根据用户的选择,我们调用 Bank 类的相应方法来执行操作。当用户选择退出时,循环结束,程序终止。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1365695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务实战系列之Filter

前言 Filter&#xff0c;又名过滤器&#xff0c;当然不是我们日常中见到的&#xff0c;诸如此类构件&#xff1a; 而应该是微服务中常使用的&#xff0c;诸如此类&#xff08;图片来自官网&#xff0c;点击可查看原图&#xff09;&#xff1a; 一般用于字符编码转换&#xf…

Unity DOTS中的baking(二)Baker的触发

Unity DOTS中的baking&#xff08;二&#xff09;Baker的触发 我们知道&#xff0c;当传入Baker的authoring component的值发生变化时&#xff0c;就会触发baking。不过在有些情况下&#xff0c;component所引用的对象没有变化&#xff0c;而是对象自身内部的一些属性发生了变化…

DNS--windows sever 2012 r2

1 安装dns服务 得到 2 配置正向解析文件 设置正向解析文件的别名 配置(1)主机名 (2)别名&#xff08;3)邮件交换器 最终得到 3 配置反向文件 建立指针 得到 4 验证

【百科物理】-1.弹力和压力

导入 问题&#xff1a; 为什么在蹦床上弹得更高&#xff1f; 现象背后的原理&#xff0c;因为有力。 力&#xff1a;物体(物质)与物体(物质)之间推、拉、挤压的相互作用。力可以改变物体的运动状态&#xff08;比如踢足球&#xff09;&#xff0c;力可以改变物体的形状&#xf…

ProtoBuf一些踩坑记录

一、Protobuf学习基础 学习的资料很多也很全&#xff0c;这里添加几个链接进行Protobuf的基础学习的链接&#xff0c;链接中的案例使用C编辑&#xff1a; 链接&#xff1a;Protobuf介绍及简单使用(上&#xff09;_google_protobuf_version-CSDN博客 Protobuf介绍及简单使用(下&…

行云部署成长之路 -- 慢 SQL 优化之旅 | 京东云技术团队

当项目的SQL查询慢得像蜗牛爬行时&#xff0c;用户的耐心也在一点点被消耗&#xff0c;作为研发&#xff0c;我们可不想看到这样的事。这篇文章将结合行云部署项目的实践经验&#xff0c;带你走进SQL优化的奇妙世界&#xff0c;一起探索如何让那些龟速的查询飞起来&#xff01;…

three.js 学习笔记(学习中1.7更新) |

文章目录 three.js 学习笔记入门基础概念透视相机 第一个three.js应用threejs画布尺寸和布局canvas画布宽高度动态变化 坐标辅助器 THREE.AxesHelper实现动画效果requestAnimationFrame时间相关属性和方法 THREE.Clock类 相机控件 轨道控制器OrbitControls 灯光点光源点光源辅助…

【ArcGIS微课1000例】0087:经纬度格式转换(度分秒转度、度转度分秒)

ArcGIS软件可以很方便的直接实现度分秒转度、度转度分秒。 文章目录 一、转换预览二、工具介绍三、案例解析一、转换预览 借助ArcGIS快速实现度分秒与度及其他格式的坐标转换。例如:度分秒→度 度分秒: 度: 二、工具介绍 转换坐标记法:将一个或两个字段包含的坐标记法从一…

PAT 乙级 1049 数列的片段和

分数 20 作者 CAO, Peng 单位 Google 给定一个正数数列&#xff0c;我们可以从中截取任意的连续的几个数&#xff0c;称为片段。例如&#xff0c;给定数列 { 0.1, 0.2, 0.3, 0.4 }&#xff0c;我们有 (0.1) (0.1, 0.2) (0.1, 0.2, 0.3) (0.1, 0.2, 0.3, 0.4) (0.2) (0.2, 0.3) …

【JAVA】Iterator 和 ListIterator 有什么区别?

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 在Java中&#xff0c;遍历集合是日常编程中常见的任务&#xff0c;而Iterator和ListIterator作为遍历集合的两个主要接口&#xff0…

【MySQL】索引基础

文章目录 1. 索引介绍2. 创建索引 create index…on…2.1 explain2.2 创建索引create index … on…2.3 删除索引 drop index … on 表名 3. 查看索引 show indexes in …4. 前缀索引4.1 确定最佳前缀长度&#xff1a;索引的选择性 5. 全文索引5.1 创建全文索引 create fulltex…

Angular学习第二天--问题记录

一、问题 1.用脚手架搭建完项目之后&#xff0c;缺少app.modules.ts文件&#xff0c; 2.解决办法&#xff1a; 在终端继续输入命令 ng new 项目名称 --no-standalone --routing --ssrfalse 3.完整目录&#xff1a; 二、问题 1.问题来源&#xff0c;源代码&#xff1a; <fo…

K8S API访问控制之RBAC利用

前言 K8S对于API的访问安全提供了访问控制&#xff0c;主要为4个阶段&#xff0c;本文为第二个阶段——鉴权的RBAC。RBAC是基于角色的访问控制&#xff0c;使用kubeadm安装集群1.6版本以上的都默认开启了RBAC。本文主要研究集群可能存在的利用点及相对应的利用手法。 API访问…

kettle的基本介绍和使用

1、 kettle概述 1.1 什么是kettle Kettle是一款开源的ETL工具&#xff0c;纯java编写&#xff0c;可以在Window、Linux、Unix上运行&#xff0c;绿色无需安装&#xff0c;数据抽取高效稳定。 1.2 Kettle核心知识点 1.2.1 Kettle工程存储方式 以XML形式存储以资源库方式存储…

Flutter 图片和资源的高效使用指南

文章目录 指定资源什么是 [pubspec.yaml](https://dart.cn/tools/pub/pubspec) 文件 图片图片常用的配置属性加载本地图片通过 pubspec.yml 文件进行配置图片目录使用 Image.asset 小部件加载本地图片 加载网络图片通过 Image.network小部件加载网络图片&#xff1a;使用Image.…

Idea将xml文件配置为模板

在配置mybatis的mapper映射文件的时候&#xff0c;通常需要到官网拷贝配置文件的内容&#xff0c;这里直接将xml的文件配置为模板&#xff0c;下次可以直接进行创建。

解锁Mac的无限可能:Sensei for Mac - 你的全能系统优化清理助手

你是否经常为Mac的缓慢速度和不断积累的缓存文件而感到烦恼&#xff1f;不用担心&#xff0c;因为今天&#xff0c;我要向您介绍一款全新的系统优化清理工具 - Sensei for Mac。 作为一款卓越的系统清理工具&#xff0c;Sensei for Mac在保持您的Mac系统流畅运行的同时&#x…

1-04C语言执行过程

一、概述 本小节主要讲解一个C程序从源代码到最终执行的过程&#xff0c;这个过程又可以细分为两部分&#xff1a; 源代码到可执行文件的过程可执行文件在内存中执行 本小节是C语言基础当中&#xff0c;比较容易被初学者忽视的知识点。而实际上&#xff1a; 熟悉C程序从源文…

【flink番外篇】9、Flink Table API 支持的操作示例(14)- 时态表的join(java版本)

Flink 系列文章 一、Flink 专栏 Flink 专栏系统介绍某一知识点&#xff0c;并辅以具体的示例进行说明。 1、Flink 部署系列 本部分介绍Flink的部署、配置相关基础内容。 2、Flink基础系列 本部分介绍Flink 的基础部分&#xff0c;比如术语、架构、编程模型、编程指南、基本的…

模板管理支持批量操作,DataEase开源数据可视化分析平台v2.2.0发布

2024年1月8日&#xff0c;DataEase开源数据可视化分析平台正式发布v2.2.0版本。 这一版本的功能升级包括&#xff1a;在“模板管理”页面中&#xff0c;用户可以通过模板管理的批量操作功能&#xff0c;对已有模板进行快速重新分类、删除等维护操作&#xff1b;数据大屏中&…