第四章 类和对象
**大家想一起学习交流的可以加群,QQ:755422568。**
一、类及其实例化
(1)、定义类
类要先声明后使用,具有惟一标识符的实体,不能使用关键字修饰,不能在类声明中对数据成员使用表达式进行初始化。
1)、声明类
类是对一组性质相同对象的程序描述。
类中所有成员默认声明为private权限。
class Point{ //类名Point
private: //声明为私有访问权限
int x,y; //私有的数据成员
public: //声明为公有的访问权限
void setXY(int a,int b);
void move(int a,int b);
void display();
int getX();
int getY();
}; //声明以分号为结尾
2)、定义成员函数
类中声明的成员函数用来对数据成员进行操作,还必须在程序中实现这些成员函数。
返回类型 类名::成员函数名(参数列表){
成员函数的函数体 //内部实现
}
“::”是作用域运算符,“类名”是成员函数所属类的名字,“::”用于表明其后的成员函数是属于特定的类。
使用关键字inline将成员函数定义为内联函数
,如下:
inline int Point :: GetX(){
return x;
}
在声明类的同时,如果在类体内给出成员函数的定义,则默认内联函数。
3)、数据成员的赋值
不能在类体内给数据成员赋值。(
选择题
)
(2)、使用类的对象
只有产生类的对象,才能使用这些数据和成员函数。类不仅可以声明为对象,还可以声明为对象的引用和对象的指针。
定义类对象指针的语法如下:
类名* 对象指针名;
对象指针名 = 对象的地址;
直接初始化:类名* 对象指针名 = 对象的地址;
类对象的指针可以通过“->”运算符访问对象的成员。
//定义print函数的重载,分别使用类指针和类对象作为参数
void print(Point *a){ //类指针作为参数重载print函数
a -> display();
}
void print(Point &b){ //类引用作为参数重载print函数
b.display();
}
int main(){
//构造函数初始化
Point a(10,20);
//对象赋值
Point b;
b.setXY(10,20);
Point *p1 = &a; //声明对象a的对象指针
Point &p2 = b; //声明对象b的对象引用
print(p1); //10,20
print(p2); //10,20
return 0;
}
总结:
① 类的成员函数可以直接使用类的私有成员;
② 类外的函数不能直接使用类的私有成员;
③ 类外的函数只能通过类的对象使用该类的公有成员函数;
④ 对象的成员函数代码都是一样的,对象的区别只是属性的取值;
⑤ 在程序运行时,通过为对象分配内存来创建对象,为了节省内存,在创建对象时,只分配用于保存数据的内存,代码为每个对象共享,类中定义的代码被放在计算机内的一个公共区中供该类的所有对象共享;
⑥ 对象和引用在访问对象的成员时,使用运算符“.”,而指针则使用“->”运算符。
(3)、数据封装
1)、内存块中不但储存数据,也储存代码。
2)、内存块的结构可被用做样板产生对象的更多副本。
3)、C++对其对象的数据成员和成员函数的访问是通过访问控制权限来限制的。
二、构造函数
C++称构造函数为特殊成员函数,可自动进行对象初始化。
(1)、默认构造函数
默认的构造函数函数名与类名相同,函数体是空的,没有参数,也没有返回值
没有定义构造函数,却可以使用类直接产生对象,原因是当没有为一个类定义任何构造函数的情况下,C++编译器总要自动建立一个不带参数的构造函数。一旦程序定义自己的构造函数,系统将不再提供默认构造函数。
(2)、定义构造函数
构造函数函数名与类名相同,并在定义构造函数时不能指定返回类型。
构造函数是在产生对象的同时初始化对象的。
#include <iostream>
using namespace std;
class Point{
private:
int x,y;
public:
Point(); //使用参数列表声明不带参数的构造函数
Point(int,int); //使用参数列表声明带2个参数的构造函数
};
Point ::Point():x(0),y(0){
cout << "Initializing default" << endl;
}
Point ::Point(int a,int b):x(a),y(b){
cout << "Initializing" << a << "," << b <<endl;
}
void main(){
Point A; //使用不带参数的构造函数产生对象A
Point B(15,25); //使用带参数的构造函数产生对象B
Point C[2]; //使用不带参数的构造函数产生对象数组C
Point D[2] = {Point(5,7),Point(8,12)}; //使用带参数的构造函数产生对象数组D
/**
*输出结果:
*Initializing default
*Initializing 15,25
*Initializing default
*Initializing default
*Initializing 5,7
*Initializing 8,12
*/
}
(3)、构造函数和运算符new
运算符new用于建立生存期可控的对象。(填空题)
使用new建立的动态对象只能使用delete删除,及时释放空间。
#include <iostream>
using namespace std;
class Point{
private:
int x,y;
public:
Point(); //使用参数列表声明不带参数的构造函数
Point(int,int); //使用参数列表声明带2个参数的构造函数
};
Point ::Point():x(0),y(0){
cout << "Initializing default" << endl;
}
Point ::Point(int a,int b):x(a),y(b){
cout << "Initializing" << a << "," << b <<endl;
}
void main(){
Point *prt1 = new Point;
Point *prt2 = new Point(5,7);
delete ptr1;
delete ptr2;
/**
*输出结果:
*Initializing default
*Initializing 5,7
*/
}
(4)、构造函数的默认参数
class Point{
private:
int x,y;
public:
//声明一个默认参数的构造函数,使用默认参数的构造函数就不能再声明无参的构造函数
Point(int=0,int=0);
};
Point :: Point(int a,int b):x(a),y(b){ //定义两个参数的构造函数
cout << "初始化对象,属性x:" << a << ",属性y:" << b <<endl;
}
int main(){
Point a;
Point b(15,25);
/**
*输出结果:
*Initializing 0,0
*Initializing 15,25
*/
}
(5)、复制构造函数
引用在类中可以用在复制构造函数中,编译器建立一个默认复制构造函数,采用拷贝方式使用已有的对象来建立新对象,又称为拷贝构造函数。复制构造函数必须使用对象的引用为形式参数,为了安全起见,建议使用const限定符。
例子:Point (const Point&)
三、析构函数
在对象消失时,应使用析构函数释放由构造函数分配的内存。
(1)、定义析构函数
同构造函数一样,只是在函数前加一个符号 “ ~ ”。一个类也只能定义一个析构函数且不能指明参数,以便编译系统自动调用。
#include <iostream>
using namespace std;
class Point{
private:
int x,y;
public:
Point(); //使用参数列表声明不带参数的构造函数
Point(int,int); //使用参数列表声明带2个参数的构造函数
};
Point ::Point():x(0),y(0){
cout << "Initializing default" << endl;
}
Point ::Point(int a,int b):x(a),y(b){
cout << "Initializing" << a << "," << b <<endl;
}
Point :: ~Point(){
cout << "Destructor is active" <<endl;
}
void main(){
Point A(5,68);
cout << "Exiting main" <<endl;
/**
*输出结果:
*Initializing 5,68 //调用构造函数
*Exiting main //主程序输出
*Destructor is active //自动调用析构函数
*/
}
析构函数在对象的生存期结束时被自动调用。
全局对象和静态对象的析构函数在程序运行结束之前调用。
类的对象数组的每个元素调用一次析构函数。
全局对象数组的析构函数在程序结束之前被调用。
(2)、析构函数和运算符delete
使用delete删除一个动态对象时,首先为这个动态对象调用析构函数,然后再释放这个动态对象占用的内存,这与使用new建立动态对象的过程相反。例子如下:
#include <iostream>
using namespace std;
class Point{
private:
int x,y;
public:
Point(int =0,int =0); //声明两个参数均为默认参数
~Point(); //声明析构函数
};
Point ::Point(int a,int b):x(a),y(b){ //定义构造函数
cout << "Initializing " << a << "," << b << endl;
}
Point :: ~Point(){ //定义构造函数
cout << "Destructor is active" << endl;
}
void main(){
Point *ptr = new Point[2];
delete [ ] ptr; ```标准格式```
/**
*输出结果:
*Initializing 0,0 //调用构造函数
*Initializing 0,0 //调用构造函数
*Destructor is active //调用析构函数
*Destructor is active //调用析构函数
*/
}
派生类构造函数必须对三个成员进行初始化:(
简答题
)
1、调用基类构造函数
2、调用子对象的构造函数
3、执行派生类的构造函数
注:析构函数和构造函数相反
(3)、默认析构函数
默认析构函数如下:Point ::~Point(){ }
四、调用复制构造函数的综合实例
例子:
#include <iostream>
using namespace std;
class Point{
private:
int X,Y;
public:
Point(int a = 0,int b = 0){ //构造函数
X=a;
Y=b;
cout << "Initializing" <<endl;
}
Point(const Point &p); //复制构造函数
int GetX(){
return X;
}
int GetY(){
return Y;
}
void Show(){
cout << "X = " << X << ",Y = " << Y << endl;
}
~Point(){
cout << "delete.."<< X << ","<< Y << endl;
}
};
Point ::Point(const Point &p){ //定义复制构造函数
X = p.X;
Y = p.Y;
cout << " Copy Initializing " << endl;
}
void display(Point p){ //Point类的对象作为函数的形参
p.show();
}
void display(Point &p){ //Point类对象的引用作为函数的形参
p.show();
}
Point fun(){
Point A(101,102);
return A;
}
void main(){
Point A(42,35); //对象A
//第一次调用复制构造函数
Point B(A); //(1)用A初始化B
Point C(58,94); //对象C
cout << "called display(B)" << endl;
//第二次调用复制构造函数
display(B); //(2)对象B作为display的实参
cout << "Next..." << endl;
cout << "called display(B)" <<endl;
display(B);
cout << "call C = fun()" << endl;
//第三次调用复制构造函数
C = fun(); //(3)fun的返回值赋给对象C
cout << "called disp(C)" << endl;
disp(C);
cout << "out..." << endl;
}
输出结果:
Initializing //创建对象A
Copy Initializing //调用复制构造函数,使用对象A初始化B
Initializing //创建对象C
called display(B) //使用对象B作为Display函数的参数
Copy Initializing //需要调用复制构造函数
X = 42,Y = 35 //进入函数体执行语句
delete...42,35 //退出函数时,需要调用析构函数清除对象
Next...
called disp(B) //使用引用作为Disp函数的参数,不需要调用复制构造函数
X = 42,Y = 35 //进入函数体执行语句
call C = fun() //C使用函数fun()的返回值
Initializing //在函数fun()内创建对象A
Copy Initializing //调用复制构造函数返回值
delete...101,102 //返回值给对象C,调用析构函数清除临时对象A
delete...101,102 //退出函数体,调用析构函数清除对象A
called disp(C) //使用引用作为参数
X = 101,Y = 102 //进入函数体执行语句
out...
delete...101,102 //调用析构函数清除对象C
delete...42,35 //调用析构函数清除对象B
delete...42,35 //调用析构函数清除对象A
五、成员函数重载及默认参数
六、this指针
执行语句"A.Setxy(25,55)"时,A.x和A.y就被赋值,系统如何操作指对A进行操作了,而不是对B进行操作?
原因:当执行A.Setxy(25,55)时,成员函数Setxy(int,int)有一个隐藏参数,名为this指针。成员函数的this指针指向对象A。形式如下:
void Point ::Setxy(int a,int b,(Point *) this){
this -> x = a;
this -> y = b;
}
使用this指针,保证了每个对象可以拥有自己的数据成员,但处理这些数据成员的代码可以被所有的对象共享。
当一个成员函数被调用时,系统自动向它传递一个隐式的参数,该参数是一个指向调用该函数的对象的指针,从而使成员函数知道该对哪个对象进行操作。
this指针可以指向常量型数据,可以指向成员函数,不可以指向静态成员函数。
七、一个类的对象作为另一个类的成员
八、类和对象的性质
(1)、对象的性质
1)、同一类的对象之间可以相互赋值。
2)、可使用对象数据。
3)、可使用指向对象的指针,使用取地址运算符&将一个对象的地址置于该指针中。
4)、对象可以用作函数参数。
5)、对象作为函数参数时,可以使用对象、对象引用和对象指针。
6)、一个对象可以用做另一个类的成员。
(2)、类的性质
1)、使用类的权限
类本身的成员函数可以使用类的所有成员。
类的对象只能访问公有成员函数。
其他函数不能使用类的私有成员,也不能使用公有成员函数,只能通过类的对象使用类的公有成员函数。
虽然一个类可以包含另一个类的对象,但这个类也只能通过被包含类的对象使用成员函数,通过成员函数使用数据成员。
2)、不完全的类声明
类不是内存中的物理实体,只有当使用类产生对象时,才进行内存分配,这种对象建立的过程称为实例化。
不完全声明的类不能实例化,否则会出现编译错误。
不完全声明仅用于类和结构,存取没有完全声明的类成员,也会出现编译错误。
3)、空类
类的目的是封装代码和数据,也可以不包括任何声明。
4)、类作用域
类中的一个成员名可以使用类名和作用域运算符来显示地指定,称为成员名限定。
九、面向对象的标记图
对象结构是指对象之间的分类关系和组成关系,统称为关联关系。
对象的属性是指描述对象的数据成员。
对象属性的集合又称为对象的状态。
对象传送的消息一般由3部分组成:接收对象名、调用操作名和必要的参数。(填空题
)
消息协议是一个对象对外提供服务的规定格式说明。
十、面向对象编程的文件规范
每个类设立一个头文件和一个实现文件。
(1)、编程的规范:
1、编译指令都以#开始
2、每条指令单独占用一行
3、同一行不能有其他编译指令和C++语句
4、注释除外
(2)、宏定义
在源程序中遇到该标识符时,编译器用定义的串代替掉,该过程称为宏替换。
#define 宏名 替换正文
#define MAX(a,b) ((a) > (b)) ? \ //"\"每行的尾行要加上一个反斜杠,表示宏定义继续到下一行。
(a) : (b))
(3)、defined操作符
关键字defined不是指令,而是一个预处理操作符。
用于判定一个标识符是否已经被#defined定义,如果被定义,则为真,否则为假。