C++
并不是一个纯粹的面向对象的语言,而是一种基于过程和面向对象的混合型的语言。由于C++
是在C语言
的基础上发展而成的,因此保留了C的绝大部分的功能和运行机制。C语言
是基于过程的语言,C++
自然保留了基于过程语言的特征。
凡是以类对象为基本构成单位的程序称为基于对象的程序。而面向对象程序设计则还有更多的要求。面向对象程序设计有4个主要特点:抽象、封装、继承、多态性。C++
的类体现了抽象和封装的特性,在此基础上再利用继承和多态性,就成为真正的面向对象的设计
对象
任何一个对象都应当具有两个要素,即属性和行为。对象是由一组属性和一组行为构成的。
在一个系统中的多个对象之间通过一定的渠道相互联系。要使某一个对象实现某种行为(即操作),应当向它传送相应的消息。
在C++
中,每个对象都是由数据和函数这两部分组成的
封装和信息隐蔽
可以对一个对象进行封装处理,把它的一部分属性和功能对外界屏蔽,也就是说从外界是看不到,甚至不可知的。“隐蔽”表达的意思把对象的内部实现和外部行为分隔开来
封装性指两方面的含义:
一是将有关的数据和操作代码封装在有关对象中,形成有关基本单位,各个对象之间相对独立,互不干扰。
二是将对象中某些部分对外隐蔽,即隐蔽其内部细节,只留下少量接口,以便于外界联系,接收外界的消息。这种对外界隐蔽的做法称为信息隐蔽
抽象
抽象的作用是表示同一类事务的本质
类是对象的抽象,而对象则是类的特例,即类是具体表现形式
继承和重用
如果在软件开发中已经建立了一个名为A的类,又想另外建立一个名为B的类,而后者与前者内容基本相同,只是在前者的基础上增加一些属性和行为,显然不必再从头设计一个新类,而只须在类A的基础上增加一些新内容即可。这就是面向对象程序设计的继承机制
比如:白马继承了马的基本特征,又增加了新的特征。那么马是父类或称为基类,白马是从马派生出来的,称为子类或派生类
多态性
如果有几个相似不完全相同的对象,有时人们要求在向它们发出同一消息时,它们的反应各不相同,分别执行不同的操作。这种情况就是“多态现象”
在C++
中,所谓多态性是指:由继承而产生的不同的派生类,其对象对同一消息会做出不同的响应。多态性是面向对象设计的一个重要特性,能增加程序的灵活性
类的声明和对象的定义
类和对象的关系
在C++
中对象的类型称为类。类代表类某一批对象的共性和特征。前面也说了:类是对象的抽象,而对象是类的具体实例。
正如同结构体类型和结构体变量的关系一样,先声明一个结构体类型,然后用它去定义结构体变量。同一个结构体类型可以定义出多个不同的结构体变量。在C++
中也是先声明一个类类型,然后用它去定义若干相同类型的对象。对象是类类型的一个变量。
类是抽象不占用内存,而对象是具体的,占用存储空间
声明类类型
类是用户建立的类型,如果程序中要用到类类型,必须自己根据需要进行声明,或者使用别人设计好的类。C++
标准本身并不提供现成类的名称、结构和内容
声明一个类
class Student // 以class开头,Student为类名
{
int num; // 数据成员
char name[20];
char sex;
void display() // 成员函数
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
};
int main(int argc, const char * argv[]) {
Student student1, student2; // 定义两个Student类的对象student1和student2
return 0;
}
可以看到声明一个类,需要使用关键字
class
开头,然后写类名花括号表示类体,类体中是类的成员表,列出类中的全部成员,可以看到除类数据部分以外,还包括对数据操作的函数,这里就体现了将数据和操作封装在一起
类的声明以分号结束
main
函数中使用Student
类定义了两个类对象
现在封装在类对象student1
和student2
中的成员对外界隐蔽,外界不能调用它们。只有本对象的函数display
可以引用本对象中的数据。也就是说,在类外不能直接调用类中的成员,这当然“安全”了。
但是在程序中怎样才能执行对象student1
的display
函数呢?它无法启动,因为缺少对外界的接口,外界不能调用类中的成员函数。因此,不能把类中的全部成员与外界隔离,一般是把数据隐蔽起来,而把成员函数作为对外界的接口。
修改Student类如下:
class Student // 以class开头,Student为类名
{
private:
int num; // 数据成员
char name[20];
char sex;
public:
void display() // 成员函数
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
};
现在声明display
函数是公用的,外界就可以调用该函数了。
如果在类的定义中既不指定private,也不指定public,则系统默认是私有的(private)
根据上面类类型的声明,可以得到一般形式为:
class 类名
{
private:
私有的数据和成员函数
public:
公用的数据和成员函数
};
private
和public
称为成员访问限定符。用它们来声明各成员的访问数据。被声明为私有的(private
)成员,只能被本类中成员函数引用,类外不能调用(友元类除外)。被声明为公用的(public
)成员,既可以被本类成员函数所引用,也可以被类的作用域内的其他函数引用。
除了private
和public
,还有一种成员访问限定符protected
(受保护的),用protected
声明的成员称为受保护的成员,它不能被类外访问(这点与私有成员类似),但是可以被派生类的成员函数访问。
定义对象的方法
定义对象也可以有3种方法
1、先声明类型,然后再定义对象
在C++
中,声明了类类型后,定义对象有两种形式
(1)class 类名 对象名;
class Student student1, student2;
把class
和Student
合起来作为一个类名,用来定义对象
(2)类名 对象名;
Student student1, student2;
不必写class
,而直接使用类名定义对象,这两种方法是等效的。第一种方法是从C语言
继承下来的,第二种方法是C++
的特性,显然第二种方法更简洁方便
2、在声明类的同时定义对象
class Student // 以class开头,Student为类名
{
private:
int num; // 数据成员
char name[20];
char sex;
public:
void display() // 成员函数
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
} student1, student2;
在定义Student
类的同时,定义了两个Student
类的对象
3、不出现类名,直接定义对象
class
{
private:
...
public:
...
} student1, student2;
直接定义对象,在C++
中是合法的、允许的,但却很少使用,也不提倡用。
类的成员函数
类的成员函数(简称类函数)是函数的一种,它也有返回值和函数类型,它与一般函数的区别只是:它是属于一个类的成员,出现在类体中。它可以被指定为private(私有的),public(公用的)或protected(受保护的)
在使用类函数时,要注意调用它的权限(它能否被调用)以及它的作用域(函数能够使用什么范围中的数据和函数)。例如,私有的成员函数只能被本类中的其他成员函数所调用,而不能被类外调用。成员函数可以访问本类对象中任何成员(包括私有的和公用的),可以引用在本作用域中有效的数据
一般的做法是将需要被外界调用的成员函数指定为public
,它们是类对外接口,但应该注意,并非要求把所有的成员函数都指定为public
。有的函数并不是准备为外界调用的,而是为本类中的成员函数调用的,就应该将它们指定为private
。这种函数的作用是支持其他函数的操作,是类中其他成员函数的工具函数,用户不能调用这些私有函数
在类外定义成员函数
前面看到成员函数是在类体中定义的,也可以在类体中只对成员函数进行声明,而在类外进行函数定义。
class Student // 以class开头,Student为类名
{
public:
void display(); // 公用成员函数原型声明
private:
int num; // 数据成员
char name[20];
char sex;
};
// 在类外定义display函数
void Student::display()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
Student student1, student2; // 定义两个Student类的对象student1和student2
注意:
在类中直接定义函数时,不需要在函数名前加上类名,因为函数属于哪一个类是知道的。但是成员函数在类外定义时,就必须在函数名前加上类名,予以限定,
“::”
是作用域运算符,用它声明函数是属于哪一个类Student::display()
表示Student
类的作用域中的display
函数,也就是Student
类中的display
函数。如果没有Student::
限定,则不是Student
类中的display
函数。由于不同的类中可能有相同的函数,用作用域限定符加以限定就明确地指出了是哪一个作用域的函数,也就是哪一个类的函数类函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应该在函数定义之前,否则编译时会出错
虽然函数在类的外部定义,但在调用成员函数时会根据在类中声明的函数原型找到函数的定义,从而执行该函数
内置成员函数
类的成员函数也可以指定为内置函数。
在类体中定义的成员函数的规模一般较小,而系统调用函数的过程所花费的时间开销较大。调用一个函数的时间开销远远大于小规模函数体中全部语句的执行时间。为了减少时间开销,如果在类体中定义的成员函数不包括循环等控制语句,C++
系统自动地对它们作为内置函数来处理。
也就是说,在程序调用这些成员函数的时候,并不是真正地执行函数的调用过程,而是把函数代码嵌入程序的调用点,这样可以大大减少调用成员函数的时间开销
C++
要求对一般的内置函数要用关键字inline
声明,但对类内定义的成员函数,可以省略inline
,因为这些成员函数已被隐含地指定为内置函数。如:
class Student // 以class开头,Student为类名
{
public:
void display()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
private:
int num; // 数据成员
char name[20];
char sex;
};
上面的void display()
其实就是inline void display()
,两者是等价的,只不过写inline
关键字是显示声明,在类体类一般情况下都省略inline
注意:
如果成员函数不在类体内定义,而在类体外定义,系统并不会把它默认为内置函数,调用这些成员函数的过程和调用一般函数的过程是相同的。如果想将这些函数指定为内置函数,应当用inline
做显示声明。如:
class Student // 以class开头,Student为类名
{
public:
inline void display(); // 公用成员函数原型声明
private:
int num; // 数据成员
char name[20];
char sex;
};
// 在类外定义display函数
inline void Student::display()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
上面对于display
函数指定为内置函数,只需要其中一处指定为inline
即可。值得注意的是:如果在类体外定义inline
函数,则必须将类定义和成员函数的定义放在同一头文件中(或者同一源文件中),否则编译时取法进行置换(将函数代码的拷贝嵌入到函数调用点)。但是这样做不利于类的接口与类的实现分离,不利于信息的隐蔽。虽然程序的执行效率提高了,但从软件工程质量的角度来看,这样做并不是好的办法
只有在类外定义的成员函数规模很小而调用频率较高时,才指定为内置函数
成员函数的存储方式
用类去定义对象时,系统会为每一个对象分配存储空间。如果一个类包括数据和函数,按理说,要分别对数据和函数代码(指经过编译的目标代码)分配存储空间。如果用同一个类定义了10个对象,那么是否为每一个对象的数据和函数代码分别分配存储单元,并把它们封装在一起呢?
事实上不是这样的。同一类的不同对象中的数据成员的值一般是不相同的,而不同对象的函数的代码是相同的,不论调用哪一个对象的函数的代码,其实调用的都是同样内容的代码。
每个对象的存储空间只是该对象的数据成员所占用的存储空间,而不包括代码所占用的存储空间。
说明:不论成员函数在类内定义还是在类外定义,成员函数的代码段的存储方式是相同的,都不占用对象的存储空间
对象成员的引用
在程序中经常需要访问对象中的成员有3种方式
- 通过对象名和成员运算符访问对象种的成员
student1.num = 1001; // 对象名.成员名
- 通过指向对象的指针访问对象种的成员
Student student1, *p;
student1.num = 1001;
p = &student1;
cout<<p->num<<(*p).num<<endl; // 输出:1001
p->num
表示p
当前指向对象student1
的num
,(*p).num
中(*p)
就是student1
等价于student1.num
- 通过对象的引用访问对象中的成员
对象的引用其实就是别名,是同一个对象
Student student1;
student1.num = 1001;
Student &student2 = student1;
cout<<student2.num<<endl; // 输出:1001
类的封装性和信息隐蔽
公用接口与私有实现的分离
外界只能通过公用成员函数来实现对类中的私有数据的操作,因此,外界与对象唯一的联系就是调用公用的成员函数。这样就使类与外界的联系减少到最低限度。公用成员函数是用户使用类的公用接口,或者说是类的对外接口
通过成员函数对数据成员进行操作称为类的功能的实现。类中被操作的数据是私有的,类的功能的实现细节对用户是隐蔽的,这种实现称为私有实现。这种类的公用接口与私有实现的分离形成了信息隐蔽。
类声明和成员函数定义的分离
如果一个类只被一个程序使用,那么类的声明和成员函数的定义可以直接写在程序的开头,但是如果一个类被多个程序使用,这样做的重复工作量就大了,效率就太低了。
在面向对象的程序开发中,往往把类的声明(其中包含成员函数的声明)放在指定的头文件中,用户如果想用该类,只要把有关的头文件包含进来即可,不必在程序中重复书写类的声明,以减少工作量,节省篇幅,提高变成的效率
由于头文件中包含了声明,因此在程序中就可以用该类来定义对象。由于在类体中包含了对成员函数的原型声明,在程序中就可以调用这些对象的公用成员函数。因此,在程序中包含类声明的头文件,才使得在程序中使用这些类成为可能,因此,可以认为类声明头文件是用户使用类库的公用接口
对成员函数的定义一般不放在头文件中,而另外放在一个文件中。包含成员函数定义的文件就是类的实现。注意:类声明和函数定义是分别放在两个文件中的
实际上,一个C++程序是由个部分组成的:(1)类声明头文件(后缀为.h);(2)类实现文件(后缀为.cpp),它包括类成员函数的定义;(3)类的使用文件(后缀.cpp),则主函数文件。在程序中使用#include
指令包含类声明头文件,并且在编译时把类的使用文件和类实现文件按多文件程序的规定组成一个统一的程序,经过编译连接后运行
实际上可以不必每次都对类实现文件(成员函数)重复进行编译,只须编译一次即可。把第一次编译后所形成的目标文件保存起来,以后在需要时把它直接与程序的目标文件连接即可。
类库包括两个组成部分:(1)包括类声明的头文件;(2)已经过编译的成员函数的定义,它是目标文件