C++ 面向对象的三大特性:封装、继承、多态
C++ 认为万事万物都皆为对象,对象上有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高、体重…, 行为有走、跑、跳、吃饭、唱歌…
车也可以作为对象,属性有轮胎、方向盘、车灯…,行为有载人、放音乐、放空调…
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
封装是 C++ 面向对象三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法: `class 类名{ 访问权限:属性 / 行为 };
示例1 设计一个圆类,求圆的周长
示例代码:
#includeusing namespace std; //圆周率 const double PI = 3.14; // 设计一个圆类,求圆的周长 // 圆求周长的公式 :2 * PI * 半径 // class代表设计一个类,类后面紧跟着的就是类名称 class Circle { // 访问权限 // 公共权限 public: // 属性 // 半径 int m_r; // 行为 //获取圆的周长 double calculateCircumference() { return 2 * PI * m_r; } }; int main() { // 通过圆类 创建具体的圆(对象) // 实例化 (通过一个类 创建一个对象的过程) Circle c1; // 给圆对象 的属性进行赋值 c1.m_r = 10; // 2 * PI * 10 = 62.8 cout << "圆的周长为:" << c1.calculateCircumference() << endl; system("pause"); return 0; }
示例2: 设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
示例2代码:
// 设计学生类
class Student {
public: // 公共权限
// 属性
string m_Name; // 姓名
int m_Id; // 学号
// 行为
// 显示姓名和学号
void showStudent() {
cout << "姓名: " << m_Name << " t学号: " << m_Id << endl;
}
// 给姓名赋值
void setName(string name) {
m_Name = name;
}
// 给学号赋值
void setId(int id) {
m_Id = id;
}
};
int main(){
// 创建一个具体的学生 实例化
Student s1;
// 给s1对象 进行属性赋值操作
s1.setName("张三");
s1.setId(1);
s1.showStudent();
Student s2;
s2.m_Name = "李四";
s2.m_Id = 2;
s2.showStudent();
system("pause");
return 0;
}
类中的属性和行为,统一称为 成员
属性:成员变量
行为:成员函数、成员方法
封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- public 公开权限
- protected 保护权限
- private 私有权限
成员在类内可以访问
4.1.2 struct 和 class 区别在 C++ 中 struct 和 class 的成员的区别就在于 默认的访问权限不同
区别:
- struct 默认权限为公共
- class 默认权限为私有
#include4.1.3 成员属性设置为私有using namespace std; class C1 { int m_A; // 默认 私有 }; struct S1 { int m_A; // 默认 公共 }; int main() { // struct 和 class 区别 // struct 默认权限是 公共 public // class 默认权限是 私有 private C1 c1; //c1.m_A = 100; // 错误 S1 s1; s1.m_A = 100; system("pause"); return 0; }
优点1: 将所有成员属性设置为私有,可以自己控制读写权限
优点2: 对于写权限,我们可以检测数据的有效性
示例:
#include#include using namespace std; // 成员属性设置为私有 // 1、可以自己控制读写权限 // 2、对于写可以检测数据的有效性 //设计一个人类 class Person { public: // 设置姓名 void setName(string name) { m_Name = name; } // 获取姓名 string getName() { return m_Name; } //获取年龄 可读可写 如果想修改(年龄的范围必须是 0~150 之间) void setAge(int age) { if (age < 0 || age >150) { m_Age = 0; return; } m_Age = age; } int getAge() { return m_Age; } //设置情人 void setLover(string lover) { m_Lover = lover; } private: // 姓名 可读可写 string m_Name; // 年龄 可读 可写 int m_Age; // 情人 只写 string m_Lover; }; int main() { Person p; p.setName("张三"); cout << "p的姓名为:" << p.getName() << endl; p.setAge(18); cout << "p的年龄为:" << p.getAge() << endl; p.setLover("苍井"); }
联系案例1:设计立方体类
设计立方体类(Cube)
求出立方体的面积和体积
分别用全局函数和成员函数判断两个立方体是否相等
#include#include 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 calculateS() { return 2 * m_L * m_W + 2 * m_W * m_H + 2 * m_L * m_H; } // 获取立方体体积 int calculateV() { return m_L * m_W * m_H; } // 利用成员函数判断两个立方体是否相等 bool isSameTo(Cube &x) { if (m_L == x.getL() && m_W == x.getW() && m_H == x.getH()) { return true; } 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; } return false; } int main() { // 创建立方体对象 Cube c1; c1.setL(10); c1.setW(10); c1.setH(10); //面积为600,体积为1000 cout << "c1的面积为:" << c1.calculateS() << endl; cout << "c1的体积为:" << c1.calculateV() << endl; // 创建第二个立方体 Cube c2; c2.setL(10); c2.setW(10); c2.setH(10); bool ret = isSame(c1, c2); if (ret) { cout << "c1和c2是相等的" << endl; } else { cout << "c1和c2是不相等的" << endl; } bool ret2 = c1.isSameTo(c2); if (ret2) { cout << "c1和c2是相等的" << endl; } else { cout << "c1和c2是不相等的" << endl; } system("pause"); return 0; }
练习案例2:点和圆的关系
设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系。
#includeusing 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 { 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 rSqure = c.getR() * c.getR(); //判断关系 if (distance == rSqure) { cout << "点在圆上" << endl; } else if (distance > rSqure) { 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); system("pause"); return 0; }
4.2 对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置。
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名() {}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
括号法
显式法
隐式转换法
示例:
#include4.2.3 拷贝构造函数调用时机using namespace std; //构造函数的分类及调用 //分类 // 按照参数分类 无参构造(默认构造) 有参构造 // 按照类型分类 普通高走 拷贝构造 class Person { public: //构造函数 Person() { age = 0; 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; } int age; }; // 调用 void test01() { // 1、括号法 Person p; // 默认构造函数 Person p2(10); //有参构造函数 Person p3(p2); //拷贝构造函数 //注意事项 // 调用默认构造函数的时候,不要加() // 因为下面这行代码编译器会认为是一个函数的声明,不会认为是在创建对象 Person p4(); void func(); //cout << "p2的年龄为:" << p2.age << endl; //cout << "p3的年龄为:" << p3.age << endl; // 2、显式法 Person p5 = Person(); Person p6 = Person(10); //有参构造 Person p7 = Person(p5); //拷贝构造 //Person(10); // 匿名对象:当前行执行结束后,系统会立即回收掉匿名对象 //cout << "aaaaa" << endl; //注意事项2 //不要利用拷贝构造函数 初始化匿名对象,编译器会认为Person(P5)等价于 Person p5;对象声明 //Person(p5); // 3、隐式转换法 Person p8 = 10; // 相当于写了 Person p8 = Person(10); Person p9 = p8; // 相当于写了 Person p9 = Person(p8); } int main() { test01(); system("pause"); return 0; }
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
示例:
#include4.2.4 构造函数调用规则using namespace std; // 拷贝构造函数调用时机 // 1、使用一个已经创建完毕的对象来初始化一个新对象 // 2、 值传递的方式给函数参数传值 // 3、 值的方式返回局部的对象 class Person { public: Person() { m_Age = 0; cout << "Person默认构造函数调用" << endl; } Person(int age) { cout << "Person有参构造函数调用" << endl; m_Age = age; } Person(const Person& p) { cout << "Person拷贝构造函数调用" << endl; m_Age = p.m_Age; } ~Person() { cout << "Person析构函数调用" << endl; } int m_Age; }; // 1、使用一个已经创建完毕的对象来初始化一个新对象 void test01(){ Person p1(20); Person p2(p1); cout << "p2的年龄为:" << p2.m_Age << endl; } // 2、值传递的方式给函数参数传值 void doWork(Person p) { } void test02() { Person p; doWork(p); } // 3、 值方式返回局部对象 Person doWork2() { //cout << "doWork2()" << endl; Person p1; cout << "(int*)&p1为:" << (int*)&p1 << endl; cout << "&p1为:" << &p1 << endl; //cout << "*&p1为" << *(&p1) << endl; //错误 //cout << "2222222222222" << endl; return p1; } void test03() { Person p = doWork2(); cout << "(int*)&p为:" << (int*)&p << endl; } int main() { //test01(); //cout << "test02" << endl; //test02(); test03(); system("pause"); }
默认情况下,C++ 编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其它构造函数
示例:
#include4.2.5 深拷贝与浅拷贝using namespace std; // 构造函数的调用规则 // 1、创建一个类,C++ 编译器会给每个类都添加至少3个函数 // 默认构造 (空实现) // 析构函数 (空实现) // 拷贝构造 (值拷贝) // 2、 // 如果我们写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造 // 如果我们写了拷贝构造函数,编译器就不再提供其它普通构造函数了 class Person { public: //Person() { // m_Age = 0; // cout << "Person的默认构造函数调用" << endl; //} //Person(int age) { // cout << "Person的有参构造函数调用" << endl; // m_Age = age; //} Person(const Person& p){ cout << "Person的拷贝构造函数调用" << endl; m_Age = p.m_Age; } ~Person() { cout << "Person的析构函数调用 " << endl; } int m_Age; }; //void test01() { // Person p; // p.m_Age = 18; // // Person p2(p); // // cout << "p2的年龄为:" << p2.m_Age << endl; //} void test02() { //Person p; //Person p(20); Person p2(p); cout << "p2的年龄为:" << p2.m_Age << endl; } int main() { //test01(); test02(); system("pause"); return 0; }
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
示例:
#includeusing namespace std; // 深拷贝与浅拷贝 class Person { public: Person() { m_Age = 0; cout << "Person的默认构造函数调用 " << endl; } Person(int age, int height){ m_Age = age; m_Height = new int(height); cout << "Person的有参构造函数调用 " << endl; } ~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.m_Height << endl; Person p2(p1); cout << "p2的年龄为:" << p2.m_Age << "身高为:" << *p2.m_Height << endl; } int main() { test01(); system("pause"); return 0; }
浅拷贝带来的问题是堆区的内存重复释放
浅拷贝带来的问题要利用深拷贝来解决
深拷贝:
代码:
// 自己实现拷贝构造函数来解决浅拷贝带来的问题
Person(const Person& p) {
cout << "Person 拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height; 编译器默认的代码
//深拷贝操作
m_Height = new int(*p.m_Height);
}
这样就不会出现重复释放某块地址的内存的问题了
如果属性有在堆区开辟的,一定要自己提供拷贝函数,防止浅拷贝带来的问题
4.2.6 初始化列表
作用:
C++ 提供了初始化列表语法,用来初始化属性
语法: 构造函数() :属性1(值1),属性2(值2)...{}
示例:
#include4.2.7 类对象作为类成员using namespace std; // 初始化列表 class Person { public: // 传统初始化操作 //Person(int a, int b, int c) { // m_A = a; // m_B = b; // m_C = c; //} // 初始化列表初始化属性 Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) { } int m_A; int m_B; int m_C; }; void test01() { Person p(30,20,10); cout << "m_A = " << p.m_A << endl; cout << "m_B = " << p.m_B << endl; cout << "m_C = " << p.m_C << endl; } int main() { test01(); system("pause"); return 0; }
C++ 类中的成员可以是另一个类的对象,我们称该成员为 对象成员
例如:
class A {}
class B {
A a;
}
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
示例:
#include#include using namespace std; // 类对象作为类成员 // 手机类 class Phone { public: Phone(string pName) { cout << "Phone的构造函数调用" m_PName = pName; } // 手机品牌名称 string m_PName; }; // 人类 class Person { public: // Phone m_Phone = pName;//隐式转换法 Person(string name, string pName):m_Name(name),m_Phone(pName) { cout << "Person的构造函数调用" } // 姓名 string m_Name; // 手机 Phone m_Phone; }; void test01() { Person p("张三", "苹果MAX"); cout << p.m_Name << "拿着" << p.m_Phone.m_PName << endl; } int main() { test01(); system("pause"); return 0; }
结果:
先构造成员类对象,再构造自己的对象
析构呢?
#include#include using namespace std; // 类对象作为类成员 // 手机类 class Phone { public: Phone(string pName) { cout << "Phone的构造函数调用" << endl; m_PName = pName; } ~Phone() { cout << "Phone的析构函数调用" << endl; } // 手机品牌名称 string m_PName; }; // 人类 class Person { public: // Phone m_Phone = pName;//隐式转换法 Person(string name, string pName) :m_Name(name), m_Phone(pName) { cout << "Person的构造函数调用" << endl; } ~Person(){ cout << "Person的析构函数调用" << endl; } // 姓名 string m_Name; // 手机 Phone m_Phone; }; void test01() { Person p("张三", "苹果MAX"); cout << p.m_Name << "拿着" << p.m_Phone.m_PName << endl; } int main() { test01(); system("pause"); return 0; }
析构和构造相反
先先析构自己,再调用成员类的析构
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化 - 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
示例1: 静态成员变量
#includeusing namespace std; // 静态成员变量 class Person { public: // 1 所有对象都共享同一份数据 // 2 编译阶段就分配了内存 // 3 类内声明,类外初始化操作 static int m_A; // 静态成员变量也是有访问权限的 private: static int m_B; }; int Person::m_A = 100; int Person::m_B = 200; void test01() { Person p; cout << p.m_A << endl; Person p2; p2.m_A = 200; cout << p.m_A << endl; } void test02() { // 静态成员变量 不属于某个对象上,所有对象都共享同一份数据 // 因此静态成员变量有两种访问方式 // 1、通过对象进行访问 Person p; cout << p.m_A << endl; // 2、通过类名进行访问 cout << Person::m_A << endl; //cout << Person::m_B << endl; 类外访问不到私有的静态成员变量 } int main() { //test01(); test02(); system("pause"); return 0; }
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
示例2: 静态成员函数
#includeusing namespace std; class Person { public: // 静态成员函数 static void func() { m_A = 100; // 静态成员函数是可以访问静态成员变量 // m_B = 200; // 静态成员函数 不可以访问 非静态成员变量,无法区分是哪个对象的属性 cout << "static void func调用" << endl; } static int m_A; // 静态成员变量 int m_B; // 非静态成员变量 // 静态成员函数也是有访问权限的 private: static void func2() { cout << "static void func2的调用" << endl; } }; int Person::m_A = 0; // 有两种访问方式 void test01() { //1、通过对象进行访问 Person p; p.func(); //2、通过类名访问 Person::func(); // Person::func2(); 错误的,类外访问访问不到私有的静态成员函数 } int main() { test01(); system("pause"); return 0; }



