《面向对象程序设计C++》课件1第3章 类和对象_第1页
《面向对象程序设计C++》课件1第3章 类和对象_第2页
《面向对象程序设计C++》课件1第3章 类和对象_第3页
《面向对象程序设计C++》课件1第3章 类和对象_第4页
《面向对象程序设计C++》课件1第3章 类和对象_第5页
已阅读5页,还剩146页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

2023/11/121第3章类和对象本章学习要点类和对象的概念及其关系类的声明和对象的定义及使用构造函数与析构函数的作用及使用对象数组√对象与指针√对象与const对象的动态创建和释放对象的复制和赋值√对象的移动向函数传递对象字面值常量类图书馆图书借阅管理系统中类的声明和对象的定义2023/11/122第3章类和对象本章学习目标正确理解类、对象及封装的概念熟练掌握类的声明和对象的定义及使用理解构造函数与析构函数的作用及使用掌握对象与指针及const的特点及使用理解并掌握对象的复制和赋值,对象的移动掌握对象的动态创建和释放理解对象作为函数的参数传递的实质掌握基本的基于对象的程序设计与实现2023/11/123§3.1类的声明和对象的定义本节主要是关于类和对象的概念、类的声明格式、对象的定义方法2023/11/1243.1.1类和对象的概念及关系对象封装了数据及在这些数据之上的操作的封装体类对具有相同属性和操作的一组对象的抽象描述类和对象的关系类是对象的抽象;对象是对类的实例化2023/11/125类和对象的关系3.1.1类和对象的概念及关系2023/11/1263.1.2

类的声明class类名{public:

公用成员…protected:

受保护成员…private:

私有成员…};声明类的一般形式:声明类的关键字合法标识符公用成员限定符受保护成员限定符私有成员限定符类结束要加分号2023/11/1273.1.2

类的声明【例3-1】声明一个学生类,要求包括学生的学号、姓名、性别等信息,并且能够显示学生的信息。2023/11/1283.1.2

类的声明classStudent

//声明学生类Student{public: //以下部分为公用成员函数

voidsetInfo(){cout<<"Pleaseenterstudent’sNo,name,sex,age:"; cin>>stuNo>>stuName>>stuSex>>stuAge;}voidshow(){ cout<<"No.:"<<stuNo<<endl; cout<<"Name:"<<stuName<<endl; cout<<"Sex:"<<stuSex<<endl; cout<<"Age:"<<stuAge<<endl;}private: //以下部分为学生类Student的私有数据成员 stringstuNo; //学号stringstuName; //姓名 stringstuSex="男"; //性别intstuAge=0; //年龄};//类声明结束,此处必须有分号2023/11/1293.1.2

类的声明1.类的成员分为数据成员和成员函数2.成员的可访问性可分为三类:私有的(private)、受保护的(protected)、公用的(public)3.由访问限定符限定它后面的成员的访问属性,直到出现下一个访问限定符或者类的结束为止4.在声明类时,这三种访问属性的成员的声明次序是任意的,并且在一个类的声明中不一定这三种访问属性全部都出现,可能只出现两种或一种。5.某个成员访问限定符在一个类的声明中也可以出现多次。注意2023/11/12103.1.3

对象的定义声明类之后,再定义对象如:Studentzhang,wang;或:class

Studentzhang,wang;在声明类的同时定义对象如:class

Student{…}zhang,wang;不出现类名,直接定义对象如:class{…}zhang,wang;声明类时系统并不分配内存单元,而定义对象时系统会给每个对象分配内存单元2023/11/12113.2.1成员函数的性质不属于任何类的函数称为普通函数。成员函数隶属于某个类成员函数与普通函数的区别成员函数是属于某个类的,是类的一个成员成员函数可以指定访问属性成员函数可以访问本类的任何成员,而普通函数只能访问对象的公用成员对于成员函数,一般是将需要被类外调用的声明为公用的,不需要被类外调用的声明为私有的。

2023/11/1212【例3-4】将Student类的成员函数改为在类外定义的形式。3.2.2在类外定义成员函数2023/11/1213classStudent

//声明Student类类型{public: //以下部分为公用成员函数

voidsetInfo();voidshow();private: //以下部分是私有数据成员

intstuNo;//学号

charstuName[20];//姓名

stringstuSex="男"; //性别

intstuAge=0; //年龄};

//类声明结束//在类的声明之外定义成员函数

//在类的声明之外定义学生类Student的setInfo成员函数voidStudent::setInfo(){cout<<"Pleaseenterstudent’sNo,Name,Sex,Age:";cin>>stuNo>>stuName>>stuSex>>stuAge;}2023/11/1214classStudent

//声明Student类类型{public: //以下部分为公用成员函数

voidsetInfo();voidshow();private: //以下部分是私有数据成员

intstuNo;//学号

charstuName[20];//姓名

stringstuSex="男"; //性别

intstuAge=0; //年龄};

//类声明结束//在类的声明之外定义成员函数

