版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
大学计算机基础教学系列教材第二章面向对象概念——封装与继承2026年5月11日目录封装第2.1节构造和析构第2.2节继承第2.3节本章小结第2.4节01020304CONTENTS学习目标
理解类、对象、封装、继承与多态的概念及其应用。
掌握类的定义、对象的创建,以及封装、继承、多态相关语法和用法,能够综合运用这些特性实现复杂的业务逻辑。
学会合理利用封装、继承与多态来设计和优化代码,以提升程序的可维护性、可扩展性和可读性。封装01第2.1节类和对象类与对象是面向对象编程中的基本构成要素。类(Class)通过对现实世界实体的抽象化过程,将该类别的属性(数据)与行为(操作)封装为一个逻辑单元。而对象则是类的具体实例,每个对象均继承类中定义的属性与行为,并可具备各自的特定状态。
一组具有相同属性和行为的对象可抽象为一个类。
为便于理解,可将属性定义为对象的数据成员(常量或变量),而将行为对应为可对对象执行的操作(即函数或方法)。类和对象C++语言引入了class语法特性,定义类的基本语法如下:采用关键字class可定义一个类,该类可包含成员属性与成员函数,分别对应于前述语法中的属性与行为。如下代码所示,定义了一个名为Person的抽象类:class类名{访问权限:属性;行为;};classPerson{public://成员属性stringname;intage;voidintroduce(){//成员函数cout<<"Mynameis"<<name<<".Iam"<<age<<"yearsold."<<endl;}};创建类对象的方式
对象是类的实例化。创建类的对象存在以下几种常见方式:直接申明对象类名
对象名//实例化一个对象对象名.属性//使用对象的属性对象名.函数//使用对象的函数在主函数或其他函数中,可如同申明普通变量一般直接申明类的对象,其定义与使用方法如下:创建类对象的方式
如下代码所示,创建Person类的一个实例对象p,并调用该对象p的属性与方法:intmain(){Personp;="Alice";p.age=30;roduce();return0;}MynameisAlice.Iam30yearsold.程序输出如下:创建类对象的方式通过new运算符动态创建对象intmain(){Person*p=newPerson();//使用new关键字动态分配内存创建Person对象p->name="Bob";//通过指针p使用->运算符符来访问对象的成员p->age=25;p->introduce();deletep;//释放动态分配的内存Personp1;="alice";p1.age=21;p=&p1;//指针p指向实例对象p1的地址
p->introduce();return0;}MynameisBob.Iam25yearsold.Mynameisalice.Iam21yearsold.程序输出如下:
使用new运算符可在程序运行时动态创建对象,以实现运行时的动态内存分配。当指针指向由new创建的对象时,需使用"->"运算符访问该对象的成员属性及成员函数。
创建类对象的方式使用对象数组在需要创建同一类的多个对象时,可考虑采用对象数组。此方式尤为适宜对同类型数据进行批量管理。intmain(){Personpeople[3];people[0].name="Charlie";people[0].age=35;people[1].name="David";people[1].age=40;people[2].name="Eve";people[2].age=28;
MynameisCharlie.Iam35yearsold.MynameisDavid.Iam40yearsold.MynameisEve.Iam28yearsold.程序输出如下:for(inti=0;i<3;i++){people[i].introduce();}return0;}封装
封装是面向对象编程的另一项基本概念,意为将数据(属性)与相关操作(行为)整合于类中,并隐藏具体内部实现细节,仅对外提供必要的访问接口。该机制实现了信息隐藏与接口隔离,能够有效降低代码耦合度,提升系统的可维护性、可扩展性及安全性保障。封装特点数据隐藏:封装机制通过将类的属性设置为私有(private)或保护(protected),实现数据隐藏。外部代码不能直接访问或修改这些数据成员,从而确保数据安全性与完整性。如下代码所示,类外部无法访问私有属性age。classPerson{public:stringname;private: intage;};intmain(){Personp;="Alice";p.age=30;//报错,类外无法访问私有属性return0;}封装特点公共接口:通过公开公共(public)函数作为访问私有数据的接口,外部代码仅能通过这些方法与对象交互,从而实现对数据访问的严格控制。如下列代码所示,第11行定义的公有函数setAge,使外部代码可通过该函数访问私有属性age。classPerson{public:stringname;voidintroduce(){cout<<"Mynameis"<<name<<"Iam"<<age<<"yearsold."<<endl;}voidsetAge(intAge){ //对私有属性赋值
age=Age; }private: intage;};intmain(){Personp;="Alice";p.setAge(30); //调用公有函数访问私有属性
roduce();return0;}封装特点提高可维护性:通过封装内部实现细节,可以在不改变类的外部接口的前提下,自由调整和优化其内部结构,从而显著提升代码的可维护性。当修改Person类中的setAge方法时,只要其调用接口保持不变,就不会影响外部使用。classPerson{public:stringname; voidsetAge(intAge){//更改代码,无需通知主函数 if(Age>0){ age=Age; }}voidintroduce(){cout<<"Mynameis"<<name<<"Iam"<<age<<"yearsold."<<endl;}private: intage;};封装特点简化复杂性:封装将复杂的内部逻辑隐藏在类内部,用户仅需关注所提供的公共接口,无需了解其底层实现细节,从而降低了使用和理解的难度。
增强代码的可读性:通过将相关的属性和行为组织在同一类中,使结构清晰且职责明确,从而使代码更易于阅读和理解。成员属性和函数类的成员属性构成对象状态的核心组成部分。通过合理的访问控制设置,可有效管理对象状态,实现数据的封装与安全保护。合理设计类的成员属性至关重要,它不仅决定了对象行为的正确性与一致性,还直接影响到程序的可维护性、扩展性以及整体架构的稳定性。定义成员属性的方式兼具直观性与简洁性。如下代码所示,定义用于表示二维平面上点的类Point:classPoint{public:intx;//成员属性,用于存储点的x坐标inty;//成员属性,用于存储点的y坐标};成员属性和函数访问修饰符对成员属性的控制C++提供三种访问修饰符:public(公共)、private(私有)与protected(受保护)。这些修饰符对成员属性的访问权限实施严格限定,有效增强了数据安全性。public成员属性在类的内部及外部均可被无限制地访问与修改。
classPoint{public:intx;inty;};intmain(){Pointp;//创建Point类的对象pp.x=3;//在类外部直接访问并修改public成员属性xp.y=5;//同理cout<<"Pointp:("<<p.x<<","<<p.y<<")"<<endl;return0;}Pointp:(3,5)程序输出如下:成员属性和函数private成员属性仅可在类的内部访问与修改,类外部无法直接访问。如下代码所示,将Point类的成员属性声明为private,将数据的访问与修改操作封装于类的内部,仅能通过公有成员函数提供有限且可控的访问接口。classPoint{private:intx;inty;public:voidsetX(intnewX){x=newX;}//提供公共的成员函数来设置x的值voidsetY(intnewY){y=newY;}//提供公共的成员函数来设置y的值intgetX()const{returnx;}
//提供公共的成员函数来获取x的值intgetY()const{returny;}
//提供公共的成员函数来获取y的值};intmain(){Pointp; //创建Point类的对象p//p.x=3;//错误,无法在类外部直接访问private成员属性x和y//p.y=5;p.setX(3);//通过公共成员函数间接设置x的值p.setY(5);//同理cout<<"Pointp:("<<p.getX()<<","<<p.getY()<<")"<<endl;return0;}成员属性和函数protected成员属性位于public和private之间,在类的内部及其子类中可访问并可修改,而在类外部则不可访问。例如,Point类中的x和y成员无法在类外部访问。classPoint{protected:intx;inty;public:voidsetX(intnewX){x=newX;}//提供公共的成员函数来设置x的值voidsetY(intnewY){y=newY;}//提供公共的成员函数来设置y的值intgetX()const{returnx;}
//提供公共的成员函数来获取x的值intgetY()const{returny;}
//提供公共的成员函数来获取y的值};intmain(){Pointp; //创建Point类的对象p//p.x=3;//错误,无法在类外部直接访问protected成员属性x和y//p.y=5;p.setX(3);//通过公共成员函数间接设置x的值p.setY(5);//同理cout<<"Pointp:("<<p.getX()<<","<<p.getY()<<")"<<endl;return0;}成员属性和函数成员属性的数据类型成员属性的数据类型涵盖基本数据类型(例如int、double、char等)、复合数据类型(例如数组、结构体、指针等)以及自定义类型。基本数据类型。
前述Point类示例中,成员属性x与y均采用int类型表征坐标值。复合数据类型。
数组用于存储一组相同类型的数据。如创建一个二维数组data作为成员属性,用以表示矩阵。表-访问修饰符权限访问修饰符公有属性私有属性/函数保护属性/函数类内函数可以调用可以调用可以调用类外函数可以调用不可调用不可调用成员属性数据类型结构体类型。结构体用于将相关数据组合,构成一个结构单元。如下代码所示,将Address结构体定义为成员属性的方式。//定义Address结构体structAddress{stringstreet;stringcity;stringzipCode;};classPerson{public:stringname;intage;Addressaddress;//结构体类型作为成员属性};指针类型。指针作为成员属性可用于动态内存分配、实现链表与树等复杂数据结构,或引用其他对象。classScore{public:int*value;//指针成员属性};需注意:不当的指针操作可能引发内存泄漏或悬空指针等问题,需谨慎管理内存分配与释放的生命周期。成员属性数据类型自定义类型。classEngine{public:inthorsepower;};classCar{public:Engineengine;//自定义类Engine作为成员属性stringbrand;};自定义类型可包含其他自定义类类型作为成员属性,从而构建复杂的对象层次结构。例如,创建表示汽车的类Car时,可将其发动机信息封装于Engine类中;继而将Engine类实例作为Car类的成员属性。intmain(){CarmyCar;//实例化Car类myCar.engine.horsepower=200;//初始化Engine成员myCar.brand="Toyota";//初始化brand成员return0;}静态成员静态成员
静态成员是C++类中的一种特殊成员,其不属于类的某个特定对象,而属于整个类。
静态成员属性。静态成员属性是在类中使用static关键字声明的变量。与普通成员属性不同,静态成员属性为类的所有对象所共有,需在类内声明并在类外进行初始化。初始化时须使用作用域解析运算符"::"定义该静态成员属性,其基本语法如下:class类名{static类型
属性;};类型
类名::属性=初始化值;classMyClass{public:staticintstaticVar;};intMyClass::staticVar=0;//使用::运算符定义类内静态属性
如下代码所示,定义一个静态成员属性staticVar并初始化为0。
静态成员属性
所有类的对象共享同一个静态成员属性,即对该属性的修改将作用于所有对象,所有对象对该变量的操作都将体现此修改。如下代码所示,对象c1与c2共享同一个count属性。
classCounter{public:staticintcount;Counter(){count++;}};intCounter::count=0;intmain(){Counterc1;c1.count=c1.count+2;cout<<"Count:"<<Counter::count<<endl;Counterc2;c2.count=c2.count*2;cout<<"Count:"<<Counter::count<<endl;return0;}Count:3Count:8程序输出如下:静态成员属性
静态成员属性在内存中仅存在唯一副本,存储于全局数据区,这与普通成员属性(各对象实例独立持有副本)存在根本差异。静态成员既可通过类名直接访问,亦可借助对象访问,但通常建议采用类名进行访问,以明确其属于类本身的特性,而非依赖于特定实例。
classMyClass{public:staticintstaticVar;};intMyClass::staticVar=10;1010程序输出如下:
intmain(){MyClassobj;cout<<MyClass::staticVar<<endl;cout<<obj.staticVar<<endl;return0;}静态成员
静态成员函数。静态成员函数指在类中使用static关键字申明的函数。该函数不属于类的任何具体对象,而是与类本身关联。如下列代码所示:classMathUtils{public:staticintadd(intnum1,intnum2){returnnum1+num2;}};intmain(){intresult=MathUtils::add(5,3);//使用::运算符调用类的静态函数cout<<"5+3="<<result<<endl;return0;}静态成员函数
在静态成员函数内部无法直接访问非静态数据成员及非静态成员函数,除非通过对象实例进行调用。如下代码所示:classMyClass{public:intnonStaticVar;voidnonStaticFunc(){cout<<"nonStaticFunc()"<<endl;}staticvoidstaticFunc1(){//cout<<nonStaticVar<<endl;//错误,无法访问非静态成员属性}staticvoidstaticFunc2(MyClass&obj){cout<<"调用非静态成员属性:"<<obj.nonStaticVar<<endl;cout<<"调用非静态成员函数:";obj.nonStaticFunc();}};intmain(){MyClassobj;obj.nonStaticVar=42;MyClass::staticFunc2(obj);return0;}调用非静态成员属性:42调用非静态成员函数:nonStaticFunc()程序输出如下:静态成员函数
静态成员函数可访问类的静态成员属性及其他静态成员函数,因其均属于类作用域范畴,不受对象实例化约束。如下代码所示:classMyClass{public:staticintstaticVar;staticvoidsetStaticVar(intvalue){staticVar=value;}};intMyClass::staticVar=0;intmain(){MyClass::setStaticVar(20);cout<<MyClass::staticVar<<endl;//输出20return0;}静态成员函数
静态成员函数可通过类名直接调用,无需创建类对象。该类函数在无需对象实例化的场景中具有实际应用价值,例如提供通用工具函数或执行与类整体状态相关的操作。如下代码所示:classStringUtils{public:staticboolisEmpty(conststring&str){returnstr.empty();}};intmain(){stringstr="";cout<<(StringUtils::isEmpty(str)?"Empty":"NotEmpty")<<endl;return0;}Empty程序输出如下:类外定义函数
类外定义函数成员函数与成员属性的定义无特定顺序要求,可在类内申明成员函数,并于类外部实现其函数体。类外定义成员函数的基本语法结构如下:class类名{
返回类型
函数;};返回类型
类名::函数{
函数体}类外定义函数
如下代码所示,类内申明成员函数area,类外定义其函数体。classRectangle{public: doublelength;doublewidth;doublearea();};//类外定义成员函数doubleRectangle::area(){//使用::运算符定义类内成员函数returnlength*width;}Theareaoftherectangleis:15程序输出如下:常量成员
常量成员属性是指在类定义中被申明为常量的成员属性。其值在对象初始化完成后,于整个生命周期内不可更改。该特性适用于需要固定数值或防止外部修改的场景,例如定义数学常量或配置参数,有助于确保数据的稳定性和一致性。
常变量:使用const关键字定义常量成员属性,又称常变量。如下代码所示,定义Circle类表示圆,其中包含常量成员属性PI以表示圆周率:classCircle{public:constdoublePI=3.1415926;//常量成员属性,表示圆周率doubleradius;};常量成员常函数:成员函数后添加const关键字后,该函数被称为常函数。当const修饰成员函数时,它表示该成员函数不会修改对象的状态,即不会也不能修改对象的任何非静态成员属性的值。该函数通常用于读取对象的成员属性值。如下代码所示:classMyClass{public:intvalue;voidmodifyValue(){value=10;}voidprintValue()const{//value=20;//错误,常量成员函数中不能修改非静态成员属性cout<<"Value:"<<value<<endl;}};常量成员常对象:常对象的定义方式是在对象申明前添加const关键字。常对象的数据成员在其生命周期内不可被修改。将对象定义为常对象后,其所有非静态数据成员均处于只读状态,任何修改这些数据成员的操作将引发编译错误。如下代码所示:classMyClass{public:intvalue;};intmain(){constMyClassobj{};//obj.value=10;//错误,不能修改常对象的数据成员return0;}常量成员
常对象仅能调用其类中的常量成员函数,而不得调用非常量成员函数。其原因在于,非常量成员函数可能修改对象状态,而常对象的状态不可修改。如下代码所示:classMyClass{public:intvalue;voidsetValue(intnewValue){value=newValue;}voidprintValue()const{cout<<"Value:"<<value<<endl;}};intmain(){constMyClassobj{};//obj.setValue(20);//错误,常对象不能调用非常量成员函数obj.printValue();//正确,常对象可以调用常量成员函数return0;}内联函数
内联函数是一种特殊类别的函数,编译器可能将其调用位置直接替换为函数体对应的代码,以此规避函数调用产生的额外开销,进而提升程序的执行效率。该类型函数通常适用于代码体量较小、调用频次较高的场景,一般使用inline关键字进行声明。如下代码所示://定义内联函数inlineintadd(inta,intb){returna+b;}intmain(){intresult=add(3,5);cout<<"Theresultofadditionis:"<<result<<endl;return0;}Theresultofadditionis:8程序输出如下:思考:为什么不能大规模使用内联函数?struct和class区别在C++语言中,struct与class均可用于定义类(即包含成员属性与成员函数的数据结构)。通常而言,若某一数据类型主要承载数据存储功能,且不涉及复杂的成员函数或封装需求,宜采用struct;常见应用场景包括坐标点、矩形尺寸等轻量级数据结构。反之,若需实现具备复杂行为、严格封装或访问控制的数据类型,则应使用class。它适用于需具备一定复杂性、状态管理及功能行为的对象设计。struct成员默认情况下,struct成员的访问权限为public。在未显式指定访问修饰符时,其成员属性与成员函数均为公有,外部代码可直接访问。如下代码所示:structPoint{intx;inty;};intmain(){Pointp;p.x=10;//可以直接访问x和y,因为它们是public成员p.y=20;return0;}注意:struct在C++中保持了与C语言中struct的兼容性。在C语言中,struct仅能包含数据成员;而在C++中,struct既可包含数据成员,亦可包含成员函数、静态成员等特性。class成员class成员的默认访问权限为private。若未显式指定访问修饰符,则成员属性与成员函数均默认为私有,外部代码不可直接访问。如下代码所示:#include<iostream>usingnamespacestd;classPoint{intx;inty;};intmain(){Pointp;p.x=10;//错误,x是private成员,不能在类的外部直接访问p.y=20;//错误,y是private成员,不能在类的外部直接访问return0;}内部类/类嵌套
内部类(亦称嵌套类)是指在某个类的内部定义另一个类。内部类与外部类的访问权限彼此独立,遵循C++类成员的访问规则。内部类可直接访问外部类的静态成员,但不能直接访问其非静态成员;
部类若要访问内部类的成员,需创建内部类的对象;
内部类的作用域受限于外部类,在外部类之外使用内部类时,需使用外部类名限定内部类名称。
对象的存储结构
在定义C++类并实例化其对象时,内存空间首先依据类定义中声明的顺序依次存储各非静态成员属性。每个类对象均拥有独立的成员变量副本,而成员函数代码仅存一份,该类的所有对象共享此份代码。this指针
在C++中,调用类的成员函数时,编译器将自动向该函数传递一个隐含参数,该参数即为this指针。this指针指向调用该成员函数的对象本身。classPoint{public:intx,y;voidsetX(intnewX){x=newX;}voidsetY(intnewY){this->y=newY;//显式使用this指针}};this指针this指针自身为一个常量指针,其指向的地址无法被修改,然而可通过this指针修改其所指对象的成员属性。如下代码所示:当成员函数中的参数名称与类的成员变量名称相同时,可通过this指针显式访问成员变量。如下代码所示:classMyClass{public:intvalue;voidmodifyValue(){this->value=10;//合法,可通过this指针修改成员属性//this=nullptr;//错误,不能修改this指针的值}};classMyClass{……voidsetValue(intvalue){this->value=value;//通过this指针区分成员属性和参数}};this指针成员函数能够返回this指针或对象的引用,用以实现链式调用或对同一对象的连续操作。如下代码所示:classMyClass{public:intvalue;MyClass&increment(){this->value++;return*this;//返回对象自身的引用}};intmain(){MyClassobj;obj.increment().increment().increment();cout<<"Value:"<<obj.value<<endl;return0;}Value:3程序输出如下:思考:如果在increment函数中returnthis会有什么问题?构造和析构0244第2.2节构造函数和析构函数
在面向对象编程中,构造函数与析构函数是两类至关重要的成员函数,分别负责管理对象生命周期的起始与终结。二者确保对象创建时的正确初始化及销毁时的必要清理工作。
构造函数作为特殊成员函数,其名称必须与类名完全一致,且无需声明返回类型。该函数在对象实例化时由系统自动调用,主要用于初始化对象的成员属性,为对象后续操作做好准备。构造函数的语法形式如下:
class类名{public:类名(){}//默认构造函数类名(){……}//无参构造函数类名(参数){……}//带参数的构造函数};构造函数
在定义类时,若未显式定义构造函数,则编译器将生成默认无参构造函数;当显式定义了类的任一构造函数时,编译器不再提供默认构造函数。
所有对象在定义时都必须调用构造函数,不存在未调用构造函数的对象实例。classRectangle{public:intwidth;intheight;//构造函数Rectangle(intw,inth){width=w;height=h;}};intmain(){Rectanglerect(5,3);//创建Rectangle对象时,自动调用构造函数return0;}构造函数
通过重载构造函数,可提供多种对象初始化方式以满足多样化编程需求,具体表现为定义多个参数列表不同的构造函数。
当一个类仅具备唯一属性,且显式定义了带参数的构造函数时,在其实例化过程中可直接采用等号赋值语法对该属性进行初始化。例如:classMyClass{public:intvalue;MyClass(intv){value=v;}};
10程序输出如下:intmain(){MyClassobj=10;cout<<obj.value<<endl;return0;}构造函数对对象的初始化,不同作用域对象的构造过程如下:全局对象于main函数执行前调用其构造函数;局部对象在每次定义时均调用其构造函数;静态对象在首次定义时调用其构造函数,该对象持续存在且构造函数仅执行一次。析构函数
析构函数也是一种特殊的成员函数,其语法形式如下:析构函数的主要作用为在对象生命周期终结时释放其所占用资源,例如动态分配的内存空间、打开的文件等。class类名{public:~类名(){}};classMyClass{public:int*data;MyClass(){data=newint[10];//动态分配内存}~MyClass(){delete[]data;//释放动态分配的内存}};析构函数当对象的生命周期结束时,其析构函数会被自动调用。对于局部对象,析构函数在所在函数执行完毕时被调用;对于动态分配的对象,析构函数在通过delete运算符执行删除操作时被调用。如下代码所示:intmain(){MyClasslocalObj;//局部对象,函数结束时析构函数自动调用MyClass*ptrObj=newMyClass();deleteptrObj;//使用delete删除动态分配对象时,析构函数自动调用return0;}析构函数析构函数无参数,且每个类仅可存在一个析构函数,不可重载。classMyClass{private:intid;staticintnextId;public:MyClass(){id=nextId++;cout<<"Constructorcalled:"<<id<<endl;}~MyClass(){cout<<"Destructorcalled:"<<id<<endl;}};intMyClass::nextId=0;voidfunc(){MyClassobj1;MyClassobj2;}intmain(){func();return0;}Constructorcalled:0Constructorcalled:1Destructorcalled:1Destructorcalled:0程序输出如下:
拷贝构造函数拷贝构造函数是一种特殊的构造函数,其功能在于创建新对象作为同类型对象的副本。其语法形式如下:拷贝构造函数的调用主要包括以下三种情形:当使用已存在的对象初始化新创建的对象时,系统将执行复制构造函数。
当函数采用按值传递方式接收对象参数时,在函数调用过程中将调用复制构造函数创建临时对象。当函数返回对象时,系统将调用复制构造函数创建临时对象,以复制函数内部局部对象的值并传递给调用者。class类名{public:类名(const类名&源对象){……}};拷贝构造函数阅读以下代码,思考其输出:classMyClass{public:intvalue;MyClass(intv){ value=v; }MyClass(constMyClass&other){value=other.value;cout<<"拷贝构造函数被调用"<<endl;}};//函数按值传递参数时调用拷贝构造函数voidfuncByValue(MyClassobj){cout<<"函数按值传递参数,函数内对象的值:"<<obj.value<<endl;}/函数返回对象时调用拷贝构造函数MyClassfuncReturnObject(){MyClassobj(20);returnobj;}intmain(){MyClassmc1(10);MyClassmc2(mc1);//1.对象初始化:用同类型对象初始化新对象funcByValue(mc1);//2.函数按值传递参数MyClassresult=funcReturnObject();//3.函数返回对象return0;}深拷贝和浅拷贝
浅拷贝是一种对象复制机制。执行浅拷贝时,目标对象将逐成员复制源对象中每个非静态成员的值。目标对象与源对象中的指针成员将指向同一内存区域。
尽管浅拷贝在多数情境下可正常运作,但在涉及动态内存分配等资源管理场景中,其可能引发严重问题。由于多个对象的指针成员共享同一动态分配的内存区域,若其中某一对象在析构时释放了该内存(例如通过delete操作),其他对象的对应指针即成为悬空指针。浅拷贝浅拷贝例题:classPerson{public: intm_age; int*m_height; Person(){ cout<<"无参构造函数!"<<endl; } Person(intage,intheight){ cout<<"有参构造函数!"<<endl; m_age=age; m_height=newint(height); } Person(constPerson&p){ cout<<"拷贝构造函数!"<<endl; //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题 m_age=p.m_age; m_height=p.m_height; }
~Person(){ cout<<"析构函数!"<<endl; if(m_height!=NULL) { deletem_height; } }};voidtest01(){ Personp1(18,180); Personp2(p1); cout<<"p1的年龄:"<<p1.m_age<<"身高:"<<*p1.m_height<<endl; cout<<"p2的年龄:"<<p2.m_age<<"身高:"<<*p2.m_height<<endl;}intmain(){ test01(); return0;}浅拷贝如图2-1所示,假设p1与p2的内存结构。通过浅拷贝操作,p2的m_height指针与p1共享同一堆内存区域。当调用p2的析构函数并释放其m_height所指向的堆内存后,p1的m_height指针将成为悬空指针。后续调用p1的析构函数时,将导致重复释放堆区内存,从而引发程序崩溃。深拷贝
深拷贝是一种更加彻底且安全的对象复制机制。在执行深拷贝时,对于指针类型的成员,不仅复制指针本身的值,还会为目标对象分配与源对象指针所指内存大小相同的新内存空间,并将源数据逐个复制到该新内存中。因此,目标对象与源对象各自拥有完全独立的资源,互不干扰,从而有效避免了因资源共享导致的悬空指针和重复释放等问题。深拷贝深拷贝例题:classPerson{public: intm_age; int*m_height; Person(){ cout<<"无参构造函数!"<<endl; } Person(intage,intheight){ cout<<"有参构造函数!"<<endl; m_age=age; m_height=newint(height); } Person(constPerson&p){ cout<<"拷贝构造函数!"<<endl; m_age=p.m_age; m_height=newint(*p.m_height);//在堆区分配一块新的内存空间,让指针m_height指向新分配的堆内存空间 }
~Person(){ cout<<"析构函数!"<<endl; if(m_height!=NULL) { deletem_height; } }};voidtest01(){ Personp1(18,180); Personp2(p1); cout<<"p1的年龄:"<<p1.m_age<<"身高:"<<*p1.m_height<<endl; cout<<"p2的年龄:"<<p2.m_age<<"身高:"<<*p2.m_height<<endl;}intmain(){ test01(); return0;}深拷贝如图2-2所示,是通过深拷贝操作后p1和p2的内存情况。p1和p2的m_height指针分别指向不同的堆区内存,各自持有独立的堆区内存资源,彼此互不干扰。因此,在析构函数被调用时,不会引发重复释放堆区内存的问题。初始化列表初始化列表是C++类构造函数中用于初始化成员变量的专用语法结构。该机制支持在构造函数体执行前,直接为成员变量赋予初始值,而非于函数体内进行赋值操作。其语法形式如下:常量成员属性与引用成员属性必须在初始化列表中进行初始化,由于二者在定义后均无法再被赋值。类名(参数列表):成员属性1(初始化值1),成员属性2(初始化值2),...,成员属性N(初始化值N){
语句体}初始化列表当成员属性属于具有多个构造函数的类对象,且需调用特定构造函数进行初始化时,初始化列表提供了一种简洁而显式的方式。例如:classClass1{public: intvalue; Class1(intv1,intv2):value(v1+v2){}};classClass2{public: Class1c1; Class2(intv1,intv2):c1(v1,v2){}//使用初始化列表调用Class1构造函数初始化c1};30程序输出如下:intmain(){ Class2c2(10,20); cout<<c2.c1.value<<endl; return0;}继承0362第2.3节继承继承是面向对象编程中的核心机制,使某一类(称为派生类或子类)能够继承另一类(称为基类或父类)的成员变量与成员函数。通过继承机制,派生类可复用基类代码实现,并可在此基础上扩展或重定义其功能。C++不仅支持单继承,亦支持多继承机制,这是C++与某些仅支持单继承的面向对象编程语言(如Java、C#等)的显著区别之处。继承语法(派生类能够继承基类的成员变量与成员函数):class派生类名:继承方式
基类名{
语句体}继承基本语法classShape{public:voiddraw(){cout<<"Drawingashape."<<endl;}protected:intarea;};classRectangle:publicShape{public:voidsetDimensions(intwidth,intheight){this->width=width;this->height=height;}voiddraw(){area=this->width*this->height;cout<<"DrawingaRectangle,weight:"<<width<<",height:"<<height<<",area:"<<area<<endl;}private:intwidth;intheight;};intmain(){ Shapeshape; shape.draw(); Rectanglerect; rect.setDimensions(10,20); rect.draw(); return0; }Drawingashape.DrawingaRectangle,weight:10,height:20,area:200程序输出如下:继承方式C++定义了三种继承方式:公共继承(public)、私有继承(private)与受保护继承(protected)。需注意,结构体(struct)的默认继承方式为公共继承,而类(class)的默认继承方式为私有继承。公共继承(public)基类的public成员在派生类中保持为public成员,能够被派生类对象及外部代码访问。
基类的protected成员在派生类中延续为protected成员,可由派生类的成员函数访问,然而,无法被外部代码访问。
基类的private成员在派生类中无法直接访问,但可以通过基类的public或protected成员函数进行间接访问。继承方式(公共继承)classShape{public:voiddraw(){cout<<"Drawingashape.Coloris"<<color<<endl;}protected:stringcolor;private: stringname;};classRectangle:publicShape{public:voidsetDimensions(intwidth,intheight){this->width=width;this->height=height;}
Theareais15Drawingashape.Colorisred程序输出如下:voidPrintShape(){intarea=width*height;cout<<"Theareais"<<area<<endl;color="red";//类内访问父类protected成员属性//name="Rectangle";//无法访问父类私有成员属性}
private:intwidth;intheight;};intmain(){ Rectangler1; r1.setDimensions(3,5); r1.PrintShape(); r1.draw();//类外访问父类成员函数}继承方式(私有继承)私有继承(private)基类的public和protected成员在派生类中均转换为private成员,仅可在派生类内部访问,派生类对象及外部代码均不可访问。基类的private成员在派生类中仍然不可直接访问。classShape{public:voiddraw(){cout<<"Drawingashape.Coloris"<<color<<endl;}protected:stringcolor;private:stringname;};继承方式(私有继承)classRectangle:privateShape{public:voidsetDimensions(intwidth,intheight){this->width=width;this->height=height;}voidPrintShape(){intarea=width*height;cout<<"Theareais"<<area<<endl;color="red";//类内访问父类protected成员属性
draw();//类内访问父类public成员函数
//name="Rectangle";//无法访问父类私有成员属性 }private:intwidth;intheight;};Theareais15Drawingashape.Colorisred程序输出如下:intmain(){Rectangler1;r1.setDimensions(3,5);r1.PrintShape();//r1.draw();//类外不可访问父类成员函数}继承方式(受保护继承)受保护继承(protected)基类的public和protected成员在派生类中均变为protected成员,仅限于派生类及其子类的成员函数访问;派生类对象以及外部代码均无法访问。
基类的private成员在派生类中仍不可直接访问。
classShape{public:voiddraw(){cout<<"Drawingashape.Coloris"<<color<<endl;}protected: stringcolor;private: stringname;};继承方式(受保护继承)classRectangle:protectedShape{public:voidsetDimensions(intwidth,intheight){this->width=width;this->height=height;}voidPrintShape(){intarea=width*height;cout<<"Theareais"<<area<<endl;color="red";//类内访问父类protected成员属性
draw();//类内访问父类public成员函数//name="Rectangle";//无法访问父类私有成员属性}
private:intwidth;intheight;};Theareais15Drawingashape.Colorisred程序输出如下:intmain(){Rectangler1;r1.setDimensions(3,5);r1.PrintShape();//r1.draw();//类外不可访问父类成员函数}继承方式总结基类成员的权限继承方式publicprotectedprivatepublic在派生类中为public在派生类中为protected在派生类中为private派生类的成员函数和类的作用域之外,都可以直接访问派生类的成员函数可以直接访问派生类的成员函数可以直接访问protected在派生类中为protected在派生类中为protected在派生类中为private派生类的成员函数可以直接访问派生类的成员函数可以直接访问派生类的成员函数可以直接访问private在派生类中被隐藏,无法访问在派生类中被隐藏,无法访问在派生类中被隐藏,无法访问任何方式都不能直接访问,但可以通过基类的public、protected成员函数间接访问任何方式都不能直接访问,但可以通过基类的public、protected成员函数间接访问任何方式都不能直接访问,但可以通过基类的public、protected成员函数间接访问重写
重写(override)是指在派生类中重新定义基类中已有的成员函数,以实现派生类特有的行为。重写的函数须严格保持与基类虚函数相同的函数名、参数列表及返回类型。
当派生类重写基类函数时,该函数在派生类作用域内将隐藏基类中同名的函数。若需在派生类中调用基类被重写的函数,需使用作用域解析运算符(::)显式指定基类名称。重写派生类Derived继承自基类Base,并重写了基类的show函数。例如:classBase{public:voidshow(){cout<<"Base::show()"<<endl;}};classDerived:publicBase{public:voidshow(){cout<<"Derived::show()"<<endl;}voidcallBaseShow(){//调用基类的show函数Base::show();}};Derived::show()Base::show()程序输出如下:intmain(){Derivedderived;//调用派生类的show函数derived.show();//通过派生类的成员函数调用基类的show函数derived.callBaseShow();return0;}继承语法
在派生类中新增成员与基类成员同名的情况下,默认优先调用派生类中的成员。如下代码所示,派生类B继承基类A,二者均包含同名数据成员x及成员函数Show。classA{public:intx;voidShow(){cout<<"A.x="<<x<<endl;}};classB:publicA{public: intx;inty;voidShow(){ cout<<"B.x="<<x<<endl;cout<<"B.y="<<y<<endl;}};B.x=100B.y=200A.x=300b1.A::x=300程序输出如下:intmain(){Bb1;b1.x=100;b1.y=200;b1.Show();
b1.A::x=300;//给基类A中的x赋值b1.A::Show();//用作用域运算符调用基类的函数cout<<"b1.A::x="<<b1.A::x<<endl;//输出基类A中的x值return0;}继承中构造和析构在继承体系中,构造函数与析构函数的调用遵循以下规则:派生类对象构造时,首先调用基类的构造函数完成基类部分的初始化,继而初始化派生类自身的成员变量。
若派生类的构造函数未显式指定基类的构造函数,编译器将隐式调用基类的默认构造函数。
析构函数的执行次序与构造函数相反:首先执行派生类的析构函数,随后调用基类的析构函数。其不接收参数,亦不可被重载。继承中构造和析构
可在派生类的构造函数中显式调用基类构造函数。例如:classAnimal{public:Animal(intage):age(age){ cout<<"Animal年龄:"<<age<<endl; }private:intage;};classDog:publicAnimal{public:Dog(intage,intbreed):Animal(age),breed(breed){ cout<<"Dog品种编号:"<<breed<<endl; }private:intbreed;};Animal年龄:3Dog品种编号:101程序输出如下:intmain(){DogmyDog(3,101);return0;}继承中构造和析构当派生类中申明了基类成员对象时,在创建派生类对象的过程中,这些成员对象会被自动创建。基类成员对象的构造函数将在派生类构造函数之前被调用,其整体调用顺序如下:基类的构造函数子对象类的构造函数派生类的构造函数继承中构造和析构
classBase{public:intx;Base(inta){x=a;cout<<"调用基类的构造函数!\n";}~Base(){cout<<"调用基类的
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026-2030中国养生酒行业营销状况与投资盈利预测报告
- 虚拟现实游戏开发实战手册
- 生产车间事故紧急处理方案
- 安全运维智能系统部署标准化指南
- 农业大数据与种植管理系统结合方案
- 员工个人行为准则与职业道德承诺书5篇范文
- 房地产销售团队培训大纲指导书
- 大型建设项目招标采购流程与管理手册
- 广东省广州市番禺区2025-2026学年八年级下学期期中数学试卷(含答案)
- 基本健康医疗保障承诺书8篇
- 2026年同等学力申硕英语模拟卷
- 摩根士丹利 -半导体:中国AI加速器-谁有望胜出 China's AI Accelerators – Who's Poised to Win
- 2026辽宁沈阳汽车集团有限公司所属企业华亿安(沈阳)置业有限公司下属子公司招聘5人笔试历年参考题库附带答案详解
- 2025~2026学年江苏镇江市第一学期高三“零模”化学试卷
- 2026年公路养护工职业技能考试题库(新版)
- 2026中国广播影视出版社有限公司高校毕业生招聘3人备考题库含答案详解(完整版)
- 宜宾市筠连县国资国企系统2026年春季公开招聘管理培训生农业考试模拟试题及答案解析
- 2026年福建南平市八年级地生会考考试真题及答案
- 2025-2030非洲智能汽车零部件行业市场供需理解及投资潜力规划分析研究报告
- 2026季华实验室管理部门招聘3人(广东)建设笔试模拟试题及答案解析
- 北京市大兴区瀛海镇人民政府招聘劳务派遣4人考试参考试题及答案解析
评论
0/150
提交评论