目录
对象与类
类的语法:
C++中class与struct的区别:
通过类实例化对象的方式
具体案例
类作用域与分文件编写
创建circle.h头文件
创建源文件circle.cpp
创建all.cpp来作为程序的入口
封装
封装的意义
访问权限符
成员属性私有化
优点
具体案例
对象的初始化和清理
构造函数
析构函数
具体案例
构造函数的分类及调用
构造函数的分类
具体案例
构造函数的调用
括号法
显示法
隐式转换法
匿名对象
拷贝构造函数的调用时机
构造函数的调用规则
默认情况下C++编译器至少给一个类添加3个函数
构造函数的调用规则
初始化列表
深拷贝与浅拷贝
类对象作为类成员
静态成员
前言:
静态成员的访问方式
静态成员变量
静态成员函数
成员变量和成员函数分开存储
this指针
定义
this指针的用途
空指针访问成员函数
const修饰成员
常函数:
常对象
友元
前言:
友元的三种实现
全局函数做友元
类做友元
成员函数做友元
运算符重载
重载运算符方式
加号运算符重载
通过成员函数进行重载
通过全局函数进行重载
左移运算符重载
通过全局函数重载左移运算符
递增运算符重载
赋值运算符重载
C++编译器至少会给一个类添加4个函数
关系运算符重载
重载==号
函数调用运算符重载
前言:
具体案例
继承
类与类之间的继承关系
继承语法:
继承的经典案例
继承的方式种类
理解结构图
继承中的对象属性
继承中构造和析构的顺序
继承同名成员处理方式
继承中同名静态成员的处理方法
C++中的多继承
语法:
菱形继承
经典案例
菱形继承带来的问题
虚继承
前言:
多态
多态的分类
静态多态和动态多态的区别
动态多态的满足条件
案例分析
动态多态的原理剖析
纯虚函数和抽象类
抽象类特点
虚析构和纯虚析构
虚析构和纯虚析构共性与区别
语法:
经典案例
总结:
class 类名{访问权限1:属性1;行为1;访问权限2:属性2;行为2;
};
注意:
语法:类名 对象名;
注意:实例化对象之后就可以通过对象名.成员名的方式来访问类中的属性及方法
#include
using namespace std;
const double PI = 3.14;
//创建圆类
class Circle {
//访问权限
public://属性int r;//行为double calculateZC() {return 2 * PI * r;}
};
void main() {//通过圆类来实例化圆的对象Circle c;c.r = 10;cout << "圆的周长为:" << c.calculateZC() << endl;system("pause");
}
//防止头文件重复包含
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
const double PI = 3.14;
//创建圆类
class Circle {
public://设置半径void setR(double newr);//访问半径double getR();//求圆的周长double calculateZC();
private://属性int r;
};
注意:头文件中只写函数声明,不写函数实现。
#include "circle.h"
//设置半径(circle作用域下的成员函数)
void Circle::setR(double newr) {r = newr;
}
//访问半径
double Circle::getR() {return r;
}
//求圆的周长
double Circle::calculateZC() {return 2 * PI * r;
}
注意:源文件中只写函数的实现,在使用时需要引入对应的头文件,并用成员作用域的方式(类名::函数名)明确要实现的是哪个类的哪个方法
#include "circle.h"
void main() {//通过圆类来实例化圆的对象Circle c;//设置圆的半径c.setR(10);cout << "圆的半径为:" << c.getR() << endl;cout << "圆的周长为:" << c.calculateZC() << endl;system("pause");
}
注意:使用时需要导入circle.h头文件
C++面向对象的三大特性:封装、继承、多态。
protected与private的区别:后面我们会讲到继承,在继承中若父类的属性访问权限修饰符为protected,那么子类就可以访问该父类属性; 若父类的属性访问权限修饰符为private,那么子类就不可以访问该父类属性;
#include
using namespace std;
//创建person类
class Person
{
public://设置姓名void setName(string name) {m_Name = name;}//读取姓名string getName() {return m_Name;}//读取年龄int getAge() {return m_Age;}//更改爱人void setLover(string lover) {m_Lover = lover;}
private://姓名——可读可写string m_Name="lili";//年龄——只读int m_Age=18;//爱人——只写string m_Lover="lan";
};
void main() {Person people;string my=people.getName();cout << "我的名字为:" << my << endl;people.setName("Dong");my=people.getName();cout << "我的名字为:" << my << endl;system("pause");
}
注意:被private修饰的属性已经无权限访问,只能通过public修饰的方法对该属性进行间接访问
前言:C++利用了构造函数和析构函数来解决以上问题,这两个函数将被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要求我们做的事情,因此,若我们不提供构造和析构那么编译器会提供。但是编译器提供的构造和析构函数都是空实现
语法:类名(){}
构造函数作用:主要作用是创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
注意:
语法:~类名(){}
注意:
析构函数作用:主要作用在于对象销毁前系统自动调用,执行一些清理工作
#include
using namespace std;
class Person {
public:Person() {cout << "Person构造函数的调用" << endl;}~Person() {cout << "Person析构函数的调用" << endl;}
};
void main() {Person p;system("pause");
}
class Person {
public:Person() {cout << "Person无参构造函数的调用" << endl;}Person(string name) {p_Name = name;cout << "Person的含参构造函数的调用" << endl;}Person(string name,int age,string sex) {p_Name = name;p_Age = age;p_Sex = sex;cout << "Person的全参构造函数的调用" << endl;}Person(const Person& p) {p_Name = p.p_Name;p_Age = p.p_Age;p_Sex = p.p_Sex;cout << "Person的拷贝构造函数的调用" << endl;}
private:string p_Name;int p_Age;string p_Sex;
};
注意:若不写构造函数那么编译器会自动调用自己的无参构造,若自己写了构造函数,那么编译器提供的构造函数就不可用。
//括号法
void kuoHaoFa() {Person p; //默认构造函数的调用Person p1("lili"); //含参构造函数的调用Person p2(p1); //拷贝构造函数的调用
}
注意:调用默认构造函数的时候不要加()——因为Person p();编译器会认为它是一个函数声明,不会认为是在创建对象
//显示法
void xianShiFa() {//默认构造函数的调用Person p;//含参构造函数的调用Person p1 = Person("lili");
}
注意:
//隐式转换法
void YinShiFa() {//默认构造函数的调用Person p;//含参构造函数的调用string name = "lili";//等同:Person p1=Person("lili");Person p1 = name;
}
注意:隐式转换只有在构造函数有单个形参的情况下才可以进行
匿名对象:类名(参数列表);
匿名对象特点:当执行结束后,系统会立即回收掉匿名对象
#include
using namespace std;
class Person {
public:Person() {cout << "Person无参构造函数的调用" << endl;}Person(const Person& p) {p_Name = p.p_Name;p_Age = p.p_Age;p_Sex = p.p_Sex;cout << "Person的拷贝构造函数的调用" << endl;}
private:string p_Name;int p_Age;string p_Sex;
};
//以值传递的方式给函数传参,会调用到拷贝构造函数
void doWork(Person p) {cout << "dowork函数" << endl;
}
//以值的方式返回局部对象,会调用到拷贝构造函数
Person doWork1() {cout << "dowork1函数" << endl;Person p1;return p1;
}
void main() {Person p;doWork(p);Person p2=doWork1();system("pause");
}
作用:C++提供了初始化列表语法:用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)……{}
#include
using namespace std;
class Person {
public://初始化列表来初始化属性Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c) {cout << "m_A=" <
浅拷贝:浅拷贝就是对象的数据之间的简单赋值;原始的数据就占用一份空间,而两个指针会共同指向原始数据所占的地址
深拷贝:深拷贝相对于浅拷贝他会在堆内存中另外申请空间来储存数据,两个指针分别会指向两块不同的内存空间
#include
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) {//先判断是否有属性在堆区,若有先释放干净再深拷贝if (m_Age != NULL) {delete m_Age;m_Age = NULL;}//深拷贝m_Age =new int(*p.m_Age);return *this;}int* m_Age;
};
void main() {Person p1(18);cout << "p1的年龄为:" << *(p1.m_Age) << endl;Person p2(20);//若不进行赋值运算符重载则为浅拷贝p2 = p1;cout << "p2的年龄为:" << *(p2.m_Age) << endl;
}
注意:
前言:C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
class A{}
class B{A a;
}
注意:B类中有对象A作为成员,A为对象成员
#include
using namespace std;
class Phone {
public:string p_Name;Phone(string name) {p_Name = name;}
};
class Person {
public:Person(string name, string pname):m_Name(name),m_Phone(pname) {cout << "初始化列表" << endl;}string m_Name;Phone m_Phone;
};
void main() {Person p("lili", "apple");cout << "name:" << p.m_Name << endl;cout << "pname:" << p.m_Phone.p_Name << endl;system("pause");
}
注意:当其他类对象作为本类的成员,构造时候先构造本类的对象,再构造自身;先析构自身再析构本类的对象
#include
using namespace std;
class Person {
public://类内声明m_Astatic int m_A;
};
//类外初始化
int Person::m_A = 23;
void main() {Person p;Person p1;p1.m_A = 200;//通过对象进行访问cout << "m_A:" << p.m_A << endl;//200//通过类名进行访问cout << "m_A:" << Person::m_A << endl;//200system("pause");
}
#include
using namespace std;
class Person {
public:static void func() {//若访问非静态成员变量后则会报错m_A = 100;cout << "静态成员方法func的调用,m_A=" <
注意:静态成员函数不可以访问非静态的成员变量,主要原因是静态成员所有的对象共享一份,若访问对应的非静态成员变量则不清楚访问的是哪个对象的成员变量
class Person {
public:int m_A; //非静态成员变量,属于类的对象上,占用类对象的空间static int m_B; //静态成员变量,不在类的对象上,不占用类对象的空间void func() {} //非静态成员函数,不在类的对象上,不占用类对象的空间static void func1(){} //静态成员函数,不在类的对象上,不占用类对象的空间
};
注意:
问题:C++中的成员变量与成员函数分开存储,每个非静态成员函数只会诞生出一份函数实例,那么该如何区分是哪个对象调用这个函数呢
含义:this指针指向被调用的成员函数所属的对象
注意:
#include
using namespace std;
class Person {
public://解决名称冲突Person(int age) {this->age = age;}int age;Person& PersonAdd(Person& p) {this->age += p.age;//返回调用该函数的对象return *this;}
};
void main() {Person p(18);Person p1(12);cout << "p的age:" << p.age << endl;p1.PersonAdd(p).PersonAdd(p);cout << "p1的age:" << p1.age << endl;system("pause");
}
注意:若PersonAdd函数以Person为返回值返回,那么返回的就不是哪个特定地址的person对象了,而是经过拷贝构造函数拷贝的新对象,因此一定要以引用的方式(Person&)返回
前言:C++中空指针也可以调用成员函数的,但是需要注意有没有用到this指针
#include
using namespace std;
class Person {
public:void showClassName() {cout << "this is Person class" << endl;}void showPersonAge() {cout << "age=" << this->m_Age << endl;}int m_Age=18;static string m_Name;
};
string Person::m_Name = "lili";
void main() {Person *p=NULL;//调用正常,因为没有访问具体对象内的资源p->showClassName();//调用正常,空指针可以调用被static修饰的静态常量cout << "name=" << p->m_Name << endl;//调用失败,因为空指针不能访问对象内的资源cout << "age=" << p->m_Age << endl;//调用失败,因为空指针访问了具体对象p->showPersonAge();system("pause");
}
注意:空指针也可以访问类资源,以及不存在于对象上的资源,但是不可以访问相关对象上的资源
class Person {
public://常函数void showPerson() const {//常函数内m_A不可以修改//this->m_A = 100;//加了mutable关键字后即使在常函数中也可以修改该值this->m_B = 300;}int m_A;mutable int m_B;
};
注意:
#include
using namespace std;
class Person {
public://常函数void showPerson() const {//常函数内m_A不可以修改//this->m_A = 100;//加了mutable关键字后即使在常函数中也可以修改该值this->m_B = 300;}int m_A;mutable int m_B;
};
void main() {//常对象const Person p;p.m_B = 39;cout << "m_B:" << p.m_B << endl;//39//常对象只能调用常函数p.showPerson();cout << "m_B:" << p.m_B << endl;//300system("pause");
}
#include
using namespace std;
class Building {//全局函数做友元(说明goodGay函数是该类的好朋友,可以访问该类的私有属性)friend void goodGay(Building* building);
public:Building() {m_Room = "客厅";m_BedRoom = "卧室";}//客厅string m_Room;
private://卧室string m_BedRoom;
};
//全局函数
void goodGay(Building *building) {cout << "好基友全局函数,正在访问:" << building->m_BedRoom << endl;
}
void main() {Building building;goodGay(&building);
}
注意:友元的声明不需要放在权限修饰符内
#include
using namespace std;
class GoodGay {
public:Building* building;GoodGay() {building = new Building;}void visit() {cout << "好基友的类正在访问:" << building->m_BedRoom << endl;};
};
class Building {//类做友元,GoodGay类可以访问Building类的私有属性friend class GoodGay;
public:Building() {m_Room = "客厅";m_BedRoom = "卧室";}string m_Room;
private:string m_BedRoom;
};
void main() {GoodGay g;g.visit();
}
#include
using namespace std;
//告诉编译器有该类,解决visit访问不到类内属性的问题
class Building;
class GoodGay {
public://让visit函数可以访问到Building的私有成员void visit();//让visit1函数不可以访问到Building的私有成员void visit1();GoodGay();
private:Building *building;
};
class Building {//成员方法做友元friend void GoodGay::visit();
public:Building();string m_Room;
private:string m_BedRoom;
};
Building::Building() {m_Room = "客厅";m_BedRoom = "卧室";
}
GoodGay::GoodGay() {building = new Building;
}
void GoodGay::visit() {cout << "好基友的类正在访问:" << building->m_BedRoom << endl;
}
void GoodGay::visit1() {cout << "好基友的类正在访问building->m_BedRoom,但是访问不到!" << endl;
}
void main() {GoodGay g;g.visit();g.visit1();
}
注意:声明成员函数为友元时必须确定该函数是存在的
概念:对已有的运算符进行重新定义,赋予其另一种功能,以适应不同的数据类型
语法:将方法名改为——operator运算符
作用:实现两个自定义数据类型相加的运算
说明:p=p1+p2 <=> p=p1.operator+(p2)
#include
using namespace std;
class Person {
public:int m_A;int m_B;Person operator+(Person& p) {Person addP;addP.m_A = this->m_A + p.m_A;addP.m_B = this->m_B + p.m_B;return addP;}
};
void main() {Person p;p.m_A = 20;p.m_B = 11;Person p1;p1.m_A = 30;p1.m_B = 9;Person p2 = p + p1;cout << "p2的m_A:" << p2.m_A << "p2的m_B:" << p2.m_B << endl;//m_A:50 m_B:20
}
注意:这里的p+p1等价于p.operator+(p1)
说明:p=p1+p2 <=> p=operator+(p1,p2)
#include
using namespace std;
class Person {
public:int m_A;int m_B;
};
Person operator+(Person& p,Person& p1) {Person addP;addP.m_A = p.m_A + p1.m_A;addP.m_B = p.m_B + p1.m_B;return addP;
}
//运算符重载的函数重载
Person operator+(Person& p, int a) {Person addP;addP.m_A = p.m_A + a;addP.m_B = p.m_B + a;return addP;
}
void main() {Person p;p.m_A = 20;p.m_B = 11;Person p1;p1.m_A = 30;p1.m_B = 9;Person p2 = p + p1;cout << "p2的m_A:" << p2.m_A << "p2的m_B:" << p2.m_B << endl;//m_A:50 m_B:20Person p3 = p + 10;cout << "p3的m_A:" << p3.m_A << "p3的m_B:" << p3.m_B << endl;//m_A:30 m_B:21
}
注意:
作用:可以输出自定义的数据类型
注意:我们通常不会使用成员函数重载左移运算符,因为无法实现cout在左侧(因为要用自动自定义的对象调用导致自定义的对象始终在左边,也就是p< 注意:我们想要cout在左边,自定义对象在右边,因此cout在第一个参数,自定义对象在第二个参数 作用:可以通过递增运算符重载,进而实现自己的整形数据 前置++:MyInteger& operator++(); 后置++:MyInteger operator++(int); 注意:参数列表中的int参数用来区分前置++还是后置++(int在这里是占位符的意思) 注意:前置递增返回的是引用,后置递增返回的是值 说明:p1=p2 <=> p1.operator=(p2) 作用:重载关系运算符,可以让两个自定义类型的对象进行对比操作 说明:p1==p2 <=> p1.operator==(p2) 说明:对象(参数) <=> 对象.operator()(参数) 总结:我们发现下级别的成员除了具有上一级的特性,同时还有自己的特性,这个时候我们就考虑用继承 注意:这里面的父类也称基类,主要是子类所复用的类,这里的子类也称为派生类 总结: 总结: 总结:子类继承了父类的所有非静态成员属性,当然也包括父类的私有属性,只是父类的私有属性子类不可见 总结:构造子类对象时会先构造他的父类对象后再构造自身对象,清理对象时会先清理自身对象再清理父类对象(主要原因:父类属性被子类所复用,父类属性是子类属性的一部分,必须要保证子类属性的完整性) 问题:当子类与父类出现同名成员,如何通过子类对象访问到子类或父类中的同名数据呢 注意: 前言:静态成员和非静态成员出现同名,处理方式一致 总结: 前言:在C++中允许一个类继承多个类 注意:多继承可能会引发父类中的同名成员出现,需要加作用域区分 含义:两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承或者钻石继承 注意:当菱形继承出现时,两个父类拥有相同的数据,所以要访问特定的父类数据应该加作用域加以区分,但是若是虚继承,所继承的父类虚基类的属性将只存在一份,可以直接通过子类对象访问 多态的理解:一个事物的多种形态 虚函数:在父类的函数的返回值类型前加virtual关键字,那么父类对应的函数就会变成一个虚函数,对应的函数地址可以实现晚绑定 动态多态的使用:父类的指针或者引用指向子类的对象 函数重写:子类重写父类的函数,其函数的返回值类型,函数名与参数列表完全相同 注意: 解释:因为成员函数的存储并不在类的对象上,所以Animal1为一个空对象,所占内存大小为1,而Animal2所占内存空间大小为4,其实Animal2中的4字节空间为一个vfptr(虚函数表指针)该vfptr会指向一个虚函数表(vftable);表的内部存放的是Animal1的虚函数地址(&Animal1::say);当Cat继承了Animal1后,那么便将父类的所有属性均拿过来一份,也就有了父类的那个虚拟函数表指针vfptr,该指针指向了子类的虚函数表(vftable)当子类重写了父类虚函数方法,那么子类的方法将会将子类的虚函数表中的(父类原有的)方法进行覆盖;当父类引用指向子类对象Animal1& animal=cat对象(右值里面有子类的虚函数表);然后在通过animal调用对应的虚函数,将会调用子类虚函数表中的虚函数(因为右值赋予左面的变量)。 前言:在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类的重写内容,因此可以将虚函数改为纯虚函数 纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0; 注意:当类中有了纯虚函数(只要有一个),那么这个类也称抽象类 前言:在多态使用时,若子类中有属性开辟到了堆区,那么父类指针在释放时无法调用子类的析构代码 解决方式:将父类中的析构函数改为虚析构或纯虚析构 虚析构和纯虚析构共性 虚析构和纯虚析构区别 虚析构:virtual ~类名(){} 纯虚析构:virtual ~类名()=0; 纯虚析构类外实现:类名::~类名(){} 注意:纯虚析构需要声明,也需要实现(类外实现),因为由于内存的释放导致析构函数会用到 理解:在堆区中通过父类指针指向子类对象,那么父类的指针在释放时(delete释放指针所在的内存空间)无法调用到子类的析构代码,只会调用到父类的析构代码,这时,只要你将父类的析构函数改为虚析构或纯虚析构,那么delete语句调用的时候便会因为多态的重写原理调用到子类的析构函数,但是由于析构对象时会先析构自身对象再析构父类对象,最终导致子类以及父类对象的析构函数都调用了。通过全局函数重载左移运算符
#include
递增运算符重载
#include
赋值运算符重载
C++编译器至少会给一个类添加4个函数
#include
关系运算符重载
重载==号
#include
函数调用运算符重载
前言:
具体案例
#include
继承
类与类之间的继承关系
继承语法:
class 子类 : 继承方式 父类{
子类特有的代码;
}
继承的经典案例
#include
继承的方式种类
理解结构图
继承中的对象属性
#include
继承中构造和析构的顺序
#include
继承同名成员处理方式
#include
继承中同名静态成员的处理方法
#include
C++中的多继承
语法:
class 子类 : 继承方式 父类1,继承方式 父类2{
子类特有的代码;
}
#include
菱形继承
经典案例
菱形继承带来的问题
虚继承
前言:
#include
多态
多态的分类
静态多态和动态多态的区别
动态多态的满足条件
案例分析
#include
动态多态的原理剖析
#include
纯虚函数和抽象类
抽象类特点
虚析构和纯虚析构
虚析构和纯虚析构共性与区别
语法:
经典案例
#include
总结: