1、类类型
每个类都定义了一个接口(interface)和一个实现(implementation).接口由使用该类的代码需要执行的操作组成。实现一般包括该类所需要的数据,还包括定义该类需要的但又不供一般性使用的函数。
定义类
1)先定义该类的接口,即该类所提供的操作;
2)通过这些操作,决定该类完成其功能所需要的数据,以及是否需要定义一些函数来支持该类的实现。
3)数据成员名称,从易读性和可维护性方面考虑,应记得始终在其数据成员的名称前加前缀
d_ :表示其为数据成员;
s_ :标识静态类数据
_p :标识一个指针数据成员
4)类定义时,在第一个访问标号前的任何成员都隐式指定为private,如果使用关键字struct,则这些成员都是public。
类成员布局:
1)对成员功能进行分类,基本方法:看它是否潜在地影响了对象的状态;
a)创建函数(CREATOR)使对象开始存在和停止存在;
b)操纵函数(MANIPULATOR)只是非常量成员函数;
c)访问函数(ACCESSOR)是常量成员函数,如所有的访问函数;
2)数据成员,作为类的实现细节,一般都放在类定义的末尾。
eg class CCar {
//...
public:
// CREATORS
CCar ( int cost = 0 );
CCar ( const CCar& car );
CCar();
//MANIPULATORS
CCar& operator =( const CCar& car );
void addFuel ( double numberOfGallons );
void drive ( double deltaGasPedal );
void turn ( double angleInDegrees );
//......
// ACCESSORS
double getFuel () const ;
double getRPMs () const ;
double getSpeed () const ;
//...
private:
double d_fuel;
double d_speed;
double d_rpms;
//....
};
3) 类中的数据成员与定义变量的区别
1.不能把类成员的初始化作为其定义的一部分,当定义数据成员时,只能指定该数据成员的名字和类型。
2.类不是在类定义里定义数据成员时初始化数据成员,而是通过称为构造函数的特殊成员函数控制初始化。
4)编译器隐式地将在类内定义的成员函数当作内联函数。
5)每个成员函数都有一个额外的,隐含的形参this。在调用成员函数是,形参this初始化为调用函数的对象的地址。
6)const成员函数的引入:const所起的作用是改变隐含的this形参的类型,使其指向对象的const 对象类型的指针。const成员函数不能修改调用该函数的对象。
7)构造函数:
形参表为空:因为正在定义的构造函数是默认调用的,无需提供任何初值;
函数体为空:除了初始化数据成员外,没有其他工作可以做。
8)类代码文件的组织
类的声明放置在头文件中,类的成员函数的定义放在与类同名的 .cpp文件中。内联函数可以放在头文件中定义,在类定义中就定义好了的成员函数默认为内联函数。
2、编写自己的头文件
A、.c 编译
1)编译过程
当一个.c文件编译时,C预处理器(cpp)首先(递归地)包含头文件,形成一个含有所有必要信息的单个源文件。然后这个中间文件(称为“编译单元”)被编译生成一个与主文件名相同的.o文件(目标文件)。
2)连接
连接把不同的编译单元中产生的符号联系起来,构成一个可执行程序。有两种截然不同的连接:内部连接和外部连接。(连接所用的类型会直接影响到我们如何将一个给定的逻辑结构合并进我们的物理设计中。)
a)内部连接:指如果一个名称对于它的编译单元来说是局部的,并且在连接时不可能与其他编译单元中的同样的名称相冲突,那么这个名称有内部连接。
内部连接意味着对这个定义的访问被局限于当前的编译单元中,也就是说,一个有内部连接的定义对于任何其他编译单元来说都是不可见的,所以在连接过程中不能用来解析未定义的符号。
关键字:static,决定了连接时内部的。
enum的定义也是一个内部连接,要想让有内部连接的定义影响程序的其他部分,它们必须放置在头文件中,而不是在.c 文件中。
内联函数的定义也是内部连接。
b)外部连接:在一个多文件程序中,如果一个名称在连接时可以和其他编译单元交互,那么这个名称就有外部连接。
由外部连接的定义可以在.o 文件中产生外部符号,这些外部符号可以被所有其他的编译单元访问,用来解析它们未定义的符号。这种外部符号必须在整个程序中唯一的,否则这个程序不能被连接。
非内联成员函数(包括静态成员)有外部连接,非内联函数、非静态自由函数(即nonmember函数)也一样。
B、 头文件(.h)
1)将一个带有外部连接的定义放置在一个.h文件中几乎都是编程错误。如果这样做了,还把这个头文件包含在不止一个编译单元中个,那么把它们连接在一起时就会出错,且会出现下面这样的信息:
MULTIPLY DEFINED SYMBOL
2)头文件的作用域内放置带有内部连接的定义是合法的,但这样不仅会污染全局名称空间,而且在有静态数据和函数的情况下,它们会在每一个包含有这个头得编译单元中消耗数据空间。
3)使用以下语句,来使得一个头文件或定义,不被多次包含进去。
#ifndef INCLUDES_H
#define INCLUDES_H
//头文件的内容
#endif
C、设计自己的头文件
头文件一般包含类的定义、extern变量的声明和函数的声明。
正确使用的好处
1)保证所有文件使用给定实体的同一声明;
2)当声明需要修改时,只有头文件需要更新;
注意事项:
1)头文件所做的声明咋逻辑上应该是适于放在一起。
2)编译头文件需要一定的时间,如果头文件太大,则需要忍受编译时间的代价。
3)头文件中不能含有定义,有三个除外:可以定义类,值在编译时就已知的const对象(若const 不是常量表达式初始化,就不能在头文件中定义),inline函数;
如:extern int ival =10;
double fica_rate;//都是定义。
4)设计头文件时,应使其可以多次包含在同一源文件中。我们必须保证多次包含同一头文件不会引起该头文件定义的类和对象被多次定义。
5)使得头文件安全的通用做法,是使用预处理器定义的头文件保护符。
#ifndef INCLUDES_H
#define INCLUDES_H
//头文件的内容
#endif
D、使用自定义的头文件
1)头文件名包括在尖括号(<>)里,那么认为该头文件是标准头文件,编译器将会在预定义的位置集查找头文件,这些预定义的位置可以通过设置查找路径环境变量或者通过命令行选项来修改。
2)头文件包含在一对引号(" ")里那么认为它是非系统头文件,非系统头文件的查找通常开始与源文件所在的路径。
3、重载函数
1)重载函数是指:在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
【注】任何程序仅有一个main函数的实例,因此main函数不能重载。
2)如果两个函数的声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。
如果两个函数的行程表完全相同,但返回类型不同,则第二个声明是错误的。(原因:函数不能仅仅基于不同的返回类型而实现重载。)
3)如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个重载函数都应在同一个作用域中声明。
eg: void print( const string & );
void print( double );
void fooBar( int ival )
{
void print( int ); //这个函数声明在这里覆盖了在foobar函数外声明的print重载函数;
print( "Value: "); // error:字面值常量无法转换为int型,print覆盖了foobar之外声明的print函数,而不是实现重载。
print( ival );
print( 3.14 ); //ok.因为3.14 可以转换为int型。
}
///
//正确的重载方式应该是:
void print( const string & );
void print( double );
void print( int );
void fooBar( int ival )
{
print( "Value: "); // ok。
print( ival );
print( 3.14 ); //ok.因为3.14 可以转换为int型。
}
4)函数匹配结果的三种可能性
a)编译器找到实参最佳匹配的函数,并生成调用该函数的代码;
b)找不到形参与函数调用的实参匹配的函数,在这种情况下,编译器将会给出编译错误信息。
c)存在多个与实参匹配的函数,但是没有一个是明显的最佳选择,这种情况也是错误的,该调用具有二义性。
5)重载确定的三个步骤:
a)候选函数
确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数;
b)选择可行函数
从候选函数中,选择一个或多个函数,他们能够用该调用中指定的实参来调用。
可行函数的条件:
1. 函数的形参个数与该调用的实参个数相同;(这里面要注意默认实参的情况。)
2. 每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型。
c)寻找最佳匹配
确定与函数调用中使用的实际参数匹配最佳的可行函数。选择对应形参与之最匹配的一个或多个可行函数。
d)含有多个形参的重载确定
如果函数调用使用了两个或两个以上的显式实参,则函数的匹配更加复杂。
(在发生调用有二义性时,可以通过显式的强制类型转换强制函数匹配。)
(枚举类型:枚举类型的对象只能用同一枚举类型的另一个对象或一个枚举成员进行初始化,整数对象即使具有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数。)
6)重载和const形参
a)可基于函数的引用形参是指向const对象还是指向非const对象,实现函数重载。将引用形参定义为const来重载函数是合法的。
b)如果实参是const对象,则调用带有const*类型形参的函数;否则,如果实参不是const对象,将调用带有普通指针形参的函数。
【注】a)不能基于指针本身是否为const来实现函数的重载。
b)当形参以副本传递是,不能基于形参是否为const来实现重载。
7)指向重载函数的指针:
指针的类型必须与重载函数的一个版本精确匹配,如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误
eg: extern void ff( vector< double > );
extern void ff ( unsigned int );
a) void ( *pf1 )( unsigned int ) = &ff ; //ok
b) void ( *pf2 )( int ) = &ff; //error
c) double (*pf3 )( vector < double > );
pf3 = &ff; //error
4、指向函数的指针
1)函数指针是指指向函数而非指向对象的指针,函数类型由其返回类型以及形参表确定,而与函数名无关。
2)用typedef简化函数指针的定义
typedef bool ( *cmpFcn ) ( const string &, const string & );
cmpFcn 是一种指向函数的指针类型的名字。
3)指向函数的指针的初始化和赋值
a)函数名可自动解释为指向函数的指针。
b)函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。
c)指向不同函数类型的指针之间不存在转换,将函数指针初始化为0,表示该指针不指向任何函数。
4)通过指针调用函数
指向函数的指针可用于调用它所指向的函数,可以不需要使用解引用操作符,直接通过指针调用函数。
5)函数指针形参
函数的形参可以是指向函数的指针。
void useBigger( const string &, const string &, bool (*)(const string &, const string & ) )
6)返回指向函数的指针
int ( *ff( int ) )( int *, int );
= ff(int);int (*)(int *,int )
表示:ff为有一个形参的函数,返回一个函数指针,该函数指针指向的是含有一个int*,和一个int类型的形参,返回int型的函数。