51.MFC单文档应用程序的结构框架
A。首先利用全局应用程序对象theApp启动应用程序。正是产生了这个全局对象,基类CWinApp的中的this指针才能指向这个对象。
B. 调用全局应用程序对象的构造函数,从而就会调用其基类的CWinAPP的构造函数,后者完成一些初始化工作,并将应用程序对象的指针保存起来。
C. 进入WinMain函数。在AfxWinMain函数中可以获取子类(CTestApp)的指针,利用此指针调用虚函数:InitInstance,根据多态性原理,实际上调用的是子类CTestApp的InitInstance函数。后者完成一些初始化工作,包括窗口类的注册、创建、显示和更新。期间会多次调用CreaeEx函数,因为一个单文档MFC应用程序有多个窗口,包括框架窗口、工具条和状态条。
D. 进入消息循环知道WM_QUIT消息时,退出消息循环。
App类:程序启动和初始化工作
CMainFrame类:框架类,其中的成员函数OnCreate()会响应WM_CREATE消息,在显示窗口之前更改窗口的结构,和显示之前的其他初始化工作。
CView类:可以用来响应客户窗口的一些消息,其父窗口是框架类窗口。
CTestDoc文档类:数据的存储和加载由文档类来完成,数据的显示和更改则由CView类来完成,从而把数据的显示和管理分离开来。
52. VC++6.0中常出现的"unexpected end of file while looking for precompiled header directive"的问题?
如何解决:"fatal error C1010:VC++6.0中常出现的"unexpected end of file while looking for precompiled header directive"的问题?
我想大家在VC6.0中经常回遇到这样的问题,如何解决呢?
1、看看是否缺少“;”,“}”
隐藏得深的是宏、.h文件的问题就要费点心思了
2、一定是你在类的部分定义被删除了,M$在每个类中定义一些特殊的常量,是成对的,如下: .h:
#if!defined(AFX_CHILDFRM_H__54CA89DD_BA94_11D4_94D7_0010B503C2EA__INCLUDED_)
#define AFX_CHILDFRM_H__54CA89DD_BA94_11D4_94D7_0010B503C2EA__INCLUDED_
.......
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_MAINFRM_H__54CA89DB_BA94_11D4_94D7_0010B503C2EA__INCLUDED_)
你可以新建一个类,然后把这些拷贝过去或补上就可以了。
3、在头部加入 #include "stdafx.h"
4、在CPP文件第一行加上#include "stdafx.h"。
或者Rebuild All.
53. 浮点数的存储方式
无论是单精度还是双精度在存储中都分为三个部分:
1符号位(Sign) : 0代表正,1代表为负
2指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
3尾数部分(Mantissa):尾数部分
例如8.25,用二进制表示为1000.01,用二进制科学计数法表示为1.00001*23
上面的指数位为3,尾数部分为00001,二进制科学计数法的整数部分一定是1,不用表示。对于float型变量指数位在存储时加127,对于double型指数位存储时加1023。下面是三个部分所占的位数(float,double)
对于8.25二进制应该存储为:
0 | 3+127 | 00001 | ||||||
0 | 1000 0010 | 0000 1000 0000 0000 0000 000 | ||||||
符号位1位 | 指数位8位 | 尾数位23位 | ||||||
0100 0001 | 0000 0100 | 0000 0000 | 0000 0000 | |||||
| ||||||||
0000 0000 | 0000 0000 | 0000 0100 | 0100 0001 | |||||
00 | 00 | 04 | 41 |
最后一行就是8.25在计算机内存中的存储位置。八位作为一个存储单元,最后一行从左到右为内存地址依次增加。低位存在低地址,高位存在高地址。
54.如何获取句柄
要找到某个CWnd对象的HWND,用GetSafeHwnd()。/
在窗口类中,有句柄的成员变量,可以直接访问: m_hWnd
在窗口类外,可以用AfxGetMainWnd()->m_hWnd获得。//
在MainFrame里直接用this;
其它地方用
CMainFrame* pMainFrame = (CMainFrame*)theApp.m_pMainWnd;///
想得到一个控件的的句柄: GetDlgItem(ID…)->m_hWnd;
得到视图的句柄:AfxGetMainWnd()->GetActiveView();
SDI:
((CFrameWnd*)(AfxGetApp()->m_pMainWnd))->GetActiveView(); ///
或者:CWnd *p;
(HWND)(p->GetSafeHwnd());
MDI:
((CFrameWnd*)(AfxGetApp()->m_pMainWnd))->GetActiveFrame()->GetActiveView();
55. 程序的时间复杂度和空间复杂度
空间复杂度:程序运行所需要的存储空间;时间复杂度:程序运行所需要的时间
空间复杂度包括:定长空间需求和变长空间需求。定长空间需求是指,与程序的输入输出没有关系的空间,主要有:代码空间,简单变量的空间,定长结构体的空间,常量的存储空间,全局变量的存储空间;///变长空间是指和问题的实力相关的变量存储空间还包括递归调用时的函数参数,局部变量和返回地址。例如:
Float sum(float list[], int n)
{if (n) return sum(list, n-1)+list[n-1]
Return 0;}
一次递归需要的空间为:参数1,数组指针,4个字节;参数2,整数,4个字节;返回地址,指针,4个字节;因此一次递归需要的存储空间为12个字节。可以看出递归调用需要大量的存储空间。
时间复杂度包括:程序时间开销包括,编译时间和运行时间。由于编译时间与实例无关,因此时间复杂度仅仅考虑程序的执行时间。我们现在计算程序执行的精确时间是和编译器有关的,因此我们仅仅统计程序执行各种基本操作的的次数。七种时间复杂度上界:
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)。
由于讨论10n和3n或者100n并没有太大的意义,我们一并归入O(n),这个复杂度的范围。对于多项式,nm+an+b+.........,我们仅仅取复杂度在n很大时,最大的一个项,O(nm)。
对应的也有时间复杂度的下界,用Ω(), 表示。如果时间复杂度的上界等于下界,则用Θ()表示。不过通常我们只关心时间复杂度的上界。
56. 程序真实的运算时间
在现实的世界中,我们较好的计算机业只能达到100亿次计算/秒左右,即使计算机的性能在提高,一下的结论依然是有效的。
n | n | nlogn | n2 | n3 | 2n |
|
|
10 | 0.01us | 0.03us | 0.1us | 1us | 1us |
|
|
30 | 0.03us | 0.15us | 0.9us | 27us | 1s |
|
|
40 | 0.04us | 0.21us | 1.6us | 64us | 18m |
|
|
50 | 0.05us | 0.28us | 2.5us | 125us | 13d |
|
|
100 | 0.10us | 0.66us | 10us | 1ms |
|
|
|
103 | 1us | 9.96us | 1ms | 1s |
|
|
|
105 | 0.1ms | 1.66ms | 10s | 11.57d |
|
|
|
可以看出对于指数级的时间复杂度,当n达到40时,已经达到了可以计算的极限。
在程序中,可以借助一些函数来计算某段程序执行的精确时间。
#include <time.h>
clock_t ClockStart;
clock_t ClockEnd;
ClockStart = clock();
...............................
ClockEnd = clock();
Double duration = ( (double) (ClockStart - ClockEnd) )/CLOCKS_PER_SEC;
这样就可以计算出某段程序的执行时间了。不过,如果执行的时间过短,会出现duration = 0。
57.进程所有部分在内存中的位置
地址上高,下底。(Windows) 地址上高,下底。(Linux)
heap --向上增长//堆内存 stack -->向下增长//栈内存
stack -->向下增长//栈内存 heap --向上增长//堆内存
bss segment//未初始化的静态变量 bss segment//未初始化的静态变量
data segment//全局变量初始化的静态变量 data segment//全局变量,初始化的静态变量
//向上增长 //向上增长
code segment//程序的代码段 code segment//程序的代码段
58. 关于重载
重载是C++新的特性。
重载的目的:便于记忆,提高了函数的易用性,这是C++语言采用重载机制的一个理由。
C++语言采用重载机制的另一个理由是:类的构造函数需要重载机制。
怎样重载:同名函数的参数不同(包括类型、顺序不同),那么容易区别出它们是不同的函数。为什么不用函数的返回值区分呢?原因就是, 在调用函数时,我们有可能不需要返回值,经常忽略返回值,因此不能用返回值进行区分。
编译器是怎样实现重载的:如果C++程序要调用已经被编译后的C函数,怎么办呢?假设某个C函数的声明如下:
void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C++程序不能直接调用C函数。C++提供了一个C连接交换指定符号extern“C”来解决这个问题。例如:
extern “C”
{
void foo(int x, int y);
… // 其它函数
}
重载的作用域:是不是两个函数重名,参数不同,就一定是重载呢?不是这样的,类的成员函数重载时,两个函数必须都是同一个类的成员函数。类A的成员函数foo(int)和类B的成员函数foo(char),因为作用域不同不能构成重载。类的成员函数和全局函数因为作用域不同也不能够构成重载,为了区分成员函数和全局函数在条用全局函数时在前面加上::来区分。
重载的二义性:重载在两种情况下会产生二义性。一是函数的参数带有默认参数的情况如:
Int foo(int a, int b = 0);和函数
Int foo(int a);由于在调用时默认参数b可以不用填写,因此会产生二义性。还有一种情况是隐式类型转换导致重载函数产生二义性。
int foo(int);int foo(float);
如果在调用时填入参数foo(0.5),编译器就不知道该把0.5转换为(int)0.5呢还是(float)0.5呢?这都是因为0.5没有类型(测试时,0.5在VC6.0中是double类型的)。
59.关于运算符重载
Type operator +(const Type &a, const Type &b);
A。运算符的重载中,其操作数至少有一个类类型。不允许对已有的内置类型的操作符进行重载。
B。操作符重载时,不能改变操作符原来的优先级和结合型。
C。当在类的外面重载操作符时,必须将其设置为要操作的类的友元函数。
D。如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两个参数的运算符叫做二元运算符。 如果运算符被重载为类的成员函数,那么一元运算符没有参数,二元运算符只有一个右侧参数,因为对象自己成了左侧参数。
运算符 | 规则 |
所有的一元运算符 | 建议重载为成员函数 |
= () [] -> | 只能重载为成员函数 |
+= -= /= *= &= |= ~= %= >>= <<= | 建议重载为成员函数 |
所有其它运算符 | 建议重载为全局函数 |
60.对于一个复杂的类,为什么不能用memcpy进行拷贝。
因为在一个类中,有各种各样的指针,比如虚表指针等。对这个类进行memcpy,类中的指针就会失去意义。对于一个不是由基本数据类型构成的结构体或者指针,不要用memecpy进行拷贝。