第一篇 VC程序开发基础

第1章 VC与C++开发基础

Visual C++作为一个功能非常强大的可视化应用程序开发工具,是计算机界公认的最优秀的开发工具之一。Microsoft的基本类库MFC使开发Windows应用程序变得非常容易。本章将通过具体实例简单介绍有关Visual C++开发的基础知识。

1.1 C++面向对象特性

Visual C++是Windows环境下最主要的C++开发环境,它支持面向对象编程,并提供可视化编程环境。要使用Visual C++进行程序开发,有必要了解面向对象的基本概念和掌握C++语言。

实例1:实现C++类的多重继承

❑实例说明

继承性是C++面向对象的一个重要特性,是指一个新类可以从现有的类中派生出来,新类具有父类中的所有特性,直接继承了父类的方法和数据,新类的对象可以调用该类及父类的成员变量和成员函数。C++支持多重继承,即一个类可以从一个以上的类中派生出来。例如某公司的某职员既是工程师,又是管理人员,则他应同时具有技术人员和管理人员的特征。

本实例将演示多重继承的实现,定义了时间类CTimeType和日期类CDateType,分别可以显示和修改时间和日期。而后以这两个类为基类,派生日期时间类CDateTimeType。这样,在CDateTimeType类中,就同时具有了时间类和日期类的属性和方法。实例的运行界面如图1.1所示。

图1.1 实例的运行界面

❑核心开发过程

程序的实现代码如下:

        #include<iostream>
        using namespace std;
        class CTimeType                                               //定义时间类
        {
            int hour,minute,second;                                   //成员变量
        public:
            CTimeType(int h=12,int m=0,int s=0)                       //构造函数
            {
                hour=h;
                minute=m;
                second=s;
            }
            void display()                                            //成员函数,输出时间
            {
                cout<<hour<<":"<<minute<<":"<<second<<endl;
            }
            void SetTime(int h,int m,int s)                           //成员函数,设置时间
            {
                hour=h;
                minute=m;
                second=s;
            }
        };
        class CDateType                                               //日期类
        {
            int month,day,year;                                       //成员变量
        public:
            CDateType(int mon=1,int d=1,int y=2008)                   //构造函数
            {
                month=mon;
                day=d;
                year=y;
            }
            void display()                                            //成员函数,输出日期
            {
                cout<<month<<"/"<<day<<"/"<<year<<endl;
            }
            void SetDate(int mon,int d,int y)                         //成员函数,设置日期
            {
                month=mon;
                day=d;
                year=y;
            }
        };
        class CDateTimeType:public CDateType,public CTimeType         //时间日期类
        {
        public:
            CDateTimeType(int mon=1,int d=1,int y=2000,int h=0,int m=0,
            int s=0):CDateType(mon,d,y),CTimeType(h,m,s){}            //构造函数
            void display()                                            //成员函数,显示时间、日期
            {
            CDateType::display();                          //调用CDateType类的display函数
            CTimeType::display();                     //调用CTimeType类的display函数
            }
        };
        int main()                                    //主函数
        {
            cout<<"类的多重继承演示"<<endl;
            CDateTimeType dt(1,1,2008,11,12,12); //直接使用CDateTimeType构造函数设置日期时间
            cout<<"调用CDateTimeType类构造函数设定的初始日期、时间为:"<<endl;
            dt.display();                             //显示时间日期
            dt.SetDate(8,8,2008);                     //调用基类的成员函数修改日期
            dt.SetTime(20,8,8);                       //调用基类的成员函数修改时间
            cout<<"调用基类成员函数修改后的日期、时间为:"<<endl;
            dt.display();
            return 0;
        }

❑要点说明

派生类的格式定义可简单表示如下:

        class 派生类名:访问权限 基类名1,访问限定符 基类名2⋯⋯访问限定符 基类名n
        { private:
            成员表1;                                   //派生类增加或替代的私有成员
        public:
            成员表2;                                   //派生类增加或替代的公有成员
        protected:
            成员表3;                                   //派生类增加或替代的保护成员
        };

可以使用的访问限定符包括public、private和protected。在派生类定义的类体中给出的成员称为派生类成员,它们是新增加的数据和函数成员。这些新增加的成员是派生类对基类的发展,它们给派生类添加了不同于基类的新的属性和功能。