voidStudent::show(){

cout<<"No.:"<<stuNo<<endl;

cout<<"Name:"<<stuName<<endl;

cout<<"Sex:"<<stuSex<<endl;cout<<"Age:"<<stuAge<<endl;}2023/11/12153.2.2在类外定义成员函数(1)成员函数在类内定义或是在类外定义,对程序执行的效果基本一样。(2)在类外定义成员函数时,必须首先在类内写出成员函数的原型,然后再在类外定义。(3)如果在类外有函数定义,但是在函数名前没有类名和作用域运算符进行限定,则该函数被认为是普通函数。(4)如成员函数的函数体很短,也可以将其定义在类内。(5)在类内声明成员函数,在类外定义成员函数,是软件工程中要求的良好的编程风格。注意2023/11/12163.2.3inline成员函数inline函数在编译时将被调用函数的代码直接嵌入到调用函数处inline成员函数就是将类中的成员函数声明为内置的当类中的成员函数是在类内定义时,默认该成员函数是inline成员函数如果成员函数定义在类的外部,则在成员函数声明或定义前必须要有inline关键字2023/11/12173.2.4成员函数的存储方式实例化对象时需要分配内存空间,数据和函数都需要存储空间同一个类的不同对象的数据是不一样的,因此要为每个对象的数据成员分配内存单元同一个类的不同对象的函数是一样的,因此将同类的所有对象的函数放在一个公共的区域。2023/11/12183.2.4成员函数的存储方式【例3-5】类的对象占用内存空间实验。#include<iostream>usingnamespacestd;classTest{public: voidshow(){cout<<"charinTestis:"<<c<<endl;}private: charc;};intmain(){Testtest;cout<<"SizeofTestis"<<sizeof(test)<<endl;return0;}2023/11/12193.2.4成员函数的存储方式【例3-6】相同类的不同对象执行相同成员函数输出不同结果。#include<iostream>usingnamespacestd;classTest{public: voidset(charch){c=ch;} voidshow() {cout<<"charinTestis:"<<c<<endl;}private: charc;};intmain(){Testtest1,test2;test1.set('a');test2.set('b');test1.show();test2.show();return0;}2023/11/12203.2.4成员函数的存储方式classTest{public: voidset(Test*this,charch){this->c=ch;} voidshow(Test

*this

) {cout<<"charinTestis:"<<this->c<<endl;}private: charc;};intmain(){Testtest1,test2;test1.set(&test1,'a');test2.set(&test2,'b');test1.show(&test1

);test2.show(&test2

);return0;}2023/11/12213.3对象成员的访问访问对象的成员主要有3种方法通过对象名和成员运算符访问对象中的成员通过指向对象的指针访问对象中的成员通过对象的引用访问对象中的成员2023/11/12223.3.1成员运算符访问成员通过对象名和成员运算符访问对象中的成员的一般形式:

对象名.成员名在使用对象的成员时一定要写清楚成员所属的对象,如果只写成员名则系统会误认为是一个普通的变量或函数如:在【例3-6】中main函数里的语句:

test1.show();2023/11/12233.3.2通过指针访问成员通过指向对象的指针访问对象中的成员可以通过C++的“->”运算符方便直观的进行,“->”称为指向运算符。例如:

Testtest1;test1.set('a');Test*pTest=&test1;

test1.show();pTest->show();此处两个语句的执行结果完全相同,只是采用了不同的访问对象成员的形式而已。2023/11/12243.3.3通过引用访问成员对象的引用和普通变量的引用在本质上是一样的通过对象的引用访问对象成员和通过对象访问成员形式上是一样的例如:

Testtest1;test1.set('a');Test&refTest=test1;

test1.show();

refTest.show();此处两个语句的执行结果完全相同,只是采用了不同的访问对象成员的形式而已。classStudent

//声明Student类类型{public: //以下部分为公用成员函数

voidsetInfo();voidshow();private: //以下部分是私有数据成员

intstuNo;//学号

charstuName[20];//姓名

stringstuSex="男"; //性别

intstuAge=0; //年龄};

//类声明结束//在类的声明之外定义成员函数

voidStudent::show(){

cout<<"No.:"<<stuNo<<endl;

cout<<"Name:"<<stuName<<endl;

cout<<"Sex:"<<stuSex<<endl;cout<<"Age:"<<stuAge<<endl;}2023/11/1225intmain(){

Student

stud;

stud.show();return0;}2023/11/1226classStudent

//声明Student类类型{public: //以下部分为公用成员函数

voidsetInfo();

voidshow();private: //以下部分是私有数据成员

intstuNo;//学号

charstuName[20];//姓名

stringstuSex="男"; //性别

intstuAge=0; //年龄};

//类声明结束//在类的声明之外定义成员函数

voidStudent::Show(){

cout<<"No.:"<<stuNo<<endl;

cout<<"Name:"<<stuName<<endl;

cout<<"Sex:"<<stuSex<<endl;}voidStudent::setInfo(){

cout<<"Pleaseenterstudent’sNo,Name,Sex,Age:";cin>>stuNo>>stuName>>stuSex>>stuAge;}intmain(){

Student

stud;

stud.setInfo();

stud.show();return0;}2023/11/12273.4.1构造函数1、构造函数的任务

创建对象时对对象的数据成员初始化【例3-9】构造函数举例2023/11/1228classSalesData

