现在的位置: 首页 > 综合 > 正文

C++结构体面向对象特性

2018年08月14日 ⁄ 综合 ⁄ 共 49654字 ⁄ 字号 评论关闭

8.2.1  结构体中添加函数

前面讲述结构体定义时只定义了其数据成员,这在C语言中是合适的。但是C++语言在其基础上针对结构体这种自定义类型作了改进,允许在结构体体内不仅可以定义数据成员。还可以定义成员函数供使用。C++中的结构体与类的定义非常的相似,通过如下实例简单了解C++中结构体扩展功能。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter0803.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter0803

* 源文件chapter0803.cpp

* 结构体增加函数方法实例

*/

#include <iostream>

using namespace std;

 

struct bookInfo                                                         //定义书本信息结构体bookInfo

{

          int m_id;                                                         //结构体数据成员整型变量m_id

          char m_name[100];                                              //结构体数据成员字符型数组m_name

          void setValue(int id,char name[]);             //结构体方法成员函数声明,用于设置结构体数据成员变量值

};

/*

* 函数功能:结构体数据成员初始化

* 函数参数:传入整型变量与数组指针

* 返回值:空

*/

void bookInfo::setValue(int id,char name[])

{

          m_id = id;                                                                //传入整型值给结构体成员赋值

          strcpy(m_name,name);                                       //传入常量字符串给结构体成员赋值

}

/*主程序入口*/

int main()

{

          bookInfo book;                                                                //定义结构体变量或对象

          book.setValue(1,"jack");                                                //调用该对象成员函数,为其对象数据成员初始化赋值

          cout<<book.m_id<<""<<book.m_name<<endl;      //打印结构体对象中数据成员的值

          return 0;

}

本实例程序主要通过向结构体定义中加入方法成员的方式,演示结构体具备面向对象类的功能。程序主要由主函数与结构体中的方法定义组成,具体程序剖析见注释与后面的程序讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter0803.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0803.o

CC=g++

 

chapter0803: $(OBJECTS)

    $(CC)$(OBJECTS) -g -o chapter0803

clean:

    rm -fchapter0803 core $(OBJECTS)

submit:

    cp -f -rchapter0803 ../bin

    cp -f -r*.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件。随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至bin目录,执行该程序文件运行结果如下所示。

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0803/src]$make

g++    -c -ochapter0803.o chapter0803.cpp

g++ chapter0803.o -g -o chapter0803

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0803/src]$makesubmit

cp -f -r chapter0803 ../bin

cp -f -r *.h ../include

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0803/src]$cd../bin

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0803/bin]$./chapter0803

1 jack

如上实例中,定义书本信息结构体同时添加了一个初始化结构体数据成员值的成员函数setValue,该成员函数的定义在结构体外进行。结构体成员函数在结构体外定义实现是通过域运算符::表示的,使用域运算符在此处表明setValue为结构体bookInfo的成员函数。

函数体内主要实现给结构体对象数据成员赋值的功能。主程序中定义该结构体对象,随后该结构体对象通过“.”运算符调用其成员函数实现其初始化功能。调用成员函数初始化后,通过该结构体对象访问其数据成员,检验是否被正确初始化。从本小节开始结构体这类自定义类型,其变量的定义都开始称为对象的概念。

对象是面向对象中的基本概念,自定义类型对象表明其除了包含数据成员外还包含方法成员即函数定义。但是在对象定义分配内存时,结构体中只有其数据成员需要分配内存空间,结构体的大小同样是其数据成员在内存对齐后的大小。结构体中可以定义多个数据成员与方法成员,定义多个对象实例操作互相不影响各自的成员值,下面将会讲述结构体中访问控制概念,进一步接近类的概念。

8.2.2  结构体成员访问控制

上述讲述结构体中添加方法成员是在默认的访问控制权限下进行的。结构体默认情况下数据成员与方法成员都为公开的,即在结构体外部可以通过其对象随意访问修改。结构体中权限访问控制通过3个关键字来作出限制,分别是publicprotectedprivate。下面分别讲述三种权限下的访问控制情况。

q 
public
权限控制最宽松,表明结构体中数据成员以及方法成员除了本结构体成员访问外还可以被本结构体外的函数访问。上小节实例中结构体中数据成员与方法成员默认都是public限制,所以在主函数中可以直接通过其对象调用其方法成员和数据成员。

q 
protected
权限控制在public基础上加了访问限制,被该限制符限制的成员除了能在本结构体中函数访问以外还可以是其派生结构体中函数访问。

q 
private
权限控制限制级别最高,被限定的结构体成员只能被本结构体中成员访问,外部或者其继承者都不能访问该结构体成员。下面通过一个完整实例比较直观的理解三种权限控制下的访问情况。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter0804.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter0804

* 源文件chapter0804.cpp

* 结构体访问控制实例

*/

#include <iostream>

using namespace std;

 

struct bookInfo                                                                            //定义结构体bookInfo

{

          private:                                                                              //限定以下两个数据成员为private访问权限

              int m_id;

              char m_name[100];

          protected:                                                                          //限定以下作者数据成员为protected访问权限

                   charm_author[100];

          public:                                                                               //限定以下两个方法成员为public访问权限

              void setValue(int id,char name[]);

              void show();

};

struct techBookInfo:public bookInfo                                       //定义结构体techBookInfo,继承至bookInfo

{

    public:                                                                                   //限定其两个方法成员为public访问权限

      voidsetAuthorValue(char author[]);

      void showAuthor();

};

void bookInfo::setValue(int id,char name[])                          //定义结构体bookInfo的设置两个数据成员值得函数

{

          m_id = id;

          strcpy(m_name,name);

}

void bookInfo::show()                                                                //定义结构体bookInfo的打印两个私有数据成员值

{

           cout<<m_id<<""<<m_name<<endl;

}

void techBookInfo::setAuthorValue(char author[])               //定义结构体techBookInfo的设置其作者信息函数

{

           strcpy(m_author,author);

}

void techBookInfo::showAuthor()                                            //定义结构体techBookInfo的打印作者信息函数

{

           cout<<m_author<<endl;

}

/*主程序入口*/

int main()

{

          bookInfo book;                                              //定义结构体bookInfo对象book

          book.setValue(1,"Linux");                           //使用book对象调用公开public接口设置其数据成员值

          book.show();                                                 //使用book对象调用公开public接口打印其数据成员值

          techBookInfo book1;                                   //定义结构体techBookInfo对象book1

          book1.setValue(2,"C++");                          //使用book1对象调用继承而来公开public接口设置其数据成员值

          book1.show();                                                        //使用book1对象调用继承而来公开public接口打印数据成员值

          book1.setAuthorValue("john");                           //使用book1对象调用自身结构体公开接口设置作者信息

          book1.showAuthor();                                   //使用book1对象调用自身结构体公开接口打印作者信息

          return 0;

}

本实例主要通过结构体中增加方法,演示结构体中访问权限控制的功能。程序主要由主函数与结构体中的方法函数组成,通过在结构体中增加权限访问控制的支持。向初学者展示结构体中的数据权限访问控制的情况,具体程序剖析见注释与后面详细的讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter0804.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0804.o

CC=g++

 

chapter0804: $(OBJECTS)

    $(CC)$(OBJECTS) -g -o chapter0804

clean:

    rm -fchapter0804 core $(OBJECTS)

submit:

    cp -f -rchapter0804 ../bin

    cp -f -r *.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至bin目录,执行该程序文件运行结果如下所示。

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0804/src]$make

g++    -c -ochapter0804.o chapter0804.cpp

g++ chapter0804.o -g -o chapter0804

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0804/src]$makesubmit

cp -f -r chapter0804 ../bin

cp -f -r *.h ../include

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0804/src]$cd../bin

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0804/bin]$./chapter0804

1 Linux

2 C++

john

以上程序中定义两个结构体,其中techBookInfo结构体继承自结构体bookInfo,拥有该结构体一切成员。继承的概念会在后续类中详细讲述,这里主要关注结构体的权限访问控制。结构体bookInfo中使用了三种权限修饰符,两个数据成员书编号m_id及书名m_name限定为私有成员。只能在本结构体函数体内使用。

