目录
c++面向对象三大特性:封装、继承、多态。
6.1 封装
6.1.1 封装的意义
1. 将属性和行为作为一个整体。(放在一个class里面)
2. 将属性和行为加以权限控制。
public公共权限:类内外都可以访问
protected保护权限: 类外不可以访问
private私有权限: 类外不可以访问
//定义一个圆类
#define PI 3.14
//class 定义一个类 circle是类的名字
class circle
{
//访问权限:公共权限
public:
//属性
int r;
//行为
double circumference( )
{
return r * PI * 2;
}
};
int main()
{
circle c1;//创建具体的圆(对象)(实例化)
c1.r = 10;//给具体的圆的属性赋值
cout << "圆的周长为:" << c1.circumference() << endl;
return 0;
}
6.1.2 struct和class的区别
struct
默认权限为: 共有 publicclass
默认权限为: 私有 private
6.1.3 将成员属性设为私有
优点:
①将成员属性设为私有,可以自己控制读写的权限。
②对于写权限,可以检测数据的有效性。
class person
{
private:
string name;//可读写
int age;//只读
string lover;//只写
public:
void SetName(string s)
{
name = s;
}
string GetName()
{
return name;
}
int GetAge()
{
age = 18;
return age;
}
void SetLover(string s)
{
lover = s;
}
};
int main()
{
person p1;
p1.SetName("xiyang");
p1.SetLover("2A");
cout << "姓名为:" << p1.GetName() << endl;
cout << "年龄为:" << p1.GetAge() << endl;
return 0;
}
设计案例1:立方体类
/*
要求:
1.设计一个立方体类
2.求出立方体的面积和体积
3.分别用全局函数和成员函数判断两个立方体是否相等
*/
class cube
{
private:
//属性
int L;
int W;
int H;
public:
//行为
//设置 获取长,宽,高
void SetL(int a)
{
L = a;
}
int GetL()
{
return L;
}
void SetW(int a)
{
W = a;
}
int GetW()
{
return W;
}
void SetH(int a)
{
H = a;
}
int GetH()
{
return H;
}
//获得面积
int S()
{
return 2 * ((L * W) + (L * H) + (W * H));
}
//获得体积
int V()
{
return L * W * H;
}
//成员函数判断
bool isSameByClass(cube& c)
{
if (c.GetL() == L && c.GetW() ==W && c.GetH() == H)
return true;
else
return false;
}
};
//全局函数判断
bool isSame(cube& c1, cube& c2)
{
if (c1.GetL() == c2.GetL() && c1.GetW() == c2.GetW() && c1.GetH() == c2.GetH())
return true;
else
return false;
}
int main()
{
cube c1,c2;
c1.SetL(10);
c1.SetW(10);
c1.SetH(10);
c2.SetL(10);
c2.SetW(10);
c2.SetH(10);
cout << "第一个立方体的面积为:" << c1.S() << endl;
cout << "第一个立方体的体积为:" << c1.V() << endl;
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;
return 0;
}
设计案例2:点和圆的关系
源.cpp
#include<iostream>
using namespace std;
#include"point.h"
#include"circle.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;
Point center;
Point q;
center.setX(10);
center.setY(10);
q.setX(10);
q.setY(10);
c.setCenter(center);
c.setR(10);
IsInCircle(c, q);
return 0;
}
circle.h
#pragma once
#include<iostream>
using namespace std;
#include"point.h"
class Circle {
public:
void setR(int r);
int getR();
void setCenter(Point center);
Point getCenter();
private:
int m_R;
Point m_Center;
};
circle.cpp
#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.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;//д
};
point.cpp
#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;
}
6.2 对象的初始化和清理
C++利用构造函数和析构函数解决了对象的初始化和清理。对象的初始化和清理工作是编译器强制要求我们做的事情,因此就算我们不提供构造和析构,编译器也会提供,只不过编译器提供的是构造函数和析构函数的空实现。
6.2.1 构造函数
定义:主要作用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
语法:类名
()
{ }
注意:①没有返回值,不用写void。
②函数名与类名相同。
③构造函数可以有参数,可以发生重载。
④创建对象时,构造函数会自动调用,并且只调用一次。
分类:
按参数分类:有参构造和无参构造(默认构造)
按类型分类:普通构造和拷贝构造
调用方式:
括号法、显示法、隐式转换法
//构造的分类和调用
class person
{
public:
//无参(普通构造)(默认构造)
person()
{
cout << "无参构造函数调用" << endl;
}
//有参(普通构造)
person(int a)
{
age = a;
cout << "有参构造函数调用" << endl;
}
//拷贝构造函数
person(const person& p) //加const作用,拷贝之后防止本体被修改
{
age = p.age;//克隆数据
cout << "拷贝构造函数的调用" << endl;
}
int age;
};
int main()
{
//括号法
person p1;//不能加(),加了()编译器会认为是一个函数声明,在一个函数体里可以声明另一个函数
person p2(10); //p2的年龄初始化为10
person p3(p2);
//显示法
person p4 = person();
person p5 = person(10);
person p6 = person(p5);
//person()为匿名对象,没有名字,但创建了对象,特点:当前行执行结束后系统就会回收掉匿名对象
//不要用拷贝构造函数初始化匿名对象,如person(p3),等价于person p3
//隐式转换法
person p7 = 10;//转换为:person p7=person(10)
}
6.2.2 析构函数
定义:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
语法:~类名
()
{ }
注意:①没有返回值,不写void
②函数名和类名相同,在名称前加~
③析构函数不可以有参数,不可以发生重载
④对象在销毁前会自动调用析构函数,并且只会调用一次
//构造函数和析构函数例子
class person
{
public:
person()
{
cout << "构造函数的调用" << endl;
}
~person()
{
cout << "析构函数的调用" << endl;
}
};
void test()
{
person P;
}
int main()
{
test();
return 0;
}
6.2.3 拷贝构造函数调用时机
C++中拷贝函数调用一般有三种情况:
①使用一个已创建完毕的对象来初始化一个新对象。
②值传递的方式给函数参数传值。
③以值的方式返回局部对象。
class person
{
public:
person()
{
cout << "默认构造函数的调用" << endl;
}
person(int a)
{
age = a;
cout << "默认构造函数的调用" << endl;
}
person(const person& p)
{
age = p.age;//克隆数据
cout << "拷贝构造函数的调用" << endl;
}
int show()
{
return age;
}
private:
int age;
};
void test1(person p)
{
}
person test2()
{
person p;
return p;
}
int main()
{
//使用一个已创建完毕的对象来初始化一个新对象
person p1(20);
person p2(p1);
cout << "p2的年龄为:" << p2.show() << endl;
//值传递的方式给函数参数传值,拷贝一份临时副本作为形参,改变临时副本的值不影响实参
person p3;
test1(p3);
//以值的方式返回局部对象,return返回时返回的不是局部对象,而是拷贝一个新对象返回
test2();
return 0;
}
6.2.4 构造函数调用规则
创建一个类,C++至少给每一个类添加4个函数:
1、默认构造(空实现)
2、析构函数(空实现)
3、拷贝构造(值拷贝)
4、赋值运算符Operator=对属性进行值拷贝
①如果用户定义一个有参构造函数,C++不会提供默认构造函数,但是会提供拷贝构造函数。
②如果用户定义一个拷贝构造函数,C++不会提供别的构造函数。
//eg.①如果用户定义一个有参构造函数,C++不会提供默认构造函数,但是会提供拷贝构造函数
class person
{
public:
person(int a)
{
age = a;
cout << "默认构造函数的调用" << endl;
}
int show()
{
return age;
}
private:
int age;
};
int main()
{
person p1;//err
person p2(18);
person p3(p2);//拷贝构造函数
cout << "p2的年龄为:" << p2.show() << endl;
return 0;
}
//②如果用户定义一个拷贝构造函数,C++不会提供别的构造函数
class person
{
public:
person(const person& p)
{
age = p.age;//克隆数据
cout << "拷贝构造函数的调用" << endl;
}
int show()
{
return age;
}
private:
int age;
};
int main()
{
person p1;//err
person p2(18);//err
person p3(p1);
cout << "p2的年龄为:" << p2.show() << endl;
return 0;
}
6.2.5 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝。
深拷贝:在堆区重新申请空间,进行拷贝。
浅拷贝存在的问题:属性有在堆区开辟的,拷贝之后两个类成员堆区属性都指向同一个地址,新拷贝成员执行析构操作之后,堆区内存被释放,旧成员执行析构操作就会使堆区内容重复释放。
解决方法:使两个成员堆区内容指向不同的地址,即进行深拷贝。
class person
{
public:
person(int a, int h)
{
age = a;
height = new int(h);
cout << "有参构造函数的调用" << endl;
}
person(const person& p)
{
age = p.age;
//height=p.height;//编译器默认实现
height = new int(*(p.height));
cout << "拷贝构造函数的调用" << endl;
}
~person()
{
if (height != NULL)
{
delete height;
height = NULL;
}
cout << "析构函数的调用" << endl;
}
int GetAge()
{
return age;
}
int GetHeight()
{
return *height;
}
private:
int age;
int* height;
};
int main()
{
person p1(18, 160);
cout << "p1的年龄为:" << p1.GetAge() << " p1的身高为:" << p1.GetHeight() << endl;
person p2(p1);
cout << "p2的年龄为:" << p2.GetAge() << " p2的身高为:" << p2.GetHeight() << endl;
return 0;
}
6.2.6 初始化列表
作用:用来初始化属性。
语法: 构造函数():属性1(值1),属性2(值2)...{}
class person
{
public:
//初始化列表初始化属性
person() :age(10), weigh(123), height(160)
{
}
person(int a, int b, int c) :age(a), weigh(b), height(c)
{
}
int age;
int weigh;
int height;
};
int main()
{
person p1;
person p2(18, 456, 180);
cout << "p1的年龄,体重,身高为:\n" << p1.age << p1.weigh << p1.height<<endl;
cout << "p2的年龄,体重,身高为:\n" << p2.age << p2.weigh << p2.height<<endl;
return 0;
}
6.2.7 类对象作为类的成员
注意:当其他类对象作为本类成员,构造时先构造类对象,再构造自身。析构顺序和构造顺序相反。
class Phone {
public:
Phone(string name) :PhoneName(name)
{
}
string PhoneName;
};
class Person {
public:
Person(string s, string p) :PersonName(s),PhoneName(p)
{
}
string PersonName;
string PhoneName;
};
int main()
{
Person p("gua", "iphone");
cout << p.PersonName << endl<<p.PhoneName << endl;
return 0;
}
6.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static。
静态成员变量:
所有对象共享一份数据。
在编译阶段分配内存(运行之前就分配了)。
类内声明,类外初始化。
class Person
{
public:
static int a;//类内声明
};
int Person::a = 100;//类外初始化
int main()
{
Person p1;
cout << "a的值为:" << p1.a << endl;
Person p2;
p2.a = 200;
cout << "a的值为:" << p1.a << endl;
return 0;
}
注意:静态成员变量不属于某一个对象。因此有两种访问方式:①类名访问,②对象访问。
class Person
{
public:
static int a;
};
int Person::a = 100;
int main()
{
//对象访问
Person p1;
cout << p1.a << endl;
//类名访问
cout << Person::a << endl;
return 0;
}
静态成员变量也有访问权限(类外访问不到私有静态成员变量)。
静态成员函数:
所有对象共享一个函数。
静态成员函数只能访问静态成员变量(访问成员变量,函数无法区分是哪个对象的成员变量)。
静态成员函数也有两种访问方式:
//静态成员函数的访问
class Person
{
public:
static void test()
{
cout << "static void test()调用" << endl;
}
};
int main()
{
//对象访问
Person p;
p.test();
//成员访问
Person::test();
return 0;
}
//静态成员函数只能访问静态成员函数
class Person
{
public:
static void test()
{
a = 200;
b = 200;//err,对象不明
cout << "static void test()调用" << endl;
}
static int a;//静态成员函数访问静态成员变量
int b;//非静态成员函数
};
int Person::a = 100;
int main()
{
Person p;
p.test();
return 0;
}
静态成员函数也有访问权限(类外访问不到私有静态成员函数)。
6.3 C++对象模型和this指针
6.3.1 成员变量和成员函数分开存储
在C++中,类的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
class person1
{
};
class person2
{
int a;//非静态成员变量
};
class person3
{
int a;
static int b;//静态成员变量
};
class person4
{
int a;
static int b;//静态成员变量
void test()//非静态成员函数
{
}
};
int main()
{
//空对象占用内存是 1
//C++会给每一个空对象分配一个字节的内存空间,为了区分空对象占内存的位置
//每一个空对象也应该有一个独一无二的内存地址
person1 p1;
cout << "sizeof(p)=" << sizeof(p1) << endl;//1
//虽然空对象有一个字节,但是一旦类里面不为空就跟着类中字节走
person2 p2;
cout << "sizeof(p)=" << sizeof(p2) << endl;//4
//静态成员变量不属于类对象上的
person3 p3;
cout << "sizeof(p)=" << sizeof(p3) << endl;//4
//类的成员变量和成员函数分开存储
person4 p4;
cout << "sizeof(p)=" << sizeof(p4) << endl;//4
return 0;
}
6.3.2 this指针的概念
this指针指向被调用的成员函数所属的对象。
this指针隐含在每一个非静态成员函数内的一种指针。
this指针不需要定义,可直接使用。
this指针的用途:
当形参和成员变量同名时,可以用this指针来区分。(解决名称冲突)
在类的非静态成员函数中返回对象本身,可以用retrun *this(返回对象本身用 *this)
形参与成员变量名称相同时,无法进行赋值,利用this指针解决
在类的非静态成员函数中返回对象本身,可以用retrun *this
class person
{
public:
person(int age)
{
this->age = age;
}
person& test(person p)//person一定要加&,使用本体
{
this->age += p.age;
return *this;
}
int age;
};
int main()
{
person p1(18);
person p2(18);
//链式编程
p2.test(p1).test(p1);
cout << "p2的年龄为:" << p2.age << endl;
return 0;
}
如果不加引用,每次返回的都是拷贝构造函数构造的新的对象,和原来不一样,要多次连续对一个对象操作,需要加上引用。
6.3.3 空指针访问成员函数
C++中空指针可以调用成员函数,但要注意有没有用到this指针。如果用到this指针,需要加以判断保证代码的健壮性。
class person
{
public:
void test1()
{
cout << "test1" << endl;
}
void test2()
{
cout << "age="<<age<< endl; //age相当于this->age
}
int age;
};
int main()
{
person *p=NULL;
p->test1();
p->test2(); //err,传入的指针为空
return 0;
}
改正方法:
void test2()
{
if(this->age==NULL)
return;//提高代码的健壮性
cout << "age="<<this->age<< endl;//err,传入的指针为空
}
6.3.4 const修饰成员函数
const,限制为只读状态。
常函数:(成员函数后加const)
常函数内不可以修改成员属性。
成员属性声明时加关键字mutable
后,在常函数中依然可以修改。
函数中this指针的定义相当于 Person * const this,指针的指向不可以修改,但指针指向的值可以修改。
常函数中this指针的定义相当于const Person * const this,指针的指向和指针指向的值均不可修改。
class person
{
void test2()
{
a = 100;
b = 200; //err b是常量不可以修改
c = 300;
}
//在成员函数后加const,修饰的是this指向,让指针指向的值不能改变
void test1() const
{
a = 100; //err 相当于this->a=100
b = 200; //err b是常量不可修改
c = 300;
}
int a;
const int b;
mutable int c;
};
int main()
{
person p;
p.test1();
p.test2();
return 0;
}
常对象:(声明对象前加const)
常对象只能调用常函数。(常对象本身不允许修改对象属性,如果常对象调用的常函数对对象属性进行了修改,相当于间接的对常对象的属性进行了修改,所以常对象只能调用不能对对象属性进行修改的函数即常函数)
常对象可以修改声明时加关键字mutable的成员属性。
class person
{
public:
void test1()
{
}
void test2() const
{
}
int a;
const int b;
mutable int c;
};
int main()
{
const person p;//不能修改指针指向的值
p.a = 100;//err
p.b = 200;//err
p.c = 300;
p.test1();//err 常对象只能调用常函数
p.test2();
}
6.4 友元
友元的目的就是让一个函数或者类访问另一个类中的私有成员。
在 private 和 public 中声明均可。
6.4.1 全局函数做友元
//全局函数做友元
class building {
private:
string bedroom;
friend void test2(building* b);//声明友元函数
public:
building()
{
bedroom = "卧室";
livingroom = "客厅";
}
string livingroom;
};
void test1(building* b)
{
cout << "访问" << b->livingroom << endl;
//cout << "访问" << b->bedroom << endl;//err 不可访问私有成员
}
void test2(building* b)
{
cout << "访问" << b->livingroom << endl;
cout << "访问" << b->bedroom << endl;
}
6.4.2 类做友元
//类做友元
class Building {
friend class GoodGay2;
public:
Building();
string Livingroom;
private:
string Bedroom;
};
class GoodGay1 {
public:
GoodGay1();
void visit();
Building* p;
};
class GoodGay2 {
public:
GoodGay2();
void visit();
Building* p;
};
Building::Building()
{
Bedroom = "卧室";
Livingroom = "客厅";
}
//非友元
GoodGay1::GoodGay1()
{
p = new Building;
}
void GoodGay1::visit()
{
cout << "朋友在访问" << p->Livingroom << endl;
cout << "朋友在访问" << p->Bedroom << endl;//err 没有访问权限
}
//友元
GoodGay2::GoodGay2()
{
p = new Building;
}
void GoodGay2::visit()
{
cout << "朋友在访问" << p->Livingroom << endl;
cout << "朋友在访问" << p->Bedroom << endl;
}
int main()
{
GoodGay1 g1;
g1.visit();
GoodGay2 g2;
g2.visit();
return 0;
}
6.4.3 成员函数做友元
class Building;//当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。
class GoodGay {
public:
GoodGay();
void visit1();
void visit2();
Building* building;
};
class Building {
public:
friend void GoodGay::visit1();
Building();
public:
string LivingRoom;
private:
string BedRoom;
};
Building::Building()
{
LivingRoom = "客厅";
BedRoom = "卧室";
}
GoodGay::GoodGay()
{
building = new Building;
}
void GoodGay::visit1()
{
cout << "visit函数正在访问:" << building->LivingRoom << endl;
cout << "visit函数正在访问:" << building->BedRoom << endl;
}
void GoodGay::visit2()
{
cout << "visit函数正在访问:" << building->LivingRoom << endl;
cout << "visit函数正在访问:" << building->BedRoom << endl;
}
void test1()
{
GoodGay gg;
gg.visit1();
}
void test2()
{
GoodGay gg;
gg.visit2();
}
int main()
{
test1();
test2();
}
6.5 运算符重载
运算符重载对已有的运算符重新定义,赋予另一种功能,以适应不同的数据类型。
对内置的数据类型,编译器知道如何进行运算,对非内置的数据类型就需要对运算符进行重载。
6.5.1 加号运算符重载
class Person {
public:
//成员函数实现+重载
Person operator+(Person& p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};
//全局函数实现+重载
Person operator+(Person& p1, Person& p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
cout << p3.m_A << endl << p3.m_B << endl;
}
运算符重载也可以发生函数重载。
对于内置的数据类型的表达式的运算符是不可能改变的。
不要滥用运算符重载。(加法写成减法,减法写成乘法,别人可能看不懂代码)
6.5.2 左移运算符(<<)的重载
作用:可以输出自定义的数据类型。
加&的原因:
输入 ostream& cout带&的原因是因为输出流对象cout全局只能有一个,不能创建一个新的传进来,要带引用。
写成:ostream& operator<<(ostream& cout, Person& p),则:cout<<a<<b;正确,因为它等同于(cout<<a)<<b;(cout<<a)返回cout的引用,即就是它自己,它可以再次作为左值,因而能够连着写这个输出流 。如果不加& 第一个括号中返回的是cout的临时变量,它不可以作为左值(因为operator << (ostream& cout, Person& p)的第一个参数是ostream&,而不是ostream),因而错误。
class Person {
friend ostream& operator<<(ostream& cout, Person& p);
public:
//舍弃成员函数重载,简化为p.operator<<cout,相当于p<<cout
//void operator<<(cout)
//{
//}
Person()
{
this->m_a = 10;
this->m_b = 10;
}
private:
int m_a;
int m_b;
};
//全局函数重载左移运算符
//返回ostream是链式编程思想,返回后cout<<p<<...后面可以再利用
ostream& operator<<(ostream& cout,Person& p)
{
cout << p.m_a << endl << p.m_b << endl;
return cout;
}
6.5.3 递增运算符的重载
作用:通过重载递增运算符,实现自己的整型数据。
重载 << 时 Intege r加 & 后输出 myint++ 报错,不加 & 不会报错。
原因:后置++不是一个可修改的左值,非常量引用必须是一个可修改的左值。
class Integer {
friend ostream& operator<<(ostream& cout, Integer p);
private:
int num;
public:
Integer()
{
this->num = 0;
}
//前置++
//&的作用是对同一个数据进行递增
Integer& operator++()
{
//先++
this->num++;
//再返回
return *this;
}
//后置++
//int是占位参数
Integer operator++(int)
{
//先暂存
Integer temp=*this;
//再++
num++;
//返回暂存的数据
return temp;
}
};
ostream& operator<<(ostream& cout, Integer p)
{
cout << p.num;
return cout;
}
void test01()
{
Integer myint;
cout << ++myint << endl;
cout << myint << endl;
}
void test02()
{
Integer myint;
cout << myint++<<endl;
cout << myint << endl;
}
6.5.4 赋值运算符重载
创建一个类,C++ 至少给每一个类添加4个函数:默认构造(空实现),析构函数(空实现),拷贝构造(值拷贝),赋值运算符 operator= 对属性进行值拷贝。
赋值运算符重载要注意数据存放在堆区时,浅拷贝带来的问题。
//赋值运算符的重载
class person
{
public:
person(int a)
{
age=new int(a);
}
~person()
{
if (age != NULL)
{
delete age;
age = NULL;
}
}
//重载赋值运算符以避免浅拷贝带来的问题
person& operator=(person& p)
{
//age=p.age;//编译器默认实现
if (age != NULL)
{
delete age;
age = NULL;
}
age = new int(*p.age);
//返回自身
return *this;
}
int *age;
};
int main()
{
person p1(10);
person p2(20);
person p3(30);
p3=p2 = p1;
cout << *(p2.age) << endl;//10
cout << *(p3.age) << endl;//10
return 0;
}
6.5.5 关系运算符的重载
作用:可以让两个自定义类型对象进行对比操作。
//关系运算符重载 == !=
class person
{
public:
person(string s,int a):name(s),age(a)
{
}
bool operator==(person& p)
{
if (this->name == p.name && this->age == p.age)
{
return true;
}
else
return false;
}
string name;
int age;
};
int main()
{
person p1("Tom",10);
person p2("Jerry",20);
if (p1 == p2)
{
cout << "p1和p2相等" << endl;
}
else
cout << "p1和p2不相等" << endl;
return 0;
}
6.5.6 函数调用运算符()重载
重载后的使用方式非常像函数调用,因此称为仿函数。
仿函数没有固定写法,非常灵活。
class MyPrint {
public:
void operator()(string test)
{
cout << test << endl;
}
};
void MyPrint02(string test)
{
cout << test << endl;
}
void test01()
{
MyPrint myprint;
myprint("Hello World!"); //使用起来非常类似函数调用,因此称为仿函数
MyPrint02("Hello World!");
}
//仿函数非常灵活,没有固定的写法
//加法类
class MyAdd {
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
cout<<myadd(10, 20)<<endl;
//匿名函数对象
cout<<MyAdd()(10, 20)<<endl;
}
6.6 继承
6.6.1 继承的基本语法
继承的好处:减少重复代码。
语法:class 子类 : 继承方式 父类{};
派生类中的成员包含两部分:一类是从基类继承过来的(共性),一类是自己增加的成员(个性)。
6.6.2 继承的方式
公共继承,public
保护继承,protected
私有继承,private
6.6.3 继承中的对象模型
父类中的所有非静态成员属性都会被子类继承
父类的私有成员被编译器隐藏,访问不到但是被继承
class Base
{
public:
int a;
static int d;//不继承
protected:
int b;
private:
int c;
};
class son :public Base
{
public:
int e;
};
int main()
{
//父类中的所有非静态成员属性都会被子类继承
//父类的私有成员被编译器隐藏,访问不到但是被继承
cout << "size of(son)=" << sizeof(son) << endl;//16
return 0;
}
利用工具查看:
1.
2. 跳转盘符
3.跳转文件路径 cd 具体路径下
4.查看命名
cl /d1 reportSingleClassLayout查看的类名 所属文件名
6.6.4 继承中构造和析构顺序
class Base
{
public:
Base()
{
cout << "父类构造"<<endl;
}
~Base()
{
cout << "父类析构"<<endl;
}
};
class Son :public Base {
public:
Son()
{
cout << "子类构造"<<endl;
}
~Son()
{
cout << "子类析构"<<endl;
}
};
6.6.5 继承中同名成员的处理方式
子类对象访问子类同名成员:直接访问
子类对象访问父类同名成员:加作用域
如果子类中出现和父类同名的成员函数,子类同名成员函数会隐藏掉父类中所有的同名成员函数(父类中同名成员函数,和同名成员函数的重载函数)。
class Base
{
public:
Base()
{
m_A=100;
}
void func()
{
cout << "Base_func"<<endl;
}
int m_A;
};
class Son :public Base {
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son_func"<<endl;
}
int m_A;
};
void test01() //同名成员属性处理
{
Son son;
cout << son.m_A << endl;
cout << son.Base::m_A << endl;
}
void test02() //同名成员函数处理
{
Son son;
son.func();
son.Base::func();
}
6.6.6 继承同名静态成员处理方式
静态成员和非静态成员出现同名时,处理方式一致。
注意通过对象访问和通过类名访问两种方式。
class Base
{
public:
static void func()
{
cout << "Base_func" << endl;
}
static int m_A;
};
int Base::m_A = 100;
class Son :public Base {
public:
static void func()
{
cout << "Son_func" << endl;
}
static int m_A;
};
int Son::m_A = 200;
void test01() //同名成员属性处理
{
//通过对象访问
Son son;
cout << son.m_A << endl;
cout << son.Base::m_A << endl;
//通过类名访问
cout << Son::m_A << endl;
cout << Son::Base::m_A << endl;
}
void test02() //同名成员函数处理
{
//通过对象访问
Son son;
son.func();
son.Base::func();
//通过类名访问
Son::func();
Son::Base::func();
}
6.6.7 多继承语法
语法:class 子类 :继承方式 父类1,继承方式 父类2,...
实际开发中不建议使用多继承。
class Base1{
public:
Base1()
{
this->a = 100;
}
int a;
};
class Base2 {
public:
Base2()
{
b = 200;
}
int b;
};
class Son :public Base1, public Base2 {
public:
Son()
{
c = 300;
}
int c;
};
6.6.8 菱形继承
两个派生类继承同一个基类,又有某个类同时继承者两个派生类。
主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义,通过虚继承来解决。
class Animal {
public:
int age;
};
class Sheep :virtual public Animal {
};
class Camel :virtual public Animal {
};
class SheepCamel :public Sheep, public Camel {
};
void test01()
{
Sheep sheep;
sheep.age = 18;
Camel camel;
camel.age = 28;
SheepCamel sheepcamel;
sheepcamel.age = 38;
cout << sheep.age << endl;
cout << camel.age << endl;
cout << sheepcamel.age << endl;
}
6.7 多态
6.7.1 多态的基本概念
多态可分为两类:
静态多态:函数重载 和 运算符重载
动态多态:派生类和虚函数实现运行时多态
两者的区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址(运行前就确定了走哪一段代码)
动态多态的函数地址晚绑定 - 运行阶段确定函数地址(运行起来才确定走哪一段代码)
class Animal {
public:
virtual void speak()
{
cout << "动物在说话?";
}
int age;
};
class Cat :public Animal {
virtual void speak()
{
cout << "小猫在说话?"<<endl;
}
};
class Dog :public Animal {
void speak()
{
cout << "小狗在说话?"<<endl;
}
};
void DoSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
Dog dog;
DoSpeak(cat);
DoSpeak(dog);
}
重写后,子类的vfptable中内容会发生改写。
6.7.2 多态案例----计算器类
多态好处:
组织结构清晰。
可读性强。
对前期和后期的扩展以及维护性高。
开发中提倡开闭原则:对扩展进行开放,对修改进行关闭。
C++开发提倡利用多态设计程序架构,因为多态优点很多。
//抽象计算机类
class AbstractCalculator {
public:
virtual int getresult()
{
return 0;
}
int num1;
int num2;
};
//加法计算机类
class AddCalculator :public AbstractCalculator {
public:
int getresult()
{
return num1 + num2;
}
};
//减法计算机类
class SubCalculator :public AbstractCalculator {
public:
int getresult()
{
return num1 - num2;
}
};
//乘法计算器类
class MulCalculator :public AbstractCalculator {
public:
int getresult()
{
return num1 * num2;
}
};
void Calculator()
{
//父类指针执行子类对象
AbstractCalculator* abc;
//加法
abc = new AddCalculator;
abc->num1 = 10;
abc->num2 = 20;
cout<<abc->getresult()<<endl;
//减法
abc = new SubCalculator;
abc->num1 = 20;
abc->num2 = 10;
cout << abc->getresult()<<endl;
//乘法
abc = new MulCalculator;
abc->num1 = 10;
abc->num2 = 20;
cout << abc->getresult()<<endl;
delete abc;
}
6.7.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可
以将虚函数改为纯虚函数。
纯虚函数定义:virtual 返回值类型 函数名 (参数列表)= 0 ;
抽象类定义:当类中有了纯虚函数,这个类也称为抽象类
抽象类的特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base
{
public:
//纯虚函数
virtual void func() = 0;
};
class Son1 :public Base
{
public:
};
class Son2 :public Base
{
public:
void func()
{
}
};
int main()
{
//base b;//err 抽象类无法实例化对象
//new base;//err
//son1 s;//子类必须重写抽象类中的纯虚函数,否则也属于抽象类
son2 s;
}
6.7.4 多态案例二----制作饮品
class AbstructDrinking {
public:
//煮水
virtual void BoilWater()
{
cout << "煮水" << endl;
}
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething() = 0;
void makeDrink()
{
BoilWater();
Brew();
PourInCup();
PutSomething();
}
};
class Coffee :public AbstructDrinking {
public:
//冲泡
virtual void Brew()
{
cout << "冲泡咖啡"<<endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入糖和牛奶" << endl;
}
};
class Tea :public AbstructDrinking {
public:
//冲泡
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入柠檬" << endl;
}
};
void DoWork(AbstructDrinking* drink)
{
drink->makeDrink();
delete drink;
}
void test01()
{
AbstructDrinking* drink = new Coffee;
DoWork(drink);
cout << "----------------------" << endl;
drink = new Tea;
DoWork(drink);
}
6.7.5 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构
函数,出现内存泄漏。
解决方式:将父类中的析构函数改为虚析构或者纯虚析构。
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名() = 0;
类名::~类名(){}
纯虚析构函数要有具体的函数实现
两者共性:
都可以解决父类指针释放子类对象。
都需要具体的函数实现。
两者区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象。
class Animal
{
public:
Animal()
{
cout << "Animal构造函数的调用" << endl;
}
~Animal()
{
cout << "Animal析构函数的调用" << endl;
}
//虚析构可以解决 父类指针释放子类对象时不干净的问题
//virtual ~Animal()
//{
// cout << "Animal虚析构函数的调用" << endl;
//}
//纯虚析构
//virtual ~Animal() = 0;
virtual void speak() = 0;
};
//纯虚析构函数要有具体的函数实现
//Animal::~Animal()
//{
// cout << "Animal纯虚析构函数的调用" << endl;
//}
class Cat:public Animal
{
public:
Cat(string n)
{
cout << "Cat的构造函数的调用" << endl;
name = new string(n);
}
void speak()
{
cout << *name<<"miao~" << endl;
}
~Cat()
{
cout << "Cat的析构函数的调用" << endl;
if (name != NULL)
{
delete name;
name = NULL;
}
}
string *name;
};
int main()
{
Animal* a = new Cat("mimi");
a->speak();
//父类指针在析构时候,不会调用子类中的析构函数,导致子类如果有堆区的属性,会出现内存的泄露
delete a;
}
总结:
虚析构或纯虚析构就是用来解决通过父类指针释放子类对象。
如果子类中没有堆区数据,可以不写为虚析构或纯虚析构。
用于纯虚析构函数的类也属于抽象类
6.7.6 多态案例三----电脑组装
//抽象不同零件的类
class CPU {
public:
virtual void calculate() = 0;
};
class VideoCard {
public:
virtual void display() = 0;
};
class Memory {
public:
virtual void storage() = 0;
};
//电脑类
class Computer {
public:
Computer(CPU* cpu, VideoCard* vc, Memory* mem)
{
this->cpu = cpu;
this->vc = vc;
this->mem = mem;
}
void Work()
{
cpu->calculate();
vc->display();
mem->storage();
}
~Computer()
{
if (cpu != NULL)
{
cout << "cpu释放" << endl;
delete cpu;
cpu = NULL;
}
if (vc != NULL)
{
cout << "显卡释放" << endl;
delete vc;
vc = NULL;
}
if (mem != NULL)
{
cout << "内存条释放" << endl;
delete mem;
mem = NULL;
}
cout << "电脑释放" << endl;
}
void Cpu()
{
cout << cpu << endl;
}
private:
CPU* cpu;
VideoCard* vc;
Memory* mem;
};
//Intel
class IntelCpu :public CPU {
public:
virtual void calculate()
{
cout << "Intel的CPU开始计算了?"<<endl;
}
};
class IntelVc :public VideoCard {
public:
virtual void display()
{
cout << "Intel的显卡开始显示了?"<<endl;
}
};
class IntelMem :public Memory {
public:
virtual void storage()
{
cout << "Intel的内存条开始存储了?"<<endl;
}
};
//Lenovo
class LenovoCpu :public CPU {
public:
virtual void calculate()
{
cout << "Lenovo的CPU开始计算了?"<<endl;
}
};
class LenovoVc :public VideoCard {
public:
virtual void display()
{
cout << "Lenovo的显卡开始显示了?"<<endl;
}
};
class LenovoMem :public Memory {
public:
virtual void storage()
{
cout << "Lenovo的内存条开始存储了?"<<endl;
}
};
void test01()
{
//第一台电脑零件
CPU* cpu = new IntelCpu;
VideoCard* vc = new IntelVc;
Memory* mem = new IntelMem;
//创建第一台电脑
Computer *computer = new Computer(cpu, vc, mem);
computer->Work();
delete computer;
}