派生类构造函数的一般形式为:

        派生类名::派生类名(参数总表):基类名1(参数表1)⋯⋯基类名n(参数表n),
            内嵌对象名1(对象参数表1)⋯⋯内嵌对象名m(对象参数表m
            {
                派生类新增加成员的初始化;
            }

派生类构造函数的执行顺序为:

(1)调用基类构造函数,调用顺序按照它们被继承时声明的基类名顺序执行。

(2)调用内嵌对象构造函数,调用顺序按各个对象在派生类内声明的顺序执行。

(3)执行派生类构造函数体中的内容。

派生类与基类的析构函数没有什么联系,彼此独立。派生类析构函数执行过程与构造函数执行过程相反,执行顺序如下:

(1)执行派生类析构函数。

(2)执行内嵌对象的析构函数。

(3)执行基类析构函数。

实例2:使用虚函数实现运行时多态

❑实例说明

多态性是面向对象程序设计的关键技术之一。在C++中有两种多态性:编译时的多态性和运行时的多态性。编译时的多态性主要是通过函数的重载和运算符的重载来实现的,而运行时多态则是通过虚函数来实现的。所谓虚函数,是指在某基类中声明为virtual,并在一个或多个派生类中被重新定义的成员函数。

本实例将演示使用虚函数实现运行时多态,即在基类CFigure(二维图形类)中,声明计算、显示面积的虚函数GetShowArea。而后从CFigure类派生新类CTriangle(三角形类)、CRect(矩形类)、CCircle(圆形类),在这3个派生类中分别覆盖虚函数GetShowArea,实现各自的面积计算与显示。在主函数中,以相同方式,调用不同类对象的同名函数GetShowArea,得到不同的结果。实例的运行界面如图1.2所示。

图1.2 实例的运行界面

❑核心开发过程

程序的实现代码如下:

        #include<iostream>
        using namespace std;
        class CFigure                                                   //基类-图形类
        {
        protected:
            double x,y;                                                 //成员变量
        public:
            void SetDim(double i,double j=0.0)                          //设置图形参数
            {
                x=i;
                y=j;
            }
            virtual void GetShowArea()                                  //使用虚函数计算并显示面积
            {
                cout<<"无法计算面积\n";
            }
        };
        class CTriangle:public CFigure                                  //三角形类
        {
        public:
            void GetShowArea()                                          //覆盖虚函数
            {
                cout<<"三角形:底边长:"<<x<<" 高:"<<y<<" 面积:"<<x*0.5*y<<endl;
            }
        };
        class CRect:public CFigure                                      //矩形类
        {
        public:
            void GetShowArea()                                          //覆盖虚函数
            {
                cout<<"矩形:长: "<<x<<" 宽: "<<y<<" 面积:"<<x*y<<endl;
            }
        };
        class CCircle:public CFigure                                        //圆形类
        {
        public:
            void GetShowArea()                                          //覆盖虚函数
            {
                cout<<"圆形:半径:"<<x<<" 面积:"<<3.14159*x*x<<endl;
            }
        };
        int main()                                                      //主函数
        {
            cout<<"使用虚函数实现运行时多态"<<endl;
            CFigure *figure;
            CTriangle triangle;                                         //三角形类对象
            CRect rect;                                                 //矩形类对象
            CCircle circle;                                             //圆形类对象
            figure=&triangle;
            figure->SetDim(8.0,5.0);                                    //设置三角形的参数
            figure->GetShowArea();                                      //计算并显示三角形的面积
            figure=&rect;
            figure->SetDim(10.0,5.0);                                   //设置矩形的参数
            figure->GetShowArea();                                      //计算并显示矩形的面积
            figure=&circle;
            figure->SetDim(10.0);                                       //设置圆形的参数
            figure->GetShowArea();                                      //计算并显示圆形的面积
            return 0;
        }

❑要点说明

运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态地确定。

在实例中,同样是通过CFigure类指针,调用GetShowArea函数,但由于指针所指向的对象不同,实现的虚函数GetShowArea的形式也就不同。程序对虚函数的调用如图1.3所示。

图1.3 虚函数调用过程

一般而言,可将类中具有共性的成员函数声明为虚函数,派生类定义虚函数的时候,必须保证函数返回值的类型和参数与基类中的声明完全一致。

实例3:使用操作符重载实现编译多态—复数的加法运算

❑实例说明

C++中预定义的运算符的运算对象只能是基本数据类型,而不适用于用户自定义的数据类型。而C++允许对类重新定义如+、-、*、/、%、+、=等操作符,这就是操作符的重载。

本实例将创建一个复数类CComplex,在该类中重载“+”运算符,实现复数与复数以及复数与实数的加法运算。实例的运行界面如图1.4所示。

图1.4 实例的运行界面

❑核心开发过程

程序的实现代码如下:

        #include <iostream.h>
        class CComplex                                                  //定义复数类
        {
            double m_fReal, m_fImag;                                    //成员变量
        public:
            CComplex(double r = 0, double i = 0): m_fReal(r), m_fImag(i){}   //构造函数
            double GetReal(){return m_fReal;}                           //成员函数,返回复数的实部
            double GetImag(){return m_fImag;}                           //成员函数,返回复数的虚部
            CComplex operator +(CComplex&);                             //重载运算符+
            CComplex operator +(double);                                //重载运算符+
        };
        CComplex CComplex::operator + (CComplex &c)   //重载运算符+,实现两个复数的加法
        {
            CComplex temp;
            temp.m_fReal = m_fReal+c.m_fReal;
            temp.m_fImag = m_fImag+c.m_fImag;
            return temp;
        }
        CComplex CComplex::operator + (double d)      //重载运算符+,实现复数与实数的加法
        {
            CComplex temp;
            temp.m_fReal = m_fReal+d;
            temp.m_fImag = m_fImag;
            return temp;
        }
        void main()                                                     //主函数
        {
            CComplex c1(2.3,5.5),c2(3.6,6.8),c3;                        //复数类对象
            cout << "使用重载的+运算符实现复数与复数的相加" << endl;
            cout << "C1 = " << c1.GetReal() << "+j" << c1.GetImag() << endl;
            cout << "C2 = " << c2.GetReal() << "+j" << c2.GetImag() << endl;
            c3 = c1+c2;
            cout << "C3 = C1 + C2 =" << c3.GetReal() << "+j" << c3.GetImag() << endl;
            cout << "使用重载的+运算符实现复数与实数的相加" << endl;
            c3 = c3+6.5;
            cout << "C3 + 6.5 = " << c3.GetReal() << "+j" << c3.GetImag() << endl;
        }

❑要点说明

编译时的多态是指编译器对源程序进行编译时就可以确定所调用的是哪一个函数。重载运算符主要用于对类的对象的操作,其形式如下:

        <类型> <类名>::operator <操作符>(<参数表>)
        {
            ......
        }

其中,operator是定义运算符重载函数的关键字。注意,与一般函数重载不同,<参数表>中最多有一个形参。

另外,重载运算符时,需要注意下面几个问题:

❑只能重载C++语言中已有的运算符,不能臆造新的运算符。

❑重载运算符后,不改变原运算符的优先级和结合性,且不能改变操作数个数。

❑经重载的运算符,其操作的数据中至少应该有一个是自定义类型。

❑不是所有的C++运算符都能重载,不能重载的运算符有下面6个:成员访问运算符“.”、成员指针运算符“*”和“->”、作用域运算符“::”、sizeof运算符和三目运算符“?:”。

实例4:使用函数模板实现不同数据类型的极值函数

❑实例说明

模板是对具有相同特性的函数或类的再抽象。模板可分为函数模板和类模板,但模板并不是一个实实在在的函数或者类,而是关于函数和类的描述。使用时,需要对参数实例化,构造出具体的函数和类,其构造过程是由编译系统自动完成的。

本实例将实现一个求3个值中最小值的函数模板,实例的运行界面如图1.5所示。

图1.5 实例的运行界面

❑核心开发过程

程序的实现代码如下:

        #include <iostream>
        #include <string>
        using namespace std;
        template <class T>                                              //定义函数模板
        T Min(T a, T b,T c)                                             //实现模板函数
        {
            if(a<b)
                b=a;
            return b<c?b:c;                                             //返回b、c中的小值
        }
        int main()                                                      //主函数
        {
            cout<<"使用模板函数求不同类型数据的最小值"<<endl<<endl;
            int i1 = 2, i2 = 6, i3 = 4;
            double d1 = 3.4, d2 = 7.2, d3 = 3.39;
            string   str1("one"), str2("two"),str3("three");
            //int型的最小值
            cout <<" int类型:"<<i1<<","<<i2<<","<<i3 <<"最小值:"<<Min(i1,i2,i3)<<
        endl<<endl;
            //double型的最小值
            cout<<"double类型:"<<d1<<","<<d2<<","<<d3<<"最小值:"<<Min(d1,d2,d3)
        << endl<<endl;
            //string型的最小值
            cout<<"string类型:"<<str1<<","<<str2<<","<<str3<<"最小值:"<<Min(str1,
        str2,str3)<<endl<<endl;
            return 0;
        }

❑要点说明

函数模板实际上就是参数化的函数。其定义格式如下:

        template <class XXX>                                           //定义函数模板
        template <<模板参数表>>                                        //实现函数模板
            <类型> <函数名>(<参数表>)
            {
                  <函数体>
            }

其中,<模板参数表>中的一项均由关键字class开始,表示一种数据类型,在使用函数模板时必须将其实例化。其中至少有一个参数说明,并且<模板参数表>中的每个模板参数都必须在<参数表>中得到使用。

函数模板的定义只是一种说明,不是一个具体的函数。编译系统不为其产生任何执行代码。只有当编译器发现一个具体的函数调用时,才根据具体的参数类型产生相应的代码,这部分代码称为模板函数,它是函数模板的一个具体实例。

1.2 C++常用的操作与算法

有关C++的语法与实现不是本书所讨论的范畴,为了便于读者进行工程开发,本节将给出一些在程序开发中常常涉及的有关C++常用的操作和算法实例。

实例5:使用C++实现格式化数据的I/O

❑实例说明

在程序开发中,特别是基于控制台的VC程序,经常需要使用C++实现数据的输入、输出操作。C++将与输入和输出有关的操作定义为一个类体系,放在一个系统库里,以备用户调用。这个执行输入和输出操作的类体系就叫做流类,C++中提供的流类库为iostream。

格式化输入、输出主要包括控制状态标志、输出宽度、填充字符、输出精度等内容。其目的是实现特定的输出、输出格式。其实现方式有两种:使用状态标志和成员函数进行格式化输出和使用流操作符进行格式输出。本实例将使用这两种方法进行格式化输入、输出,实例的运行界面如图1.6所示。

图1.6 实例的运行界面

❑核心开发过程

程序的实现代码如下:

        #include <iostream>
        #include <iomanip>
        using namespace std;
        void main()
        {
            char bookname[100];double price;int counts;
            cout << "请输入书名:"<< endl;
            cin>>bookname;
            cout << "请输入单价:"<< endl;
            cin>>price;
            cout << "请输入销售数量:"<< endl;
            cin>>counts;
            cout<<"使用状态标志和成员函数进行格式化输出"<<endl;
            cout<<""<<bookname<<"》:";
            cout.width(5);                                            //单价显示宽度为5个字符
            cout.fill('*');                                           //宽度不满用*字符填充
            cout<<price<<"(单价)   ";                                 //按照上面的格式设置输出
            cout.setf(ios::scientific);                               //用科学计数法输出销售额
            cout.precision(3);                                        //保留3位精度
            cout<<price*counts<<"(销售额)   ";
            //显示销售数量,带有正负号、左对齐
            cout.setf(ios::showpos|ios::left);
            cout<<counts<<"(销售数量)"<<endl;
        }

如果使用流操作符进行同样的格式化输出,实现代码如下:

        cout<<"使用流操作符进行格式输出"<<endl;
            cout<<""<<bookname<<"》:"
            <<setw(5)<<setfill('*')<<price<<"(单价)   "
            <<scientific<<setprecision(3)<<price*counts<<"(销售额)   "
            <<showpos<<left<<counts<<"(销售数量)"<<endl;

❑要点说明

使用状态标志和成员函数进行格式化输出,其输出格式由状态标志来确定,它们是定义在ios类中的枚举量,引用时必须包含ios::前缀。常用的状态标志及其含义如表1.1所示。

表1.1 状态标志及其含义

通过setf函数可以设置状态标志,unsetf函数可以清除状态标志,flaps函数实现获取状态标志。

另外,C++标准库还提供了流操作符函数专门操控这些输出状态。这组操作符定义在iomanip.h头文件中,用在提取运算符“>>”或插入运算符“<<”后面来设定输入/输出格式。常用的流操作符及其含义如表1.2所示。

表1.2 流操作符及其含义

实例6:实现数字金额的中文大写转换

❑实例说明

本实例实现了数字金额的中文大写转换,即输入阿拉伯数字的金额,可以将其转换为CString类型的中文大写的数字金额。实例的运行界面如图1.7所示。

图1.7 实例的运行界面

❑核心开发过程

“转换”按钮响应函数的实现代码如下:

        void CNumToChineseDlg::OnTrans()
        {
            // TODO: Add your control notification handler code here
            UpdateData(true);
            m_strChineseCapital=ChineseCapitalMoney(m_dmoney);
            UpdateData(false);
        }

数字金额的中文大写转换是通过函数ChineseCapitalMoney实现的,具体代码如下:

        CString CNumToChineseDlg::ChineseCapitalMoney(double Num)
        {
            CString szChMoney,szNum;
            int iLen, iNum, iAddZero=0;
            TCHAR* hzUnit[18]={_T(""),_T(""),_T(""),_T(""),_T(""),
        _T(""),_T(""),
            _T(""),_T(""),_T(""),_T("亿"),_T(""),_T(""),_T(""),_T(""),
        _T(""),
            _T(""),_T("")};
            TCHAR* hzNum[10]={_T(""),_T(""),_T(""),_T(""),_T(""),_T(""),
        _T(""),
            _T(""),_T(""),_T("")};
            szNum.Format(_T("%18.0f"), Num*100);                          //只是到分
            szNum.TrimLeft();
            iLen=szNum.GetLength();
            if(iLen>15 || iLen==0 || Num<0)return "";                     //数据错误返回
            for(int i=0;i<iLen;i++)
            {
                iNum=_ttoi((LPCTSTR)szNum.Mid(i,1));
                if(iNum==0)//如果为0
                    iAddZero++;
                else
                {
                    if(iAddZero>0)
                        szChMoney+=_T("");
                    szChMoney+=hzNum[iNum];                               //转换为相应的数字
                    iAddZero=0;
                }
                if(iNum!=0||iLen-i==3||iLen-i==11||((iLen-i+1)%8==0&&iAddZero<4))
                szChMoney+=hzUnit[iLen-i-1];                              //添加相应的汉字
            }
            if(szNum.Right(2)==_T("00"))                                  //没有角和分
            szChMoney+=_T("");
            return szChMoney;
        }

实例7:将十进制数转换为二进制输出

❑实例说明

本实例将实现将用户输入的十进制正整数,使用递归方法将其转换为对应的二进制数进行输出。实例的运行界面如图1.8所示。

图1.8 实例的运行界面

❑核心开发过程

程序的实现代码如下:

        #include <iostream.h>
        void binary(int number)                                        //转换为二进制输出
        {
            int remainder;                                             //余数
            if(number <= 1)
            {
                cout << number;
                return;
            }
            remainder = number%2;                                      //求余数
            binary(number >> 1);                                       //递归调用
            cout << remainder;
        }
        void main(void)                                                //主函数
        {
            int number;
            cout << "请输入一个正整数: ";
            cin >> number;
            if (number < 0)
                cout << "输入错误!\n";
            else
            {
                cout << number << "转换为二进制为: ";
                binary(number);                                        //转换为二进制输出
                cout << endl;
            }
        }

实例8:产生随机数

❑实例说明

在C++中,可以使用rand函数得到随机数,但其返回的是虚假的随机数,默认的种子是1。使用srand函数可以设置随机数的种子,但如果将它设为常量,那么随机数列也就是常量,即每次程序初始运行时,其值是相同的。这时,可以使用srand(time(NULL))来设置不同的种子。

本实例将实现产生某一区间范围内的随机数,实例的运行界面如图1.9所示。

图1.9 实例的运行界面

❑核心开发过程

“产生”按钮的响应函数的实现代码如下:

        void CGenerateRandDlg::OnGenerate()
        {
            // TODO: Add your control notification handler code herein
            UpdateData(true);
            if(m_Min>=m_Max)
            {
            AfxMessageBox("最大值、最小值设置错误!");
            return;
            }
            srand((unsigned)time(NULL));                                  //随机数计时开始
            m_Rand1=GetRand(m_Min,m_Max);
            m_Rand2=GetRand(m_Min,m_Max);
            m_Rand3=GetRand(m_Min,m_Max);
            m_Rand4=GetRand(m_Min,m_Max);
            UpdateData(false);
        }

其中,GetRand函数实现产生某一范围内的随机数,代码如下:

        double CGenerateRandDlg::GetRand(double MIN, double MAX)    //产生随机数
        {
            double max;
            max=RAND_MAX;                                            //rand()函数随机数的最大值
            return (double)(rand()*(MAX-MIN)/max+MIN);
        }

实例9:实现排序操作

❑实例说明

排序操作在工程开发中经常用到。常用的排序算法有冒泡法、插入法、选择法、快速排序法等。本实例将创建一个排序类CSort,其中定义了4个函数模板分别实现这4种排序算法。实例的运行界面如图1.10所示。

图1.10 实例的运行界面

❑核心开发过程

CSort类的实现代码如下:

        template <class T>
        class CSort
        {
        public:
            CSort(int nArraySize);
            virtual ~CSort();
            void Bubble(T array[]);                                                    //冒泡排序
            void Insertion(T array[]);                                                 //插入排序
            void Quick(T array[], int nLeftLimit, int nRightLimit);                    //快速排序
            void Selection(T array[]);                                                 //选择排序
        private:
            int m_nArraySize;                                                          //成员变量
        };
        template <class T>
        CSort<T>::CSort(int nArraySize)                                                //构造函数
        {
            if (nArraySize<0)
                m_nArraySize = 0;
            else
                m_nArraySize = nArraySize;
        }
        template <class T>
        CSort<T>::~CSort()
        {
        }
        template <class T>
        void CSort<T>::Bubble(T array[])                                //冒泡排序函数模板
        {
            T temp;
            int nLast = m_nArraySize -1;
            BOOL bSorted = TRUE;
            do
            {
                bSorted = TRUE;
                for (int i = 0; i < nLast; i++)
                {
                    //如果前一个元素比后一个元素大,则交换两个元素
                    if (array[i] > array[i + 1])
                    {
                        temp = array[i];
                        array[i] = array[i + 1];
                        array[i + 1] = temp;
                        bSorted = FALSE;
                    }
                }
                nLast--;
            } while (!bSorted);
        }
        template <class T>
        void CSort<T>::Insertion(T array[])                             //插入排序函数模板
        {
            T cVal;                                                     //记录当前被测试的值
            for (int i = 1; i < m_nArraySize; i++)
            {
                cVal = array[i];
                for (int n = i -1; n >= 0 && cVal < array[n]; n--)
                {
                    array[n + 1] = array[n];
                }
                array[n + 1] = cVal;
            }
        }
        template <class T>
        void CSort<T>::Quick(T array[], int llimit, int rlimit)          //快速排序函数模板
        {
            T temp;
            int nLeft = llimit;
            int nRight = rlimit;
            int pivot = (nLeft + nRight) / 2;                            //中间值

T nMedian = array[pivot];

do{

while ((array[nLeft] < nMedian) && (nLeft < rlimit)){

nLeft++;}

while ((nMedian < array[nRight]) && (nRight > llimit)){

nRight--;}

if (nLeft <= nRight){

//交换元素

temp = array[nLeft];

array[nLeft] = array[nRight];

array[nRight] = temp;

nLeft++;

nRight--;}

} while (nLeft <= nRight);

if (llimit < nRight){

CSort<T>::Quick(array, llimit, nRight);}

if (nLeft < rlimit){

CSort<T>::Quick(array, nLeft, rlimit);}}

template <class T>

void CSort<T>::Selection(T array[])

//选择排序函数模板{

T temp;

int min;

for (int i = 0; i < m_nArraySize -1; i++){

min = i;

for (int n = i + 1; n < m_nArraySize; n++){

if (array[n] < array[min]){

min = n;}}

temp = array[min];

array[min] = array[i];}

array[i] = temp;}