另外一个数据成员作者信息m_author限定为保护成员。除了在本结构体函数内访问外还可以在其继承的结构体的函数中访问。两个方法成员定义为公开访问方式,在外部非结构体函数中可以访问。

结构体techBookInfo继承bookInfo而来,拥有其一切成员并且在其内部又定义了两个公开接口函数供外部函数访问。主程序中定义两个结构体变量bookbook1分别访问其公开接口函数,而公开接口函数中分别操作了结构体本身的私有成员。此时私有和保护的数据成员在主函数中都不可以访问,初学者可以修改程序以证实权限控制基本概念。而保护成员除了在本身结构体公开的接口函数中访问外,还可以在其继承的结构体techBookInfo的函数中访问。

8.3  C++的类类型

上节讲述结构体面向对象部分特征,由此过渡到C++面向对象中最重要的类类型概念。C++中类类型在扩展了的结构体类型之上提供了更加丰富的功能,用于由过程化程序设计过度到面向对象程序设计思想层面。下面将会详细讲述类的基本概念及使用方式。

8.3.1  类的定义

与结构体定义类似,首先C++中类的声明使用关键字class标识,类类型定义基本语法如下所示。

class class_name                                 //类名的声明

{

         private:

                   dataTypedata;                    //类数据成员定义

                   …

         public:

                   dataTypefunction();          //类方法成员定义

                   …

};

类的定义与结构体方式基本相同,关键字class后跟类的名称。随后一对大括号为类体的定义,其中包括访问权限限制的数据成员与方法成员的定义,最后以分号作为结尾。每个类的定义引入一个不同的类类型,即使是拥有相同成员的类类型其定义的对象互相之间不可以相互赋值。但同一类类型的对象可以互相赋值。下面通过几个实例了解类及其类类型对象常见定义方式,如下所示。

class bookInfo                                       //定义类类型bookInfo

{

         public:                                            //定义公有方法成员

                   bookInfo();                           //定义bookInfo类构造函数

                   ~bookInfo();                         //定义bookInfo类析构函数

                   voidsetValue();                            //设置bookInfo类数据成员值的方法

                   voidshow();                         //打印bookInfo类数据成员值得方法

         private:                                           //定义私有数据成员

                   intm_id;                               //书本信息类数据成员m_id,表示书编号

                   charm_name[50];             //书本信息类数据成员m_name,表示书名

                   charm_author[50];            //书本信息类数据成员m_author,表示作者名

};

bookInfo book;          //定义bookInfo类类型对象book

class bookInfo

{

         …                        //类成员定义

}book;

类类型定义默认情况下的控制权限为private私有,即所有的数据成员与方法成员只能在类体函数中访问操作。类类型对象同样可以在类定义同时或者类定义之后定义,类定义时由于规约了其类型域。所以不同的类类型定义如果在同一个文件中出现成员名都相同的情况不会影响到彼此。

前面已经提及过由于属于不同的两个类类型定义。因此其定义的对象分别包含了两份完全不同的成员列表,互相之间不能直接赋值并且所有针对成员的操作也互不影响。如下实例定义是合法的操作,初学者可以自行测试学习。

class bookInfo                              //书本信息类声明

{

         public:                                   //公共方法成员

                   voidshow();               //打印显示信息

         private:                                 //私有保护数据成员

                   intid;                            //信息编号整型变量

};

class techBookInfo                      //教师书本信息类声明

{

         public:                                   //公共方法成员

                   voidshow();               //打印显示信息方法成员

         private:                                 //私有保护数据成员

                   intid;                            //信息编号整型变量

};

bookInfo book1;                           //书本信息类对象定义

techBookInfo book2;                            //教师书本信息类对象定义

由于类类型定义同时规约了类域的范围,因此在各自类类型定义体中虽然相互间的成员可能相同,但由于不在同一个领域之内,所以可以作到相互之间互不影响。

8.3.2  类的成员定义

C++中类的成员主要包括两类,数据成员与方法成员。数据成员可以理解为该类类型提供的可操作的数据,在类域中增加了权限访问控制。通常如果不是必须的话类中数据成员都是采用private作访问限制来实现类类型中数据隐藏机制。

而方法成员则约定为类类型对外统一操作接口。一般定义为public权限访问控制,在类体外供其它函数调用。类的方法成员主要用来提供在该类类型之上提供不同的功能操作。类中的成员函数说白了是将类中不同类型的数据成员与这些数据成员相关的操作封装在一起,作为类的接口供外界统一操作使用。

C++中类的数据成员可以是任意类型的基本数据类型,如结构体、联合体、类等一些复合类型变量,也可以是指针型变量。该数据成员同样享有三种访问权限的控制。正常情况下public表明该成员数据在类域的外部函数也可以通过类对象直接访问。protected表明该成员数据除了在本类域的方法成员访问外,还可以通过该类类型的继承者中的方法成员访问。而private则表明该数据成员在类域中作了隐藏,只能在本类中的方法成员中访问操作。下面是几种类型的数据成员的定义实例。

class bookInfo

{

         public:                                            //公共方法成员

                   …                                           //具体方法成员定义

         private:                                           //私有数据成员

                   intm_id = 1;                        //不合法定义初始化

                   charm_name[50];

                   techBookInfom_book1;

                   vector<string>m_input_path;

                  

};

类中数据成员的定义与扩展后的结构体类似,可以包含各种各样的类型变量成员。需要注意的是类类型的数据成员定义时需要和普通的外部变量区分出来。这并不是约定非要这么作,而是从程序开发规范角度来讲,区别类中数据成员与普通类外部变量定义有助于更加清晰的理解程序流程,方便维护与修改。本书中约定类体中的数据成员都以m_开头标识,随后尽量采用完整的单词来清晰的表示数据成员。

另外在类体中定义数据成员不能够在定义时显式的初始化值。如上实例中m_id成员定义时初始化语句是不合法的操作。类数据成员初始化必须放到其构造函数中去实现,构造函数初始化类类型数据成员会在后续章节详细讲述。到目前为止此处所讲述的数据成员都可认为非静态类型数据,静态数据成员定义与初始化概念与此不同,同样会在后续章节详细讲述。

8.3.3  类成员的访问控制

类类型成员的访问控制是非常重要而且复杂的概念,尤其在引入继承多态等概念后,类成员以及继承类成员等访问控制情况。下面详细讲述复杂情形下类成员访问控制规则,让初学者即使在类类型复杂应用的情形下,抓住本质规律清晰了解所操作的类中哪些成员可以在外部访问操作。了解并掌握哪些成员因作了保护外部访问操作属于非法的。

单个类中成员访问控制规则很简单。按照前面讲述的三种访问权限控制方式来作限定,分别是publicprotected以及private。单个类成员访问控制规则可以总结如下。

1.数据成员访问控制

数据成员访问控制,权限访问控制作用于类的数据成员上。public限定表明该类的数据成员不仅仅可以在类体内部的方法中操作,在外部可以通过类对象直接操作。并且派生继承下去的类以及友元类中也拥有此类访问权限。

protected限定表明数据成员只能在本类体中方法成员访问,并且派生继承下去的类中方法成员以及友元类也可以访问,但不能被类外定义的类对象直接访问。private限定表明该类数据成员只能被本类及友元类中方法成员访问,但不能被外部定义的函数以及该类对象直接访问。

2.方法成员访问控制

方法成员访问控制,作用于方法成员的权限控制规则与上述数据成员访问控制相似,只是在具体实现中表达的意义不同。数据成员的控制在C++中反映了数据封装隐藏的概念,而方法成员的访问控制则区分了对外的公共接口与内部支持接口。所谓的内部支持接口可以看成为了更好的完成对外公开接口的功能而提供的内部操作方法,在类的外部不能操作该方法。

类的访问控制还会涉及类继承权限控制规则。在扩展结构体部分讲述的仅仅是public公开继承情况下的访问控制情况,更加详细的集成控制访问规则会在类继承章节讲述。

8.3.4  类的初始化

由于结构体定义成员默认情况下都为public访问控制。所以可以在外部通过定义其对象或者称为变量同时进行初始化。类中当数据成员都定义为public访问权限时,同样可以使用结构体变量初始化方式进行类对象数据成员初始化。

