一、思路
1、Shape类应该具有方法获取面积和类型
2、两个类Rectangle和Circle,它们都继承自类Shape
3、定义一个容器vector,用来放置建立的示例,并处理输入输出
4、对输入的类型进行判断,如果是"end", 终止程序,如果是图形,则建立对应的实例
5、遍历列表,并输出面积即可,
题目要求输出小数点后两位,可以引入 iomanip 库文件中的内容,当使用 fixed 时,浮点数会以固定小数点格式输出,setprecision() 函数用于设置输出浮点数的精度,即小数点后的位数, fixed << setprecision(2)表示输出小数点后两位。
二、知识点
1、类和面向对象
“对象”实际上是对现实世界中所存在的事物的一种抽象。
例如,在计算机世界中怎么表示“人”这个概念?
我们可以将其抽象为一个类Person, 并具有一些“属性”和“方法”。
- “属性”表示Person类所具有的特征,比如姓名、年龄、性别,通过这些特征,我们可以描述一个“人”的基本状态。
- “方法”表示Person类的行为和功能,比如吃饭、睡觉、行走,通过这些动作,我们可以描述一个“人”的动态行为。
只有创造一个类的实例(即对象),比如"张三,18, 男", "李明、20,男",才能真正的使用。
“类”是现实世界中的实体在计算机世界中的抽象概念,类可以看作是对象的模板,它定义了对象的结构和行为方式,可以用来创建具有相同属性和行为的多个对象,而对象是“类”的实现。
2、类的写法
C++使用class
定义一个类,并在类中定义成员变量和成员方法。
访问修饰符指定了成员变量和成员方法的可见性和访问权限。常用的修饰符包括 private(私有)
、public(公有)
和protected(受保护)
,
public
: 被修饰的成员在类的内部、派生类(子类)的内部和类的对象外部都可以访问。private
: 被修饰的成员只能在定义该成员的类的内部访问。protected
: 被修饰的成员只能在定义该成员的类的内部以及派生类汇总访问。
class MyClass {
public:
// 成员变量
int myAttribute;
// 成员方法
void myMethod() {
// 方法实现
}
};
int main() {
// 创建对象
MyClass obj;
// 访问属性
obj.myAttribute = 42;
// 调用方法
obj.myMethod();
return 0;
}
3、三大特性
总的来说,封装确保了对象中的数据安全,继承使得代码更加简洁,而且保证了对象的可扩展性,而多态则保证了程序的灵活性,不同的对象调用同一个方法有不同的响应。
1)封装
通过封装隐藏对象中一些不希望被外部所访问到的属性或方法
- 将对象的属性名,设置为
private
,只能被类所访问 - 提供公共的
get
和set
方法来获取和设置对象的属性
class Circle {
// 私有属性和方法
private:
// 将圆的半径设置为私有的
int radius;
// 公有属性和方法
public:
// set方法设置属性
void setRadius(int r) {
// 对输入的半径进行验证,只有半径大于0,才进行处理
if (r >= 0) {
radius = r;
} else {
cout << "半径不能为负数" << endl;
}
}
// get方法获取属性
int getRadius() {
return radius;
}
};
/*
* 使用封装,隐藏了类的一些属性,
* 具体的做法是使用get方法获取属性,使用set方法设置属性,
* 如果希望属性是只读的,则可以直接去掉set方法,
* 如果希望属性不能被外部访问,则可以直接去掉get方法。
* 此外还可以在读取属性和修改属性的同时做一些其他的处理
*/
2)继承
- 采用“继承”的方式使得一个类获取到其他类中的属性和方法。
- 在定义类时,可以在类名后指定当前类的父类(超类), 子类可以直接继承父类中的所有属性和方法,从而避免编写重复性的代码,此外我们还可以对子类进行扩展。
- 在子类和父类中都有getArea这个方法,这被称为方法的重写,方法的重写需要 override 关键字,其意思是子类重写父类的方法,并提供自己的实现。
// 图形的类Shape
class Shape {
protected:
string type; // 形状类型
public:
// 构造函数
Shape(const string& shapeType) : type(shapeType) {}
// 求面积的函数
// const表示该函数不会修改对象的状态,能保证对对象的访问是安全的
double getArea() const {
return 0.0;
}
// 获取形状类型
string getType() const {
return type;
}
};
/*
* 图形类拥有type属性和getArea、getType方法,
* 而子类在父类这些属性和方法的基础上新增了radius属性和getRadius方法,
* 并且在子类和父类中都有getArea这个方法,这被称为方法的重写,
* 方法的重写需要override关键字,
* 其意思是子类重写父类的方法,并提供自己的实现
*/
// 圆的类Circle,它继承自Shape类
class Circle : public Shape {
private:
// 圆的半径
int radius;
public:
// 构造函数, 调用Shape的构造函数,初始化了类型为"circle"
Circle(int circleRadius) : Shape("Circle"), radius(circleRadius) {}
// 重写基类的方法
// 圆的面积公式
double calculateArea() const override{
return 3.14 * radius * radius;
}
// 获取半径
int getRadius() const {
return radius;
}
};
3)多态
多态和继承有着紧密的关系,多态允许不同的对象使用相同的接口进行操作,但在运行时表现出不同的行为。
多态性使得可以使用基类类型的指针或引用来引用派生类的对象,从而在运行时选择调用相应的派生类方法。
C++中实现多态性的方法是通过 virtual
虚函数
注意:virtual在父类中定义了一个虚函数,而 = 0 表示这是一个纯虚函数,
即定义的函数在基类中没有实现,但是要求它的派生类都必须提供这个函数的实现,
这种抽象的方法使得 父类成为一个抽象基类,不能被实例化,
只能被用作派生其他类的基类
// 父类 Shape
class Shape {
public:
/*
* virtual在父类中定义了一个虚函数,
* 而 = 0 表示这是一个纯虚函数,
* 即定义的函数在基类中没有实现,但是要求它的派生类都必须提供这个函数的实现,
* 这种抽象的方法使得 Shape 类成为一个抽象基类,不能被实例化,
* 只能被用作派生其他类的基类
*/
virtual double calculateArea() const = 0;
};
// 两个派生类 Circle 和 Rectangle 重写了 calculateArea 方法,
// 它们提供了各自的实现,有着不同的计算逻辑。
// 子类1、Circle
class Circle : public Shape {
private:
int radius;
public:
double calculateArea() const override {
return 3.14 * radius * radius;
}
};
// 子类2、Rectangle
class Rectangle : public Shape {
private:
int width;
int height;
public:
// 构造函数,用于初始化 width 和 height
Rectangle(int w, int h) : width(w), height(h) {}
// width * height 的结果是整数,但 calculateArea 方法的返回类型是 double
// 为了确保结果是一个浮点数,使用 static_cast<double> 将其显式转换为 double 类型
double calculateArea() const override {
return static_cast<double>(width * height);
}
};
int main() {
// 创建了一个容器shapes,包含不同类型的图形对象
std::vector<Shape*> shapes;
shapes.push_back(new Rectangle(4, 5));
shapes.push_back(new Circle(3));
/*
* 循环遍历该容器并为每一个shape对象调用 calculateArea 方法,
* 尽管方法名称相同,但实际调用的方法是根据对象的类型而动态确定的,
* 这就是多态的概念
*/
for (const Shape* shape : shapes) {
std::cout << "Area: " << shape->calculateArea() << std::endl;
}
return 0;
}
4、构造函数
类的构造函数(类似于结构体的构造函数),用于初始化对象的成员变量,
构造函数与类同名,没有返回类型,并且在对象创建时自动调用。
其基本语法包括:
- 函数名:与类名相同
- 参数列表:可以有零个或多个参数,用于在创建对象时传递初始化信息。
- 函数体: 用于执行构造函数的初始化逻辑。
const string& personName表示对string类型对常量引用,可以传递字符串参数,但是不能在函数中修改这个参数的值
class Person {
private:
int age;
string name;
public:
// 默认构造函数
Person() {
age = 20;
name = "Tom"
}
// 法1:带参数的构造函数
Person(int personAge, const string& personName) {
age = personAge;
name = personName
}
}
int main() {
// 使用默认构造函数创建对象
Person person1;
// 使用带参数的构造函数创建对象
Person person2(20, "Jerry");
return 0;
}
// 法2:构造函数的成员初始化列表写法
// 这种写法允许在进入构造函数主体之前对类成员进行初始化
Person(int personAge, const string& personName) : age(personAge), name(personName) {
}
三、代码
#include <iostream>
#include <vector>
#include <string>
// 引入iomanip库文件,用于控制输出格式
#include <iomanip>
using namespace std;
// Shpe类
class Shape {
public:
// 定义计算面积和获取类型的函数为纯虚函数
virtual double CalculateArea() const = 0;
virtual string GetType() const = 0;
};
class Rectangle : public Shape {
public:
// 初始化参数列表
Rectangle(int width, int height) : width(width), height(height) {}
// 计算长方形面积,将整数转为浮点数
double CalculateArea() const override {
return static_cast<double>(width * height);
}
// 获取图形的形状
string GetType() const override {
return "Rectangle";
}
// 属性:宽度和高度
private:
int width;
int height;
};
class Circle : public Shape {
public:
// 初始化参数列表
Circle(int radius) : radius(radius) {}
// 计算圆的面积
double CalculateArea() const override {
return 3.14 * radius * radius;
}
// 获取图形形状
string GetType() const override {
return "Circle";
}
// 属性:半径
private:
int radius;
};
int main() {
// 定义一个容器,容纳 shape对象
vector<Shape*> shapes;
while (true) {
string type;
// 获取输入的 type类型
cin >> type;
if (type == "end") {
break;
}
if (type == "rectangle") {
// 获取输入的宽度和高度
int width, height;
cin >> width >> height;
// 新建Rectangle对象
shapes.push_back(new Rectangle(width, height));
} else if (type == "circle") {
int radius;
// 获取输入的半径
cin >> radius;
// 新建 Radius 对象
shapes.push_back(new Circle(radius));
}
}
// 输出结果,控制小数位数为两位
for (const Shape* shape : shapes) {
// shape对象调用同一个方法,有不同的处理逻辑
cout << shape->GetType() << " area: " << fixed << setprecision(2) << shape->CalculateArea() << endl;
}
return 0;
}
四、回顾
(一)语言知识
- 变量和常量
- 数据类型(基本数据类型和数据类型转换)
- 函数的组成部分
- 输入输出流
- C++标准库
- 循环结构:for循环、while循环、do while循环
- 条件结构:if 、else
- 运算符:关系运算符、逻辑运算符、算术运算符、赋值运算符
- 自增和自减、累加操作
- 取模运算
break
和continue
- 循环嵌套
- 数组、容器、字符串的概念、特点、声明、访问和操作方法
printf
函数、getline()
函数、getchar()
函数flag
编程思想- 字符大小的比较
- 函数的定义和使用、形参和实参
- 引用
&
- 结构体
- 指针
- 构造函数
- new运算符
- 箭头语法
set
的概念、特点和基本操作map
的概念、特点和基本操作- 迭代器
iterator
const
限定符pair
类型- 范围for循环
stack
的概念、特点和基本操作queue
的概念、特点和基本操作
(二)数据结构
此外,我们还接触了一些基本的数据结构,它们会在后面的刷题之旅中经常使用。
- 数组(一维数组、二维数组)
- 字符串
- 链表
- 哈希表
- 栈
- 队列