慢羊羊的空间

工作做不完了,300出,无瑕。

学习保存应用程序的配置信息 金牌收录

为什么要用配置文件

需要使用配置文件的场景很多,例如:

  • 应用程序启动的时候,通过读取外部的一个“配置文件”,以加载不同的值,实现程序按照不同的需求来执行。
  • 应用程序启动后,用户会有各种操作。当用户下一次启动应用程序时,就会希望保持上一次的操作状态。或者游戏结束后,需要将最高分记录等等。因此,可以在程序关闭的时候,将一些必要的变量保存下来,并且在下一次程序启动的时候,读取这些变量。通常,可以把这些变量保存在“配置文件”中。

创建配置文件

配置文件一般以 .ini 为扩展名,编码为 ANSI 或 Unicode(即 UTF-16 LE)。例如在 \windows 文件夹里面可以找到很多 .ini 文件。.ini 文件的内容是纯文本的,可以直接双击打开编辑。举一个实际例子,下面是一个标准的 .ini 配置文件的内容:

; EasyX 范例
[Ball]
x=10
y=37
r=20
[Wall]
width=70
height=60

可以看到,配置文件的格式很简单,包括三种情况:

  1. 注释:写在半角分号后面;
  2. 节:用方括号括起来;
  3. 变量:用“键=值”这种形式记录,整形和字符串不做区分。

读写配置文件

要读写这些配置值,可以使用 Windows API 函数,相关 API 函数的原型如下:

// 写入字符串的配置值
//	参数含义:
//		lpAppName: 表示“节”
//		lpKeyName: 表示“键”
//		lpString: 表示“值”
//		lpFileName: 表示配置文件名
//	返回值:执行成功后返回非 0,否则返回 0。具体的错误原因可以通过 GetLastError 函数获取。 
BOOL WritePrivateProfileString(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpString, LPCTSTR lpFileName);

// 读入整形的配置值
//	参数含义:
//		lpAppName: 表示“节”
//		lpKeyName: 表示“键”
//		nDefault: 表示没有找到指定“键”时,返回的默认“值”
//		lpFileName: 表示配置文件名
//	返回值:获取到指定键的整形值。如果没有获取到,返回 nDefault 指定的默认值。
UINT GetPrivateProfileInt(LPCTSTR lpAppName, LPCTSTR lpKeyName, INT nDefault, LPCTSTR lpFileName);

// 读入字符串的配置值
//	参数含义:
//		lpAppName: 表示“节”
//		lpKeyName: 表示“键”
//		nDefault: 表示没有找到指定“键”时,返回的默认“值”
//		lpReturnedString: 用于保存返回字符串值的缓冲区指针
//		nSize: 缓冲区的大小
//		lpFileName: 表示配置文件名
//	返回值:返回获取到的字符串的长度。
DWORD GetPrivateProfileString(LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR lpReturnedString, DWORD nSize, LPCTSTR lpFileName);

(以上三个函数的更详细介绍,以及更多的关于配置文件的 API,请参考 MSDN)

使用上很简单,举个例子:

// 在当前路径下的 test.ini 配置文件中,创建 [Ball] 节,并记录 x=30
WritePrivateProfileString(_T("Ball"), _T("x"), _T("30"), _T(".\\test.ini"));  

// 读取前面代码创建的键 x 的值。如果没找到,返回默认值 320
int age;
age = GetPrivateProfileInt(_T("Ball"), _T("x"), 320, _T(".\\test.ini"));

范例代码

下面是一个完整的代码范例,实现这样的功能:鼠标可以拖动一个圆球,关闭程序后,保存球的位置;重新打开程序时,读取上次次保存的球的位置。第一次运行程序时,并没有相应的 .ini 配置文件,因此读取到的是默认的配置值。详见代码:

#include <graphics.h>
#include <stdio.h>
#include <conio.h>
#include <math.h>

#define R 30		// 定义球的半径

int main()
{
	// 创建图形窗口,禁用“关闭”按钮,以防止无法正确保存配置文件
	initgraph(640, 480, EW_NOCLOSE);

	// 读取上一次的位置
	int x, y;
	x = GetPrivateProfileInt(_T("Ball"), _T("x"), 320, _T(".\\test.ini"));
	y = GetPrivateProfileInt(_T("Ball"), _T("y"), 240, _T(".\\test.ini"));

	// 设置球的颜色,并绘制
	setfillcolor(GREEN);
	solidcircle(x, y, R);

	MOUSEMSG msg;			// 鼠标消息
	int mx, my;				// 鼠标上一次的位置
	bool keydown = false;	// 左键是否按下

	// 主循环
	while(!_kbhit())
	{
		while(MouseHit())
		{
			msg = GetMouseMsg();		// 获取鼠标消息
			switch(msg.uMsg)
			{
				// 按下鼠标左键,开始拖动
				case WM_LBUTTONDOWN:
					if (sqrt((double)((msg.x - x) * (msg.x - x) + (msg.y - y) * (msg.y - y))) < R)
					{
						keydown = true;
						mx = msg.x;
						my = msg.y;
					}
					break;

				// 抬起鼠标左键,停止拖动
				case WM_LBUTTONUP:
					keydown = false;
					break;

				// 鼠标移动,处理拖动
				case WM_MOUSEMOVE:
					if (keydown)
					{
						clearcircle(x, y, R);
						x += (msg.x - mx);
						y += (msg.y - my);
						mx = msg.x;
						my = msg.y;
						solidcircle(x, y, R);
					}
					break;
			}
		}

		// 延时,降低 cpu 占用率
		Sleep(10);
	}

	// 保存坐标到配置文件
	TCHAR s[20];
	_stprintf(s, _T("%d"), x);			// vc6 的写法
	//	_stprintf_s(s, _T("%d"), x);	// >= vc6 的写法
	::WritePrivateProfileString(_T("Ball"), _T("x"), s, _T(".\\test.ini"));

	_stprintf(s, _T("%d"), y);			// vc6 的写法
	//	_stprintf_s(s, _T("%d"), y);	// >= vc6 的写法
	::WritePrivateProfileString(_T("Ball"), _T("y"), s, _T(".\\test.ini"));

	// 关闭绘图窗口
	closegraph();
	return 0;
}

添加评论