但是类中提供了另外一种初始化的方式,那就是类的特殊方法成员构造函数。当数据需要对外隐藏时,外部显式初始化就不可以使用了。此时类提供的对外公开的构造函数便起到初始化其数据成员的作用。如下一个类初始化实例,了解类初始化基本操作。

class bookInfo                                                          //bookInfo定义

{

         public:                                                               //公有方法成员

                   bookInfo(intid,char name[]);              //bookInfo构造函数,负责初始化其数据成员

                   ~bookInfo();                                          //bookInfo析构函数,负责释放类对象占用内存资源

                   voidshow();                                           //bookInfo成员函数,负责打印类数据成员值

         private:                                                             //私有数据成员

                   intm_id;                                                  //类的数据成员,表示书本信息编号

                   charm_name[100];                              //类的数据成员,表示书本信息书名

};

bookInfo:: bookInfo(int id,char name[])

{

         m_id= id;

         strcpy(m_name,name);

}

void bookInfo::show()

{

         cout<<m_id<<”“<<m_name<<endl;

}

bookInfo book(1,”jack”);                                         //定义类bookInfo对象同时根据传入实参初始化数据成员

book.show();                                                             //调用该类的显示信息方法,打印该类对象数据成员

类的初始化主要指其数据成员初始化值。对于类的数据成员需要对外隐藏时,类提供了构造函数来进行类数据成员初始化操作。构造函数名与类名相同,并且允许该方法有参数列表,但不允许其拥有返回值。类的构造函数可以在体类定义也可以在类体外部定义,该实例构造函数在类体的外部定义。与其它普通成员函数定义方式相同,根据传入的参数为该类数据成员赋值。

构造函数的调用是伴随着类对象定义同时进行的。类对象定义时编译器为类的数据成员分配内存资源。随后默认调用自定义构造函数,根据传入的实参值初始化数据成员。如上实例中初始化该对象的数据成员分别为1与“jack”值。如果类没有显式的定义构造函数初始化其数据成员,对象定义时会调用编译器编译期提供的默认构造函数来初始化。此刻该对象的数据成员分别为未知值。实例中最后调用公开接口方法打印该类对象中数据成员值。

8.3.5  类对象实例

类是用户自定义类型,真正用户操作时需要通过定义该类型的对象。类对象的定义又可以称为类实例的定义。与结构体类型相同类类型在定义时并不占用内存空间,编译器分配内存需要在对象定义时进行。对象的内存布局与结构体变量类似,遵循内存对齐概念,即编译器可能会为对象的数据变量分配内存时认为添加一定大小。至于对其参数视不同平台下不同编译器而定,32Linux平台下遵循4字节对齐方式。

类可以同时定义多个对象实例,各个对象实例之间可以通过逗号隔开,并且可以在定义类对象的同时隐式调用其构造函数初始化。同一类类型不同对象独享类定义中的成员一份拷贝,所以同类型的对象针对其成员操作互相之间不会有影响。另外同类型的对象互相之间与C++基本数据类型一样可以赋值,对象的赋值意味着其所有的成员拷贝。下面通过一个完整的实例,展示类对象实例定义及相关操作。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter0806.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter0806

* 源文件chapter0806.cpp

* 类对象实例

*/

#include <iostream>

using namespace std;

 

class bookInfo                                                                   //定义类类型bookInfo1

{

         public:                                                                        //定义类对外公开接口

                   bookInfo(intid,char name[]);                        //定义类构造函数,根据传入参数初始化数据成员

                   voidshow();                                                     //定义类方法成员,打印类数据成员信息

         private:                                                                      //定义类私有隐藏数据成员

                   intm_id;                                                           //类数据成员定义,表示书本编号

                   charm_name[100];                                       //类数据成员定义,表示书名

};

/*类构造函数定义*/

bookInfo::bookInfo(int id,char name[])

{

          m_id = id;                                                                //根据传入编号值初始化数据成员

          strcpy(m_name,name);                                       //根据传入的字符串初始化拷贝至对应数据成员中

}

/*类方法成员,打印数据成员信息函数定义*/

void bookInfo::show()

{

          cout<<m_id<<""<<m_name<<endl;

}

/*主程序入口*/

int main()

{

         bookInfobook1(1,"jack"),book2(2,"john"), book3(3,"lining");      //定义类bookInfo对象并初始化

         bookInfo*book4 = &book2;                                   //定义类bookInfo对象指针,同时使用book2地址初始化

         book1.show();                                                          //对象book1调用其显示数据成员信息方法

         book2.show();                                                          //对象book2调用其显示数据成员信息方法

         book3.show();                                                          //对象book3调用其显示数据成员信息方法

         book3= book1;                                                                 //同类型类对象互相赋值

         book3.show();

         book4->show();                                                                 //对象指针book4调用其指向对象的显示数据成员信息方法

         return0;

}

本实例程序主要通过类类型定义演示类的对象实例操作功能。程序主要由主函数与书本信息类组成,而书本类中包含了构造函数与信息打印方法。具体程序剖析见注释与后面的详细讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter0806.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0806.o

CC=g++

 

chapter0806: $(OBJECTS)

    $(CC)$(OBJECTS) -g -o chapter0806

clean:

    rm -fchapter0806 core $(OBJECTS)

submit:

    cp -f -rchapter0806 ../bin

    cp -f -r*.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0806/src]$make

g++    -c -ochapter0806.o chapter0806.cpp

g++ chapter0806.o -g -o chapter0806

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0806/src]$makesubmit

cp -f -r chapter0806 ../bin

cp -f -r *.h ../include

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0806/src]$cd../bin

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0806/bin]$./chapter0806

1 jack

2 john

3 lining

1 jack

2 john

以上实例中定义实现了类bookinfo,该类包含两个数据成员与两个方法成员,注释中已清晰说明。主程序中首先使用该类定义三个对象book1book2book3,同时传入实参通过构造函数初始化各自对象的数据成员。随后定义该类对象指针book4,同时使用对象book2地址初始化之,即对象指针book4此时指向对象book2首地址。

三个对象book1book2book3分别通过逗号调用其方法成员打印各自对象的数据成员信息。之后将该类对象boo1直接赋值给对象book3,即book3对象拥有了book1的全部成员拷贝。此刻再通过book3调用打印其当前拥有的book1的拷贝的数据成员。最后类对象指针通过“->”符号调用指向的book2对象的方法成员打印其数据成员信息。

8.3.6  类中内联函数

关于内联函数在前面讲述函数时已经提及。C++中依然支持在类体内部定义内联函数作为其方法成员。内联函数的使用得当会为那些被频繁调用的精巧的小函数节省一定的开销,因为内联函数省去了调用时的开销,直接在调用点处展开代码。

内联函数属于编译器行为,即类的方法成员定义为内联函数之后,由编译器来针对其作出相应的优化操作。这种效率提高是以增加代码体的大小为代价的,所以如果一个功能函数或方法其代码体非常的短小,甚至能够小于函数调用所生成的代码。那么内两函数的运用此时真正可以作到减小代码体积的同时又提高了程序的效率。

类中内联函数使用需要经过慎重的思考来决定。对于代码体过于庞大的方法成员来讲,最好不要定义为内联的方式,其结果会适得其反。下面是一些内联函数在类中定义的实例。

//类中内联函数定义两种方式

class bookInfo                                       //书本信息类声明

{

         public:                                            //公共方法成员

                   …

                   intgetBookId();                   //获取书本编号的公共方法成员

         private:                                           //私有数据成员

                   intm_id;                               //书本编号变量

};

inline int bookInfo::getBookId()          //采用外部定义内联函数方式

{

         returnm_id;

};

class bookInfo                                       //书本信息类声明

{

         public:                                            //公共方法成员

                   intgetBookId(){return m_d}       //获取书本编号的公共方法成员,采用类内部定义实现内联

         private:

                   intm_id;

};

以上方式定义了两种类中内联函数的实现方式。一个书本信息类中某种情况下需要一个获取本身数据成员的操作,该操作只有一行代码实现那就是直接返回需要的数据成员。另外如果在整个处理过程中估算出调用的频繁度,这种情形下可以将其定义为类的内联函数。

