C++面向对象的三大特性:封装,继承,多态
万事万物皆可为对象,有其相应的属性和行为
一、封装
1.1 封装的意义
·将属性和行为作为一个整体,表现生活中的事物
·将属性和行为加以权限控制
·在设计类的时候,属性和行为写在一起,表现事物
1.2 语法
class 类名{ 访问权限:属性 / 行为};
eg1.设计一个圆类
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;//设计一个圆类,求圆的周长:2*PI*半径
const double PI = 3.14;
//class代表一个类,类后面紧跟就是类名称
class Circle {//访问权限
public://公共权限//属性int m_r;//半径//行为,通常用函数double calculateZC() {return 2 * PI * m_r;}
};int main() {//通过圆类 创建具体的圆//实例化 (通过一个类 创建一个对象的过程)Circle cl;//给圆对象的属性进行赋值cl.m_r = 10;cout << "圆的周长:" << cl.calculateZC() << endl;system("pause");return 0;
}
eg2.设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,也可以显示
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;class Student {//类中的属性和行为都属于 成员//属性 成员属性 成员变量//行为 成员函数 成员方法
public://属性string m_Name;//姓名int m_id;//学号//行为void showStudent() {cout << "姓名:" << m_Name << "学号:" << m_id << endl;}//给姓名赋值void setName(string name) {m_Name = name;}//给学号赋值void setId(int id) {m_id = id;}
};int main() {//实例化Student s1;//赋值s1.m_Name = "张三";s1.m_id = 123;s1.showStudent();Student s2;s2.setName("李四");//通过函数赋值s2.setId(321);s2.showStudent();system("pause");return 0;
}
1.3 访问权限
类在设计时,可以把属性和行为放在不同的权限下加以控制
1.public 公共权限 成员类内可以访问,类外可以访问
2.protected 保护权限 成员类内可以访问,类外不可以访问,可以继承
3.private 私有权限 成员类内可以访问,类外不可以访问,不可以继承
eg.
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;class Persson {
public://公共属性string m_Name;
protected://保护权限string m_Car;
private://私有权限int m_Password;public:void func() {m_Name = "张三";m_Car = "拖拉机";m_Password = 123456;//这里类内都可以访问}
};int main() {//实例化Persson p1;p1.m_Name = "李四";//p1.m_Car = "奔驰";//保护权限内容,类外无法访问//p1.m_Password = 132;//私有权限内容,类外无法访问p1.func();system("pause");return 0;
}
1.4 struct和class区别
在C++中struct和class唯一的区别在于 默认的访问权限不同
·struct默认权限为公共
·class默认权限为私有
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;class C1 {int m_A;//默认权限 私有
};struct C2 {int m_A;//默认权限 公共
};int main() {//struct 默认权限为公共//class 默认权限为私有C1 c1;//c1.m_A = 100;//会报错的,类外无法访问C2 c2;c2.m_A = 100;//成功system("pause");return 0;
}
1.5 成员属性设置为私有
优点1:将所有成员设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;class Person {
public://姓名的设置void setName(string name) {m_Name = name;}string getName() {return m_Name;}void setAge(int age) {//检测优点2的,与开始只读不冲突哈if (age < 0 || age>100) {cout << "年龄"<<age<<"有误" << endl;return;}m_Age = age;}int getAge() {return m_Age;}void SetIdol(string idol) {m_Idol = idol;}
private:string m_Name;//姓名 可读可写int m_Age = 18;//年龄 只读 也可以写在0~100之间string m_Idol;//偶像 只写
};int main() {Person p;//姓名p.setName("张三");cout << "姓名:" << p.getName() << endl;//年龄//p.setAge(20);//报错//p.m_Age = 20;//报错//后面加的p.setAge(15);cout << "年龄:" << p.getAge() << endl;//偶像p.SetIdol("小米");//cout << "偶像:" << p.SetIdol() << endl;//报错,只写状态system("pause");return 0;
}
eg1.设计立方体类,求出其面积和体积,分别用全局函数和成员函数判断两个立方体是否相等
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
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 calculateS() {return 2 * m_L * m_W + 2 * m_L * m_H + 2 * m_H * m_W;}//获取体积int calculateV() {return m_L * m_W * m_H;}//成员函数判断两立方体是否相等bool isSameByClass(Cube &c) {if (m_L == c.getL() && m_H == c.getH() && m_W == c.getW()) {return true;}else {return false;}}
private:int m_L;int m_H;int m_W;
};//利用全局函数判断 两个立方体是否相等
bool isSame(Cube& c1, Cube& c2) {if (c1.getH() == c2.getH() && c1.getL() == c2.getL() && c1.getW() == c2.getW()) {return true;}return false;
}int main() {Cube c1;c1.setH(10);c1.setL(10);c1.setW(10);cout << "c1的面积为:" << c1.calculateS() << endl;cout << "c1的体积为:" << c1.calculateV() << endl;Cube c2;c2.setH(10);c2.setL(10);c2.setW(10);//利用全局函数判断bool ret = isSame(c1, c2);if (ret) {cout << "全局函数判断相等" << endl;}else {cout << "全局函数判断不想等" << endl;}//利用成员函数判断ret = c1.isSameByClass(c2);if (ret) {cout << "成员函数判断相等" << endl;}else {cout << "成员函数判断不想等" << endl;}system("pause");return 0;
}
eg2.点和圆的关系,设计圆类和点类
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;class Point {
public:void setX(int x) {m_X = x;}int getX() {return m_X;}void setY(int y) {m_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 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(11);isInCircle(c, p);system("pause");return 0;
}
在这里我们把两个类分开写,写法如下:
文件包括
其中头文件
circle.h
#pragma once
#include <iostream>
#include "point.h"
using namespace std;class Circle {
public:void setR(int r);int getR();void setCenter(Point center);Point getCenter();private:int m_R;Point m_Center;
};
point.h
pragma once //防止头文件重复包含
#include <iostream>
using namespace std;class Point {
public:void setX(int x);int getX();void setY(int y);int getY();private:int m_X;int m_Y;
};
circle.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
point.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "point.h"void Point::setX(int x) {m_X = x;
}
int Point::getX() {return m_X;
}
void Point::setY(int y) {m_Y = y;
}
int Point::getY() {return m_Y;
}
run.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
#include "circle.h"
#include "point.h"//判断点和圆的关系
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(11);isInCircle(c, p);system("pause");return 0;
}
头文件只有声明函数,相对应的文件实现函数功能,注意加上类的作用域
二、对象的初始化和清理
2.1 构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其只用后果是未知的,同样的使用完后不清理,也会造成安全问题
C++利用构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象的初始化和清理工作。是强制要我们做的,如果我们不提供构造函数和析构函数,编译器会提供空实现的函数。
·构造函数:主要作用于创建对象时为对象的成员赋值,构造函数由编译器自动调用
·析构函数:主要作用于在对象销毁前系统自动调用,执行一些清理功能
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用一次构造函数
析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前加~
3.构造函数不可以有参数,因此不可以发生重载
4..程序在销毁对象前会自动调用一次析构函数
eg.
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;class Person {
public://构造函数,可以有参数Person() {cout << "Person 构造函数的调用" << endl;}//析构函数~Person() {cout << "Person 析构函数的调用" << endl;}};void test01() {Person p;//创建在栈上,test01执行完毕后,会释放这个对象
}int main() {test01();Person q;//这里只有创建,没有释放system("pause");//在这里按下时才会释放qreturn 0;
}
2.2 构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造 无参又称为默认构造函数
按类型分为:普通构造和拷贝构造
三中调用方式:
括号法、显示法、隐式转换法
eg.
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
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;}int age;};//调用
void test01() {//1.括号法Person p1;//默认构造函数的调用Person p2(10);//有参构造函数Person p3(p2);//拷贝构造函数//注意事项// 调用默认构造函数的时候,不要加() Person p1();会认为是一个函数的声明//cout << "p2的年龄:" << p2.age << endl;//cout << "p3的年龄:" << p3.age << endl;//2.显示法Person p4;Person p5 = Person(10);//有参构造Person p6 = Person(p2);//拷贝构造//注意事项://Person(10);匿名对象 特点:当前行执行结束后,系统会立即回收掉//所以你单独写时,会调用构造函数,并立刻销毁//不要利用拷贝构造函数初始化匿名对象 Person(p3) 编译器会认为Person(p3) == Person p3;//3.隐式转换法Person p7 = 10;//相当于 写了 Person p7 = Person(10);有参构造Person p8 = p7;//拷贝构造}int main() {test01();system("pause");return 0;
}
2.3 拷贝构造函数调用的时机
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给构造函数参数传值
3.以值方式返回局部对象(注意新版VS中已经不再适用,不会再调用,两地址相同了)
eg.
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;class Person {
public:Person() {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) {//test02的p传给了这里的p就进行了拷贝
}void test02() {Person p;doWork(p);}//3.以值方式返回局部对象,注意新版VS中已经不再适用,不会再调用,两地址相同了
Person doWork2() {Person p1;cout << (int*)&p1 << endl;return p1;
}void test03() {Person p = doWork2();cout << (int*)&p << endl;
}int main() {//test01();//test02();test03();system("pause");return 0;
}
2.4 构造函数的调用规则
1.默认构造函数(无参,函数体为空)
2..默认析构函数(无参,函数体为空)
3..默认拷贝构造函数,对属性值进行值拷贝
构造函数调用规则如下:
1.如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造函数
2.如果用户定义默认拷贝构造,c++不会提供其他构造函数
eg.
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;class Person {
public:Person() {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;
}int main() {//test01();//如果不写拷贝构造,就会实现拷贝构造的空实现//如果用户定义有参构造函数,c++不再提供默认无参构造//如果用户定义默认拷贝构造,c++不会提供其他构造函数system("pause");return 0;
}
2.5 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间进行拷贝操作
浅拷贝带来的问题
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;class Person {
public:Person() {cout << "Person默认构造函数调用" << endl;}Person(int age,int height) {cout << "Person有参构造函数调用" << endl;m_Age = age;m_Height = new int(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.m_Height<<endl;Person p2(p1);//编译器给其做了一个浅拷贝cout << "p2的年龄为:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;
}int main() {test01();system("pause");return 0;
}
如上面代码所示,我并没有写拷贝构造,而是编译器自己进行默认拷贝,即为浅拷贝,在int m_Height上没有问题,但是在指针变量int *m_Height上,因为是在堆区上创建的,在该堆区地址上进行赋值,p1和p2指的是同一个地址,而p1,p2入栈后,p2先出来释放了该堆区地址,p1出来再次释放,从而导致错误
所以要利用深拷贝进行解决,即在堆区新开辟一个空间,代码如下
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;class Person {
public:Person() {cout << "Person默认构造函数调用" << endl;}Person(int age,int height) {cout << "Person有参构造函数调用" << endl;m_Age = age;m_Height = new int(height);}Person(const Person &p) {cout << "Person拷贝构造函数调用" << endl;m_Age = p.m_Age;//m_Height = p.m_Height;编译器默认实现的代码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.m_Height<<endl;Person p2(p1);//编译器给其做了一个浅拷贝cout << "p2的年龄为:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;
}int main() {test01();system("pause");return 0;
}
2.6 初始化列表
作用:
c++提供了初始化列表语法,用来初始化属性
语法:
构造函数():属性1(值1),属性2(值2)...{}
eg.
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
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(10, 20, 30);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;
}
2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称为 对象成员
class A{};
class B{A a;
};
B类中有对象A作为成员,A为对象成员,那么创建B的时候,A与B的构造与析构顺序?
显然是A。当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构入栈顺序不变,先进去类对象再进去自身,所以自身先出来,类对象后出来,看上去顺序相反
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Phone {
public:Phone(string pName) {m_PName = pName;cout << "Phone构造函数调用" << endl;}~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("张三", "华为");cout << p.m_Name << "拿着" << p.m_Phone.m_PName << endl;
}int main() {test01();system("pause");return 0;
}
2.8 静态成员
静态成员就是在成员函数前加上关键字static,称为静态成员
静态成员分为:
1.静态成员变量
1.1所有对象共享同一份数据
1.2在编译阶段分配内存
1.3类内声明,类外初始化
2.静态成员函数:
2.1所有对象共享同一函数
2.2静态成员函数只能访问静态成员变量
eg静态成员变量.
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using 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;
}
eg静态成员函数.
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Person {//静态成员函数只能访问静态成员变量
public:static void func() {m_A = 100;//m_B = 200;会报错 静态成员函数 不可以访问 非静态成员变量//无法区分到底是哪个对象的m_B属性cout << "static void func调用" << endl;}static int m_A;int m_B;//静态成员函数也是有访问权限的
private:static void func() {m_A = 200;//m_B = 200;会报错 静态成员函数 不可以访问 非静态成员变量//无法区分到底是哪个对象的m_B属性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;
}