C++核心编程:类和对象 笔记
4.类和对象
- C++面向对象的三大特性为:封装,继承,多态
- C++认为万事万物都皆为对象,对象上有其属性和行为
例如:
- 人可以作为对象,属性有姓名、年龄、身高、体重...,行为有走、跑、跳、说话...
- 车可以作为对象,属性有轮胎、方向盘、车灯...,行为有载人、放音乐、开空调...
- 具有相同性质的对象,我们可以对其进行抽象,抽象为类,人属于人类,车属于车类
4.1 封装
4.1.1 封装的意义
- 封装是C++面向对象三大特性之一
- 封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义一:在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{访问权限: 属性 / 行为};
类中的属性和行为 我们统一称为成员
- 属性 -> 成员属性/成员变量
- 行为 -> 成员函数/成员方法
- 示例1:设计一个圆类,求圆的周长
- 类和对象-封装-属性和行为作为整体.cpp
#include <iostream>
using namespace std;
// 圆周率
const double PI = 3.1415926;
// 设计一个圆类,求圆的周长
// 圆求周长的公式 : 2 * PI * 半径
class Circle {// 访问权限// 公共权限
public:// 行为// 获取圆的周长double calculatePerimeter(double radius) {return 2 * PI * radius;}// 属性:半径int m_radius;
};
int main() {// 通过圆类,创建具体的圆(对象)// 实例化(通过一个类 创建一个对象的过程)Circle c1;c1.m_radius = 10;cout<<"圆的周长为 : "<<c1.calculatePerimeter(c1.m_radius)<<""<<endl;return 0;
}
- 示例2:设计学生类
#include <iostream>
using namespace std;
#include <string>
// 设计一个学生类,属性有姓名和学号
// 可以给姓名和学号赋值,可以显示学生的姓名和学号// 设计学生类
class Student{
public://公共权限// 类中的属性和行为 我们统一称为成员// 属性 -> 成员属性/成员变量// 行为 -> 成员函数/成员方法string m_name; // 姓名int m_Id; // 学号// 行为void setName(string name){ // 设置姓名m_name = name;}void setId(int id){ // 设置学号m_Id = id;}void display(){ // 显示姓名和学号cout << "姓名:" << m_name << endl;cout << "学号:" << m_Id << endl;}
};int main() {// 创建一个具体学生 实例化对象Student s1;// 给s1对象 进行属性赋值操作s1.m_name = "张三";s1.m_Id = 2019001;// 显示学生信息s1.display();s1.setName("李四");s1.setId(2019002);s1.display();
}
运行结果:
PS D:\Work\c++\build\bin> ."D:/Work/c++/bin/app.exe"
姓名:张三
学号:2019001
姓名:李四
学号:2019002
PS D:\Work\c++\build\bin>
封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- public 公共权限
- protected 保护权限
- private 私有权限
#include <iostream>
using namespace std;/*三种访问权限:公共权限 public 成员 类内可以访问 类外可以访问保护权限 protected 成员 类内可以访问 类外不可以访问(儿子可以访问父亲中的保护内容)私有权限 private 成员 类内可以访问 类外不可以访问(儿子不可以访问父亲中的私有内容)
*/class Person{
public:void func() {m_Name = "张三";//公共权限m_Car = "拖拉机";//保护权限m_Password = 123456;//私有权限}
public:// 公共权限string m_Name;//姓名
protected:// 保护权限string m_Car;//汽车
private:// 私有权限int m_Password;//密码
};int main() {// 实例化具体对象Person p1;p1.m_Name = "呵呵哒";//类外可以访问(public)// p1.m_Car = "保时捷";//保护权限内容,在类外访问不到 error:成员"Person::m_Car"不可访问// p1.m_Password = 123456;//私有权限内容,在类外访问不到 error:成员"Person::m_Password"不可访问p1.func();//类外可以访问(public)return 0;
}
4.1.2 struct和class区别
- 在C++中struct和class唯一的区别就是默认的访问权限不同
区别:
- struct默认权限是公共权限
- class默认权限是私有权限
#include <iostream>
using namespace std;class C1{int m_A;// 默认权限 是私有
};struct C2{int m_A;// 默认权限 是公共
};int main() {/*在C++中struct和class唯一的区别就是默认的访问权限不同区别:struct默认权限是公共权限class默认权限是私有权限*/C1 c1;// c1.m_A = 10;// error:成员"C1::m_A"不可访问C2 c2;c2.m_A = 100;// ok return 0;
}
4.1.3 成员属性设置为私有
- 优点1:将所有成员属性设置为私有,可以自己控制读写权限
- 优点2:对于写权限,我们可以检测到数据的有效性
演示:控制读写权限
#include <iostream>
using namespace std;
/*成员属性设置私有优点1:将所有成员属性设置为私有,可以自己控制读写权限优点2:对于写权限,我们可以检测到数据的有效性
*/
// 人类
class Person{
public:// 设置姓名void setName(string name) {m_Name = name;}// 获取姓名string getName() const {// 返回类型为const,表示返回的是常量,不可修改返回值。return m_Name;// 返回m_Name的值。}// 获取年龄int getAge() const {// 返回类型为const,表示返回的是常量,不可修改返回值。return m_Age;// 返回m_Age的值。}// 设置偶像void setIdol(string idol) {m_Idol = idol;}
private:string m_Name;// 姓名 可读可写int m_Age = 18;// 年龄 只读string m_Idol;// 偶像 只写
};int main() {Person p;// 姓名设置p.setName("张三");cout<<"姓名:"<<p.getName()<<endl;// 姓名:张三// 获取年龄cout<<"年龄:"<<p.getAge()<<endl;// 输出年龄:18// 偶像设置p.setIdol("迪丽热巴");return 0; // 程序执行成功,返回0
}
演示:检测到数据的有效性,例如年龄设置为0-150之间
#include <iostream>
using namespace std;
/*成员属性设置私有优点1:将所有成员属性设置为私有,可以自己控制读写权限优点2:对于写权限,我们可以检测到数据的有效性
*/
// 人类
class Person{
public:// 设置姓名void setName(string name) {m_Name = name;}// 获取姓名string getName() const {// 返回类型为const,表示返回的是常量,不可修改返回值。return m_Name;// 返回m_Name的值。}// 设置年龄 0-150void setAge(int age) {if (age < 0 || age > 150) {cout << "年龄: "<< age << ",输入错误!" << endl;// 输出错误信息。return;}m_Age = age;}// 获取年龄int getAge() const {// 返回类型为const,表示返回的是常量,不可修改返回值。return m_Age;// 返回m_Age的值。}// 设置偶像void setIdol(string idol) {m_Idol = idol;}
private:string m_Name;// 姓名 可读可写int m_Age = 18;// 年龄 只读string m_Idol;// 偶像 只写
};int main() {Person p;// 姓名设置p.setName("张三");cout<<"姓名:"<<p.getName()<<endl;// 姓名:张三// 获取年龄p.setAge(250);cout<<"年龄:"<<p.getAge()<<endl;// 输出年龄:18// 偶像设置p.setIdol("迪丽热巴");return 0; // 程序执行成功,返回0
}
练习案例1:设计立方体类
- 设计立方体类(Cube)
- 求出立方体的面积和体积
- 分别用全局函数和成员函数判断两个立方体是否相等
1.类和对象-封装-设计案例1-立方体类
#include <iostream>
using namespace std;/*立方体类设计1.创建立方体类2.设计属性3.设计行为 获取立方体面积和体积4.分别利用全局函数和成员函数 判断两个立方体是否相等
*/
class Cube{
public:// 设置长void setL(int l){m_L=l;}// 获取长int getL(){return m_L;}// 设置宽void setW(int w){m_W=w;}// 获取宽int getW(){return m_W;}// 设置高void setH(int h){m_H=h;}// 获取高int getH(){return m_H;}// 获取立方体面积int getArea(){return 2*(m_L*m_W+m_L*m_H+m_W*m_H);}// 获取立方体体积int getVolume(){return m_L*m_W*m_H;}// 利用成员函数判断两个立方体是否相等bool isSameByClass(Cube &c){if(getL() == c.getL() && getW() == c.getW() && getH() == c.getH()) {return true;}else{return false;}}
private:int m_L;//长int m_W;//宽int m_H;//高
};// 利用全局函数判断 两个立方体是否相等
bool isSame(Cube &c1,Cube &c2) {if(c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()) return true; // 判断长宽高是否相等,如果相等则返回true,否则返回falseelse return false;
}int main() {Cube c1,c2;c1.setL(5);c1.setW(4);c1.setH(3);c2.setL(10);c2.setW(10);c2.setH(10);cout<<"c1的面积为:"<<c1.getArea()<<endl;cout<<"c1的体积为:"<<c1.getVolume()<<endl;cout<<"***************************"<<endl;cout<<"c2的面积为:"<<c2.getArea()<<endl;cout<<"c2的体积为:"<<c2.getVolume()<<endl;// 判断c1和c2是否相等// 利用全局函数判断bool ret = isSame(c1,c2);if(ret) cout<<"利用全局函数判断:c1和c2相等"<<endl;else cout<<"利用全局函数判断:c1和c2不相等"<<endl;// 利用成员函数判断ret = c1.isSameByClass(c2);if(ret) cout<<"利用成员函数判断:c1和c2相等"<<endl;else cout<<"利用成员函数判断:c1和c2不相等"<<endl;return 0;
}
2.类和对象-封装-设计案例2-点和圆关系案例
#include <iostream>
using namespace std;// 点和圆关系案例// 点类
class Point{
public:// 设置xvoid setX(int x) {m_X = x;}// 获取xint getX() {return m_X;}// 设置yvoid setY(int y) {m_Y = y;}// 获取yint getY() {return m_Y;}
private:int m_X;int m_Y;
};// 圆类
class Circle {
public: // 设置半径void setR(int r) {m_R = r;}// 获取半径int getR() {return m_R;}// 设置圆心void setCenter(Point center) {m_Center = center;}// 获取圆心Point getCenter() {return m_Center;}
private:int m_R;// 半径// 在类中可以让另一个类 作为本类中的成员Point m_Center;// 圆心
};
// 判断点和圆关系
void isInCircle(Circle& c, Point& p) {// 计算两点之间距离 平方int distance = (c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());// 计算半径的平方int rDistance = c.getR() * c.getR();// 判断关系if(distance == rDistance) {cout<<"点在圆上"<<endl;}else if(distance < rDistance) {cout<<"点在圆内"<<endl; }else {cout<<"点在圆外"<<endl;}
}
int main() {// 创建圆Circle c;c.setR(10);Point center;center.setX(10);center.setY(0);c.setCenter(center);// 创建点Point p;p.setX(10);p.setY(9);// 判断关系isInCircle(c,p);return 0;
}
进一步完善项目
- point.h
#pragma once
// 点类
class Point{
public:// 设置xvoid setX(int x);// 获取xint getX();// 设置yvoid setY(int y);// 获取yint getY();
private:int m_X;int m_Y;
};
- point.cpp
#include "point.h"// 设置x
void Point::setX(int x) {m_X = x;}
// 获取x
int Point::getX() {return m_X;}
// 设置y
void Point::setY(int y) {m_Y = y;}
// 获取y
int Point::getY() {return m_Y;}
- circle.h
#pragma once
#include "point.h"
// 圆类
class Circle {
public: // 设置半径void setR(int r);// 获取半径int getR();// 设置圆心void setCenter(Point center);// 获取圆心Point getCenter();
private:int m_R;// 半径// 在类中可以让另一个类 作为本类中的成员Point m_Center;// 圆心
};
- circle.cpp
#include "circle.h"
// 设置半径
void Circle::setR(int r) {m_R = r;}
// 获取半径
int Circle::getR() {return m_R;}
// 设置圆心
void Circle::setCenter(Point center) {m_Center = center;}
// 获取圆心
Point Circle::getCenter() {return m_Center;}
4.2 对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置
4.2.1 构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
- 一个对象或者变量没有初始状态,对其使用后果是未知
- 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数写法:~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁时候会自动调用析构,无须手动调用,而且只会调用一次
#include <iostream>
using namespace std;// 对象的初始化和清理
// 1.构造函数 进行初始化操作
class Person {
public:// 1. 构造函数// 没有返回值 不用写void// 函数名 与类名相同// 构造函数可以有参数,可以发生重载// 创建对象的时候,构造函数会自动调用,而且只调用一次Person() {cout<<"Person 构造函数的调用"<<endl;}// 2.析构函数 进行清理的操作// 没有返回值 不写void// 函数名和类名相同 在名称前加~// 析构函数不可以有参数的,不可以发生重载// 对象在销毁前,会自动调用析构函数,而且只会调用一次~Person () {cout<<"Person 析构函数的调用"<<endl;}
};// 构造和析构都是必须有的实现,如果我们不提供,编译器会提供一个空实现的构造和析构
void test01() {Person p;// 在栈上的数据,test01执行完毕后,释放这个对象
}int main() {test01();return 0;
}
执行结果:
PS D:\Work\c++\bin> .\app
Person 构造函数的调用
Person 析构函数的调用
PS D:\Work\c++\bin>
未完待续~