从上述实例中可以看出,内联函数可以以两种方式实现定义。一种被称为显式定义,即应用程序中类的内联函数定义在外部时,需要显式的添加inline关键字标识。另一种被称为隐式定义,即程序中类体内部定义的成员函数直接被编译器作出优化隐式认定为内联函数。

C++的学习难度来自于编译器通常会在开发者们的代码中无形添加代码。而函数调用、内联隐式转换等相当一部分的C++提供的功能都与编译器背后编译时添加无形的不可见的代码有关。初学者初步学习C++时可以先抛开这些困扰,只要作到能够区分哪些功能的提供离不开编译器层实现的即可。至于编译器到底针对代码不同的功能添加了如何处理的代码,可以在以后深入学习中去探讨。

8.4  C++类的构造函数

C++中构造函数前面已经简单介绍过作为对象初始化功能出现的情形,同时构造函数还可以用来实现类内部一些管理操作。C++中约定每个类的定义必须要有至少一个构造函数,此时才可以定义该类的对象。如果开发者没有提供构造函数,那么C++会自动提供默认构造函数,但是要清晰的认识到默认构造函数是编译器所作的事情。

8.4.1  构造函数基本概念

前面在类对象初始化小节已经提及构造函数定义的基本方式,类的构造函数名称与类名相同。这是为了区别一般的类方法成员而定义的,构造函数可以看作为类的特殊方法成员。类中构造函数的定义基本语法形式如下所示。

class class_name

{

         public:

                   class_name();                     //构造函数声明

                   …

};

class_name:: class_name()               //外部定义类构造函数

{

         …                                                    //构造函数体

}

class class_name

{

         public:

                   class_name(){…}               //声明同时定义构造函数,以内联函数的方式定义

                   …

};

构造函数可以有以上两种定义方式。一种是在类体中声明该方法,类体外部通过域符号::定义该构造函数。另一种是在类体内部声明的同时定义构造函数,这种情形下编译器会隐式的将其作为内联函数方式处理。但是由于构造函数在编译器层面处理有可能会加入一定的代码处理,增加构造函数的代码量,所以通常情况下构造函数不需要在类体内部定义。如下构造函数一般形式定义实例。

class Test                                               //定义名为Test的类

{

         public:                                            //声明如下公有构造函数,传入整型参数

                   Test(intdata);

         private:                                           //定义类Test私有数据成员

                   intm_data;

};

Test::Test(int data)                                //Test类构造函数外部定义

{

         m_data= data;                             //构造函数内部赋值以初始化操作

}

class Test

{

         public:

                   Test(intdata){m_data = data}   //类内部以内联函数方式定义构造函数

         privete:

                   intm_data;

};

Test test(1);

构造函数属于类的成员方法,同样权限限定访问符对其也有限制作用。可以将类的构造函数定义为私有或保护形式,这样类不可以在外部定义其对象实例化,保护形式只能在本类中或者派生类中定义。私有形式只能在本类中定义对象实例化。该种方式适用于只想定义类单个实例的情形,限制外部用户定义该类的对象实例。

构造函数可以拥有函数参数列表,根据传入的参数用来初始化相关数据成员。同一个类中构造函数可以拥有多个。通过不同的参数个数或者类型等方式来实现构造函数的重载,以此来确定多个构造函数并存的情况下不会被编译器误解为函数重复定义。

8.4.2  重载构造函数

C++类中的构造函数约定,构造函数不能拥有返回值但是可以拥有函数的参数列表,即允许定义对象时通过传入参数来与其进行通信。由于C++支持同个类中多个构造函数并存,以供类对象实现多元化初始化以及内部管理处理。构造函数重载与普通函数定义方式类似,虽然构造函数没有返回值,但是函数重载只与参数表中参数个数以及参数类型有关。

下面通过一个重载的实例来了解构造函数重载使用情况,其代码编辑如下所示。

class Test                                                                  //定义名Test

{

         public:

                   Test(intid);                                             //声明公开构造函数,传入整型参数

                   Test(intid,int data);                               /声明公开构造函数,传入两个整型值

         private:

                   intm_id;                                                  //整型数据成员

                   ntm_data;                                              //整型数据成员

};

Test::Test(int id)                                                       //构造函数定义实现

{

          m_id = id;

}

Test::Test(int id,int data)                                         //构造函数定义实现

{

          m_id = id;

          m_data = id;

}

Test test1(1);                                                             //定义对象

Test test2(1,2);                                                         //定义对象

以上实例中定义了类Test,同时根据参数个数的不同在类体内声明两个构造函数实现重载。构造函数在类体外部定义实现,主要功能是为该类的数据成员初始化赋值。随后定义类Test两个对象实例,并且传入不同个数的参数让编译器决定类实例化时调用哪个构造函数。

对象一:test1传入单个整型值1,编译器根据参数个数以及类型检查确定调用对应的构造函数。此时该构造函数只初始化了其成员m_id值,另外未被初始化的成员m_data中存在一个未知值。

对象二:test2传入两个整型实参值,调用对应得构造函数分别初始化该对象的数据成员。初学者可以自行添加打印该类数据成员信息。按照前面实例的编译方法,编译执行该程序来验证构造函数重载部分的知识,加深理解。

类定义了一种自定义类型,对象则实现了该类的实例拥有了具体的被分配的内存空间。对象彼此之间相互不影响,每个对象都拥有一份该类成员的拷贝。对象之间可以相互初始化或赋值,原因是拷贝构造与赋值函数的存在所提供的功能,每个类成员被依次拷贝实现。下面小节就拷贝构造函数作出详细讲述。

8.4.3  拷贝构造与赋值函数

前面的小节讲解以及实例中已经展示了类构造函数在初始化类对象数据成员时的使用情形。类对象实例定义时还可以使用该类的其它对象进行初始化。默认情况下由编译器支持的默认拷贝构造函数来完成。与默认构造函数情形类似,默认拷贝构造函数属于编译器级别的支持,但是开发者可以显式的自定义拷贝构造函数,为对象之间互相初始化提供更加灵活的方式。

拷贝构造函数定义实现方式与普通构造函数类似,只是传入的参数为该类本身类型的对象。可以通过如下完整实例了解拷贝构造函数基本定义应用情况。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter0808.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter0808

* 源文件chapter0808.cpp

* 拷贝构造方法实例

*/

#include <iostream>

using namespace std;

 

class Test                                                                           //定义类Test

{

         public:                                                                        //公开成员声明

                   Test(intid,int data);                                        //拥有两个整型参数的构造函数

                   Test(Test&test);                                              //拥有参数为本类对象的拷贝构造函数

                   voidshow();                                                     //无参数无返回值的打印类数据成员信息函数

         private:                                                                       //私有成员定义

                   intm_id;                                                           //Test整型数据成员

                   intm_data;                                                      //Test整型数据成员

};

Test::Test(int id,int data)                                                  //构造函数定义,主要实现数据成员初始化

{

         m_id= id;

         m_data= data;

}

Test::Test(Test &test)                                                        //拷贝构造函数定义,主要实现传入对象成员相互赋值

{

         m_id= test.m_id;

         m_data= test.m_data;

}

void Test::show()                                                               //打印数据成员信息函数定义

{

         cout<<m_id<<""<<m_data<<endl;

}

/*主程序入口*/

int main()

{

         Testtest1(1,2);                                                         //定义Test对象test1,同时传入两个整型实参初始化

         test1.show();                                                             //调用打印对象test1中数据成员信息

         Testtest2(test1);                                                      //定义Test对象test2,同时将对象test1传入初始化

         test2.show();                                                             //调用打印对象test2中数据成员信息

         return0;

}

本实例程序主要演示类类型中通过构造函数实现类对象拷贝赋值的方式。本程序主要由主函数与测试类组成,测试类中包含了相关拷贝构造函数与打印数据成员函数定义。程序具体剖析见注释与后面详细讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter0808.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0808.o

CC=g++

 

chapter0808: $(OBJECTS)

    $(CC)$(OBJECTS) -g -o chapter0808

clean:

    rm -fchapter0808 core $(OBJECTS)

