C++之MFC学习

问题1:stdafx.h是怎么引入进来的?define.h与stdafx.h之间的关系?为什么在MuisicPlayer.cpp中引入stdafx.h

问题2:enum class的使用

问题3:列表初始化:int window_transparency{ 100 }

问题4:CDC的使用

CDC类定义的是设备上下文对象的类。 CDC对象提供处理显示器或打印机等设备上下文的成员函数,以及处理与窗口客户区对应的显示上下文的成员。 通过CDC对象的成员函数进行所有的绘图。 类对设备上下文操作提供了成员函数,处理绘图工具。安全型图形设备接口(GDI)对象收集,以及处理颜色和调色板。 它还为获取和设置绘图属性、映射,处理视点、窗口扩展、转换坐标,处理区域、剪贴、绘制直线及绘制简单椭圆和多边形等形状提供了成员函数。 另外还为绘制文本、处理字体,使用打印机跳转,滚动和播放元文件提供成员函数。 使用CDC对象时要构造它,然后调用与它平等的、使用设备上下文的Windows函数的成员函数。

问题5:BitMap、HBitMap、CBitMap?

C++ CBitmap,HBitmap,Bitmap区别及联系 - 鹿我所录 - 博客园

问题6:Microsoft SDKs和Windows Kits有什么区别?

Microsoft SDKs包含了Windows Kits、Azure SDK、Kinect SDK,等。

问题7:wchar_t类型

wchar_t是C/C++的字符类型,是一种扩展的存储方式。wchar_t类型主要用在国际化程序的实现中,但它不等同于unicode编码。unicode编码的字符一般以wchar_t类型存储。

char是8位字符类型,最多只能包含256种字符,许多外文字符集所含的字符数目超过256个,char型无法表示。

wchar_t数据类型一般为16位或32位,但不同的C或C++库有不同的规定,如GNU Libc规定wchar_t为32位,总之,wchar_t所能表示的字符数远超char型。

标准C中的wprintf函数以及标准C++的iostream类库中的类和对象能提供wchar_t宽字符类型的相关操作。

问题8:CString是什么

CString是MFC中最常见的类之一,用于封装字符串数据结构。

CString没有基类。 一个CString对象由可变长度的一队字符组成。CString使用类似于Basic的语法提供函数和操作符。连接和比较操作符以及简化的内存管理使CString对象比普通字符串数组容易使用。 CString是基于TCHAR数据类型的对象。如果在你的程序中定义了符号UNICODE,则TCHAR被定义为类型wchar_t,即16位字符类型;否则,TCHAR被定义为char,即8位字符类型。在UNICODE方式下,CString对象由16位字符组成。非UNICODE方式下,CString对象由8位字符组成。 当不使用UNICODE时,CString是多字节字符集(MBCS,也被认为是双字节字符集,DBCS)。注意,对于MBCS字符串,CString仍然基于8位字符来计算,返回,以及处理字符串,并且你的应用程序必须自己解释MBCS的开始和结束字节。 CString对象还具有下列特征:

· CString可作为连接操作的结果而增大。
· CString对象遵循“值语义”。应将CString看作是一个真实的字符串而不是指向字符串的指针。
· 你可以使用CString对象任意替换const char*和LPCTSTR函数参数。
· 转换操作符使得直接访问该字符串的字符就像访问一个只读字符(C-风格的字符)数组一样。

问题9:MFC中wstring是什么?

看你要使用什么字符编码了, std::wstring主要用于 UTF-16编码的字符,而std::string主要用于存储单字节的字符( ASCII字符集 ),但是也可以用来保存UTF-8编码的字符。(UTF-8和UTF-16是UNICODE字符集的两种不同的字符编码)

如果你的程序支持多种语言,那么使用UTF-16来处理字符会方便一些,因为该编码中的每个字符都占用2个字节;而UTF-8中的字符所占的字节可能是1个字节或者多个字节(范围是1 ~ 6 个字节),多字节的字符编码对于处理字符不方便,而且std::string也没有提供对UTF-8的支持。

问题10:CImage类

CImage是MFC和ATL共享的新类,它能从外部磁盘中调入一个JPEG、GIF、BMP和PNG格式的图像文件加以显示,而且这些文件格式可以相互转换。

CImage是VC.NET中定义的一种MFC/ATL共享类,也是ATL的一种工具类,它提供增强型的(DDB和DIB)位图支持,可以装入、显示、转换和保存多种格式的图像文件,包括BMP、GIF、JPG、PNG、TIF等。CImage是一个独立的类,没有基类。(CImage类是基于GDI+的,从VC.NET起引进,VC 6.0中没有。)

ATL(Active Template Library,活动模板库)是一套基于模板的 C++ 类,用以简化小而快的 COM 对象的编写。

为了在MFC程序中使用CImage类,必须包含ATL的图像头文件atlimage.h:(在VS08 SP1中不用包含)

#include <atlimage.h>

问题11:CCriticalSection

类CCriticalSection的对象表示一个“临界区”,它是一个用于同步的对象,同一时刻只允许一个线程存取资源或代码区。临界区在控制一次只有一个线程修改数据或其它的控制资源时非常有用。例如,在链表中增加一个结点就只允许一次一个线程进行。通过使用CCriticalSection对象来控制链表,就可以达到这个目的。 在运行性能比较重要而且资源不会跨进程使用时,建议采用临界区代替信号灯。有关在MFC中使用信号灯的详细信息,请参阅CMutex。使用CCriticalSection对象之前,需要构造它。在构造函数返回后,就可以使用临界区了。在使用完之后要调用UnLock函数。 存取由CCriticalSection控制的资源时,要在资源的存取函数中定义一个CSingleLock型的变量。然后调用加锁对象的Lock成员函数(如CSingleLock::Lock)。此时,调用的线程要么获得对资源的存取权,要么等待他人释放资源等待加锁,或者等待他人释放资源,但又因为超时而加锁失败。这样就保证了一次只有一个线程在存取临界资源。释放资源只需调用成员函数UnLock(例如CSingleLock:Unlock),或让锁对象在作用范围之外。 此外,可以单独地建立一个CCriticalSection对象,并在存取临界资源之前显式地存取它。这种方式有助于保持代码的清晰,但是更容易出错,因为程序员要记住在存取临界资源前加锁,存取之后开锁。 要了解有关使用CCriticalSection对象的更详细的信息,请参阅联机文档“Visual C++程序员指南”中的“多线程:如何使用同步类”。 #include <afxmt.h>

问题12:static_cast/dynamic_cast/ const_cast/reinterpret_cast

static_casts

static_cast相当于传统的C语言里的强制转换,该运算符把expression转换为new_type类型,用来强迫隐式转换,例如non-const对象转为const对象,编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。

进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;

进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。

②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

③把空指针转换成目标类型的空指针。

④把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。

dynamic_cast

dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)

  type必须是一个类类型,在第一种形式中,type必须是一个有效的指针,在第二种形式中,type必须是一个左值,在第三种形式中,type必须是一个右值。在上面所有形式中,e的类型必须符合以下三个条件中的任何一个:e的类型是是目标类型type的公有派生类、e的类型是目标type的共有基类或者e的类型就是目标type的的类型。如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个std::bad_cast异常(该异常定义在typeinfo标准库头文件中)。e也可以是一个空指针,结果是所需类型的空指针。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast)。

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;

在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。dynamic_cast是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。

(1)指针类型

举例,Base为包含至少一个虚函数的基类,Derived是Base的共有派生类,如果有一个指向Base的指针bp,我们可以在运行时将它转换成指向Derived的指针,代码如下:

if(Derived *dp = dynamic_cast<Derived *>(bp)){
  //使用dp指向的Derived对象  
}
else{
  //使用bp指向的Base对象  
}

值得注意的是,在上述代码中,if语句中定义了dp,这样做的好处是可以在一个操作中同时完成类型转换和条件检查两项任务。

(2)引用类型

因为不存在所谓空引用,所以引用类型的dynamic_cast转换与指针类型不同,在引用转换失败时,会抛出std::bad_cast异常,该异常定义在头文件typeinfo中。

void f(const Base &b){
 try{
   const Derived &d = dynamic_cast<const Base &>(b);  
   //使用b引用的Derived对象
 }
 catch(std::bad_cast){
   //处理类型转换失败的情况
 }
}

const_cast

const_cast,用于修改类型的const或volatile属性。

该运算符用来修改类型的const(唯一有此能力的C++-style转型操作符)或volatile属性。除了const 或volatile修饰之外, new_type和expression的类型是一样的。

①常量指针被转化成非常量的指针,并且仍然指向原来的对象;

②常量引用被转换成非常量的引用,并且仍然指向原来的对象;

③const_cast一般用于修改底指针。如const char *p形式。

举例转换如下:

const int g = 20;
int *h = const_cast<int*>(&g);//去掉const常量const属性
​
const int g = 20;
int &h = const_cast<int &>(g);//去掉const引用const属性
​
 const char *g = "hello";
char *h = const_cast<char *>(g);//去掉const指针const属性

reinterpret_cast

new_type必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。

reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编辑器,这也就表示它不可移植

  举一个错误使用reintepret_cast例子,将整数类型转换成函数指针后,vc++在执行过程中会报"...中的 0xxxxxxxxx 处有未经处理的异常: 0xC0000005: Access violation"错误:

#include <iostream>
using namespace std;
int output(int p){
    cout << p <<endl;  return 0;
}
​
typedef int (*test_func)(int );//定义函数指针test_func
int main(){
    int p = 10;
    test_func fun1 = output;
    fun1(p);//正确
    test_func fun2 = reinterpret_cast<test_func>(&p);
    fun2(p);//...处有未经处理的异常: 0xC0000005: Access violation
    return 0;
}

IBM的C++指南、C++之父Bjarne Stroustrup的FAQ网页MSDN的Visual C++也都指出:错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。

  MSDN中也提到了,实际中可将reinterpret_cast应用到哈希函数中,如下(64位系统中需将unsigned int修改为unsigned long):

// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>
​
// Returns a hash code based on an address
unsigned short Hash( void *p ) {
   unsigned int val = reinterpret_cast<unsigned int>( p );
   return ( unsigned short )( val ^ (val >> 16));
}
​
using namespace std;
int main() {
   int a[20];
   for ( int i = 0; i < 20; i++ )
      cout << Hash( a + i ) << endl;
}

另外,static_cast和reinterpret_cast的区别主要在于多重继承,比如

class A {
    public:
    int m_a;
};
 
class B {
    public:
    int m_b;
};
 
class C : public A, public B {};

  那么对于以下代码:

C c;
printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));

前两个的输出值是相同的,最后一个则会在原基础上偏移4个字节,这是因为static_cast计算了父子类指针转换的偏移量,并将之转换到正确的地址(c里面有m_a,m_b,转换为B*指针后指到m_b处),而reinterpret_cast却不会做这一层转换。

 因此, 你需要谨慎使用 reinterpret_cast。

c++强制转换注意事项

  • 新式转换较旧式转换更受欢迎。原因有二,一是新式转型较易辨别,能简化“找出类型系统在哪个地方被破坏”的过程;二是各转型动作的目标愈窄化,编译器愈能诊断出错误的运用。

  • 尽量少使用转型操作,尤其是dynamic_cast,耗时较高,会导致性能的下降,尽量使用其他方法替代。

问题13:必须清楚HWND、HANDLE、HMODULE、HINSTANCE的区别

HWND是线程相关的,你可以通过HWND找到该窗口所属进程和线程

Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。 系统对内核对象以链表的形式进行管理,载入到内存中的每一个内 核对象都有一个线性地址,同时相对系统来说,在串列中有一个索引位置,这个索引位置就是内核对象的handle。

HINSTANCE的本质是模块基地址,他仅仅在同一进程中才有意义,跨进程的HINSTANCE是没有意义

HMODULE 是代表应用程序载入的模块,win32系统下通常是被载入模块的线性地址。

HINSTANCE 在win32下与HMODULE是相同的东西(只有在16位windows上,二者有所不同). ———————————— 原文链接:Windows客户端开发--必须清楚HWND、HANDLE、HMODULE、HINSTANCE的区别_一蓑烟雨任平生 也无风雨也无晴-CSDN博客_hwnd

