C++学习 day5

发布于 26 天前  24 次阅读


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 和屏幕图片显示,都放进类的封装思路里理解。

"When faced with uncertainty, ask the spring breeze."