submit:

    cp -f -rchapter0808 ../bin

    cp -f -r*.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0808/src]$make

g++    -c -ochapter0808.o chapter0808.cpp

g++ chapter0808.o -g -o chapter0808

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0808/src]$makesubmit

cp -f -r chapter0808 ../bin

cp -f -r *.h ../include

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0808/src]$cd../bin

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0808/bin]$./chapter0808

1 2

1 2

如上实例中定义使用了对象拷贝构造函数。Test类定义中除了提供包含两个参数的构造函数以外,还提供了拷贝构造函数。其参数为本类Test的对象,内部实现对象数据成员赋值操作。主程序中首先定义Test类对象test1同时传入实参调用构造函数初始化数据成员,后调用该对象show方法成员打印刚刚初始化的对象成员值。

主程序中再定义Test类对象test2,定义的同时使用已经定义的对象test1初始化。此刻使用到拷贝构造函数,即传入已经定义的对象test1,函数内部主要实现一些成员赋值初始化操作。如果用户没有自定义拷贝构造函数,则编译器会添加默认的拷贝构造函数来实现对象拷贝初始化。这里通过自定义拷贝构造函数可以作到更加灵活的函数内部实现。同样拷贝构造函数也可以重载实现,注意参数类型、个数等不同即可。

另外同类类型的对象实例之间相互可以赋值,编译器同样也提供默认的类对象赋值函数。从目前分析来看默认赋值函数应该是重载的等于号操作符,用户也可以自定义重载=号的操作。如下实例在拷贝构造函数实现的基础上用户自定义对象赋值操作。

1.准备实例

打开UE编辑器,新建空白文件另存为chapter0809.cpp。随后连同makefile编译文件一起通过FTP工具传输至Linux服务器。客户端通过scrt访问操作。实例程序代码编辑如下所示。

/**

* 实例chapter0809

* 源文件chapter0809.cpp

* 赋值方法实例

*/

#include <iostream>

using namespace std;

 

class Test                                                                  //定义类Test

{

         public:                                                               //公开成员声明

                   Test(intid,int data);                               //拥有两个整型参数的构造函数

                   Test(Test&test);                                    //拥有参数为本类对象的拷贝构造函数

                   voidshow();                                           //无参数无返回值的打印类数据成员信息函数

                   voidoperator=(Test &test);                  //重载=符号,实现对象赋值

         private:                                                             //私有成员定义

                   intm_id;                                                  //Test整型数据成员

                   intm_data;                                             //Test整型数据成员

};

Test::Test(int id,int data)                                         //测试类构造函数,根据参数传入初始化成员变量

{

         m_id= id;                                                         //传入参数赋值

         m_data= data;                                               //传入参数赋值

}

Test::Test(Test &test)                                              //拷贝构造函数定义

{

         m_id= test.m_id;                                           //通过传入对象给成员依次赋值

         m_data= test.m_data;

}

void Test::show()                                                     //打印测试类数据成员

{

         cout<<m_id<<""<<m_data<<endl;

}

void Test::operator=(Test &test)                            //重载对象赋值=符号定义

{

         m_id= test.m_id;

         m_data= test.m_data;

         cout<<"here!"<<endl;

}

/*主程序入口*/

int main()

{

         Testtest1(1,2);                                                //定义测试类对象test1,根据传入参数初始化对象成员

         test1.show();                                                    //打印输出测试类对象成员值

         Testtest2(3,4);                                                //定义测试类对象test2,同时根据传入参数初始化

         test2= test1;                                                    //通过重载=符号,对象之间直接赋值

         test2.show();                                                    //被对象赋值后的对象test2打印其成员值

         return0;

}

上述实例程序主要通过重载“=”符号的方式演示除拷贝构造函数另外一种对象赋值方式。程序主要由主函数与测试类组成,其中测试类包含拷贝构造函数以及重载“=”符号、打印类成员方法。程序具体剖析见注释与后面详细讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter0809.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0809.o

CC=g++

 

chapter0809: $(OBJECTS)

    $(CC)$(OBJECTS) -g -o chapter0809

clean:

    rm -fchapter0809 core $(OBJECTS)

submit:

    cp -f -rchapter0809 ../bin

    cp -f -r*.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0809/src]$make

g++    -c -ochapter0809.o chapter0809.cpp

g++ chapter0809.o -g -o chapter0809

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0809/src]$makesubmit

cp -f -r chapter0809 ../bin

cp -f -r *.h ../include

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0809/src]$cd../bin

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0809/bin]$./chapter0809

1 2

here!

1 2

该实例在以上实例代码中增加赋值操作符重载实现。只要根据传入的对象实现赋值功能,为了区别系统默认的赋值操作的实现。添加一行输出here字符串的代码表示执行赋值时调用的是自定义赋值函数。该程序主要增加重载=操作符方法,其中operator是表示操作符重载的关键字。等于号为重载实现的方法名,其实现即根据传入的该类对象为被赋值的对象数据成员赋值。

实例主程序中,定义Test类对象test1并传入实参初始化。调用test1show方法来打印该对象数据成员信息。随后定义test2对象同时传入实参初始化,之后将test1对象赋值给test2,此时调用了赋值函数,调用test2对象的show方法打印信息。由于先调用赋值函数操作,所以会在显示test2数据信息之前打印here字符串,表示在对象赋值之时调用了赋值函数。

C++中区分初始化与赋值的概念,在对象定义以及赋值时正确区分调用的是拷贝构造函数还是赋值函数是对基本概念熟悉的考验之一。如上例中定义如下语句时需要注意区分。

Test test1(1,2);                                                                //定义Test类对象test1并初始化

Test  test2= test1;                                                           //定义Test类对象test2同时使用test1对象初始化

Test test3(3,4);                                                                //定义Test类对象test3并初始化

test 3 = test2;                                                                      //使用对象test2test3赋值

如上述语句,第一条语句表明定义Test类对象test1。同时根据传入的实参调用该类的自定义构造函数初始化该对象数据成员。第二条语句表明定义Test类对象test2,虽然使用了赋值符号,但对象之间是初始化的概念,此刻调用的是自定义的拷贝构造函数。第三、四条语句即定义test3对象并初始化,后用对象test2为其赋值,此时调用为自定义赋值函数。

需要注意的是拷贝构造有两种方式。一种是直接定义对象时候通过随后已存在对象作为实参传入调用拷贝构造函数。另一种是定义对象同时用赋值符号使用已存在的对象初始化,此时也是调用拷贝构造函数。如Test test2(test1)Test test2 = test1,通常情形下建议使用第一种方式清晰明了,因为后种方式容易引起认识上的歧义。

8.5  析构函数

C++中针对调用构造函数构造对象实例,同时提出了对象使用结束后释放相应资源的析构函数。析构函数与构造函数正巧相反,所以在命名规则上通过在类名称前加“~”符号区别于构造函数而存在。析构函数与构造函数一样,属于类类型特殊成员函数,都是在对象实例定义以及对象消失时自动调用。

8.5.1  析构函数基本概念

C++中类的析构函数与类同名。为了区别构造函数的定义,析构函数在函数名之前加上“~”符号表示。与构造函数相同的是析构函数同样没有返回值,但不同的是析构函数不允许拥有函数参数列表以及不允许重载使用,即一个类构造函数可以拥有多个但是析构函数却只能有一个。

析构函数如果用户不定义,则编译程序时会默认提供。自定义析构函数通常有两个作用。第一种情况当类中数据成员在定义分配内存时涉及堆内存动态分配,需要开发者释放资源的情形可以放入析构函数中实现。第二种情况当类的对象实例处理完毕直至消失时,析构函数可以处理任务的结束时的一些扫尾工作。

类的析构函数语法定义非常简单,与构造函数同样可以在类的内部定义实现。也可以在类的内部声明,在外部定义析构函数。下面小节将会通过完整的实例了解类的析构函数在应用程序中的基本作用。

1.准备实例

打开UE编辑器,新建空白文件另存为chapter0810.hchapter0810.cpp。随后连同makefile编译文件一起通过FTP工具传输至Linux服务器。客户端通过scrt访问操作。实例程序代码编辑如下所示。

