C++面向对象的三大特性:封装、继承、多态。具有相同性质的对象,抽象为类。
1 封装
1.1 封装的意义(一)
- 将属性和行为作为一个整体,表现为生活的事物
- 将属性和行为加以权限控制
语法:class 类名 { 访问权限 : 属性/行为}
示例:设计一个圆类,求圆的周长
/* 设计一个圆类,求圆的周长 圆的周长公式: 2 * PI * 半径 */ const double PI = 3.14; class Circle { //访问权限 public: //1、属性 //半径 int m_r; //2、行为 //获取圆的周长 double calculateZC() { return 2 * PI * m_r; } }; int main() { system("color 1E"); //通过圆类,创建具体的圆c ==>>实例化(通过一个类实例化一个具体的对象) Circle c1; //给圆对象的属性赋值 c1.m_r = 10; cout << "圆的周长:" << c1.calculateZC() << endl; system("pause"); return 0; }
示例2:设计一个学生类,属性:姓名、学号。给学生姓名和学号赋值,然后显示出姓名和学号
class Student { public: // 访问权限 ->公共权限 //1、属性 :成员属性,成员变量 string s_name; int stuId; //2、行为 :成员函数 成员方法 void showStudent() { cout << "学生的姓名:" << s_name << " 学号是:" << stuId << endl; } //给姓名赋值 void setName(string name) { s_name = name; } //给学号赋值 void setId(int id) { stuId = id; } }; int main() { system("color 1E"); //通过学生类,创建具体那个学生 ==>>实例化(通过一个类实例化一个具体的对象) Student stu1; Student stu2; //给学生对象的属性赋值 stu1.setName ("张三"); stu1.setId( 21202201); stu1.showStudent(); stu1.s_name = "李四"; stu1.stuId = 21202202; stu1.showStudent(); system("pause"); return 0; }
1.2 封装的意义(二)
说明:类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限三种:
- public:公共权限
- 成员 :类内可以访问,类外也可以访问
- protected:保护权限
- 成员: 类内可以访问,类外不可以访问
- 儿子可以访问父亲的保护内容
- private:私有权限
- 成员:类内可以访问,类外不可以访问
- 儿子不可以访问父亲的私有内容
class Person { public: string m_name; protected: string m_Car; private: int m_Password; public: void func() { m_name = "张三"; m_Car = "保时捷"; m_Password = 12345; } };
1.3 struct 和 class区别
说明:在C++中struct 和class唯一的区别在于默认的访问权限不同
区别:
- struct:默认权限为公共
- class:默认权限为私有
class C1 { int m_a; //默认权限 私有 }; struct C2 { int m_a; //默认权限 公共 }; C1 c1; //c1.m_a = 100; //报错 C2 c2; c2.m_a = 100; //公共权限,类内类外都可以访问
1.4 成员属性设置为私有
优点:
- 将所有成员属性设置为私有之后,自己可以控制读写权限
- 对于写权限,检测数据的有效性
#include<iostream> #include<algorithm> #include<string> using namespace std; /* 成员属性设置为私有 1、可以自己控制读写权限 --某些属性的读写 2、对于以检测数据的有效性 --对于年龄的特殊设置 */ //设计人类 class Person { public: //设置姓名 void setName(string name) { m_Name = name; } //获取姓名 string getName() { return m_Name; } int setAge(int age) { m_Age = 0; if (age < 0 || age>150) { cout << "你输入的年龄有误,默认设置为0." << endl; return m_Age; } m_Age = age; } int getAge() { return m_Age; } void setLower(string lover) { m_Lover = lover; } private: //姓名 可读可写 string m_Name; //年龄 只读 int m_Age; //情人 只写 string m_Lover; }; int main() { system("color 1E"); Person p; p.setName("唐三"); cout << "输入的姓名:" << p.getName()<<endl; //检测数据的有效性 p.setAge(1000); cout << "输入的年龄:" << p.getAge()<<endl; //年龄只读 p.setLower("小舞"); //情人只能可写 return 0; }
练习案例:
1 设计立方体类
求出立方体的面积和体积。分别用全局函数和成员函数判断两个立方体是否相等
#include<iostream> #include<algorithm> #include<string> using namespace std; /* 立方体的类的设计 1、创建立方体类 2、设计属性 3、设计行为 获取立方体面积和体积 4、分别利用全局函数和成员函数 判断两个立方体是否相等 */ class Cube { public: void setL(double l) { m_L = l; } void setW(double w) { m_W = w; } void setH(double h) { m_H = h; } double getL() { return m_L; } double getW() { return m_W; } double getH() { return m_H; } //获取立方体的面积 double getArea() { double area; area = 2 * (m_H*m_L + m_H * m_W + m_L * m_W); return area; } //获取立方体的体积 double getVolu() { double volume; volume = m_H * m_L * m_W ; return volume; } //利用成员函数判断两个立方体是否相等 bool isSameByClass(Cube &c) { if (m_L = c.m_L && m_H == c.m_H && m_W == c.m_W) { return true; } return false; } private: double m_L, m_W, m_H; }; //全局函数判断两个函数是否相等 bool isSameByClasss(Cube &c1, Cube &c2) { if (c1.getL() == c2.getL() && c1.getH() == c2.getH() && c1.getW() == c2.getW()) { return true; } return false; } int main() { system("color 1E"); Cube c,c1,c2; c.setL(1); c.setW(2); c.setH(3); c1.setL(2); c1.setW(2); c1.setH(2); c2.setL(2); c2.setW(2); c2.setH(3); cout << "立方体的面积:" << c.getArea() << endl; cout << "立方体的体积:" << c.getVolu() << endl; bool gouRet = isSameByClasss(c1, c2); if (gouRet == true) { cout << "全局函数的两个立方体是相等的" << endl; } else cout << "全局函数的两个立方体是不相等的" << endl; bool ret = c1.isSameByClass(c2); if (ret == true) { cout << "两个立方体是相等的" << endl; } else cout << "两个立方体是不相等的" << endl; return 0; }
2 对象的初始化和清理
- 生活中有些电子产品都有出厂设置
- 每个对象初始设置以及对象销毁前的清理数据的设置
2.1 构造函数和析构函数
对象的初始化和清理是重要的安全问题
一个对象或者变量没有初始状态,对其使用后果也是未知的
同样,使用完一个对象或变量,没有及时清理也会造成 一定的安全问题
C++利用构造函数和析构函数解决上述问题,这两个函数会被编译器自动调用 ,完成对象初始化和清理工作。若是不提供构造和析构,编译器会提供构造函数和析构函数是空实现的(自己不写,有人写)
构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动
析构函数:作用于对象销毁前自动调用,执行一些清理 工作。
构造函数语法: 类名(){ }
- 1 构造函数,没有返回值也不写void
- 2 函数名称与类明相同
- 3 构造函数可以有参数,因此可以重载
- 4 程序在调用对象的时候会自动调用构造,无需 手动调用,且只调用一次
析构函数语法 :~类名(){ }
- 1 析构函数,没有返回值不写void
- 2 函数名称与类名相同,在名称前面加上符合 ~
- 3 析构函数不可以有参数,因此不能发送重载
- 4 程序在对象销毁前会自动调用析构,无需手动调用,只调用一次
class Person { public: Person() { cout << "正在构造函数!! " << endl; } ~Person() { cout << "正在析构函数!! " << endl; } }; void test01() { Person p; } int main() { test01(); return 0; }
2.2 构造函数的分类及调用
两种分类方式:
- 按照参数分:有参构造和无参构造
Person() { cout << "无参的构造函数调用!! " << endl; } Person(int a) { age = a; cout << "有参的构造函数调用!! " << endl; }
按照类型分:普通构造和拷贝构造
//拷贝构造函数 /* 定义:若是有张三这个人,然后用李四去克隆张三这个人的属性 不过张三本身属性不能改变,加上 const只读 */ Person(const Person &p) { age = p.age; }
三种调用方式:
- 括号法(一般这个比较好 )
注意:若是无参的函数构造不要写"p1()";这样会被认为是函数的声明
//1、括号法 Person p1; //有参构造函数 Person p2(10); cout << "p2的年龄为:" << p2.age << endl; //拷贝构造函数 Person p3(p2); cout << "p3的年龄为:" << p3.age << endl;
- 显示法
注意:Person(10)若是在左侧,则是匿名对象;执行结束后,系统立马回收匿名对象函数
Person p1; //有参构造函数 Person p2 = Person(10); //拷贝构造函数 Person p3 = Person(p2);
隐式转换法
Pensor p4=10; //相当于写了Person p4=Person(10) 有参构造 Pensor p5=p3; //拷贝构造
2.3 拷贝构造函数调用时机
C++拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个对象
- 值传递的方式给函数参数传值
void doWork(Person p) { } void test() { Person p; //默认构造函数 doWork(p);//拷贝构造函数,给函数的p, }
以值的方式返回局部对象
Person doWork() { Person p1; return p1; //返回p1的时候,也会产生一个拷贝构造函数。返回会重新生成一个跟p1一样的数据 } void test() { Person p = doWork(); }
2.4 构造函数调用规则
默认情况下,只要创建类,C++编译器至少给应该类添加3个函数
- 默认构造函数(无参,为空)
- 默认析构函数(无参,为空)
- 默认拷贝析构函数,对属性的值进行拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不提供默认无参构造,但会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不再提供其他构造函数(有参、无参)
2.5 深拷贝与浅拷贝(面试提及)
- 浅拷贝:简单的赋值拷贝(编译器做)-》会导致堆区的内存重复释放
- 深拷贝:在堆区重新申请空间,进行拷贝操作
class Person { public: int age; int * Height; //构造函数 Person() { cout << "无参的构造函数调用!! " << endl; } Person(int a,int height) { age = a; //用new在堆区创建一个对象 Height = new int(height); cout << "有参的构造函数调用!! " << endl; } //自己实现拷贝构造函数 解决浅拷贝带来的问题 Person(const Person &p) { cout << "Person 拷贝构造函数调用" << endl; age = p.age; //Height = p.Height; 编译器默认实现这行代码 //在堆区重新申请空间,进行深拷贝 Height = new int(*p.Height); } //析构函数 ~Person() { //释放堆区创建的内存 if (Height != NULL) { delete Height; Height = NULL; } //作用:析构代码,将堆区开辟数据做释放 cout << "正在析构函数!! " << endl; } }; //调用 void test01() { //p1走自己的析构,p2也是自己的析构 Person p1(18, 180); cout << "p1的年龄为:" << p1.age << " 身高为:" << *p1.Height << endl; //*表示指针的实际化,输出的是一个值,不是地址 Person p2(p1); cout << "p2的年龄为:" << p2.age << " 身高为:" << *p2.Height << endl; }
说明:若是使用指针就会在堆区使用内存空间,p1和p2两个对象,然后两个对象指向同一块内存;delete是释放对象指向的内存,这里用浅拷贝会被释放两次。
2.6 初始化列表
说明:C++提供初始化列表语法,用来初始化属性
语法:构造类名():属性1(值1),属性2(值2) . . . { }
1)传统初始化列表:
class Person { public: int m_A; int m_B; int m_C; //传统列表赋初值 Person(int a, int b, int c) { m_A = a; m_B = b; m_C = c; } }; void test() { Person p(10, 20, 30); cout << "m_A = " << p.m_A << endl; cout << "m_A = " << p.m_B << endl; cout << "m_A = " << p.m_C << endl; }
改进之后的:
Person(int a,int b,int c):m_A(a),m_B(b),m_C(c) { }
2.7 类对象作为类成员
说明:C++类中的成员可以是另一个类的对象,称为该成员为对象成员。当其他类对象作为本类对象,构造的时候先构造其他类对象,再构造自身对象。析构的时候先释放自身内存 ,再释放其他对象内存。
//创建手机的类 class Phone { public: string m_Pname; Phone(string name) { m_Pname = name; cout << "正在调用手机类的构造函数" << endl; } ~Phone() { cout << "正在调用手机类的析构函数" << endl; } }; class Person { public: string m_Name; Phone m_Phone; Person(string name, string phone):m_Name(name),m_Phone(phone) { cout << "正在调用Person类的构造函数" << endl; } ~Person() { cout << "正在调用Person类的析构函数" << endl; } }; //调用 void test01() { Person p("张三", "苹果14pro"); cout << p.m_Name << "拿着:" << p.m_Phone.m_Pname << "手机" << endl; }
2.8 静态成员
静态成员就是想在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
1)静态成员变量
所有对象共享一份数据(若是p1.a=100,若是p2也调用,p2.a=200,则p1.a=200;a是共享的)
在编译阶段分配内存
类内声明,类外初始化(在类外声明一个类内定义的属性,初始化:int Person::a=100),类外访问不到私有静态成员变量。
私有成员变量访问不到
class Person { public: static int m_Age; }; int Person::m_Age = 100; Person p; //访问方式一:通过对象 cout << p.m_Age << endl; ->100 //访问方式二:通过类名 cout<<Person::m_Age<<endl; ->100 Person p1; p1.m_Age = 200; cout << p.m_Age << endl; ->200 //修改了
2)静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量,非静态成员变量函数,无法区分到底是那个对象的属性;而静态成员变量所有人共享一份,不属于任何人。
- 私有成员函数访问不到