版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第十三章类与对象(二)C++程序设计——大模型思维与实践内容导航类与对象(二)静态成员运算符重载友元类模板常成员与常对象大模型探究拷贝构造函数本章小结313.2静态成员类的静态成员静态成员可以用来解决数据共享问题,实现多个对象之间的数据共享。一个类无论有多少个对象,它们共享同一个静态成员。静态成员包括静态数据成员和静态成员函数。4静态数据成员类与对象的关系:类与对象的关系类似于数据类型与变量的关系。对象是类的一个实例,每个对象拥有自己独立的存储空间。实例属性:每个对象都拥有自己的存储空间,以保存自身的属性值,这意味着每个对象都有自己独立的一份属性数据,即实例属性。不同对象的实例属性可以有不同的值,实例属性用来保存对象的状态或特征。例如,Student类中定义有“学号”、“姓名”等属性,使用该类定义30个对象,则每个对象都有自己的学号和姓名。类属性:描述类的所有对象的共同特征的数据项,在C++中称为静态数据成员或静态成员变量。静态数据成员与类的实例属性不同,不依赖于类的任何特定对象的存在。属于类本身,而不属于类的某个具体对象。13.2静态成员5静态数据成员静态数据成员的定义分为两个必不可少的部分:类内声明、类外初始化。在类内,声明静态数据成员的格式为:在类外初始化的形式为:
数据类型类名::静态数据成员名=初始值;
static
数据类型静态数据成员名;静态数据成员的初始化必须在类外进行(包括private成员和protected成员),且只能初始化一次。不加static关键字限定所属的类6静态数据成员例13-1:定义静态数据成员的方法:包括两个必不可少的部分。class
Student{
intNo;
stringname;public:
static
intcount;//类内声明};int
Student::count=0;//类外初始化实例属性,每个对象(学生)都有自己的一个属性类属性,统计该类有多少个对象,不管有多少个对象,count只有一个类外初始化,千万不要忘!不加static关键字限定所属的类7静态数据成员例13-2:公有静态数据成员的使用。class
Student{
intNo;stringname;public:
static
intcount;//类内声明Student(int
no=0,string
name="")
{
this->No=no;this->name=
name;count++;
}};int
Student::count=0;//类外初始化intmain(){//未创建对象,只能通过“类名::静态成员名”访问cout<<"初始时有"
<<
Student::count<<
"个学生"<<endl;
Students1(1,"zhangsan");
Students2(2,"lisi");cout<<"总共创建了"<<Student::count<<"个学生"<<endl;
//也可通过对象访问cout<<
"使用对象访问静态成员:"
<<
s1.count<<endl;
return0;}静态数据成员的访问方式类名::静态数据成员名;对象名.静态数据成员名(或对象指针->静态数据成员名)(不推荐)在类的成员函数中直接访问,不需加“类名::”或“对象名.”运行结果:初始时有0个学生总共创建了2个学生使用对象访问静态成员:2类名::静态数据成员名对象名.静态数据成员名成员函数中直接访问8静态数据成员静态数据成员的特性唯一性:每个类只有一个静态数据成员的拷贝,所有对象共享同一份。共享性:所有对象均可访问和修改静态数据成员,适合跨对象共享数据,可看作对象之间的全局变量。访问方式:可以通过类的对象进行访问,但推荐通过类名访问。存储位置:静态数据成员存储在程序的静态存储区,而非对象中。可见性:遵循普通成员变量的访问规则:在类的成员函数中可以访问所有静态成员;在类外,通过对象或类名只能访问public的成员。9静态成员函数静态成员函数可以直接通过类名调用,无需创建对象。静态成员函数只能访问类的静态成员,不能访问非静态成员,也不允许使用this指针静态成员函数静态成员函数的主要作用是提供一种与类关联但不依赖于对象实例的功能,适用于需要操作类的静态数据或执行与对象无关的逻辑的场景。静态成员函数属于类本身,不依赖于类的某个具体对象与普通成员函数的区别:10静态成员函数class
类名{访问控制符:
static
返回值类型静态成员函数名(参数表);};返回值类型类名::静态成员函数名(参数表){…}静态成员函数的定义类内定义:可直接在类中定义,在成员函数前加关键字static即可static
返回值类型静态成员函数名(参数表){…};类外定义:可以在类内声明为static,在类外定义。当在类外定义时,不能再加static关键字。对静态成员函数的访问权限由访问控制符决定不加static关键字限定所属的类11静态成员函数3.静态成员函数的访问方式(1)通过类名访问:
类名::静态成员函数名(参数列表);(2)通过对象调用:(不推荐)
对象名.静态成员函数名(参数列表);
对象指针->静态成员函数名(参数列表);(3)在本类的成员函数中可以直接访问,不需加“类名::”或“对象名.”。12例13-3:展示如何定义一个静态成员函数并调用它。class
Student{
intNo;
stringname;
static
intcount;public:Student(int
no=0,string
name=""){
this->No=no;this->name=
name;count++;
}static
voidshowCount(){cout<<
"创建的对象个数:"
<<
count<<endl;
}
/*staticvoidshowName(){cout<<"nameis:"<<name<<endl;}*/};int
Student::count=0;intmain(){
Student::showCount();//使用类名访问
Students1(1,"zhangsan");
Student::showCount();//使用类名访问
Students2(2,"lisi");s2.showCount();//使用对象s2访问(不推荐)
StudentsArr[10];s1.showCount();//使用对象s1访问(不推荐)
return0;}运行结果:创建的对象个数:0创建的对象个数:1创建的对象个数:2创建的对象个数:12
静态成员函数私有静态数据成员,在main函数中不能直接访问,可通过公有函数间接访问输出私有count的值错误!静态成员函数不能访问非静态成员(包括数据成员与成员函数)通过类名调用静态成员函数通过对象名调用静态成员函数内容导航类与对象(二)静态成员运算符重载友元类模板常成员与常对象大模型探究拷贝构造函数本章小结1413.3友元类的封装性与安全性类的封装性:通过私有成员的限制,确保数据安全与隐藏。仅类的成员函数能访问其私有成员。特殊需求:跨类协作场景:类间协作或外部函数需直接访问私有数据。问题:通过公有成员函数间接访问时,由于参数传递、类型检查等都需占用时间,会影响效率。为了解决这些需求,同时兼顾类的封装性,C++提供了友元机制友元声明为友元的外界对象既可以是另一个类的成员函数,也可以是不属于任何类的全局普通函数,称之为友元函数。友元也可以是整个的一个类,称之为友元类。友元不是类的成员,但可以访问类的任何成员。15友元函数友元函数是指被某个类声明为友元的非成员函数。可以将一个全局的普通函数或者另一个类的成员函数声明为自己的友元函数。友元函数全局的普通函数声明为一个类的友元函数类内声明、类外定义格式:classA{访问控制符:friend返回类型
函数名(形参表);}返回类型
函数名(形参表){…//函数体}类内定义格式:classA{访问控制符:friend返回类型
函数名(形参表){…//函数体}}友元函数不是成员函数,不受访问控制符的影响。需加上friend关键字,否则就是成员函数的定义。类内声明加上friend关键字类外定义不加friend关键字单独看,就是一个全局函数的定义16友元函数例13-4:友元函数的使用。classCar{private:stringbrand;intyear;//声明一个友元函数friend
voiddisplayCar(Car&car);public:Car(stringb,inty):brand(b),year(y){}};//友元函数定义在类外部voiddisplayCar(Car&car){cout<<"Brand:"<<car.brand<<",Year:"<<car.year<<endl;}intmain(){CarmyCar("大众",2020);displayCar(myCar);return0;}友元函数必须将对象作为友元函数的参数,并在函数体中用运算符.或->来访问对象的成员。友元函数不是类的成员,因此没有this指针,如displayCar函数中不能使用this->year!虽然访问控制符为private,不影响可访问性,可在main中调用友元函数可以直接访问Car类对象的私有成员使用displayCar(myCar)调用该函数并将对象作为实参传递给该函数,而不是“myCar.displayCar()”17友元函数A类的成员函数被声明为B类的友元函数classB;//前向引用声明classA{//A类的定义public://外部接口
voidfunA(Bb);//以B类对象b为形参的成员函数};classB{//B类的定义public://外部接口
friendvoidA::funA(Bb);//声明友元};voidA::funA(Bb){}//A的成员函数funA的定义由此A类的该成员函数就能访问B类的所有成员。语法格式:引用实现类A的成员函数funA需要引用类B,而类B中需要将A的funA声明为友元函数,两个类相互引用,必然有一个类在定义之前就将被引用,怎么解决?前向引用声明!18友元函数【例13-5】Car类的成员函数作为Engine类的友元函数。
//前向声明Engine类class
Engine;class
Car{private:
stringbrand;public:Car(string
b):brand(b){}
//Car类的成员函数,作为Engine类的友元函数
voiddisplayPower(Engine
eng);//这里使用前向引用声明的Engine};提前告诉编译器Engine是一个类,
但不明确Engine具体包含什么成员
此处用到了Engine类,所以需要提前声明
19友元函数class
Engine{private:intpower;public:Engine(int
hp):power(hp){}
//声明Car类的成员函数displayPower为友元函数
friend
void
Car::displayPower(Engine
eng);};//声明为友元的成员函数在Engine类之后定义,可访问Engine类的私有成员void
Car::displayPower(Engine
eng){cout<<
"brand:"
<<brand<<
",power:"
<<
eng.power<<endl;}intmain(){
EnginemyEngine(300);//创建一个Engine对象
CarmyCar("byd");//创建一个Car对象myCar.displayPower(myEngine);
return0;}访问Engine的成员,所以本函数必须定义在Engine类之后,否则编译时报错“errorC2027:使用了未定义类型Engine”。声明为友元函数若类A是类B的友元类,则类A的所有成员函数都是类B的友元函数。声明类A是类B的友元类的格式:class
B{…friend
class
A;…};20友元类2.规则友元类是指将某个类A声明为另一个类B的友元,从而允许类A的所有成员函数访问类B的所有成员(包括私有和保护成员)。友元类1.定义
使用friend关键字21【例13-6】Car类被声明为Engine类的友元类。
class
Engine{private:
intpower;public:Engine(int
hp):power(hp){}
friend
class
Car;};class
Car{private:
Engineengine;
stringbrand;public:Car(string
b,int
hp):brand(b),engine(hp){}
voiddisplay(){
//直接访问Engine的私有成员powercout<<
"brand:"
<<brand<<
",power:"
<<engine.power<<endl;
}};intmain(){
CarmyCar("byd",200);myCar.display();
return0;}友元类Car类成员函数可以访问Engine类的私有成员声明Car为Engine的友元类友元关系是授予的而不是索取的。也就是说,如果函数f要成为类A的友元,类A必须显式声明函数f是他的友元,而不是函数f自称是类A的友元。友元关系不是对称关系,如果类A声明了类B是它的友元,并不意味着类A也是类B的友元。友元关系不是传递关系。如果类A是类B的友元,类B是类C的友元,并不意味着类A是类C的友元。提高了数据的共享性,加强了函数与函数之间、类与类之间的相互联系,大大提高程序的效率,这是友元的优点。破坏了数据隐蔽和数据封装,导致程序的可维护性变差,给程序的重用和扩充埋下了隐患,这是友元的缺点。对友元的使用必须慎重,在提高效率和增加共享之间把握好一个“度”。友元的优缺点友元特点友元内容导航静态成员运算符重载友元类模板常成员与常对象大模型探究拷贝构造函数本章小结类与对象(二)24常数据成员const关键字用于表示常量性。它可以用于限定对象、函数参数、函数返回值和类的成员等,确保程序中的某些数据不被修改。常数据成员常数据成员(const数据成员)是使用const关键字修饰的数据成员。常数据成员的定义与一般常变量的定义方式相同,只是它的定义必须出现在类体中。常数据成员定义的格式如下:常数据成员在对象创建时必须被初始化,并且不可修改。
数据类型
const数据成员名;或
const数据类型数据成员名;初始化方式1:通过构造函数初始化列表进行初始化,可以在创建对象时传入值常数据成员classA{constintSIZE;public://只能使用初始化列表,不能在构造函数体中赋值A(intsize):SIZE(size){}};intmain(){Aa(100);//对象a的SIZE的值为100Ab(200);//对象b的SIZE的值为200}初始化方式2:直接初始化。C++11及以上标准支持该方式,内部机制仍然是通过构造函数的初始化表进行初始化,即:编译时不赋值,运行创建对象时才赋值。classA{constintSIZE=200;//错误,编译时SIZE并没有值intarray[SIZE];};26【例13-7】常数据成员的使用。class
Car{private:const
int
currentYear
=
2025;conststringbrand;int
year;public:Car(stringb,inty):brand(b),year(y){}void
display(){cout<<
"Brand:"
<<
brand
<<
",Years:"
<<
currentYear-year
<<endl;//currentYear=2026;}};int
main(){
//创建对象,初始化常成员变量Car
myCar(“byd",2021);
//输出对象信息myCar.display();
return
0;}常数据成员错误,常数据成员不允许被修改方式2初始化方式1初始化27常成员函数常成员函数:成员函数声明的末尾加上const关键字。不能修改对象的任何数据成员,同时也不能调用任何非常成员函数。常成员函数的特点保证了对象的状态不会被改变,适合只执行读取操作、不进行修改操作的场景。如果在编写const成员函数时,不慎修改了数据成员,编译器将指出错误,将提高程序的健壮性。常成员函数28常成员函数常成员函数可以在类内定义,也可以在类内声明、类外定义函数体。如果函数体在类外实现,const必须在类内声明和类外实现时都指明,即const属于函数原型的一部分而不仅是修饰词。常成员函数不允许修改类的数据成员,也不允许调用该类中没有用const修饰的成员函数。const关键字可以用于参与重载函数的区分。即:如果两个成员函数的函数头部完全相同,其中一个加了const,一个没加,则它们是重载函数,允许同时存在。
常成员函数定义的语法格式:class类名{public:
类型名
函数名()const;//类内声明常成员函数};类型名
类名::函数名()const{函数体}//类外定义函数体。函数体也可定义在类内。都指明29例13-8:常成员函数的使用class
Car{private:
const
intcurrentYear=2025;
const
stringbrand;
intyear;public:Car(string
b,int
y):brand(b),year(y){}
voiddisplay()const;
intgetYear()const{returnyear;}
intgetYear(){returnyear++;}
stringgetBrand(){returnbrand;}};void
Car::display()const{cout<<
"Brand:"
<<getBrand()<<endl;cout<<
",Years:"
<<currentYear-year<<endl;cout<<
"Years:"
<<currentYear-getYear()<<endl;}常成员函数常成员函数getYear()const定义在类内。注意,该函数不能修改year非常成员函数getYear()可以修改year常成员函数display()在类内的声明和类外的定义末尾都需要加const。错误:常成员函数不能调用非常成员函数自动选择const的getYear()版本30如果某个对象不允许被修改,则该对象称为常对象。C++用关键字const来定义常对象,表示该对象的任何数据成员都不能被修改。常对象一旦定义,在其生存期内不允许改变,否则将导致编译错误。
常对象的定义语法格式:常对象由于const对象的状态不能被修改,通过常对象只能调用类的常成员函数以及类的静态成员函数,不允许调用该类中没有用const修饰的普通成员函数。
类名
const
对象名;或const类名
对象名;31【例13-9】常对象的使用。class
Car{private:
stringbrand;
intyear;public:Car(string
b,int
y):brand(b),year(y){}
voiddisplay()const{
//常成员函数:不修改对象的数据cout<<
"Brand:"
<<brand<<
",Year:"
<<year<<endl;
}
voidsetYear(int
y){year=y;}
intgetYear(){returnyear++;}
intgetYear()const{returnyear;}};常对象32voidprintCar(const
Car&car3){//函数参数为常对象
car3.display();//只能调用常成员函数
//car3.setYear(2021);//错误:不能调用非常成员函数cout<<
car3.getYear()<<endl;//自动选择成员函数的const重载版本}intmain(){
const
Carcar1("qirui",2020);//常对象car1.display();//只能调用常成员函数
//car1.setYear(2021);//错误:不能调用非常成员函数cout<<car1.getYear()<<endl;//自动选择成员函数的const重载版本
Carcar2("byd",2024);//普通对象
//car1=car2;//错误:常对象不能被赋值car2.display();//普通对象可以调用常成员函数cout<<car2.getYear()<<endl;//自动选择成员函数的非const版本printCar(car2);
return0;}常对象33class
Clock{public:int
H,M,S;Clock(int
h=0,int
m=0,int
s=0):H(h),M(m),S(s){}void
setH(int
h){H
=
h;}void
display()const{ cout<<
H
<<
":"
<<
M
<<
":"
<<
S
<<endl;}void
fun()const{setH(10);}//调用非常成员函数,错误};常对象通过Clock类,理解C++对常对象的防修改机制,从而理解前述概念。(1)常对象不允许被赋值
(2)常对象的数据成员不允许被赋值
(4)常成员函数不能修改任何成员变量
(3)常对象只能调用常成员函数
Clockc1;constClockc2;c2=c1;//错误constClockc2;c2.H=10;//错误(5)常成员函数只能调用常成员函数
内容导航静态成员运算符重载友元类模板常成员与常对象大模型探究拷贝构造函数本章小结类与对象(二)3513.5拷贝构造函数拷贝构造函数是一种特殊的构造函数,允许开发者使用一个已存在的对象初始化一个新建立的对象。拷贝构造函数名与类名相同,其形参是本类的对象的引用,形参通常采用常量引用(constClassName&)的形式。用户可以根据自己的需要定义拷贝构造函数例13-10:定义Point类的拷贝构造函数class
Point{
intx,y;public:Point(int
a,int
b){x=a;y=b;}Point(const
Point&p){x=p.x;y=p.y;}}3613.5拷贝构造函数定义拷贝构造函数的语法格式:类名
(const类名
&另一个对象){……}
//函数体完成对应数据成员的赋值类的名称必须加引用符号要拷贝的对象
如果不加const,则不能拷贝常对象此即默认拷贝构造函数的功能,实现数据成员的一一对应拷贝。因此可省略。如果用户没有定义拷贝构造函数,系统会提供一个默认拷贝构造函数。该函数将已存在的对象的数据成员的值一模一样地复制给新对象。如果自己定义了拷贝构造函数,C++不再提供默认拷贝构造函数,也不会提供默认构造函数。
默认拷贝构造函数3713.5拷贝构造函数调用时机用类的一个已存在的对象去初始化该类的另一个对象时,拷贝构造函数被自动调用。以下三种情况系统将自动调用拷贝构造函数:①函数参数传递时:如果函数的形参是类的对象,调用函数时,将对象作为函数实参传递给函数的形参(传值方式)。②函数返回值时:如果函数的返回值是类的对象,函数执行完成,将一个对象返回。③创建对象时:如果用类的一个对象去初始化该类的另一个对象。3813.5拷贝构造函数三种情况:(1)通过值传递将对象传递给函数当对象以值传递的方式传递给函数时,C++会将实参复制给形参。此时,拷贝构造函数会被自动调用以实现复制功能。传引用与传指针都不会创建新对象,因此不会调用拷贝构造函数。39class
MyString{private:char
str[100];int
len;public:void
ShowStr(){cout<<
"string:"
<<
str
<<
",length:"
<<
len
<<endl;}MyString(){len
=
0;str[0]='\0';}MyString(const
char*
p){len
=
strlen(p);strcpy(str,p);}MyString(const
MyString&
other){//拷贝构造函数len
=
other.len;strcpy(str,other.str);cout<<
str
<<
"copied"<<endl;}};例13-11:通过值传递将对象传递给函数。13.5拷贝构造函数void
displayStr(MyString
s){
s.ShowStr();}int
main(){MyStrings1("hello");displayStr(s1);displayStr(MyString("China"));return
0;}运行结果:hellocopiedstring:hello,length:5string:China,length:5C++编译器使用了优化技术,有些情况不调用拷贝构造函数,从而提高运行效率。按值传递,自动调用拷贝构造函数实参为常对象,因此拷贝构造函数形参必须加const默认拷贝构造函数可以实现数据成员的一一复制,因此本例可不定义拷贝构造函数使用输出语句可观察到拷贝构造函数被调用40MyString
createString(int
choice){MyStrings1("hello");MyStrings2("China");if(choice==1)return
s1;else
return
s2;}int
main(){MyStrings
=
createString(2);return
0;}例13-12:如下代码(MyString类使用例13-11的定义),createString返回一个MyString对象,需要调用拷贝构造函数。13.5拷贝构造函数运行结果:Chinacopied(2)对象作为函数的返回值
执行到createString的return语句时,会创建一个MyString类的临时对象,并调用拷贝构造函数用对象s2初始化该临时对象,将此临时对象的值作为返回值。C++编译器使用了优化技术,有些情况不调用拷贝构造函数。比如:
MyStringcreateString(intchoice){MyStrings1("hello");returns1;}41例13-13:用一个现有的MyString类对象直接初始化另一个新建的对象。13.5拷贝构造函数运行结果:hellocopiedhellocopied(3)用一个对象直接初始化另一个对象。
int
main(){MyStrings1("hello");MyStrings2
=
s1;//拷贝s1,调用拷贝构造函数MyStrings3(s1);//拷贝s1,调用拷贝构造函数return
0;}MyString
s2;s2=s1;初始化方式:1、用“=”符号,如:
MyStrings2=s1;定义对象时用s1初始化s22、将初始值放在圆括号中,如:
MyStrings3(s1);修改成执行第2行代码时,s2不是新对象,从而不调用拷贝构造函数,而是执行赋值操作。
42浅拷贝和深拷贝浅拷贝默认的拷贝构造函数采用
浅拷贝策略,将已存在的同类对象的数据成员依次复制给新对象对应的数据成员。当对象成员为普通数据类型或者数组时,以上策略有效。当对象中有指针成员及动态分配的内存时,浅拷贝将导致两个对象的指针成员指向同一块内存区域。由此将导致两个方面的错误(见示例)。43例13-14:定义MyString类,指针成员pStr指向动态分配的内存。系统提供的默认拷贝构造函数执行浅拷贝操作,将导致错误。class
MyString{private:char*
pStr;int
len;public:MyString(const
char*
p){len
=
strlen(p);pStr
=
new
char[len
+
1];strcpy(pStr,p);}~MyString(){delete[]
pStr;}void
modifyStr(){pStr[0]=
'A';}void
showStr(){cout<<
pStr
<<endl;}};浅拷贝和深拷贝int
main(){MyString
s1("hello");MyString
s2
=
s1;//自动调用拷贝构造函数s2.modifyStr();s1.showStr();s2.showStr();return
0;}错误1:s2.modifyStr()修改了s2;调用s1.showStr()时发现s1字符串也跟着被修改了。错误2:程序结束时,对象s2和s1被释放,程序崩溃44原因分析浅拷贝和深拷贝执行MyStrings2=s1;
时,s2的所有成员获得s1所有成员的值
s1.pStr为一个指针,存储了一个动态分配数组的首地址
(“地址A”)s2.pStr的值也为“地址A”,从而s2.pStr和s1.pStr都指向同一个数组。修改s2时,首地址为A的数组内容被修改为Aello,s1.pStr也为该数组,所以观察到s1的内容也被修改s2和s1的析构函数分别被调用,都将执行delete[]pStr,导致该动态分配的数组被释放两次,从而引发重复释放内存的运行时错误
比较:例13-13拷贝后,s2.str和s1.str数组为两个独立的数组
class
MyString{private:char
str[100];int
len;}45期望的拷贝形式浅拷贝和深拷贝将首地址为A的数组拷贝一份到首地址为B的数组s2.pStr内存储的值不要为地址A,而应该为地址B。如此,两个对象的指针成员指向了不同的内存区域,它们互相独立。总结:我们期望将数组拷贝一份,而非简单地将s1.pStr的值拷贝给s2.pStr。需要自定义拷贝构造函数,以实现深拷贝!46浅拷贝和深拷贝深拷贝深拷贝是C++中一种重要的对象复制机制,指在复制对象时,不仅复制对象本身的数据成员,还为动态分配的内存资源(如指针指向的堆内存)创建独立的副本,使得复制后的对象与原对象完全独立,互不影响。MyString(const
MyString&
other){len=
other.len;pStr=
new
char[len+
1];strcpy(pStr,other.pStr);}例13-15:为例13-14的MyString类定义拷贝构造函数,解决共享数组的错误。执行MyStrings2=s1;时拷贝构造函数被自动调用
。s1传递给了拷贝构造函数的other参数,在函数体中被赋值的len和pStr为s2的数据成员47class
Student{intNo;stringname;static
intcount,living;public:Student(){No=0;name=
"";count++;living++;}Student(int
no,string
name){this->No=no;this->name=
name;count++;living++;}Student(const
Student&s){No=s.No;name=
s.name;count++;living++;}~Student(){living--;}};int
Student::count=0;int
Student::living=0;在程序的某个执行阶段,我们需要统计某个类的实例创建总数以及当前仍然存活的实例数量。这不仅要考虑通过常规构造函数生成的对象,还要包括通过拷贝构造函数产生的对象。例:在程序执行的某个时刻,统计Student类已创建的对象个数,以及现在仍存活的对象个数。Student类部分代码如右,完整代码及详细解释请阅读进阶内容。应用实例:对象计数定义两个静态的数据成员:count和living。创建对象时两个计数器各加1拷贝构造时对象数也增1注意:系统不再提供默认拷贝构造函数,因此需要在函数体内复制No和name两个成员释放对象时,living减1内容导航静态成员运算符重载友元类模板常成员与常对象大模型探究拷贝构造函数本章小结类与对象(二)4913.6运算符重载预定义运算符(例如+、-、<、>等)适用于基本数据类型(如整型、浮点型),无法直接应用于用户自定义类型。实际开发中,我们常常需要对类的对象进行类似于基本数据类型的运算操作,例如复数类、有理数类。为了使用户自定义类型也能够以自然的方式使用运算符,C++提供了运算符重载功能。例如:希望让自定义的复数类Complex能够实现加法操作:Complex
a1,a2,a3;a3
=
a1
+
a2;通过运算符重载,我们可以使自定义类的对象像基本数据类型的变量一样使用。5013.6运算符重载运算符转换成函数调用:(1)对于一元运算符,表达式@obj被转换为:成员函数:obj.operator@()全局/友元函数:operator@(obj)(2)二元运算符,表达式obj1@obj2被转换为:成员函数:
obj1.operator@(obj2)全局/友元函数:operator@(obj1,obj2)举例:-a
(负a)转换为成员函数:a.operator-()全局/友元函数:operator-(a)举例:a+b
转换为成员函数:
a.operator+(b)全局/友元函数:operator+(a,b)运算符重载的本质就是编写一个函数来解释某个运算符在某个类中的含义。要使系统能够自动找到我们编写的运算符函数,函数名必须要体现出与被重载运算符的联系。运算符函数可以重载为该类的成员函数或者一个全局函数(通常为该类的友元函数,从而可以访问类的所有成员)。C++编译器在处理运算符时,会将其转换成特定的函数调用形式:形式:函数名称为
operator@(@
为运算符)例如:
重载加法运算符
+,函数名为
operator+51重载为成员函数可以将运算符函数重载为类的成员函数,从而可以自由地访问本类的所有数据成员。成员函数重载运算符的语法格式:class类名{返回类型operator运算符(形参表){
//函数体,运算符重载的实现}}举例:-a
(负a)转换为成员函数:a.operator-()举例:a+b
转换为成员函数:
a.operator+(b)要重载该运算符的类两者共同构成运算符重载函数名运算符计算结果的类型一元运算符的形参表为空,二元运算符的形参为右操作数。52例13-16:定义一个复数类,并能对复数对象执行二元运算符+、-操作和一元运算符负号-操作。要求将运算符重载为成员函数。复数类Complex的设计思路与要点:类的定义:Complex类包含两个私有成员变量real和imag,分别表示复数的实部和虚部。构造函数Complex(doubler=0,doublei=0)可以初始化复数对象,默认值为0。运算符重载:Complexoperator+(constComplex&other):重载加法运算符,使得能执行“c1+c2”之类的操作,编译器将执行函数调用c1.operator+(c2),使得c1作为当前对象,c2作为实参传给函数形参other,在函数体中完成加法操作,最后返回一个Complex类型的值。Complexoperator-(constComplex&other):重载减法运算符,使得能执行“c1-c2”之类的操作。Complexoperator-():重载一元负号运算符,使得能执行“-c1”之类的操作。重载为成员函数53重载为成员函数class
Complex{private:
double
real,imag;public:Complex(double
r
=
0,double
i
=
0):real(r),imag(i){}Complex
operator+(const
Complex&
other){
return
Complex(real
+
other.real,imag
+
other.imag);}Complex
operator-(const
Complex&
other){
return
Complex(real
-
other.real,imag
-
other.imag);}Complex
operator-(){return
Complex(-real,-imag);}void
display(){if(imag
==
0)
cout<<
real
<<endl;else
if(imag
>
0)
cout<<
real
<<
"+"
<<
imag
<<
"i"
<<endl;else
cout<<
real
<<
imag
<<
"i"
<<endl;}};intmain(){
Complexc1(2.1,3.5);
Complexc2(3.2,5.8);
Complexc3;c3=c1+c2;//实际解释为c3=c1.operator+(c2);c3=c1.operator+(c2);c3.display();c3=c2-c1;//实际解释为c3=c2.operator-(c1);c3=c2.operator-(c1);c3.display();c3=
-c1;//实际解释为c3=c1.operator-();c3=c1.operator-();c3.display();
return0;}引用(&)可以避免传值操作产生的拷贝const保证在函数内部不会修改other对象,并且可接受const对象作为实参运行结果:5.3+9.3i1.1+2.3i-2.1-3.5i54重载为友元函数可重载为全局函数,最好设为友元函数。这样可以自由地访问该类的任何数据成员。友元(全局)函数重载运算符的语法格式:friend
返回类型operator运算符
(形参表){
//函数体,运算符重载的实现}举例:-a
(负a)转换为全局/友元函数:operator-(a)举例:a+b
转换为全局/友元函数:operator+(a,b)friend关键字声明该运算符函数为友元函数一元运算符的形参表有一个参数,二元运算符的形参表有两个参数,比重载为成员函数时多一个也可以在类中声明友元函数,在类外定义。55【例13-17】:复数类二元运算符+、-和一元运算符负号-重载为友元函数。运算符重载为全局函数的原型为:friendComplexoperator+(Complexcomp1,Complexcomp2):重载加法运算符,使得能执行“c1+c2”之类的操作,编译器将执行函数调用operator+(c1,c2),将c1和c2都作为实参传给函数形参,在函数体中完成加法操作,最后返回一个Complex类型的值。friendComplexoperator-(Complexcomp1,Complexcomp2);重载减法运算符,使得能执行“c1-c2”之类的操作。friendComplexoperator-(Complexc):重载一元负号运算符,使得能执行“-c1”之类的操作。重载为友元函数56重载为友元函数class
Complex{private:
double
real,imag;public:Complex(double
r
=
0,double
i
=
0):real(r),imag(i){}friend
Complex
operator+(Complex
comp1,Complex
comp2){
return
Complex(comp1.real
+
comp2.real,comp1.imag
+
comp2.imag);}friend
Complex
operator-(Complex
comp1,Complex
comp2);friend
Complex
operator-(Complex
c){
return
Complex(-c.real,-c.imag);}void
display(){if(imag
==
0)cout<<
real
<<endl;else
if(imag
>
0)
cout<<
real
<<
"+"
<<
imag
<<
"i"
<<endl;elsecout<<
real
<<
imag
<<
"i"
<<endl;}};//类外定义友元函数Complex
operator-(Complex
comp1,Complex
comp2){
return
Complex(comp1.real
-
comp2.real,comp1.imag
-
comp2.imag);}57重载为友元函数intmain(){
Complexc1(2.1,3.5);
Complexc2(3.2,5.8);
Complexc3;c3=c1+c2;//实际解释为c3=operator+(c1,c2);c3=
operator+(c1,c2);c3.display();c3=c2-c1;//实际解释为c3=operator-(c2,c1);c3=
operator-(c2,c1);c3.display();c3=
-c1;//实际解释为c3=operator-(c1);c3=
operator-(c1);c3.display();
return0;}运行结果:5.3+9.3i1.1+2.3i-2.1-3.5i58成员函数和友元函数重载运算符总结(1)命名规范都需严格遵循operator@的命名格式,其中@代表被重载的运算符符号。例如:重载运算符"-"时,函数名为operator-。重载运算符"++"时,函数名为operator++。(2)操作数传递方式重载为友元函数时,所有的操作数都需要作为参数传递给运算符函数重载为成员函数时,第一个操作数隐含地传给了调用函数的对象,因此成员函数比友元函数少一个参数。(3)编译器转换
当编译器遇到表达式c3=c2-c1时,会进行如下转换:若"-"被重载为成员函数:转换为c3=c2.operator-(c1)若"-"被重载为友元函数:转换为c3=operator-(c2,c1)也可以直接通过函数调用的方式显式调用运算符函数。59运算符重载注意事项运算符重载的基本原则:(1)只能重载已有运算符,不能创建新的运算符例如,+、-可以被重载,但不能定义新的运算符@或$$等。(2)运算符的优先级和结合性不能改变重载后的运算符的优先级和结合性(左结合或右结合)不变,例如,*的优先级始终高于+。(3)运算符的参数个数不能改变C++中运算符的参数个数是固定的,重载后不变,例如,一元运算符(如++)只能接受一个参数,二元运算符(如+、-、*)只能接受两个参数。(4)不能改变运算符的原有语义例如,重载+运算符时,应实现加法或类似的操作,而不应让+执行减法或其他不相关的操作。(5)以下所列的5个运算符不可重载,其余的运算符都可以通过重载重新定义它们的行为,以便作用于用户自定义类型:.
成员选择运算符.*成员指针运算符::作用域分辨符?:
三目选择运算符sizeof计算数据大小运算符60=、()、[]、->
只能被重载为成员函数,不能重载为友元函数。(+=、-=等复合赋值运算符不包括在内
)(2)如果第一个操作数需要支持隐式类型转换
,则只能重载为友元函数。例如:Complexc1(2.1,3.5);Complexc2=1+c1;(3)
当左边操作数是一个不同类型的对象,则必须重载为友元函数。Complexc1(2.1,3.5);cout<<c1;运算符重载注意事项重载为成员函数和友元函数的限制第一个操作数1需要从int到Complex进行隐式类型转换第一个操作数cout不是Complex类型61赋值与输入输出符的重载赋值运算符“=”的重载对于任意类,若用户未自定义赋值运算符函数,系统将自动生成一个默认的赋值运算符函数,该函数负责在相应的数据成员间执行复制操作。通常情况下,这个自动生成的默认赋值运算符函数能够满足大多数用户的需求。然而,当类中包含指针类型的数据成员时,默认的行为可能会导致浅拷贝问题。针对例13-15的MyString类(在例13-14的MyString基础上增加了拷贝构造函数),执行以下代码:int
main(){MyStrings1("hello"),s2("");s2
=
s1;s2.modifyStr();s1.showStr();s2.showStr();return
0;}需要为MyString类重载赋值运算符调用赋值运算,而非拷贝构造函数!
类名&
operator=(const
类名&
对象名){//复制成员return
*this;}62赋值与输入输出符的重载重载“=”运算符的语法格式为:赋值运算符只能重载为成员函数形参与返回值都为引用,减少了对象的拷贝操作return*this;目的为允许进行链式赋值操作,例如:MyStrings1("hello"),s2(""),s3("");s3=s2=s1;//链式赋值63赋值与输入输出符的重载MyString类重载赋值运算符:
MyString&
operator=(const
MyString&
other){delete[]pStr;//先释放旧内存len=
other.len;pStr=
new
char[len+
1];strcpy(pStr,other.pStr);return
*this;}helloAelloint
main(){MyStrings1("hello"),s2("");s2
=
s1;s2.modifyStr();s1.showStr();s2.showStr();return
0;}MyString(const
MyString&other){len=
other.len;pStr=
new
char[len+
1];strcpy(pStr,other.pStr);}代码与拷贝构造函数基本类似64赋值与输入输出符的重载输入运算符(>>)、输出运算符(<<)重载通过运算符重载,iostream可以识别用户自定义的数据类型,提供类似标准类型的输入输出功能。输入输出运算符只能重载为友元函数,因为它们的第一个操作数是输入输出流,而非用户自定义的类friend
istream&
operator>>(istream&is,类名&
obj);friend
ostream&
operator
<<(ostream&
os,const类名&
obj);重载>>运算符的语法格式为:重载<<运算符的语法格式为:返回类型:输入流的引用输入流引用,如cin待输入数据的对象的引用返回类型:输出流的引用输出流引用,如cout待输出数据的对象的常量引用65class
Complex{private:
doublereal;
doubleimag;public:Complex(double
r=0,double
i=0):real(r),imag(i){}
friend
istream&operator>>(istream&in,Complex&c){
charch;
in
>>
c.real>>ch>>
c.imag>>ch;
return
in;
}
friend
ostream&operator<<(ostream&out,const
Complex&c){
out
<<
c.real<<
"+"
<<
c.imag<<
"i";
return
out;
}};例13-18:为Complex类重载输入输出运算符。1+2i1+2iintmain(){
Complexc1;cin>>c1;cout<<c1;
return0;}1+2ic.real=1,
c.imag=2赋值与输入输出符的重载返回输入输出流对象的引用,使得可以连续输入/输出多个复数对象。例如:cout<<c1<<c2;operator>>功能:将1读入到real成员中,将2读入到imag成员中,其它字符读取然后丢弃operator<<功能:将real和imag的值按照某种特定的格式(根据需要添加额外字符)输出66classMyString{private:char*Str;//字符串指针intlen;//字符串长度public:voidShowStr();//显示字符串内容和长度MyString(constchar*p=NULL);//构造函数MyString(constMyString&s);//拷贝构造函数MyString&operator=(constMyString&s);//赋值运算符重载~MyString();//析构函数char&operator[](intn);//重载运算符[],处理String对象constchar&operator[](intn)const;//重载运算符[],处理constString对象//友元函数声明friendostream&operator<<(ostream&os,constMyString&s);friendistream&operator>>(istream&is,MyString&s);friendMyStringoperator+(constMyString&s1,constMyString&s2);friendbooloperator==(constMyString&s1,constMyString&s2);friendboo
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 沈括与《梦溪笔谈》普及讲解
- 2025-2026月考试卷八年级数学上学期期中模拟卷(沪教版)(全解全析)
- 2025年农村电商创业人才孵化政策效果分析
- 2026年公司家庭日活动方案
- 2026年小学大队委组织工作规划
- 2026年农业企业销售自产农产品
- 2026年外贸销售问题分析报告
- 2026年幼儿园下半年活动策划
- 2026年飞机安全案例分析报告
- 2026年大学生情绪活动策划方案
- 2025-2026学年人教版五年级数学下册全册知识点总结(完整版)
- 2026年高压电工考试科目一试题及答案
- 建筑施工企业人员资格管理制度范本
- 2026年全国高考试卷及答案解析
- 2026年安全生产法律法规知识培训考试试卷及答案
- (五调)武汉市2026届高三年级五月调研考试数学试卷(含答案及解析)
- 2025年5月-2026年4月时事政治要点(7.8.9年级道德与法治考试专用)
- 2026江苏苏州工业园区管理委员会招聘44人笔试模拟试题及答案解析
- 重症医学科(ICU)ARDS患者机械通气护理指南
- 水电工程后评价技术导则(2023版)
- CDO首席数字官面试题(某大型集团公司)试题集解析
评论
0/150
提交评论