/**

* 实例chapter0810

* 源文件chapter0810.hchapter0810.cpp

* 析构函数实例

*/

//头文件chapter0810.h

#ifndef CHAPTER0810_H_

#define CHAPTER0810_H_

 

#include <iostream>

using namespace std;

 

const int LENGTH=1024;

 

class DtorTest

{

         public:

                   DtorTest();

                   ~DtorTest();

                   void  showTestInfo();

                   void  setTestInfo(char*testData){cout<<"setTestInfo..."<<endl;strcpy(m_testData,testData);}

                   char*getTestInfo(void){return m_testData;}

         private:

                   char*m_testData;

};

 

#endif

 

//源文件chapter0810.cpp

#include"chapter0810.h"

 

DtorTest::DtorTest()

{

         m_testData= new char[LENGTH+1];

}

 

DtorTest::~DtorTest()

{

         cout<<"~DtorTest()..."<<endl;

         if(m_testData != NULL)

         {

                   cout<<"deletem_testData..."<<endl;

           delete []m_testData;

         }

         cout<<"~DtorTest()complete!"<<endl;

}

 

void DtorTest::showTestInfo()

{

        

         cout<<"m_testData:"<<m_testData<<endl;

}

 

int main()

{

         charstrDate[LENGTH] = "Hello dtor!";

         DtorTest dtorTest;

         dtorTest.setTestInfo(strDate);

         dtorTest.showTestInfo();

         return 0;

}

本实例程序分为头文件和源文件两个部分。头文件声明定义了析构函数测试类DtorTest,并且规定了其相应的方法成员接口和私有数据成员。其中方法接口主要包含构造函数、析构函数、设置测试信息方法setTestInfo()、获取测试信息方法getTestInfo()以及打印输出测试信息的方法showTestInfo()实例程序主要由两个部分组成,头文件chapter0810.h中主要由类DtorTest的声明、DtorTest相应方法成员的声明为主;源文件chapter0810..cpp主要定义实现了DtorTest类的各个成员方法以及主函数两个部分组成,具体程序详细讲解见后续程序剖析部分。

2.编辑makefile

Linux平台下需要编译的源文件为chapter0810.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0810.o

CC=g++

 

chapter0810: $(OBJECTS)

         $(CC)$(OBJECTS) -g -o chapter0810

clean:

         rm-f chapter0810 core $(OBJECTS)

submit:

         cp-f -r chapter0810 ../bin

         cp-f -r chapter0810.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至bin目录,执行该程序文件运行结果如下所示。

[developer@localhost src]$ make

g++    -c -ochapter0810.o chapter0810.cpp

g++ chapter0810.o -g -o chapter0810

[developer @localhost src]$ make submit

cp -f -r chapter0810 ../bin

cp -f -r chapter0810.h ../include

[developer @localhost src]$ cd ../bin

[developer @localhost bin]$ ./chapter0810

setTestInfo...

m_testData:Hello dtor!

~DtorTest()...

delete m_testData...

~DtorTest() complete!

4.程序剖析

本实例中析构函数测试类所包含的方法成员和数据成员前面都介绍过,其中数据成员定义为一个字符类型指针。该字符类型指针定义时为指向不定的值,主程序中通过定义析构函数测试类DtorTest的对象,该对象定义同时执行了其构造函数。析构函数测试类中构造函数内部实现了该类私有保护成员动态内存的申请工作。通过new运算符,申请相应的char字符类型的空间,其大小为LENGTH+1,而LENGTH为头文件中定义的常量值,具体大小已经规定。

申请完空间之后,将之前字符数组strDate通过其析构函数测试类的setTestInfo方法,将其数据拷贝至私有数据成员指向的空间中。最后通过showTestInfo方法打印输出其私有成员数据的指向值。程序退出时,执行该类的析构函数释放已经动态申请的空间。

本实例中主要演示说明了析构函数在程序中基本应用情况,更多的析构函数使用场景靠初学者不断实践中掌握。但是初学者此刻需要明白,小节开始处讲述的析构函数在程序中大致应用的两种场景。

8.6  C++友元

前面已经讲述过,类中为了实现数据隐藏C++提供了成员的控制访问功能,对于public的成员外部可以通过该类对象轻松访问。但是对于类的保护类成员由于访问控制机制的限制,类的外部函数无法访问类内部保护成员。下面将会介绍C++提供的一种机制友元,灵活的让外部函数访问类内部保护成员。

8.6.1  C++友元基本概念

软件编程中特殊情况下需要外部函数或者类中的成员函数访问另一个类中的保护成员。C++中提供友元机制在不用公开类成员之时,外部函数可以轻松访问类内部保护成员。友元的定义需要使用关键字friend表示,友元可以是类的友元函数也可以是整个类的友元类。

下面通过几个友元定义实例,了解友元在C++面向对象类中基本定义方式,实例定义如下所示。

class bookInfo                                                //书本信息类声明

{

         public:

                   friendshowBookInfoMember();         //友元方法定义,打印书本信息类成员

         private:

                   …

};

class bookInfo                                                //书本信息类声明

{

         public:

                   friendclass Test;                         //友元类Test,作为书本信息类的公开友元数据成员

         private:

                  …

};

如上两个实例分别定义类bookInfo友元函数与友元类定义。首先类bookInfo公共成员中声明外部的友元函数showBookInfoMember()用于打印该类私有数据成员。此时该友元函数具有可以访问bookInfo类私有保护成员。其次,友元还可以是整个类的友元。上述第二个实例中在类bookInfo公共成员中定义友元类Test。此时友元类Test中所有成员函数分别为类bookInfo友元函数,即该类中所有成员函数都可以访问类bookInfo私有保护数据成员。

C++语言中友元概念可以应用于外部函数或者外部整个类上。友元的提出解决了特殊情况下外部函数需要访问类中私有保护成员的需求。同时类定义通常都对外公开接口访问其私有保护数据,友元的出现一定程度上在特殊需求下提高访问类私有数据的效率。原因是友元函数通常是在外部函数需要频繁访问该类私有保护数据而出现的,由于友元的定义直接访问了类中数据成员因而减少了频繁调用类公共接口而带来的类型检查等开销。

但是友元定义只适用于特殊需求的情况下。通常在软件编程中该用法也破坏了C++中类提出的数据隐藏和封装性。所以类的友元定义需求在具体分析之后慎重使用,不要一昧追求效率提高而破坏类本身应有的封装性。

8.6.2  类的友元函数定义使用

类的友元函数声明在上一小节已经介绍过。本小节就类中友元函数的定义使用作详细的讲解。首先下面通过一个完整实例演示讲述友元函数定义以及对类的内部保护成员访问的情形。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter0811.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter0811

* 源文件chapter0811.cpp

* 类友元函数应用实例

*/

#include <iostream>

using namespace std;

 

class bookInfo                                                                                               //定义书本信息类bookInfo

{

          public:                                                                                                  //bookInfo公开成员

                  bookInfo(int id,char *name,char *author,char*publisher);       //bookInfo构造函数

                  ~bookInfo();                                                                               //bookInfo析构函数

                  friend void showBookInfoMember(bookInfo&book);                //bookInfo友元函数

          private:

                  int m_id;                                                                                  //bookInfo数据成员,表示书本编号

                  char m_name[50];                                                                   //bookInfo数据成员,表述书本名称

                  char m_author[50];                                                                  //bookInfo数据成员,表示书本作者

                  char m_publisher[50];                                                             //bookInfo数据成员,表示书本出版社

};

bookInfo::bookInfo(int id,char *name,char*author,char *publisher)  //bookInfo构造函数定义

{

          m_id = id;                                                                                            //书编号成员赋值

          strcpy(m_name,name);                                                                   //书名拷贝赋值

          strcpy(m_author,author);                                                                           //书作者拷贝赋值

          strcpy(m_publisher,publisher);                                                                //书出版社拷贝赋值

}

bookInfo::~bookInfo()                                                                                  //bookInfo析构函数定义

{

}

void showBookInfoMember(bookInfo &book)                                        //友元函数定义,用于显示bookInfo数据信息

