C++面向对象的三大特性为:封装,继承,多态
C++认为万事万物都皆为对象,对象上有其属性和行为
Eg:人可以作为一个对象,属性有姓名,年龄,身高,体重…行为有走,跑,跳…
车也可以作为对象,属性有轮胎,方向盘,车灯…行为有载人,放音乐,放空调
具有相同性质的对象,我们可以抽象为类,人属于人类,车属于车类
封装是C++面向对象三大特性之一
封装的意义:1. 将属性和行为作为一个整体,表现生活中的事物
将属性和行为加以权限控制
封装的意义一:在设计类的时候,属性和行为写在一起,表示事物,
语法:class 类名{ 访问权限 :属性 / 行为 };
#include
using namespace std;
const double PI = 3.14;class Circle {//访问权限,这里先暂时不解释
public://属性int m_r;//行为double calculate2C() {return 2 * PI * m_r;}
};int main()
{//通过一个圆类创建具体的圆(类)//实例化,通过一个类来创建一个对象的过程Circle c1;//给对象进行赋值c1.m_r = 10;//圆的周长公式 C = 2 * PI * rcout << "圆的周长为:" << c1.calculate2C() << endl;system("pause");return 0;
}
//设计一个学生类,属性有姓名学号,可以给姓名学号赋值,可以显示学生的姓名和学号
#include
using namespace std;
class Student {//访问权限
public://属性(成员属性或成员变量)string m_Name;int m_Id;//行为(成员函数或者成员方法)void setName(string name) {m_Name = name;}void setID(int id) {m_Id = id;}void showStudent() {cout << "学生姓名:" << m_Name << "\n学生学号: " << m_Id << endl;}};
int main()
{Student s1;s1.setName("xiaochen");s1.setID(118);s1.showStudent();
}
封装意义二:类在进行设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
1.public 公共权限:类内可以访问,类外可以访问
2.protected 保护权限:类内可以访问,类外不可以访问,儿子可以访问父亲中的保护内容
3.private 私有权限:类内可以访问,类外不可以访问,儿子不可以访问儿子的私有内容
#include
using namespace std;
class Person {
public:string m_Name;
protected:string car;
private:string password;
public:void func(){m_Name = "陈赢";car = "拖拉机";password = "123456";}
};
int main()
{Person p1;p1.m_Name = "小陈";cout << p1.m_Name << endl;//p1.car="benchi";//p1.password="qwert";这两个属性都是访问不到的,因为这两个属性的访问权限收限制
}
在C++中struct和class唯一的区别就是在于默认访问的权限不同
struct默认的访问权限为公共的,class默认访问的权限是私有的
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
成员的访问有权限的控制,可以通过将属性进行私有化,在利用成员函数公共化,类内元素可以访问的条件,就可以达到这样的效果
#include
#include
using namespace std;
class Person
{
public://可读成员方法string getName() {return m_Name;}//可写成员方法void setName(string name) {m_Name = name;}//只读函数int getAge() {return m_Age;}void setAge(int age) {if (age < 0 || age > 150) {cout << "你输入的年龄有问题,请重新输入" << endl;m_Age = 0;return;}m_Age = age;}//只写函数void setLover(string lover) {m_Lover = lover;}private:string m_Name;//可读可写int m_Age;//只读string m_Lover;//只写};
int main()
{Person p1;p1.setName("小陈");p1.setLover("小刘");p1.setAge(200);cout << "年龄:" << p1.getAge() << endl;cout << "姓名:" << p1.getName() << endl;system("pause");return 0;
}
封装案例1:
问题:设计一个类的立方体,可以计算立方体的面积和体积,并且分别用全局函数和成员函数进行判断两个立方体是否相同
#include
using namespace std;
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 calculcateS() {return m_L * m_W * 2 + m_L * m_H * 2 + m_W * m_H * 2;}//计算立方体的体积int calculcateV() {return m_L * m_W * m_H;}//成员函数判断bool isSameByClass(Cube& c) {if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH()) {return true;}elsereturn 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;}return false;
}
int main()
{//创建第一个立方体Cube c1;c1.setL(10);c1.setW(10);c1.setH(10);cout << "第一个立方体的面积为:" << c1.calculcateS() << endl;cout << "第一个立方体的体积为:" << c1.calculcateV() << endl;//创建第二个立方体Cube c2;c2.setL(10);c2.setW(10);c2.setH(10);cout << "第二个立方体的面积为:" << c2.calculcateS() << endl;cout << "第二个立方体的体积为:" << c2.calculcateV() << endl;//调用全局函数判断bool ret = isSame(c1, c2);if (ret) {cout << "全局函数:两个立方体相等" << endl;}else {cout << "全局函数:两个立方体的不相等" << endl;}//调用成员函数判断int ret_class = c1.isSameByClass(c2);if (ret_class) {cout << "成员函数调用:两个立方体相等" << endl;}else {cout << "成员函数调用:两个立方体不相等" << endl;}system("pause");return 0;}
总结:在进行全局函数和成员函数判断的时候,成员函数可以只传入一个参数,本身又调用自己的成员函数
注意代码的规范,每一步细化
封装案例2:
总结:在类中可以让另一个类作为本来中的成员
在这个案例中,存在每个类进行分类并且用头文件和cpp 文件进行分开操作,在后续的修改更加方便
//创建点和圆的类,判断圆和点的关系#include
#include
using namespace std;
class Point {//成员的函数
public://设置x的坐标void setX(int x) {m_X = x;}//获取x的坐标int getX() {return m_X;}//设置y的坐标void setY(int y) {m_Y = y;}//获取y的坐标int getY() {return m_Y;}//成员的属性
private:int m_X;int m_Y;
};class Circle {//成员的函数,设置获取半径的大小,计算两点之间的距离,设置P点
public://设置半径的大小void setR(int r) {m_R = r;}//获取半径的大小int getR() {return m_R;}//设置圆心的大小void setCenter(Point m) {center = m;}//获取圆心的大小Point getCenter() {return center;}//成员的属性:圆的半径和圆心,计算两者之间的距离
private:int m_R;Point center;int d;
};void calculateDistance(Circle& O, Point& p) {//计算两点之间的距离int distance =(O.getCenter().getX() - p.getX()) * (O.getCenter().getX() - p.getX()) +(O.getCenter().getY() - p.getY()) * (O.getCenter().getY() - p.getY());//计算半径的平方int rdistance = O.getR() * O.getR();if (distance == rdistance) {cout << "点在圆上" << endl;}else if (distance > rdistance){cout << "点在圆外" << endl;}else {cout << "点在圆内" << endl;}
}int main()
{//创建一个P点Point p1;p1.setX(10);p1.setY(10);cout << "P点的x坐标为:" << p1.getX() << endl;cout << "P点的y坐标为:" << p1.getY() << endl;//创建圆心Point m;m.setX(10);m.setY(0);cout << "圆心X坐标为:" << m.getX() << endl;cout << "圆心Y坐标为:" << m.getY() << endl;//创建一个圆Circle O1;O1.setR(10);O1.setCenter(m);cout << "圆的半径为:" << O1.getR() << endl;calculateDistance(O1, p1);system("pause");return 0;
}
(1)、构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
(2)、析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象的时候会自动调用构造,无须手动调用,且只会调用一次
析构函数:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前加上~
3.构造函数不可以有参数,因此不可以发生重载
4.程序在对象的时候会自动调用构造,无须手动调用,且只会调用一次
构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
#include
using namespace std;
class Person {
public:Person(){cout << "Person的构造函数" << endl;}~Person(){cout << "Person的析构函数" << endl;}
};//构造函数和析构函数都是自动会调用的,在进行销毁的时候,主要看数据存放的位置
//例如这个函数存放在栈区,在调用完这个函数的时候就会自动进行销毁,所以进能够看到调用析构函数void test01()
{Person p;
}int main()
{Person p1;//test01();system("pause");return 0;
}
两种分类的方式:
按参数分:有参构造和无参构造
按类型分:普通构造和拷贝构造
三种调用的方式:括号法,显示法,隐式转换法
#include
using namespace std;
class Person {
public://构造函数Person() {cout << "Person的无参构造函数" << endl;}Person(int a){age = a;cout << "Person的有参构造函数" << endl;}//拷贝构造函数Person(const Person& p) {age = p.age;cout << "Person的拷贝构造函数" << endl;}//析构函数~Person() {cout << "Person的析构函数调用" << endl;}private:int age;
};void test01() {//括号法Person p1;//构造函数无参调用Person p2(10);//构造函数有参调用Person p3(p2);//拷贝构造函数调用//注意事项1:调用默认构造函数的时候,不能加 (),因为加上编译器就会认为这是一个函数声明//显示法Person p4;Person p5 = Person(10);//有参构造Person p6 = Person(p5);//拷贝构造//隐式转换法//Person(10);这个是属于匿名对象,特点:当前执行结束后,系统就会立即回收掉匿名对象//注意事项2:不要利用拷贝构造函数来初始化匿名对象,编译器会认为这个是一个对象的声明//隐式转换法Person p7 = 10;//相当于写了Person p4=Person(10);有参构造Person p8 = p7;//拷贝构造}int main()
{test01();system("pause");return 0;
}
C++中拷贝构造函数调用的时机通常由三种情况:
使用一个已经创建完毕的对象来初始化一个新对象
值传递的方式给函数参数传值
以值的方式返回局部对象
#include
using namespace std;
class Person {
public://构造函数Person() {cout << "Person的无参构造函数" << endl;}Person(int a){age = a;cout << "Person的有参构造函数" << endl;}//拷贝构造函数Person(const Person& p) {age = p.age;cout << "Person的拷贝构造函数" << endl;}//析构函数~Person() {cout << "Person的析构函数调用" << endl;}public:int age;
};//使用一个已经创建完毕的对象来初始化一个新对象void test01() {Person p1(20);Person p2(p1);cout << "p2的年龄: " << p2.age << endl;
}// 值传递的方式给函数参数传值
//值传递就相当于在栈区开辟一块新的空间,拷贝过去所有的值,但是本身的值是不会改变的,他们所指的不是同一个地址
void doWork(Person p) {}void test02() {Person p;doWork(p);
}//以值的方式返回局部对象Person doWork2() {Person p1;return p1;
}void test03() {Person p = doWork2();
}int main()
{//test01();//test02();test03();system("pause");return 0;
}
默认情况下:C++的编译器至少给一个类添加3个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
构造函数的调用规则如下:
如果用户定义有参构造函数,C++不在提供默认无参构造函数,但是会提供默认拷贝函数
如果用户定义拷贝构造函数,C++不会提供默认其他构造函数
深浅拷贝是一个常见的坑
浅拷贝:简单赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include
using namespace std;
class Person {
public:Person() {cout << "Person的无参构造函数" << endl;}Person(int age, int height) {m_Age = age;m_Height = new int(height);cout << "Person的有参构造函数" << endl;}Person(const Person& p) {cout << "Person的拷贝构造函数" << endl;m_Age = p.m_Age;m_Height = new int(*p.m_Height);}~Person() {if (m_Height != NULL){delete m_Height;m_Height = NULL;}cout << "Person的析构函数" << endl;}int m_Age;int* m_Height;
};void test01()
{Person p1(18, 160);cout << "p1的年龄:" << p1.m_Age << "p1的身高:" << endl;Person p2(p1);cout << "p2的年龄:" << p2.m_Age << "p2的身高:" << endl;
}
int main()
{test01();system("pause");return 0;
}
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
因为浅拷贝仅仅只是简单的赋值,但是当数据是一个地址,并且拷贝的时候肯定是指向同一个地址,但是在进行删除操作的时候,就有一个找不到,因为已经被删除了,他们指的是同一片空间,但是在自己写的拷贝函数就是两个相同的存放地址的元素,虽然值相等,但是他们所指的空间是不同的
作用:C++提供初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)…{}
#include
using namespace std;
class Person {
public:Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}void printPerson() {cout << "mA=" << m_A << endl;cout << "mB=" << m_B << endl;cout << "mC=" << m_C << endl;}private:int m_A;int m_B;int m_C;};
int main()
{Person p(30, 20, 10);p.printPerson();system("pause");return 0;
}
7.类对象作为类成员
C++类中的成员可以是另一个类的对象,我们就称该对象为成员对象
总结:通过实验,我深刻领会类与对象的区别,类实现数据隐藏与封装的原理,并对C++编程有了更进一步的理解。掌握了声明类的方法,类和类的成员概念以及定义对象的方法,也掌握了成员函数的实现与调用方法,掌握引检查和调试基于对象的程序的方法。在学习类与对象的时候,了解写程序又两种方法,一个是面向过程,一个是面向对象,其中面向过程就是直接通过主函数或者借用其他函数,在主函数中直接写算法程序,但是面向对象的程序,就是要先创建对象,例如老师让我们定义一个学生,在处理学生的信息程序上面,就需要考虑学生的信息,学号,班级,成绩等等,然后在定义出学生这一类后,在进行对类操作的函数,面向对象的方法程序,只需要在主函数定义一个变量,直接调用这个变量项目下的程序即可进行对其他数据的操作。面向对象还有三个特性,封装,继承,多态。其中学习的封装,就是封闭的程序,也就是把客观的事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的则进行信息隐蔽。