第4章 类与对象

面向对象程序的基本特点

抽象

  • 对同一类对象的共同属性和行为进行概括,形成类。

  • 先注意问题的本质及描述,其次是实现过程或细节。

  • 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。

  • 代码抽象:描述某类对象的共有的行为特征或具有的功能。

  • 抽象的实现:类。

  • 抽象实例——钟表

  • 数据抽象:

int hour,int minute,int second
  • 代码抽象:
setTime(),showTime()
class  Clock {

 public:

  void setTime(int newH, int newM, int newS);
  void showTime();

 private:

  int hour, minute, second;

};

封装

  • 将抽象出的数据、代码封装在一起,形成类。
  • 目的:增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。
  • 实现封装:类声明中的{}

例:

class  Clock {

  public: void setTime(int newH, int newM, int newS);
            void showTime();

  private: int hour, minute, second;

};

继承

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

多态

  • 多态:同一名称,不同的功能实现方式。

  • 目的:达到行为标识统一,减少程序中标识符的个数。

  • 实现:重载函数和虚函数

类和对象

类和对象的定义

  • 对象是现实中的对象在程序中的模拟。
  • 类是同一类对象的抽象,对象时类的某一特定实体。
  • 定义类的对象,才可以通过对象使用类中定义的功能。
  • 设计类就是设计类型
    • 此类型的“合法值”是什么?
    • 此类型应该有什么样的函数和操作符?
    • 新类型的对象该如何被创建和销毁?
    • 如何进行对象的初始化和赋值?
    • 对象作为函数的参数如何以值传递?
    • 谁将使用此类型的对象成员?

类定义的语法形式

class 类名称
{
   public:
   		公有成员(外部接口)
   private:
   		私有成员
   protected:
   		保护型成员
};

类内初始值

(注意:类内初始化是c++11的新特性,vs2012不支持该设定,请注意编译器版本)

  • 可以为数据成员提供一个类内初始值
  • 在创建对象时,类内初始值用于初始化数据成员
  • 没有初始值的成员将被默认初始化。
  • 类内初始值举例
class Clock {
public:
   void setTime(int newH, int newM, int newS);
   void showTime();
private:
   int hour = 0, minute = 0, second = 0;
};

类成员的访问控制

公有类型成员

  • 在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。

私有类型成员

  • 在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
  • 如果紧跟在类名称的后面声明私有成员,则关键字private可以省略。

保护类型成员

  • 与private类似,其差别表现在继承与派生时对派生类的影响不同,详见第七章。

对象定义的语法

  • 类名 对象名;

  • 例:Clock myClock;

类成员的访问权限

  • 类中成员互相访问
    直接使用成员名访问
  • 类外访问
    使用“对象名.成员名”方式访问 public 属性的成员

类的成员函数

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

内联成员函数

  • 为了提高运行时的效率,对于较简单的函数可以声明为内联形式。
  • 内联函数体中不要有复杂结构(如循环语句和switch语句)。
  • 在类中声明内联成员函数的方式:
    • 将函数体放在类的声明中。
    • 使用inline关键字。

类和对象程序实例

#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;
}

//对象的使用
int main() {
	Clock myClock;
	myClock.setTime(8, 30, 30);
	myClock.showTime();
	return 0;
}

构造函数

构造函数的作用

  • 在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的初始状态。
  • 例如:
    希望在构造一个Clock类对象时,将初试时间设为0:0:0,就可以通过构造函数来设置。

构造函数的形式

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

构造函数的调用时机

  • 在对象创建时被自动调用
  • 例如:
Clock myClock(0,0,0;

默认构造函数

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

隐含生成的构造函数

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

    • 参数列表为空,不为数据成员设置初始值;

    • 如果类内定义了成员的初始值,则使用内类定义的初始值;

    • 如果没有定义类内的初始值,则以默认方式初始化;

    • 基本类型的数据默认初始化的值是不确定的。

“=default”

  • 如果程序中已定义构造函数,默认情况下编译器就不再隐含生成默认构造函数。如果此时依然希望编译器隐含生成默认构造函数,可以使用“=default”。

  • 例如

class Clock {
public:
   Clock() =default; //指示编译器提供默认构造函数
   Clock(int newH, int newM, int newS);     //构造函数
private:
   int hour, minute, second;
};

构造函数例题

例题1

#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, int newM, int newS) {
	hour = newH;
	minute = newM;
	second = newS;
}

void Clock::showTime() {
	cout << hour << ":" << minute << ":" << second;
}

//对象的使用
int main() {
	Clock myClock(0,0,0);//自动调用构造函数
	myClock.showTime();
	return 0;
}

例题2

#include <iostream>
using namespace std;

//类的定义
class Clock {
public:
	Clock(int newH, int newM, int newS); //构造函数
	Clock();//默认构造函数
	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) {}

Clock::Clock() : hour(0), minute(0), second(0) {}

//成员函数的实现
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 myClock1(10,0,0);//自动调用构造函数
	Clock  myClock2;//调用默认构造函数
	myClock1.showTime();
	myClock2.showTime();
	return 0;
}