//声明图书交易记录类SalesData{public:

//定义SalesData类的构造函数,构造函数名与类名相同

SalesData(){//利用构造函数对数据成员赋初值bookNo="null";unitsSold=0;revenue=0.0;cout<<"SalesData'sconstructorisexecuted!"<<endl;}……private:stringbookNo;//书号intunitsSold;//销售出的册数doublerevenue;//总销售金额};

//类声明结束2023/11/12293.4.1构造函数构造函数的特点(1)构造函数是类的一个特殊的成员函数,构造函数名与类名相同,且没有返回值(2)构造函数可以被重载,一个类可以包含多个构造函数,不同的构造函数之间在形参数量或形参类型上有所不同。稍后介绍。(3)构造函数不需要用户调用,由系统在创建对象时自动调用的(4)构造函数不能声明为const的。(5)构造函数内容一般是初始化数据语句,但也可以是其他的语句(6)创建对象时肯定会执行一个构造函数2023/11/12303.4.1构造函数默认构造函数

如果用户自己没有定义构造函数,编译器会隐式地提供一个构造函数,称之为合成的默认构造函数,该构造函数的形参表和函数体皆为空,它按如下规则初始化类的数据成员:如果存在类内的初始值,用它来初始化成员,这是C++11标准中新增的类内初始化。否则,默认初始化该成员,其初始化规则与普通变量的默认初始化规则相同。2023/11/12313.4.1构造函数默认构造函数

合成的默认构造函数只适合非常简单的类,对于大多数普通的类来说,必须定义它自己的默认构造函数(该构造函数或者形参表为空,如例3-9中的构造函数,或者形参不为空但全部参数都有默认实参)。2023/11/12323.4.1构造函数2、定义自己的构造函数

classSalesData

//声明图书交易记录类SalesData{public:

SalesData()=default;//无参的默认构造函数

//带1个conststring&参数的构造函数

SalesData(conststring&No):bookNo(No){}//带1个istream&参数的构造函数

SalesData(istream&input);//带3个参数(书号、销售册数、销售价格)的构造函数

SalesData(conststring&No,intn,doubleprice):bookNo(No),unitsSold(n),revenue(n*price){}……};

//类声明结束2023/11/12333.4.1构造函数2、定义自己的构造函数

intmain(){SalesDatabook1;book1.show();cout<<endl;

SalesDatabook2("1002"); book2.show();cout<<endl;

SalesDatabook3("1003",2,35.0);book3.show();cout<<endl;……return0;}2023/11/12343.4.1构造函数带参数的构造函数一般格式为:构造函数名(参数表);

实参是在定义对象时给出的,一般格式为:类名对象名(实参表);带参数的构造函数形式可以方便的实现对不同对象进行不同的初始化2023/11/12353.4.1构造函数构造函数重载在类中定义多个构造函数,这些构造函数具有相同的函数名而参数表中参数的个数或类型不同相当于给类实例化对象时提供了不同的初始化方法2023/11/12363.4.1构造函数(1)如果在类的声明中没有写一个构造函数,系统会自动生成一个无参的、函数体为空的默认构造函数。默认构造函数只是在形式上保证创建对象时必须有构造函数。(2)由于一旦写了一个构造函数,系统就不再提供默认的构造函数,因此在程序中定义对象时一定要注意类中有几个构造函数,它们要求的参数分别是什么样的,如果创建对象时给出的参数表和所有的构造函数的参数表都不符合,则系统无法创建对象。(3)使用参数实例化对象时的格式是:类名对象名(实参表);而使用默认构造函数实例化对象时的格式是:类名对象名;注意2023/11/12373、构造函数的初始化列表构造函数的初始化列表为函数参数表后面的冒号及冒号和花括号之间的代码,它负责为新创建对象的一个或多个数据成员赋初值。它是成员名字的一个列表,每个名字后面紧跟括号括起来的(或者在花括号内的)成员初始值。不同成员的初始化通过逗号分隔开来。3.4.1构造函数//带3个参数(书号、销售册数、销售价格)的构造函数SalesData(conststring&No,intn,doubleprice):bookNo(No),unitsSold(n),revenue(n*price){}2023/11/12383、构造函数的初始化列表构造函数的执行分为两个阶段:(1)初始化阶段(初始化)初始化阶段具体指的是用构造函数初始化列表方式来初始化类中的数据成员。没有在构造函数的初始值列表中显式地初始化的成员,则该成员将在构造函数体之前执行默认初始化。(2)普通计算阶段(赋值)执行构造函数的函数体,函数体内一般是对类的数据成员进行赋值的操作。这两个阶段按照顺序依次执行。3.4.1构造函数2023/11/12393、构造函数的初始化列表初始化列表有时必不可少除了性能问题之外,有些时候初始化列表是不可或缺的,以下几种情况时必须使用初始化列表:(1)常数据成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面。(2)引用类型成员,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面。(3)没有默认构造函数的类类型成员,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用复制构造函数初始化(复制构造函数的内容在3.9.1节介绍)3.4.1构造函数2023/11/12403、构造函数的初始化列表成员初始化的顺序:初始化列表中成员初始化按照变量定义的先后顺序来初始化,与初始化列表中成员顺序无关。如果成员初始化依赖其他成员的值,那么要注意初始化顺序。为了避免这个问题,一般按照定义的顺序来初始化成员。3.4.1构造函数2023/11/12414、委托构造函数委托构造函数是C++11中对C++的构造函数的一项改进,其目的是为了简化构造函数的书写,减少冗余代码。一个委托构造函数可以使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或全部)职责委托给了其他构造函数。3.4.1构造函数2023/11/1242classSalesData

