十四天学会C++之第五天:类的详细讨论
1. 友元函数和友元类
- 什么是友元函数和友元类,它们的作用。
- 如何声明和使用友元函数和友元类,访问类的私有成员。
友元函数(Friend Functions)
友元函数是一种特殊的函数,它被允许访问类的私有成员。这意味着即使成员是私有的,友元函数也能够直接访问它们,而不需要通过公有接口。这提供了更多的灵活性,允许外部函数与类密切合作。
示例-演示如何声明和使用友元函数:
#include <iostream>class MyClass {
private:int secretData;public:MyClass() : secretData(0) {}friend void FriendFunction(MyClass& obj); // 友元函数的声明};// 友元函数的定义
void FriendFunction(MyClass& obj) {obj.secretData = 42; // 可以访问私有成员
}int main() {MyClass myObj;FriendFunction(myObj); // 调用友元函数std::cout << myObj.secretData << std::endl; // 输出 42return 0;
}
示例中,FriendFunction
被声明为 MyClass
的友元函数,可以直接访问 secretData
私有成员。
友元类(Friend Classes)
友元类与友元函数类似,但它允许整个类成为另一个类的友元,而不仅仅是一个函数。这意味着友元类的所有成员都可以访问其他类的私有成员。
class MyClass {
private:int secretData;public:MyClass() : secretData(0) {}friend class FriendClass; // 友元类的声明};class FriendClass {
public:void AccessPrivateData(MyClass& obj) {obj.secretData = 42; // 可以访问私有成员}
};int main() {MyClass myObj;FriendClass friendObj;friendObj.AccessPrivateData(myObj); // 通过友元类访问私有成员std::cout << myObj.secretData << std::endl; // 输出 42return 0;
}
FriendClass
被声明为 MyClass
的友元类,因此它的成员函数可以访问 secretData
私有成员。
2. 拷贝构造函数
- 介绍拷贝构造函数的概念。
- 定义和使用拷贝构造函数,以处理对象的复制。
拷贝构造函数是C++中的一个特殊构造函数,用于创建一个对象的副本。当对象按值传递给函数、作为函数的返回值返回或者在初始化过程中需要复制时,拷贝构造函数会被自动调用。它用于确保对象的复制是正确的,包括成员变量的深拷贝。
示例-演示了如何定义和使用拷贝构造函数:
#include <iostream>
#include <cstring>class MyString {
private:char* str;public:// 构造函数,用于创建字符串对象MyString(const char* s) {str = new char[strlen(s) + 1];strcpy(str, s);}// 拷贝构造函数,用于创建对象的副本MyString(const MyString& other) {str = new char[strlen(other.str) + 1];strcpy(str, other.str);}// 析构函数,用于释放内存~MyString() {delete[] str;}// 显示字符串内容void display() {std::cout << str << std::endl;}
};int main() {MyString original("Hello, World!");MyString copy = original; // 使用拷贝构造函数创建副本original.display(); // 输出 "Hello, World!"copy.display(); // 输出 "Hello, World!"return 0;
}
在示例中,首先定义了一个 MyString
类,它包含一个字符数组 str
用于存储字符串。然后,定义一个拷贝构造函数,通过分配新内存并复制原始对象的内容来创建副本。最后,在 main
函数中,创建了一个 original
对象,并使用拷贝构造函数创建了 copy
对象。这两个对象分别存储相同的字符串内容,但它们在内存中有不同的副本。
3. 运算符重载
- 解释运算符重载的概念。
- 提供示例,说明如何重载常见的运算符,如+、-、*等。
运算符重载是C++中一种强大的特性,它允许为自定义类创建特定的运算符行为。通过运算符重载,可以让对象像内置类型一样执行加法、减法、乘法等操作,使代码更直观和易读。
示例-演示如何重载加法运算符:
#include <iostream>class Complex {
private:double real;double imag;public:Complex(double r, double i) : real(r), imag(i) {}// 运算符重载:重载+运算符,实现复数相加Complex operator+(const Complex& other) const {return Complex(real + other.real, imag + other.imag);}// 显示复数void display() const {std::cout << real << " + " << imag << "i" << std::endl;}
};int main() {Complex num1(3.0, 2.0);Complex num2(1.5, 4.5);Complex result = num1 + num2; // 使用重载的+运算符num1.display(); // 输出 "3 + 2i"num2.display(); // 输出 "1.5 + 4.5i"result.display(); // 输出 "4.5 + 6.5i"return 0;
}
在示例中,定义一个 Complex
类表示复数。然后,重载加法运算符 +
,使得两个 Complex
对象可以像内置数值类型一样相加。通过运算符重载,让复数对象的操作更自然和直观。
4. 静态成员和静态函数
- 讲解静态成员和静态函数的作用。
- 如何声明和使用静态成员和静态函数。
在C++中,静态成员和静态函数是属于整个类而不是类的实例的。它们被称为类级别的成员,与类的每个实例无关,而是与类本身关联。
静态成员是在类级别共享的数据成员。它们对于所有类的实例都是相同的。要声明静态成员,可以使用 static
关键字。
#include <iostream>class MyClass {
public:static int count; // 静态成员变量MyClass() {count++; // 每次创建实例时增加计数}static void showCount() {std::cout << "Total instances: " << count << std::endl;}
};int MyClass::count = 0; // 初始化静态成员变量int main() {MyClass obj1;MyClass obj2;MyClass obj3;MyClass::showCount(); // 调用静态函数显示计数return 0;
}
我们创建一个名为 MyClass
的类,包含一个静态整数 count
用于跟踪创建的实例数。创建 MyClass
的实例时,静态成员变量 count
都会增加。定义一个静态函数 showCount
来显示实例的总数。
静态函数是在类级别共享的成员函数。它们不需要访问类的实例数据,因此可以在没有实例的情况下调用。静态函数使用与类相关的方式调用,而不是使用实例。
5. 类的继承和多态性
- 介绍类的继承的概念,包括基类和派生类。
- 讲解多态性的概念和实现方式,包括虚函数和运行时多态性。
在C++中,类的继承和多态性是面向对象编程的核心概念之一。它们允许构建更强大、更灵活的对象模型。
类的继承允许创建一个新的类(称为派生类),它可以继承另一个类(称为基类)的属性和行为。可以在现有类的基础上创建新类,而不必从头开始编写代码。派生类可以添加额外的成员变量和成员函数,也可以覆盖基类的成员函数以改变其行为。
#include <iostream>// 基类
class Shape {
public:virtual void draw() {std::cout << "绘制形状" << std::endl;}
};// 派生类
class Circle : public Shape {
public:void draw() override {std::cout << "绘制圆形" << std::endl;}
};int main() {Shape shape;Circle circle;shape.draw(); // 输出:绘制形状circle.draw(); // 输出:绘制圆形return 0;
}
基类 Shape
和一个派生类 Circle
。派生类继承了基类的 draw
函数,并覆盖了它以提供不同的行为。在 main
函数中,我们创建了基类和派生类的对象,然后调用它们的 draw
函数,演示了多态性的概念。
多态性是一种能够在运行时选择正确函数版本的机制。在上面的示例中,Shape
类的 draw
函数是虚函数,而 Circle
类中的 draw
函数使用了 override
关键字来表示它是一个覆盖了基类函数的虚函数。这允许我们在基类指针或引用的上下文中调用派生类的函数,而选择的是正确的版本。
6. 示例和练习
- 示例代码,演示友元函数、拷贝构造函数、运算符重载、静态成员、类的继承和多态性的用法。
- 练习,以加强对这些高级主题的理解和应用。
1. 创建一个类 Fraction
表示分数,包括分子和分母。编写运算符重载函数,实现分数的加法和减法运算:
#include <iostream>class Fraction {
private:int numerator;int denominator;public:Fraction(int num, int den) : numerator(num), denominator(den) {// Ensure denominator is not zeroif (denominator == 0) {std::cerr << "Error: Denominator cannot be zero." << std::endl;exit(1);}}// Overload the + operator to add fractionsFraction operator+(const Fraction& other) const {int newNumerator = numerator * other.denominator + other.numerator * denominator;int newDenominator = denominator * other.denominator;return Fraction(newNumerator, newDenominator);}// Overload the - operator to subtract fractionsFraction operator-(const Fraction& other) const {int newNumerator = numerator * other.denominator - other.numerator * denominator;int newDenominator = denominator * other.denominator;return Fraction(newNumerator, newDenominator);}void display() const {std::cout << numerator << "/" << denominator << std::endl;}
};int main() {Fraction frac1(1, 2);Fraction frac2(1, 3);Fraction sum = frac1 + frac2;Fraction diff = frac1 - frac2;std::cout << "Fraction 1: ";frac1.display();std::cout << "Fraction 2: ";frac2.display();std::cout << "Sum: ";sum.display();std::cout << "Difference: ";diff.display();return 0;
}
解答说明:
- 创建名为
Fraction
的类,表示分数,包括分子和分母属性。 - 通过运算符重载,重载
+
和-
运算符,实现了分数的加法和减法。 - 在
main()
函数中,创建两个分数对象frac1
和frac2
,然后对它们进行加法和减法运算。
2. 创建一个基类 Vehicle
表示交通工具,包括名称和速度属性。创建两个派生类 Car
和 Bike
,它们继承了基类并添加了特定的属性:
#include <iostream>
#include <string>class Vehicle {
protected:std::string name;double speed;public:Vehicle(const std::string& n, double s) : name(n), speed(s) {}void display() const {std::cout << "Name: " << name << ", Speed: " << speed << " km/h" << std::endl;}
};class Car : public Vehicle {
private:int numWheels;public:Car(const std::string& n, double s, int wheels) : Vehicle(n, s), numWheels(wheels) {}void display() const {std::cout << "Car - ";Vehicle::display();std::cout << "Wheels: " << numWheels << std::endl;}
};class Bike : public Vehicle {
private:bool hasBasket;public:Bike(const std::string& n, double s, bool basket) : Vehicle(n, s), hasBasket(basket) {}void display() const {std::cout << "Bike - ";Vehicle::display();std::cout << "Basket: " << (hasBasket ? "Yes" : "No") << std::endl;}
};int main() {Car car("Sedan", 120.0, 4);Bike bike("Mountain Bike", 25.0, true);car.display();bike.display();return 0;
}
解答说明:
- 创建一个基类
Vehicle
,包括名称和速度属性。 - 然后,创建两个派生类
Car
和Bike
,它们继承了基类Vehicle
并添加了特定的属性。 - 在
main()
函数中,创建了一个Car
对象和一个Bike
对象,分别调用它们的display()
方法以显示车辆信息,包括名称、速度等。
3. 使用多态性存储和调用不同类型的动物对象:
#include <iostream>
#include <vector>class Animal {
public:virtual void speak() const {std::cout << "Animal speaks." << std::endl;}
};class Dog : public Animal {
public:void speak() const override {std::cout << "Dog barks." << std::endl;}
};class Cat : public Animal {
public:void speak() const override {std::cout << "Cat meows." << std::endl;}
};int main() {std::vector<Animal*> animals;animals.push_back(new Dog());animals.push_back(new Cat());for (const auto& animal : animals) {animal->speak();delete animal; // Don't forget to free memory}return 0;
}
解答说明:
- 定义一个基类
Animal
,创建两个派生类Dog
和Cat
。 - 在派生类中重写了虚函数
speak()
以实现不同类型的动物的声音。 - 使用指向基类的指针存储不同类型的动物对象,实现了多态性。
- 在循环中,通过基类指针调用虚函数
speak()
,实际执行的是派生类的版本。
4. 银行账户类 BankAccount
和友元函数 transfer
:
#include <iostream>class BankAccount; // Forward declarationclass BankAccount {
private:std::string accountNumber;double balance;public:BankAccount(const std::string& accNum, double initBalance) : accountNumber(accNum), balance(initBalance) {}void displayBalance() const {std::cout << "Account: " << accountNumber << ", Balance: " << balance << " USD" << std::endl;}friend void transfer(BankAccount& from, BankAccount& to, double amount);
};void transfer(BankAccount& from, BankAccount& to, double amount) {if (from.balance >= amount) {from.balance -= amount;to.balance += amount;std::cout << "Transfer successful." << std::endl;} else {std::cout << "Insufficient balance for transfer." << std::endl;}
}int main() {BankAccount acc1("12345", 1000.0);BankAccount acc2("67890", 500.0);acc1.displayBalance();acc2.displayBalance();transfer(acc1, acc2, 300.0); // Transfer money from acc1 to acc2acc1.displayBalance();acc2.displayBalance();return 0;
}
解答说明:
- 定义一个银行账户类
BankAccount
,包括账户号码和余额属性。 - 使用友元函数
transfer
实现了账户之间的资金转移。 - 在
main()
中,创建两个账户,显示它们的余额,然后进行转账操作。
5. 图形类 Shape
和派生类 Circle
和 Rectangle
,计算总面积:
#include <iostream>
#include <vector>class Shape {
protected:std::string color;public:Shape(const std::string& c) : color(c) {}virtual double getArea() const {return 0.0; // Default area for a generic shape}
};class Circle : public Shape {
private:double radius;public:Circle(const std::string& c, double r) : Shape(c), radius(r) {}double getArea() const override {return 3.14159 * radius * radius; // Area of a circle}
};class Rectangle : public Shape {
private:double width;double height;public:Rectangle(const std::string& c, double w, double h) : Shape(c), width(w), height(h) {}double getArea() const override {return width * height; // Area of a rectangle}
};int main() {std::vector<Shape*> shapes;shapes.push_back(new Circle("Red", 5.0));shapes.push_back(new Rectangle("Blue", 4.0, 6.0));double totalArea = 0.0;for (const auto& shape : shapes) {totalArea += shape->getArea();delete shape; // Don't forget to free memory}std::cout << "Total Area: " << totalArea << std::endl;return 0;
}
解答说明:
- 定义一个基类
Shape
,创建两个派生类Circle
和Rectangle
,重写虚函数getArea()
以返回不同形状的面积。 - 使用指向基类的指针存储不同类型的图形对象,实现了多态性。
- 在循环中,计算了所有图形的总面积。
相关文章:
十四天学会C++之第五天:类的详细讨论
1. 友元函数和友元类 什么是友元函数和友元类,它们的作用。如何声明和使用友元函数和友元类,访问类的私有成员。 友元函数(Friend Functions) 友元函数是一种特殊的函数,它被允许访问类的私有成员。这意味着即使成员…...

字典树学习笔记
trie 树,即字典树,是一种可以实现 O ( S ) O(S) O(S) 的预处理( S S S 为所有字符串的长度和), O ( N ) O(N) O(N)( N N N 为查询的字符串的长度)的查询的数据结构。 举个栗子,对于…...

web各个指标理解
QPS : 单位时间得请求次数 TPS :单位时间得事务数 并发 : QPS *单位响应时间 pv :进入一个网站,又单击打开该网站的其他页面,每打开一个页面就 增加一个PV,甚至在同一页面每刷新一次也多一个PV 二八定律:百…...
Java后端开发(七)-- 在gitee上部署远程仓库,通过idea上传本地代码(用idea2022版本开发)
目录 1. 在Gitee上创建gitee远程仓库 2.在打开idea,再打开您要上传的idea代码,先创建 本地git仓库...

Go语言入门心法(十二): GORM映射框架
Go语言入门心法(一): 基础语法 Go语言入门心法(二): 结构体 Go语言入门心法(三): 接口 Go语言入门心法(四): 异常体系 Go语言入门心法(五): 函数 Go语言入门心法(六): HTTP面向客户端|服务端编程 Go语言入门心法(七): 并发与通道 Go语言入门心法(八): mysql驱动安装报错o…...

Ubuntu更新镜像源切换
概述 用ubuntu用apt命令,自动安装或更新包的时候,默认的镜像源服务器非常卡,很不方便。切换到国内的镜像源,下载更新非常快。为防止以后忘记,本文以国内服务器阿里巴巴的为例简单描述。 版本 Ubuntu23.10 找到更新…...

“一键合并剪辑,轻松添加片头——全新的视频编辑工具让你成为视频制作达人“
在日常生活中,我们时常会遇到需要制作视频的情况。但面对繁琐的视频剪辑和合并,你是否感到无从下手?今天,我们为你带来一款全新的视频编辑工具,让你轻松成为视频制作达人! 首先我们要进入好简单批量智剪主页…...

1.3 矩阵
一、向量与矩阵 下面是三个向量 u \boldsymbol u u、 v \boldsymbol v v、 w \boldsymbol w w: u [ 1 − 1 0 ] v [ 0 1 − 1 ] w [ 0 0 1 ] \boldsymbol u\begin{bmatrix}\,\,\,\,1\\-1\\\,\,\,\,0\end{bmatrix}\kern 10pt\boldsymbol v\begin{bmatrix}\,\,\,…...
阿里云-AnalyticDB【分析型数据库】总结介绍
一、背景 随着企业IT和互联网系统的发展,产生了越来越多的数据。数据量的积累带来了质的飞跃,使得数据应用从业务系统的一部分演变得愈发独立。物流、交通、新零售等越来越多的行业需要通过OLAP做到精细化运营,从而调控生产规则、运营效率、企…...

数二思维导图
高数上 第一章:函数、极限、连续 函数 函数的单调性、周期性、奇偶性复合函数 极限 求直接代入型的极限求∞∞型的极限用等价无穷小代换求00型的极限用洛必达法则求00型或∞∞型的极限求∞•0型的极限求幂指函数的极限函数的左右极限及需要求左右极限的情形极限的…...

ESXI6.5安装教程
设置从IPMI Virtual Disk 3000启动,出现如下界面: 默认选择第一项,回车安装 安装程序正在检测服务器硬件信息,如果不满足系统安装条件会跳出错误提示。 检测完成之后会出现下面界面 回车 按F11 这里列出了服务器硬盘信息&#…...
2023-9-25 美团售后服务系统后端一面【2024秋招】
1 实习 1.1 讲讲你做的一个需求,为什么这么做之类的 答: 1.2 什么是接线 1.3 什么的初始接线,和权威接线 答:初始接线是现状,权威是规划中的 1.4 为什么要做比较呢? 答:运维人员需要查看…...

YOLOv5改进实战 | GSConv + SlimNeck双剑合璧,进一步提升YOLO!
前言 轻量化网络设计是一种针对移动设备等资源受限环境的深度学习模型设计方法。下面是一些常见的轻量化网络设计方法: 网络剪枝:移除神经网络中冗余的连接和参数,以达到模型压缩和加速的目的。分组卷积:将卷积操作分解为若干个较小的卷积操作,并将它们分别作用于输入的不…...
Redis之zset在异步队列上的应用
当遇到并发的客户端请求时,为了缓解服务端的处理压力,当请求对响应的处理的实时性要求不高时,可以实现一个异步的请求消息队列。 一种实现策略是使用redis的zset,将消息的到期处理时间作为score,然后用多个线程去轮训…...
day4:Node.js 核心库
day4:Node.js 核心库 文章目录 day4:Node.js 核心库常用工具模块util 模块Moment 模块Lodash 模块web模块文件模块path 模块常用工具模块 Node.js有许多常用的工具,以下是一些常见的: util: 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心 JavaScript 的功能…...
PHP非对称与对称双向加密解密的方式
目录 RSA非对称加密解密: 什么是RSA非对称加密解密解析: 解析: 为什么使用: 有什么优点: DEMO: AES、DES、3DES等对称加密解密: 解析: 为什么使用: 有什么优点: DEMO: RSA非对称加密解密: 什么是RSA非对称加密解密解析: 解析: RSA非对称加密…...

C++之struct匿名结构体实例(二百四十四)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...

npm publish发布到在线仓库时,提示:Scope not found
当npm publish发布时,控制台提示:Scope not found,具体错误信息如下: npm notice npm ERR! code E404 npm ERR! 404 Not Found - PUT https://registry.npmjs.org/xxx%2fxxx - Scope not found npm ERR! 404 npm ERR! 404 xxx/xx…...

AWS Lambda 操作 RDS 示例
实现目标 创建一个 Lambda 接收调用时传入的数据, 写入 RDS 数据库 Post 表存储文章信息. 表结构如下: idtitlecontentcreate_date1我是标题我是正文内容2023-10-21 15:20:00 AWS 资源准备 RDS 控制台创建 MySQL 实例, 不允许 Public access (后面 Lambda 需要通过 VPC 访问…...

【java爬虫】使用selenium获取某交易所公司半年报数据
引言 上市公司的财报数据一般都会进行公开,我们可以在某交易所的官方网站上查看这些数据,由于数据很多,如果只是手动收集的话可能会比较耗时耗力,我们可以采用爬虫的方法进行数据的获取。 本文就介绍采用selenium框架进行公司财…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...
文件上传漏洞防御全攻略
要全面防范文件上传漏洞,需构建多层防御体系,结合技术验证、存储隔离与权限控制: 🔒 一、基础防护层 前端校验(仅辅助) 通过JavaScript限制文件后缀名(白名单)和大小,提…...

Qwen系列之Qwen3解读:最强开源模型的细节拆解
文章目录 1.1分钟快览2.模型架构2.1.Dense模型2.2.MoE模型 3.预训练阶段3.1.数据3.2.训练3.3.评估 4.后训练阶段S1: 长链思维冷启动S2: 推理强化学习S3: 思考模式融合S4: 通用强化学习 5.全家桶中的小模型训练评估评估数据集评估细节评估效果弱智评估和民间Arena 分析展望 如果…...

Selenium 查找页面元素的方式
Selenium 查找页面元素的方式 Selenium 提供了多种方法来查找网页中的元素,以下是主要的定位方式: 基本定位方式 通过ID定位 driver.find_element(By.ID, "element_id")通过Name定位 driver.find_element(By.NAME, "element_name"…...