在写 C++ 程序时,大部分出现空格符的地方可用换行符代替。这条规则的一个例外是字符串字面值中的空格符不能用换行符代替。另一个例外是空格符不允许出现在预处理指示中。
1.void f(long lng,int i)。
{
int i1=lng;
il=(int)lng;
char c=i;
}
为什么要在C上++?(C的灵活,高效,可用,可移植)
C++设计者曾经考虑过用Modula-2,Ada,Smalltalk,Mesa,Clu代替C
编译的原理示意图:源代码→cpp(C语言的预处理器)→cfront(C++的编译器)→cc(C语言的编译器)→目标代码
设计者认为当时C语言是最具可移植性的汇编语言
基于预处理器的语言:Objective C
注意!这里C编译器仅仅是用做一个代码生成器,从C语言编译器产生的任何错误信息,所反映的或者是C编译器本身的错误,或者就是Cfront的错误,绝不会是C++源代码的,所有语法或语义错误原则上都由C++的前端Cfront捕捉!
C++的一个目标就是使C语言的预处理器变得多余。
Cfront的基本目标就是让C++具有合理的语义。C++不可能使用任何传统的预处理技术进行翻译。
C++是与C语言兼容的C的超集。
2.对象布局模型:(这是最关键的实现思想!)
就是把在一个类里定义的一集虚函数定义为一个指向函数的指针数组!!!
我们把这样的数组称为虚函数表(vtbl=virtual table)
这些类的每个对象都包含一个隐式指针,称为vprt,指向该对象的类的虚函数表。
eg:
class A
{
int a;
public:
virtual void f();
virtual void g(int);
virtual void h(double);
};
class B:public A
{
public:
int b;
void g(int); //overrides A::g()
virtual void m(B*);
};
class C:public B
{
public:
int c;
void h(double); //overrides A::h();
virtual void n(C*);
};
注意!!!所以类C的一个对象可以这样理解:
对象中包含隐式指针vprt,vprt所指向的vtbl包含(&A::f() &B::g() &C::h() &B::m() &C::n())有顺序!
故虚函数的调用:
void f(C* p) //定义了一个数组指针p
{
p->g(2); //在编译的时候等价于(*(p->vprt[1]))(p,2),这里个人理解为二级调用,第一级先由p指向vprt,再由vprt去调用g()
}
至此,对象再也不是它的类的数据成员的一个简单汇集,所以带虚函数的类的对象与C语言中的struct概念完全不同了!!!
但是作者始终没有把class和struct这两个概念分开!!!
3.1.覆盖和虚函数匹配:
虚函数只能被派生类里的函数覆盖,函数名应该相同,参数相同以及返回类型也应该相同(优点:避免任何形式的运行时参数类型检查,也不需要在运行是保存大量的类型信息)
eg:
class Base
{
public:
virtual void f();
};
class Derived:public Base
{
public:
void f(); //overrides Base::f()
};
2.基成员的遮蔽:
派生类里的名字将遮蔽基类中的具有相同名字的任何对象或函数
eg:
class X
{
int x;
public:
virtual void copy(X* p){ x=p->x;}
};
class XX:public X
{
int xx;
public:
virtual void copy(XX* p){ xx=p->xx; X::copy(p);}
};
void f(X a,XX b)
{
a.copy(&b); //ok
b.copy(&a); //error:copy(X*) is hidden by copy(XX*)
}
3.重载:
eg:
class complex
{
double re,im;
public:
complex(double);
complex(double,double);
friend complex operator+(complex,complex)
friend complex operator*(complex,complex)
//...
};
void f(complex z1,complex z2)
{
complex z3=z1+z2; //operator+(z1,z2)
}
void g(complex z1,complex z2,double d)
{
complex z3=z1+z2; //operator+(z1,z2)
complex z4=z1+d; //operator+(z1,complex(d))
complex z5=d+z2; //operator+(compex(d),z2)
}
4.连接问题:
void task::schedule(){/*...*/} //4+8 characters
void hashed::print(){/*...*/} //6+5 characters
complex sqrt(complex); //4 character plus 'complex'
double sqrt(double); //4 character plus 'double'
虚函数:
一个抽象的数据类型定义了一种黑盒子,一旦定义好之后,它就不会实际的与程序其他部分产生交互作用,没有其他方法能为某些新的用途而调整它,除非是修改他的定义。
class point{/*...*/};
class color{/*...*/};
enum kind{circle,triangle,square}; //枚举类型包含圆,三角形,矩形
class shape
{
point center;
color col;
kind k;
//representation of shape
public:
point where(){ return center; }
void move(point to){ center=to; draw();}
void draw();
void rotate(int);
//more operations
};
我们所需要的一种东西就是能区分任意形状的普遍性质和与某种特定形状有关的属性——这正是面向对象的程序设计思想所要解决的问题!!!
class shape
{
point center;
color col;
//...
public:
point where(){ return center;}
void move(point to){ center=to;draw();}
virtual void draw();
virtual void rotate(int); //virtual函数可以在将来对这些类继承的类中重新定义!!!virtual函数是为某些特定类型定义的功能
};
void rotate_all(shape** v,int size,int angle)
//rotate all members of vector"v"
//of size "size" "angle" degrees
{
for(int i=0;i<size;i++)
v[i]->rotate(angle);
}
class circle :public shape
{
int radius;
public:
void draw(){/*...*/};
void rotate(int){} //yes, the null function
}
<三>VC++
1.这些函数中包含一个或多个特定成员函数——构造函数,它们被用作初始化对象,构造函数定义前没
有返回值(其实构造函数是有返回值的,这个返回值就是构造完整后的对象的引用)
2.构造函数可以重载,即类的构造函数可以有多个,他们通过不同的参数表来区分。
3.如果类的定义中不带任何构造函数,那么编译器将产生一个不带任何参数的默认构造函数,对于任何一个作为类的数据成员的对象来说,编译器会调用默认的构造函数。当编译器使用默认构造函数时,会给其对应的类对象分配空间,但并不对其内部类型的值进行初始化
4.类成员的三个区:
(1)类的成员分三个区,分别用private、public、protected来说明。
(2)凡是定义在public内的成员可以被类外程序直接访问;
(3)凡是定义在private区的类成员,只能被本类的成员函数直
接访问,类外的程序则必须通过类区的成员函数间接访问。
5.继承和多态:
(1)为了保证基类数据封装的安全,无论何种继承方式得到的派生类都不能直接访问基类的private区成员
(2)
继承的格式:
class 派生类名:<访问权限>基类名列表{类定义实体}
注:
<1>基类名列表中各基类用逗号隔开。
<2>其中访问权限可以是public或private,它们表示两种不同的继承方式,以public方式继承得到的成员属性与其基类中的属性相同;以private方式继承得到的成员属性将全部成为private属性。
<3>前面提到的protected区成员在使用上与private区成员完全一样,惟一不同只是在派生时,protected区成员可以被派生类直接访问,即对派生类来说是可见的。
6.类派生引出的成员覆盖问题:
<1>派生类可以根据派生时的成员访问机制访问基类的任何成员(除非这些成员在派生类中重新进行了定义)。
<2>当基类的成员在派生类里面被重新定义时,可以用域操作符“∷”来强制调用基类成员。
7.基于对话框的程序
(1)基于对话框的程序与基于文本框的程序的工作原理基本相同,都是通过对程序外部接口组件进行操作,然后将消息映射给相应的函数并进行相应的处理。
(2)不同的是,基于文本框的程序的操作对象是比较简单的菜单,基于对话框的程序的操作对象是相对复杂的各种控件(当然一般的基于文本框的程序也可以调用对话框)。
8.Application Framework
(1)什么是Application Framework:
通俗的说,Application Framework是一个完整的程序模型,它具备了标准应用软件所需的一切基本功能,以及这些功能的使用接口。以术语来讲,Application Framework就是一组类构造起来的大模型。
(2)为什么要用Application Framework:
<1>使用Application Framework的最直接的原因是,Windows API函数非常复杂,很难直接用它来编写程序。而用MFC,一个类就可以帮我们解决以前一大堆API才能解决的事情。
<2>Application Framework的引入决不仅仅是为了减少我们花在Windows API上的大量时间。它带来的是一种全新的面向对象程序设计的思想和方法。
(3)目前,主要有三套Application Framework:
Microsoft的MFC(MicrosoftFoundation Class)
Borland的OWL(Object Window Library)
IBM的OCL(Open Class Library)
9.MFC
MFC主要分为以下几个大类:
(1)General Purpose Class:提供字符串类、数据处理类(数组和链表)、异常情况处理类、文件类等。
(2)Windows API Class:用来封装Windows API,如窗口类、对话框类等
(3)Application Framework Class:这些类是组成应用程序的骨干,包括文档/视图、消息驱动、消息映射、消息传递、动态创建、文件读写等
(4)High Level Abstractions:包括工具栏、状态栏、拆分窗口、滚动窗口等
(5)Operation System Extensions:包括OLE,ODBC,DAO,MAPI,WinSock,ISAPI等。
10.用Application Wizard生成的程序的结构:
(1)利用Application Wizard,没有写一行代码,我们就得到了一个可以运行的Windows程序(当然它只是个空架子,什么也干不了)。
(2)一般来说,MFC遵循这样的规则:类的声明放在头文件(.h)中,对象的实现放在相对应的程序文件(.cpp)中,各种资源(如各种对话框、位图、图标、字体等)放在资源文件(.rc)中,在编译的时候由编译器把它们组装起来。
11.MFC程序的来龙去脉:
(1)用传统的C/SDK编写Windows程序,最大的好处就是可以看清楚整个程序的来龙去脉和消息动向。然而在用MFC编写的应用程序中,这些东西都变得隐晦不明了。
(2)用C/SDK编写的Windows程序是从WinMain()函数开始执行的。
12.CwinApp——程序的诞生
(1)Windows程序总要有WinMain()函数,在MFC的程序中看不到它,是因为它被隐藏在Application Framework中了。
(2)MFC会根据你的程序名( 假设我们的程序名是MyApp)建立一个程序的主类CMyWinApp,这个类继承自CWinApp。在程序中,你需要建立一个在整个程序中惟一的CMyWinApp对象theApp,我们称之为程序对象。因为我们没有编写CMyWinApp的构造函数,由于虚函数的作用,MFC是调用CWinApp的构造函数来产生对象theApp。程序对象theApp产生后,也就相应分配了内存,程序也就诞生了。
13.CMyApp::InitInstance——主框架的构建
这是CMyWinApp定义的一个虚函数,我们需要自己改写这个函数。一般情况下,这个函数应该这样来改写:
CMyApp::InitInstance()
{
m_pMainWnd->new CMyFrameWnd();//首先创建CMyFrameWnd对象,CMyFrameWnd的构造函数会产生主窗口
m_pMainWnd->ShowWindow(m_nCmdShow);//调用ShowWindow()函数来显示窗口
m_pMainWnd->UpdateWindow();//调用UpdateWindow()发出WM_PAINT消息
Return Ture;
}
14.CMyApp::Run——程序的发动机
CMyApp::Run承担着整个程序的消息驱动任务,它利用Message Map机制,借助于一些宏(如DECLARE_MESSAGE_MAP)等,将消息循环建立起来,把不同的消息映射到不同的消息处理函数。例如: WM_PAINT 消息被映射到OnPaint 函数,WM_COMMAND(IDM_ABOUT)消息被映射到OnAbout函数。消息循环建立起来后,程序就这样不断的等待着外界消息的输入,映射消息,处理消息直到程序的结束。
15.CMyApp::ExitInstance——程序的死亡
当使用者单击了File菜单中的Close命令时。发出了WM_CLOSE消息,这个消息进而触发WM_DESTROY消息,然后触发WM_QUIT消息,遵循Message Map的原理WM_QUIT由CMyApp::ExitInstance来处理,如果我们没有改写这个函数,根据虚函数的概念,将由CWinApp::ExitInstance来处理这个消息,程序结束。以上的这些消息驱动,都已经由MFCFramework封装了起来,我们初学者知道其然就行了,不需要知道其所以然。
回顾:
1.C++的新特性:
(1)支持//注释行
(2)局部变量的声明可以放在程序的任何位置,只要是变量的首次声明即可
(3)在函数作用域内也可以访问同名的作用域外的变量,但是要加上作用域操作符::
(4)定义函数时可以定义一些参数的默认值来简化编程
(5)可用&操作符声明引用,声明为引用的变量是另一变量的别名,他们共用同一个存储空间。
(6)引用类型也可以运用到函数当中
(7)可以用const定义常量类型
(8)new和delete操作符
(9)面向对象机制
2.Visual C++ IDE Developer Studio:
(1)AppWizard:Visual C++提供的一个高级编程工具,它可以产生应用程序的C++源代码框架
(2)ClassWizard:一个交互式工具,用来建立新的类,把消息映射成类成员函数,或者把控制框映射为类变量成员
(3)WizardBar:一个可停泊的工具栏,可与Class Wizard配合使用。它主要用于快速访问一些Developer Studio最实用的功能,比如Class Wizard或Class View的一些功能
3.Windows 程序的基本框架
(1)基于文本框的程序
定义菜单,定义消息,定义函数
(2)基于对话框的程序
添加控件,定义消息,处理函数