{

           cout<<"bookInfo Memberdata:"<<endl;

           cout<<"[bookid:]"<<book.m_id<<" "

               <<"[bookname:]"<<book.m_name<<" "

               <<"[bookauthor:]"<<book.m_author<<" "

               <<"[bookpublisher:]"<<book.m_publisher<<endl;

}

/*主程序入口*/

int main()

{

           bookInfo book(1,"LinuxC++","jack","Oxford University Press");//定义bookInfo类对象同时初始化

           showBookInfoMember(book);                                                      //调用外部函数打印bookInfo数据信息

           return 0;

}

本实例主要通过友元函数演示程序外部访问类方法成员的功能。程序主要由主函数与书本信息类组成,其中书本信息类除了构造、析构方法外还包含打印书本信息以及相关的类数据成员。具体程序分析见注释与后面的程序讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter0811.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0811.o

CC=g++

 

chapter0811: $(OBJECTS)

    $(CC)$(OBJECTS) -g -o chapter0811

clean:

    rm -fchapter0811 core $(OBJECTS)

submit:

    cp -f -rchapter0811 ../bin

    cp -f -r*.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件。随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0811/src]$make

g++    -c -ochapter0811.o chapter0811.cpp

g++ chapter0811.o -g -o chapter0811

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0811/src]$makesubmit

cp -f -r chapter0811 ../bin

cp -f -r *.h ../include

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0811/src]$cd../bin

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0811/bin]$./chapter0811

bookInfo Member data:

[book id:]1 [book name:]Linux C++ [bookauthor:]jack [book publisher:]Oxford University Press

上述实例中主要演示了类bookInfo中友元函数定义使用情况。首先定义书本信息类bookInfo,该类数据成员为私有保护类型。此时定义该类外部友元函数showBookInfoMember()用于访问bookInfo类私有数据成员。

bookInfo中友元函数定义使用方式非常简单。在类体中声明函数showBookInfoMember()同时传入类bookInfo对象在函数内部通过该对象访问并打印该类数据成员。友元函数定义在类bookInfo体内标以函数声明前加关键字friend。该函数在类体外定义但不属于类中成员,类外部定义实现友元函数不需要加关键字friend,与普通函数定义实现无异。

8.6.3  类的友元类定义使用

类体中友元类的定义表明,该友元类中所有成员函数都为当前类中的友元函数,即该类中所有成员函数都可以访问当前类中所有保护类型的数据成员。下面通过一个完整实例演示友元类定义使用情况。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter0812.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter0812

* 源文件chapter0812.cpp

* 友元类应用实例

*/

#include <iostream>

using namespace std;

class Test;                                                                                                                //声明类Test

class bookInfo                                                                                                        //定义书本信息类bookInfo

{

          public:

                  bookInfo(int id,char *name,char *author,char*publisher);                 //书本信息类构造函数

                  ~bookInfo(){}                                                                                      //书本信息类析构函数

          public:

                  friend class Test;                                                                               //声明友元类Test

          private:

                  int m_id;                                                                                              //类的数据成员,表示书本编号信息

                  char m_name[50];                                                                             //类的数据成员,表示书本名称信息

                  char m_author[50];                                                                            //类的数据成员,表示书本作者信息

                  char m_publisher[50];                                                                      //类的数据成员,表示书本出版社信息

};

bookInfo::bookInfo(int id,char *name,char*author,char *publisher)           //bookInfo类构造函数定义

{

          m_id = id;

          strcpy(m_name,name);

          strcpy(m_author,author);

          strcpy(m_publisher,publisher);

}

class Test                                                                                                                 //定义友元类Test

{

          public:

                  Test(){}                                                                                                 //定义Test类构造函数

                  ~Test(){}                                                                                               //定义Test类析构函数

                  void showBookInfoMember(bookInfo &book);                                     //Test成员函数,打印书本类信息

          private:

};

void Test::showBookInfoMember(bookInfo &book)                                        //Test打印书本信息的方法函数定义

{

           cout<<"bookInfo Memberdata:"<<endl;

           cout<<"[bookid:]"<<book.m_id<<" "

               <<"[bookname:]"<<book.m_name<<" "

               <<"[bookauthor:]"<<book.m_author<<" "

               <<"[bookpublisher:]"<<book.m_publisher<<endl;

}

/*主程序入口*/

int main()

{

          bookInfo book(1,"LinuxC++","jack","Oxford University Press");//定义书本信息类对象实例,同时初始化

          Test test;                                                                                              //定义测试类Test对象实例

          test.showBookInfoMember(book);                                                 //调用测试类Test成员函数打印书本信息

          return 0;

}

本实例主要通过友元类的方式演示外部类中方法访问书本信息类私有保护成员的功能。本程序主要由主函数与书本信息类、测试类组成,其中测试类作为书本信息类的友元类出现。具体程序剖析见程序注释与后面程序的讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter0812.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0812.o

CC=g++

 

chapter0812: $(OBJECTS)

    $(CC)$(OBJECTS) -g -o chapter0812

clean:

    rm -fchapter0812 core $(OBJECTS)

submit:

    cp -f -rchapter0812 ../bin

    cp -f -r*.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0812/src]$make

g++    -c -ochapter0812.o chapter0812.cpp

g++ chapter0812.o -g -o chapter0812

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0812/src]$makesubmit

cp -f -r chapter0812 ../bin

cp -f -r *.h ../include

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0812/src]$cd../bin

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0812/bin]$./chapter0812

bookInfo Member data:

[book id:]1 [book name:]Linux C++ [bookauthor:]jack [book publisher:]Oxford University Press

该实例主要演示友元类基本定义及使用方法。实例中主要涉及两个类,即类bookInfo表示书本信息与类Test表示友元测试信息。本实例之前为表达基本概念以及操作的实例中都只按照单文件处理的方式来实现。通常涉及不同类的定义应该分别放入不同的代码文件中。下章开始所有实例会按照多文件,以及Linux工程编译makefile文件方式来实现。

上述实例中,首先定义书本信息类bookInfo同时将类Test声明为该类的友元类,即Test类中所有成员函数都为类bookInfo的友元函数。由于类的使用前都必须声明,或者直接声明同时定义,以便后续可以使用该类。则上述实例中首先声明类Test,类的声明直接使用关键字后跟类名,随后在bookInfo类中定义该类为其友元类。

Test类作为类bookInfo友元类即该类中所有成员函数都为书本信息类友元函数。从上述实例来看Test类成员函数showBookInfoMember()此时为类bookInfo友元函数,可以访问该类中私有保护成员。主程序中首先定义书本信息类对象实例,同时调用其构造函数进行初始化。随后,定义Test类对象实例test,使用该对象调用其成员函数showBookInfoMember()。该函数主要通过传入bookInfo类对象,在函数体内直接通过该对象访问并打印bookInfo类中私有保护成员。

8.7  类的静态成员

C++类中有一类特殊的成员,即静态数据成员与静态成员方法。类类型中静态成员基本定义及使用方式与普通成员存在一定的区别。下面将会分别讲述类类型中静态成员的基本概念与使用方式。

8.7.1  静态数据成员

类的静态数据成员最大的特点即可以实现该类多个对象共享该成员,从而做到不同类对象之间数据共享。之前讲述类对象实例时大致了解,类对象不同实例之间是互不影响,都拥有该类的一份成员拷贝。而特殊情况下类的多个实例可能需要其封装的数据之间实现共享访问处理,此时静态成员变量可以满足该类要求。

软件编程中,类静态数据成员的应用非常多。如日志记录中的日志数、以及最常见线程编程中数据共享操作等。下面通过一个计数实例演示类静态数据成员基本应用情况。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter0813.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter0813

* 源文件chapter0813.cpp

* 静态数据成员实例

*/

#include <iostream>

using namespace std;

 

class Clog                                                                                                                //定义日志类Clog

{

         public:

                   Clog(){}                                                                                         //日志类构造函数定义

                   ~Clog(){}                                                                                      //日志类析构函数定义

                   voidlogCount();                                                                          //日志计数方法成员

                   voidshowCount();                                                                      //打印计数结果方法成员

         private:

                   staticint m_count;                                                                      //静态计数数据成员

};

int Clog::m_count = 0;                                                                                  //静态计数数据成员外部初始化