1.3 创建基本的应用程序框架

Visual C++ 6.0提供了应用程序框架生成向导(AppWizard),使用它可以自动生成一个应用程序框架结构。本节将通过实例介绍使用API函数创建程序窗口以及使用AppWizard创建基本的MFC应用程序框架的过程,这也是使用VC进行应用程序开发的基础。

实例10:使用Windows API创建程序窗口

❑实例说明

本实例将使用AppWizard创建一个Win32控制台工程,介绍如何使用API函数实现创建窗口程序。通过本实例,将掌握典型的Windows窗口程序的基本结构和消息处理。实例的运行界面如图1.11所示。

图1.11 实例的运行界面

❑核心开发过程

用AppWizard建立一个Win32Application的空工程,在其中创建一个.cpp实现文件,添加如下代码:

        #include <windows.h>                                           //头文件
        LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);             //窗口函数声明
        char szClassName[]="windowclass";                              //窗口结构体的名称
        char szAppTitle[]="使用API创建窗口";                           //窗口的标题
        int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpszCmdParam
        , INT nCmdShow)                                                //WinMain()函数的定义
        {
            HWND hMainWnd;                                             //窗口句柄
            MSG msg;                                                   //消息结构体
            WNDCLASS winclass;                                         //窗口结构体
            if(!hPrevInstance)//判断是否已有应用程序的实例在运行,给窗口结构体的数据成员赋值
            {
                winclass.style=CS_HREDRAW|CS_VREDRAW;                  //窗口风格
                winclass.lpfnWndProc=WndProc;                          //窗口的消息处理函数
                winclass.cbClsExtra=0;                                 //窗口类无扩展
                winclass.cbWndExtra=0;                                 //窗口实例无扩展
                winclass.hInstance=hInstance;                          //当前应用程序实例句柄
                winclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);//窗口的最小化图标为默认图标
                winclass.hCursor=LoadCursor(NULL,IDC_ARROW);//窗口采用箭头光标
                winclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
                                                                       //窗口背景色为白色
                winclass.lpszMenuName=NULL;//无窗口菜单
                winclass.lpszClassName=szClassName;                    //给窗口结构体命名
                RegisterClass(&winclass);                              //注册窗口
            }
            //下面用CreateWindow()函数来建立窗口,并返回所建立窗口的句柄
            hMainWnd=CreateWindow(
                szClassName,                                           //窗口结构体的名称
                szAppTitle,                                            //窗口的标题
                WS_OVERLAPPEDWINDOW,                                   //窗口风格为可重叠窗口
            //下面4个参数代表窗口左上角x,y坐标与窗口的宽度和高度,都使用默认值
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
            //下面分别为父窗口句柄、窗口菜单句柄、应用程序实例句柄和附加参数
            NULL,NULL,hInstance,NULL);
            ShowWindow(hMainWnd,SW_SHOWNORMAL);               //显示窗口
            UpdateWindow(hMainWnd);                           //更新窗口
            //下面用While()循环来建立消息循环
            while(GetMessage(&msg,NULL,0,0))                  //获取消息,填充msg结构体
            {
                TranslateMessage(&msg);                       //翻译消息
                DispatchMessage(&msg);                        //向窗口函数发送消息,让窗口函数处理
            }
            return msg.wParam;
        }
        LRESULT CALLBACK WndProc(HWND hMainwnd, UINT message, WPARAM wParam, LPARAM lParam)
                                                              //窗口函数的定义
        {
            HDC hdc;                                          //设备描述表
            PAINTSTRUCT ps;                                   //刷新区域
            RECT rect;                                        //矩形结构
            char messageleft[]="按下了鼠标左键!";            //单击鼠标左键,消息框将显示提示内容
            switch(message)                                   //判断消息标识符
            {
                case WM_PAINT:                                //窗口重绘
                  {
                  hdc=BeginPaint(hMainwnd,&ps);
                  GetClientRect(hMainwnd,&rect);              //获取客户区区域
                  rect.bottom=rect.top+50;
                  DrawText(hdc,TEXT("使用Windows API创建Windows窗口程序!"),
                  -1,&rect,DT_SINGLELINE| DT_CENTER|DT_VCENTER);  //在客户区输出文字
                  EndPaint(hMainwnd,&ps);
                  break;
                  }
                case WM_LBUTTONDOWN:
                    {
                    MessageBox(GetFocus(),messageleft,"使用API创建窗口",MB_OK|
                          MB_ICONINFORMATION);
                    break;
                    }
                case WM_DESTROY:                              //关闭应用程序窗口时发出的消息
                    {
                    PostQuitMessage(0);                       //发出WM_QUIT消息,结束应用程序
                    return 0;
                    }
                default :
                break;
            }
        return DefWindowProc(hMainwnd,message,wParam,lParam); //其他消息交给Windows做默认处理
        }

