类与对象
一. 知识点部分。
6.1.1、类和对象的定义
1.定义:类是面向对象程序设计,实现信息封装的基础。类是一种用户定义类型,也称为类类型。
2.定义类和对象:
在C++中,属性用数据的存储结构实现,称为类的数据成员;
方法用函数实现,称为成员函数,它们都是类的成员。
1> 定义类的时候一定不能忘了在最后的中大括号后面加上
分号。例:class<>
{
};
2> 类的一般形式(包含类的三个基本属性)
Class<类名 >
{ public: //公有段数据和成员函数;
Protect: //保护段数据成员和成员函数;
Private: //私有段数据成员和成员函数;
};
Private用来声明私有数据成员,只能在类中出现,不能在类外或派生类中出现,如果放在第一段,可以省略private。不允许在类外进行访问。
Protect可以在类中和它的派生类中可见。
而public声明的数据在类中和类外都可见,也就是说可以在类外访问里面的程序。
注:一般情况下,把public放在开始的位置。
Struct也可以用于定义类,一般情况下,定义的成员都是公有的。
3> 简单的成员函数的实现可以在类中定义,此时编译器作为内联函数处理
但是绝大部分的成员函数的实现都是在类外实现的。
形式就象是这样:
void Date::SetDate(int y,int m,int d)
成员函数的作用:
l 操作数据成员,包括访问和修改数据成员;
l 协同不同的对象操作,称为传递消息。
5>类的数据成员可以是基本类型也可以是数组、结构、类等自定义的数据类型
6>类与对象的关系:
1) 对象是类的实体。
2) 类与对象的关系,如同c++基本数据类型和该类型变量之间的关系
注意:必须在定义了类之后才能定义类的对象。
6.1.2访问对象成员
使用对象包括访问对象的数据成员和调用成员函数。类中的成员函数可以使用自身不同性质的数据成员和调用成员函数。公有成员是提供给外部的接口,即,只有公有成员在类体系外可见。
对象成员的访问形式:
l 指针访问形式:类对象指针->变量”
l 圆点访问形式:对象名.公有成员
#include<iostream>
using namespace std;
class Tclass
{ public:
Int c,y;
Void print()
{ cout<< x<<”,”<<y<<endl; };
};(千万别掉了“;”)
Int add(Tclass*ptf)
{ return(ptf->x+ptf->y); }(指针访问数据成员)
Int main()
{ Tclass test,*p=&test;
Pt->y=200; ( 用对象指针访问数据成员)
Pt->print(); (用对象指针调用成员函数)
………..
Test.print();………} (对象名.公有成员)
6.1.3指针
C++中同一类的各个对象都有自己的数据成员的储存空间,但系统不会为每个类的对象建立成员函数副本,类的成员函数可以被各个对象调用。
例如说明一个Tclass类的对象test,函数调用:test.print();
在对象test上操作。
如果说明一个指向Tclass的指针:Tclass*p;则函数同样调用p->print()
在*p上操作。
实际上c++为成员函数提供了一个叫this的隐含指针参数,它不能显式说明但可以在成员函数中显式使用。
void Tclass::print()
{ cout<<this->x<<”,”<<this->y<<endl; };
(this指针的显式使用主要在运算符重载、自引用等场合)
this指针是一个常指针,相当于:class_Type*const this 这里要注意的是this 指针一旦初始化(成员函数被调用)后,获取了对象的地址,指针值就不能修改和赋值了,来保证不会指向其他对象。
6.2构造函数和析构函数
当我们说明一个变量的时候,例如 int a;系统自动给它了一个内存空间,当一个变量的生存期结束时,系统将自动回收这个存储单元。
6.2.1简单构造函数和析构函数
1>构造函数名与类名相同,构造函数可以有任意类型的参数,但不能有返回类型,构造函数在建立类对象时自动调用。
2>析构函数名之前要有一个“~”,析构函数没有参数,也没有返回类型。析构函数在类对象作用域结束时自动调用。
3>构造函数和析构函数的原型是:
类名::类名(参数表);
类名::~类名();
构造函数和析构函数不应该定义在私有部分,因为对象必须在类说明之外创建和撤销。
4> 构造函数的作用是:
为对象分配空间;对数据成员赋初值;请求其他资源
5> 没有用户定义的构造函数时,系统提供缺省版本的构造函数,但是当用户定义了构造函数时,缺省的构造函数不起作用,必须自己再定义。
6> 构造函数可以重载。
7> 构造函数可以有任意类型的参数,但是没有返回类型。
8> 析构函数的作用:进行对象消亡时的清理工作。
9> 如果我们没有在定义析构函数,系统提供缺省版本的析构函数。
10> 如果类中没有定义构造函数,系统自动生成一个默认形式的构造函数,用于创建对象,默认构造函数形式:
类名::类名(){}
默认构造函数是一个空函数。
例如:
#include<iostream>
class Date
{public:
Date(); //无参构造函数
~Date();
Void SetDate(int x,int y,intz);
…………};
Date::Date() //构造函数
{ cout<<”Date object initialized.\n”; }
Date::~Date() //析构函数
{ cout<<”Date object destroyed.\n” }
6.2.2带参数的构造函数
带参数的构造函数可以在建立一个对象时,用指定的数据初始化对象的数据成员。
1> 我们在写构造函数的时候,必须要写一个不带参数的构造函数。
2> #include<iostream.h>
classDate {
public:
Date();
Date(int y,int m,int d); //带参数的构造函数
void showDate();
private:
int year, month, day;
};
构造函数Date::Date()与成员函数Date::SetDate()的作用和调用时机不相同。通过SetDate,可以多次重新设置数据成员的值,但构造函数不能通过对象显式调用,它仅用于创建对象和数据初始化。另外,对象的构造次序和析构次序是相反的,首先创建的对象将最后析构。
3> 利用构造函数创建对象有两种方式:
l 利用构造函数直接创建对象,它的一般形式是:
类名 对象名[(实参表)];
这里的“类名”与构造函数名相同,“实参表是为构造函数提供的实际参数。
Date *date1;
date1=new Date(1998,4,28);
以上两条语句可合写成:Date *date1=newDate(1998,4,28);
l 通过指针和new来实现。其一般语法形式为:
类名*指针变量=new类名[(实参表)];
例如:
Date*date1=new Date(1998,4,28);
就创建了对象(*date1)。
4> 如果对象是有new 运算动态创建的,delete运算会自动调用析构函数。
5> Int main()
{ Date *pd;
Pd=new Date(1982,6,6);
Pd->PrintDate();
Delete(pd); //调用析构函数
}
new动态创建的对象如果不用delete释放,那么,即使建立对象的函数执行结束,系统也不会调用析构函数,这样会导致内存泄漏。
6> 构造函数的初始化列表--------数据成员的初始化
构造函数初始化成员有两种方法:
l 使用构造函数的函数体进行初始化
class Date
{
int d, m, y;
public:
Date(int dd, int mm, int yy)
{
d=dd;
m=mm;
y=yy;
}
l 使用构造函数的初始化列表进行初始化
格式:
funname(参数列表):初始化列表
{ 函数体,可以是空函数体 }
初始化列表的形式:
成员名1(形参名1),成员名2(形参名2),成员名n(形参名n)
7> 必须使用参数初始化列表对数据成员进行初始化的几种情况
A. 数据成员为常量
B. 数据成员为引用类型
C. 数据成员为没有无参构造函数的类的对象
8> 类成员的初始化的顺序:
按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现的顺序无关。
构造函数的重载
构造函数与普通函数一样,允许重载如果Date类具有多个构造函数,创建对象时,将根参数匹配调用其中的一个。
函数的重载
l 函数重载:函数名相同,但参数不相同(类型不同,或者个数不同)的一组函数。
l 编译器根据不同参数的类型和个数产生调用匹配
l 函数重载用于处理不同数据类型的类似任务
其实就是根据参数的个数的不同,调用参数相同的函数,但是一定要注意如果两个函数同名函数的参数个数相同,容易出现二义性,要避免这种情况。
6.2.4复制构造函数
创建对象时,有时希望用一个已有的同类型对象的数据对它进行初始化。C++可以完成类对象数据的简单复制。用户自定义的复制构造函数用于完成更为复杂的操作。
1>复制构造函数要求有一个类类型的引用参数:
类名::类名(const 类名 & 引用名,….);
2>为了保证所引用对象不被修改,通常把引用参数说明为const参数。
3>复制构造函数只在创建对象时起作用
4>以下三种情况下由编译系统自动调用复制构造函数:
1.声明语句中用类的一个已知对象初始化该类的另一个对象时。
2.当对象作为一个函数实参传递给函数的形参时,需要将实参对象去初始化形参对象时,需要调用复制构造函数。
3.当对象是函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数。
5>调用复制构造函数的时机:
当说明语句建立对象时,可以调用复制构造函数进行数据初始化。另外,当函数据有类类型传值参数或者函数返回类类型值时,都需要调用复制构造函数,完成局部对象的初始化工作。
#include<iostream>
using namespace std;
class Location
{ public:
Location(int xx=0,int yy=0)
{X=xx;Y=yy;}
Location(const Location &p);
………..
Private:
Int X,Y;};
Location::Location(const Location &p) //复制构造函数
X=p.X ; //数据复制
Y=p.Y;
…….}
如果函数具有类类型传值参数,那么,调用该函数时,首先要调用复制构造函数,用实际参数对象的值初始化参数对象。
6>对象生存期结束时,需要做清理工作,比如:释放成员(指针)所占用的储存空间,析构函数完成上述工作。
7>一个类中只能有一个析构函数。
9> 当撤消对象时,编译系统会自动地调用析构函数。
10> 若没有显式定义析构函数,则系统自动生成一个默认形式的构造函数。
浅复制和深复制
浅复制:
l 在用一个对象初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。
即:对于复杂类型的数据成员只复制了存储地址而没有复制存储内容
●默认复制构造函数所进行的是简单数据复制,即浅复制
深复制:
●通过一个对象初始化另一个对象时,不仅复制了数据成员,也复制了资源的复制方式称为深复制。
●自定义复制构造函数所进行的复制是浅复制。
定义支持深复制的复制构造函数:
1. 深复制构造函数必须显式定义
2. 深复制构造函数的特点
① 定义:类名::类名([const] 类名 &对象名);
② 成员变量的处理:对复杂类型的成员变量,使用new操作符进行空间的申请,然后进行相关的复制操作
6.3类的其他成员
l 类定义中除了一般指定访问权限的成员,还可以定义各种特殊用途的成员。
常成员
静态成员
友元
6.3.1常成员
l 常数据成员是指数据成员在实例化被初始化后,其值不能改变。
l 在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。
1> 如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他函数都不能对该成员赋值
2>不能在类外修改常成员函数。
3>常对象
如果在说明对象时用const修饰,则被说明的对象为常对象。
常对象的说明形式如下:
类名 const 对象名[(参数表)];
或者
const 类名 对象名[(参数表)];
在定义常对象时必须进行初始化,而且不能被更新。
说明:
(1)C++不允许直接或间接更改常对象的数据成员。
(2)C++规定常对象只能调用它的常成员函数、静态成员函数、构造函数(具有公有访问权限)。
成员函数(静态成员函数、构造函数除外)
6.3.2静态成员
l 类成员冠以static声明时,称为静态成员。
l 静态数据成员为同类对象共享。
l 静态成员函数与静态数据成员协同操作
1>静态成员函数:
l 静态成员不属于某一个单独的对象,而是为类的所有对象所共有
l 静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员: 保证在不依赖于某个对象的情况下,访问静态数据成员静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员: 保证在不依赖于某个对象的情况下,访问静态数据成员
2>对于类的普通数据成员,每一个对象都各自拥有一个副本。(分配不同的存储空间)
对于静态数据成员,每个类只拥有一个副本 。
A. 公有访问权限的静态成员,可以通过下面的形式进行访问
类名::静态成员的名字
对象名.静态成员名字
对象指针->静态成员的名字
B. 在静态成员函数内部,直接访问
3>静态数据成员声明及初始化
在类外进行静态数据成员的声明
类型 类名::静态数据成员[=初始化值]; //必须进行声明
不能在成员初始化列表中进行初始化
如果未进行初始化,则编译器自动赋初值(默认值是0)
初始化时不能使用访问权限
4>(1)静态成员函数在类外定义时不用static前缀。
在一个类中,如果在一个函数原型或类名之前加“friend”,那么函数或类成为该类的友元。
1>友元函数不是类的成员,它通过参数访问对象的私有成员
2>友元关系是非对称的,非传递的。
3>如果在本类以外的其他地方定义了一个函数这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数,
在类体中用friend对其函数进行声明,此函数就称为该类的友元函数。
友元函数可以访问这个类中的私有成员
当类的的数据成员为另外一个已经定义的类对象时数据成员的访问形式。这种类的包含是软件重用技术。在定义一个新的类时,通过编译器把另一个类“抄写”进来,程序员不用在编写一模一样的代码,只需添加新的功能代码。
二、心得体会
深切的体会到这一章学的类与前面所学的不一样。就像上课讲的那样,思维,思路和之前的完全不一样。之前学的感觉用数学的方法就能写出来,而这一章的,就拿student那个程序,个人的成绩啥的,就只是最前面的不到三十行的程序,我想了一天才想明白,在写student的时候其实遇到了很多问题,刚开始的时候打开code blocks准备写,发现啥也写不出来,有点想法,也不会写。脑袋里有东西也倒不出来,闷得难受。后来慢慢会写点了,又出现了更多的错误,不是掉括号,就是大小写,分号掉了,每个单独的类外的函数后面一个分号也没加,小的符号掉了,编译结果成片成片的全是红色,错误数都数不过来,
有一次气的全删了,我又一点一点写的,滚雪球滚的。
这一章的内容真的很多,全记住不太可能,只能慢慢的来,我感觉。
通过这次堆知识点,感觉理解“类”又深了一点。多看看,多敲敲,多思考,理清思路,把基本的套路学会,是接下来的任务。