//声明图书交易记录类SalesData{public:

//受委托的构造函数,也称为目标构造函数SalesData(conststring&No,intn,doubleprice):bookNo(No),unitsSold(n),revenue(n*price){}

//其余构造函数全部委托给另一个构造函数SalesData():SalesData("null",0,0){}SalesData(conststring&No):SalesData(No,0,0){}SalesData(istream&input):SalesData(){doubleprice;input>>bookNo>>unitsSold>>price;revenue=unitsSold*price;}

……(此处省略的代码同例3-10)private:stringbookNo;//书号intunitsSold;//销售出的册数doublerevenue;//总销售金额};2023/11/12431.委托构造函数的初始化列表中有且仅有一个对目标构造函数的调用。即,如果委托构造函数的初始化列表中有一个对目标构造函数的调用,则该初始化列表中就不能再有其它东西(即不允许再有其它基类或数据成员的初始化)。2.目标构造函数还可以再委托给另一个构造函数,但不要形成委托环。如,构造函数C1委托给另一个构造函数C2,而C2又委托给C1,这样的代码通常会导致编译错误。注意2023/11/12443.委托构造函数的函数体中的语句在目标构造函数完全执行后才被执行。4.对象的生命期从任意一个构造函数执行完毕开始(对于委托构造的情况,就是最终的目标构造函数执行完毕时),这意味着从委托构造函数体中抛出异常将导致析构函数的自动执行。注意2023/11/12453.4.2析构函数析构函数的作用:在系统释放对象之前进行清理工作。析构函数的特点析构函数的函数名是固定的,由“~”+“类名”组成析构函数没有返回值析构函数没有参数,因此析构函数无法重载。一个类有且仅有一个析构函数如果没有自己写出析构函数,系统会自动生成一个析构函数。合成析构函数的函数体为空。2023/11/12463.4.2析构函数析构函数的特点析构函数在对象生命周期结束时由系统自动调用析构函数的作用不是释放对象,释放对象是由系统来进行的,而是在系统释放对象之前进行一些清理工作,把对象所占用的额外内存空间归还给系统注意2023/11/12473.4.2析构函数如同构造函数有一个初始化部分和一个函数体,析构函数也有一个函数体和一个析构部分。在一个构造函数中,成员的初始化是在函数体执行之前完成的,且按照它们在类中出现的顺序进行初始化。在一个析构函数中,首先执行函数体,然后析构成员。成员按初始化顺序的逆序析构。在析构函数的函数体内,可以书写在对象最后一次使用之后类设计者希望执行的任何收尾工作。通常,析构函数的函数体完成释放对象在生存期分配的所有资源。2023/11/12483.4.2析构函数析构部分是隐式的。成员析构时发生什么完全依赖于成员的类型。析构类类型的成员系统会自动执行成员对象自己的析构函数。内置类型没有析构函数,因此析构内置类型成员什么也不需要做。注意:隐式析构一个内置指针类型的成员不会delete它所指向的对象。认识到析构函数体自身并不直接析构成员是非常重要的。成员是在析构函数体之后隐含的析构阶段中被析构的。在整个对象被析构过程中,析构函数体是作为成员析构步骤之外的另一部分进行的。

2023/11/12493.4.2析构函数【例3-11】简化版的SalesData类。classSalesData//声明图书交易记录类SalesData{public:

//受委托的构造函数,也称为目标构造函数

SalesData(conststring&No,intn,doubleprice):bookNo(No),unitsSold(n),revenue(n*price){}

//其余构造函数全部委托给另一个构造函数

SalesData():SalesData("null",0,0){}

SalesData(conststring&No):SalesData(No,0,0){}

SalesData(istream&input):SalesData(){doubleprice; input>>bookNo>>unitsSold>>price; revenue=unitsSold*price;}……};2023/11/12503.4.2析构函数【例3-11】简化版的SalesData类。classSalesData

//声明图书交易记录类SalesData{public:……~SalesData(){

//对象被销毁前输出图书基本销售记录:书号、销售册数、总销售金额、销售均价 cout<<"bookNo="<<bookNo; cout<<",unitsSold="<<unitsSold; cout<<",revenue="<<revenue; if(unitsSold!=0) cout<<",avgPrice="<<avgPrice(); cout<<endl;//换行}……};2023/11/12513.4.2析构函数【例3-11】简化版的SalesData类。classSalesData//声明图书交易记录类SalesData{public:……

//计算图书销售均价doubleavgPrice(){returnrevenue/unitsSold;}private:stringbookNo; //书号intunitsSold; //销售出的册数doublerevenue;//总销售额};intmain(){SalesDatabook1("1001",2,35.0);