注意,在编译工程时,可能会出现以下错误提示信息:

        LIBCD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
        Debug/APIWindowsDemo.exe : fatal error LNK1120: 1 unresolved externals
        Error executing link.exe.

这时,执行Project→Settings菜单命令,在弹出的“Project Settings”对话框的“Link”选项卡中的“Project Options”编辑框中找到/subsystem:console,将其修改为/subsystem:windows即可。

❑要点说明

Windows窗口的创建主要由两个函数完成:WinMain函数负责建立窗口和建立消息循环,WndProc函数负责消息的处理,如图1.12所示。

图1.12 Windows窗口创建过程

WinMain函数是所有应用程序的入口,类似C语言中的Main函数,它完成一系列的定义和初始化工作,并产生消息循环。程序执行时,WinMain函数首先注册窗口类,建立窗口并对窗口进行初始化,然后进入消息循环,根据接收的消息调用相应的处理过程。当消息循环检索到WM_QUIT消息时,终止程序运行。

窗口函数WinProc定义了应用程序对接收到的不同消息的响应,包含了对各种可能接收到的消息的处理过程。WinProc函数由一个或多个switch语句组成。每一条case语句对应一种消息,当应用程序接收到一个消息时,相应的case语句被激活并执行相应的响应程序模块。

实例11:使用AppWizard创建基于文档/视图结构MFC应用程序框架

❑实例说明

基于文档/视图结构的应用程序是最常见的Windows应用程序结构,如常用的Word、Excel等软件均是采用这种结构。其典型特征是应用程序界面由框架窗口、客户窗口、菜单栏、工具栏、状态栏等组成。

使用Visual C++ 6.0的AppWizard可以创建两种类型的基于文档/视图结构的应用程序框架:单文档(SDI)和多文档(MDI)。本实例将使用AppWizard创建单文档MFC应用程序框架,实例的运行界面如图1.13所示。读者通过本例将掌握创建步骤及各选项的含义。

图1.13 实例的运行界面

❑核心开发过程

启动Visual C++ 6.0,执行“File”→“New”菜单命令,在“New”对话框的“Projects”选项卡左侧的项目列表中选中“MFC AppWizard(exe)”。在“Project name”文本框中键入项目的名称,如“SDIDemo”,在“Location”文本框中指定所要创建的项目的位置(文件夹)。单击“OK”按钮,即弹出的“MFC AppWizard Step1”对话框。

(1)Step 1用于选择应用的结构以及语言等,如图1.14所示。3个单选项用于确定创建的应用类型,包括Single document(SDI,单文档界面)、Multiple documents(MDI,多文档界面)以及Dialog based等,这里选择SDI。然后从下面的下拉列表框中选择所使用的语言种类,单击“Next”按钮。

