类和对象

面向对象程序设计的基本特点

抽象

  • 对同一类对象的共同属性和行为进行概括,形成类。
    • 先注意问题的本质及描述,其次是实现过程或细节。
    • 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。
    • 代码抽象:描述某类对象的共有的行为特征或具有的功能。
    • 抽象的实现:类。
  • 抽象实例——钟表
    • 数据抽象:
      int hour,int minute,int second
    • 代码抽象:
      setTime(),showTime()
1
2
3
4
5
6
7
class  Clock {
public:
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour, minute, second;
};

封装

  • 将抽象出的数据、代码封装在一起,形成类。
    • 目的:增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。
    • 实现封装:类声明中的{}
  • 例:
1
2
3
4
5
class  Clock {
public: void setTime(int newH, int newM, int newS);
void showTime();
private: int hour, minute, second;
};

继承

  • 在已有类的基础上,进行扩展形成新的类。

多态

  • 多态:同一名称,不同的功能实现方式。
  • 目的:达到行为标识统一,减少程序中标识符的个数。

类和对象的定义

类定义的语法形式

1
2
3
4
5
6
7
8
9
class 类名称
{
public:
公有成员(外部接口)
private
私有成员
protected
保护型成员
}

类内初始值

  • 可以为数据成员提供一个类内初始值
  • 在创建对象时,类内初始值用于初始化数据成员
  • 没有初始值的成员将被默认初始化

类成员访问控制

  • 公有类型成员
    • 在关键值public后面声明,它们是类与外部的借口,任何外部函数都可以访问公有类型数据和函数
  • 私有类型成员
    • 在关键字private后面声明,只允许本类中的函数访问,而外部任何函数都不能访问。
    • 如果紧跟在累名称后面声明私有成员,则关键字private可以省略
  • 保护类型成员
    • 与private类似,其差别表表现在继承与派生时对派生类的影响不同

类的成员函数

  • 在类中说明函数原型
  • 可以在类外给出函数体实现,并在函数名前使用类名加以限定
  • 也可以直接在类中给出函数体,形成内联成员函数
  • 允许声明重载函数和带默认参数值的函数

