vc++培训教程 第1章.docx
- 文档编号:4332282
- 上传时间:2022-11-29
- 格式:DOCX
- 页数:18
- 大小:28.23KB
vc++培训教程 第1章.docx
《vc++培训教程 第1章.docx》由会员分享,可在线阅读,更多相关《vc++培训教程 第1章.docx(18页珍藏版)》请在冰豆网上搜索。
vc++培训教程第1章
第1章掌握C++
在学习VisualC++6.0编程之前,有必要复习一下C++中面向对象的一些基本概念。
我们知道C++比C有许多优点,主要体现在封装性(Encapsulation)、继承性(Inheritance)和多态性(Polymorphism)。
封装把数据与操作数据的函数衔接在一起,不仅使程序结构更加紧凑,并且提高了类内部数据的安全性;继承性增加了软件的可扩充性及代码重用性;多态性使设计人员在设计程序时可以对问题进行更好的抽象,利于代码的维护和可重用。
VisualC++不仅仅是一个编译器,更是一个全面的应用程序开发环境,使用它你充分利用具有面向对象特性的C++来开发出专业级的Windows应用程序。
因此,熟练掌握本章内容,以便为后继章节打下良好的基础。
1.1类的定义与应用
在C语言中,我们学过结构体,用于将描述某一对象的若干变量包装成一个整体使用,就像是一个集合,在这个集合里的变量,可以是相同、部分相同,或完全不同的数据类型。
但该集合中没有将与该对象相关的函数包含进来。
C语言中的结构体只能描述一个对象的特征(属性),不能描述一个对象的动作(方法)。
在C++中,我们是通过类的定义来解决这个问题的,在类的定义中,不仅可以包含变量,还可以包含函数。
1.1.1结构的定义
定义结构(struct)的语法如下:
struct结构数据类型名称
{
成员数据类型成员名称;
};
注意定义结尾的分号。
在C++中分号代表一段程序的结尾,因此,在类和结构定义完后,在大括号后面一定要用";"号结尾,忘记";"是许多人常犯的错误。
下面就是一个简单结构的定义,用来描述一个学生的共同属性:
structstudent
{
intnumber;
charname[10];
intage;
};
定义了该结构,我们就多了一个“student”数据类型,这时候就可以象使用C语言中的其他标准数据类型那样来声明一个结构变量。
例如:
structstudentstu;
这样我们就定义了一个名为stu的student结构类型的变量。
1.1.2类的定义与应用
1.1.2.1类与结构
下面让我们来看一个简单的类(class)的定义:
classstudent
{
public:
intnumber;
charname[10];
intage;
};
看起来是不是和结构的定义很类似?
在C++语言中,类和结构的定义除了使用关键字“class”和“struct”不同之外,更重要的是在成员的访问控制方面有所差异。
另外,类兼具有封装、继承、数据隐藏等特性。
1.1.2.2类的使用
下面,我们将通过清单1-00中的代码来讲解类的使用。
完整的例程请参见光盘中的例子代码EX01-00。
清单1-00类的使用
———————————————————————————————————————
1#include"iostream.h"
2classCPoint
3{
4public:
5intx1;
6inty1;
7voidOutput();
8CPoint();
9CPoint(intx2,inty2);
10~CPoint();
11private:
12intx2;
13inty2;
14int*pCount;
15};
16voidCPoint:
:
Output()
17{
18if(pCount)
19(*pCount)++;
20else
21{
22pCount=newint;
23*pCount=1;
24}
25cout<<"thefirstpointis("< 26cout<<"thesecondpointis("< 27} 28CPoint: : CPoint() 29{ 30pCount=0; 31cout<<"thefirstconstructoriscalling"< 32} 33CPoint: : CPoint(intx2,inty2) 34{ 35this->x2=x2; 36this->y2=y2; 37pCount=0; 38cout<<"thesecondconstructoriscalling"< 39} 40CPoint: : ~CPoint() 41{ 42if(pCount) 43{ 44cout<<"你调用了Output成员函数共"<<*pCount<<"次"< 45deletepCount; 46} 47else 48cout<<"你还没有调用过Output成员函数"< 49cout<<"thedeconstructoriscalling"< 50} 51voidOutput(CPointpt) 52{ 53cout<<"thefirstpointis("< 54//cout<<"thesecondpointis("< 55//上面被注释的语句会造成编译错误,因为不能从类的外部访问类中的私有成员。 56} 57voidmain() 58{ 59if(1==1)//限定pt变量的有效范围 60{ 61CPointpt; 62cout<<"请输入两个整数"; 63cin>>pt.x1>>pt.y1; 64//pt.x2=10; 65//pt.y2=10; 66//上面被注释的语句会造成编译错误,因为不能从类的外部访问类中的私有成员。 67pt.Output(); 68pt.Output(); 69pt.Output();//故意演示Output被调用多次的情况。 70Output(pt); 71} 72CPointpt(10,10); 73pt.Output(); 74} ——————————————————————————————————————— 注意,在c++中,//......用于注释一行,/*......*/可以注释多行。 2~15行代码定义了一个类CPoint,其中包含有变量,称之为成员变量,也包含有函数的声明,称之为成员函数。 在类定义之外,我们必须对成员函数进行实现,成员函数的实现格式为: 返回类型 类名: : 函数名(参数列表) { 函数体代码 } 51~56行代码编写了一个名为Output的全局函数,注意与16~27行代码编写的类CPoint中的Output成员函数区别。 57~74行代码还编写了一个main主函数,代码中演示了如何使用CPoint类。 C++中提供了一套输入输出流方法的对象,它们是cin、cout和cerr,对应c语言中的三个文件指针stdin、stdout、stderr,分别指向终端输入、终端输出和标准出错输出(也从终端输出)。 cin与>>一起完成输入操作,cout、cerr与<<一起完成输出与标准错误输出。 例如程序main函数中第63行代码使用cin为pt.x1,pt.y1输入两个整数,Output函数中第53行使用cout连续输出字符串、整数、字符、换行。 在输出中使用endl(endofline)表示换行,相当于'\n'。 利用cin和cout比scanf和printf要方便得多,cin和cout可以自动判别输入输出数据类型而自动调整输入输出格式,不必像scanf和printf那样一个个由用户指定。 使用cin,cout不仅方便,而且减少了出错的可能性。 从16~27行类CPoint的Output成员函数的实现中,我们可以看到类中的成员函数可以直接访问同类中的成员变量,如: x1,y1,x2,y2。 说明: 如果成员函数中的局部变量与成员变量同名,则在局部变量的作用范围内,成员变量不起作用。 如果有全局变量与成员变量同名,则在成员变量的作用范围内(所有同类成员函数中),全局变量不起作用。 main函数中59~71行的if(1==1)语句部分,主要是为了说明局部变量的有效范围。 局部变量的有效范围位于定义它的复合语句之中,一对{}中所定义的语句即一个复合语句。 也就是说,局部变量的有效范围并不是在定义它的函数体当中,而是在外层最靠近它定义的那对{}中,main()函数中第61行定义的第一个CPoint对象pt在if语句的}处被系统释放。 在类中使用的private和public访问修饰符,它们限定成员被访问的范围。 从一个修饰符的定义处,直到下一个修饰符定义之间的所有成员都属于第一个修饰符所定义的访问类型。 以public定义的成员,能够被同类中的成员函数及类定义之外的所有其他函数访问,如CPoint类中的x1,y1,Output等成员变量与函数。 但要注意的是,在类之外的函数中访问类成员,必须是下列语法格式: 对象.成员变量; 以private定义的成员,只能被同类中的成员函数访问,不能在其他函数中访问(即使是对象.成员的格式),如类CPoint中的成员变量x2,y2能被成员函数Output访问,但不能在main函数及全局Output函数中访问。 说明: 如果在类定义中的开始处没有使用任何修饰符,则在类定义的开始处使用private作为其默认修饰符。 在C++中定义struct结构体也可以包含成员函数,除了开始处使用的默认修饰符为public外,其余之处与class类基本相同。 1.2函数的重载 在C语言中,如果同一程序中有两个函数名一样,不管其参数类型或个数是否一样时,编译就会出错。 例如,程序中有两个名为Add的函数定义: intAdd(intx,inty); intAdd(intx,inty,intz); 在C语言中编译时,将提示函数名重复错误,而在C++中上述定义是合法的,这就是C++新增的函数重载(Overload)的概念。 C++能够根据函数调用时所传递的参数个数及数据类型的不同而选择适当的函数。 1.2.1重载参数个数不同的函数 例如,下面的清单1-01中的程序拥有两个函数名相同,但参数个数不同的函数,让我们看看他们在C++编译器中运行的结果是怎样的。 完整例程请参见光盘中例子代码EX01-01。 清单1-01函数重载(参数个数不同) ——————————————————————————————————————— 1#include 2voidadd(intx,inty) 3{ 4cout<<"functionhavetwoparameters: "< 5cout<<"thesumis: "< 6} 7voidadd(intx,inty,intz) 8{ 9cout<<"functionhavethreeparameters: "< 10cout<<"thesumis: "< 11} 12voidmain() 13{ 14add(1,2); 15add(1,2,3); 16} ——————————————————————————————————————— 2~6行代码定义了一个带有两个参数的add函数,7~11行代码定义了带有三个参数的add函数,执行清单1-01的程序会发现,不仅不报错而且分别打印出3和6两个结果。 可见,C++编译器可以根据参数个数的不同来选择执行相应的函数。 1.2.2重载参数数据类型不同的函数 请看清单1-02的程序拥有两个函数名相同、参数个数相同,但参数数据类型不同的函数,让我们看看他们在C++编译器中运行的结果是怎样的。 完整例程请参见光盘中例子代码EX01-02。 清单1-02函数重载(参数类型不同) ——————————————————————————————————————— 1#include 2voidoutput(intx) 3{ 4cout<<"thisisanumber: "; 5cout< 6} 7voidoutput(char*c) 8{ 9cout<<"thisisastring: "< 10} 11voidmain() 12{ 13inta=6; 14chars[6]="china"; 15output(a); 16output(s); 17} ——————————————————————————————————————— 2~6行代码定义了一个参数为int型的output函数,7~10行代码定义了一个参数为char型的output函数,同样编译运行也不会报错,程序执行结果为: thisisanumber: 6 thisisastring: china 虽然两个函数的参数个数是一样的,但是,编译器会根据传入的参数类型的不同而选用对应的函数来执行。 然而,值得注意的是,编译器只能根据传入的参数个数和参数类型来识别函数,全然不能以函数的返回值来分辨函数,如intoutput();和charoutput();这两个函数的声明在一个程序中是不能通过的,请读者在使用时多加注意! 1.3构造函数与析构函数 在类的使用过程中,读者可能会有这样的经历,我们定义的类具有很多的成员变量,而我们又不得不由此类创建多个该类的对象,那么,当我们需要为每个对象的成员变量赋初值的时候,就要一一地为对象的成员变量指定初始值,这样是不是很烦琐? 那么C++对这个问题是怎样解决的呢? 1.3.1构造函数 在类的定义中,有一种特殊的函数,函数的名称与类的名称相同,我们称之为构造函数(Constructor)。 构造函数的主要功能是为对象分配空间,也可用来为类成员变量赋初值,因此构造函数不能有返回类型,甚至不能有return语句。 之前我们讲过C++支持函数的重载,所以一个类中可以有多个不同参数形式的构造函数。 用类去定义一个变量(后面可以附带参数),也就是在内存中产生一个类的实例(用类定义的实例变量通常也叫对象)时,程序将根据参数自动调用该类中对应的构造函数。 如清单1-00代码中的第61行,当调用CPointpt;语句来产生一个实例时,相应的会去调用构造函数CPoint(),而以第72行CPointpt(10,10)语句形式来产生一个实例时,就会调用构造函数CPoint(intx2,inty2)。 除此之外,C++的构造函数不仅仅只能为成员变量设置固定的值,与一般的函数一样,也允许外部程序的值传入构造函数,包括用户从键盘输入的值。 1.3.2析构函数 如果类中有一个函数定义格式为~类名(),如~CPoint(),这个函数就称为析构函数(Destructor)。 析构函数是“反向”的构造函数,同样析构函数也不允许有返回值,更重要的是析构函数不允许带参数,并且一个类中只能有一个析构函数。 析构函数的作用正好与构造函数相反,对象超出其作用范围,对应的内存空间被系统收回或被程序用delete删除时,析构函数被调用。 可以说,析构函数主要用来被系统调用来释放早先分配给对象的内存空间。 根据构造函数的这种特点,可以在构造函数中初始化对象的某些成员变量,也就是初始化实例对象。 在析构函数中释放对象运行期间所申请的资源,如动态申请的内存空间。 通俗地讲,构造函数的作用是,在对象产生时,自动为其赋初值;析构函数的作用是,在对象消失时,为对象处理后事。 这两者是成对出现的。 提示: 在类中定义成员变量时,不能给这些变量赋初值。 如: classA { intx=0;//错误,此处不能给变量x赋值。 }; 在清单1-00代码里,类CPoint中第14行定义了一个指向整数的指针成员变量pCount,它所指向的内存地址中的数据(一个整数大小空间)用于统计成员函数Output被调用的次数。 提示: 在例子中的pCount的用法在实际应用中并不合理,我们这么使用主要是为了分析问题。 在c++动态申请内存,是new操作符完成的,new操作符申请的内存是从堆中分配的。 在程序中定义的变量所用内存是从栈中分配的,当变量超出其作用范围时,系统收回该变量所占用的内存,以后再分配给其他变量使用。 new操作符申请的内存空间在程序运行期间是不会被系统收回的,除非程序调用delete操作符明确要求释放该内存空间。 现在,我们对清单1-00程序中关于pCount的语句进行分析。 第14行代码int*pCount定义了一个变量pCount,pCount变量自身是系统从栈中分配的,占四个字节。 这个过程同定义一个整数变量的过程(intx;)没有什么两样,其中的数据没有被初始化,是一个不确定的数,一般不等于零。 与定义一个整数变量不一样的是,pCount中的数据是用来表示某一内存块的地址的。 尽管系统已为pCount自身分配了内存空间,但pCount中的那个不确定的数据所对应的地址空间却是没有分配的,是不可使用的,如图1所示。 *pCount=1;表示将pCount所指向的内存块中数据置为1,如果pCount中的数据所指向的内存块是不存在的或未被分配的,程序将会出错。 同样(*pCount)++;表示将pCount所指向的内存块中数据加1,deletepCount;表示释放pCount所指向的内存块,这些操作都要求pCount中的数据所指向的内存块是被分配过的,合法的。 第22行代码pCount=newint;通过new操作符在堆中分配了一个整数变量空间大小的内存块,并将该内存块的首地址赋值给pCount,pCount中的数据便指向了这一段合法的地址空间,如图2所示。 如果程序超出了pCount的定义范围,pCount变量自身的内存空间将被系统收回,但不影响pCount中的数据所指向的内存块,如果该内存块是通过new分配的,必须保证该内存块不再被使用时用delete删除掉,否则将会造成内存泄漏。 那么,我们如何确定pCount中的数据所指向的内存块是否被正常分配过的呢? 我们一般通过检查pCount是否为NULL来判断的,这就需要我们在pCount被分配之后将其初始化为NULL。 由于pCount是在类中定义的成员变量,不能在定义变量时为其赋值,所以必须在构造函数中将其初始化为NULL。 这样,一旦用CPoint定义一个对象,该对象中的pCount成员将立即被初始化为NULL,这下读者应该明白构造函数的作用了吧! 在16~27行Output成员函数中,检查pCount是否为NULL,如果为NULL,则用newint为其分配一个整数变量大小的空间,并将其地址赋值给pCount,否则,直接引用原来已分配的空间。 当CPoint定义的对象超出其有效范围时,为该对象分配的空间将被释放,pCount变量也将随该对象一并被释放,如果pCount已指向一个用new操作符分配的内存空间,该内存空间不会被释放,所以,我们必须在析构函数中用delete操作符释放该内存空间,保证pCount被释放前也释放掉pCount中的数据所指向的内存空间,这下读者也应该明白析构造函数的作用了! 1.4this指针的引用 下面我们继续分析清单1-00代码,如果成员函数Output被调用,一定是产生了一个对象实例,在这假设对象名称为a,并以a.Output形式调用的,Output的操作一定是针对对象a的。 有时,成员函数需要访问它所依赖的那个对象,而不仅仅是这个对象中的其他成员。 在类的成员函数中,可以用this关键字代表成员函数所依赖的那个对象的地址,所以,在成员函数中可以用this->成员的方式访问其它的成员,如CPoint(intx2,inty2)函数中用this->x2访问成员变量x2。 在成员函数中,我们通常可以省略this->,直接访问类中的成员变量。 在CPoint(intx2,inty2)函数中,由于函数参数变量x2,y2与成员CPoint中的成员变量x2,y2同名,要在该函数中访问成员变量x2,y2,可用this->x2,this->y2与参数变量x2,y2区分。 小技巧: 在以后的MFC编程中,如果在成员函数中想调用同类中的某个成员,可以使用VC++提供的自动列出成员函数功能,使用this->,VC++将列出该类中的所有成员,我们可以从列表中选择我们想调用的成员。 自动列出成员函数功能,可以提高编写速度,减少拼写错误。 特别是我们不能完全记住某个函数的完整拼写,但却能够从列表中辨别出该函数时,自动列出成员函数功能更是有用。 事实上,在各种IDE编程环境中,我们通常都没有完全记住某些函数的完整拼写,只是记住其大概写法和功能,要调用该函数时都是从自动列出成员函数中选取的。 这样能够大大节省我们的学习时间,我们没有花大量的时间去死记硬背许多函数,利用自动列出成员函数功能和帮助系统,却也能够在编程时顺利使用这些函数,等用的次数多了,也就在不知不觉中完全掌握了这些函数。 注意比较Output全局函数与Output成员函数的差别。 对Output全局函数的调用,可以理解成“输出某个pt点的坐标”,是一种谓宾关系,是面向过程(或函数)Output的。 对Output成员函数的调用,可以理解成“pt这个点对象执行输出动作”,是面向对象pt的。 希望通过这样的比较,能够有助于读者理解c++中关于面向对象的概念。 1.5类的继承与protected访问修饰符 类是可以继承(Inherit)的,就是基于现有的类再创建新类。 这里假定类B继承了类A,那么,我们称A为基类(BaseClass,也叫父类),B为派生类(DerivedClass,也叫子类)。 派生类不但拥有自己新的成员变量和成员函数,还可以拥有基类的成员变量和成员函数。 一个派生类可以只继承一个类,也可以继承多个类,我们称这种情况为多重继承。 1.5.1单一继承 在“单一继承”这种最普通的形式中,派生类仅有一个基类,定义方法是: class派生类名: 访问权限基类名称 { ..... }; 要实现类B与类A的继承关系,我们在定义类B之前必须已定义了类A,并用如下的格式定义类B。 classB: public或privateA { .... }; 讲到类的继承后,我们再讲解另一种成员访问权限修饰符,protected。 public,protected,private三种访问权限的比较: public定义的成员可以被在任何地方访问。 protected定义的成员只能在该类及其子类中访问。 private定义的成员只能在该类自身中访问。 派生类可以用public和private两种访问权限继承基类中的成员,如果在定义派生类时没有指定如何继承访问权限,则默认为private。 如果派生类以private继承基类的访问权限,基类中的成员在派生类中都变成private类型的访问权限。 如果派生类以public继承基类的访问权限,基类中的成员在派生类中
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- vc+培训教程 第1章 vc 培训 教程
![提示](https://static.bdocx.com/images/bang_tan.gif)