SalesDatabook2("1002",5,32.6);return0;}程序执行后运行的结果如下:bookNo=1002,unitsSold=5,revenue=163,avgPrice=32.6bookNo=1001,unitsSold=2,revenue=70,avgPrice=352023/11/12523.4.3

构造函数和析构函数调用次序总的原则当创建对象时调用构造函数,当释放对象时调用析构函数创建对象是当程序执行到了非静态对象的定义语句或第一次执行到静态对象的定义语句释放对象则是对象到了生命周期的最后时系统释放对象或通过delete运算符动态释放new运算符动态申请的对象2023/11/12533.4.3

构造函数和析构函数调用次序【例3-12】验证调用构造函数和析构函数的顺序的例子。教材P962023/11/12543.4.3

构造函数和析构函数调用次序下面对何时调用构造函数和析构函数的问题进行小结。(1)在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。2023/11/12553.4.3

构造函数和析构函数调用次序下面对何时调用构造函数和析构函数的问题进行小结。(2)如果定义的是局部自动对象(如在函数中定义对象),则在创建对象时调用其构造函数。如果函数被多次调用,则在每次创建对象时都要调用构造函数。在函数调用结束、释放对象时先调用析构函数。(3)如果在函数中定义静态(static)局部对象,则只在程序第一次调用此函数创建对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。2023/11/12563.4.3

构造函数和析构函数调用次序构造函数和析构函数是类的两个特殊的且非常重要的成员函数,在设计一个类时,应尽可能考虑将来创建对象的各种情况,写出多个构造函数,而对于类的析构函数,如果该类不包含指向动态分配的内存的指针数据成员,则可以不写析构函数,如果该类包含指向动态分配的内存的指针数据成员,则必须写析构函数,在析构函数的函数体写出释放指针所指向内存空间的语句,否则会造成内存泄漏。看下面的自定义字符串类String。关于析构函数的其他注意事项,在本书第5章5.4.2节将会继续介绍。2023/11/12燕京理工学院573.4.3

构造函数和析构函数调用次序classString

//自定义字符串类{public:

String();//默认构造函数

String(unsignedint);

//带一个无符号整型形参的构造函数,传递字符串的长度

String(char);//带一个字符形参的构造函数,传递一个字符

String(constchar*src);

//带一个字符指针形参的构造函数,传递一个字符串

~String();//析构函数

char*toString(){returnstr;}//到普通字符串的转换

unsignedintlength()//求字符串的长度

{returnlen;}

private:

char*str;

//字符指针str,将来指向动态申请到的存储字符串的内存空间unsignedintlen;//字符串的长度};2023/11/12583.4.3

构造函数和析构函数调用次序String::String(){len=0;str=newchar[len+1];

//指针str指向动态申请到的内存空间assert(str!=nullptr);

//如果括号内表达式的值为假,则终止程序执行

str[0]='\0';}2023/11/12593.4.3

构造函数和析构函数调用次序//带一个无符号整型形参的构造函数,传递字符串的长度String::String(unsignedintsize){assert(size>=0);len=size;str=newchar[len+1];assert(str!=nullptr);for(unsignedinti=0;i<len;i++)str[i]='\0';}2023/11/12603.4.3

构造函数和析构函数调用次序String::String(charc)//带一个字符形参的构造函数,传递一个字符{len=1;

str=newchar[len+1];

assert(str!=nullptr);

str[0]=c;

str[1]='\0';}String::String(constchar*src)//带一个字符指针的构造函数,传递一个字符串{len=strlen(src);

str=newchar[len+1];

assert(str!=nullptr);

strcpy(str,src);}2023/11/12613.4.3

构造函数和析构函数调用次序String::~String()//析构函数{

delete[]str;str=nullptr;

//动态释放指针str所指向的内存空间}2023/11/12623.5对象数组对象数组和普通的数组没有本质的区别,只不过普通的数组的元素是简单变量,而对象数组的元素是对象而已对象数组在实际中主要用于系统需要一个类的多个对象的情况在建立数组时,系统会自动调用每一个对象元素的构造函数2023/11/1263【例3-13】创建含有3个长方体元素的长方体数组,并显示长方体构造函数的调用情况。教材P993.5对象数组2023/11/12643.4.2析构函数#include<iostream>usingnamespacestd;classBox{public:

Box(){……}

Box(floatL,floatw,floath){……}

~Box(){……}

floatvolume(){returnlength*width*height;}private:

floatlength,width,height;};intmain(){Boxboxs[3];//创建含有三个元素的对象数组boxsreturn0;}2023/11/12653.4.2析构函数intmain(){Boxboxs[3];//创建含有三个元素的对象数组boxsreturn0;}程序执行后运行的结果如下:Box(1,1,1)isconstructed!Box(1,1,1)isconstructed!Box(1,1,1)isconstructed!Box(1,1,1)isdestructed!Box(1,1,1)isdestructed!Box(1,1,1)isdestructed!2023/11/12663.5对象数组对象数组在建立时还可以给出实参以实现对数组元素进行不同的初始化如果Box类有只有一个参数的构造函数,可以使用如下形式的数组初始化形式:Boxboxs[3]={10,20,30};如果对象的构造函数需要多个参数,则在初始化的花括号里要分别写明构造函数,并指定实参对象数组初始化参见【例3-14】2023/11/1267【例3-14】定义对象数组并初始化,观察对象数组建立的情况。教材P1013.5对象数组2023/11/12683.4.2析构函数#include<iostream>usingnamespacestd;classBox{public:

Box(){……}

Box(floatL,floatw,floath){……}

~Box(){……}

floatvolume(){returnlength*width*height;}private:

floatlength,width,height;};2023/11/12693.4.2析构函数intmain(){//创建含有三个元素的对象数组boxesBoxboxs[3]={Box(1,3,5),Box(2,4,6),Box(3,6,9)};

//pbegin指向boxs数组的首元素,pend指向boxs数组尾元素的下一位置Box*pbegin=begin(boxs),*pend=end(boxs);

//遍历对象数组boxs,计算并输出每个Box类对象的体积while(pbegin!=pend){ cout<<"volume="<<pbegin->volume()<<endl; ++pbegin;}return0;}程序执行后运行的结果如下:Box(1,3,5)isconstructed!Box(2,4,6)isconstructed!Box(3,6,9)isconstructed!volume=15volume=48volume=162Box(3,6,9)isdestructed!Box(2,4,6)isdestructed!Box(1,3,5)isdestructed!2023/11/12703.4.2析构函数intmain(){Boxboxs[3]={Box(1,3,5), Box(), Box(3,6,9)};//创建含有三个元素的对象数组boxsreturn0;}程序执行后运行的结果如下:Box(1,3,5)isconstructed!Box(1,1,1)isconstructed!Box(3,6,9)isconstructed!Box(3,6,9)isdestructed!Box(1,1,1)isdestructed!Box(1,3,5)isdestructed!2023/11/12713.6对象指针以指针的概念为基础,同时又加入了对象的特点对象与指针主要从三个方面进行讨论指向对象的指针指向对象成员的指针this指针2023/11/12723.6.1指向对象的指针指针指向的是内存中对象所占用空间的首地址定义指向对象的指针的一般形式是:类名*指针名;

通过指向对象的指针访问对象的成员有两种形式通过“->”调用对象的公用成员函数通过“*”运算符得到对象,然后再使用“.”运算符调用对象的公用成员函数2023/11/12733.6.2指向对象成员的指针指向对象数据成员的指针指向对象数据成员的指针和普通的指针是完全相同的,声明指针变量的格式如下:

数据类型名*指针变量名;而使指针指向对象的公用数据成员使用如下语句:

指针变量=&对象名.数据成员名;设类A有公用整型数据成员data,定义了一个整型指针p和A类对象a,则p=&a.data;//使整型指针p指向对象a的数据成员datacout<<*p<<endl;2023/11/12743.6.2指向对象成员的指针指向对象成员函数的指针指向对象成员函数的指针需要在指针名前面加上所属的类名及域运算符“::”指向对象成员函数的指针不但要匹配将要指向函数的参数类型、个数和返回值类型,还要匹配将要指向函数所属的类2023/11/12753.6.2指向对象成员的指针指向普通函数的指针变量定义如下:

返回值类型(*指针名)(参数表);而指向成员函数的指针变量定义如下:

返回值类型(类名::*指针名)(参数表);使用指向成员函数的指针指向一个公用成员函数时如下:

指针名=&类名::成员函数名;使用指向成员函数的指针调用对象的成员函数时如下:

(对象名.*指针名)(实参表);2023/11/12763.6.2指向对象成员的指针【例3-15】使用指向对象成员函数的指针调用对象的成员函数.intmain(){Boxbox(2,2,2); //创建Box的对象box//定义指向Box类的成员函数的指针pfloat(Box::*p)();

//给指针p赋值,使其指向Box类的成员函数Volume

p=&Box::volume;

//调用指针p指向的函数cout<<“Thevolumeofboxis"<<(box.*p)()<<endl;

return0;}2023/11/12773.6.2指向对象成员的指针(1)在给指向对象成员函数的指针进行赋值时要把类的函数名赋值给指针,而不是对象的函数名(2)调用指向对象成员函数的指针指向的成员函数时,要通过(对象名.*指针名)(实参表)的形式,而不是(类名.*指针名)(实参表)的形式。(3)定义指向对象成员函数的指针时可以同时进行初始化操作,形式为:返回值类型(类名::*指针名)(形参表)=&类名::成员函数名;如:float(Box::*p)()=&Box::Volume;注意2023/11/12783.6.3

this指针在每一个成员函数中都包含了一个特殊指针,这个指针的名字是固定的,称为this指针。如Box类中的volume函数,其中的height*width*length实际上是(this->height)*(this->width)*(this->length)this指针是指向本类对象的指针,它的指向是被调用成员函数所在的对象this指针是由系统通过参数隐式传递给成员函数的2023/11/1279this指针的处理过程源程序编译后调用时floatBox::volume(){returnlength*width*height;}floatBox::volume(Box*this){returnthis->length*this->width*this->height;}box1.volume(&box1);2023/11/12803.7对象与const在程序设计的过程中需要考虑的一个非常重要的因素就是数据的安全性在程序中,不同的模块之间经常出现需要共享数据的情况,此时数据的安全性降低在程序中既要让数据在一定范围内共享,又要保证数据的安全,这时可以使用const,把对象或对象相关成员定义成常量本节主要从常对象、常对象成员、指向对象的常指针、指向常对象的指针和对象的常引用五个方面进行说明2023/11/12813.7.1常对象常对象中的所有成员的值都不能被修改常对象中数据成员为常变量且必须有初值声明常对象的一般形式const类名对象名[(实参表)];类名const对象名[(实参表)];如果一个对象被声明为常对象,则不能调用该对象的非const型的成员函数(构造函数和析构函数除外)在成员函数声明的后面加上const即可访问2023/11/12823.7.1常对象如何将一个成员函数声明成const型成员函数呢?其实很简单,只需要在成员函数声明的后面加上const即可。floatvolume()const;特别提醒:有时在编程时有要求,一定要修改常对象中的某个数据成员的值,ANSIC++考虑到实际编程时的需要,对此作了特殊的处理,将该数据成员声明为mutable,如:mutableintcount;把count声明为可变的数据成员,这样就可以用声明为const的成员函数来修改它的值。2023/11/12833.7.2常对象成员常数据成员常数据成员的声明和作用与普通的常变量类似在程序运行过程中常数据成员的值不能修改常变量在声明的同时必须初始化注意常数据成员在初始化时必须使用构造函数的参数初始化表2023/11/12843.7.2常对象成员例:若Box类中的数据成员length声明为常数据成员,则如下的构造函数:Box::Box(floatL,floatw,floath){length=L;

width=w; height=h;}×不能使用这种形式进行初始化Box::Box(floatL,floatw,floath):length(L){width=w; height=h;}√2023/11/12853.7.2常对象成员常成员函数就是const型成员函数声明常成员函数的一般形式:返回值类型成员函数名(形参表)const;常成员函数只能访问而不能修改数据成员的值,如果在常成员函数中出现了修改数据成员值的语句,系统编译是通不过的。2023/11/12863.7.2常对象成员常成员函数非常成员函数常数据成员可以访问,但不可修改值可以访问,但不可修改值非常数据成员可以访问,但不可修改值可以访问,也可以修改值数据成员分类成员函数分类2023/11/12873.7.2常对象成员(1)在一个类中可以根据需要将部分数据成员声明为const数据成员,另一部分数据成员为非const数据成员。(2)如果一个类的所有数据成员都不允许修改,可以将这个类中的所有数据成员都声明成const型数据成员,或者定义对象时声明为const对象,两者都可以保证对象的数据成员的安全。注意2023/11/12883.7.2常对象成员(3)常对象中的数据成员都是const型数据成员,但是常对象中的成员函数不一定都是const型成员函数,只有在成员函数的声明和定义部分有const关键字的才是const型成员函数。注意2023/11/12893.7.2常对象成员(4)如果已定义了一个常对象,只能调用其中的const型成员函数。如果一个成员函数没有修改数据成员,但是没有声明为const型成员函数,也不能通过常对象名调用。因此,如果在使用一个类的对象时可能会声明const对象,则在定义类时应该将那些不会修改数据成员的成员函数声明为const型,否则如果该类中没有公用的const型成员函数,则声明了该类的const对象之后将无法调用任何一个成员函数。(5)在类的定义中,const型成员函数不能调用非const型成员函数。注意2023/11/12903.7.3指向对象的常指针将指向对象的指针变量声明为const型定义指向对象的常指针的一般形式:类名*const指针名[=&类的对象];例如:Boxbox(2,2,2);Box*constpbox=&box;在给pbox赋初值后pbox的值不能再修改2023/11/12913.7.3指向对象的常指针一般情况下指向对象的const指针用作函数的形参指针在函数的执行过程中不会改变指针的指向可以防止误操作,增加系统的安全性2023/11/12923.7.4指向常对象的指针变量指向常对象的指针变量和指向常变量的指针变量的概念和用法非常接近定义指向常对象的指针变量的一般形式:const类名*指针变量名;说明:(1)如果一个对象已被声明为常对象,只能用指向常对象的指针变量指向它,而不能用一般的(指向非const型对象的)指针变量去指向它2023/11/12933.7.4指向常对象的指针变量classClock//声明时钟类Clock{public:Clock(inth,intm,ints)//带有3个形参的构造函数{hour=h;minute=m;second=s;}voiddisplay()//公用成员函数display显示时间{cout<<hour<<":"<<minute<<":"<<second<<endl;}inthour,minute,second;//公用数据成员};intmain(){//定义Clock类对象clock1,它是常对象constClockclock1(1,1,1);constClock*p1=&clock1;//正确,clock1是常对象,p1是指向常对象的指针//Clock*p2=&clock1;//错误,clock1是常对象,而p2是普通指针

……return0;}2023/11/12943.7.4指向常对象的指针变量说明:(2)如果定义了一个指向常对象的指针变量,并使它指向一个非const的对象,则其指向的对象是不能通过指针来改变的Clockclock2(2,2,2);constClock*p2=&clock2;//正确//正确,通过p2可以访问clock2对象的数据成员的值cout<<p2->hour<<endl;//错误,不能通过p2修改clock2对象的数据成员的值p2->hour=2;p2->display();//错误,display是非const型成员函数2023/11/12953.7.4指向常对象的指针变量说明:(3)如果定义了一个指向常对象的指针变量,虽然不能通过它改变所指向的对象的值,但是指针变量本身的值是可以改变的Clockclock3(3,3,3);Clockclock4(4,4,4);//定义指向常对象的指针变量p3,并指向对象clock3constClock*p3=&clock3;//正确,p3改为指向对象clock4p3=&clock4;2023/11/12963.7.4指向常对象的指针变量说明:(4)指向常对象的指针变量最常用于函数的形参,目的是在保护形参指针所指向的对象,使它在函数执行过程中不被修改2023/11/12973.7.4指向常对象的指针变量classClock//声明时钟类Clock{public:Clock(inth,intm,ints)//带有3个形参的构造函数{hour=h;minute=m;second=s;}voiddisplay()//公用成员函数display显示时间{cout<<hour<<":"<<minute<<":"<<second<<endl;}inthour,minute,second;//公用数据成员};intmain(){//函数func的形参为指向常对象的指针voidfunc(constClock*p);

