Day 5:异常、this、static 和 BMP 图片显示
今天目标:继续完善类的封装,掌握异常处理、this 指针、静态成员,并用类封装屏幕绘制和 BMP 图片显示。
对应代码:
2-代码/day5-异常/list.hpp
2-代码/day5-异常/list.cpp
2-代码/day5-异常/dir.hpp
2-代码/day5-异常/dir.cpp
2-代码/day5-异常/24-异常示例.cpp
2-代码/day5-异常/25-this指针.cpp
2-代码/day5-异常/26-static成员示例.cpp
2-代码/day5-异常/tongxun_v3.cpp
2-代码/day5-异常/28-绘制位图/
1、List 链表补充
Day5 的 List 链表用来保存字符串,例如文件名:
using Type = std::string;
常用接口:
void push_back(Type x);
void push_front(Type x);
void pop_back();
void pop_front();
int size() const;
bool empty() const;
Type getElem(int n) const;
按位置获取元素:
Type List::getElem(int n) const
{
if (n < 1 or n > _size)
throw std::out_of_range("n out of range!");
Node *p = _head;
for (int i = 1; i < n; i++)
{
p = p->_next;
}
return p->_data;
}
说明:
1. 当前链表下标从 1 开始。
2. 获取元素前要判断范围。
3. 越界时抛出 std::out_of_range,避免访问空指针。
2、Dir 类筛选 BMP 文件
Dir 类负责打开目录、读取目录项、筛选 BMP 文件。
主要接口:
class Dir
{
public:
Dir(std::string path);
~Dir();
List getAllFilenames();
List getBitmaps();
bool isBitmap(std::string pathfile);
};
构造函数打开目录:
_pdir = opendir(_path.c_str());
if (_pdir == nullptr)
throw std::runtime_error("open dir error");
析构函数关闭目录:
closedir(_pdir);
筛选 BMP 文件:
bool Dir::isBitmap(std::string pathfile)
{
return pathfile.find(".bmp") != std::string::npos;
}
使用方式:
Dir dir("./");
List list = dir.getBitmaps();
list.print();
一句话理解:
Dir 把 opendir、readdir、closedir 封装成对象操作,并用 List 保存读取到的文件名。
3、异常处理
异常用于处理运行时错误。
抛出异常:
throw std::logic_error("x out of range!");
捕获异常:
try
{
foo(-1);
}
catch (int err)
{
cout << "捕获int类型异常" << endl;
}
catch (double err)
{
cout << "捕获double类型异常" << endl;
}
如果抛出的是标准异常,例如 std::logic_error,通常这样接:
try
{
foo(-1);
}
catch (const std::exception& e)
{
cout << e.what() << endl;
}
标准异常类型需要头文件:
#include <stdexcept>
常见异常:
std::logic_error 逻辑错误
std::runtime_error 运行时错误
std::out_of_range 越界错误
std::bad_alloc 内存申请失败
注意:
1. throw 后面的代码不会继续执行。
2. catch 按顺序匹配异常类型。
3. 没有被捕获的异常会继续向上抛出。
4. 析构函数中不建议抛异常。
4、this 指针
对象调用非静态成员函数时,编译器会自动把对象地址传给函数,这个隐藏参数就是 this。
示例:
class Demo
{
public:
void setX(int x)
{
_x = x; // 等价于 this->_x = x;
}
int getX() const
{
return _x; // 等价于 return this->_x;
}
private:
int _x;
};
理解:
this 指向当前调用成员函数的那个对象。
类型:
普通成员函数中:this 是 类名* const
const 成员函数中:this 是 const 类名* const
补充:
成员变量存放在每个对象里。
成员函数存放在代码段,同类型对象共用一份。
this 用来区分当前函数正在操作哪个对象。
空类对象:
没有数据成员的对象也占空间,通常 sizeof(空类) == 1。
原因是每个对象都需要有唯一地址。
5、static 静态成员
static 修饰类成员时,表示这个成员属于类,而不是属于某一个对象。
静态成员变量:
class Demo
{
private:
static int cnt;
};
int Demo::cnt = 0;
特点:
1. 所有对象共享同一份静态成员变量。
2. 不占用普通对象的成员空间。
3. 必须在类外单独定义和初始化。
静态成员函数:
static int getCnt()
{
return cnt;
}
调用方式:
Demo::getCnt();
注意:
1. 静态成员函数没有 this 指针。
2. 静态成员函数不能直接访问普通成员变量。
3. 静态成员函数不能写成 const 成员函数。
Day5 示例中用 cnt 统计当前 Demo 对象数量:
构造函数中 cnt++
析构函数中 cnt--
6、通讯录封装
tongxun_v3.cpp 把通讯录封装成 Addressbooks 类型。
成员数据:
Person personArray[MAX] 联系人数组
int m_Size 当前联系人数量
主要接口:
addPerson() 添加联系人
showPerson() 显示联系人
isExist() 判断联系人是否存在
deletePerson() 删除联系人
findPerson() 查找联系人
modifyPerson() 修改联系人
cleanPerson() 清空联系人
封装思想:
1. Person 结构体放在 Addressbooks 内部,只服务通讯录。
2. 联系人数组和数量设为 private,外部不能随便修改。
3. 外部只能通过 public 成员函数操作通讯录。
7、屏幕与图形类封装
28-绘制位图 目录把屏幕绘制拆成多个类。
类的分工:
Point 保存坐标 x、y
Color 保存 RGB 颜色,并转换成整数颜色值
Screen 打开 /dev/fb0,mmap 映射显存,提供 drawPoint
Rect 保存矩形宽高、位置、颜色,并绘制矩形
Circle 保存圆心、半径、颜色,并绘制圆
Bitmap 读取 BMP 文件,并绘制图片
屏幕绘制核心:
*(_addr + _w * y + x) = color;
含义:
显存可以看成一维数组。
第 y 行第 x 列的位置 = _addr + _w * y + x。
构造函数适合做初始化:
Screen screen("/dev/fb0");
使用方式:
Bitmap bmp("./1.bmp");
bmp.draw(screen);
8、BMP 图片显示
BMP 是无压缩位图文件,常见 24 位和 32 位格式。
文件关键位置:
0x00 文件标识,BMP 文件以 'B' 'M' 开头
0x0A 像素数组起始偏移
0x12 图片宽度
0x16 图片高度
0x1C 色深
0x1E 压缩方式,0 表示无压缩
像素格式:
24 位 BMP:B G R
32 位 BMP:B G R A
每行补齐:
BMP 每一行字节数必须是 4 的倍数,不足部分要跳过。
方向判断:
width > 0:每行从左往右保存
width < 0:每行从右往左保存
height > 0:整张图从下往上保存
height < 0:整张图从上往下保存
绘制坐标:
int x = _w > 0 ? j : width - 1 - j;
int y = _h > 0 ? height - 1 - i : i;
screen.drawPoint(x + x0, y + y0, Color(r, g, b).toInt());
说明:
如果 height > 0,BMP 第一行像素其实是图片最下面一行,
所以要画到屏幕的 height - 1 - i 行,避免图片上下颠倒。
9、循环播放 BMP 图片
把一个目录中的 BMP 文件名读取出来,保存到 List,再循环显示:
Dir dir("./bmp");
List bmp_list = dir.getBitmaps();
Screen screen("/dev/fb0");
while (true)
{
for (int i = 1; i <= bmp_list.size(); i++)
{
std::string filename = bmp_list.getElem(i);
Bitmap bmp(filename);
bmp.draw(screen);
sleep(1);
}
}
运行方式:
./a.out ./bmp
./a.out ./bmp 2
说明:
第 1 个参数:BMP 文件夹路径。
第 2 个参数:每张图片显示的秒数。
10、Day5 总结
1. List::getElem 通过范围判断和异常避免越界访问。
2. Dir 类封装目录读取,并筛选 BMP 文件。
3. throw 抛异常,try/catch 捕获异常。
4. this 指针表示当前对象地址,由编译器自动传入。
5. static 成员属于类,所有对象共享,没有 this。
6. 通讯录示例体现了 private 数据 + public 接口的封装。
7. Screen 类封装了 framebuffer 打开、mmap 映射和画点。
8. Rect、Circle、Bitmap 都通过 draw(Screen&) 绘制到屏幕。
9. BMP 像素按 BGR 保存,每行 4 字节对齐。
10. BMP 高度为正时,像素数据从下往上保存,绘制时要翻转 y 坐标。
一句话记忆:
Day5 的重点是把错误处理、对象自身 this、类共享 static 和屏幕图片显示,都放进类的封装思路里理解。






Comments | NOTHING