(HWND 是一个基本类型,和char int等同级别的,可以把它当做long型去看待,和身份证号一样。

HWND,h 是类型描述,表示句柄(handle), Wnd 是变量对象描述,表示窗口,所以hWnd 表示窗口句柄。hWnd 属性,返回窗体或控件的句柄(注意 OLE 容器控件不支持该属性。句柄:是由操作环境定义的一个唯一的整数值,它被程序用来标识或者切换到对象,如窗体或控件等。

问题14:GetModuleFileName

想要访问执行程序(.exe)路径下的文件,可以通过函数GetModuleFileName获取执行程序的绝对路径。

TCHAR szPath[ MAX_PATH ] = {0}; GetModuleFileName( NULL, szPath, MAX_PATH ); 解释说明: GetModuleFileName函数为windows的API函数,使用的时候需要包含windows.h的头文件; MAX_PATH是一个宏定义,值为260。执行完GetModuleFileName函数之后,szPath数组中保存的就是执行程序当前的绝对路径。 举例: 假设执行程序xp.exe的绝对路径为C:\Program Files\Dll\xp.exe,那么szPath数组中存储的值就是C:\Program Files\Dll\xp.exe。

问题15:std::string::npos

std::string::npos是一个常数,它等于size_type类型可以表示的最大值,用来表示一个不存在的位置,类型一般是std::container_type::size_type。

问题16:GetDeviceCaps()函数相关说明

CDC::GetDeviceCaps()物理长度与屏幕像素间的转换

作用: 读取DC的一些打印区域信息,主要是像素和英寸方面的数据.

声明: GetDeviceCaps(int )

使用例子: //所有像素数 int pagecx=dc.GetDeviceCaps(HORZRES); int pagecy=dc.GetDeviceCaps(VERTRES);

//即每英寸点数 short cxInch = dc.GetDeviceCaps(LOGPIXELSX); short cyInch = dc.GetDeviceCaps(LOGPIXELSY);

// 计算一个设备单位等于多少0.1mm double scaleX = 254.0 / (double)GetDeviceCaps(dc.m_hAttribDC,LOGPIXELSX); double scaleY = 254.0 / (double)GetDeviceCaps(dc.m_hAttribDC, LOGPIXELSY);

说明: 主要用到的参数见例子中的:HORZRES,VERTRES,LOGPIXELSX,LOGPIXELSY.总的来说是为了方便控制打印或重画时的控制,如为了定制打印时,一般依据的是物理的长度,而不是像素,而DC一般是用像素的映射模式,所以需要一下转换,上面这个函数就为这种转换设计的.

GDI中有一个函数是GetDeviceCaps(),可以获取一些关于设备的一些属性,如HORZSIZE/HORZRES/LOGPIXELSX等。 以上三者的关系通常满足:HORZSIZE = 25.4 * HORZRES/LOGPIXELSX HORZSIZE为屏幕水平尺寸(定为度量尺寸,以mm计),HORZRES为水平的像素总数(定为像素大小,平时所说的屏幕分辨率,但在这不这么称呼。这里,分辨率定为“每英寸的像素数”),LOGPIXELSX为逻辑像素(假设的每英寸的像素数,并不是刚才所说的实际的“分辨率”)。因此HORZSIZE也称为逻辑宽度。 当我们选择“显示”属性里的大字体时,LOGPIXELSX(通常分为96dpi与120dpi)变大了,这样假设原来的字体为10磅,则原来的字体横向所占像素(实际所占的像素数)为10(1/72)LOGPIXELSX,现在LOGPIXELSX变大了,则字体所占像素也大了,因此看起来字体大了。如果HORZRES不变的话,则HORZSIZE应该变小。然后这是和Windows有关的,在16位OS中,HORZSIZE值是固定的。 在XP系统上验证了一下,发现HORZSIZE值与LOGPIXELSX的值也是不变的,如果改变HORZRES的话,则HORZSIZE会发生相应变化,但LOGPIXELSX不变,一直是96。 验证数值是:当HORZRES/VERTRES分别为800/600、1280/1024、1360/768时,LOGPIXELSX/LOGPIXELSY一直为96,但HORZSIZE/VERTSIZE分别为320/240、375/300、400/320。于是个人断定:LOGPIXELSX/LOGPIXELSY与所选的字体(如TrueType)有关,windows默认的字体LOGPIXELSX/LOGPIXELSY值是定的,选大字体或小字体取它们的值都是一样的,而一些字体是不同的。而HORZSIZE/VERTSIZE与系统版本有关,在有的系统中,这两个值是适合此分辨率的标准显示器的尺寸(定值,长宽比与分辨率的比一样),不是通过公式计算的,也不等于公式计算的值;而有的系统版本这两个值为公式所得的值。 下边是petzold那本书上的两句(没摘英文的):“

然而,在Windows NT中,用老的方法定义HORZSIZE和VERTSIZE值。这种方法与Windows的16位版本一致。HORZRES和VERTRES值仍然表示水平和垂直图素的数值,LOGPIXELSX和LOGPIXELSY仍然与在「控制台」的「显示器」程序中选择的字体有关。在Windows 98中,LOGPIXELSX和LOGPIXELSY的典型值是96和120 dpi,这取决于您选择的是小字体还是大字体。

在Windows NT中的区别是HORZSIZE和VERTSIZE值固定表示标准显示器大小。对于普通的显示卡,取得的HORZSIZE和VERTSIZE值分别是320和240毫米。这些值是相同的,与选择的图素大小无关。因此,这些值与用HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY索引从GetDeviceCaps中得到的值不同。然而,可以用前面的公式计算在Windows 98下的HORZSIZE和VERTSIZE值。

HFONT CreateFont( int nHeight, //字体的高度 int nWidth, //字体的宽度 int nEscapement, //字体显示的角度 int nOrientation, //字体的角度 int nWeight, //字体的磅数 BYTE bItalic, //斜体字体 BYTE bUnderline, //带下划线的字体 BYTE cStrikeOut, //带删除线的字体 BYTE nCharSet, //所需的字符集 BYTE nOutPrecision, //输出的精度 BYTE nClipPrecision, //裁减的精度 BYTE nQuality, //逻辑字体与输出设备的实际 //字体之间的精度 BYTE nPitchAndFamily, //字体间距和字体集 LPCTSTR lpszFacename //字体名称 );

示例:

/************************/ HFONT hFont; HDC hDC; hFont=CreateFont(10,10,0,0,FW_THIN,true,false,false, CHINESEBIG5_CHARSET,OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,DEFAULT_QUALITY, FF_MODERN,"宋体"); SelectObject(hDC,hFont); /************************/

GDI中有一个函数是GetDeviceCaps(),可以获取一些关于设备的一些属性,如HORZSIZE/HORZRES/LOGPIXELSX等。 以上三者的关系通常满足:HORZSIZE = 25.4 * HORZRES/LOGPIXELSX HORZSIZE为屏幕水平尺寸(定为度量尺寸,以mm计),HORZRES为水平的像素总数(定为像素大小,平时所说的屏幕分辨率,但在这不这么称呼。这里,分辨率定为“每英寸的像素数”),LOGPIXELSX为逻辑像素(假设的每英寸的像素数,并不是刚才所说的实际的“分辨率”)。因此HORZSIZE也称为逻辑宽度。 当我们选择“显示”属性里的大字体时,LOGPIXELSX(通常分为96dpi与120dpi)变大了,这样假设原来的字体为10磅,则原来的字体横向所占像素(实际所占的像素数)为10(1/72)LOGPIXELSX,现在LOGPIXELSX变大了,则字体所占像素也大了,因此看起来字体大了。如果HORZRES不变的话,则HORZSIZE应该变小。然后这是和Windows有关的,在16位OS中,HORZSIZE值是固定的。 我在XP系统上验证了一下,发现HORZSIZE值与LOGPIXELSX的值也是不变的,如果改变HORZRES的话,则HORZSIZE会发生相应变化,但LOGPIXELSX不变,一直是96。 验证数值是:当HORZRES/VERTRES分别为800/600、1280/1024、1360/768时,LOGPIXELSX/LOGPIXELSY一直为96,但HORZSIZE/VERTSIZE分别为320/240、375/300、400/320。于是个人断定:LOGPIXELSX/LOGPIXELSY与所选的字体(如TrueType)有关,windows默认的字体LOGPIXELSX/LOGPIXELSY值是定的,选大字体或小字体取它们的值都是一样的,而一些字体是不同的。而HORZSIZE/VERTSIZE与系统版本有关,在有的系统中,这两个值是适合此分辨率的标准显示器的尺寸(定值,长宽比与分辨率的比一样),不是通过公式计算的,也不等于公式计算的值;而有的系统版本这两个值为公式所得的值。 下边是petzold那本书上的两句(没摘英文的):“ 然而,在Windows NT中,用老的方法定义HORZSIZE和VERTSIZE值。这种方法与Windows的16位版本一致。HORZRES和VERTRES值仍然表示水平和垂直图素的数值,LOGPIXELSX和LOGPIXELSY仍然与在「控制台」的「显示器」程序中选择的字体有关。在Windows 98中,LOGPIXELSX和LOGPIXELSY的典型值是96和120 dpi,这取决于您选择的是小字体还是大字体。

在Windows NT中的区别是HORZSIZE和VERTSIZE值固定表示标准显示器大小。对于普通的显示卡,取得的HORZSIZE和VERTSIZE值分别是320和240毫米。这些值是相同的,与选择的图素大小无关。因此,这些值与用HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY索引从GetDeviceCaps中得到的值不同。然而,可以用前面的公式计算在Windows 98下的HORZSIZE和VERTSIZE值。

问题17:Afx是什么?

afx是application framework的缩写。 对于类向导来说这个符号是有意义的.它是一个消息处理函数的前缀。类向导生成的消息函数,分发函数,事件响应函数都以这个为前缀;如果去掉了,向导将不能识别。

问题18:初始化列表

#include <iostream>
#include <vector>
#include <map>
​
// 使用 std::initializer_list<int> 来初始化任意长度的初始化列表
//stl中的容器是通过使用 std::initializer_list 完成的
class Foo
{
public:
    Foo(std::initializer_list <int>){}
};
​
class FooVector
{
    std::vector<int> content_;
​
public:
    FooVector(std::initializer_list<int> list)//initializer_list 负责接收初始化列表
    {
        for (auto it = list.begin(); it != list.end(); ++it)
        {
            content_.push_back(*it);
        }
    }
};
​
​
//map 是以 pair形式插入的。map中的元素的类型value_type 
//typedef pair<const Key, Type> value_type;
​
​
class FooMap
{
    std::map<int, int> content_;
    using pair_t = std::map<int, int>::value_type;//重新命名类型   typedef
​
public:
    FooMap(std::initializer_list<pair_t> list)
    {
        for (auto it = list.begin(); it != list.end(); ++it)
        {
            content_.insert(*it);
        }
    }
};
​
//使用 std::initializer_list 给自定义类型做初始化
void test01()
{
    Foo foo = { 1,2,3,4,5 };
    FooVector foo1 = { 1, 2, 3, 4, 5 };
    FooMap foo2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
}
​
//使用 std::initializer_list 传递同类型的数据
void func(std::initializer_list<int> list)
{
    std::cout << "size = "<<list.size() << std::endl;
    //对 std::initializer_list 访问只能通过begin() end() 循环遍历
    //迭代器是只读的,无法修改某一个元素,但可以整体赋值
    for (auto it = list.begin(); it != list.end(); it++)
    {
        std::cout << *it << std::endl;
    }
}
​
void test02()
{
    func({});//1个空集合
    func({ 1,2,3 });//传递 { 1,2,3 }
}
​
/*
        std::initializer_list 的内部并不负责保存初始化列表中元素的拷贝,仅仅
        存储了列表中元素的引用而已,因此需要再持有对象的生存周期之前传递完毕
*/
​
//错误的使用方法
std::initializer_list<int> func2(void)
{
    int a = 1, b = 2;
    return { a,b };//ab 返回时并没有拷贝
}
​
//正确的使用
std::vector<int> func3(void)
{
    int a = 1, b = 2;
    return { a,b };//ab 返回时并没有拷贝
}
​
void test03()
{
    std::initializer_list<int> myList;
    size_t n = myList.size();
    myList = { 1,2,3,4,5,6 };
    n = myList.size();
    myList = { 11,22};
    n = myList.size();
​
    std::vector<int> a;
    a = func2();//值时乱码值
    a = func3();
​
}
​
int main(void)
{
    test01();
    test02();
    test03();
    system("pause");
    return 0;
}

问题19:__FILE__, __LINE__是什么?

在写程序的时候,总是或多或少会加入一些printf之类的语句用于输出调试信息,但是printf语句有个很不方便的地方就是当我们需要发布程序的时候要一条一条的把这些语句删除,而一旦需要再次调试的时候,这些语句又不得不一条条的加上,这给我们带来了很大的不便,浪费了我们很多的时间,也造成了调试的效率低下。所以,很多人会选择使用宏定义的方式来输出调试语句。

编译器内置宏,ANSI C标准中有几个标准预定义宏(也是常用的):

LINE: 在源代码中插入当前源代码行号; FILE: 在源文件中插入当前源文件名; DATE: 在源文件中插入当前的编译日期 TIME: 在源文件中插入当前编译时间; STDC: 当要求程序严格遵循ANSI C标准时该标识被赋值为1; __cplusplus: 当编写C++程序时该标识符被定义。 ———————————————— 版权声明:本文为CSDN博主「郎涯技术」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:C/C++ __FILE__,__LINE__输出调试信息_郎涯技术-CSDN博客

问题20:关于宏的一些知识

#define 定义一个预处理宏 #undef 取消宏的定义

#if 编译预处理中的条件命令,相当于C语法中的if语句 #ifdef 判断某个宏是否被定义,若已定义,执行随后的语句 #ifndef 与#ifdef相反,判断某个宏是否未被定义 #elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if #else 与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else #endif #if, #ifdef, #ifndef这些条件命令的结束标志. defined  与#if, #elif配合使用,判断某个宏是否被定义

问题21: c++中 . 和 -> 的区别是什么?

主要用于访问类的成员。

->主要用于类类型的指针访问的成员,而.运算符,主要用于类类型的对象访问类的成员。

class A{
​
public:
​
  int a;
​
}
​
A  ma;
​
A *p=ma;

指针p应用->来访问成员a,比如p->a,而ma应使用.来访问,比如ma.a区别就在这里,凡是指针就使用->,对象就使用.运算符。

如果定义了一个结构体数组。

struct student
​
{
​
  int age;
​
  char name[100];
​
};
​
struct student array[3];

通过这个数组进行调用这个结构体中的成员的时候,只能使用.而不能使用->。

如果要是让一个指针指向这个数组的话,可以使用->这个符号。

struct student *p = array;

p->age = 30;

memcpy(p->name,"刘德华");

这样是可以的。

问题22: wcscpy_s

wcscpy_s是一个能够拷贝宽字符类型字符串的安全函数。它返回一个error_t类型的值。

wcscpy_s的函数原型为:

error_t wcscpy_s(wchar_t *strDestination,size_t numberOfCharacters,const wchar_t *strSource); 

其中strDestination为指向将要复制字符串的目的缓冲区的地址,numberOfCharacters为缓冲区大小(以字符计),strSource为指向源字符串的指针

下面是一个例子:

PCWSTR string = TEXT("mydef"); 
WCHAR buff[6];
wcscpy_s(buff, _countof(buff), string);

其中TEXT是一个宏,该宏能够自动判断当前字符集环境(Unicode还是ANSI还是其他的)并进行适当的转换。

与其类似的还有以下函数:

error_t wcscat_s(wchar_t *strDestination,size_t numberOfCharacters,const wchar_t *strSource)

问题23: #define __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_1

#define __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_1(_ReturnType, _FuncName, _DstType, _Dst, _TType1, _TArg1)   
            extern "C++"                                                                 
        {                                                                             
                template <size_t _Size>                                                   
                inline   
 _ReturnType __CRTDECL _FuncName(_DstType (&_Dst)[_Size], _TType1 _TArg1) _CRT_SECURE_CPP_NOTHROW 
                {
                    return _FuncName(_Dst, _Size, _TArg1);
                }
       }

这个 __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0 宏在 MSVC 的 MSVCRT 头文件 crtdefs.h 中定义,也在 MinGW(对 MSVCRT 开源重实现)中定义。

作者:breaker
链接:https://www.zhihu.com/question/323362565/answer/693091109
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
​
#include <stdio.h>
#include <stdlib.h>
​
#define __MY_DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(__ret, __func, __dsttype, __dst)   \
extern "C++" {                                              \
template <size_t __size>                                    \
inline __ret __cdecl __func(__dsttype (&__dst)[__size]) {   \
    return __func(__dst, __size);                           \
}   \
}
​
int accumulate(const int* arr, size_t len)
{
    int sum = 0;
    for (size_t i = 0; i < len; ++i)
        sum += arr[i];
    return sum;
}
​
__MY_DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(int, accumulate, int, arr)
​
int main()
{
    int arr[] = {12, 23, 34, 45, 56};
    int sum1 = accumulate(arr, _countof(arr));
    int sum2 = accumulate(arr);
    printf("sum1 = %d\n"
           "sum2 = %d\n",
           sum1, sum2);
    return 0;
}

由于在C/C++里,原始数组作参数会隐式转换为指针,无法使用sizeof来得出数组的长度,所以一般会带一个参数来传数组长度:

int func(char array[], size_t length); // array为传递的数组,length传递长度

但是手动传长度嫌麻烦,C++里可以利用模板推导长度:

template <sizet __size>
inline int func(char (&array)[__size]) // 模板参数__size自动推导长度
{
    return func(array, __size);       // 调用包含数组和长度两个参数的版本
}

问题中的宏用于快速定义该模板:

// 宏的四个参数依次是:
// __ret     : 返回值类型 
// __func    : 函数名 
// __dsttype : 参数数组的元素类型 
// __dst     : 模板函数的形参名字(这个可以随便写)
__DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(int, func, char, random)

#include <cstdio>

// 好像已经被定义了
#ifdef __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0
#undef __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0
#endif

// 题中的宏
#define __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(__ret, __func, __dsttype, __dst) \
extern "C++" { \
	template <size_t __size> \
	inline __ret __cdecl __func(__dsttype (&__dst)[__size]) { \
		return __func(__dst,__size); \
	} \
}

// 带两个参数的原始函数
int func(char array[], size_t length)
{
	printf("length : %u\n", length);
	return 0;
}

// 生成模板
__DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(int, func, char, chicken_you_are_too_beautiful)

// 不解释,你懂的
int main()
{
	char array1[15];
	char array2[20];
	func(array1);
	func(array2);
	
	return 0;
}

输出:

$ ./a.out
length : 15
length : 20

既然用推导了数组长度,干脆连类型一起推导了:

#include <cstdio>
​
// 带两个参数的原始函数
size_t func1(char array[], size_t length)
{
    (void)array;
    printf("func1 array length : %u\n", length);
    return length;
}
​
char* func2(int array[], size_t length)
{
    (void)array;
    static char ret[] = "hello world";
    printf("func2 array length : %u\n", length);
    return ret;
}
​
// 更加简化的方式(-std=c++14)
#define SIMPLIFY_OVERRIDE(__func) \
template<typename __dsttype, size_t __size> \
inline decltype(auto) __func(__dsttype (&array)[__size]) \
{ \
    return __func(array, __size); \
}
​
SIMPLIFY_OVERRIDE(func1)
SIMPLIFY_OVERRIDE(func2)
​
// 不解释,你懂的
int main()
{
    char array1[15];
    char array2[20];
    printf("func1 return %u\n", func1(array1));
    printf("func1 return %u\n", func1(array2));
    
    int array3[25];
    printf("func2 return %s\n", func2(array3));
    
    return 0;
}

问题24: swprintf_s

函数原型

template <size_t size>
int swprintf_s(
   wchar_t (&buffer)[size],
   const wchar_t *format [,
   argument]...
); // C++ only

这个函数只有C++中才有,C++中字符串遇到 int, double等时不能像Java那样自动实现类型的转换 ,所以需要程序员做些事情来代替编译器的工作

参数:

1.一个类型为wchar_t的数组

2.数组的大小

3.目标字符串的格式

4.需要你拼接的部分

注:当然你的格式可以自己定义,后面的参数根据你自己定义的格式来。swprintf_s的功能也不仅限于此,还有许多其它用法。这里就不讲了。

// crt_swprintf_s.c
// wide character example
// also demonstrates swprintf_s returning error code
#include <stdio.h>
​
int main( void )
{
   wchar_t buf[100];
   int len = swprintf_s( buf, 100, L"%s", L"Hello world" );
   printf( "wrote %d characters\n", len );
   len = swprintf_s( buf, 100, L"%s", L"Hello\xffff world" );
   // swprintf_s fails because string contains WEOF (\xffff)
   printf( "wrote %d characters\n", len );
}

输出结果:

wrote 11 characters wrote -1 characters

问题25: CFile与CArchive

CFile是MFC文件类的基类,它直接提供非缓冲的二进制磁盘输入/输出设备,并直接地通过派生类支持文本文件和内存文件。CFile与CArchive类共同使用ÿ

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C MFCMicrosoft Foundation Class)是一种基于C++的应用程序框架,用于开发Windows平台上的图形用户界面(GUI)应用程序。它提供了许多类和函数来简化GUI应用程序的开发过程,包括绘制图形、处理用户输入、管理窗口和控件等。下面是一个简要的C MFC图文教程。 首先,为了使用C MFC,我们需要安装Visual Studio开发环境。在Visual Studio中创建一个新的MFC项目,选择适当的应用程序类型,例如单文档或多文档应用程序。在创建项目后,会生成一些默认的代码和文件,我们可以在这些基础上进行开发。 接下来,我们可以在窗口中添加各种控件,例如按钮、文本框、列表框等。可以通过拖拽控件到窗口上或使用资源编辑器来完成。对于每个控件,我们可以为其定义属性、事件处理程序等。 然后,我们可以在C++代码中编写事件处理程序,以响应控件的各种事件,例如按钮的点击事件。在事件处理程序中,我们可以执行各种操作,例如读取输入、更新界面、绘制图形等。 除了控件和事件处理程序,我们还可以使用MFC提供的绘图函数来绘制各种图形。这包括绘制直线、矩形、椭圆等基本图形,以及绘制文本、位图等其他元素。 最后,我们可以通过编译和运行应用程序来查看和测试我们的界面和功能。可以进行调试和修改,直到满足我们的需求为止。 总结起来,C MFC图文教程可以包括创建MFC项目、添加控件、编写事件处理程序、绘制图形等内容。通过学习这些基本知识,我们可以开始开发自己的MFC应用程序,并在Windows平台上创建功能丰富的图形界面。 ### 回答2: MFCMicrosoft Foundation Class)是微软提供的一种用于Windows平台开发视窗界面的编程框架。下面是一个简单的MFC图文教程。 1. 安装MFC:首先,确保已经安装了Visual Studio开发环境。在进行安装时,务必勾选MFC组件,这样才能使用MFC进行开发。 2. 创建MFC项目:打开Visual Studio,在菜单中选择“文件”->“新建”->“项目”,选择“Visual C++”->“MFC”->“MFC应用程序”。填写项目名称和存储位置,点击“确定”。 3. 设计界面:在MFC应用程序向导中,可以选择对话框或文档视图结构。对话框结构用于创建基于对话框的用户界面,而文档视图结构用于创建多文档类型的应用程序。选择适合自己项目需求的结构。 4. 添加控件:在对话框或文档视图中,可以通过拖拽的方式添加各种控件,如按钮、文本框和列表框等。双击控件可以打开相应的事件处理函数,编写控件的功能代码。 5. 设置属性:对于每个控件或窗口,可以通过右击选择“属性”来设置其属性,如大小、位置和样式等。通过属性设置,可以自定义界面的外观和行为。 6. 处理事件:在事件处理函数中,可以编写代码响应用户的操作。例如,当用户点击按钮时,可以在按钮的点击事件处理函数中编写相应的代码。 7. 编译运行:完成界面设计和事件处理后,可以点击菜单中的“生成”->“生成解决方案”来编译项目。如果没有错误,可以运行程序进行测试。 8. 调试和发布:在开发过程中,可能会遇到各种问题。Visual Studio提供了强大的调试工具,可以帮助我们定位和修复错误。完成调试后,可以通过“生成”->“发布解决方案”将程序发布为可执行文件。 以上是一个简单的MFC图文教程,希望对您有所帮助。如果您想深入学习MFC,可以参考相关的书籍和在线教程,以便更好地掌握和应用这个强大的开发框架。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值