qlearner的个人博客分享 http://blog.sciencenet.cn/u/qlearner

博文

C++大学教程学习笔记

已有 2961 次阅读 2012-12-1 21:35 |个人分类:编程学习|系统分类:科研笔记| 小知识

1、一个C++程序可由main函数和若干个类组成,每个类包含数据成员和成员函数。数据成员是为了说明类的对象的属性(类必须创建了对象才能对其进行操作,自己创建的每个新类都会成为一个新的类型,这些新的类型可以用来创建对象,故C++具有良好的扩展性);成员函数是为了执行具体的程序任务。向对象发送消息——每个消息都被认为是一次成员函数调用,并告诉对象的成员函数去执行它的任务,这就是“向对象请求服务”。
2、数据成员VS局部变量:局部变量声明在一个函数定义体中,只能在函数中声明它的行到函数定义结束的右花括号中使用,必须在使用前声明,并且不能在声明它们的函数外部被访问。数据成员则不考虑声明和使用顺序,且存在于对象的整个生命周期中,类的每个对象管理它自己的在内存中的一份数据成员副本。绝大多数数据成员声明出现在成员访问说明符private之后,说明该成员函数只能被该对象自己的成员函数所操作(友元函数和类也可以访问)。(根据经验,数据成员应声明为私有,成员函数应声明为公有,当然也有少数例外。)
数据成员可以被成员函数所操作,这样使得成员函数调用常常没有参数或者至少比常用的非面向对象编程语言中的函数调用具有更少的参数,这样能够减小出错的可能性。
3、类定义:类名如GradeBook,按照惯例,用户定义的类名以大写字母开头,类中每个随后的单词其首字母也为大写,这种大写风格常称为“骆驼风格”。类定义以分号结束。类的成员函数(所有函数都得指定返回类型,以说明函数完成任务后返回值的类型)的命名,按照惯例,函数名以小写字母开头,所有随后的单词以大写字母开头,如displayMessage。为了避免二义性,最好不要使传递给函数的实参与函数定义中相应的形参同名。
4、UML(Unified Modeling Language)统一建模语言,是一种标准化的形式用来描述面向对象系统。
5、cin和流提取运算符配合使用时,它读取字符直到遇到第一个空白间隔符为止。用getline(cin,nameOfCourse)从cin连续读取字符(包括分隔输入中的单词的空格),直到遇到换行符为止。
6、一个良好的软件工程:用private声明数据成员来实现数据隐藏,用public声明的set和get函数来允许类的客户访问隐藏了的数据,但这是间接的。客户明白正在试图修改或获取对象数据,但并不知道该对象如何执行这些操作。尽管类内的其它成员函数可以直接访问类的私有数据,但如果它们需要这些数据,也应该使用类的set函数和get函数。这样可以提供有效性检验,防止出现不正常的数值。
为了保持程序的清晰性和可读性,每个成员访问说明符在类定义中只使用一次,应将public成员放在最前面,这样更便于查找。
7、使用构造函数初始化对象的数据成员:声明的每个类都可以提供一个构造函数(constructor),用于类对象创建时的初始化。构造函数是一种特殊的成员函数,定义时必须和类同名,但它不同于其它函数,它不能返回值,故对它不可以指定返回类型。通常情况下,构造函数声明为public。对于每个创建的对象,C++要求调用一次构造函数,这有助于保证在程序使用该对象前它被正确地初始化,即给类的对象提供实参。构造函数的调用隐式地发生在对象创建时。构造函数在形参列表中指定它执行任务所需要的数据,且在创建对象时后面的圆括号中放置这些实参数据。
为类提供默认构造函数的两种方法(不接受实参的构造函数,称为默认构造函数):
(1)编译器隐式地在没有定义构造函数的类中创建一个默认的构造函数。对于基本类型的数据成员(如纯变量),这种默认构造函数不初始化它,因此,这种变量通常包含“垃圾”值;对于其它类的对象作为的数据成员,调用每个数据成员的默认构造函数来初始化。
(2)程序员显式定义一个不接受实参的构造函数。这样的默认构造函数将执行程序员规定的初始化任务,并且调用其它类的对象的每个数据成员的默认构造函数。
类的数据成员不能在类体中声明时初始化(有个例外,static const数据成员可以在类体内初始化)。强烈推荐由类的构造函数初始化这些数据成员。
注意:一个默认所有实参的构造函数也是一个默认的构造函数,即一个调用时可以不带任何实参的构造函数,如:Time(int = 0,int = 0,int = 0)。对函数默认实参值的任何修改都要求重新编译客户代码。如果后面的实现代码中重新赋予了实参值,那么将会覆盖之前的默认的实参值。
每个类最多只有一个默认构造函数。
8、.h和.cpp文件:
在建立面向对象的C++程序时,通常在一个文件中定义可复用源代码(如一个类),按照惯例这个文件扩展名为.h——称为头文件。在测试有关代码时,通常使用一个包含了main函数的独立的源代码文件,这就是“驱动程序”,扩展名为.cpp。
为了保证预处理器能够正确找到头文件,#include指令应将用户定义的头文件名放在双引号中,将C++标准库头文件名放置在尖括号中。
9、好的软件工程基本原则是:(1)将可复用代码与客户代码分离,有利于代码的重复使用;(2)将类的接口与类的实现分离,即在类定义的外部定义成员函数,这样,成员函数的实现细节对于客户代码而言是隐藏的。故,(1)使用函数原型定义类的接口,如GradeBook.h:它只描述类的公共接口而没有暴露类的成员函数的实现,函数原型告诉编译器函数的名字、返回类型和形参类型。函数原型末尾要加分号;(2)在一个独立文件中定义类的实现即成员函数,如GradeBook.cpp:按照惯例,这个文件与类的头文件基本名同名而扩展名为.cpp。定义成员函数时,在函数名前要加上类名和二元作用域分辨运算符“::”,且匹配它的函数原型。同时在文件中包含GradeBook.h头文件。(3)在驱动程序中测试该类。
接口与实现的分离不会影响客户代码对类的使用方式,只影响程序的编译和连接:
(1)类实现程序员创建两个文件,包含类接口的头文件GradeBook.h和包含类实现的源文件GradeBook.cpp,两者经过编译后形成类的目标代码,目标代码包含了描述成员函数的机器指令,故客户代码程序员不知道类的实现。
(2)客户代码程序员创建驱动程序源代码,即main函数,文件名可为execute.cpp,它与头文件GradeBook.h经过编译后形成main函数的目标代码。
(3)连接器连接上述两个目标代码和C++标准库目标代码,形成可执行程序。
这就形成了一种商业模式:独立软件提供商(ISV)以销售或发放许可证的方式提供各种类库,在产品中只提供头文件和目标代码,不会透露任何非公开的信息(除非提供源代码)。
10、一个重要的C++软件工程概念——在头文件中使用“预处理器封套”。这可以避免头文件中的代码被多次包含到同一个源代码文件中的情况。由于一个类只能被定义一次,因此使用这样的预处理器指令阻止了重复定义的错误。企图多次包含一个头文件的错误,通常发生在具有多个头文件(这些头文件本身可能包含其它的头文件)的大型程序中。注意:预处理器指令中符号常量命名的通常约定是简单地将头文件名用大写形式,其中的圆点用下划线代替。
如:#ifndef TIME_H  
       #define TIME_H
        ......
       #endif