(2)Step 2为选择数据库支持选项,如图1.15所示。如果选择了数据库支持,那么单击“Data Source”按钮或者选择外部ODBC数据库、DAO数据库或者选择合适的数据源和数据库表项。一般默认即可,单击“Next”按钮。

图1.14 应用向导Step 1

图1.15 应用向导Step 2

(3)Step 3选择希望包含于应用中的复合文档支持项,同时确定是否启用标准的ActiveX资源和为应用的菜单条添加额外的自动化命令,如图1.16所示。一般默认即可,单击“Next”按钮。

(4)Step 4用于选择应用所需的基本用户接口特征以及所想使用的工具栏类型:IE 4.0 ReBars或者MFC(命令)工具栏。如果想要修改所使用的文件名和扩展名或者想要调整用户接口窗口的框架风格,单击“Advanced”按钮,然后修改即可,如图1.17所示。一般默认即可,单击“Next”按钮。

(5)Step 5设置工程的风格:Explorer风格的应用类似于资源管理器,左边是一个树视图,右边为列表视图,而标准MFC风格带有文件视图区域。还要确定是否希望应用向导在源文件中生成注释(这些注释用于指导开发人员书写程序代码),最后确定MFC库是动态链接(shared)还是静态链接(statically),动态链接的应用运行时依赖于MFC库的存在,而静态链接的应用则不需要MFC库就可以直接运行,不过链接方式在工程生成后还可以更改,如图1.18所示。一般默认即可,单击“Next”按钮。

(6)Step 6可以更改由应用向导提供的默认类名、基类、头文件和实现文件名,对于视图,还可以更改它的基类,如图1.19所示。一般默认即可,单击“Finish”按钮,然后在弹出的工程信息对话框中单击“OK”按钮即结束应用程序的创建过程。

图1.16 应用向导Step 3

图1.17 应用向导Step 4

图1.18 应用向导Step 5

图1.19 应用向导Step 6

至此,即完成了基于单文档的MFC应用程序框架的创建。

❑要点说明

下面以实例生成的单文档应用程序SDIDemo为例,简单分析一下程序框架的结构。在工程SDIDemo中,程序框架共生成了5个类,其功能介绍如下。

• CAboutDlg:对话框框类,即对应应用程序执行“帮助”→“关于”菜单命令后,弹出的“关于”对话框。

• CSDIDemoApp:派生自应用程序类CWinApp,为应用程序对象,负责应用程序的初始化和退出清理工作。

• CMainFrame:派生自CFrameWnd类,为框架窗口对象,对应应用程序的主窗口。

• CSDIDemoDoc:派生自CDocument文档类,为文档对象,储存与应用程序相关的数据。其在应用程序中没有直观的对应关系。

• CSDIDemoView:派生自视图类CView,为视图对象,对应应用程序的客户窗口,用来显示文档数据的内容。

对应用程序而言,视图是程序窗口的客户区,框架窗口是客户区的框架,程序数据显示在视图窗口,用户通过视图与程序交互。同时,文档、视图和框架三者之间是相互关联、相互协调的,彼此都包含了指向对方的指针。文档、视图与框架的关系如图1.20所示。

图1.20 文档、视图与框架的关系

程序的执行过程可简单表示如下:

• CWinApp类创建的实例theApp,整个程序有且只有一个,一切由它开始,最后以它结束。

• Visual C++所产生的代码首先通过初始化数据段来建立全局变量,以及建立一些MFC内部使用的对象,然后执行CWinApp类的构造函数。

• 一旦所有静态对象的构造函数都执行完毕,运行时间库就会调用WinMain函数,该函数初始化MFC应用,并调用CWinApp类的InitInstance函数。

• 完成了这些工作后,WinMain函数调用CWinApp类的Run函数,通常默认为CWinThread::Run,用来得到应用程序的消息循环,或称消息队列。

• 当程序接收到WM_QUIT消息,就意味着程序终止。这时,MFC会调用CWinApp类的ExitInstance,然后是静态对象的析构函数,包括CWinApp对象,然后将控制权交还操作系统。

实例12:使用AppWizard建立对话框应用程序框架

❑实例说明

基于对话框的应用程序,是以对话框为形式的应用程序,它对于那些涉及文档较少,主要是交互式操作的应用程序来说比较合适。本实例将实现使用AppWizard创建基于对话框的MFC应用程序框架。实例的运行界面如图1.21所示。

图1.21 实例的运行界面

❑核心开发过程

与上例介绍的基于SDI的MFC应用程序框架的创建步骤相似,基于对话框的应用程序框架的创建过程共有4步。

首先确定应用程序的结构,在“AppWizard Step 1”对话框中选择“Dialog based”;在“AppWizard Step 2”对话框中选择应用所需的基本用户接口特征以及包含什么样的WOSA支持;在“AppWizard Step 3”对话框中确定是否希望应用向导在源文件中生成注释,并确认MFC库是动态链接还是静态链接;在“AppWizard Step 4”对话框中,可以实现更改由应用向导提供的默认类名、基类、头文件和实现文件名。基于对话框的应用中只有两个类被创建,一个是应用类CDialogbaseDemoApp,另一个是对话框类CDialogbaseDemoDlg。

设置完毕,单击“Finish”按钮,然后在弹出的工程信息对话框中单击“OK”按钮即实现了基于对话框的MFC应用程序框架的创建。

项目创建成功后,在工作区窗口会发现自动生成了一些相关类和资源,在资源编辑窗口显示的则是自动生成的对话框的界面,如图1.22所示。

图1.22 利用AppWizard生成的基于对话框的MFC项目

用户可以在此基础上添加、修改、设计资源,完善功能设计。

❑要点说明

MFC的应用程序运行过程如下:

• 应用程序定义一个应用类全局对象。

• 应用类对象的构造函数开始执行。

• 构造函数执行完后,调用初始化函数InitInstance()。

• 在函数InitInstance()中,构造文档模板。

• 构造文档模板时,按照生成一个文档的顺序先产生一个最初的文档、主框架。

• 函数InitInstance()生成工具条和状态条,以及其他用户需要的工具。

• 函数InitInstance()执行完成后,应用程序处于等待消息的状态。

这3种应用程序框架的共同点就是应用程序的应用类都会自动创建一个InitInstance()函数。而其不同的地方如下:

• 在单文档应用程序中,应用类的对象由应用框架构造,使用单文档模板类CSingleDoc-Template的对象来构造文档模板。

• 在MDI应用程序中,应用类的对象同样也由应用框架构造,使用多文档模板类CMulitDocTemplate对象来构造文档模板。

• 在基于对话框的应用程序中,首先在函数中生成一个对话框对象,然后再通过DoModal()函数来调用和显示这个对话框。

1.4 使用CString类进行字符串操作

MFC的CString类提供了对字符串的操作,一个CString类的对象由一个长度可变的字符序列组成。CString类没有基类,可以单独应用于MFC框架的其他部分,因此其使用方法十分方便。本节将通过实例介绍CString类的常用操作。

实例13:查找、替换字符串

❑实例说明

查找和替换是字符串常用的操作,CString类提供了Find函数可以从字符串的指定位置开始,查找指定字符或字符串,返回字符所在字符串中的位置。而CString类中的Replace函数可以替换CString对象中的字符或字符串。本实例将实现字符串的查找和替换操作,实例的运行界面如图1.23所示。

图1.23 实例的运行界面

❑核心开发过程

“查找”按钮的响应函数的实现代码如下:

        void CSearchReplaceStrDlg::OnSearch()
        {
            // TODO: Add your control notification handler code here
            UpdateData(true);
            if(m_strFind.IsEmpty())                                    //判断编辑框字符串是否为空
            {
                AfxMessageBox("查找的字符串为空!");
                return;
            }
            int pos=0;                                                 //记录查找位置
            CString strpos,temp;
            strpos.Format("%s字符串在原字符串中的起始位置为:\n",m_strFind);
            while((pos>=0)&&(pos<m_strData.GetLength()))
            {
                pos=m_strData.Find(m_strFind,pos);                     //查找字符串
                if(pos>0)
                {
                    temp.Format("%d ",pos);
                    strpos+=temp;
                    pos+=m_strFind.GetLength();                        //新的起始查找位置
                }
            }
            AfxMessageBox(strpos);
        }

