类和对象的特性(C++谭浩强第三版笔记)

0.0 从程序结构上看:

  基于过程的程序中:围绕功能进行的,函数是构成程序的基本部分,程序面对的是一个个函数。

  面向对象的程序中:除主函数外,其他函数基本上都出现在“类”中,只有通过类才能调用类中的函数。程序的基本构成单元是类,程序面对的是一个一个的类和对象。
  面向对象程序设计四个主要特点:抽象、封装、继承和多态性。

PS:面向过程的程序设计思路更像是数学解题,分清步骤,先做什么,再做什么。
面向对象的程序设计思路更偏向现实,分别设计细节,最终在进行组装。

1.0 对象:

万物皆可看做对象。对象是构成系统的基本单元。
若以一个班级作为一个对象,有两个要素。
一、静态特征:班级所属专业、人数、教师等(称为属性)。
二、动态特征:学习、开会、体育比赛等(称为行为或功能,在程序设计中也称为方法)。
       若想从外部控制学生活动,则可从外界对班级发信息,如打铃就是下课(称为消息)。
消息的作用:对对象的控制。


  1.任一对象都应具有两要素:属性(attribute)和行为(behavior)。
  2.对象是由一组属性和一组行为构成的。
  3.一个系统中的多个对象通过一定渠道相互联系,要使某一个对象实现某一种行为(即操作),应当向它传送相应的消息。
  4.每个对象都是由数据(体现了属性)和函数(即操作码,对数据进行操作)。

几个名词:
方法类的成员函数在面向对象程序理论中被称为“方法”,指的是对数据的操作
消息:其实就是一个命令,由程序语句来实现。

2.0 面向对象程序设计 四个主要特点

2.1 抽象

  抽象的作用:表示同一类事物的本质
  类是对象的抽象,而对象则是类的特例,即类的具体表现形式。

2.2 封装与信息隐蔽

  可以对一个对象进行封装处理,把它的一部分属性和功能对外界屏蔽,也就是说从外界是看不到的、甚至是不可知的。

封装性(encapsulate):
  一、是将有关的数据和操作代码封装在一个对象中,形成一个基本单位,各个对象之间相对独立,互不干扰。
  二、将对象中某些部分对外隐蔽,即隐蔽其内部细节,只留下少量接口,以便与外界联系,接收外界的消息。(信息隐蔽,有利于数据安全,防止无关的人了解和修改数据)。

2.3继承与重用

  若每设计一个事物都要从头写不是很麻烦,所以一般不会全部从头设计,而是选择在有一个事物的基础上,在继续增加一些新功能。这就是面向对象程序设计中的继承机制。
  如:大家都清楚了马的特征,若要叙述“白马”的特征,显然无需从头介绍,只需说:白马是白色的马即可。这边的“白马”(子类或派生类)继承了“马”(父类)的基本特征,又增加了新的特征(颜色)。
而这就是常说的“软件重用”(software reusability)的思想。

2.4多态性

  多态性(polymorphism):由继承而产生的不同的派生类,其对象对同一消息会作出不同的响应。
  若几个相似而不完全相同的对象,有时人们要求向它们发出同一个消息时,它们的反应各不相同,分别执行不同的操作。这种情况就是多态现象
  如:甲乙丙三个同学,他们都是高二年级,具有基本相同的属性和行为,在同时听到上课铃声时,会分别走向三个不同的教室。
面向对象程序设计的特点:
把数据和有关操作封装成一个对象。

3.0 程序设计者的任务包括:

一、设计所需的各种类和对象(决定把那些数据和操作封装在一起)。
二、考虑怎样向有关对象发送消息,以完成所需的任务。

类是C++的灵魂,基于对象的程序是以类和对象为基础的,程序的操作是围绕对象进行的。

面向对象的程序设计中对象:程序中的每一组数据都是有特定用途的,是为某种操作而准备的,也就是说,一组数据是与一组操作相对应的。因此人们把相关的数据和操作放在一起,形成一个整体,与外界相对分隔。

程序设计的关键:设计好每一个对象以及确定向这些对象发出命令,使各对象完成相应的操作。

面向对象的软件工程:
1.面向对象分析(Object Oriented Analysis,简称:OOA)
2.面向对象设计(Object Oriented Design,简称:OOD)
3.面向对象编程(Object Oriented Programming,简称:OOP)
4.面向对象测试(Object Oriented Test,简称:OOT)
5.面向对象维护(Object Oriented Soft Maintenance,简称:OOSM)

4.0 类

4.1类的定义

类(class):代表了某一批对象的共性和特征。
  类是对象的抽象,而对象是类的具体实例
  对象是类的一个变量类是对象的模版,是用来定义对象的一种抽象类型。

struct Student      /声明了一个名为Student的结构体类型,可以看到它只包括数据(变量),没有包括操作
{int num;
 char name[20];
 char sex;
 }; 
 Student stud1,stud2;         /定义了两个结构体变量stud1,stud2
class Student            /class开头,类名Student,把数据和操作封装在一起
{int num;                /{类体 class bodychar name[20];
 char sex;
 void display()         /成员函数
    {cout<<"num :"<<num<<endl;
    cout<<"name :"<<name<<endl;
    cout<<"sex :"<<sex<<endl;
    }
 };
 Student stud1,stud2;    /定义了两个Student类的对象stud1和stud2

  现在封装在类对象stud1和stud2的成员都对外界隐蔽,外界不能调用。只有本对象中的函数display可以引用本对象中的数据。也就是说,在类外不能直接调用类中的成员
  这就相当安全了,但在程序中怎样才能执行对象stud1和display函数呢?现在是无法启动,因为缺少对外界的接口,外界不能调用类中的成员函数,完全与外界隔绝了。很显然这样的类是毫无实际作用的。
  因此,一般是把数据隐蔽起来,而把成员函数作为对外界的接口。譬如:可以从外界发出一个命令,通知对象stud1执行其中display函数,输出某一学生的有关数据。
  注意:一个类应当至少有一个公用的成员函数,作为对外的接口,否则就无法对对象进行任何操作。

class Student     					 /声明类Student
{private:     						 /声明以下部分为私有的
  int num;  
  char name[20];
  char sex;
 public:
  void display()       				 /成员函数
     {cout<<"num :"<<num<<endl;
     cout<<"name :"<<name<<endl;
     cout<<"sex :"<<sex<<endl;
     }
 };
 Student stud1,stud2;                /定义了两个Student类的对象stud1和stud2

现在声明了display函数是公用的,外界就可以调用该函数。
====》类的声明本身并不能修改程序的行为,必须使用类,也就是像函数那样去调用。也就是将类变为对象(类的实例),并通过对象访问成员方法和属性。

若类定义中没有private(私有的)和public(公用的),则系统就默认为私有的。

4.2 类的声明的一般形式

对类类型的声明的一般形式为:
  class 类型
  {private:
     私有的数据和成员函数;
   public:
     公用的数据和成员函数l
  }

4.3 成员访问限定符:

private(私有的):只能被本类中的成员引用,类外不能调用(友元类除外)
public(公用的):可被本类中的成员函数所引用,也可被类的作用域内的其他函数引用(外界可调用)
protected(受保护的):不能被类外访问(但可被派生类的成员函数访问)

访问属性 属性 对象内部 对象外部
public 公有 可访问 可访问
protected 保护 可访问 不可访问
private 私有 可访问 不可访问
====》struct 中所有的行为和属性都是public的(默认)。

4.4 定义对象的三种方法

一、先声明类类型,然后再定义对象:(一般采用该方法)
定义对象的两种形式:
(1)class 类名 对象名;
(2)类名 对象名;
二、在声明类的同时定义对象:(跟结构体类似)
三、不出现类名,直接定义对象:

4.5 在类外定义成员函数:

“::”:作用域限定符(或称作用域运算符):用它声明函数是属于哪个类的

void Student::display()               /在类外定义display类函数(注意必须加上类名)
	 {cout<<"num :"<<num<<endl;
     cout<<"name :"<<name<<endl;
     cout<<"sex :"<<sex<<endl;
     }                               /注意:类函数必须现在类体中作原型声明,然后在类外定义
                                     /也就是说类体的位置应在函数定义之前,否则编译时会出错

4.6 内置成员函数(inline):可省略inline

  注:若成员函数不在类体内定义,而在类体外定义,系统并不把它默认为内置函数,调用这些成员函数的过程和调用一般函数的过程是相同的。
  在函数的声明时或函数的定义时做inline声明均可(二者选其一)。
  若类外定义inline函数,则必须将类定义的成员函数的定义都放在同一个头文件(或写在同一个源文件中),否则编译时无法进行置换(将函数代码的拷贝嵌入到函数调用点)。

4.7 成员函数的存储方式:

