1、类的组合:一个类内嵌其他类的对象作为成员的情况,他们之间的关系是一种包含与被包含的关系。当创建一个类的对象时,如果这个类具有内嵌对象成员,那么这个内嵌对象将首先被自动创建,因此在创建对象时既要对本类的基本类型数据成员进行初始化,又要对内嵌对象成员进行初始化。
组合类构造函数定义的一般形式为:类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),......
2、创建一个组合类的对象时,不仅他自身的构造函数将被调用,而且还将调用其内嵌对象的构造函数,此时构造函数调用顺序是:
a、调用内嵌对象的构造函数,调用顺序按照内嵌对象在组合类的定义中出现的次序。
b、执行本类构造函数的函数体
若声明组合类的对象时没有指定对象的初始值,则默认形式(无形参)的构造函数被调用,这是内嵌对象的默认形式构造函数也被调用。析构函数的调用执行顺序与构造函数刚好相反。
3、对一个类,若没有编写拷贝构造函数,编译系统会在必要时自动生成一个默认的拷贝构造函数,若建立组合类的对象时调用默认拷贝构造函数,则编译器将自动调用内嵌的拷贝构造函数。如果要为组合类编写拷贝构造函数,则需要为内嵌成员对象的拷贝构造函数传递参数,例如C类中包含B类对象b作为成员,C类的拷贝构造函数形式如下:
C::C(C &c1):b(c1.b){.......}接下来让我们看一个实例:
class Point
{
public :
Point(int xx = 0, int yy = 0){X = xx; Y = yy;}
Point(Point &p);
int GetX(){return X;}
int GetY(){return Y;}
private:
int X,Y;
};
Point ::Point(Point &p)
{
X = p.X;
Y = p.Y;
cout<<"Point 拷贝构造函数被调用"<<endl;
}
class Line
{
public:
Line (Point xp1, Point xp2);
Line (Line &);
double GetLen(){return len;}
private:
Point p1,p2;
double len;
};
Line::Line(Point xp1,Point xp2):p1(xp1),p2(xp2)
{
cout<<"Line 构造函数被调用"<<endl;
double x = double(p1.GetX() - p2.GetX());
double y = double(p1.GetY() - p2.GetY());
len = sqrt(x * x + y * y );
}
Line::Line(Line &L):p1(L.p1) ,p2(L.p2)
{
cout<<"Line 拷贝构造函数被调用"<<endl;
len = L.len;
}
int main()
{
Point myq1(1,1),myq2(4,5);
Line line(myq1,myq2);
Line line2(line);
return 0;
}
其结果输出为:
Point 拷贝构造函数被调用
Point 拷贝构造函数被调用
Point 拷贝构造函数被调用
Point 拷贝构造函数被调用
Line 构造函数被调用
Point 拷贝构造函数被调用
Point 拷贝构造函数被调用
Line 构造函数被调用
4、前向引用声明
对于两个类相互引用的情况,也称为循环依赖,需要前向引用,如:
class A class B
{ {
public: public:
void f(B b); void g(A a);
} }
那么在类A中的成员函数f中的形式参数是类B的对象,此时编译会引起错误,可在class A前加上类B的前向引用:class B;
但是在提供一个完整类定义之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。即当使用前向引用声明时,只能使用被声明的符号,而不能使用涉及到类的任何细节。
5、类模板与函数模版
对于功能相同但是成员的类型不同的类
a、使用template<class T>前向声明,之后在类定义中,欲采用通用数据类型的数据成员、成员函数的参数或返回值,前面需加上T
b、在类定义外定义成员函数时,若此成员函数中有模版参数存在,则需要在函数体外进行模版声明,并在函数名前的类名后缀上加上“<T>”,如
template<class T>
T 类名<T>::function(){return number;}
c、类模板不代表一个具体的、实际的类,而代表一类类。实际上,类模板的使用就是将类模板实例化成一个具体的类,格式为:
类名<实际的类型>对象名;
对于函数模版也类似,即采用template<typename T>,之后直接用 T function (T x,T y)
6、UML类图
UML规定数据成员表示的语法为:【访问控制属性】名称【重数】【:类型】【=默认值】【|约束条件|】
至少必须指定数据成员的名称,其他的都是可选的,其中:
访问控制属性:分为public 、private 、protected 三种,分别对应于UML中的“+”、“-”、“#”
UML规定函数成员表示为:【访问控制属性】名称【(参数表)】【:返回类型】【约束特性】
7、类的静态数据成员
a、静态成员解决了同一个类的不同对象之间数据的和函数共享问题,面向对象方法中还有“类属性”的概念,如果某个属性为整个类所共有,不属于任何一个具体对象,则采用static关键字来声明为静态成员,静态成员在每一个类只有一份拷贝,由该类所有对象共同维护和使用,从而实现同一类不同对象之间的数据共享。
b、静态成员具有静态生存期,只能通过类名对他进行访问,即类名::标识符,在类的定义中仅仅对静态数据成员进行声明,必须在文件作用域的某个地方使用类名限定对静态数据成员进行定义性说明(此时系统分配空间),这时也可以进行初始化。在UML语音中静态数据成员通过在数据成员下方添加下划线表示。
c、静态数据成员通常应该通过非内联函数来访问,因为编译器确保在调用任何一个非内联函数之前初始化静态数据成员。但当从另一个编译单元通过内联函数调用静态成员时,静态成员有可能尚未被初始化,这样的调用是不安全的。
d、公有静态成员可以在类外通过类名进行访问。
e、如果要通过非静态函数来访问静态数据成员,应该使用非内联函数,而且访问静态数据成员的函数,其函数体的定义应该与静态成员的初始化写在同一个源程序文件中。
8、类的静态函数成员
a、对普通函数成员的调用必须通过对象名,但是可以对于静态函数成员可通过类名来访问,对于公有的静态成员函数,可以通过类名或者对象名来调用。
b、静态成员函数可以直接访问该类的静态数据和静态函数成员,而访问非静态数据成员必须通过参数传递方式得到对象名,然后通过对象名来访问。
c、在UML中静态函数成员是通过在函数成员前添加《static》构造来表征。
9、友元
a、友元是c++提供的一种破坏数据结构封装和数据隐藏的机制
b、通过将一个模块声明为另一个模块的友元,一个模块能够引用到另一个模块中本事被隐藏的信息(如private、protected成员),但访问对象的成员必须通过对象名
c、如果友元是一般成员或类的成员函数,称为友元函数,若友元的是一个类,则称友元类
d、一个类的成员函数可以是另一个类的友元,即一个类的成员函数可以说明为另一个类的友元函数,以便通过该成员函数访问到另一个类的成员
e、若一个类为另一个类的友元,则此类的所有成员都能访问对方的私有成员。将友元类名在另一个类中使用friend修饰说明。
g、友元没有传递性,且是单向的。
10、常类型
常类型的对象必须进行初始化,而且不能被更新。
a、常引用:被引用的对象不能被更新:const 类型说明符 &引用名
b、常对象:必须进行初始化,不能被更新:类名 const 对象名
c、常数组:数组元素不能被更新:类型说明符 const 数组名[大小]...
d:常指针:指向常量的指针
11、常成员函数与常数据成员
a、使用const关键字说明的函数,常成员函数不更新对象的数据成员,常成员函数说明格式:类型说明符 函数名 (参数表)const;
const关键字可以被用于参与对重载函数的区分,通过常对象只能调用它的常成员函数,其他成员函数不能调用。
b、常使用const说明的数据成员,对于常数据成员的初始化只能用类似A::A(int i):a(i),r(i)的方法进行初始化,而不能直接赋值。