“替换”按钮的响应函数的实现代码如下:

        void CSearchReplaceStrDlg::OnReplace()
        {
            // TODO: Add your control notification handler code here
            UpdateData(true);
            if(m_strFind.IsEmpty())
            {
                AfxMessageBox("查找的字符串为空!");               //判断编辑框字符串是否为空
                return;
            }
            int num;
            num=m_strData.Replace(m_strFind,m_strReplace);         //替换字符串
            CString str;
            str.Format("共完成了%d处替换",num);
            AfxMessageBox(str);
            UpdateData(false);
        }

其中,m_strData、m_strFind、m_strReplace分别为原始字符串、查找字符串和替换字符串编辑控件对应的CString类型的成员变量。

❑要点说明

CString包含很多成员函数用来操作字符串,其中常用的成员函数及实现的功能如表1.3所示。

表1.3 CString类的常用成员函数

CString类中的Find、ReverseFind、FindOneOf函数实现了字符串的查找操作。其中Find函数实现从指定位置开始,查找指定字符或字符串,返回字符所在字符串中的位置。ReverseFind函数则是从字符串的最末尾开始查找指定的字符,返回字符所在字符串中的位置。而FindOneOf函数的功能是查找给定字符串中的任意一个字符,查到一个就把位置返回,没有查到则返回0。

实例14:根据指定字符分割字符串

❑实例说明

在程序开发中,有时在读取文本形式的配置文件时,需要对字符串进行分析。如字符串“用户名=gaoshou;密码=gsc;权限=管理员;”,需要根据字符“;”将其分割为3个字符串。本实例将实现根据指定字符分割字符串,实例的运行界面如图1.24所示。

图1.24 实例的运行界面

❑核心开发过程

“分割”按钮的响应函数的实现代码如下:

        void CDecodeStringDlg::OnDecode()
        {
            // TODO: Add your control notification handler code here
            UpdateData(true);
            CStringArray dest;                                               //CString数组类
            char division=m_strDivision.GetAt(0);                            //获取分割符
            StringDecode(m_strOrigin, dest, division);                       //分割字符串
            m_ctlList.ResetContent();                                        //清空列表框
            for(int i=0;i<dest.GetSize();i++)
            {
                m_ctlList.AddString(dest[i]);           //将分割后得到的字符串添加到列表框
            }
        }

其中,分割字符串函数StringDecode的实现代码如下:

        void CDecodeStringDlg::StringDecode(CString source,CStringArray& dest,char division)
        {
            dest.RemoveAll();
            for(int i=0;i<source.GetLength();i++)                          //遍历字符串
            {
                if(source.GetAt(i)== division)                             //找到分割符
                {
                  dest.Add(source.Left(i));                                //去掉右边
                  for(int j=0;j<(dest.GetSize()-1);j++)
                  {
                    dest[dest.GetSize()-1] = dest[dest.GetSize()-1].Right(dest
        [dest.GetSize()-1].GetLength()
                                                -dest[j].GetLength()-1);//去掉左边
                  }
                }
            }
        }

❑要点说明

CString类提供的函数Left、Mid和Right实现从一个字符串的左边、中间、右边位置开始提取字符串。

实例中的分割字符串函数StringDecode的实现也可以使用查找函数Find结合提取字符串函数来实现,其代码如下:

        void CDecodeStringDlg::StringDecode(CString source, CStringArray& dest, char division)
        {
            dest.RemoveAll();
            int nStart = 0;
            int nEnd = source.Find(division);                      //查找分割符
            while(nEnd > nStart)
            {
                dest.Add(source.Mid(nStart,nEnd - nStart));
                nStart = nEnd + 1;                                 //定位起始位置
                nEnd = source.Find(division,nStart);               //查找分割符
            }
            nEnd = source.GetLength();
            if(nStart < nEnd)
                dest.Add(source.Mid(nStart,nEnd - nStart));
        }

实例15:格式化字符串

❑实例说明

与C++的printf函数类似,使用CString类的Format函数可以实现将数字或者字符类型的变量格式化为字符串。本实例将实现将double类型的数据按照不同的形式转换为CString类型的字符串,实例的运行界面如图1.25所示。

图1.25 实例的运行界面

❑核心开发过程

“格式化”按钮的响应函数的实现代码如下:

        void CFormatStrDlg::OnFormat()
        {
            // TODO: Add your control notification handler code here
            UpdateData(true);
            switch(m_radio)
            {
            case 0:
                m_strValue.Format("%+.3e",m_doubleValue);             //转换为指数形式
                break;
            case 1:
                m_strValue.Format("%+10.2f",m_doubleValue);           //转换为小数形式
                break;
            }
            UpdateData(false);
        }

❑要点说明

Format函数调用的一般形式为:

        str. Format ("格式控制字符串",输出内容);

其中格式控制字符串用于指定输出格式。格式控制串可由格式字符串和非格式字符串两种组成。格式字符串是以“%”开头的字符串,在“%”后面跟各种格式字符,以说明输出数据的类型、形式、长度、小数位数等。如“%d”表示按十进制整型输出,“%ld”表示按十进制长整型输出,“%c”表示按字符型输出等。非格式字符串在输出时保持原样,在显示中起提示作用。

格式字符串的一般形式为:

        [标志][输出最小宽度][.精度][长度]转换说明符

其中方括号[]中的项为可选项。各项的含义介绍如下。

• 转换说明符:表示输出数据的类型,其格式符和意义如表1.4所示。

表1.4 转换说明符的格式符及其含义

• 长度:长度格式符共分为h和l两种,h表示按短整型量输出,l表示按长整型量输出。一般很少用到。

• 精度:精度格式符以“.”开头,后跟十进制整数。其含义是:如果输出数字,则表示

小数的位数;如果输出的是字符,则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分。

• 输出最小宽度:用十进制整数来表示输出的最少位数。若实际位数多于定义的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。

• 标志:常用的标志字符为“-”和“+”。“-”表示结果左对齐,右边填空格;“+”表示结果右对齐,对于有符号数,在其左边加上“+”或“-”。

实例16:CString字符串的类型转换

❑实例说明

字符串类CString的一个重要特性就是它可以很方便地与其他数据类型进行转换。本实例将实现将用户输入的十六进制(CString类型)的数转换为int类型的十进制数,实例的运行界面如图1.26所示。

图1.26 实例的运行界面

❑核心开发过程

“转换”按钮的响应函数的实现代码如下:

        void CHexStrToDemDlg::OnChange()
        {
            // TODO: Add your control notification handler code here
            UpdateData(true);
            m_intDem=HexToDem(m_strHex);
            if(m_intDem==-1)
            {
                AfxMessageBox("转换错误!");
            }
            else
            {
                UpdateData(false);
            }
        }

其中,HexToDem函数的实现代码如下:

        int CHexStrToDemDlg::HexToDem(CString str)
        {
            int dem=0;
            for(int i=0;i<str.GetLength();i++)
            {
                dem=dem*16;
                if((str[i]<='9') && (str[i]>='0'))                            //0~9之间的字符
                  dem+=str[i]-'0';
                else if((str[i]<='F') && (str[i]>='A'))                       //A~F之间的字符
                  dem+=str[i]-'A'+10;
                else if((str[i]<='f') && (str[i]>='a'))                       //a~f之间的字符
                  dem+=str[i]-'a'+10;
                else
                  return -1;                                                  //出错时返回-1
            }
            return dem;
        }

❑要点说明

前面实例已经介绍了,可以调用CString对象的Format方法格式化字符,使得数据类型方便地转换为字符串。而CString类型字符串也可以转换为其他类型。

(1)转换为数值型

可以使用atoi函数将CString数字字符串转换为整数,如下面的代码:

        CString str("352");
        int iTemp=atoi(str);

同样,可以使用atof函数将CString数字字符串转换为浮点数,如下面的代码:

        CString s;
        s="12345.6890";
        float f = (float)atof((char*)(LPCTSTR)s);