void Clog::logCount()                                                                                   //日志类计数方法成员方法定义

{

         ++m_count;

}

void Clog::showCount()                                                                               //打印日志类计数结果成员方法定义

{

         cout<<"logcount:"<<m_count<<endl;

}

int main()

{

         Cloglog1,log2;                                                                                             //定义日志类对象实例log1log2

         for(inti = 0;i < 10;i++)                                                                          //限定for循环中log1对象调用其计数方法

         {

                   log1.logCount();

         }

         log1.showCount();                                                                               //log1对象实例调用其打印计数结果方法

         for(intj = 0;j < 10;j++)                                                                          //限定for循环中log2对象调用其计数方法

         {

                   log2.logCount();

         }

         log2.showCount();                                                                               //log2对象实例调用其打印计数结果方法

         return0;

}

本实例主要通过类静态成员变量演示类的不同对象之间共享数据的功能。本程序主要由主函数与日志记录类组成,其中日志记录类中的记录计数变量定义为静态数据变量,并且在外部初始化。具体程序分析见程序注释与后面程序的讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter0813.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0813.o

CC=g++

 

chapter0813: $(OBJECTS)

    $(CC)$(OBJECTS) -g -o chapter0813

clean:

    rm -fchapter0813 core $(OBJECTS)

submit:

    cp -f -rchapter0813 ../bin

    cp -f -r*.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件。随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0813/src]$make

g++    -c -ochapter0813.o chapter0813.cpp

g++ chapter0813.o -g -o chapter0813

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0813/src]$makesubmit

cp -f -r chapter0813 ../bin

cp -f -r *.h ../include

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0813/src]$cd../bin

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0813/bin]$./chapter0813

log count:10

log count:20

该实例程序主要演示静态数据成员在软件编程中日志记录的计数应用情况。此处为了演示静态数据成员而设想的实例。真正的日志类为了在不同文件中记录日志,最通常的作法是定义一个对象,其余都定义为指向该对象的对象指针。然后在不同的代码文件中记录日志文件,因为如此会让同一个进程中的不同程序处理过程入到同一个日志文件中。

回归本实例中,首先实例中定义类Clog。该类包含空的构造、析构函数,两个方法成员分别表示计数方法与打印计数结果方法。类中包含一个私有保护的静态数据成员,用于作该类计数变量。此处需要注意的是静态数据成员需要在类体外部初始化,随后类体外部初始化该数据成员为0。实例主程序中定义Clog类两个对象log1log2,分别拥有Clog类不同的拷贝。

随后通过for循环调用log1对象的计数方法,该方法中内容定义很简单,即每调一次计数变量增加1。循环结束后调用log1对象的打印计数结果方法显示其计数结果。同样log2对象也以一样的处理方式紧跟着log1对象之后。

程序运行结果为第一次log1对象循环执行计数方法10次,即此时对象静态数据成员m_count值为10,随后执行方法中打印出来。由于静态数据成员使得类不同对象之间数据实现共享,即此时操作的m_count将会从log1对象中的该值。继续同种方式处理log2对象后打印其结果,验证该静态数据成员特性。由于循环计数执行10次,则此时打印结果为20

从上述实例演示中可以得知,类的静态数据成员并不属于类的各个对象。重新定义的类对象实例不会影响到该类的静态数据成员,即类不同对象之间的静态数据成员是共享的。

8.7.2  静态成员函数

由于类中静态数据成员不属于某个特定的对象实例。上述小节中讲述静态数据成员时依然通过类的公开接口来访问操作类中的静态数据成员。类的公开接口成员属于各个类对象,即该类的各个对象实例都拥有一份拷贝,而对于静态数据成员则共享一份拷贝。在软件编程思想中,考虑到一种更加合理的方式,即采用静态成员函数来操作其类的静态数据成员。

静态数据成员不属于某个特定的对象实例。如果使用类的公用接口通过其对象来调用该接口操作其静态数据成员并不会修改对象本身的数据,因为静态数据成员是类各个对象所共享的。C++中提供静态成员函数来合理解决上述问题,规定类的静态成员函数不能操作其类中的普通数据成员,处理静态数据成员与全局变量。并且类外部访问时不依赖于特定的对象来调用。下面通过一个完整实例了解类静态成员函数在解决这一问题时的基本应用情况。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter0814.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter0814

* 源文件chapter0814.cpp

* 静态成员函数实例

*/

#include <iostream>

using namespace std;

class Clog                                                                                                      //日志类Clog定义

{

         public:

                   Clog(){}                                                                               //类构造函数定义

                   ~Clog(){}                                                                             //类析构函数定义

                   staticvoid logCount();                                                      //类静态成员函数声明,主要实现日志计数

                   staticvoid showCount();                                                  //类静态成员函数声明,主要用于打印计数结果

         private:

                   staticint m_count;                                                             //类静态数据成员,表示计数变量

};

 

int Clog::m_count = 0;                                                                        //外部初始化类静态数据成员为0

void Clog::logCount()                                                                         //类实现日志计数方法定义

{

         ++m_count;                                                                                 //每次调用后都会在该成员变量上加1

}

void Clog::showCount()                                                                     //类打印计数结构方法定义

{

         cout<<"logcount:"<<m_count<<endl;

}

/*主程序入口*/

int main()

{

         Cloglog;                                                                                      //定义Clog类对象log

         for(inti = 0;i < 10;i++)                                                                //执行for循环,使用该对象调用日志计数方法

         {

                   log.logCount();

         }

         log.showCount();                                                                        //对象log调用打印日志计数成员,打印计数结果

         for(intj = 0;j < 10;j++)                                                                //执行for循环,用外部::方式调用其静态计数方法

         {

                   Clog::logCount();

         }

         Clog::showCount();                                                                   //类外部方式调用其静态打印计数结果方法

         return0;

}

本实例主要通过类静态方法成员演示通过类的静态方法调用操作类私有数据成员的功能。本程序主要由主函数与日志记录类两个部分组成,其中日志类包括基本的日志记录计数与打印计数信息的方法成员。具体程序剖析见程序注释与后面详细讲解。

2.编辑makefile

Linux平台下需要编译源文件为chapter0814.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0814.o

CC=g++

 

chapter0814: $(OBJECTS)

    $(CC)$(OBJECTS) -g -o chapter0814

clean:

    rm -fchapter0814 core $(OBJECTS)

submit:

    cp -f -rchapter0814 ../bin

    cp -f -r*.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0814/src]$make

g++    -c -ochapter0814.o chapter0814.cpp

g++ chapter0814.o -g -o chapter0814

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0814/src]$makesubmit

cp -f -r chapter0814 ../bin

cp -f -r *.h ../include

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0814/src]$cd../bin

[ocs@vm-rh:~/users/wangfeng/Linux_c++/chapter08/chapter0814/bin]$./chapter0814

log count:10

log count:20

上述实例主要演示了与日志类静态数据成员的操作全部转移至静态成员函数中的实现过程。与上一小节实例不同。本节提出C++静态成员函数概念,由于静态成员函数同样也不属于类某个特定对象实例,因此在类的外部可以使用::符号类访问其静态成员。

实例中可以看出,C++中静态成员函数只需在函数定义前加上关键字static表示即可。其余定义实现等方式与普通无异。该实例中基本实现与上小节无异,仅仅将两个操作静态数据成员的方法全部定义为静态成员方法。另外最大的区别就是外部访问问题,原先定义两个对象log1log2来分别通过循环调用计数方法,最后调用打印计数结果方法来演示静态数据成员的操作方式。

当前实例采用两种方式调用静态成员方法,第一定义对象实例并通过对象实例来调用其静态成员计数方法,随后打印结果。另一种方式即通过::符号来直接访问。

C++静态成员方法不属于类的对象实例。同样在类域的范围内只有一份拷贝,与静态数据成员相同其操作方式可以通过对象实例调用,也可以通过::符号方式来调用。另外需要注意的是,静态方法成员只可以操作静态数据成员以及全局变量,因为类的数据成员是属于各个不同对象的,而静态方法成员又属于共有的,所以该类方法中不允许操作类普通数据成员。

抱歉!评论已关闭.