黑马程序员学习笔记
4、类和对象
C++面向对象的三大特性为:封装、继承、多态
4.1 封装
封装的意义
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制(public 公共权限 protected 保护权限 private私有权限)
struct 和class区别
在C++中struct 和class区别唯一的区别就在于默认的访问权限不同
区别:
- struct默认权限为公共
- class默认权限为私有
class C1
{
int m_A; //默认是私有权限
};
struct C2
{
int m_A; //默认是公共权限
};
设计案例1—立方体类
案例描述:
设计立方体类(Cube),求出立方体的面积和体积,分别用全局函数和成员函数判断两个立方体是否相等。
#include<iostream>
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 isSameByClass(Cube& c)
{
if (m_L == c.getL() && m_W == c.getW() && m_H == c.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);
cout << "c1的面积为:" << c1.calculateS() << endl; //600
cout << "c1的体积为:" << c1.calculateV() << endl; //1000
//创建第二个立方体
Cube c2;
c2.setL(10);
c2.setW(10);
c2.setH(10);
//利用全局函数判断
bool ret1 = isSame(c1, c2);
if (ret1)
{
cout << "全局函数判断:c1和c2是相等的" << endl;
}
else
{
cout << "全局函数判断:c1和c2是不相等的" << endl;
}
//利用成员函数判断
bool ret2 = c1.isSameByClass(c2);
if (ret2)
{
cout << "成员函数判断:c1和c2是相等的" << endl;
}
else
{
cout << "成员函数判断:c1和c2是不相等的" << endl;
}
system("pause");
return 0;
}
设计案例2—点和圆的关系
案例描述:
设计一个圆形类(Circle)和一个点类(Point),计算点和圆的关系
第一种代码方式
#include <iostream>
using namespace std;
//点类
class Point
{
public:
//设置坐标x
void set_x(int x)
{
m_x = x;
}
//获取坐标x
int get_x()
{
return m_x;
}
//设置坐标y
void set_y(int y)
{
m_y = y;
}
//获取坐标y
int get_y()
{
return m_y;
}
private:
int m_x;
int m_y;
};
//圆类
class Circle
{
public:
//设置半径
void set_r(int r)
{
m_r = r;
}
//获取半径
int get_r()
{
return m_r;
}
//设置圆心
void set_center(Point center)
{
m_center = center;
}
//获取圆心
Point get_center()
{
return m_center;
}
private:
int m_r;
Point m_center;
};
void isIncircle(Circle& c, Point& p)
{
//计算两点之间距离的平方
int distance =
(c.get_center().get_x() - p.get_x()) * (c.get_center().get_x() - p.get_x()) +
(c.get_center().get_y() - p.get_y()) * (c.get_center().get_y() - p.get_y());
//计算半径的平方
int rdistance = c.get_r() * c.get_r();
if (distance == rdistance)
{
cout << "点在圆上" << endl;
}
else if (distance > rdistance)
{
cout << "点在圆内" << endl;
}
else
{
cout << "点在圆外" << endl;
}
}
int main()
{
//创建圆
Circle c;
c.set_r(10);
Point center;
center.set_x(10);
center.set_y(0);
c.set_center(center);
//创建点
Point p;
p.set_x(10);
p.set_y(10);
//判断关系
isIncircle(c, p);
system("pause");
return 0;
}
第二种代码方式
头文件 point.h
#pragma once
#include <iostream>
using namespace std;
//点类
class Point
{
public:
//设置坐标x
void set_x(int x);
//获取坐标x
int get_x();
//设置坐标y
void set_y(int y);
//获取坐标y
int get_y();
private:
int m_x;
int m_y;
};
源文件 point.cpp
# include "point.h"
//设置坐标x
void Point::set_x(int x)
{
m_x = x;
}
//获取坐标x
int Point::get_x()
{
return m_x;
}
//设置坐标y
void Point::set_y(int y)
{
m_y = y;
}
//获取坐标y
int Point::get_y()
{
return m_y;
}
头文件 circle.h
#pragma once
#include <iostream>
using namespace std;
#include "point.h"
//圆类
class Circle
{
public:
//设置半径
void set_r(int r);
//获取半径
int get_r();
//设置圆心
void set_center(Point center);
//获取圆心
Point get_center();
private:
int m_r;
Point m_center;
};
源文件 circle.cpp
#include "circle.h"
//设置半径
void Circle::set_r(int r)
{
m_r = r;
}
//获取半径
int Circle::get_r()
{
return m_r;
}
//设置圆心
void Circle::set_center(Point center)
{
m_center = center;
}
//获取圆心
Point Circle::get_center()
{
return m_center;
}
main.h
#include <iostream>
using namespace std;
#include "circle.h"
#include "point.h"
void isIncircle(Circle& c, Point& p)
{
//计算两点之间距离的平方
int distance =
(c.get_center().get_x() - p.get_x()) * (c.get_center().get_x() - p.get_x()) +
(c.get_center().get_y() - p.get_y()) * (c.get_center().get_y() - p.get_y());
//计算半径的平方
int rdistance = c.get_r() * c.get_r();
if (distance == rdistance)
{
cout << "点在圆上" << endl;
}
else if (distance < rdistance)
{
cout << "点在圆内" << endl;
}
else
{
cout << "点在圆外" << endl;
}
}
int main()
{
//创建圆
Circle c;
c.set_r(10);
Point center;
center.set_x(10);
center.set_y(0);
c.set_center(center);
//创建点
Point p;
p.set_x(10);
p.set_y(11);
//判断关系
isIncircle(c, p);
system("pause");
return 0;
}
4.2 对象的初始化和清理
构造函数和析构函数
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
C++利用了构造函数和析构函数解决对象的初始化和清理,这两个函数将会被编译器自动调用完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此,如果我们不提供构造和析构,编译器会提供,编译器提供的构造和析构函数是空实现。
浅拷贝和深拷贝
深拷贝和浅拷贝最根本的区别在于是否真的获取一个对象的复制实体,而不是引用。
假设B复制了A,修改A时,看B是否发生变化:
若B跟着也变了,就是浅拷贝!(修改堆区内存中的同一个值)
若B没有改变,就是深拷贝!(修改堆区内存中的不同的值)
浅拷贝只是增加了一个指针指向已存在的内存地址。
深拷贝是增加了一个指针并且申请了一个新的内存空间,使这个新增的指针指向这个新的内存。
利用深拷贝能够在释放堆区内存的时候,不出现浅拷贝时释放同一内存的非法操作。
#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 = 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;
}
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
思考:B类中有对象A作为成员,A为对象成员,那么在创建B对象时,A与B的构造和析构的顺序是谁先谁后?
当其他类对象作为本类成员,构造时先构造本类中的类对象,再构造本类自身,析构的顺序与构造相反,即先析构本类,再析构类对象。
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
-
静态成员变量
所有对象共享同一份数据 在编译阶段分配内存 类内声明,类外初始化
-
静态成员函数
所有对象共享同一个函数 静态成员函数只能访问静态成员变量
静态成员函数特点:
- 程序共享一个函数
- 静态成员函数只能访问静态成员变量
- 静态成员函数也是有访问权限的,私有的静态成员函数,类外访问不到
4.3 C++对象模型和this指针
成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
空对象占用内存空间为1。C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置。
this指针概念
我们知道在C++中,类内的成员变量和成员函数分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码,那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
- 在形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return * this
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//this指针指向 被调用的成员函数 所属的对象 这里的this指向p1
this->age = age;
}
//Person PersonAddAge(Person& p) 返回值的话,是创建一个新的对象
Person& PersonAddAge(Person& p) //返回引用,是返回p2
{
this->age += p.age;
return *this;
}
int age; //不用this指针的话,这里应该设置成int m_Age;
};
//1.解决名称冲突
void test01()
{
Person p1(18);
cout << "p1的年龄为:" << p1.age << endl;
}
//2.返回对象本身用*this
void test02()
{
Person p1(10);
Person p2(10);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年龄为:" << p2.age << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
空指针访问成员函数
C++ 中空指针也可以调用成员函数的,但是也要注意有没有用到this指针,如果用到this指针,需要加以判断保证代码的健壮性。
#include <iostream>
using namespace std;
class Person
{
public:
void showClassName()
{
cout << "This is Person class" << endl;
}
void showPersonAge()
{
if (this == NULL)
{
return;
}
cout << "age = " << this->m_Age << endl;
}
int m_Age;
};
void test01()
{
Person* p = NULL;
p->showClassName();
p->showPersonAge(); // 如没有this是否为空指针的判断,此行代码会报错
}
int main()
{
test01();
system("pause");
return 0;
}
const修饰成员函数
常函数:
- 成员函数后加const后我们称这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
4.4 友元
友元的关键字为 friend
友元的三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
全局函数做友元
#include <iostream>
using namespace std;
#include <string>
class Building
{
//goodGay全局函数是Building好朋友,可以访问Building中的私有成员
friend void goodGay(Building* building);
public:
Building()
{
m_sittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_sittingRoom;
private:
string m_BedRoom;
};
//全局函数做友元
void goodGay(Building* building)
{
cout << "好基友全局函数正在访问:" << building->m_sittingRoom << endl;
cout << "好基友全局函数正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
Building building;
goodGay(&building);
}
int main()
{
test01();
system("pause");
return 0;
}
类做友元
#include <iostream>
using namespace std;
#include <string>
class Building;
class GoodGay
{
public:
GoodGay();
void visit();//参观函数 访问Building中的属性
Building* building;
};
class Building
{
//GoodGay类是本类的好朋友,可以访问本类中私有成员
friend class GoodGay;
public:
Building(); //构造函数 类外实现
public:
string m_sittingRoom;
private:
string m_BedRoom;
};
//类外写成员函数
Building::Building()
{
m_sittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
//创建一个建筑物对象
building = new Building;
}
void GoodGay::visit()
{
cout << "好基友类正在访问:" << building->m_sittingRoom << endl;
cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
}
int main()
{
test01();
system("pause");
return 0;
}
成员函数做友元
#include <iostream>
using namespace std;
#include <string>
class Building;
class GoodGay
{
public:
GoodGay();
void visit1(); //可以访问Building中的私有成员
void visit2(); //不可以访问Building中的私有成员
Building* building;
};
class Building
{
//GoodGay类下的visit1成员函数是本类的好朋友,可以访问本类中私有成员
friend void GoodGay::visit1();
public:
Building(); //构造函数 类外实现
public:
string m_sittingRoom;
private:
string m_BedRoom;
};
//类外写成员函数
Building::Building()
{
m_sittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
//创建一个建筑物对象
building = new Building;
}
void GoodGay::visit1()
{
cout << "visit1函数正在访问:" << building->m_sittingRoom << endl;
cout << "visit1函数正在访问:" << building->m_BedRoom << endl;
}
void GoodGay::visit2()
{
cout << "visit2函数正在访问:" << building->m_sittingRoom << endl;
//cout << "visit2函数正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit1();
gg.visit2();
}
int main()
{
test01();
system("pause");
return 0;
}
4.5 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载实现两个自定义数据类型相加的运算
**左移运算符重载<<**可以输出自定义数据类型
递增运算符重载实现自己的整型数据
关系运算符重载可以让两个自定义类型对象进行对比操作
int a = 10;
cout << ++a << endl; // 11
cout << a << endl; // 11
int b = 10;
cout << b++ << endl; // 10
cout << b << endl; // 11
#include <iostream>
using namespace std;
//自定义整型
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//重载前置++运算符
MyInteger& operator++() //返回引用为了一直对一个数据进行递增操作
{
//先进行++运算
m_Num++;
//再将自身做返回
return *this;
}
//重载后置++运算符
MyInteger operator++(int) //int代表占位参数,可以用于区分前置和后置递增
{
//先 记录当时结果
MyInteger tmp = *this;
//后 递增
m_Num++;
//最后将记录的结果返回
return tmp;
}
private:
int m_Num;
};
//重载左移运算符
ostream& operator<<(ostream& cout, MyInteger myint)
{
cout << myint.m_Num;
return cout;
}
void test01()
{
MyInteger myint;
cout << ++(++myint) << endl;
cout << myint << endl;
}
void test02()
{
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
思考?上述代码中若写成这样,后置++实现报错,为什么?
ostream& operator<<(ostream& cout, MyInteger& myint)
赋值运算符重载
C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//重载 赋值运算符
Person& operator=(Person& p)
{
//m_Age = p.m_Age; 编译器提供的浅拷贝
//应该用深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return *this;
}
int* m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1;
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此成为仿函数
- 仿函数没有固定写法,非常灵活