委托构造函数

  • 类中往往有多个构造函数,只是参数表和初始化列表不同,其初始化算法都是相同的,这时,为了避免代码重复,可以使用委托构造函数。

  • 回顾
    Clock类的两个构造函数:

Clock(int newH, int newM, int newS) : hour(newH),minute(newM),  second(newS)  {         //构造函数
}

Clock::Clock(): hour(0),minute(0),second(0) { }//默认构造函数
  • 委托构造函数
    • 委托构造函数使用类的其他构造函数执行初始化过程
    • 例如:
Clock(int newH, int newM, int newS):  hour(newH),minute(newM),  second(newS){

}

Clock(): Clock(0, 0, 0) { }

复制构造函数

复制构造函数定义

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

class 类名 {

public :
    类名(形参);//构造函数
    类名(const  类名 &对象名);//复制构造函数
    //       ...
};

类名::类( const  类名 &对象名)//复制构造函数的实现
{    函数体    }

隐含的复制构造函数

  • 如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的复制构造函数。

  • 这个构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。

“=delete”

  • 如果不希望对象被复制构造

    • C++98做法:将复制构造函数声明为private,并且不提供函数的实现。

    • C++11做法:用“=delete”指示编译器不生成默认复制构造函数。

  • 例:

class Point {   //Point 类的定义
public:
    Point(int xx=0, int yy=0) { x = xx; y = yy; }    //构造函数,内联
    Point(const Point& p) =delete;  //指示编译器不生成默认复制构造函数
private:
    int x, y; //私有数据
};

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

  • 定义一个对象时,以本类另一个对象作为初始值,发生复制构造;
  • 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
  • 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造。
    • 这种情况也可以通过移动构造避免不必要的复制(第6章介绍)
#include <iostream>
using namespace std;

class Point {	//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 << "Calling the copy constructor" << endl;
}

//形参为Point类对象的函数
void fun1(Point p) {
	cout << p.getX() << endl;
}

//返回值为Point类对象的函数
Point fun2() {
	Point a(1, 2);
	return a;
}

//主程序
int main() {
	Point a(4, 5);	//第一个对象A
	Point b = a;	//情况一,用A初始化B。第一次调用拷贝构造函数
	cout << b.getX() << endl;
	fun1(b);		//情况二,对象B作为fun1的实参。第二次调用拷贝构造函数:首先需要进行形实结合的参数传递
	b = fun2();		//情况三,函数的返回值是类对象,函数返回时,调用拷贝构造函数,构造一个临时无名对象;赋值的时候不会调用拷贝构造函数,会进入赋值运算函数
	cout << b.getX() << endl;
	return 0;
}

析构函数

  • 完成对象被删除前的一些清理工作。
  • 在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。
  • 如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数,其函数体为空。
  • 构造函数和析构函数举例
#include
using namespace std;
class Point{
public:
	Point(int xx, int yy);
	~Point();
private:
	int x,y;
};

Point::Point(int xx, int yy){
	x=xx;
	y=yy;
}
Point ::~Point(){
}

类的组合

组合的概念

  • 类中的成员是另一个类的对象。

  • 可以在已有抽象的基础上实现更复杂的抽象。

  • 类组合的构造函数设计
    原则:不仅要负责对本类中的基本类型成员数据初始化,也要对对象成员初始化。

  • 声明形式:

类名::类名(对象成员所需的形参,本类成员形参)
:对象1(参数),对象2(参数)......
{
//函数体其他语句
}
  • 构造组合类对象时的初始化次序

    • 首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序。

    • 成员对象构造函数调用顺序:按对象成员的声明顺序,先声明者先构造。

    • 初始化列表中未出现的成员对象,调用用默认构造函数(即无形参的)初始化

    • 处理完初始化列表之后,再执行构造函数的函数体。

#include <iostream>
#include <cmath>
using namespace std;

class Point {	//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 << "Calling the copy constructor of Point" << endl;
}

//类的组合
class Line {	//Line类的定义
public:	//外部接口
	Line(Point xp1, Point xp2);
	Line(Line &l);
	double getLen() { return len; }
private:	//私有数据成员
	Point p1, p2;	//Point类的对象p1,p2
	double len;
};

//组合类的构造函数
Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {
	cout << "Calling constructor of Line" << endl;
	double x = static_cast<double>(p1.getX() - p2.getX());
	double y = static_cast<double>(p1.getY() - p2.getY());
	len = sqrt(x * x + y * y);
}

//组合类的拷贝构造函数
Line::Line (Line &l): p1(l.p1), p2(l.p2) {
	cout << "Calling the copy constructor of Line" << endl;
	len = l.len;
}

//主函数
int main() {
	Point myp1(1, 1), myp2(4, 5);	//建立Point类的对象
	Line line(myp1, myp2);	//建立Line类的对象
	Line line2(line);	//利用拷贝构造函数建立一个新对象
	cout << "The length of the line is: ";
	cout << line.getLen() << endl;
	cout << "The length of the line2 is: ";
	cout << line2.getLen() << endl;
	return 0;
}

