+参考书: 郑莉(清华大学)C++ 语言程序设计 (第3版)
文章目录
3 类与对象
3.1 面向对象编程的基本特点
抽象:
封装:
继承:
多态:
3.2 类与对象
构建、封装独立的功能模块是提高程序开发速度、提高程序效率的有效手段;函数是对运算的封装,库是对函数的封装。
类是对逻辑上相关的函数和数据的封装,不仅包括函数,也包括函数要处理的数据类型;是数据抽象
和行为抽象
而这结合的。
3.2.1 C语言中的类
实际上C语言
中就已经有类和对象了,但是C中的类是不开放的,并且不提供自定义类的接口。类的全称是类型,而我们已经接触过int
, bool
, double
, char
,或者自定义的结构体;当我们使用这些类声明一个对象时,例如
int i = 1;
bool a = false;
double pi = 3.14;
char ch = 'c';
struct student{
char name[20] = 'wuyao';
long int id = 1150250107;
char gender = 'm';
char email[32] = 'wuyao1997@qq.com';
}wuyao;
(类型的实例就是对象!)
我们不仅定义了这些变量的值,并且还隐式的定义了对于这些对象可以采用的操作,例如整型变量可以加减乘除,字符(数组)变量就不能加减,只能进行连接、拷贝等操作。
3.3.2 C++中类
3.2.2.1 定义
创建类
语法形式为
class classname
{
public:
外部接口;
protected:
保护型成员;
private:
私有成员;
};
以时钟
为例,可初步的抽象为
- 数据抽象:时
int hour
、分int minute
、秒int second
- 行为抽象:显示时间
setTime(int newh, int newm, int news);
,现实时间showTime();
当然这只是初步的抽象,可以进一步细化数据抽象和行为抽象,因为一般不允许时间为42时99分-87秒
这种格式,用户也希望时钟可以现实多种格式的时间,或者有定时功能等等,但这里暂不讨论;于是时钟类可以写为
class Clock
{
public:
void setTime(int newh, int newm, int news);
void showTime();
private:
int hour, minute, second;
};
类的成员函数的函数原型必须在类中声明,但函数体可以在类外定义;但是为了区别普通函数,类的成员函数的定义需要类名来限定和标识,如
void Clock::setTime(int newh, int newm, int news)
{
hour = newh;
minute = newm;
second = news;
}
void Clock::showTime()
{
cout << hour << ":" << minute << ":" << second << endl;
}
创建对象
使用类名来实例一个对象,形如
Clock myclock; // 类型名 类型变量名
3.2.2.2 访问控制
类的成员类型上分为:
- 数据成员:描述对象的属性
- 函数成员:描述对象的行为
以时钟为例,一般用户只需要能显示时间即可,如果有必要可以设定下时间;而时间的显示样式一般人就没有必要关心了;因此需要设定不同的访问权限,避免一般用户在不了解函数内部情况时,误操作导致程序崩溃。
访问控制属性:
- 公有类型
public
- 私有类型
private
- 保护类型
protected
公有类型成员定义了类的外部接口,使用public
关键字声明,在类外仅能访问类的共有成员。
私有类型成员只能被本类的成员函数访问,来自类外部的任何访问都是非法的。若不声明,默认为私有类型函数。
保护类型成员的性质和私有成员的性质相似,差别在于继承过程中对产生的新类影响不同。
访问类的成员:
- 成员数据:
- 外部访问格式:
对象名.变量名
,例如myclock.hour
(这里当然是非法的,不能访问类的私有成员数据) - 内部访问格式:直接使用变量名,例如
hour
- 外部访问格式:
- 成员函数:
- 外部访问格式:
对象名.函数名(参数列表)
,形如myclock.showTime()
- 内部访问格式:直接使用函数名,例如
showTIme()
- 外部访问格式:
3.2.2.3 成员函数
函数的具体实现一般放在类定义的外部,并用类名作为命名空间,这部分前面以及举例。
带默认参数的成员函数
规则与普通函数相同,形如
void Clock::setTime(int newh = 0, int newm = 0, int news = 0)
{
hour = newh;
minute = newm;
second = news;
}
内联成员函数
-
隐式声明:将函数体直接放在类体内,形如
class Clock { public: void setTime(int newh, int newm, int news); void showTime() { cout << hour << ":" << minute << ":" << second << endl; } private: int hour, minute, second; };
-
显示声明:使用
inline
显示声明,位置在函数返回值类型之前,形如inline void Clock::showTime() { cout << hour << ":" << minute << ":" << second << endl; }
3.2.2.4 时钟类的完整程序
//3_2.cpp
#include <iostream>
using namespace std;
class Clock
{
public:
void setTime(int newh = 0, int newm = 0, int news = 0);
void showTime();
private:
int hour, minute, second;
};
void Clock::setTime(int newh, int newm, int news)
{
hour = newh;
minute = newm;
second = news;
}
void Clock::showTime()
{
cout << hour << ":" << minute << ":" << second << endl;
}
int main()
{
Clock myclock;
cout << " First time set and output : " << endl;
myclock.setTime();
myclock.showTime();
cout << " Second time set and output : " << endl;
myclock.setTime(8,30,30);
myclock.showTime();
return 0;
}
编译后运行结果
First time set and output :
0:0:0
Second time set and output :
8:30:30
3.3 构造函数与析构函数
在使用类来声明对象时,可以对其数据成员赋值,成为对象的初始化。在特定对象使用结束时,需要进行一些内存清理工作。这两项工作需要两个特殊的成员函数来完成,分别为构造函数和析构函数。
3.3.1 构造函数
构造函数是用来初始化对象的,那么初始化一个基本对象需要:
- 分配内存单元
- 写入变量的初始值
但是程序员构建的类可能千变万化,编译器有时不能自动生成代码来的初始化,需要程序员来编写初始化程序,即构造函数。
- 构造函数的作用是初始化一个对象;
- 若程序员不编写构造函数时,编译器会自动生成构造函数,一般情况是可以正常行使初始化的功能;
- 构造函数在对象被创建时会被自动调用;
- 构造函数的函数名与类名相同,并且没有返回值;
- 一般是公有函数;
例如时钟类中写一个构造函数,其声明为Clock (int newh, int newm, int news);
,函数实现为
Clock::Clock(int newh, int newm, int news)
{
hour = newh;
minute = newm;
second = news;
}
此时可以这样创建一个时钟对象
Clock myclock(0, 0, 0);
作为类的成员函数,构造函数也可以有默认参数,可以重载,可以为内联函数;形如
class Clock
{
public:
Clock(int newh, int newm, int news);
Clock()
{ hour = 0; minute = 0; second = 0;}
void setTime(int newh = 0, int newm = 0, int news = 0);
void showTime();
private:
int hour, minute, second;
};
3.3.2 拷贝构造函数
可以认为拷贝构造函数是构造函数的一种重载形式。
现实生活中存在很多复制
操作,C++中复制一个对象时,可以新建一个对象,然后将需要复制对象的数据成员的值一一提取出来,一一赋值给新的对象;但是这样太过于繁琐;于是创造了一个拷贝构造函数
的概念,其作用就是使用一个已经存在的对象,去初始化同类的一个新对象。本质上是复制操作符的函数化表现。
- 拷贝构造函数的形参是对本类对象的引用
- 若程序员不编写,系统会默认生成一个拷贝构造函数,将被拷贝对象的每个数据成员的值都复制到新建立的对象中,即克隆
声明格式为
类名(类名 & 对象名)
形如
class Ponit
{
public:
Ponit(int xx=0, int yy=0) {X=xx;Y=yy;} //构造函数
Ponit(Ponit &p) //拷贝构造函数
int GetX() {return X;} //内联函数
int GetX() {return X;}
private:
int X,Y;
};
实现格式为
类名::类名 (类名 & 对象名)
{
函数体
}
形如
Ponit::Ponit(Ponit &p)
{
X=p.X;
Y=p.Y;
cout << "拷贝构造函数被调用" << endl;
}
构造函数在以下情况被调用
-
当用一个类初始化另一个类时
int main() { Ponit A(1,2); Ponit B(A); cout << B.GetX() << endl; return 0; }
-
函数的形参是类的对象时,调用函数时,进行参数传递,实际上是拷贝一份
类
传递到被调函数中void f(Ponit p) { cout << p.GetX() << endl; } int main() { Ponit A(1,2); f(A); }
-
函数的返回值是类的对象时,从被调函数返回时也会进行拷贝传递
Ponit g()
{
Ponit A(1,2);
return A;
}
int main()
{
Ponit B;
B = g();
}
实例
//3_3b.cpp
#include <iostream>
using namespace std;
class Ponit
{
public:
Ponit(int xx=0, int yy=0) {X=xx;Y=yy;} //构造函数
Ponit(Ponit &p) ; //拷贝构造函数
int GetX() {return X;} //内联函数
int GetY() {return Y;}
private:
int X,Y;
};
Ponit::Ponit(Ponit &p)
{
X=p.X;
Y=p.Y;
cout << "拷贝构造函数被调用" << endl;
}
void f(Ponit p)
{
cout << p.GetX() << endl;
}
Ponit g()
{
Ponit A(1,2);
return A;
}
int main()
{
Ponit A(4,5);
Ponit B(A);
cout << B.GetX() << endl;
f(B);
B=g();
cout << B.GetX() << endl;
return 0;
}
编译执行