第六章 类与对象
第六章 类与对象
一、类与对象的定义与访问
面向对象程序设计:程序=对象(数据允许的操作组成的封装体)+消息
6.1.1、类和对象的定义
具有相同性质和功能的东西构成的集合,通常归为“一类”这就是类的通俗定义,例如:“人”是类的概念,为了描述人的特点,有姓名、性别、年龄、身高、体重等特征,称为“属性”。人还有各种生活技能和工作技能,称为“方法”。类是抽象的,对象是类的能动实体。
类的说明语句:
Class 类名
{
访问限制:
返回值类型 函数名(){}......
访问限制:
返回值类型 函数名(){}.....
访问限制:
数据类型 变量......
.........
};
Class<类名>
{
Public:
公有段数据成员和成员函数
Protected:
保护段数据成员和成员函数
Private:
私有段数据成员和成员函数
};(分号在类定义结束后一定要有)
PS:
(1)class是关键词,表示后续的语句是一类。
(2)访问限制方式:3个关键词:public、protected和private。
(3)对public的访问限制:允许从类外边进行访问。比如“类名::成员函数()”
“类对象.变量”“类对象指针->变量”这样的访问
(4)对private 不许从类外边进行访问,通过public成员函数还是可以访问到的。
(5)对protected:不允许从类外边进行访问,主要用于继承。
(6)类与结构体的区别:
没有明确指定类成员的访问权限时,C++结构体的成员是公有的,而类的成员是私有的。
(7)对象和类的关系:
●对象是类的实例或实体。
●类与对象的关系,如同C++基本数据类型和该类型的变量之间的关系。
Class Data
{
Public:
Void SetDate(int y,int m,int d);
int IsLeap();
Void PrintDate();
Private:
Int Year,Month,Day;
};
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;}
对象数组
Date date[100] 每一个数组成员就是一个对象
调用方式 date[i].成员函数
6.1.2访问等于对象成员
使用对象包括访问对象的数据成员和调用成员函数。类中的成员函数可以是用自身不同性质的数据成员和调用成员函数。公有成员是提供给外部的接口,即,只有公有成员在类体系外可见。对象成员的访问形式与访问结构的形式相同,运算符“.”和“->”用与访问对象成员。
例1:
#include <iotream>
Using namespace std;
Class Tclass
{
Public:
Int x,y;
Void print()
{
Cout<<x<<”,”<<y;
}
};
Int main()
{
Tclass test;
test.x=100;
Test.y=200;
test.print(); //圆点访问形式
}
例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;
}
这个例子就将两种对象访问形式展现了出来。
6.1.3 this指针
一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。
this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。全局仅有一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址。
根据以下程序来说明this指针
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 Point { private: int x,y; public: Point(int a,int b) { x=a; y=b; } void MovePoint(int a,int b) { x+=a; y+=b; } void print() { cout<<"x="<<x<<"y="<<y<<endl; } }; int main() { Point point1(10,10); point1.MovePoint(2,2); point1.print(); return 0; } |
当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。
MovePoint函数的原型应该是 void MovePoint( Point *this, int a, int b);第一个参数是指向该类对象的一个指针,我们在定义成员函数时没看见是因为这个参数在类中是隐含的。这样point1的地址传递给了this,所以在MovePoint函数中便显式的写成:
void MovePoint(int a, int b) { this->x +=a; this-> y+= b;}
即可以知道,point1调用该函数后,也就是point1的数据成员被调用并更新了值。
4. 关于this指针的一个经典回答:
当你进入一个房子后,
你可以看见桌子、椅子、地板等,
但是房子你是看不到全貌了。
对于一个类的实例来说,
你可以看到它的成员函数、成员变量,
但是实例本身呢?
this是一个指针,它时时刻刻指向你这个实例本身
5.使用this指针要注意的事项
相信大家对指针的用法已经很熟了,这里也不多说些定义性的东西了,只说一下指针使用中的注意事项吧。
一.在定义指针的时候注意连续声明多个指针时容易犯的错误,例如int * a,b;这种声明是声明了一个指向int类型变量的指针a和一个int型的变量b,这时候要清醒的记着,而不要混淆成是声明了两个int型指针。
二.要避免使用未初始化的指针。很多运行时错误都是由未初始化的指针导致的,而且这种错误又不能被编译器检查所以很难被发现。这时的解决办法就是尽量在使用指针的时候定义它,如果早定义的话一定要记得初始化,当然初始化时可以直接使用cstdlib中定义的NULL也可以直接赋值为0,这是很好的编程习惯。
三.指针赋值时一定要保证类型匹配,由于指针类型确定指针所指向对象的类型,因此初始化或赋值时必须保证类型匹配,这样才能在指针上执行相应的操作。
This指针的两种使用方式:
一种情况就是,在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;另外一种情况是当参数与成员变量名相同时使用this指针,如this->n = n (不能写成n = n)。
6.2.1构造函数和析构函数
<一>构造函数
1、构造函数是用于创建对象的特殊成员函数。
当创建对象时,系统自动调用构造函数。
没有用户定义的构造函数时,系统提供缺省版本的构造函数。
2、构造函数的作用是:
为对象分配空间;对数据成员赋初值;请求其他资源。
3、构造函数名与类名相同:类名。
4、构造函数可以重载。
5、构造函数可以有任意类型的参数,但没有返回类型。
<二>析构函数
1、析构函数是用于取消对象的成员函数.
当一个对象作用域结束时,系统自动调用析构函数。
2 、析构函数的作用是进行对象消亡时的清理工作。
3、没有用户定义析构函数时,系统提供缺省版本的析构函数。
4、析构函数名为: ~ 类名。
5、析构函数没有参数,也没有返回类型。
创建对象的两种:
(1)、 如果类中没有定义构造函数,系统将自动生成一个默认形式的构造函数,用于创建对象,默认构造函数形式:
类名::类名(){}
默认构造函数是一个空函数。
例如:
为类Date建立一个构造函数。
#include <iostream.h>
class Date {
public:
Date(); // 无参构造函数
Date(int y,int m,int d);
void showDate();
private:
int year, month, day;
};
Date::Date() // 构造函数的实现
{ year=0; month=0; day=0; }
Date::Date(int y,int m,int d)
{ year=y; month=m; day=d; }
inline void Date::showDate()
{ cout<<year<<"."<<month<<"."<<day<<endl; }
int main()
{
Date a_date,bDate(2014,3,25); //初始化对象的值 即用到了构造函数。
a_date.showDate();
b_date.showDate();
return 0;
}
(2)、通常,利用构造函数创建对象有以下两种方法:
(1) 利用构造函数直接创建对象.其一般形式为:
类名 对象名[(实参表)];
这里的“类名”与构造函数名相同,“实参表”是为构造函数提供的实际参数。
int main()
{
Date *date1;
date1=new Date(1998,4,28);
// 以上两条语句可合写成:Date *date1=new Date(1998,4,28);
cout<<"Date1 output1:"<<endl;
date1->showDate();
delete date1;
return 0;
}
(2) 利用构造函数创建对象时,通过指针和new来实现。其一般语法形式为:
类名 *指针变量 = new 类名[(实参表)];
例如:
Date *date1=new Date(1998,4,28);
就创建了对象(*date1)。
构造函数的初始化列表-数据成员的初始化
1、使用构造函数的函数体进行初始化
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;
}
}
2、使用构造函数的初始化列表进行初始化
格式:
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)
{ }
}
必须使用参数初始化列表对数据成员进行初始化的几种情况:
1、数据成员为常量
2、数据成员为引用类型
#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;
}
3、数据成员为没有无参构造函数的类的对象
#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 ( ) ;
}
类成员的初始化的顺序:按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现的顺序无关。
#include <iostream>
using namespace std;
class CMyClass{
public:
CMyClass(int x, int y):m_y(x),m_x(m_y)
{
cout<<"m_x="<<m_x<<endl;
cout<<"m_y="<<m_y<<endl;
}
private:
int m_x,m_y;
};
int main()
{
CMyClass mc(15,10);
return 0;
}
6.2.3 重载构造函数
构造函数与普通函数一样,允许重载。如果Date类具有多个构造函数,创建对象是,将更具参数匹配调用其中的一个。
像所有的C++hanshu一样,构造函数可以具有默认参数。但是,定义默认参数构造函数时注意调用时有可能产生的二义性。例如:
Class X
{
Public:
X();
X(int i=0);
.....
};
Void f()
{
X one(10); //正确
X two; //错误
}
上述程序再说明X类对象one时,调用构造函数X::X(int i=0);但在说明two的时候,系统无法判断调用的是哪一个?
因此会出现匹配二义性。
6.2.4复制构造函数
1、创建对象是,有时希望用一个已有的同类型对象的数据对他进行初始化。
这就叫复制构造函数
格式:
类名::类名(const 类名 & 引用名,........)
Class A
{
Public:
A(int);
A(const A&,int=1); //复制构造函数
}
A a(1); //创建对象a,调用 A(int);
A b(a,0); //创建对象b,调用A(const A&,int=1);
A c=b; //创建对象c,调用A(const A&,int=1);
复制构造函数的特点;
(1)、复制构造函数名与类名相同,并且也没有返回值类型。
(2)、复制构造函数可写在类中,也可以写在类外。
(3)、复制构造函数要求有一个类类型的引用参数。
(4)、如果没有显式定义复制构造函数,系统自动生成一个默认形式的复制构造函数。
2、浅复制与深复制
关于浅复制:
●在用一个对象初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。
即:对于复杂类型的数据成员只复制了存储地址而没有复制存储内容
●默认复制构造函数所进行的是简单数据复制,即浅复制
关于深复制:
●通过一个对象初始化另一个对象时,不仅复制了数据成员,也复制了资源的复制方式称为深复制。
●自定义复制构造函数所进行的复制是浅复制。
定义支持深复制的复制构造函数
(1)、深复制构造函数必须显式定义
(2)、深复制构造函数的特点
定义:类名::类名([const] 类名 &对象名);
成员变量的处理:对复杂类型的成员变量,使用new操作符进行空间的申请,然后进行相关的复制操作
6.3类的其他成员
6.3.1常成员
常数据成员是指数据成员在实例化被初始化后,其值不能改变。
在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数
1. 常数据成员
使用const说明的数据成员称为常数据成员。
如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他函数都不能对该成员赋值。
#include<iostream>
using namespace std;
class Mclass
{ public :
int k;
const int M;
Mclass() : M(5) { }
void testFun()
{ //M++;
k++;
}
} ;
int main()
{ Mclass t1, t2;
t1.k=100;
//t1.M=123; //错误,不能在类外修改常数据成员
cout<<"t1.k="<<t1.k<<'\t'<<"t1.M="<<t1.M<<endl;
t2.k=200; t2.testFun();
cout<<"&t1.M="<<&t1.M<<endl;
cout<<"t2.k="<<t2.k<<'\t'<<"t2.M="<<t2.M<<endl;
cout<<"&t2.M="<<&t2.M<<endl;
}
2、常对象
如果在说明对象时用const修饰,则被说明的对象为常对象。
常对象的说明形式如下:
类名 const 对象名[(参数表)];
或者
const 类名 对象名[(参数表)];
在定义常对象时必须进行初始化,而且不能被更新。
说明:
(1)C++不允许直接或间接更改常对象的数据成员。
(2)C++规定常对象只能调用它的常成员函数、静态成员函数、构造函数(具有公有访问权限)
#include<iostream>
using namespace std;
class T_class
{ public:
int a, b;
T_class( int i, int j ) {
a=i; b=j;
}
} ;
int main()
{ const T_class t1( 1, 2 );
T_class t2( 3, 4 );
//t1.a=5;
//t1.b=6;
t2.b=8;
t2.a=7;
cout<<"t1.a="<<t1.a<<'\t'<<"t1.b="<<t1.b<<endl;
cout<<"t2.a="<<t2.a<<'\t'<<"t2.b="<<t2.b<<endl;
}
3、常成员函数
在类中使用关键字const说明的函数为常成员函数,常成员函数的说明格式如下:
类型说明符 函数名(参数表) const;
const是函数类型的一个组成部分,因此在函数的实现部分也要带关键字const。
常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数(静态成员函数、构造函数除外)
#include<iostream>
using namespace std ;
class Simple
{ int x, y ;
public :
void setXY ( int a, int b) { x = a ; y = b ; }
void printXY() { cout << x << "," << y << endl ; }
void constFun ( ) const
{ x ++ ; y ++ ; }//非法
};
6.3.2静态成员
类成员冠以static声明时,称为静态成员。
静态数据成员为同类对象共享。
静态成员函数与静态数据成员协同操作。
静态数据成员的初始化:
在类外进行静态数据成员的声明
类型 类名::静态数据成员[=初始化值]; //必须进行声明
不能在成员初始化列表中进行初始化
如果未进行初始化,则编译器自动赋初值(默认值是0)
初始化时不能使用访问权限
静态成员函数
静态成员不属于某一个单独的对象,而是为类的所有对象所共有
静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员: 保证在不依赖于某个对象的情况下,访问静态数据成。
除静态数据成员以外,一个类还可以有静态成员函数。
静态函数仅可以访问静态成员,
或是静态成员函数或是静态数据成员。
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
静态成员函数没有this指针,只能对静态数据操作
定义静态成员函数的格式如下:
static 返回类型 静态成员函数名(参数表);
与静态数据成员类似,调用公有静态成员函数的一般格式有如下几种:
类名::静态成员函数名(实参表)
对象. 静态成员函数名(实参表)
对象指针->静态成员函数名(实参表)
#include <iostream>
using namespace std;
class Goods
{
int weight;
static int total_weight;
public:
Goods(int x):weight(x){}
void in() {total_weight=total_weight+weight; }
void out(){total_weight=total_weight-weight; }
static void display_store(){ cout<<total_weight<<endl; }
};
int Goods::total_weight=0;
int main(){
Goods g1(10),g2(20);
g1.in(); g2.in();
Goods::display_store(); //g1.display_store(); g2.display_store();
g1.out(); Goods::display_store();
return 0;
}
Ps:
(1)静态成员函数在类外定义时不用static前缀。
(2)静态成员函数主要用来访问同一类中的静态数据成员。
(3) 私有静态成员函数不能在类外部或用对象访问。
(4)可以在建立对象之前处理静态数据成员。
(5)编译系统将静态成员函数限定为内部连接(在其他文件中不可见)。
(6)静态成员函数中是没有this指针的。
(7)静态成员函数不访问类中的非静态数据成员。如有需要,只能通过对象名(或指向对象的指针)访问该对象的非静态成员。
静态成员函数来访问非静态数据成员。
#include<iostream.h>
class small_cat{
public:
small_cat(double w){weight=w;total_weight+=w;total_number++;}
static void display(small_cat &w)
{cout<<"The small_cat weights "<<w.weight<<"kg\n";}
static void total_disp()
{cout<<total_number<<"small_cat total weight ";
cout<<total_weight<<" kg "<<endl;}
private:
double weight;
static double total_weight; static double total_number;
};
double small_cat::total_weight=0;double small_cat::total_number=0;
int main()
{ small_cat w1(0.9),w2(0.8),w3(0.7);
small_cat::display(w1);small_cat::display(w2);
small_cat::display(w3);small_cat::total_disp();
return 0;}
6.3.3友元
友元:可以访问类的所有成员,包括私有成员。友元可以是一个普通函数、成员函数或者另一个类。
友元关系是非对称的,非传递的。除非特别声明,否则:
F是A的友元,但A不是F的友元;
B是A的友元,C是B的友元,但C不是A的友元。
1、友元函数
如果在本类(类A)以外的其他地方定义了一个函数(函数B)
这个函数可以是不属于任何类的非成员函数,
也可以是其他类的成员函数,
在类体中用friend对其(函数B)进行声明,此函数就称为本类(类A)的友元函数。
友元函数(函数B)可以访问这个类(类A)中的私有成员
class A
{ private:
int i ;
friend void FriendFun(A * , int) ;
public:
void MemberFun(int) ;
} ;
…
void FriendFun( A * ptr , int x )
{ ptr -> i = x ; } ;
void A:: MemberFun( int x )
{ i = x ; } ;
2、友元类
若F类是A的友元类。则F类的所有成员函数都是A类的友元函数,F类中能访问A中的所有数据。在程序中,友元类通常设计为一种对数据操作或类之间传递消息的辅助类。
6.4类的包含
类的包含是程序设计中一种软件重用技术。即定义一个新的类时,通过编译器把另一个类 “抄”进来。
当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式。
构造函数 ( 形参表 ) : 对象成员1(形参表 ) , … , 对象成员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;
};
#include<iostream>
using namespace std;
class A{
public:
A( ){ cout<<"This is A."<<endl; } //缺省构造函数
A(int i) { cout<<"This is A, and it's value = "<<i<<endl; }
};
class B{
public:
B( ) { cout<<"This is B"<<endl; } //缺省构造函数
B(int i):a2(i) { cout<<"Hello B!"<<endl;m} //采用成员初始化列表的方式,成员对象a1的形参未初始化,将成员对象a2的形参初始化为i