//定义Clock类对象clock,它不是常对象Clockclock(10,10,10);func(&clock);//实参为对象clock的地址return0;}voidfunc(constClock*p){p->hour=12;//错误cout<<p->hour<<endl;//正确}2023/11/12983.7.4指向常对象的指针变量请记住这样一条规则:当希望在调用函数时参数对象的值不被修改,就应当把形参定义为指向常对象的指针变量,同时用对象的地址作实参,而对象参数可以是const或非const型。2023/11/12993.7.5对象的常引用对象的常引用表示一个对象的别名,但是无法通过引用来修改对象的数据成员声明对象的常引用的一般形式:const类名&引用名=对象名;常引用的应用也是主要用在函数的形参中,保证函数调用时实参对象的安全性例如:Clockclock(12,12,12);constClock&refclock=clock;refclock.Display();2023/11/121003.8对象的动态创建和释放使用new和delete运算符实现对对象的动态创建与释放对于例3-14程序中声明的长方体类Box,可以使用如下语句动态地创建一个Box类的对象:newBox;在程序执行到new语句时,系统会从内存堆中分配一块内存空间,存放类的对象,调用构造函数初始化对象。如果内存分配成功,返回分配的内存的首地址;如果分配内存失败,则会返回一个nullptr。2023/11/121013.8对象的动态创建和释放但是通过new运算符动态创建的对象没有名字,要声明一个指针变量来保存对象的首地址。如:Box*pc=newBox;2023/11/121023.8对象的动态创建和释放可以在使用new运算符创建对象时给出实参,调用带有参数的构造函数初始化对象如:Box*pc=newBox(2,2,2);