类和对象程序举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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 = 0,int newM = 0,int newS = 0);
{
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime()
{
cout << hour << ":" << minute << ":" << second << endl;
}
//对象的使用
int main()
{
Clock myClock;
myClock.setTime(8,30,30);
myClock.showTime();
return 0;
}

构造函数和析构函数

构造函数

构造函数的作用

在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的初始状态。

希望在构造一个Clock类对象时,将初始时间设为0:0:0,就可以通过构造函数来设置。


构造函数的形式

  • 函数名和类名相同
  • 不能定义返回值类型,也不能有return语句
  • 可以有形式参数也可以没有形式参数
  • 可以是内联函数
  • 可以重载
  • 可以带默认参数值

构造函数的调用时机

在对象创建时被自动调用


构造函数实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;
//类的定义
class Clock{
public:
Clock(int newH,int newM,int newS);//构造函数
void setTime(int newH = 0,int newM = 0,int newS = 0);
void showTime();
private:
int hour,minute,second;
};//这个地方有个分号
//构造函数的实现
Clock::Clock(int newH,int newM,int newS): hour(newH),minute(newM),second(newS){}
//成员函数的实现
void Clock::setTime(int newH = 0,int newM = 0,int newS = 0);
{
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime()
{
cout << hour << ":" << minute << ":" << second << endl;
}
//对象的使用
int main()
{
Clock c(0,0,0);//自动调用构造函数
c.showTime();
return 0;
}

::符号是作用域解析运算符,也就是函数定义是需要用,类名::来限制成员函数。

一件很重要的事儿是类的定义结束之后有个分号!!!(浪费大把时间)


默认构造函数

  • 调用时可以不需要实参的构造函数
    • 参数表全为空的构造函数
    • 全部参数都有默认值的构造函数
  • 下面两个都是默认构造函数,如在类中同时出现,将产生编译错误:
1
2
3
Clock();
Clock(int newH=0,int newM=0,int newS=0);
// 这两个函数均不需要初始值,所以会产生调用矛盾

隐含生成的构造函数

如果程序中未定义构造函数,编译器将在需要时自动生成一个默认构造函数

  • 参数列表为空,不为数据成员设计初始值
  • 如果类内定义了成员的初始值,则使用类内定义的初始值
  • 如果没有定义类内的初始值,则以默认方式初始化
  • 基本类型的数据默认初始化的值是不确定的

“=default”

如果类内已定义构造函数,默认情况下编译器不再隐含生成默认构造函数。
如果你坚持希望隐含生成默认构造函数,用“=default”

1
Clock() = default;//指示编译器提供默认构造函数

委托构造函数

委托构造函数(delegating constructor)使用类其他构造函数执行初始化过程

例如

1
2
3
Clock(int newH,int newM,int newS):hour(newH),minute(newM),second(newS){}
Clock():Clock(0,0,0){}
//有的编译器并不支持委托构造函数

复制构造函数

复制构造函数时一种特殊的构造函数,其形参为本类的对象引用。作用是用一个已存在的对象去初始化同类型的新对象。

用法:

1
2
3
4
5
6
7
8
9
class 类名
{
public:
类名(形参);//构造函数
类名(const 类名&对象名);//复制构造函数
// ...
};
类名::类(const 类名&对象名)//复制构造函数的实现
{函数体}

隐含的复制构造函数

  • 如果没有为类声明拷贝初始化构造函数,则比编译器自己生成一个复制构造函数。
  • 这个构造函数执行的功能是:用作为初始值对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。

“=delete”

如若不希望被复制构造

1
Point(const Point&p) = delete;

复制构造函数被调用的三种情况

  • 定义一个对象是,以被雷另一个对象作为初始值,发生复制构造
  • 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造
  • 如果函数的返回值是类的对象,函数执行完成返回主函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主函数,此时发生复制构造

析构函数

作用:完成对象被删除前的一些清理工作

  • 在对象生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间
  • 如果程序中未声明析构函数,编译器会自动生成一个默认的析构函数,函数体为空

类的组合

说白了就是类的数据成员是别的类的对象,下面用计算两点之间线段的距离的程序来说明。


线段类和点类实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <cmath>
using namespace std;

//Point类
class Point{
public:
Point(float xx,float yy); //构造函数
Point(); //默认构造函数
Point(Point &p); //复制构造函数
float getX(){return x;}
float getY(){return y;}
private:
float x,y;
};
Point::Point(float xx,float yy):x(xx),y(yy){} //构造函数的定义
Point::Point():x(0),y(0){} //默认构造函数的定义
Point::Point(Point &p) //复制构造函数的定义
{
x=p.x;
y=p.y;
cout << "Calling the copy constructor of Point" << endl;
}
//Line类,类的组合,计算两点之间的距离
class Line{
public:
Line(Point o1,Point o2); //参数为Point对象的构造函数
Line(Line &l); //复制构造函数
float getLen(); //外部接口
private:
Point p1,p2;
float len=0;
};
Line::Line(Point o1,Point o2):p1(o1),p2(o2) //构造函数是每一个成员变量都要初始化的
{
float x=p1.getX()-p2.getX();
//此处并不能够直接写 p1.x - p2.x 因为x属于私有变量
// 通过两个public成员函数可以访问,相当于提供了外部接口
float y=p1.getY()-p2.getY();
len= sqrt(x*x+y*y);
cout << "Calling the constructor of Line" << endl;
}
Line::Line(Line &l): p1(l.p1),p2(l.p2) //复制构造函数是每一个成员变量都需要进行复制的 且这个'.'只能够在类的作用域内使用。
{
len=l.len;
cout << "Calling the copy constructor of Line" << endl;
}
float Line::getLen() {return len;}
int main()
{
Point myp1(1,1),myp2(4,5);
Line line(myp1,myp2); //参数传递的时候也需要复制构造哦
Line line2(line);
cout << "The length of line is " << line.getLen() << endl;
cout << "The length of line2 is " << line2.getLen() << endl;
return 0;
}


前向引用声明

遇到两个类相互引用的情况,也称为循环依赖,简单理解就是你不能使用一个在前面完全没有出现过的标识符。

1
2
3
4
5
6
7
8
9
class B;  					//前向引用声明
class A{ //A类的定义
public: //外部接口
void f(B b); //以B类对象b为形参的成员函数
};
class B{ //B类定义
public: //外部接口
void g(A a); //以A类对象a为形参的成员函数
};