前向引用声明

  • 类应该先声明,后使用
  • 如果需要在某个类的声明之前,引用该类,则应进行前向引用声明
  • 前向引用声明只为程序引入一个标识符,但具体声明在其他地方。
  • 例:
class B;  //前向引用声明
class A {
public:
  void f(B b);
};
class B {
public:
  void g(A a);
};
  • 前向引用声明注意事项

    • 使用前向引用声明虽然可以解决一些问题,但它并不是万能的。

    • 在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。

    • 当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。

class Fred; //前向引用声明
class Barney {
   Fred x; //错误:类Fred的声明尚不完善
};
class Fred {
   Barney y;
};

UML简介

在这里插入图片描述
## 对象图在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

结构体与联合体

  • 结构体是一种特殊形态的类
  • 与类的唯一区别:类的缺省访问权限是private,结构体的缺省访问权限是public
  • 结构体存在的主要原因:与C语言保持兼容
  • 什么时候用结构体而不用类
    • 定义主要用来保存数据、而没有什么操作的类型
    • 人们习惯将结构体的数据成员设为公有,因此这时用结构体更方便

结构体的定义

struct 结构体名称 {
	 公有成员
protected:
    保护型成员
private:
     私有成员
};

结构体的初始化

如果一个结构体的全部数据成员都是公共成员,并且没有用户定义的构造函数,没有基类和虚函数(基类和虚函数将在后面的章节中介绍),这个结构体的变量可以用下面的语法形式赋初值

类型名 变量名 = { 成员数据1初值, 成员数据2初值, …… };

例题

#include <iostream>
#include <iomanip>
#include <string>
using namespace std;

struct Student {	//学生信息结构体
	int num;		//学号
	string name;	//姓名
	char sex;		//性别
	int age;		//年龄
};

int main() {
	Student stu = { 97001, "Lin Lin", 'F', 19 };
	cout << "Num:  " << stu.num << endl;
	cout << "Name: " << stu.name << endl;
	cout << "Sex:  " << stu.sex << endl;
	cout << "Age:  " << stu.age << endl;
	return 0;
}

联合体

  • 声明形式
union 联合体名称 {
    公有成员
protected:
    保护型成员
private:
    私有成员
};
  • 特点

    • 成员共用同一组内存单元
    • 任何两个成员不会同时有效
  • 联合体的内存分配

    举例说明:

union Mark {	//表示成绩的联合体
	char grade;	//等级制的成绩
	bool pass;	//只记是否通过课程的成绩
	int percent;	//百分制的成绩
};

在这里插入图片描述

  • 特点
union {
  int i;
  float f;
}

在程序中可以这样使用:
i = 10;
f = 2.2;

#include <string>
#include <iostream>
using namespace std;

class ExamInfo {
public:
	//三种构造函数,分别用等级、是否通过和百分来初始化
	ExamInfo(string name, char grade)
		: name(name), mode(GRADE), grade(grade) { }
	ExamInfo(string name, bool pass)
		: name(name), mode(PASS), pass(pass) { }
	ExamInfo(string name, int percent)
		: name(name), mode(PERCENTAGE), percent(percent) { }
	void show();

private:
	string name;	//课程名称
	enum {
		GRADE,
		PASS,
		PERCENTAGE
	} mode;			//采用何种计分方式
	union {
		char grade;		//等级制的成绩
		bool pass;		//是否通过
		int percent;	//百分制的成绩
	};
};

void ExamInfo::show() {
	cout << name << ": ";
	switch (mode) {
	case GRADE:
		cout << grade;
		break;
	case PASS:
		cout << (pass ? "PASS" : "FAIL");
		break;
	case PERCENTAGE:
		cout << percent;
		break;
	}
	cout << endl;
}

int main() {
	ExamInfo course1("English", 'B');
	ExamInfo course2("Calculus", true);
	ExamInfo course3("C++ Programming", 85);
	course1.show();
	course2.show();
	course3.show();
	return 0;
}

枚举类

枚举类定义

  • 语法形式
enum class 枚举类型名: 底层类型 {枚举值列表};

例:

enum class Type { General, Light, Medium, Heavy};
enum class Type: char { General, Light, Medium, Heavy};
enum class Category { General=1, Pistol, MachineGun, Cannon};

枚举类的优势

  • 强作用域,其作用域限制在枚举类中。

    • 例:使用Type的枚举值General:

Type::General

  • 转换限制,枚举类对象不可以与整型隐式地互相转换。

  • 可以指定底层类型
    例:

enum class Type: char { General, Light, Medium, Heavy};

枚举类举例

#include<iostream>
using namespace std;
enum class Side{ Right, Left };
enum class Thing{ Wrong, Right };  //不冲突
int main()
{
Side s = Side::Right;
Thing w = Thing::Wrong;
cout << (s == w) << endl;  //编译错误,无法直接比较不同枚举类
return 0;
}

小结

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值