为了保险起见,在使用对象指针之前一般先判断指针的值是否为nullptr。如下所示:Box*pc=newBox(2,2,2);if(pc!=nullptr){pc->volume();}2023/11/121033.8对象的动态创建和释放使用delete运算符释放该对象delete运算符的使用格式是:delete指针变量名;如:deletepc;通过new运算符动态创建的对象只能通过delete运算符动态的释放2023/11/121043.9对象的复制和赋值在创建对象时使用已有对象快速复制出完全相同的对象对象复制的一般形式类名对象2(对象1);类名对象2=对象1;其中对象1是和对象2同类的已经存在的对象。上面语句的作用就是使用已经存在的对象1“克隆”出新的对象2。在这种情况下,创建对象2时系统会调用一个称为“复制构造函数”的特殊的构造函数。3.9.1对象的复制2023/11/12105以Box类为例,看看复制构造函数的形式:Box::Box(constBox&c)//Box类的复制构造函数{length=c.length;width=c.width;height=c.height;}3.9对象的复制和赋值3.9.1对象的复制复制构造函数只有一个形参,这个形参就是本类对象的引用复制构造函数的代码主要是将形参中对象引用的各数据成员值赋给自己的数据成员2023/11/121063.9.1对象的复制如果用户自己没有定义复制构造函数,编译器会隐式地提供一个。与合成默认构造函数不同,即使定义了其他构造函数,编译器也会合成一个复制构造函数,它会将实参对象中的非static数据成员逐个复制到正在创建的对象中。每个成员的类型决定了它如何复制:类类型的成员,会使用其复制构造函数来复制;内置类型的成员则直接复制。对于数组,合成复制构造函数会逐元素地复制一个数组类型的成员。如果数组元素是类类型,则使用元素的复制构造函数来进行复制。2023/11/12燕京理工学院1073.9.1对象的复制普通构造函数和复制构造函数的区别:(1)在形式上普通构造函数一般是形参列表而复制构造函数的形参则只有一个(2)在调用时系统会根据实参类型来自动的选择调用普通构造函数还是复制构造函数。(3)调用的情况不同,普通构造函数是在创建对象时由系统自动调用;而复制构造函数是在使用已有对象复制一个新对象时由系

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论