知识框架
类的定义
类的访问属性
->
Public ,Protected ,Private
成员函数
->
定义
对象:类的实例或实体
类与对象的关系:如同
c + +
基本数据类型和该类型的变量之间的关系
对象的定义:必须在定义了类之后才能,才可以定义类的对象
类成员的访问
类
内联函数
与
函数重载
对
构造函数
(
初始化
->
数据成员的初始化、重载、带默认值、调用
)
与析构函数
象
This
指针、返回对象的引用
复制构造函数(特点、基本工作、调用、浅复制与深复制)
类的其他成员【常成员、静态成员、友元(
friend
)】
类的包含(敲重点!!)
对象成员的初始化
对象数组
->
初始化
一、
重点知识
类与对象
1.
类的定义
1
)类是对具有相同属性和行为的一组对象的抽象与统一描述,是用户自定义的数据类型。
2
)类的定义包括行为和属性两个部分。
3
)属性以数据表示,行为通过函数实现。
4
)
c++
类定义的格式:(数据成员私有化,成员函数公有化)
Class 类名
{ Public:公有数据成员和成员函数 //(类的接口,在类中和类外可见) Protected:保护数据成员和成员函数 //(在类和它的派生类中可见) Private:私有数据成员和成员函数 //(只能在类中可见,不能在类外或派生类中使用;如果私有成员放在第一段,则可以省略关键字private) };->分号不能省略!! 各成员函数的实现
例:定义一个日期类
class Date { public: void SetDate(int y ,int m , int d); int IsLeapYear(); void PrintDate(); private: int year ,month ,day; };
此外,关键字
struct
也可以用于定义类。用
struct
定义时,若不特别指出,则所有成员都是公有的。
【注意事项】
●类的成员可以是其他类的对象,但不能以类自身的对象作为本类的成员,而类自身的指针和引用可以作为类的成员。
●
类定义
必须以分号
“
;
”
结束。
●类与结构体的区别:
没有明确指定类成员的访问权限时,
C++
结构体的成员是公有的,而类的成员是私有的。
1.
成员函数
1
)概念:
类的成员函数是实现类的行为属性的成员。
一般将成员函数声明为函数原型,在类外具体实现成员函数。
2
)定义:
返回值类型
类名
::
成员函数名
(
参数表
)
{
函数体
}
两个冒号表示作用域区分符,用于标识属于什么类的成员。
Date
类的成员函数定义在类外写为:
void Date::SetDate(int y, int m, int d) { year=y; month=m; day=d; } int Date::IsLeapYear() { return(year%4==0&&year%100!=0)||(year%400==0);} void Date::PrintDate() {cout<<year<<”.”<<month<<”.”<<day;}
当然,简单的函数可以直接在类中定义。
//… public: void SetDate(int y,int m,int d) {year=y;month=m;day=d;} //…
3)
作用
操作数据成员,包括访问和修改数据成员;
用于协同不同的对象操作,称为传递消息。
1.
对象
1
)概念:对象是类的实例或实体。二者之间的关系如同
c++
基本数据类型和该类型的变量之间的关系。
2
)定义
格式如下:
类名
对象名
1
,对象名
2
,…,对象名
n
;
【注意】必须在定义了类之后,才可以定义类的对象。
例:坐标系中点类的定义
class Point { public: void InitPoint(float PointA_x, float PointA_y); void Move(float New_x, float New_y); float GetPointx(); float GetPointy(); private: float P1_x,P1_y; }; int main() { Point p1,p2; }
4.
类成员的访问
1
)
对象成员的访问包括:
●
圆点访问形式:对象名
.
公有成员
●
指针访问形式:对象指针变量名
->
公有成员
2
)例:给坐标点赋值类
#include<iostream.h> class ptr_access { public: void setvalue(float a, float b) { x=a; y=b; } float Getx() {return x;} float Gety() {return y;} void print() { cout<<“x=”<<x<<endl; cout<<“y=”<<y<<endl; } private: //私有数据成员 float x,y; }; int main() { float a1,a2; ptr_access *ptr=new ptr_access; ptr->setvalue(2,8); //通过指针访问公有成员函数 ptr->print(); a1=(*ptr).Getx(); //通过公有成员函数访问私有数据成员 a2=(*ptr).Gety(); cout<<“a1=”<<a1<<endl; cout<<“a2=”<<a2<<endl; return 0; }
3
)
this
指针的引入
指明当前对象,在类内使用。
this
是一个隐含指针,不能显式说明,但可以在成员函数中显式使用。
this
指针的显式使用的三种情况:
①在类的非静态成员函数中返回类对象本身或对象的引用的时候,直接使用
return *this
,返回本对象的地址时,
return this
。
②当参数与成员变量名相同时,如
this->x = x
,不能写成
x = x
。
③避免对同一对象进行赋值操作,判断两个对象是否相同时,使用
this
指针。
this
指针是一个常指针,相当于:
class_Type*const this
(其中,
class_Type
是用户定义的类类型标识符。这里,
this
指针一旦初始化之后,获得了对象的地址,指针值就不能再修改和赋值,以保证不会指向其他对象。)
【内容小节】
类定义和使用应注意:
1
)在类的定义中不能对数据成员进行初始化。
2
)类的任何成员都必须指定访问属性,一般将数据成员定义为私有成员或保护成员,将成员函数定义为公有成员。
3
)类中的数据成员可以是
C++
语法规定的任意数据类型。
4
)类的成员可以是其他类的对象,称为类的组合。但不能以类自身的对象作为本类的成员。
5
)类定义必须以分号“;”结束
6
)
class
与
struct
的不同:
①
class
中,成员缺省情况是
private
。
②
struct
中,成员缺省情况是
public
。
5.
内联函数
1
)作用:减少频繁调用小子程序的运行的时间开销
2
)机制:编译器在编译时,将内联函数的调用以相应代码代替
3
)声明:
inline
函数原型
注:内联函数仅在函数原型作一次声明
适用于只有
1 ~ 5
行的小函数
不能含有复杂结构控制语句,不能递归调用
4
)例:
class Coord { public: void setCoord(int a,int b) { x=a; y=b;} int getx() { return x;} int gety() { retrun y;} private: int x,y; };
其实就是前面所说的简单的成员函数在类内定义的情况。
6.
函数重载
1
)概念:函数名相同,但参数不相同(类型不同,或者个数不同)的一组函数。
2
)调用:编译器根据不同参数的类型和个数产生调用匹配
3
)作用:函数重载用于处理不同数据类型的类似任务
4
)重载示例:
①参数个数相同,参数类型不同
#include<iostream> using namespace std ; int abs ( int a ) ; double abs ( double f ) ; int main () { cout << abs ( -5 ) << endl ; cout << abs ( -7.8 ) << endl ; } int abs ( int a ) { return a < 0 ? -a : a ; } double abs ( double f ) { return f < 0 ? -f : f ; }
②参数个数不同
#include<iostream> using namespace std ; int max ( int a , int b ) ; int max ( int a , int b, int c ) ; int main () { cout << max ( 5, 3 ) << endl ; cout << max (4, 8, 2 ) << endl ; } int max ( int a , int b ) { return a > b ? a : b ; } int max ( int a , int b, int c ) { int t ; t = max ( a , b ) ; return max ( t , c ) ; }
7.析构函数
1)概念:析构函数是用于取消对象的成员函数, 当一个对象作用域结束时,系统自动调用析构函数。
2)作用:进行对象消亡时的清理工作
3)函数名:~类名
4)性质:没有用户定义析构函数时,系统提供缺省版本的析构函数
析构函数没有参数,也没有返回类型,不能重载
5)定义格式:
类名::~类名()
{
函数语句
}
6)特点:
① 析构函数与构造函数名字相同,但它前面必须加一个波浪号(~);
② 析构函数没有参数,也没有返回值,而且不能重载。因此在一个类中只能有一个析构函数;
③ 当撤消对象时,编译系统会自动地调用析构函数。
7)默认析构函数
若没有显式定义析构函数,则系统自动生成一个默认形式的析构函数。
系统自动生成的默认构造函数形式如下:
类名::~类名(){}
一般情况下,可以不定义析构函数 。但如果类的数据成员中包含指针变量是从堆上进行存储空间分配的话,需要在析构函数中进行存储空间的回收。
8.构造函数
1)
概念:
构造函数是用于创建对象的特殊成员函数;当创建对象时,系统自动调用构造函数。
2)作用:为对象分配空间;对数据成员赋初值;请求其他资源。
3)性质:
没有用户定义的构造函数时,系统提供缺省的构造函数;
构造函数名与类名相同;
构造函数可以重载;
构造函数可以有任意类型的参数,但没有返回类型。
4)利用构造函数创建对象
①利用构造函数直接创建对象.其一般形式为:
类名 对象名[(实参表)];
这里的“类名”与构造函数名相同,“实参表”是为构造函数提供的实际参数。
②利用构造函数创建对象时,通过指针和new来实现。其一般语法形式为:
类名 *指针变量 =new 类名[(实参表)];
例如:
Date*date1=new Date(1998,4,28);
就创建了对象(*date1)。
5)构造函数的初始化列表——数据成员的初始化
a.使用构造函数的函数体进行初始化
class Date { int d, m, y; public: Date(int dd, int mm, int yy) { d=dd; m=mm; y=yy; } Date(int dd, int mm) { d=dd; m=mm; } }
b.使用构造函数的初始化列表进行初始化
格式:
funname(参数列表):初始化列表
{ 函数体,可以是空函数体 }
初始化列表的形式:
成员名1(形参名1),成员名2(形参名2),成员名n(形参名n)
class Date { int d, m, y; public: Date(int dd, int mm, int yy):d(dd),m(mm),y(yy) { } Date(int dd, int mm): d(dd),m(mm) { } }
【注意】
必须使用参数初始化列表对数据成员进行初始化的几种情况:
①数据成员为常量
②数据成员为引用类型
③数据成员为没有无参构造函数的类的对象
例:数据成员为常量且数据成员为引用类型
#include <iostream> using namespace std; class A{ public: A(int i):x(i),rx(x),pi(3.14) {} void display() {cout<<“x=”<<x<<“rx=”<<rx<<“pi=”<<pi<<endl;} private: int x,℞ const float pi; }; int main(){ A aa(10); aa.display(); return 0; }
例:数据成员为无参构造函数的类的对象
#include<iostream> using namespace std ; class A { public : A ( int x ) : a ( x ) { } int a ; } ; class B { public : B( int x, int y ) : aa( x ), b( y ) { } void out() { cout << "aa = " << aa.a << endl << "b = " << b << endl ; } private : int b ; A aa ; } ; int main () { B objB ( 3, 5 ) ; objB . out ( ) ; }
6)类成员的初始化的顺序:
按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现的顺序无关
7)默认构造函数
如果类中没有定义构造函数,系统将自动生成一个默认形式的构造函数,用于创建对象。该函数是一个
空函数
。
默认构造函数形式:
类名::类名(){}
8)带参数的构造函数
带参数的构造函数可以在建立一个对象时,用指定的数据初始化对象的数据成员
#include <iostream> using namespace std; class Date { public: Date(int, int, int); ~Date(); void SetDate(int y, int m, int d); void IsLeapYear()const; void PrintDate()const; private: int year, month, day; }; Date:: Date( int y, int m, int d) { year =y; month =m; day =d; cout<<year<<”/”<<month<<“/”<<day<<“: Date object initialized.”<<”\n”; } Date::~Date() { cout<<year<<“/”<<month<<“/”<<day <<”:Date object destroyed. “<<”\n”; } void Date:: SetDate(int y, int m, int d) { year=y; month= m; day=d; }
如果对象是由new
运算符动态创建的,delete运算会自动调用析构函数。
int mian() { Date *pd; pd=new Date(1982,6,6); pd->PrintDate(); delete(pd);//调用析构函数 }
new动态创建的对象如果不用delete释放,那么,即使建立对象的函数执行结束,系统也不会调用析构函数,这样会导致内存泄漏。
9)重载构造函数
构造函数与普通函数一样,允许重载。如果Date类具有多个构造函数,创建对象时,将根据参数匹配调用其中的一个。
class Date { public: Date(); Date(int); Date(int,int); Date(int,int,int); ~Date(); //… }; //… void f() { Date d; Date d1(2000); Date d1(2000,1); Date d1(2000,1,1); }
10)复制构造函数
①
复制构造函数用一个
已有
同类对象创建
新对象
进行数据初始化
②C++为类提供默认版本的复制构造函数
③程序员可以定义用户版本的复制构造函数
④语法形式:
类名::
类名(const 类名&引用名,…);
⑤特点:
复制构造函数名与类名相同,并且也没有返回值类型。
复制构造函数可写在类中,也可以写在类外。
复制构造函数要求有一个类类型的引用参数。
如果没有显式定义复制构造函数,系统自动生成一个默认形式的复制构造函数。
⑥
调用
编译系统自动调用的三种情况:
声明语句中用类的一个已知对象初始化该类的另一个对象时;
当对象作为一个函数实参传递给函数的形参时,需要将实参对象去初始化形参对象时,需要调用复制构造函数;
当对象是函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数;
【浅复制】只复制数据成员而没有复制资源,使两个对象同时指向了同一资源,也就是只复制存储地址而没有复制存储内容。默认复制构造函数所进行的是简单数据复制,即浅复制。
【深复制】通过一个对象初始化另一个对象时,不仅复制了数据成员,也复制了资源的复制方式称为深复制。自定义复制构造函数所进行的复制是浅复制。
9.类的其他成员
1)常成员
【常数据成员】
常成员数据是指数据成员在实例被初始化后,其值不能改变。
如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成
员进行初始化,而任何其他函数都不能对该成员赋值。
【常对象成员】
如果在说明对象时用const修饰,则被说明的对象为常对象。
常对象的说明形式如下:
类名 const
对象名[(参数表)];
或者
const
类名 对象名[(
参数表)];
在定义常对象时
必须进行初始化
,而且不能被更新。
说明:
①C++不允许直接或间接更改常对象的数据成员。
②C++规定常对象只能调用它的常成员函数、静态成员函数、构造函数(具有公有访问权限)。
【常成员函数】
在类中使用关键字const说明的函数为常成员函数。
常成员函数的说明格式如下:
类型说明符 函数名(
参数表) const;
const
是函数类型的一个组成部分,因此在函数的
实现部分
也要带关键字const。
常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数(静态成员函数、构造函数除外)
2)静态成员
类成员冠以static声明时,称为静态成员。
静态数据成员为
同类对象
共享。
静态成员函数与静态数据成员协同操作。
【静态成员函数】
静态成员
不属于
某一个
单独
的对象,而是为类的
所有对象
所共有。
静态成员函数的作用不是为了对象之间的沟通,而是为了能
处理静态数据成员
: 保证在
不依赖于
某个对象的情况下,访问静态数据成员。
静态成员函数没有this指针,只能对静态数据操作。
【静态数据成员】
静态数据成员在定义或说明时前面加
关键字
static
,如:
class A { int n; static int s; };
【静态数据成员与普通数据成员的区别】
对于类的普通数据成员,每一个对象都各自拥有一个副本。(分配不同的存储空间)
对于静态数据成员,每个类只拥有一个副本。(在静态存储区分配一个存储空间,对所有对象都是可见的)
【静态成员的访问】
类名::静态成员的名字
对象名.静态成员名字
对象指针->静态成员的名字
在静态成员函数内部,直接访问。
【静态数据成员的声明及初始化】
在
类外
进行静态数据成员的声明
类型 类名::静态数据成员[=初始化值]; //必须进行声明
不能
在成员初始化列表中进行初始化
如果未进行初始化,则编译器自动赋初值(默认值是0)
初始化时不能使用访问权限
【说明】
①
静态成员函数
在类外定义时不用static
前缀
。
②
静态成员函数主要用来访问
同一类中
的静态数据成员。
③
私有
静态成员函数
不能在类外部或用对象访问
。
④可以在建立对象之前处理静态数据成员。
⑤
编译系统将静态成员函数
限定为内部连接
(在其他文件中不可见)。
⑥
静态成员函数中是
没有this
指针
的。
⑦
静态成员函数
不访问
类中的
非
静态数据成员。如有需要,只能通过对象名(或指向对象的指针)访问该对象的非静态成员。
3)友元(不建议使用)
n
如果在本类(类A)以外的其他地方定义了一个函数(函数B)
n
这个函数可以是不属于任何类的非成员函数,
n
也可以是其他类的成员函数
n
在类体中用friend对其(函数B)进行声明,此函数就称为本类(类A)的友元函数。
n
友元函数(函数B)可以访问这个类(类A)中的私有成员
10.
类的包含
Ø
类的包含是程序设计中一种软件重用技术。即定义一个新的类时,通过编译器把另一个类
“抄”
进来。
Ø
当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式。
构造函数 ( 形参表 ) : 对象成员1(形参表 ) , … , 对象成员n (形参表 ) ;
【
对象成员的初始化
】
n
出现成员对象时,该类的构造函数要包含对象成员的初始化。如果构造函数的成员初始化列表没有对成员对象初始化时,则使用成员对象的无参(缺省)构造函数。
n
建立一个类的对象时,要先执行成员对象自己的构造函数,再执行当前类的构造函数。
例:用类包含求两点之间的距离
#include<cmath> #include<iostream> using namespace std; class Point { public: Point( int xi=0, int yi=0 ) { x = xi; y = yi; } int GetX() { return x; } int GetY() { return y; } private: int x; int y; }; class Distance { public: Distance( Point xp1, Point xp2 ); double GetDis() { return dist; } private: Point p1, p2; double dist; };
【对象数组】
所谓对象数组是指
每一数组元素
都是对象的数组。
定义一个一维对象数组的格式如下:
类名 数组名[下标表达式];
初始化:
(1
)当对象数组所属的类中包含带参的构造函数,可用初始化列表完成对象数组的初始化。
Point
p3[4]={Point(9,10),Point(11,12),Point(13,14),Point(15,16)};
(2
)当对象数组所属的类中包含无参的构造函数,也可以先定义,再给每个数组元素赋值.
int main()
{
Point p1[4];
p1[0]=Point(1,1);
}
(3
)当对象数组所属的类中包含单个参数的构造函数,可简写。
Point p2[4]={5,6,7,8};
//Point p2[4]={Point(5), Point(6), Point(7), Point(8)};
二、心得体会
比起上学期学习的c
语言,类与对象对于我来说是一种全新的开始。类与对象以一种截然不同的方式去解决实际问题,对于我来说也是一个巨大的挑战。从接触类与对象开始,我发现它的知识点繁重而又琐碎,我们需要去了解并记住。最重要的是,我们要把一个个知识点串起来,分析数据、整合数据,敲出系统从而解决实际问题。
就比如我们目前一直在写的学生信息管理系统,虽然只是一个小小的系统,但是也需要我们去思考数据类中都需要哪些数据、这些数据是否可以归为一个类、如何得到这些数据以及在学生信息操作类(studentOperation
)中应该写出什么样的成员函数去实现增、删、查、改的功能。这些都是大的方面的问题,还有很多细小的问题,比如如何给学生成绩排名以及如何记录等等,这就涉及到sort函数的使用,如果两个学生的平均成绩不相同时,就返回平均成绩大的学生,如果两个学生的平时成绩相同时,就返回学号小的学生。
由于写系统时,功能很多,要写的功能函数也就多,免不了会有error
。所以我们每增加一个功能时,就要在主函数中把该函数调通,以免到最后出现无数的错误无法修改导致前功尽弃。现在还处于初学阶段,我们要在写小系统时就养成增一调一的好习惯,达到高效率写代码的目的。