11、组成和继承:包含类对象作为其它类的成员称为组成(composition),或称为聚合(aggregation)。
从已有的类派生出新的类称为继承(inheritance)。
12、在不同地方及同一地方,类成员的访问都有不同的形式:
(1)在类的作用域内(如类体内和成员函数的定义体中),类的成员可以被类的所有成员函数直接访问,也可以利用名字引用。
(2)在类的作用域外,public类成员(包括成员函数和数据成员)可以通过对象的句柄之一而访问。句柄可以是对象名称、对象的引用(引用初始化时就要赋值)或者对象的指针:
Time sunset;
Time &dinnerTime=sunset;
Time *timePtr=&sunset;
其中,对象名称或对象引用通过圆点成员选择运算符(.)来访问,对象指针通过箭头成员选择运算符(->)来访问。
13、面向对象编程的一个现象是:一旦类被定义,建立并且操作该类对象常常只涉及一个简单的成员函数调用序列,不需要或者只需要少量控制语句。对比之下,在类的成员函数实现中通常含有控制语句。
14、析构函数是另一种特殊的成员函数。当对象撤销时,类的析构函数会隐式地调用。析构函数不接收任何参数,也不返回任何值,且不可以指定返回类型。一个类只能有一个析构函数(不管是自动默认的还是人为定义的),且不允许重载,必须是public的。实际上,析构函数本身并不释放对象占用的内存空间,它只是在系统收回对象的内存空间之前执行扫尾工作,这样内存可以重新用于保存新的对象。
对象的存储类别可以改变调用构造函数和析构函数的顺序:
(1)全局对象:全局作用域内定义的对象,有static和非static之分,又可称为static对象和自动对象。全局对象的构造函数在文件内任何其它函数(包括main函数)开始执行之前调用。当main函数执行结束时,相应的析构函数被调用。如果是exit函数迫使程序立即结束,不执行自动对象(对所有类型的自动对象都适用)的析构函数;如果是abort函数迫使程序立即终止,则不允许调用任何对象(即所有类型的对象)的析构函数。
(2)局部对象:非全局作用域内定义的对象,如在main函数中定义的对象,也有static和非static之分(static和自动)。对于static局部对象,它的构造函数只调用一次,即在程序第一次执行到该对象的定义处时,而相应的析构函数的调用发生在main函数结束或者调用exit函数时;对于自动局部对象当程序的执行每次进入(即执行其定义时)或者离开对象的作用域时,构造函数和析构函数就会调用。
全局对象和static局部对象的撤销顺序与它们建立的顺序正好相反。自动局部对象则要看对象的作用域的大小范围,如果是同一个范围内的对象,也是与它们的建立顺序相反。
15、参数传递有三种方式:
(1)按值传递:这种方式很安全,因为被调用的函数无法访问主调函数中的原始对象,但如果传递大数据,则会使性能下降。
(2)按引用传递:这种方式性能好,传递的是指针或引用。但安全性较低,因为被调用的函数可以访问原始对象。
(3)按常量引用传递:这种方式既安全性能又好(这可应用const引用参数或指向const数据的指针参数来实现)。
16、C++提供了三种继承:public、protected、private。在public继承中,每个派生类的对象同时也是基类的对象。但是,基类的对象不是派生类的对象。
在各种形式的继承中,基类的private成员都不能被其派生类直接访问,但private基类成员仍得到了继承(即它们仍被视为派生类的一部分)。在public继承中,基类的所有其它成员在成为派生类的成员时仍保持了其原始的成员访问权限,故派生类也可以影响private基类成员的状态改变,但是只能通过基类中提供的且继承到派生类中的非private成员函数。
友元函数是不被继承的。
派生类的成员函数引用基类的public和protected成员时,只需直接使用成员名。当派生类的成员函数重新定义基类的成员函数时,可以通过在基类成员名前加上基类名和二元作用域分辨符(::)而在派生类中访问相应的基类成员,如果不加上这个分辨符,将会产生无限递归的错误。
使用继承时,类层次中所有类共同的数据成员和成员函数在基类中声明。
派生类继承了基类中除构造函数以外的所有成员(每个类都提供特定于自己的构造函数),当然析构函数和重载的赋值运算符也不能继承,但是派生类的构造函数、析构函数和重载的赋值运算符可以调用基类的构造函数、析构函数和重载的赋值运算符函数。然后就可以在派生类的定义中添加自己特有的成员函数和数据成员。
C++要求派生类构造函数调用其基类的构造函数来初始化继承到派生类的基类的数据成员。故在派生类的构造函数中,采用成员初始化器列表,将参数传递给基类的构造函数。派生类构造函数调用其基类构造函数时,如果传递给基类构造函数的参数,它的个数和类型与基类构造函数的相应定义不符,将导致编译错误。
如果派生类的构造函数没有显式地调用基类的构造函数,C++将尝试隐式地调用基类的默认构造函数,但如果在基类中已经人为地定义了显式的构造函数,那么编译器不再提供默认构造函数,这时就会产生编译错误。
当程序创建一个派生类对象时,派生类构造函数立即调用基类的构造函数,基类的构造函数执行,然后派生类的成员初始化器执行,最后派生类构造函数的函数体执行。如果这时的继承层次有多层,那么这一过程还将向上翻滚。调用析构函数时,则是与构造函数的执行次序是相反的。
17、多数情况下,使用private数据成员是更好的软件工程方法,此时可以使用public成员函数来访问private数据成员。这也使得以后的修改产生的影响局部化。


https://blog.sciencenet.cn/blog-441611-638235.html

上一篇:枝晶生长是一个非线性的复杂自组织过程
下一篇:火狐键盘常用快捷操作
收藏 IP: 210.72.136.*| 热度|

0

该博文允许注册用户评论 请点击登录 评论 (0 个评论)

数据加载中...

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2022-8-14 09:20

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部