(2)转换为char*类型

这种类型转换程序开发中经常用到,因为MFC的很多函数的参数使用了char*数据类型,而用户能够提供的数据多是CString类型。将CString类型转换为char*类型主要有下面几种方法。

• 使用GetBuffer函数

典型的实例代码如下:

        char *p;
        CString str="Good Morning";
        p=str.GetBuffer(str.GetLength());

• 使用memcpy函数

典型的实例代码如下:

        CString str ("Good Morning ");
        char mch[20];
        memcpy(mch, str, str.GetLength());

• 使用LPCTSTR强制类型转换

典型的实例代码如下:

        char *ch;
        CString str ("Good Morning ");
        ch=(LPSTR)(LPCTSTR)str;

而char*类型的数据则可以直接赋值给CString对象。

1.5 时间与日期操作

MFC提供了两个日期和时间类:CTime和CTimeSpan。CTime类表示的是绝对时间,即基于格林威治平均时间(GMT);CTimeSpan则表示的是时间间隔。本节将通过具体的实例讲解MFC时间与日期的常用操作。

实例17:获取当前的日期、时间并格式化输出

❑实例说明

CTime对象代表一个绝对的时间和日期,使用其提供的GetCurrentTime函数可以获取当前的日期、时间。而使用CTime类的Forma函数则可以将时间、日期格式化。本实例将实现获取系统当前的时间、日期,并将其格式化输出。实例的运行界面如图1.27所示。

图1.27 实例的运行界面

• 核心开发过程

“获取”按钮的响应函数的实现代码如下:

        void CGetCurTimeDlg::OnGet()
        {
            // TODO: Add your control notification handler code here
            CTime m_time;
            m_time=CTime::GetCurrentTime();                               //获取当前时间日期
            m_strDate=m_time.Format("%x");                                //格式化日期
            m_strTime=m_time.Format("%X");                                //格式化时间
            m_strDateTime=m_time.Format("%Y-%m-%d %H:%M:%S %W-%A");//格式化日期时间
            UpdateData(false);
        }

• 要点说明

CTime类以秒为单位保存时间,它采用带符号的4字节数存储。由于CTime只是简单地计算从1970年1月1日之后经过的秒数,所以到了2037年它将达到4294967295,从而不能再使用。为此,MFC还提供了COleDataTime类,它封装了OLE使用的DATE类型,可以使用到9999年,其使用方法与CTime相同。

时间、日期有很多种表达方式,可以使用Format函数将时间格式化,该函数带有%符号的格式符号,设定输出的格式。可用的格式符号及其含义如表1.5所示。

表1.5 Format函数的格式符号及其含义

实例18:计算某日为星期几

❑实例说明

使用CTime类的GetDayOfWeek成员函数可以方便地计算某一日为星期几,这在实际程序开发中也经常用到。本实例将实现计算用户给定日期为星期几,实例的运行界面如图1.28所示。

图1.28 实例的运行界面

❑核心开发过程

“计算”按钮的响应函数的实现代码如下:

        void CGetWeekDayDlg::OnCal()
        {
            // TODO: Add your control notification handler code here
            UpdateData(true);
            int weekday=m_Time.GetDayOfWeek();
            switch(weekday)
            {
                case 1:
                    m_strWeekDay="星期日";
                    break;
                case 2:
                    m_strWeekDay="星期一";
                    break;
                case 3:
                    m_strWeekDay="星期二";
                    break;
                case 4:
                    m_strWeekDay="星期三";
                    break;
                case 5:
                    m_strWeekDay="星期四";
                    break;
                case 6:
                    m_strWeekDay="星期五";
                    break;
                case 7:
                    m_strWeekDay="星期六";
                    break;
            }
            UpdateData(false);
        }

其中,计算日期编辑控件对应的成员变量m_Time的数据类型为COleDateTime。

❑要点说明

CTime类没有基类,因此可以在MFC框架的任何位置直接使用。CTime类常用的成员函数及其功能如表1.6所示。

表1.6 CTime类的主要成员函数及功能

COleDataTime类相比CTime类多了两个成员函数,其实现的功能及使用方法如下。

• 使用DayOfYear函数获得一年中的某一天,典型代码如下:

        COleDateTime datetime;
        datetime = COleDateTime::GetCurrentTime();
        int DayOfYear = datetime.GetDayOfYear();

• 使用ParseDateTime函数可以从字符串中读取时间,典型代码如下:

        COleDate Time datetime;
        datetime.ParseDateTime("10:12:23 27 January 98");

有时,用户需要进行COleDataTime与CTime之间的类型转换,可采用下面的方法。

• COleDataTime转换为CTime类型,典型代码如下:

        COleDateTime time1(1977,4,16,2,2,2);
        SYSTEMTIME systime;
        VariantTimeToSystemTime(time1, &systime);
        CTime tm(systime);
        • CTime转换为COleDataTime类型,典型代码如下:
        CTime tm(1997,7,1,8,12,30);
        time_t time2=tm.GetTime();
        COleDateTime time3(time2);

实例19:计算两个时间点的时间间隔

❑实例说明

CTimeSpan类主要是保存两个时间之间的间隔,一个CTimeSpan对象代表一个相对的时间段,它以秒为单位保存时间。本实例将实现计算当前距2010年元旦的时间间隔,并动态显示。实例的运行界面如图1.29所示。

图1.29 实例的运行界面

❑核心开发过程

在对话框的初始化函数OnInitDialog中设置定时器,每秒触发一次,其代码如下:

        BOOL CGetTimeSpanDlg::OnInitDialog()
        {
            CDialog::OnInitDialog();
        ...
            // TODO: Add extra initialization here
            SetTimer(1,1000,NULL);                        //设置定时器,每秒触发一次
            return TRUE;   // return TRUE   unless you set the focus to a control
        }

使用ClassWizard为对话框添加WM_TIMMER消息响应函数OnTimer,实现计算时间间隔并显示。实现代码如下:

        void CGetTimeSpanDlg::OnTimer(UINT nIDEvent)
        {
            // TODO: Add your message handler code here and/or call default
            CTime m_time1=CTime::GetCurrentTime();                        //获取当前时间
            m_strTime=m_time1.Format("%Y%m%d %H%M%S");
            CTime m_time2(2010,1,1,0,0,0);                                //2010年元旦
            CTimeSpan m_timespan=m_time2-m_time1;                         //获取时间间隔
            m_Days=m_timespan.GetDays();                                  //天数
            m_Hours=m_timespan.GetHours();                                //小时数
            m_Minutes=m_timespan.GetMinutes();                            //分钟数
            m_Seconds=m_timespan.GetSeconds();                            //秒数
            m_TotalHours=m_timespan.GetTotalHours();                      //合计小时数
            m_TotalMinutes=m_timespan.GetTotalMinutes();                  //合计分钟数
            UpdateData(false);
            CDialog::OnTimer(nIDEvent);
        }

❑要点说明

CTimeSpan类主要的成员函数及功能如表1.7所示。

表1.7 CTimeSpan类的主要成员函数及功能

另外,CTimeSpan类也提供了“+”、“-”、“=”、“>=”等操作符,其含义与CTime类中的完全相同。常用的CTimeSpan对象的构造方式如下:

        CTimeSpan timespan( 3,1,5,12 ); // 3days,1 hour, 5 minutes, and 12 seconds

timespan对象表示的时间间隔为3天1小时5分12秒。通过其成员函数就可以计算出总共包含多少小时,或包含多少分钟、多少秒等。

另外,两个CTime对象直接相减就可以得到一个CTimeSpan对象,CTime对象还可以直接与CTimeSpan对象进行加、减运算。

1.6 MFC集合类的使用

在应用程序中,经常需要保存与对象有关的集合。在C程序中,集合通常是指简单的数组或某种类型的链表。而MFC则提供了数组类、链表类以实现集合操作。本节将通过典型实例说明MFC数组类和链表类的使用。

实例20:使用CStringArray类创建和使用字符串数组

❑实例说明

