一、概念
面向对象的三大特征:
封装、继承、多态
面向对象的四大特征:
封装、继承、多态、抽象
二、类的定义
(一)声明一个类类型的语法格式:
class 类名{
//访问控制权限
//类中的成员(成员函数和成员变量)
};
- 注:
- 类最后的分号不可丢;
- 类中的访问控制权限只是起到一个标识作用,不占类的空间
- 成员函数属于类,但是不占类的空间,没分配内存
- 如果是一个空类,空类的大小占1个字节;如果类中有成员,那么这1个字节的大小就不算了
(二)类中的访问控制权限
class 类名{
public:公有权限
既可以在类内进行访问,也可以在类外进行访问
在子类中也可以访问
private:私有权限
私有权限的成员只能在类内进行访问,不能再类外进行访问
在子类中也不可以进行访问
protected:受保护权限
受保护成员只能在类内进行访问,不能再类外进行访问
在子类中也可以访问
};
- 注:
- 访问控制权限在类中可以有多个,但是一般写一个
- 如果没有访问控制权限,默认是私有权限(private)
- 如果类中的私有权限成员和受保护权限成员想要在类外进行访问
需要通过公有权限成员函数进行操作; - 访问控制权限可以写多个,但是使用时一般写一个
(三)实例化对象
1. 栈区对象
类名 对象名;
2. 堆区对象
类名 *对象名 = new 类名;
(四)类内声明类外实现
需要在类外实现时
(五)涉及到的多文件编程
(六)对比C++类和结构体的区别
类的默认权限时私有的,结构体的默认权限是共有的;
如果需要定义一个数据结构,不需要封装,也没有复杂的继承关系,可以使用结构体;
三、类中的构造函数
(一)概念
构造函数是类中一个特殊的成员函数;构造函数会自动调用;
构造函数是用来申请资源,完成对类中的成员变量进行初始化工作;
(二)语法格式
class 类名{
类名(){...} //构造函数
};
- 注:
- 构造函数的函数名和类名一致
- 构造函数没有返回值类型
(三)构造函数的调用时机
1. 栈区对象
在实例化对象时进行调用
类名 对象名 = 类名(传递的实参);
类名 对象名(传递的实参);
类名 对象名 = 传递的实参;
- 注:第三种方式只能传递一个实参
2. 堆区对象
在new对象时进行调用
语法格式:
类名 *对象名 = new 类名(传递实参);
- 注: 当实例化对象和new对象时,构造函数会自动调用,且仅调用一次,在后面的操作中不能在进行显式地调用。
- 当没有提供自己写的构造函数,会调用编译器提供的缺省的构造函数,编译器提供的构造函数是无参构造函数;当提供了自己写的构造函数,就会使用自己提供的构造函数
- 不同版本的构造函数会构成重载关系;
- 构造函数的参数可以是缺省参数或者哑元参数。
(四)构造函数的初始化列表
在实例化对象的时候,同时给类中的成员变量进行初始化。
类中写有参构造函数时,最好用初始化列表,效率相对高一点
以下场合是必须要使用初始化列表的情况:
类中的成员变量的名称和构造函数的形参一样时,必须使用初始化列表
当成员变量中有引用时,必须在初始化列表中进行初始化工作
当成员变量有const属性时,必须在初始化列表中进行初始化工作
(五)类中包含类 类型成员
构造函数调用顺序:
先调用的是成员对象的构造函数,再调用当前对象的构造函数;
四、类中的析构函数
(一)概念
析构函数也是类中的一个特殊的成员函数
用来释放资源,完成清理工作
(二)语法格式
class 类名{
~类名(void){}
};
- 注:析构函数没有返回值,没有参数(无法构成重载关系)
- 栈区对象:当对象作用域结束时调用析构函数
- 堆区对象:当delete对象时调用析构函数
- 当没有显式地提供析构函数,会调用编译器提供的缺省的析构函数
(三)类中包含类 类型成员
析构函数调用顺序:
先调用当前对象的构造函数,再调用的是成员对象的构造函数
(与构造函数调用顺序相反)
关于构造函数和析构函数的调用顺序总结:
堆区对象:
构造函数:什么时候new什么时候调用
析构函数:什么时候delete什么时候调用
栈区对象:
构造函数:先实例化对象的先调用
析构函数:先构造的后析构
五、类中的拷贝构造函数
(一)概念
又叫做复制构造函数,是类中的特殊的成员,也是一个特殊的构造函数
用一个已知的对象为一个新对象进行拷贝操作
(二)语法格式
class 类名{
类名(const 类名& obj){}
}
- 注:拷贝构造函数的名字与类名一致
- 参数加const属性表示已知对象的数据不会发生改变
(三)调用时机
当用一个已知对象为一个新对象进行拷贝操作时会调用拷贝构造函数
(四)深拷贝与浅拷贝
浅拷贝:
如果类中没有显式的定义拷贝构造函数,编译器会默认提供一个拷贝构造函数,
这个默认提供的拷贝构造函数,只会完成成员一对一的简单赋值,
如果类中没有指针,使用这个默认的拷贝构造函数,是没有问题的;
如果类中有指针成员,并且使用浅拷贝时,指针成员也会只做简单的赋值,
相当于两个对象的指针成员指向的是同一块空间,调用析构函数释放时,就会出现问题
此时,需要在类中显式重写拷贝构造函数,并给新对象的指针成员重新分配空间,将浅拷贝变成深拷贝;
在有指针成员的情况下,浅拷贝并不能实现两个对象的完全独立,浅拷贝的指针成员指向的是同一块内存空间;
所以实现深拷贝时,要先申请一块内存空间,然后将值复制到该内存空间
深拷贝:
MyClass(const MyClass &obj):m_a( obj.m_a), m_b(obj.m_b), m_p(new int(*obj.m_p)){
cout << "MyClass(const MyClass &obj)" << endl;
// m_a = obj.m_a;
// m_b = obj.m_b;
// m_p = new int(*obj.m_p);
}
六、类中的拷贝赋值函数
(一)概念
拷贝复制函数又叫做等号运算符重载;
(二)语法格式
类名& operator = (const 类名& obj){
if(this != &obj){
//拷贝赋值操作
}
return *this;
}
实现多文件编程的时候,加类名::
时,应该加在函数名前面,即
类名& 类名::operator=(const 类名& obj){//函数实现}
(三)调用时机
用一个已知对象为另一个已知对象做赋值操作