1.封装
封装是c++面向对象的三大特性之一
将属性和行为作为一个整体
将属性和行为加以权限控制
语法:
class 类名{ 访问权限: 属性/行为 };
访问权限
public 公共权限 类内类外均可以访问
protected 保护权限 类内可以访问,类外不可以访问
private 私有权限 类内可以访问,类外不可以访问
保护权限和私有权限的区别:
保护权限儿子可以访问父亲的保护内容
私有权限儿子不可以访问父亲的私有内容
note:在c++中
struct默认的访问区别为公有
class默认权限为私有
在开发中我们大部分将变量设置为私有,自己控制读写权限
而外部访问我们定义一些公有函数作为接口即可
对象的初始化
构造函数和析构函数
在编译过程中,如果我们不提供构造和析构函数,编译器会提供
但是编译器提供的构造函数和析构函数是空实现
均写在public作用域下
构造函数
类名(){}
特点
- 没有返回值
- 函数名称和类名相同
- 构造函数可以有参数,可以发生重载
- 无需手动调用构造函数,只会在调用对象的时候自动调用构造函数一次
析构函数
~类名(){}
特点
- 没有返回值
- 函数名称与类名相同,在名称前加上~
- 析构函数不可以存在参数,不可以重载
- 程序在对象销毁前自动调用,无需手动调用,而且只会调用一次
析构函数在拷贝构造中先进后出
构造函数的分类和调用
按照参数分类:无参构造,有参构造
按照类型分类:普通构造,拷贝构造
拷贝构造传入 const 类名 &p
将对象作为参数传入,而传入参数不是对象的称为普通构造
拷贝函数
如果用户定义了有参构造函数,C++不在提供默认无参构造函数,但是会提供默认拷贝构造
如果用户定义了拷贝构造函数,C++不提供其他构造函数
浅拷贝:简单的赋值拷贝:问题是内存的重复释放
如果在类中定义了指针,并申请内存,在析构函数中释放内存,那么由于浅拷贝是赋值拷贝,就会出现清理两次相同地址的内容的情况
深拷贝:
在拷贝构造函数中定义新的内存
{变量 = new int (*指针)
}
初始化列表
写在类中,另外一种初始化方法
语法:构造函数(类型 变量,类型 变量): 属性1(变量1), 属性2(变量2) 。。。{}
类对象作为类成员
编译器会先构造其他类的对象,然后再构造自己的类的对象
静态成员
在成员变量或者成员函数前加上static,称为静态成员
1.静态成员变量
- 所有对象共享一份数据
- 在编译阶段分配内存(全局区)
- 类内声明,类外初始化
class Person{static int text;
};//类内声明int Person::text = 1;
//类外初始化
静态成员变量访问方式
- 通过对象进行访问
- 通过类名进行访问
Person::text
2.静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
成员变量和成员函数
空对象占用1字节内存空间,为了区分空对象占内存的位置
成员变量和成员函数分开存储
静态成员变量不属于类的对象
非静态成员变量属于类的对象
成员函数不属于类的对象
所以占用类的存储的只有非静态成员变量
this指针
当存在很多对象使用成员函数时,通过this指针解决对象区分问题,即知道是哪一个对象调用的自己
this指针指向被调用的成员函数所属对象
无需定义,可直接使用
用途
- 当形参和成员变量同名时,使用this指针来区分
- 在类的非静态成员函数中返回对象本身,使用return *this
使用值的方式返回会创建一个新的对象
使用引用的方式返回返回的时原来的对象
//值返回
Person add()
{this->age++;return *this
}//引用方式
Person &add()
{this->age++;return *this
}
空指针访问成员函数
空指针也可以调用成员函数
但是不可以调用成员变量,因为在函数中默认为this ->成员变量
所以一般在函数前加上
if(this == NULL){return ;}
const修饰成员函数
常函数:成员函数加const我们称这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明的时候加关键字mutable后,在常函数依然可以修改
常对象:声明对象前加const
note:常对象只能调用常函数
友元
有些私有属性也想让类外特殊的一些函数或者类访问,就需要用到友元
目的在于让一个函数或者一个类访问另一个类中的私有成员
关键字为friend
三种实现:声明在类内进行
- 全局函数作为友元
- 类做友元
- 成员函数做友元
全局函数做友元
friend 返回值类型 函数名(函数参数)
类做友元
friend class 类名
类的成员函数作为友元
friend 函数返回值 友元类名::类函数名();
运算符重载
最已有的运算符重新定义,赋予其另一种功能,以适应不同的函数类型
加号运算符重载
实现两个自定义数据类型的相加
可以通过成员函数重载,也可以通过全局函数重载
成员函数重载
全局函数重载
左移运算符重载
如果利用成员函数重载左移运算符,无法实现 cout在前面
所以只能利用全局函数重载左移运算符
#include<iostream>using namespace std;class Person {
public:int age;int money;
};ostream& operator<<(ostream& cout, Person& p) {cout << "p_age = " << p.age << endl;cout << "p_money = " << p.money << endl;return cout;
}int main() {Person p;p.age = 1;p.money = 0;cout << p << endl;system("pause");
}
递增运算符重载
自定义整形变量
#include<iostream>using namespace std;class myint {friend ostream& operator<<(ostream& cout, myint my_int);friend myint& operator++(myint& my_int);
public:myint() {number = 0;}
private:int number;
};ostream& operator<<(ostream& cout, myint my_int) {cout << my_int.number;return cout;
}
前置递增
//重载++运算符
myint & operator++(myint &my_int) {my_int.number++;return my_int;
}
后置递增
//重载++运算符
myint& operator++(myint& my_int,int) {//int 代表占位参数,可以用于区分前置和后置递增//先记录当前结果myint temp = my_int;//然后返回my_int.number++;//最后递增return temp;}
赋值运算符重载
c++默认给一个类添加赋值运算符对属性进行值拷贝
但是在析构函数释放空间时会出现问题
Person & operator=(Person &p1,Person &p2){//先判断是都有属性在堆区,如果有释放干净,然后再深拷贝if(m_age != NULL){delete m_age;m_age = NULL;}m_age= new int (*p2.m_age);return p1;}
关系运算符重载和上面的相差不大,就不做介绍了
继承
有些类与类之间存在特殊关系,定义类的时候,下级别的成员除了拥有上一级的共性,还有自己的特性,那么这个时候我们就可以考虑到继承的技术,目的在于减少重复代码
基本语法
class 子类名:继承方式 父类名{};
note:子类也成为派生类,父类也称为基类
继承方式
- 公共继承
- 保护继承
- 私有继承
继承中的对象
问题:从父类继承过来的成员,哪些属于子类对象中
父类中的所有成员都会被子类继承下去,私有成员属性被编译器隐藏了,访问不到,但是被继承下去了
继承中构造和析构的顺序
先构造父类,后构造子类
先析构子类,后析构父类
继承中同名成员处理方式
当子类和父类中出现同名的成员
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加上作用域
子类::父类.父类成员
note:如果子类中出现了和父类同名的成员函数,那么子类中的成员函数会隐藏所有父类的同名成员函数
多继承语法
语法
class 子类: 继承方式 父类1,继承方式 父类2,...
note:多继承可能会引发父类中有同名成员出现,需要加上作用域区分
菱形继承
使用虚继承来解决该问题
我们只需要在继承的类(羊类和驼类)前面加上virtual即可解决此问题
多态
多态分为两类
- 静态多态
- 动态多态
函数重载和运算符重载属于静态多态,复用函数名
派生类和虚函数实现运行时多态(动态多态)
静态多态函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
动态多态条件
- 子类中重写父类虚函数
- 需要有继承关系
动态多态使用
- 父类的指针或者引用执行子类对象,如:
void do(animal &animal){ animal.speak(); }Cat cat;
do(cat);//可以看到定义了传入父类的函数,却使用子类作为参数
纯虚函数和抽象类
纯虚函数语法
virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也成为抽象类
抽象类特点
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
虚析构和纯虚析构
多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放的时候无法调用到子类的析构代码
解决方法:将父类的析构函数改为虚析构或者纯虚析构
虚析构:在父类的析构函数前加上virtual。解决释放子类对象时不干净的问题
纯虚析构:有了纯虚析构之后,该类也属于纯虚类
virtual ~父类名() = 0;
//下面要写实现