文章目录
类和对象的使用的知识点
C++面向对象的三大特性为:封装,继承,多态
C++认为万事万物都皆为对象,对象上有其属性和行为
Eg:人可以作为一个对象,属性有姓名,年龄,身高,体重…行为有走,跑,跳…
车也可以作为对象,属性有轮胎,方向盘,车灯…行为有载人,放音乐,放空调
具有相同性质的对象,我们可以抽象为类,人属于人类,车属于车类
封装
封装的意义
封装是C++面向对象三大特性之一
封装的意义:1. 将属性和行为作为一个整体,表现生活中的事物
将属性和行为加以权限控制
封装的意义一:在设计类的时候,属性和行为写在一起,表示事物,
语法:class 类名{ 访问权限 :属性 / 行为 };
#include <iostream>
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 * r
cout << "圆的周长为:" << c1.calculate2C() << endl;
system("pause");
return 0;
}
//设计一个学生类,属性有姓名学号,可以给姓名学号赋值,可以显示学生的姓名和学号
#include <iostream>
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 <iostream>
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";这两个属性都是访问不到的,因为这两个属性的访问权限收限制
}
struct和class区别
在C++中struct和class唯一的区别就是在于默认访问的权限不同
struct默认的访问权限为公共的,class默认访问的权限是私有的
成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
成员的访问有权限的控制,可以通过将属性进行私有化,在利用成员函数公共化,类内元素可以访问的条件,就可以达到这样的效果
#include <iostream>
#include <string>
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 <iostream>
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;
}
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;
}
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 <iostream>
#include <math.h>
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 <iostream>
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 <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;
}
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 <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;
}
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 <iostream>
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 <iostream>
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++编程有了更进一步的理解。掌握了声明类的方法,类和类的成员概念以及定义对象的方法,也掌握了成员函数的实现与调用方法,掌握引检查和调试基于对象的程序的方法。在学习类与对象的时候,了解写程序又两种方法,一个是面向过程,一个是面向对象,其中面向过程就是直接通过主函数或者借用其他函数,在主函数中直接写算法程序,但是面向对象的程序,就是要先创建对象,例如老师让我们定义一个学生,在处理学生的信息程序上面,就需要考虑学生的信息,学号,班级,成绩等等,然后在定义出学生这一类后,在进行对类操作的函数,面向对象的方法程序,只需要在主函数定义一个变量,直接调用这个变量项目下的程序即可进行对其他数据的操作。面向对象还有三个特性,封装,继承,多态。其中学习的封装,就是封闭的程序,也就是把客观的事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的则进行信息隐蔽。