补充上周的一个深拷贝案例操作// m_Height=new int (*p.m_Height);
接着上周的顺序继续写
7)初始化列表
作用:c++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)... {}
//Person(int a,int b): m_A = (a), m_B = (b){}
8)类对象作为成员
c++中的成员可以是另一个类的对象,我们称该成员为对象成员
下图中Phone为Person中的对象成员
#include<iostream>
#include<string>
using namespace std;
class Phone
{
public:
string PName;
Phone(string name)
{
PName=name;
}
};
class Person
{
public:
string Name;
Phone m_Phone;
//Phone m_Phone=Pname 隐式转换法
Person(string name,string Pname):Name(name),m_Phone(Pname)
{
}
};
void test()
{
Person p("张三","遥遥领先");
cout<<p.Name<<"拿着"<<p.m_Phone.PName<<endl;
}
int main()
{
test();
return 0;
}
顺序:当其他类对象作为本类成员,构造时候先构造类对象,在构造自身
析构时先释放构造自身,在释放构造类对象
9)静态成员(在成员函数和成员变量前加上关键字static)
静态成员也有访问权限
(1)静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
在类内中有static int 变量;
在类外中有int 类名::变量(=值);
可以通过对象访问 (对象.变量)
可以通过类名访问(类名::变量)
(2)静态成员函数 static 类型 函数名(){}
所有对象共享同一个函数
静态成员函数只能访问静态成员变量(不能访问非静态成员变量,无法区分到底是哪个对象的)
通过对象访问(对象.函数名)
通过类名访问(类名::函数名)
6.c++对象模型和this指针
1)成员变量和成员函数分开储存
只有非静态成员变量才属于类的对象上
空对象占用内存空间为:1
2)this指针概念(this指针指向被调用的成员函数所属的对象)
this指针是隐含每一个非静态成员函数内的一种指针(本质是:类名*const this)常量指针,指向不可修改
this指针不需要定义,直接使用即可
用途:
当形参和成员变量同名时,可用this指针来区分(this->变量=变量)
在类的非静态成员函数中返回对象本身,可使用return *this;
在进行重复的函数操作时可以返回原函数值,如add为执行加法的构造函数操作
遇到p.add(10).add(10).add(10).add(10)时在add中return *this;后就能进行此操作
3)空指针访问成员函数
c++中空指针也可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include<iostream>
using namespace std;
class Person
{
public:
int m_Age;
void showname()
{
cout<<"showname"<<endl;
}
void showage()
{
if(this==NULL) return;//防止空指针输入崩溃
cout<<m_Age<<endl;
}
};
void test()
{
Person *p;
p->showage();
p->showname();
}
int main()
{
test();
return 0;
}
4)const修饰成员函数
常函数:
成员函数后加const后我们称为这个函数为常函数
常函数内不可修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改//如mutable int b;
用法 类型 函数名()const{}//加入const后内部的值不可修改
常对象:
声明常对象前加const称该对象为常对象(const 类名 变量;)
常对象只能调用常函数(因为不同成员函数的属性可以修改)
加了mutable的变量后在常函数中也可以修改
7.友元(friend)(在下面8.2的左移运算符重载中有示例)
目的:让一个函数或者类访问另一个类中的私有成员
1)全局函数做友元
将声明的全局函数放到类中并在前面放一个friend,就可以使得全局函数访问到类中的私有成员(只需写到类中即可)
friend 类型 函数名(); 最后加一个“;”
2)类做友元
在类外中也可以写成员函数,需要加一个作用域 类名::函数名(){}
用法与(1)中大体相同 friend class 类名
3)成员函数做友元
friend 类型 类名::成员函数名();
8.运算符重载
概念:对已有的运算符进行重新定义,赋予其另一种功能,以适应不同的数据类型
1)加号运算符重载
对于内置的数据类型的表达式的运算符是不可能改变的
不要滥用运算符重载
作用:实现两个自定义数据类型的相加运算
成员函数重载+号//类名 operator+(变量){操作}
#include<iostream>
using namespace std;
class Person
{
public:
int A;
int B;
Person operator+(Person &p)
{
Person t;
t.A=this->A+p.A;
t.B=this->B+p.B;
return t;
}
};
void test()
{
Person p1;
p1.A=10;
p1.B=10;
Person p2;
p2.A=10;
p2.B=20;
Person p3=p1+p2;
cout<<p3.A<<endl;
cout<<p3.B<<endl;
}
int main()
{
test();
system("pause");
return 0;
}
也可以全局函数重载+号
#include<iostream>
using namespace std;
class Person
{
public:
int A;
int B;
};
Person operator+(Person &p1,Person &p2)
{
Person t;
t.A=p1.A+p2.A;
t.B=p1.B+p2.B;
return t;
}
void test()
{
Person p1;
p1.A=10;
p1.B=10;
Person p2;
p2.A=10;
p2.B=20;
Person p3=p1+p2;
cout<<p3.A<<endl;
cout<<p3.B<<endl;
}
int main()
{
test();
system("pause");
return 0;
}
还可以重载不同类型的变量如p1与10相加
2)左移运算符重载
可以输出自定义数据类型
只能利用全局函数重载左移运算符
重载左移运算符配合友元可以实现输出自定义数据类型
#include<iostream>
using namespace std;
class Person
{
friend ostream &operator<<(ostream &out,Person &p);//友元
public:
Person(int a,int b)
{
m_A=a;
m_B=b;
}
private:
int m_A;
int m_B;
};
ostream &operator<<(ostream &out,Person &p)//全局函数重载左移符号
{
out<<"m_A="<<p.m_A<<" m_B="<<p.m_B;
return out;
}
void test()
{
Person p(10,10);
cout<<p<<"hello"<<endl;
}
int main()
{
test();
system("pause");
return 0;
}
3)递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
#include<iostream>
using namespace std;
class MyInteger
{
friend ostream &operator<<(ostream &out,MyInteger p);//友元
public:
MyInteger()
{
m_A=0;
}
//前置重载++运算符,需要用引用
MyInteger &operator++()
{
m_A++;
return *this;
}
//重载后置++运算符,不需要引用 (int)代表占位参数,可以区分前置和后置递增
MyInteger operator++(int)
{
MyInteger t=*this;
m_A++;
return t;
}
private:
int m_A;
};
ostream &operator<<(ostream &cout,MyInteger p)//全局函数重载左移符号
{
cout<<"m_A="<<p.m_A;
return cout;
}
void test()
{
MyInteger p;
cout<<"前置为"<<++p<<endl;
cout<<"后置为"<<p++<<endl;
cout<<p<<endl;
}
int main()
{
test();
system("pause");
return 0;
}
这里在左移重载时注意类的参数不需要引用。
前置递增返回引用,后置递增返回值
4)赋值运算符重载
c++编译器至少给一个类添加4个函数
默认构造函数
默认析构函数
默认拷贝函数
赋值运算符 operator=,对属性进行拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
示例
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
m_Age=new int(age);//堆区建立
}
//重载赋值运算符
Person &operator=(Person &p)
{
if(m_Age!=NULL)
{
delete m_Age;
m_Age=NULL;
}
//提供深拷贝,解决浅拷贝问题
m_Age=new int(*p.m_Age);
return *this;
}
~Person()
{
if(m_Age!=NULL)
{
delete m_Age;
m_Age=NULL;
}
}
int *m_Age;
};
void test()
{
Person p1(18);
Person p2(20);
Person p3(22);
p3=p2=p1;
cout<<"p1的年龄为"<<*p1.m_Age<<endl;
cout<<"p2的年龄为"<<*p2.m_Age<<endl;
cout<<"p3的年龄为"<<*p3.m_Age<<endl;
}
int main()
{
test();
system("pause");
return 0;
}
5)关系运算符重载
作用:可以让两个自定义类型对象进行对比操作
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
string m_name;
int m_age;
Person(string name,int age)
{
this->m_age=age;
this->m_name=name;
};
bool operator==(Person &p)
{
if(this->m_name==p.m_name&&this->m_age==p.m_age) return true;
else return false;
}
bool operator!=(Person &p)
{
if(this->m_name!=p.m_name&&this->m_age!=p.m_age) return true;
else return false;
}
};
void test()
{
Person p1("张三",18);
Person p2("张三",18);
if(p1==p2) cout<<"p1和p2相等"<<endl;
else cout<<"p1和p2不相等"<<endl;
if(p1!=p2) cout<<"p1和p2不相等"<<endl;
else cout<<"p1和p2相等"<<endl;
}
int main()
{
test();
system("pause");
return 0;
}
6)函数调用运算符重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
示例打印函数
#include<iostream>
#include<string>
using namespace std;
class Print
{
public:
void operator()(string test)
{
cout<<test<<endl;
}
};
void test()
{
Print p;
p("Hello");
}
int main()
{
test();
system("pause");
return 0;
}
此重载可以用来做各种各样的操作,非常灵活
9.继承(可以把公共拥有的部分写在一起,不用重复写代码,只需写一遍重复调用即可)
1)继承的基本语法
好处:减少重复代码
子类也称派生类 父类也称基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过来的表现其共性,而新增的成员体现了其个性。
2)继承方式
语法:class 子类:继承方式 父类
三种继承方式:公共继承,保护继承,私有继承
具体继承方式
![](https://img-blog.csdnimg.cn/direct/1e5d0fb43968442eb0c7d821083911d8.png)
3)继承中的对象模型
父类中的所有非静态成员属性都会被子类继承下去
父类中的私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
利用开发人员命令提示工具查看对象模型(英文名为Developer Command Prompt for VS 版本)
跳转至程序所保存的盘符
跳转文件路径cd具体路径下(复制粘贴路径)
查看命名
输入cl/d1 reportSingleClassLayot类名 文件名
效果图
![](https://img-blog.csdnimg.cn/direct/3dc979cbed6e45e697ca93ffbc97a5ca.png)
4)继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
5)继承同名成员处理方式
子类对象可以直接访问到子类中同名成员
子类对象加作用域可以访问到父类同名成员
当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
6)继承中同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致
访问子类同名成员 直接访问即可
访问父类同名成员 需要加作用域
访问方式两种使用方式看 7.9)的静态成员
在通过类名访问过程中有//类名::作用域类名::成员变量
与 5)中的特点基本相同
7)多继承语法
c++中允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2......
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
8)菱形继承
概念:两个派生类继承同一个基类
又有某个类同时继承着两个派生类
这种继承被称为菱形继承,或者钻石继承
带来的问题:
子类继承两份相同的数据,导致资源浪费以及毫无意义
菱形继承案例:
![](https://img-blog.csdnimg.cn/direct/19abff3b6a7548a0b7e15a18b34852b4.png)
在这个案例中会存在不知道继承谁的年龄,这时我们需要用到虚继承
继承之前 加上关键字 virtual 变为虚继承//class 子类:virtual 继承类型 父类{};
虚继承的类称为虚基类
虚继承会使两个要继承的结果系统自动变成一个唯一的结果
10.多态
1)基本概念:
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
区别:
静态多态函数地址早绑定-编译阶段确定函数地址
动态多态的函数抵制晚绑定-运行阶段确定函数地址
将静态变成动态,需要再成员函数前面加上virtual
动态多态的使用:
父类的指针或者引用 指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致的称为重写
2)纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
语法:virtual 返回值类型 函数名(参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
3)案例一:计算器类
输入两个类,进行运算处理
普通写法
#include<iostream>
#include<string>
using namespace std;
class Calculator
{
public:
int getResult(string oper)
{
if(oper=="+")
{
return num1+num2;
}
else if(oper=="-")
{
return num1-num2;
}
else if(oper=="*")
{
return num1*num2;
}
}
int num1;//操作位1
int num2;//操作位2
};
void test()
{
Calculator c;
c.num1=10;
c.num2=10;
cout<<c.num1<<"+"<<c.num2<<"="<<c.getResult("+")<<endl;
cout<<c.num1<<"-"<<c.num2<<"="<<c.getResult("-")<<endl;
cout<<c.num1<<"*"<<c.num2<<"="<<c.getResult("*")<<endl;
}
int main()
{
test();
system("pause");
return 0;
}
弊端:如果想扩展新的功能,需求修改源码
在真实的开发中 提倡 开闭原则
开闭原则:对扩展进行开放,对修改进行关闭
利用多态技术改进
#include<iostream>
#include<string>
using namespace std;
class AbstractCalculator//实现计算器抽象类
{
public:
virtual int getResult()=0;//纯虚函数
int num1;
int num2;
};
//加法
class AddCalculator:public AbstractCalculator
{
public:
virtual int getResult()
{
return num1+num2;
}
};
//减法
class SubCalculator:public AbstractCalculator
{
public:
virtual int getResult()
{
return num1-num2;
}
};
//乘法
class MulCalculator:public AbstractCalculator
{
public:
virtual int getResult()
{
return num1*num2;
}
};
void test()
{
//多态使用条件
//加法
AbstractCalculator *abc=new AddCalculator;
abc->num1=100;
abc->num2=100;
cout<<abc->num1<<"+"<<abc->num2<<"="<<abc->getResult()<<endl;
delete abc;//用完记得销毁
//减法
abc=new SubCalculator;
abc->num1=100;
abc->num2=100;
cout<<abc->num1<<"-"<<abc->num2<<"="<<abc->getResult()<<endl;
//乘法
abc=new MulCalculator;
abc->num1=100;
abc->num2=100;
cout<<abc->num1<<"*"<<abc->num2<<"="<<abc->getResult()<<endl;
}
int main()
{
test();
system("pause");
return 0;
}
多态好处:
组织结构清晰
可读性强
对于前期和后期扩展以及维护性高
4)虚析构和纯虚析构
多态使用时,如果子类中属性开辟的堆区,那么父类指针在释放时无法调用到子类的析构代码
父类指针在析构时候 不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
纯虚析构需要声明也需要实现,类外声明,类内实现
虚析构语法:virtual ~类名(){}
纯虚析构语法:类内:virtual ~类名()=0;
类外:类名:: ~类名(){};
如果子类中没有堆区数据,可以不写虚析构或纯虚析构
虚析构案例:
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal()
{
cout<<"Animal构造函数调用"<<endl;
}
virtual void speak()=0;
virtual ~Animal()
{
cout<<"Animal析构函数调用"<<endl;
}//虚析构
string *name;
};
class Cat:public Animal
{
public:
Cat(string n)
{
name=new string(n);
cout<<"Cat构造函数调用"<<endl;
}
void speak()
{
cout<<*name<<"小猫在说话"<<endl;
}
~Cat()
{
if (name!=NULL)
{
cout<<"Cat析构函数调用"<<endl;
delete name;
name=NULL;
}
}
string *name;
};
void test()
{
Animal *a=new Cat("Tom");
a->speak();
delete a;
}
int main()
{
test();
system("pause");
return 0;
}
纯虚析构:
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal()
{
cout<<"Animal构造函数调用"<<endl;
}
virtual void speak()=0;
virtual ~Animal()=0;//纯虚析构
string *name;
};
Animal::~Animal()
{
} //声明
class Cat:public Animal
{
public:
Cat(string n)
{
name=new string(n);
cout<<"Cat构造函数调用"<<endl;
}
void speak()
{
cout<<*name<<"小猫在说话"<<endl;
}
~Cat()
{
if (name!=NULL)
{
cout<<"Cat析构函数调用"<<endl;
delete name;
name=NULL;
}
}
string *name;
};
void test()
{
Animal *a=new Cat("Tom");
a->speak();
delete a;
}
int main()
{
test();
system("pause");
return 0;
}
5)电脑组装案例(多态大部分知识都包括在内)
描述:
电脑主要组成部分为CPU,显卡,内存
将每个零件封装出抽象基类,并提供不同的厂商产生不同的零件,例如Intel和Lenovo
创建电脑类提供让电脑工作的函数,并调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include<iostream>
#include<string>
using namespace std;
//抽象CPU类
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)
{
m_cpu=cpu;
m_vc=vc;
m_mem=mem;
}
//工作函数,让零件工作起来
void work()
{
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
//析构释放3个零件
~Computer()
{
if(m_cpu!=NULL)
{
delete m_cpu;
m_cpu=NULL;
}
if(m_vc!=NULL)
{
delete m_vc;
m_vc=NULL;
}
if(m_mem!=NULL)
{
delete m_mem;
m_mem=NULL;
}
}
private:
CPU *m_cpu;//CPU的零件指针
VideoCard *m_vc;//显卡的零件指针
Memory *m_mem;//内存的零件指针
};
//Intel产商
class IntelCPU:public CPU
{
public:
virtual void calculate()
{
cout<<"Intel的CPU开始计算"<<endl;
}
};
class IntelVideoCard:public VideoCard
{
public:
virtual void display()
{
cout<<"Intel的显卡开始显示"<<endl;
}
};
class IntelMemory:public Memory
{
public:
virtual void storage()
{
cout<<"Intel的内存开始储存"<<endl;
}
};
class LenovoCPU:public CPU
{
public:
virtual void calculate()
{
cout<<"Lenovo的CPU开始计算"<<endl;
}
};
class LenovoVideoCard:public VideoCard
{
public:
virtual void display()
{
cout<<"Lenovo的显卡开始显示"<<endl;
}
};
class LenovoMemory:public Memory
{
public:
virtual void storage()
{
cout<<"Lenovo的内存开始储存"<<endl;
}
};
void test()
{
//零件创建
CPU *intelCpu=new IntelCPU;
VideoCard *intelCard=new IntelVideoCard;
Memory *intelMem=new IntelMemory;
//创建第一台电脑
Computer *computer1=new Computer(intelCpu,intelCard,intelMem);
computer1->work();
delete computer1;
cout<<"-------------------"<<endl;
cout<<"第二台电脑开始工作"<<endl;
//创建第二台电脑
Computer *computer2=new Computer(new LenovoCPU,new LenovoVideoCard,new LenovoMemory);
computer2->work();
delete computer2;
cout<<"-------------------"<<endl;
cout<<"第三台电脑开始工作"<<endl;
//创建第三台电脑
Computer *computer3=new Computer(new LenovoCPU,new IntelVideoCard,new LenovoMemory);
computer3->work();
delete computer3;
}
int main()
{
test();
system("pause");
return 0;
}
结果为
![](https://img-blog.csdnimg.cn/direct/ad59d8bf187b4f898ece404169009df8.png)
到这里c++中类和对象的内容就学完了,但有很多东西还需要自己去摸索,写出一个属于自己风格的代码
注:部分图片来自b站黑马程序员的视频之中