前言
笔者近期学习到了天津理工大学计算机科学与工程系杨淑莹老师所授《数字图像处理》一课,所用配套教材为《VC++图象处理程序设计》,由于笔者在代码方面表现实在不堪以至于要逐字学习代码,遂下定决心痛改前非狠狠学之,将笔者所学艰难历程存留成文,也便利其他志同道合之同学,与君共勉。
在撰写本文的过程中,笔者的思路左右横跳,想到哪查到哪,因此很多内容与程序本身无关,完全是程序的延伸,读者可选择性的进行阅读。
笔者引用了网络及本站的大量相关材料,如有侵权或不宜转载内容请与笔者联系。
本文是个人学习笔记,资料来源参差不齐,笔者亦无法判断资料是否准确,如有错误烦请读者在评论区勘误。
一、整体代码
本节所学为随书光盘第二章特效显示Cdib.cpp,先贴代码。
#include "stdafx.h"
#include "cdib.h"
#include "windowsx.h"
#include "math.h"
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)
CDib::CDib()
{
size=0;
}
CDib::~CDib()
{
GlobalFreePtr(m_pBitmapInfo);
}
void CDib::LoadFile(const char* dibFileName)
{
strcpy(m_fileName,dibFileName);
CFile dibFile(m_fileName, CFile::modeRead);
dibFile.Read((void*)&bitmapFileHeader,sizeof(BITMAPFILEHEADER));
if (bitmapFileHeader.bfType == 0x4d42)
{
DWORD fileLength = dibFile.GetLength();
size = fileLength -sizeof(BITMAPFILEHEADER);
pDib =(BYTE*)GlobalAllocPtr(GMEM_MOVEABLE, size);
dibFile.Read((void*)pDib, size);
dibFile.Close();
m_pBitmapInfo = (BITMAPINFO*) pDib;
m_pBitmapInfoHeader = (BITMAPINFOHEADER*) pDib;
m_pRGB = (RGBQUAD*)(pDib +
m_pBitmapInfoHeader->biSize);
int m_numberOfColors = GetNumberOfColors();
if (m_pBitmapInfoHeader->biClrUsed == 0)
m_pBitmapInfoHeader->biClrUsed =
m_numberOfColors;
DWORD colorTableSize = m_numberOfColors *
sizeof(RGBQUAD);
m_pData = pDib + m_pBitmapInfoHeader->biSize
+ colorTableSize;
if (m_pRGB == (RGBQUAD*)m_pData) // No color table
m_pRGB = NULL;
m_pBitmapInfoHeader->biSizeImage = GetSize();
m_valid = TRUE;
}
else
{
m_valid = FALSE;
AfxMessageBox("This isn't a bitmap file!");
}
}
BOOL CDib::IsValid()
{
return m_valid;
}
char* CDib::GetFileName()
{
return m_fileName;
}
UINT CDib::GetWidth()
{
return (UINT) m_pBitmapInfoHeader->biWidth;
}
UINT CDib::GetHeight()
{
return (UINT) m_pBitmapInfoHeader->biHeight;
}
DWORD CDib::GetSize()
{
if (m_pBitmapInfoHeader->biSizeImage != 0)
return m_pBitmapInfoHeader->biSizeImage;
else
{
DWORD height = (DWORD) GetHeight();
DWORD width = (DWORD) GetWidth();
return height * width;
}
}
UINT CDib::GetNumberOfColors()
{
int numberOfColors;
if ((m_pBitmapInfoHeader->biClrUsed == 0) &&
(m_pBitmapInfoHeader->biBitCount < 9))
{
switch (m_pBitmapInfoHeader->biBitCount)
{
case 1: numberOfColors = 2; break;
case 4: numberOfColors = 16; break;
case 8: numberOfColors = 256;
}
}
else
numberOfColors = (int) m_pBitmapInfoHeader->biClrUsed;
return numberOfColors;
}
BYTE* CDib::GetData()
{
return m_pData;
}
RGBQUAD* CDib::GetRGB()
{
return m_pRGB;
}
BITMAPINFO* CDib::GetInfo()
{
return m_pBitmapInfo;
}
WORD CDib::PaletteSize(LPBYTE lpDIB)
{
return (DIBNumColors(lpDIB) * sizeof(RGBTRIPLE));
}
WORD CDib::DIBNumColors(LPBYTE lpDIB)
{
WORD wBitCount; // DIB bit count
wBitCount = ((LPBITMAPCOREHEADER)lpDIB)->bcBitCount;
switch (wBitCount)
{
case 1:
return 2;
case 4:
return 16;
case 8:
return 256;
default:
return 0;
}
}
void CDib::SaveFile(const CString filename)
{
strcpy(m_fileName,filename);
CFile dibFile(m_fileName, CFile::modeCreate|CFile::modeWrite);
dibFile.Write((void*)&bitmapFileHeader,sizeof(BITMAPFILEHEADER));
dibFile.Write((void*)pDib, size);
dibFile.Close();
}
二、代码分析
#include "stdafx.h"
#include "cdib.h"
#include "windowsx.h"
#include "math.h"
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)
这段代码中,#include
指令用于包含头文件。
stdafx.h 是预编译头文件的文件名,通常是在 Visual Studio 中使用的 C++ 项目中使用的默认头文件名。在使用预编译头文件的情况下,预编译头文件的内容会被预先编译并保存为二进制文件,从而提高编译速度。stdafx.h"文件通常包含常用的系统头文件和项目特定的头文件,这些头文件会被经常引用,并且很少发生更改。
cdib.h
是一个 C++ 头文件,它提供了一些用于处理位图文件的函数和类。它通常用于 Windows 平台下的开发,可以实现加载、保存和处理位图文件等功能。其中包括了 CDib
类,该类可以打开、创建、保存和显示 BMP 格式的图像文件。需要注意的是,cdib.h
并不是标准库的一部分,可能需要下载和安装相应的库文件后才能使用。
windowsx.h
是一个 Windows 编程头文件,提供了许多实用的宏和函数,以帮助开发者编写更加简洁高效的 Windows 程序。其中包括一些处理消息、控件、对话框等常用功能的宏和函数,GetDlgItemInt
、GetDlgItemText
、SetDlgItemInt
、SetDlgItemText
、Button_SetCheck
、ListBox_AddString
、ComboBox_AddString
等。此外,它还包括了一些宏定义,如 HINST_COMMCTRL
、HINST_THISDLL
等,以及一些结构体和数据类定义。windows.h是 Windows SDK 中的一部分,如果需要使用其中的功能,需要在程序中包含该头文件,并链接相应的库文件。
math.h
是C/C++标准库的头文件,包含各种数学函数。
#define
指令用于定义预处理宏。define语句通常格式如下:
#define 宏名称 宏替换文本
其中,宏名称是定义的宏的名称,宏替换文本是宏名称在代码中被替换成的文本。宏名称可以是任何有效的标识符,宏替换文本可以是任何合法的C/C++代码,可以是常量、变量、表达式、语句等等。需要注意的是,在宏替换文本中,可以使用#和##运算符。#运算符可以将宏参数转换为字符串,##运算符可以将两个标记组合成一个标记。
例如:
#define MAX(x,y) ((x)>(y)?(x):(y))
这个宏定义了一个求两个数中较大值的宏。在宏替换文本中,使用了三个标记,其中第一个标记x和第二个标记y表示宏的两个参数,第三个标记是一个三目运算符,用来比较两个参数的大小并返回较大值。
使用时,可以通过宏名称来调用宏,例如:
int a = 5, b = 8;
int max_value = MAX(a, b);
在这个例子中,宏调用将被展开成以下代码:
int max_value = ((a)>(b)?(a):(b));
WIDTHBYTES(bits)
宏是计算图像每行字节数的一个宏定义。((bits) + 31) / 32 * 4
的含义是将位数(bits)加上31,再除以32向下取整,最后再乘以4,得到每行的字节数,这是一种常用的计算方式。
这段代码用于计算位图数据在内存中的每行字节数。在位图数据存储中,每个像素的颜色值都用二进制数来表示,多个像素组成的一行数据也是一组二进制数。因为计算机内存中的数据存储是按字节为单位进行的,所以需要将每行数据的位数转换为字节数来计算。其中,WIDTHBYTES是宏定义的名称,bits是位图数据每行的位数,通过这个宏定义可以计算出每行的字节数。具体的计算过程为,首先将位数加上31,然后除以32向下取整,最后乘以4。这个计算公式可以保证计算出来的结果是4的倍数,因为每个像素的颜色值是一个字节,所以每行的字节数需要是4的倍数,这样才能在内存中正确地存储和处理位图数据。
当我们想要知道一个图像占用的内存大小时,需要先知道图像的宽度和高度,再知道每个像素的位数(比如 RGB 图像中每个像素通常有 24 位,也就是 3 个字节),根据这些信息计算出图像占用的总字节数。但是,为了在内存中方便地存储图像数据,我们需要将图像的每一行都填充到 4 字节的整数倍,即每一行的字节数都应该是 4 的倍数。这就是本代码中 WIDTHBYTES
函数的作用。
至于为什么是/32*4而不是/8,笔者在另一篇帖子找到了解释。
位图的一个像素值所占的字节数:
当biBitCount=1时,8个像素占1个字节;
当biBitCount=4时,2个像素占1个字节;
当biBitCount=8时,1个像素占1个字节;
当biBitCount=24时,1个像素占3个字节,此时图像为真彩色图像。当图像不是为真彩色时,
图像文件中包含颜色表,位图的数据表示对应像素点在颜色表中相应的索引值,当为真彩色时,
每一个像素用三个字节表示图像相应像素点彩色值,每个字节分别对应R、G、B分量的值,这时候图像文件中没有颜色表。
上面我已经讲过了,Windows规定图像文件中一个扫描行所占的字节数必须是4的倍数(即以字为单位),不足的以0填充,
图像文件中一个扫描行所占的字节数计算方法:// biwidth 像素,biBitCount 每个像素占的字节数
DataSizePerLine= (biWidth* biBitCount+31)/32*4;// 一个扫描行所占的字节数最主要是/32*4而不能直接/8,见下面例子:
举一个例子,对于2色图,如果图象宽(每行像素数)是31,则每一行需要31位存储(对于2色图是每个像素占一位),
合3个字节加7位,因为字节数必须是4的整倍数,所以应该是4,
而此时biWidth=31,biBitCount=1,WIDTHBYTES(31*1)=(31+31)/32*4=62/32*4=1*4=4,和我们设想的一样。
再举一个256色的例子,如果图象宽(每行像素数)是31,
则每一行需要31个字节存储(对于256色图是每个像素占8位即一个字节),因为字节数必须是4的整倍数,所以应该是32,
而此时的biWidth=31,biBitCount=8,WIDTHBYTES(31*8)=(31*8+31)/32*4=31*9/32*4=279/32*4=8*4=32
————————————————
版权声明:本文为CSDN博主「iduanbin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_27396861/article/details/85525788
CDib::CDib()
{
size=0;
}
这是一个CDib类的默认构造函数,它没有任何参数。在函数体中,将CDib对象的size成员变量初始化为0。
CDib是一个自定义的类,代表了一个Windows DIB(设备无关位图)图像对象。它包含了图像的数据、颜色表和其他元数据,并提供了一些方法来处理和操作这些数据。这个类可以被用来读取、写入、显示和修改DIB图像数据。
构造函数是一种特殊的成员函数,它用于在创建对象时初始化对象的数据成员。当创建一个对象时,构造函数会自动调用,以确保对象在被使用之前被正确地初始化。构造函数的名称必须与类的名称相同,它没有返回类型,也不可以被显式地调用,而是隐式地在对象创建时调用。
在构造函数中,你可以使用成员初始化列表来初始化对象的数据成员,也可以在函数体中执行其他的初始化操作。构造函数还可以重载,你可以根据需要定义多个构造函数,以支持不同的对象初始化方式。如果没有为类定义构造函数,则编译器将会自动生成一个默认的无参构造函数,该构造函数不会执行任何初始化操作。
总之,构造函数是用于初始化对象的特殊成员函数,它在对象创建时自动调用,以确保对象在使用之前被正确地初始化。
默认构造函数是在C++中一种特殊的构造函数,它不带任何参数,也不执行任何初始化操作。当我们没有提供任何构造函数时,编译器会默认生成一个无参的默认构造函数。
默认构造函数可以在对象创建时自动被调用,用于初始化对象的数据成员和状态。如果我们不提供自定义的构造函数,则可以使用默认构造函数来初始化数据成员的默认值,避免未定义的行为和内存崩溃。
当我们没有提供任何构造函数时,编译器会默认生成一个无参的默认构造函数,这个默认构造函数会在类的公共部分被定义。因此,如果我们没有提供任何构造函数,编译器会自动生成一个默认构造函数并在类的公共部分进行定义,这样我们就可以使用默认构造函数来初始化对象。如果我们在类的公共部分未定义默认构造函数,则编译器无法自动生成默认构造函数,这将导致编译错误。默认构造函数必须在类的公共部分进行声明或定义。
默认构造函数有几个特点:
- 默认构造函数必须在类的公共部分声明或定义,否则编译器将无法自动生成。
- 如果我们定义了自定义的构造函数,但没有定义默认构造函数,编译器将不会生成默认构造函数,这意味着对象创建时必须使用自定义构造函数进行初始化。
- 在某些情况下,我们可能需要提供一个显式的默认构造函数,以便在创建对象时不执行任何初始化操作,或者对数据成员进行默认初始化。
- 默认构造函数也可以在类的继承中使用,用于初始化基类的数据成员和虚表指针等。在继承中,如果派生类没有显式提供构造函数,则可以使用基类的默认构造函数来初始化基类数据成员。
如果你没有在类的公共部分定义默认构造函数,编译器会自动生成一个默认构造函数,这个默认构造函数将不做任何初始化操作。但是,如果你在类的公共部分定义了自定义构造函数,编译器将不会自动生成默认构造函数,除非你显式地声明和定义一个无参的默认构造函数。
需要注意的是,如果你的类继承自其他类,且基类没有默认构造函数,那么你必须显式定义一个构造函数来调用基类的构造函数,否则编译器将无法生成默认构造函数。因此,在这种情况下,你必须提供自定义的构造函数来初始化对象,并确保基类的构造函数被正确地调用。
在以下情况下,类的公共部分可能未定义默认构造函数:
- 当类定义了自定义构造函数,但未定义无参的默认构造函数时,编译器将不会自动生成默认构造函数。
- 当类定义了私有的默认构造函数时,该构造函数将无法在类的公共部分访问,因此在类的公共部分将无法定义默认构造函数。
- 当类的成员变量是无法默认构造的类型(如引用类型、const类型、数组类型等)时,编译器将无法为该类生成默认构造函数。
在这些情况下,如果你需要使用默认构造函数来初始化对象,则必须显式地定义一个无参的默认构造函数,并将其放在类的公共部分。注意,在定义默认构造函数时,你应该确保正确地初始化类的数据成员,否则可能会导致未定义的行为或内存泄漏。
其他关于默认构造函数的内容,可参考C++默认构造函数——深入理解_c++缺省构造函数_HAN-Kai的博客-CSDN博客
在C++中,双冒号(::)是作用域解析运算符(scope resolution operator)的一种表达方式。它通常用于访问命名空间、类、结构体、枚举、全局变量或函数等定义在特定作用域内的成员。例如,当你想要使用某个类的成员时,你可以使用双冒号来指定这个成员所属的类。在上面的代码中,CDib类的构造函数是通过双冒号来指定的,表明该函数属于CDib类。
在这段代码中,size是CDib类的一个成员变量,表示图像数据的大小。可以理解为这个CDib对象所包含的图像数据占用的字节数。在构造函数中将size初始化为0,表示该CDib对象目前还没有分配内存来存储图像数据。
CDib::~CDib()
{
GlobalFreePtr(m_pBitmapInfo);
}
这段代码是一个C++类CDib的析构函数。析构函数的作用是释放CDib对象的位图信息数据所分配的内存。
析构函数是一种特殊类型的函数,用于在对象的生命周期结束时进行清理和释放资源。在C++中,每个类都可以定义一个析构函数。当对象的生命周期结束时(例如对象被销毁、变量超出其作用域或程序结束时),析构函数会被自动调用。
析构函数的主要作用是释放对象所占用的内存和资源。它可以用于释放动态分配的内存、关闭文件、释放网络连接等。通过使用析构函数,可以确保对象所占用的资源被正确地释放,从而避免内存泄漏和其他资源泄漏的问题。
C++中的析构函数名称与类名相同,但前面加上一个波浪号(~)。析构函数没有返回类型,也不带任何参数。例如,在一个名为CDib的类中,析构函数的名称应该是 ~CDib()。
该函数使用GlobalFreePtr()函数释放m_pBitmapInfo指针所指向的内存。GlobalFreePtr()函数是Windows API函数,用于释放使用GlobalAlloc()函数分配的内存。
GlobalFreePtr()是Windows API中的一个函数,用于释放由GlobalAlloc()函数分配的全局内存。它的函数原型定义在Windows.h头文件中。该函数有一个参数,即指向要释放的内存块的指针。调用GlobalFreePtr()函数时,指定的内存块将被释放并标记为未分配状态,可以重新分配使用。如果指定的指针参数为空(NULL),则该函数将不执行任何操作,也不会出错。
GlobalFreePtr()函数的使用需要注意以下几点:
- 只能用于释放由GlobalAlloc()函数分配的内存块,不适用于其他内存分配函数(如malloc()或new)分配的内存。
- 被释放的内存块应该是以HGLOBAL句柄的形式来管理。因此,在调用GlobalAlloc()函数时,需要将第一个参数设置为GHND或GMEM_MOVEABLE,以便返回一个有效的HGLOBAL句柄。
- GlobalFreePtr()函数不会修改指针变量本身,因此在调用完函数之后,指针变量仍然指向之前被释放的内存块,这可能会导致内存泄漏和悬挂指针等问题。因此,在释放内存块之后,建议将指针变量设置为NULL,以避免此类问题的发生。
m_pBitmapInfo是一个指针,因为它是用于存储位图信息数据的变量的地址。
在C++中,指针是一种变量类型,它存储另一个变量的内存地址。通过使用指针,可以动态地分配内存、访问堆内存中的数据、在函数之间传递复杂的数据结构等。
在这段代码中,m_pBitmapInfo指针被用来存储位图信息数据的地址,该数据通常包含有关位图文件格式、尺寸、颜色深度等信息。由于位图信息数据通常较大,因此将其存储在堆上,并使用指针进行引用,可以更有效地利用内存。同时,在CDib对象的生命周期结束时,析构函数会释放m_pBitmapInfo指向的内存,避免内存泄漏。
GlobalAlloc()函数是Windows API中用于动态分配内存的函数,可以在程序运行时向Windows申请一段指定大小的内存。该函数的原型如下:
HGLOBAL GlobalAlloc(
UINT uFlags,
SIZE_T dwBytes
);
其中,参数uFlags是内存分配的标志,有以下几个可选项:
- GMEM_FIXED:表示分配固定的内存,内存地址在调用函数后不会改变。
- GMEM_MOVEABLE:表示分配可移动的内存,内存地址在调用函数后可能发生改变。
- GMEM_ZEROINIT:表示将分配的内存清零,可以避免程序在使用新分配的内存时出现未初始化的情况。
参数dwBytes是需要分配的内存大小,以字节为单位。函数返回值是一个类型为HGLOBAL的句柄,表示成功分配的内存块的地址。
HGLOBAL句柄是Windows API中用于管理全局内存(Global Memory)的句柄类型,也称为全局内存句柄。它是一个无符号长整型数值,用于唯一标识一个全局内存块。
在Windows操作系统中,应用程序可以通过调用GlobalAlloc()函数来分配全局内存块。GlobalAlloc()函数返回一个HGLOBAL类型的句柄,应用程序可以使用该句柄来访问、修改、释放分配的内存块。
全局内存是一种特殊类型的内存,它可以被多个进程共享,并且可以通过句柄来访问。与局部内存(Local Memory)相比,全局内存的优点是可以跨越进程边界共享数据,但缺点是需要使用句柄来访问数据,而且使用不当容易导致内存泄漏和悬挂指针等问题。
在使用HGLOBAL句柄时,需要注意以下几点:
- 要确保使用GlobalAlloc()函数分配的内存块是由一个有效的HGLOBAL句柄所管理的。
- 当不再需要使用GlobalAlloc()函数分配的内存块时,应使用GlobalFree()或GlobalFreePtr()函数释放它们,以避免内存泄漏。
- 如果在使用HGLOBAL句柄时发生错误或异常,需要注意释放内存块,以避免程序崩溃或出现其他问题。
指针悬挂(Pointer Dangling)指的是指针指向已经被释放的内存或未初始化的内存,这种情况下,指针的值没有被置为NULL,导致程序在访问该指针时出现未定义的行为。
指针悬挂通常会导致程序崩溃、内存泄漏、数据损坏等问题,因此需要避免。常见的导致指针悬挂的原因包括:
- 释放内存后未将指针赋值为NULL,导致指针悬挂。
- 对局部变量取地址后,离开变量的作用域,导致指针指向已经释放的内存。
- 对未初始化的指针进行间接引用,导致程序崩溃或产生未定义的行为。
为避免指针悬挂,我们可以采用以下方法:
- 在释放内存后,将指针置为NULL。
- 尽可能使用智能指针等资源管理类,以避免手动管理内存。
- 在使用指针前,确保它指向的内存已经被正确地分配和初始化。
- 对于指针的使用要谨慎,尽可能避免对未初始化的指针进行间接引用。
指针悬挂是一种常见的错误,可能导致程序崩溃、内存泄漏、数据损坏等问题。为避免指针悬挂,我们需要注意指针的使用,并采取相应的措施来避免该问题的发生。
将指针置为NULL可以使用赋值语句ptr = NULL;
,其中ptr是需要置为NULL的指针变量。这个语句的含义是将指针ptr的值设置为0,表示指针不指向任何有效的内存地址。
将指针置为NULL可以避免指针悬挂问题的发生,也可以在程序中判断指针是否为NULL,以避免使用未初始化的指针变量导致程序崩溃或产生不可预知的错误。另外,使用指针前应该先判断指针是否为NULL,以确保指针指向的内存已经成功分配。
内存泄漏(Memory Leak)指的是程序在动态分配内存后,在不再需要该内存时没有正确释放它,导致该内存无法再次被使用的情况。内存泄漏会导致程序占用的内存逐渐增加,直到程序耗尽可用内存并崩溃。
内存泄漏通常发生在以下情况:
- 动态分配内存后没有释放。
- 对同一块内存重复分配,导致之前的内存无法被释放。
- 对象在销毁时没有正确释放其内部动态分配的内存。
- 异常情况下未能正确释放内存。
为了避免内存泄漏,我们可以采用以下方法:
- 在动态分配内存后,必须及时使用对应的释放函数释放该内存,如:free()、delete等。
- 避免对同一块内存重复分配,尽可能在分配前检查是否已经分配过。
- 在对象销毁时,必须正确释放其内部动态分配的内存,可以通过在类中实现析构函数来释放内存。
- 在异常情况下,必须使用try-catch语句来确保分配的内存在程序崩溃时得到释放。
内存泄漏是一种常见的问题,它可能导致程序崩溃、占用过多的内存等问题。为避免内存泄漏,我们需要注意动态内存的分配和释放,以及对于对象的析构函数的正确实现,从而保证程序能够正确地管理内存。
try-catch语句是一种异常处理机制,可以在程序运行时捕捉并处理异常。通常情况下,当程序出现异常时,程序会直接退出并提示错误信息。而使用try-catch语句可以在出现异常时,捕捉异常并进行处理,从而使程序能够正常运行下去。
try-catch语句的基本语法如下:
try {
// 可能会抛出异常的代码
} catch (ExceptionType e) {
// 处理异常的代码
}
其中,try语句块中包含可能会抛出异常的代码。如果在try语句块中出现了异常,程序会跳转到catch语句块中,并执行其中的代码。catch语句块中的ExceptionType表示捕获的异常类型,可以是任意类型的异常,也可以指定特定的异常类型。在catch语句块中,可以通过异常对象e来获取异常的详细信息,并进行相应的处理。
使用try-catch语句可以提高程序的稳定性和鲁棒性,特别是在程序与外部资源交互时,如文件读写、网络通信等。通过使用try-catch语句,程序可以在遇到异常时进行优雅地处理,避免程序崩溃或产生不可预知的错误,提高了程序的可靠性和可维护性。
void CDib::LoadFile(const char* dibFileName)
这是一个类CDib的成员函数,用于从一个文件中加载DIB(设备无关位图)数据,并初始化CDib对象的相关成员变量。
void是C++中的一种数据类型,表示“没有类型”或“没有值”。在函数定义中,void作为返回类型表示该函数没有返回值。在指针中,void*表示“指向任意类型的指针”,可以用于泛型编程等场景。
LoadFile是CDib类中的一个成员函数,用于从指定文件中加载DIB图像数据并存储到CDib对象中。该函数接受一个指向字符数组的指针,表示DIB文件名。在函数内部,它打开指定文件,读取文件头和文件内容,并解析这些数据以设置CDib对象的各个成员变量,以便后续使用和处理DIB数据。
在这个函数的参数列表中,const是一个关键字,用于修饰指针类型参数,表示这个指针所指向的数据是只读的,不能被修改。在这个函数中,const char* dibFileName表示一个指向常量字符数组的指针,也就是说,函数不能修改dibFileName所指向的字符串内容。这个关键字的作用是为了确保函数在使用指针类型参数时不会不小心修改数据,从而提高程序的鲁棒性。
strcpy(m_fileName,dibFileName);
该行代码将dibFileName指向的C字符串复制到CDib对象的m_fileName成员变量中。
strcpy函数是C++中的字符串处理函数之一,用于将源字符串复制到目标字符串中。它的函数原型为:
char* strcpy(char* dest, const char* src);
其中,dest为目标字符串,src为源字符串。函数会将源字符串src中的内容复制到目标字符串dest中,并在dest字符串末尾添加一个空字符'\0',表示字符串的结束。例如,在CDib类中的LoadFile函数中,使用了strcpy函数将dibFileName复制到m_fileName中,即将输入的位图文件名赋值给类中的成员变量m_fileName。
const char*
是一个指向字符数组(或字符串)的指针,其中 const
表示该指针指向的字符数组是只读的,不能通过该指针修改字符数组中的内容。通常用于函数参数或变量声明中,表示该指针不会修改指向的字符串的内容。
CFile dibFile(m_fileName, CFile::modeRead);
dibFile.Read((void*)&bitmapFileHeader,sizeof(BITMAPFILEHEADER));
第一行代码使用m_fileName指向的文件路径创建一个CFile对象dibFile,该对象打开文件并以只读模式进行操作。第二行代码从dibFile对象中读取BITMAPFILEHEADER结构的数据,并将读取的数据存储在内存地址&bitmapFileHeader中。
CFile 是 MFC(Microsoft Foundation Class)框架中的类,用于操作文件。它封装了 Win32 API 中的文件操作函数,提供了更高层次、更易于使用的文件 I/O 接口。CFile 可以进行打开、关闭、读、写、移动文件指针等操作。在本例中,使用 CFile 类中的 modeRead 模式打开文件。
dibFile
并不是函数,而是一个对象。它是一个CFile
类的对象,是用于操作文件的类之一,可以通过它进行文件的读写操作。在这段代码中,它被用来读取一个bmp格式的图像文件,并将读取到的数据存储到内存中。
代码中的参数m_fileName是一个指向文件名的C字符串(char*)。CFile类的构造函数有两个参数,第一个参数是文件名,第二个参数是文件打开的模式。在这里,CFile构造函数的第一个参数是一个C字符串类型,表示要打开的文件的路径和名称。因此,需要将文件名存储在一个C字符串类型的变量中,以便传递给CFile构造函数。第二个参数CFile::modeRead表示以只读模式打开文件。
dibFile.Read()
是MFC类CFile
中的一个成员函数,用于从文件中读取数据到指定的内存缓冲区中。其函数原型为:
UINT Read(void* lpBuf, UINT nCount);
其中,lpBuf
是指向内存缓冲区的指针,nCount
是要读取的字节数。函数返回值是实际读取的字节数。
在CDib类中,dibFile
是一个CFile对象,通过调用dibFile.Read()
函数,可以从文件中读取指定长度的数据到内存缓冲区中。在CDib::LoadFile函数中,这个函数被用于读取BITMAPFILEHEADER结构体的数据。
BITMAPFILEHEADER 是 Windows 操作系统中用来描述 BMP 图像文件头部信息的结构体。它是位于 BMP 文件的开头部分,用来标识文件类型以及文件头部的长度等信息。其定义如下:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
其中,各个成员的含义如下:
bfType
: 用来标识该文件的类型,取值应该为 "BM"(即 0x4D42),表示这是一个 Windows BMP 文件。bfSize
: 表示整个 BMP 文件的大小,包括文件头、文件信息头以及图像数据等。bfReserved1
和bfReserved2
: 保留字段,必须设置为 0。bfOffBits
: 表示从文件头开始到位图数据的偏移量。
(void*)&bitmapFileHeader
是将 bitmapFileHeader
的地址转换为 void*
类型的指针,以便可以向 dibFile.Read()
函数传递地址作为参数。这是因为 dibFile.Read()
函数需要一个 void*
类型的指针作为参数,以便在读取数据时将其写入指向指针的内存位置。
在C++中,&符号通常被用作取地址符号,用于获取变量的内存地址。在(void*)&bitmapFileHeader
中,&符号被用于获取bitmapFileHeader
变量的地址,并将其强制转换为void*
类型的指针,以便可以将该地址传递给Read
函数。