一、类中的默认函数 + this 指针
(一)默认函数 + this 指针
-
构造函数
-
析构函数
-
拷贝构造函数
-
赋值运算符函数
#include <string.h> class Point { public: Point(int x, int y); // 构造函数 ~Point(); // 析构函数 Point(const Point &rhs); // 拷贝构造函数 Point &operator = (const Point &rhs); // 赋值运算符函数 private: char *_contant; int _ix; int _iy; }
1. 构造函数
tips:
- 初始化列表的赋值顺序应当和成员变量定义顺序相同
- 初始化与赋值不同,具体体现在于三种特殊数据成员:常量成员、引用成员、类对象成员
实现代码
Point::Point(char *contant, int x = 0, int y = 0)
: _contant(new char[strlen(contant + 1)]())
, _ix(x)
, _iy(y)
{
strcpy(_contant, contant);
}
2. 析构函数
tips:
-
一个类只有唯一一个析构函数,析构函数不能重载
-
在堆中创建变量时,不会自动执行析构函数
指针仍指向堆空间的类成员,所以类未被及时销毁,所以需要手动执行析构函数
-
对象的销毁与析构函数不等价
堆对象,对象销毁时,除了执行析构函数,还需要执行 operator delete(void *)
析构函数调用时机
- 对于全局定义的对象,每当程序开始运行,在主函数
main
接受程序控制权之前,就调用构造函数创建全局对象,整个程序结束时自动调用全局对象的析构函数。 - 对于局部定义的对象,每当程序流程到达该对象的定义处就调用构造函数,在程序离开局部对象的作用域时调用对象的析构函数。
- 对于关键字
static
定义的静态局部变量,当程序流程第一次到达该对象定义处调用构造函数,在整个程序结束时调用析构函数。 - 对于用
new
运算符创建的对象,每当创建该对象时调用构造函数,当用delete
删除该对象时调用析构函数。
实现代码
Point::~Point()
{
delete _contant;
_contant = nullptr;
}
3. 拷贝构造函数
拷贝函数调用时机
-
当用一个已经存在的对象初始化另一个新对象时,会调用拷贝构造函数。
Point pt2 = pt1
-
当实参和形参都是对象,进行实参与形参的结合时,会调用拷贝构造函数。
void func(Point pt) { // 结构体 }
-
当函数的返回值是对象,函数调用完成返回时,会调用拷贝构造函数。
Point func() { // 结构体 }
编译器会自动优化该返回值,若要实现则通过以下命令进行编译
g++ [filename].cc -fno-elide-constructors
tips:当类类型中有数组时,需注意浅拷贝和深拷贝
实现代码
#include <string.h>
Point::Point(const Point &rhs)
: _contant(new char[strlen(rhs.contant) + 1]())
, _ix(rhs._ix)
, _iy(rhs._iy)
{
strcpy(_contant, rhs.contant); // 深拷贝
}
const Point &rhs
中需要注意的事项:
- &rhs 中的 &
由于当创建临时变量时,会调用拷贝构造函数,若不为引用,即直接为 rhs,则在创建实参时,再次执行拷贝构造函数,反复如此,从而导致栈溢出 - const
必须是 const 类型,才能指向右值
4. 赋值运算符函数
步骤:
- 自复制
- 释放左操作数
- 深拷贝(包含空间的申请)
- 返回 *this
实现代码
Point &Point::operator=(const Point &rhs)
{
if(this == &rhs) // 返回本值时,由于先 delete 成员函数,从而会导致值丢失
return *this;
delete[] _contant;
_contant = nullptr;
_contant = new char[strlen(rhs._contant) + 1]();
strcpy(_contant, rhs._contant);
return *this;
}
tips:
-
如果去掉返回类型的 &,那当返回时,返回的为类本身,从而调用一次拷贝构造函数,造成浪费
-
如果不返回 Point &,比如返回类型为 void,则当连等时,会异常
Point pt1, pt2, pt3; pt3 = pt2 = pt1; // 若不为 Point &,此处会报错
调用方法
Point pt1, pt2;
pt2 = pt1;
pt2.operator=(pt1); // 两者等价
5. this 指针
在类中定义的非静态成员函数中都有一个隐含的this指针,它代表的就是当前对象本身,它作为成员函数的第一个参数,由编译器自动补全。
this 指针本身不能修改,是顶层 const / 指针常量 / *const
案例代码
void Point::print(Point * const this) {
// 结构体
}
还有
移动构造函数
Point(const Point&& rhs); // && 右值引用
移动拷贝函数
Point& operator=(Point&& rhs);
有缘再写!
(二)几种模板 - ?不确定
= delete
-
禁止使用编译器默认生成的函数
-
delete 关键字可用于任何函数,不仅仅局限于类的成员函数
-
模板特化:在模板特例化中,可以用delete来过滤一些特定的形参类型
= default
相当于无参的构造函数
参考博客:https://blog.csdn.net/lmb1612977696/article/details/80035487
https://docs.microsoft.com/en-us/cpp/cpp/explicitly-defaulted-and-deleted-functions?view=vs-2019#explicitly-defaulted-functions
= 0
虚函数 ==> 纯虚函数,详情见 - 继承和多态
有缘再写!
学完再改
(三)类中的特殊数据成员
- 常量成员
- 引用成员
- 类对象成员
- 静态成员
常量成员、引用成员、类对象成员
tips:必须在构造函数的初始化列表中初始化
引用成员占据一个指针空间大小
类对象成员如果不显示初始化,会调用默认构造函数,若类对象的类类型无默认构造函数,则会报错
静态数据成员
tips:
- 静态数据成员在编译时就被创建并初始化
- 不能在
[filename].h
中定义,会导致多次定义 ——> 必须在[filename].cc
中定义 - 静态数据成员不占类的空间
留个坑:
静态成员变量,在类中的声明定义
static const std::string = “xxx”; // 异常!
有缘再改!
(四)类中的特殊成员函数
1. 静态成员函数
静态成员函数的特点
-
静态成员函数内部不能使用非静态的数据成员和非静态的成员函数
-
静态成员函数内部只能调用静态数据成员和静态的成员函数
原因:无 this 指针
-
可以直接使用类名,来调用静态成员函数
tips:声明时需要加上 static,但是在实现时,不能加 static 修饰
2. const 成员函数
const 成员函数的特点
- const 成员函数的 this 指针类型为 const [classname] *const this
- 与非 const 成员函数可以进行重载,但 const 成员函数不能修改数据成员
- const 对象只能调用 const 成员函数,非 const 成员函数均可调用
定义方法
void point() const; // 静态成员函数,通常优先写静态成员函数,再写普通成员函数
void point(); // 普通成员函数,非 const 对象优先调用普通成员函数
(五)对象的组织
- const 对象
- 指向对象的指针
- 对象数组
- 堆对象
对象数组
代码示例
Point pt1[3] = {Point(1, 2), Point(3, 4)} // 会有三个对象创建
Point pt2[3] = {{1, 2}, {3, 4}} // 等价
Point pt2[3] = {(1, 2), (3, 4)} // 错误
/*
* (1, 2) 为逗号表达式,即执行表达式中最后的部分
* pt2[0] = Point(2, 0);
* pt2[1] = Point(4, 0);
*/
二、几种设计模式
1. 单例模式
实现代码
// Singleton.h
class Singleton
{
public:
static Singleton *getInstance()
{
if(nullptr == _pInstance)
_pInstance = new Singleton();
return _pInstance;
}
static void destory()
{
if(_pInstance)
{
delete _pInstance;
_pInstance = nullptr;
}
}
private:
static Singleton *_pInstance;
singleton() {}
~singleton() {}
}
// Singleton_test.cc 文件
Singleton *Singleton::_pInstance = nullptr;
int main()
{
Singleton *ps = Singleton::getInstance();
Singleton::destroy();
return 0;
}
单例模式的自动释放
四种释放方法
- 以友元的形式
- 内部类 + 静态数据成员形式
atexit
形式pthread_once
形式
1. 以友元的形式
#include <iostream>
using std::cout;
using std::endl;
class AutoRelease;
class Singleton
{
// 友元函数,实现自动释放
friend AutoRelease;
public:
static Singleton *getInstance()
{
if(nullptr == _pInstance)
_pInstance = new Singleton();
return _pInstance;
}
// 用友元函数替代 destroy()
/* static void destroy() */
/* { */
/* delete _pInstance; */
/* _pInstance = nullptr; */
/* } */
private:
Singleton()
{
cout << "Singleton()" << endl;
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
static Singleton *_pInstance;
};
// 饿汉模式和懒汉模式
// 懒汉模式 - 在类加载时,不创建实例,所以类加载速度快,但运行时获取对象的速度慢
// 饿汉模式 - 在类加载时完成初始化,所以类加载慢,但获取对象的速度快
// 在多线程环境,使用懒汉模式可能会导致单例模式的失效
/* Singleton *Singleton::_pInstance = nullptr; // 懒汉模式 */
Singleton *Singleton::_pInstance = getInstance(); // 饿汉模式
class AutoRelease
{
public:
AutoRelease()
{
cout << "AutoRelease()" << endl;
}
~AutoRelease()
{
cout << "~AutoRelease()" << endl;
delete Singleton::_pInstance;
Singleton::_pInstance = nullptr;
}
};
int main()
{
// 创建 Singleton 的单例对象 _pInstance
Singleton::getInstance();
// 生成 AutoRelease 对象,其中该友元类在生命周期结束后会自动调用析构函数
// 在 ~AutoRelease() 中,会销毁单例对象 _pInstance
AutoRelease ar; // 栈对象
// 由于第一次 AutoRelease 的析构函数中已经删除单例对象 _pInstance
// 本次的析构函数中不会再次调用 ~Singleton()
AutoRelease ar2; // 栈对象
return 0;
}
/***
* 运行结果:
* Singleton()
* AutoRelease()
* AutoRelease()
* ~AutoRelease()
* ~Singleton()
* ~AutoRelease()
*/
2. 内部类 + 静态数据成员形式
#include <iostream>
using std::cout;
using std::endl;
class Singleton
{
public:
static Singleton *getInstance()
{
if(nullptr == _pInstance)
_pInstance = new Singleton();
return _pInstance;
}
/* static void destroy() */
/* { */
/* delete _pInstance; */
/* _pInstance = nullptr; */
/* } */
private:
Singleton()
{
cout << "Singleton()" << endl;
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
// 内部私有成员
class AutoRelease
{
public:
AutoRelease()
{
cout << "AutoRelease()" << endl;
}
~AutoRelease()
{
cout << "~AutoRelease()" << endl;
delete _pInstance;
_pInstance = nullptr;
}
};
static Singleton *_pInstance;
// 内部私有成员
static AutoRelease _ar;
};
/* Singleton *Singleton::_pInstance = nullptr; // 懒汉模式 */
Singleton *Singleton::_pInstance = getInstance(); // 饿汉模式
// 初始化
Singleton::AutoRelease Singleton::_ar;
int main()
{
Singleton::getInstance();
return 0;
}
/***
* 运算结果:
* AutoRelease()
* Singleton()
* ~AutoRelease()
* ~Singleton()
*/
3. atexit
形式
#include <iostream>
#include <stdlib.h>
using std::cout;
using std::endl;
// atexit 介绍
// 使用方法:int atexit(void (*function)(void));
// 作用:调用该方法后,在程序正常结束后,调用该函数指针指向的函数
class Singleton
{
public:
static Singleton *getInstance()
{
if(nullptr == _pInstance)
{
_pInstance = new Singleton();
atexit(destroy);
}
return _pInstance;
}
static void destroy()
{
delete _pInstance;
_pInstance = nullptr;
}
private:
Singleton()
{
cout << "Singleton()" << endl;
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
static Singleton *_pInstance;
};
/* Singleton *Singleton::_pInstance = nullptr; // 懒汉模式 */
Singleton *Singleton::_pInstance = getInstance(); // 饿汉模式
int main()
{
Singleton::getInstance();
return 0;
}
4. pthread_once
形式 - Linux 环境
#include <iostream>
#include <pthread.h>
#include <stdlib.h>
using std::cout;
using std::endl;
// pthread_onec 介绍
// 使用方法:int pthread_once(pthread_once_t * once_-control
// void (*init_routine)(void));
// 作用:在首次执行 pthread_once 时,会执行 init 函数
// 只有首次运行该函数时,才会执行 init,由宏 PTHREAD_ONCE_INIT 来记录
// 所以,在多线程环境中,pthread_once 依旧可以用懒汉模式初始化变量
// pthread_once 是 POSIX - Linux 下的标准,所以只能在 Linux 系统中使用,跨平台性较差
// 执行时需要加载线程库:
// g++ Singleton_pthread_once -lpthread
class Singleton
{
public:
static Singleton *getInstance()
{
pthread_once(&_once, init);
return _pInstance;
}
static void init()
{
_pInstance = new Singleton();
atexit(destroy);
}
static void destroy()
{
delete _pInstance;
_pInstance = nullptr;
}
private:
Singleton()
{
cout << "Singleton()" << endl;
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
static Singleton *_pInstance;
static pthread_once_t _once;
};
Singleton *Singleton::_pInstance = nullptr; // 懒汉模式
pthread_once_t Singleton::_once = PTHREAD_ONCE_INIT; // 宏
int main()
{
Singleton::getInstance();
return 0;
}
2. PIMPL 模式 - 编译防火墙
PIMPL
(Private Implementation
或 Pointer to Implementation
):通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏。
PIMPL
又称作“编译防火墙”,它的视线中就用到了嵌套类。
PIMPL
设计模式的优点
- 提高编译速度
- 实现信息隐藏
- 减小编译依赖,可以用最小的代价平滑的升级库文件
- 接口与实现进行解耦
- 移动语义友好
案例代码
Line.h
#ifndef __LINE_H
#define __LINE_H
class Line
{
public:
Line(int, int, int, int);
~Line();
void printLine() const;
private:
// 将 Line 的私有成员的实现封装在 class - LineImpl 中
class LineImpl;
LineImpl *_pimpl;
};
#endif // __LINE_H
Line.cc
#include <iostream>
#include "Line.h"
using std::cout;
using std::endl;
// 实现 Line 的私有成员的实现
class Line::LineImpl
{
public:
// 构造函数
LineImpl(int x1, int y1, int x2, int y2)
: _pt1(x1, y1)
, _pt2(x2, y2)
{}
void printLineImpl() const;
private:
// 私有类
class Point
{
public:
Point(int x = 0, int y = 0)
: _x(x), _y(y)
{}
void print() const;
private:
int _x;
int _y;
};
Point _pt1;
Point _pt2;
};
void Line::LineImpl::Point::print() const
{
cout << "(" << _x
<< ", " << _y
<< ")";
}
void Line::LineImpl::printLineImpl() const
{
_pt1.print();
cout << " ----> ";
_pt2.print();
cout << endl;
}
Line::Line(int x1, int y1, int x2, int y2)
: _pimpl(new LineImpl(x1, y1, x2, y2))
{}
Line::~Line()
{
delete _pimpl;
_pimpl = nullptr;
}
void Line::printLine() const
{
_pimpl->printLineImpl();
}
Line_test.cc
#include "Line.h"
int main()
{
Line the_line(1, 2, 3, 4);
the_line.printLine();
return 0;
}
三、new 与 delete 表达式
工作步骤
new 表达式工作步骤
- 调用名为
operator new
的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象 - 运行该类型的一个构造函数初始化对象
- 返回指向新分配并构造的构造函数对象的指针`
delete 表达式工作步骤
- 调用析构函数,回收对象中数据成员所申请的资源
- 调用名为
operator delete
的标准库函数释放该对象所用的内存
顺序不能调换:operator delete
会将从栈空间指向堆空间的指针删除,从而导致析构函数无法 delete
实现代码
// Student.cc
#include <string.h>
class Student
{
public:
Student(int id, const char *name) // 构造函数
:_id(id)
, _name(new char[strlen(name) + 1]())
{
strcpy(_name, name);
}
~Student() // 析构函数
{
delete[] _name;
_name = nullptr;
}
// 默认为静态函数,所以没有 this 指针
void *operator new(size_t sz) // operator new 函数的重载
{
// 1. 调用名为 operator new的标准库函数
// 分配足够大的原始的未类型化的内存,以保存指定类型的一个对象
void *pret = malloc(sz); // 只能用 malloc 以避免死循环
return pret;
}
// 默认为静态函数,所以没有 this 指针
void operator delete(void *pointer) // operator delete 函数的重载
{
free(pointer);
}
private:
int _id;
char *_name;
}
int main()
{
Student *pstu = new Student(123, "LiMing");
// 函数体
delete pstu;
return 0;
}
实现只能生成栈对象
tips:栈区(stack) - 自动分配释放,存放函数的参数值,局部变量的值等
堆对象: // new 表达式的工作步骤
1. operator new 函数 ——> 私有
2. 构造函数
3. 指针
所以将 operator new/delete 设置为私有
实现代码
private:
void *operator new(size_t sz) // operator new 函数的重载
{
// 1. 调用名为 operator new 的标准库函数
// 分配足够大的原始的未类型化的内存,以保存指定类型的一个对象
void *pret = malloc(sz);
return pret;
}
// 可省去对 operator delete 的私有操作
void operator delete(void *pret) // operator delete 函数的重载
{
free(pret);
}
实现只能生成堆对象
tips:堆区(heap) - 由程序员分配释放,若不释放,程序结束时由 OS 回收
栈对象必须确保构造函数和析构函数同时为 public
将 析构函数 设为 private ——> delete 失效 ——> 在 class 的 public 中创建 destory 函数,调用 private 的析构函数
实现代码
public:
// 由于要调用 this,所以不能将该函数设置为 static
void destory()
{
/* this->~Student(); // 用 this 指向本身,从而调用 ~Student() 函数
// 但是删除仍不干净,operator delete 函数未被调用 */
delete this;
}
private:
~Student() // 析构函数
{
delete[] _name;
_name = nullptr;
}
四、类域
作用域分为类作用域、类名的作用域以及对象的作用域
类的定义没有生存周期的概念,但类定义有作用域和可见域。
使用类名创建对象时,前提是类名可见,而类名是否可见,取决于类定义的可见域。
tip:一个重点为:当内部作用域声明了同外部作用域函数名相同的函数时(函数名相同即可!返回类型 和 形参列表 无所谓!),会隐藏外部作用域的所有同名函数。除非将外部作用域的同名函数定义为 virtual 虚函数。这一点在 继承和多态 中集中体现