  同一类的不同对象中的数据成员的值一般是不相同的,而不同对象的函数的代码是相同的,不论调用哪一个对象的函数的代码,其实调用的都是相同内容的代码。故有一个公用函数代码区域
  一个对象所占的空间大小只取决于该对象中数据成员所占的空间,而与成员函数无关。
  提问:不同的对象使用的是同一个函数代码段,它怎么能够分别对不同对象中的数据进行操作呢?
  回答:C++为此专门设立了一个名为this的指针,用来指向不同的对象。
  说明:
  (1)不论成员函数在类内定义还是在类外定义,成员函数的代码段的存储方式是相同的,都不占用对象的存储空间。
  (2)不要将成员函数的这种存储方式和inline函数的概念混淆。

4.8访问对象中的成员可以有三种方法:

方法一:通过对象名和成员运算符访问对象中的成员;
方法二:通过指向对象的指针访问对象中的成员;
方法三:通过对象的引用访问对象中的成员。

  一般形式:
    对象名.成员名          “.”是成员运算符

====》①普通定义的类,使用成员运算符(.)。
   ②new出来的实例,使用指针运算符(->)。

5.0 接口与实现

当接口与实现(对数据操作)分离时,只要类的接口没有改变,对私有实现的修改不会引起程序的其他部分的修改。
声明头文件是用户使用类库的公用接口
包含成员函数定义的文件就是类的实现
类声明和函数定义是分别放在两个文件中。

6.0 一个C++程序是由三部分组成:

一、类声明头文件(后缀.h);(系统提供的只包括成员函数申明,不包括定义)
二、类实现文件(后缀.cpp),包含类成员函数的定义;
三、类的使用文件(后缀.cpp),即主文件。
类库包括两个组成部分:
(1)包括类声明的头文件。
(2)已经过编译的成员函数的定义,它是目标文件。

7.0 构造函数:

  作用:处理对象的初始化
  是一种特殊的成员函数,与其他成员函数不同,无需用户来调用它,而是在建立对象时自动执行
注意:
  构造函数的名字必须与类名同名,而不能任意命名,以便编译系统能识别它并把它作为构造函数处理。不具有任何类型,不返回任何值一经使用,默认将不复存在

对象的初始化
  就是对数据成员赋初值,对象代表一个实体,每个对象都有它确定的属性。
注意:
  不要试图在声明类时对数据成员初始化。因为类并不是一个实体,而是一种抽象类型,并不占存储空间,显然无处容纳数据。

7.1 情景一:所有成员为公用

若类中所有成员为公用,则定义对象时可对数据成员进行初始化。
若数据成员有私有的,或类中有private或protected的数据成员则不可用此方法。

class Time
{public:
	hour; minute;sec;
};
Time t1 ={14,56,30};         				 /将t1初始化为145630

7.2 情景二:无参构造函数

使用构造函数(一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行)来处理对象的初始化。
注意:构造函数名字需与类名同名,不可任意命名,不具有任何类型,不返回任何值。

class Time
{public:
	Time()					   /也可以在类中声明,在类外定义
	{hour=0; minute=0;sec=0;}  /没有类型,只是为了初始化,故不需要在定义构造函数时进行声明类型。
 private:
    int hour; int minute; int sec;
};
int main()
{
	Time t1; 					/建立对象t1,同时调用构造函数t1.Time()
	Time t2  = t1; 			    /建立对象t2,并用一个t1初始化t2   用一个类对象初始化另一个类对象
}

  这边不要误认为是在声明类时直接对程序数据赋初值(这是不允许的),赋值语句写在构造函数的函数体中,只有在调用构造函数时才执行这些赋值语句。
  若用户没有定义构造函数,系统会自动生成一个,只是该构造函数的函数体是空的,没有参数,不执行初始化。

7.3 情景三:带参构造函数