使用MFC的数组类可以创建和操作一个可以处理各种数据类型的一维数组对象。MFC的数组类包含CByteArray、CDWordArray、CPtrArray、CUIntArray、CWordArray和CStringArray。每一个类都被设计成能够处理一个特定的数据类型。如,CUIntArray类是一个处理无符号整型数的数组类,CObjectArray类代表对象数组类。这些数组类几乎相同,仅仅的区别在于它们存储的数据类型不同。

本实例将演示CStringArray数组类的创建和操作,即实现在任意位置插入和删除数组元素,实例的运行界面如图1.30所示。通过本实例读者可以体会数组类成员函数的使用以及使用数组类的便利与高效。

图1.30 实例的运行界面

❑核心开发过程

首先在对话框类的头文件中声明CStringArray型的数组类m_array,实现代码如下:

        public:
            CStringArrayDlg(CWnd* pParent = NULL);    // standard constructor
            CStringArray m_array;

在提交按钮的响应函数OnCheck中,根据用户选择的操作,实现在数组的特定位置插入或删除字符串数组元素,列表框中实时显示数组中的内容,实现代码如下:

        void CStringArrayDlg::OnCheck()
        {
            // TODO: Add your control notification handler code here
            UpdateData(true);
            switch (m_radio)
            {
                int i;
                case 0:                                               //插入操作
                    if(m_pos<0||m_pos>m_array.GetSize())              //判断插入位置的有效性
                    {
                        AfxMessageBox("数组位置不正确!");
                        return;
                    }
                    if(m_str.IsEmpty())                               //判断插入信息的有效性
                    {
                        AfxMessageBox("信息不能为空!");
                        return;
                    }
                    m_array.InsertAt(m_pos,m_str);                    //在数组的指定位置插入字符串
                    m_list.ResetContent();                            //清空列表框
                    for(i=0;i<m_array.GetSize();i++)
                    {
                        m_list.AddString(m_array.GetAt(i)); //按数组顺序添加列表项
                    }
                    m_str="";                                         //清空信息编辑框
                    m_pos=m_array.GetSize();
                    UpdateData(false);
                    break;
                case 1:                                               //删除操作
                    if(m_pos<0||m_pos>=m_array.GetSize())             //判断删除位置的有效性
                    {
                        AfxMessageBox("该位置没有数组元素!");
                        return;
                    }
                    m_array.RemoveAt(m_pos);                          //在数组的指定位置删除字符串
                    m_list.ResetContent();                            //清空列表框
                    for(i=0;i<m_array.GetSize();i++)
                    {
                        m_list.AddString(m_array.GetAt(i)); //按数组顺序添加列表项
                    }
                    m_str="";                                         //清空信息编辑框
                    m_pos=m_array.GetSize();
                    UpdateData(false);
                    break;
            }
        }

❑要点说明

与常规数组的最大不同就是,MFC可以在运行时动态地增大和缩小数组对象,即在声明数组对象时不必关心它的维数。

各数组类除构造函数不同外,其他成员函数和实现功能完全相同。数组类中常用的函数及其功能如表1.8所示。

表1.8 数组类的主要成员函数及功能

另外,在使用数组类前最好使用SetSize函数设定数组的大小并申请内存空间。如果不这样做,向数组中增加元素时,需要不断地移动和复制元素,造成运行的低效率和产生内存碎块。

实例21:使用CPtrList类创建和使用链表

❑实例说明

MFC的链表类包括CPtrList、CStringList、CObList,分别提供了void*类型指针、CString字符串、CObject类及其派生类类型的链表集合。

本实例将使用CPtrList链表类创建链表对象,该链表对象为自定义结构体CAccount,记录了财务支出名目和费用两方面的信息。实现了链表的添加、删除和遍历操作,实例的运行界面如图1.31所示。通过本实例读者可以体会链表类的各种常用操作。

图1.31 实例的运行界面

❑核心开发过程

在对话框类实现文件的顶部定义结构体,实现代码如下:

        struct CAccount
        {
            CString m_name;                                 //名目
            double m_count;                                 //费用
        };
        在对话框类的头文件中,声明CPtrList链表类,实现代码如下:
        public:
            void Reflesh();                                 //自定义刷新函数
            CPtrListDlg(CWnd* pParent = NULL);              // standard constructor
            CPtrList m_accountlist;                         //链表类对象

当执行了提交操作后,根据用户选择的操作类型,对链表进行相应操作,并更新列表框和总量编辑框。提交按钮响应函数OnCheck的实现代码如下:

        void CPtrListDlg::OnCheck()
        {
            // TODO: Add your control notification handler code here
            UpdateData(true);                                //获取编辑框数据
            CAccount* m_pAccount = new CAccount;             //创建结构体对象指针
            switch(m_radio)
            {
                case 0:                                      //插入操作
                    if(m_name.IsEmpty()||m_count<=0) //判断插入信息的有效性
                    {
                        AfxMessageBox("输入的费用信息不正确!");
                        return;
                    }
                    m_pAccount->m_name = m_name;             //将用户输入信息赋值给结构体对象
                    m_pAccount->m_count=m_count;
                    m_accountlist.AddTail(m_pAccount);                //将结构体对象加入到链表尾
                    m_name="";                                        //编辑框复位
                    m_count=0.0;
                    UpdateData(false);
                    Reflesh();                                        //更新显示
                    break;
                case 1:                                               //去除表头操作
                    if(m_accountlist.GetCount())                      //链表不为空
                    {
                        m_accountlist.RemoveHead();                   //去除表头
                    }
                    m_name="";                                        //复位
                    m_count=0.0;
                    UpdateData(false);
                    Reflesh();                                        //更新显示
                    break;
                case 2:                                               //去除表尾操作
                    if(m_accountlist.GetCount())                      //链表不为空
                    {
                        m_accountlist.RemoveTail();                   //去除表尾
                    }
                    m_name="";
                    m_count=0;
                    UpdateData(false);
                    Reflesh();                                        //更新显示
                    break;
            }
        }

其中,Reflesh函数为自定义函数,实现在列表框中显示链表对象中的数据,并计算总的费用信息,然后显示在编辑框中。实现代码如下:

        void CPtrListDlg::Reflesh()
        {
            CString str,str1;
            m_total=0;//总量赋值0
            m_list.ResetContent();                                           //清空列表框
            if(!m_accountlist.IsEmpty())                                     //链表不为空
            {
                POSITION pos = m_accountlist.GetHeadPosition();              //获取链表头的位置
                for (int i=0;i<m_accountlist.GetCount();i++)                 //遍历链表
                {
                    CAccount* m_pAccount = (CAccount*)m_accountlist.GetNext(pos);
                    str1.Format("%.2f",m_pAccount->m_count);                 //格式化费用
                    str="名目:"+m_pAccount->m_name+"    支出费用:"+str1;
                    m_list.AddString(str);                                   //添加列表框数据
                    m_total+=m_pAccount->m_count;                            //计算总费用
                }
            }
            UpdateData(false);                                               //更新总费用编辑框
        }

❑要点说明

链表类常用的函数及实现的功能如表1.9所示。

与数组不同,链表的位置是用POSITION类型标识的。可以通过遍历链表元素或搜索特定项来得到一个有效的POSITION型值。

表1.9 链表类的主要成员函数及功能

添加链表元素的典型代码如下:

        CStringList   m_List;
        //向链表集合中添加元素
        POSITION pos = m_List.AddHead(CString("表头"));
        pos = myList.InsertAfter(pos, CString("节点2"));
        pos = myList.InsertAfter(pos, CString("节点3"));

如果要从链表头开始遍历链表,首先需要通过GetHeadPosition函数获取表头的POSITION值。向GetNext函数传递POSITION值,即获取链表头的第一个元素,函数返回链表当前位置的元素,并将POSITION设置为链表中下一个元素的位置。用这种方法就可以遍历链表中的所有元素,直到获得链表中的最后一个元素,这时POSITION的值为NULL。

例如,遍历前面创建的链表,代码如下:

        // 进行遍历链表操作
        POSITION pos = m_List.GetHeadPosition();
        for (int i=0;i < myList.GetCount();i++)
        {
            AfxMessageBox(myList.GetNext(pos));
        }

而使用GetAt函数可以得到链表当前位置的元素而不改变POSITION的值。