  带参数的构造函数。在调用不同对象的构造函数时,从外面将不同的数据传递给构造函数,以实现不同的初始化。
构造函数首部的一般形式为
构造函数名(类型1 形参1 ,类型2 形参2 ,…)

定义对象的一般形式为
类名 对象名(实参1,实参2,…)

class Box
{public:
	Box(int,int,int){  }
 private:
	int hight;int width;int length;
}
Box::Box(int h,int w,int len){height=h;width=w;length=len;} 
int main()
{Box box1(12,25,30)
}

7.4 情景四:参数初始化表

参数初始化表来实现对数据成员的初始化。
这种方法不再函数体内对数据成员初始化,而是在函数首部实现。

Box::Box(int h,int w,int len):height(h),width(w),length(len) { }

在上例函数首部末尾加冒号,然后列出参数初始化表
表示:形参h的值初始化数据成员height…
若数据成员是数组,则应当在构造函数的函数体中用语句对其赋值,而不能在参数初始化表中对其初始化。

7.5 情景五:拷贝构造函数(赋值构造函数)

由已存在的对象,创建新的对象,也就是说新对象,不由构造器来构造,而是由拷贝构造器来完成。拷贝构造器也是固定的

class Box
{public:
	Box(const Box &another) { }           /Box为类名,another为已存在的对象
 private:
	int hight;int width;int length;
}

应用场景:

7.5.1 场景一:
int main(){
	Test t1(10) ;
	//这里第一个应用场景
	Test t2 = t1;  						/用对象t1初始化对象t2
}
7.5.2 场景二:
int main(){
	Test t1(10) ;
	//这里第二个应用场景
	Test t2(t1);		  				/用对象t1初始化对象t2
}
7.5.3 场景三:
#include<iostream>
class Location{
	public:
		Location(int xx = 0;int yy = 0)				/带参构造函数
		{X=xx;   Y==yy;   cout<<"Constructor Object"<<endl;}
		Location(const Lacation &obj)				/拷贝构造函数
		{X=obj.x; Y=obj.Y;  cout<<"Copy Constructor"<<endl;}
		~Location()									/析构函数
		{cout<<X<<","<<Y<<"Object destroyed."<<endl;}
		int GetX() {  return X; }
	private:
		int X,Y;
};
void func(Location p)							/会执行p=b的操作,p会调用拷贝构造函数进行初始化
{   cout<<"func begin"<<endl;   cout<<p.GetX()<<endl;   cout<<"func end"<<endl;   }

void test()
{ 	Location a(1,2);							/对象a调用 带参构造函数 进行初始化
	Location b=a;								/对象b调用 拷贝构造函数 进行初始化
	//注意区分下面这种情况:若改成下面这样,则不会调用拷贝构造函数。
	//Location b;
	//b=a;
	cout<<"-----"<<endl;
	func(b);									/b实参去初始化形参p,会调用拷贝构造函数。
}	
int main(){
	test() ;
	return 0;
}

====》其实就是:当有多个构造函数时,按照对应格式进行匹配。

7.6 构造函数重载:

系统怎么辨别调用哪个构造函数呢?
编译系统是根据函数调用的形式去确定对应哪个构造函数。

注意:
  构造函数是不能被用户显式调用的。
  一个类只能有一个默认构造函数,也就说,可以不用参数而调用的构造函数,一个类只能有一个。

使用默认参数的构造函数
应该在什么地方指定构造函数的默认参数?
应在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。
原因:
  因为类声明是放在头文件中的,它是类的对外接口,用户是可以看到的。
  函数的定义是类的实现细节,用户往往是看不到的。
  在声明构造函数时指定默认参数值,使用户知道在建立对象时怎样使用默认参数。

8.0析构函数(destructor):

  作用与构造函数相反,它的名字是类名前加上“~”。
当对象的生命周期结束时,会自动执行析构函数,具体为以下四种情况:
情况一
  若在一定函数中定义了一个对象(假设是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
情况二
  静态局部对象在函数调用结束时对象并不释放,因此不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
情况三
  若定义了一个全局的对象,则在程序的流程离开其作用域时(如main函数结束或调动exit函数)时,调用该全局的对象的析构函数。
情况四
  若用new运算符动态地建立了一个对象,当delete运算符释放该对象时,先调用该对象的析构函数。

析构函数的作用:
  并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。
  用来执行用户希望在最后一次使用对象之后所执行的任何操作。
注意:
  不返回任何值,没有函数类型,没有函数参数(故不能被重载)。
  一个类可有多个构造函数,但只能有一个析构函数。

包含构造函数和析构函数的C++程序

class Student
{public:
	Student(int n,string nam,char s) //定义有参数的构造函数
	{num = n;name = nam;sex = s; cout<<"Constructor called."<<endl;}
	~Student()   //定义析构函数
	{cout<<"Destructor called."<<endl;}
	void display()
	{cout<<"num:"<<num<<endl;cout<<"name:"<<name<<endl;cout<<"sex:"<<sex<<endl;}
 private:
    int num; char name[10];char sex;
};
int main()
{Student stud1(10010,"Wang_li",'f'); std1.display();return 0;}

运行结果:

Constructor called.
num:10010
name:Wang_li
sex:f

Destructor called.

析构函数相当于一个栈,先进后出。先构造的后析构,后构造的先析构。(这是对同类存储类别的对象而言的)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值