版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C++:C++类与对象教程1C++类的基础1.1定义类和对象在C++中,类是一种用户定义的数据类型,用于封装数据和操作这些数据的函数。类的定义通常包括数据成员和成员函数,它们共同描述了类的行为和状态。1.1.1数据成员数据成员是类中定义的变量,用于存储类的状态信息。例如,一个Person类可能包含name和age作为其数据成员。1.1.2成员函数成员函数是类中定义的函数,用于操作类的数据成员。例如,Person类可能包含一个display成员函数,用于显示人的姓名和年龄。1.1.3定义类的语法classClassName{
//数据成员和成员函数的定义
};1.1.4创建对象对象是类的实例。一旦定义了类,就可以创建该类的对象,并使用这些对象来访问类的数据成员和成员函数。//定义类
classPerson{
public:
stringname;
intage;
//成员函数
voiddisplay(){
cout<<"Name:"<<name<<",Age:"<<age<<endl;
}
};
//创建对象
intmain(){
Personp1;
="JohnDoe";
p1.age=30;
p1.display();//输出:Name:JohnDoe,Age:30
return0;
}1.2访问修饰符:public,private,protectedC++中的类支持三种访问修饰符:public、private和protected。这些修饰符控制了类的成员(数据成员和成员函数)的可访问性。1.2.1publicpublic成员可以在任何地方访问,包括类的外部。这是默认的访问级别。1.2.2privateprivate成员只能在类的内部访问。它们不能被类的外部代码直接访问,但可以通过成员函数间接访问。1.2.3protectedprotected成员与private类似,但在派生类中可以访问。这使得protected成员在继承中非常有用。1.2.4示例下面的示例展示了如何使用不同的访问修饰符。#include<iostream>
#include<string>
classPerson{
private:
stringname;
intage;
public:
//构造函数
Person(stringn,inta):name(n),age(a){}
//成员函数
voidsetAge(inta){
age=a;//可以访问private成员
}
voiddisplay(){
cout<<"Name:"<<name<<",Age:"<<age<<endl;
}
protected:
voidsayHello(){
cout<<"Hello,"<<name<<"!"<<endl;
}
};
//派生类
classEmployee:publicPerson{
public:
Employee(stringn,inta):Person(n,a){}
voidwork(){
sayHello();//可以访问protected成员
display();
}
};
intmain(){
Employeee1("JaneDoe",25);
e1.setAge(26);
e1.work();//输出:Hello,JaneDoe!Name:JaneDoe,Age:26
return0;
}在这个例子中,Person类的name和age数据成员被声明为private,这意味着它们不能在类的外部直接访问。setAge和display成员函数被声明为public,允许外部代码通过这些函数间接访问private数据成员。sayHello成员函数被声明为protected,这意味着它不能在类的外部访问,但可以在派生类Employee中访问。2构造与析构2.1构造函数的定义与使用构造函数是C++类中一种特殊类型的成员函数,它在创建对象时自动调用,用于初始化对象的成员变量。构造函数的名称与类名相同,没有返回类型,可以重载以提供不同的初始化方式。2.1.1定义构造函数构造函数可以有参数,也可以没有参数。当构造函数有参数时,它可以根据这些参数初始化对象的成员变量。//定义一个类
classPoint{
public:
//成员变量
intx,y;
//无参构造函数
Point(){
x=0;
y=0;
}
//有参构造函数
Point(inta,intb){
x=a;
y=b;
}
};2.1.2使用构造函数在创建对象时,可以使用构造函数来初始化对象。//使用无参构造函数创建对象
Pointp1;
//使用有参构造函数创建对象
Pointp2(10,20);2.1.3构造函数的重载构造函数的重载允许我们使用不同的参数列表来初始化对象。classCircle{
public:
doubleradius;
//无参构造函数
Circle():radius(1.0){}
//有参构造函数
Circle(doubler):radius(r){}
//重载构造函数,接受Point对象作为圆心
Circle(Pointcenter,doubler):radius(r){
//这里可以使用center对象的x和y成员变量
}
};2.2析构函数的作用与实现析构函数是C++类中另一种特殊类型的成员函数,它在对象生命周期结束时自动调用,用于释放对象占用的资源,如动态分配的内存。2.2.1定义析构函数析构函数的名称是在类名前加上波浪线~,没有参数,没有返回类型。classMyClass{
public:
//构造函数
MyClass(){
//分配资源
}
//析构函数
~MyClass(){
//释放资源
}
};2.2.2使用析构函数当对象的生命周期结束时,析构函数自动调用。MyClassobj;//构造函数调用
//...使用obj...
//当obj不再使用时,析构函数自动调用2.2.3析构函数与动态内存当类的成员变量需要动态分配内存时,析构函数用于释放这些内存,避免内存泄漏。classDynamicArray{
private:
int*array;
intsize;
public:
//构造函数
DynamicArray(ints):size(s){
array=newint[size];
}
//析构函数
~DynamicArray(){
delete[]array;
}
//其他成员函数...
};2.2.4析构函数与异常安全在析构函数中释放资源可以确保即使在异常情况下,资源也能被正确释放。classResourceHandler{
private:
FILE*file;
public:
//构造函数
ResourceHandler(constchar*filename){
file=fopen(filename,"r");
if(file==NULL){
throwstd::runtime_error("Failedtoopenfile");
}
}
//析构函数
~ResourceHandler(){
if(file!=NULL){
fclose(file);
}
}
};在这个例子中,如果fopen失败,ResourceHandler的构造函数会抛出异常,但析构函数仍然会被调用,确保文件被正确关闭,即使在异常情况下。2.2.5小结构造函数和析构函数是C++类中用于初始化和清理对象的重要机制。构造函数用于设置对象的初始状态,而析构函数则用于在对象不再需要时释放其占用的资源。正确使用构造函数和析构函数可以提高代码的健壮性和效率。3成员函数与数据成员3.1成员函数的声明与定义在C++中,类是面向对象编程的基础,它允许我们定义包含数据和函数的自定义类型。成员函数是类的一部分,用于操作类的数据成员或执行与类相关的任务。成员函数可以在类定义内部声明,然后在类定义外部定义,或者直接在类定义内部完全定义。3.1.1声明成员函数成员函数的声明通常在类定义中,使用public、protected或private关键字来控制其访问级别。例如:classMyClass{
public:
//公有成员函数声明
voidmyFunction();
private:
intmyPrivateData;
};3.1.2定义成员函数成员函数可以在类定义内部直接定义,也可以在类定义外部定义。在外部定义时,需要使用作用域解析运算符::来指定类名。内部定义classMyClass{
public:
voidmyFunction(){
//函数体
std::cout<<"Hello,World!"<<std::endl;
}
};外部定义classMyClass{
public:
voidmyFunction();
private:
intmyPrivateData;
};
//外部定义
voidMyClass::myFunction(){
//函数体
std::cout<<"Hello,World!"<<std::endl;
}3.1.3成员函数示例下面是一个包含成员函数的类的完整示例,该函数用于设置和获取私有数据成员:#include<iostream>
classMyClass{
private:
intmyPrivateData;
public:
//成员函数声明
voidsetMyData(intdata);
intgetMyData();
};
//成员函数定义
voidMyClass::setMyData(intdata){
myPrivateData=data;
}
intMyClass::getMyData(){
returnmyPrivateData;
}
intmain(){
MyClassobj;
obj.setMyData(10);
std::cout<<"Mydatais:"<<obj.getMyData()<<std::endl;
return0;
}在这个例子中,setMyData和getMyData成员函数分别用于设置和获取myPrivateData私有数据成员的值。3.2数据成员的初始化与访问数据成员是类的组成部分,用于存储类的属性或状态。它们可以在类的构造函数中初始化,并通过成员函数访问。3.2.1初始化数据成员数据成员的初始化通常在构造函数中进行。构造函数是一种特殊类型的成员函数,用于在创建对象时初始化对象的数据成员。classMyClass{
private:
intmyData;
public:
//构造函数声明
MyClass(intdata);
//成员函数声明
voiddisplayData();
};
//构造函数定义
MyClass::MyClass(intdata){
myData=data;
}
//成员函数定义
voidMyClass::displayData(){
std::cout<<"Datais:"<<myData<<std::endl;
}3.2.2访问数据成员数据成员可以通过成员函数访问,这些函数提供了对数据成员的读取或修改的接口。在上面的示例中,displayData成员函数用于访问myData数据成员。3.2.3数据成员初始化与访问示例下面是一个示例,展示了如何在构造函数中初始化数据成员,并通过成员函数访问它:#include<iostream>
classMyClass{
private:
intmyData;
public:
//构造函数
MyClass(intdata):myData(data){}
//成员函数
voiddisplayData(){
std::cout<<"Datais:"<<myData<<std::endl;
}
};
intmain(){
MyClassobj(20);
obj.displayData();
return0;
}在这个例子中,myData数据成员在构造函数中通过初始化列表被初始化为20,然后通过displayData成员函数被访问并输出。3.2.4初始化列表初始化列表是一种更有效的方式,用于在构造函数中初始化数据成员。它使用冒号:后跟数据成员列表,每个数据成员后面跟着其初始值。例如:MyClass(intdata):myData(data){}这行代码将myData数据成员初始化为data的值,而不是在构造函数体内部赋值,这样可以更清晰地表达初始化意图,并且在某些情况下可以提高性能。通过以上示例和解释,我们了解了成员函数如何在类中声明和定义,以及数据成员如何在构造函数中初始化,并通过成员函数访问。这些是C++面向对象编程中类的基本组成部分,掌握它们对于编写有效的C++代码至关重要。4类的继承4.1继承的概念与语法在C++中,继承是一种机制,允许一个类(派生类)继承另一个类(基类)的属性和方法。这种机制促进了代码的重用和类的层次结构的构建,使得程序设计更加模块化和易于管理。4.1.1继承的语法派生类的定义通常如下所示:classDerived:access_specifierBase{
//派生类的成员
};其中:-Derived是派生类的名称。-access_specifier是访问说明符,可以是public、protected或private,分别表示派生类对基类成员的访问权限。-Base是基类的名称。4.1.2示例假设我们有一个基类Animal,它有一个公共成员函数speak()。然后我们创建一个派生类Dog,继承自Animal。//基类Animal
classAnimal{
public:
voidspeak(){
cout<<"Ananimalmakesasound."<<endl;
}
};
//派生类Dog
classDog:publicAnimal{
public:
voidspeak(){
cout<<"Woof!"<<endl;
}
};
intmain(){
DogmyDog;
myDog.speak();//输出"Woof!"
return0;
}在这个例子中,Dog类继承了Animal类的speak()方法,并重写了它以提供更具体的行为。4.2基类与派生类的关系4.2.1访问权限派生类对基类成员的访问权限取决于继承时使用的访问说明符。以下是不同访问说明符的影响:public:派生类可以访问基类中的公共和受保护成员,但不能访问私有成员。protected:派生类可以访问基类中的受保护成员,但不能访问公共和私有成员。此外,派生类的公共成员在进一步派生的类中将变为受保护成员。private:派生类不能访问基类的任何成员,这种继承主要用于实现细节的封装。4.2.2示例考虑以下基类Base和派生类Derived的定义://基类Base
classBase{
public:
voidpublicMethod(){
cout<<"Publicmethodcalled."<<endl;
}
protected:
voidprotectedMethod(){
cout<<"Protectedmethodcalled."<<endl;
}
private:
voidprivateMethod(){
cout<<"Privatemethodcalled."<<endl;
}
};
//派生类Derived
classDerived:publicBase{
public:
voidaccessMethods(){
publicMethod();//可以访问
protectedMethod();//可以访问
//privateMethod();//不能访问,会编译错误
}
};
intmain(){
DerivedmyDerived;
myDerived.accessMethods();//输出"Publicmethodcalled."和"Protectedmethodcalled."
return0;
}在这个例子中,Derived类能够访问Base类的公共和受保护成员,但不能访问私有成员。4.2.3多态多态是面向对象编程的一个重要特性,允许派生类重写基类的虚函数,从而在运行时根据对象的实际类型调用正确的函数版本。这在处理基类指针或引用时特别有用,因为它们可以指向派生类对象。4.2.4示例假设我们有基类Shape和两个派生类Circle和Rectangle。//基类Shape
classShape{
public:
virtualvoiddraw(){
cout<<"Drawingashape."<<endl;
}
};
//派生类Circle
classCircle:publicShape{
public:
voiddraw()override{
cout<<"Drawingacircle."<<endl;
}
};
//派生类Rectangle
classRectangle:publicShape{
public:
voiddraw()override{
cout<<"Drawingarectangle."<<endl;
}
};
intmain(){
Shape*shapes[2];
shapes[0]=newCircle();
shapes[1]=newRectangle();
for(inti=0;i<2;i++){
shapes[i]->draw();//多态调用,根据对象类型输出不同的结果
}
deleteshapes[0];
deleteshapes[1];
return0;
}在这个例子中,Shape类的draw()方法被声明为虚函数,允许Circle和Rectangle类重写它。通过基类指针数组shapes,我们可以调用正确的draw()方法,这展示了多态的使用。4.2.5构造与析构派生类的构造函数需要调用基类的构造函数来初始化基类部分。这通常通过构造函数初始化列表中的基类构造函数调用来完成。同样,派生类的析构函数应该声明为虚函数,以确保当通过基类指针删除派生类对象时,派生类的析构函数会被正确调用。4.2.6示例考虑以下基类Base和派生类Derived的构造与析构函数://基类Base
classBase{
public:
Base(){
cout<<"Baseconstructorcalled."<<endl;
}
virtual~Base(){
cout<<"Basedestructorcalled."<<endl;
}
};
//派生类Derived
classDerived:publicBase{
public:
Derived():Base(){
cout<<"Derivedconstructorcalled."<<endl;
}
virtual~Derived(){
cout<<"Deriveddestructorcalled."<<endl;
}
};
intmain(){
Base*basePtr=newDerived();
deletebasePtr;//通过基类指针删除派生类对象
return0;
}在这个例子中,Derived类的构造函数通过初始化列表调用了Base类的构造函数。同时,Derived类的析构函数被声明为虚函数,确保了当通过基类指针basePtr删除派生类对象时,Derived类的析构函数会被正确调用,从而避免了资源泄露。通过这些示例和解释,我们深入了解了C++中类的继承机制,包括访问权限、多态以及构造与析构函数的处理。这些概念是面向对象编程的核心,对于构建复杂和可维护的软件系统至关重要。5C++:多态性5.1虚函数与抽象类多态性是面向对象编程的一个核心特性,允许我们使用一个接口来表示多种类型。在C++中,多态性主要通过虚函数和抽象类来实现。5.1.1虚函数虚函数允许在基类中声明,在派生类中重定义。当通过基类指针或引用调用虚函数时,会调用派生类的版本,这就是运行时多态。示例代码#include<iostream>
//基类
classAnimal{
public:
virtualvoidspeak(){std::cout<<"Somesound"<<std::endl;}//虚函数
virtual~Animal(){}//虚析构函数
};
//派生类
classDog:publicAnimal{
public:
voidspeak()override{std::cout<<"Woof!"<<std::endl;}//重定义虚函数
};
//派生类
classCat:publicAnimal{
public:
voidspeak()override{std::cout<<"Meow!"<<std::endl;}//重定义虚函数
};
intmain(){
Animal*animal1=newDog();
Animal*animal2=newCat();
animal1->speak();//输出:Woof!
animal2->speak();//输出:Meow!
deleteanimal1;
deleteanimal2;
return0;
}5.1.2抽象类抽象类是包含至少一个纯虚函数的类。纯虚函数没有实现,必须在派生类中实现。抽象类不能实例化,主要用于作为基类。示例代码#include<iostream>
//抽象类
classShape{
public:
virtualvoiddraw()=0;//纯虚函数
virtual~Shape(){}//虚析构函数
};
//派生类
classCircle:publicShape{
public:
voiddraw()override{std::cout<<"Drawingacircle"<<std::endl;}
};
//派生类
classSquare:publicShape{
public:
voiddraw()override{std::cout<<"Drawingasquare"<<std::endl;}
};
intmain(){
Shape*shape1=newCircle();
Shape*shape2=newSquare();
shape1->draw();//输出:Drawingacircle
shape2->draw();//输出:Drawingasquare
deleteshape1;
deleteshape2;
return0;
}5.2运行时多态的实现运行时多态是通过虚函数表(virtualtable)来实现的。每个包含虚函数的类都有一个虚函数表,其中包含指向虚函数的指针。当通过基类指针调用虚函数时,会查找派生类的虚函数表,从而调用正确的函数。5.2.1示例代码#include<iostream>
classBase{
public:
virtualvoidprint(){std::cout<<"Base::print()"<<std::endl;}
virtual~Base(){}
};
classDerived:publicBase{
public:
voidprint()override{std::cout<<"Derived::print()"<<std::endl;}
};
intmain(){
Base*basePtr=newDerived();
basePtr->print();//输出:Derived::print()
deletebasePtr;
return0;
}在这个例子中,Base类有一个虚函数print(),Derived类重定义了这个函数。当我们通过Base类型的指针调用print()时,实际上调用的是Derived类的print()函数,这就是运行时多态的体现。6C++中的封装与抽象6.1封装的实现封装是面向对象编程的一个核心概念,它指的是将数据和操作数据的方法绑定在一起,隐藏对象的属性和实现细节,仅对外提供公共访问方式。在C++中,封装主要通过类(class)来实现,类可以定义私有(private)、保护(protected)和公共(public)成员,从而控制外部对对象内部状态的访问。6.1.1示例:一个简单的封装类//定义一个表示银行账户的类
classBankAccount{
private://私有成员变量,外部不可直接访问
doublebalance;//账户余额
public://公共成员函数,提供外部访问接口
BankAccount(doubleinitialBalance):balance(initialBalance){}//构造函数,初始化余额
//存款操作
voiddeposit(doubleamount){
if(amount>0){
balance+=amount;
}
}
//取款操作
boolwithdraw(doubleamount){
if(amount>0&&balance>=amount){
balance-=amount;
returntrue;
}
returnfalse;
}
//查询余额
doublegetBalance()const{
returnbalance;
}
};
//使用BankAccount类的示例
intmain(){
BankAccountaccount(1000);//创建一个初始余额为1000的账户
account.deposit(500);//存款500
account.withdraw(200);//取款200
std::cout<<"Currentbalance:"<<account.getBalance()<<std::endl;//输出当前余额
return0;
}在这个例子中,balance变量是私有的,不能从类的外部直接访问。而deposit、withdraw和getBalance函数提供了对balance的访问和操作方式,实现了封装。6.2抽象类与接口抽象类是C++中用于定义接口的一种方式,它包含至少一个纯虚函数,这样的类不能被实例化,主要用于被继承。接口的概念在C++中通常通过抽象类来实现,它规定了类的行为规范,但不提供具体的实现。6.2.1示例:定义一个抽象类作为接口#include<iostream>
//定义一个抽象类Shape,作为所有形状类的基类
classShape{
public:
virtual~Shape(){}//虚析构函数,确保派生类正确析构
//纯虚函数,表示所有形状类都必须实现的计算面积方法
virtualdoublearea()const=0;
};
//定义一个圆形类,继承自Shape
classCircle:publicShape{
private:
doubleradius;//圆的半径
public:
Circle(doubler):radius(r){}//构造函数,初始化半径
//实现Shape类的area方法
doublearea()constoverride{
return3.14159*radius*radius;
}
};
//定义一个矩形类,继承自Shape
classRectangle:publicShape{
private:
doublewidth,height;//矩形的宽和高
public:
Rectangle(doublew,doubleh):width(w),height(h){}//构造函数,初始化宽和高
//实现Shape类的area方法
doublearea()constoverride{
returnwidth*height;
}
};
//使用Shape接口的示例
intmain(){
Circlecircle(5);//创建一个半径为5的圆形
Rectanglerect(4,6);//创建一个宽为4,高为6的矩形
Shape*shapes[]={&circle,&rect};//创建一个Shape指针数组,存储不同形状的指针
for(Shape*shape:shapes){
std::cout<<"Area:"<<shape->area()<<std::endl;//通过Shape接口调用area方法
}
return0;
}在这个例子中,Shape类是一个抽象类,它定义了一个纯虚函数area,表示所有形状都应该有计算面积的方法。Circle和Rectangle类分别继承自Shape,并实现了area方法,提供了具体的面积计算逻辑。通过Shape接口,我们可以统一处理不同形状的面积计算,体现了多态性。7运算符重载7.1运算符重载的规则在C++中,运算符重载允许我们为自定义类型(如类)定义运算符的行为。这使得我们可以使用内置类型的运算符语法来操作我们自己的数据类型,提高了代码的可读性和表达力。然而,运算符重载有一些规则和限制:不能创建新的运算符:只能重载已存在的运算符。运算符的优先级和结合性不能改变:重载运算符不会改变其原有的优先级和结合性。至少有一个操作数必须是用户定义的类型:这意味着至少一个操作数必须是类或结构体的实例。重载运算符不能有比内置类型更多的参数:例如,+运算符在重载时只能有两个参数。重载运算符不能改变其返回类型:例如,+运算符重载后仍然必须返回一个值。某些运算符不能被重载:如::,?,.*,.*=,::=,alignof,sizeof,typeid,noexcept。7.2常见运算符的重载示例7.2.1重载加法运算符+假设我们有一个Complex类,用于表示复数,我们可以重载加法运算符来实现两个复数的相加。classComplex{
public:
doublereal,imag;
//构造函数
Complex(doubler=0.0,doublei=0.0):real(r),imag(i){}
//重载加法运算符
Complexoperator+(constComplex&c){
returnComplex(real+c.real,imag+c.imag);
}
};
//测试代码
intmain(){
Complexc1(3.0,2.0);
Complexc2(1.0,7.0);
Complexc3=c1+c2;//使用重载的加法运算符
std::cout<<"c3="<<c3.real<<"+"<<c3.imag<<"i"<<std::endl;
return0;
}在这个例子中,Complex类的实例可以使用加法运算符进行相加,结果是一个新的Complex对象,其实部和虚部分别是两个复数的实部和虚部之和。7.2.2重载比较运算符<如果我们有一个Point类,用于表示二维空间中的点,我们可以重载小于运算符来比较两个点的坐标。classPoint{
public:
intx,y;
//构造函数
Point(inta=0,intb=0):x(a),y(b){}
//重载小于运算符
booloperator<(constPoint&p)const{
if(x==p.x){
returny<p.y;
}
returnx<p.x;
}
};
//测试代码
intmain(){
Pointp1(1,2);
Pointp2(3,4);
if(p1<p2){
std::cout<<"p1islessthanp2"<<std::endl;
}
return0;
}在这个例子中,我们定义了Point类的实例之间的比较规则,首先比较x坐标,如果x坐标相同,则比较y坐标。7.2.3重载索引运算符[]如果我们有一个Vector类,用于表示动态数组,我们可以重载索引运算符来访问或修改数组中的元素。classVector{
public:
int*data;
intsize;
//构造函数
Vector(ints):size(s),data(newint[s]){}
//析构函数
~Vector(){
delete[]data;
}
//重载索引运算符
int&operator[](intindex){
returndata[index];
}
};
//测试代码
intmain(){
Vectorv(5);
v[0]=10;//使用重载的索引运算符
std::cout<<"v[0]="<<v[0]<<std::endl;
return0;
}在这个例子中,Vector类的实例可以使用索引运算符来访问或修改数组中的元素,就像操作内置数组一样。7.2.4重载流插入运算符<<如果我们有一个Student类,用于表示学生信息,我们可以重载流插入运算符来方便地输出学生信息。classStudent{
public:
std::stringname;
intage;
//构造函数
Student(std::stringn,inta):name(n),age(a){}
//重载流插入运算符
friendstd::ostream&operator<<(std::ostream&os,constStudent&s){
os<<"Name:"<<<<",Age:"<<s.age;
returnos;
}
};
//测试代码
intmain(){
Students("Tom",20);
std::cout<<s<<std::endl;//使用重载的流插入运算符
return0;
}在这个例子中,Student类的实例可以使用流插入运算符输出到标准输出流,使得输出学生信息变得非常简单。7.2.5重载赋值运算符=如果我们有一个Matrix类,用于表示矩阵,我们需要重载赋值运算符来确保深拷贝,避免浅拷贝的问题。classMatrix{
public:
int**data;
introws,cols;
//构造函数
Matrix(intr,intc):rows(r),cols(c){
data=newint*[r];
for(inti=0;i<r;i++){
data[i]=newint[c];
}
}
//析构函数
~Matrix(){
for(inti=0;i<rows;i++){
delete[]data[i];
}
delete[]data;
}
//重载赋值运算符
Matrix&operator=(constMatrix&m){
if(this!=&m){
for(inti=0;i<rows;i++){
delete[]data[i];
}
delete[]data;
rows=m.rows;
cols=m.cols;
data=newint*[rows];
for(inti=0;i<rows;i++){
data[i]=newint[cols];
for(intj=0;j<cols;j++){
data[i][j]=m.data[i][j];
}
}
}
return*this;
}
};
//测试代码
intmain(){
Matrixm1(2,2);
m1[0][0]=1;
m1[0][1]=2;
m1[1][0]=3;
m1[1][1]=4;
Matrixm2(2,2);
m2=m1;//使用重载的赋值运算符
return0;
}在这个例子中,Matrix类的实例可以使用赋值运算符进行赋值,确保了数据的深拷贝,避免了两个矩阵实例共享数据的问题。通过这些示例,我们可以看到运算符重载在C++中如何增强类的功能,使得操作自定义类型就像操作内置类型一样自然和直观。8C++:友元函数与友元类8.1友元的概念在C++中,友元(friend)是一种特殊的机制,允许一个函数或类访问另一个类的私有(private)和保护(protected)成员。通常,类的封装性意味着外部函数或类不能直接访问其私有成员,但通过声明友元,可以打破这一封装,实现更灵活的编程。8.1.1为什么需要友元?友元的主要用途在于提高代码的效率和简化编程。例如,当一个类需要另一个类的帮助来完成某些操作,而这些操作涉及到私有成员时,友元可以提供一种解决方案。此外,友元也可以用于实现一些特殊的设计模式,如流操作符重载,其中友元函数通常用于实现输入输出操作。8.2友元函数与类的声明8.2.1友元函数的声明友元函数可以在类的声明中被声明,但不在类的任何成员函数中定义。它通常被放在类的public部分,但其访问权限不受public、protected或private的限制。友元函数的声明如下:classMyClass{
friendvoidmyFriendFunction(MyClass&obj);
//...
};8.2.2示例:友元函数访问私有成员假设我们有一个Rectangle类,它包含两个私有成员变量width和height,我们希望创建一个友元函数printDetails来打印这些私有成员的值。#include<iostream>
classRectangle{
private:
doublewidth;
doubleheight;
public:
Rectangle(doublew,doubleh):width(w),height(h){}
friendvoidprintDetails(Rectangle&rect);
};
voidprintDetails(Rectangle&rect){
std::cout<<"Width:"<<rect.width<<",Height:"<<rect.height<<std::endl;
}
intmain(){
Rectanglerect(5.0,10.0);
printDetails(rect);//输出:Width:5,Height:10
return0;
}在这个例子中,printDetails函数被声明为Rectangle类的友元,因此它可以访问Rectangle类的私有成员width和height。8.2.3友元类的声明友元类的声明与友元函数类似,但整个类被声明为另一个类的友元,这意味着友元类中的所有成员函数都可以访问被声明为友元的类的私有和保护成员。classMyClass{
friendclassMyFriendClass;
//...
};8.2.4示例:友元类访问私有成员假设我们有两个类Account和Bank,Account类包含私有成员变量balance,而Bank类需要访问Account类的balance来执行一些银行操作。#include<iostream>
classAccount{
private:
doublebalance;
public:
Account(doubleb):balance(b){}
friendclassBank;
};
classBank{
public:
voiddeposit(Account&acc,doubleamount){
acc.balance+=amount;
}
};
intmain(){
Accountacc(1000.0);
Bankbank;
bank.deposit(acc,500.0);
std::cout<<"Newbalance:"<<acc.balance<<std::endl;//输出:Newbalance:1500
return0;
}在这个例子中,Bank类被声明为Account类的友元,因此Bank类中的deposit函数可以访问Account类的私有成员balance。8.2.5友元的注意事项友元关系不是互惠的。如果A是B的友元,B并不自动成为A的友元。友元关系不传递。如果A是B的友元,B是C的友元,A并不自动成为C的友元。友元函数和友元类不改变它们自己的访问权限。即使它们被声明为友元,它们仍然需要被适当地声明为public、protected或private。通过以上内容,我们了解了友元函数和友元类的基本概念和使用方法,以及它们在C++编程中的重要性和注意事项。9模板类与对象9.1模板类的定义在C++中,模板类允许我们定义一个通用的类,这个类可以处理不同数据类型的对象。模板类的定义使用关键字template,后跟一个尖括号<>,在尖括号内定义模板参数。模板参数可以是类型参数,也可以是非类型参数。类型参数通常用typename或class关键字来声明,而非类型参数则可以是整数、枚举值等。9.1.1示例:模板类定义//定义一个模板类,用于存储任意类型的元素
template<typenameT>
classStack{
private:
T*data;
inttop;
intcapacity;
public:
Stack(intsize);//构造函数
~Stack();//析构函数
voidpush(Titem);//入栈操作
Tpop();//出栈操作
Tpeek();//查看栈顶元素
boolisEmpty();//判断栈是否为空
boolisFull();//判断栈是否已满
};
//模板类的构造函数定义
template<typenameT>
Stack<T>::Stack(intsize){
capacity=size;
top=-1;
data=newT[capacity];
}
//模板类的析构函数定义
template<typenameT>
Stack<T>::~Stack(){
delete[]data;
}在这个例子中,Stack是一个模板类,可以用于创建处理不同数据类型(如int、char、string等)的栈对象。模板参数T代表了栈中元素的类型。9.2模板对象的实例化模板对象的实例化是指在使用模板类时,为模板参数提供具体的类型或值,从而创建出具体的类实例。实例化时,模板参数的类型或值必须明确指定。9.2.1示例:模板对象实例化#include<iostream>
#include"Stack.h"//假设Stack模板类定义在Stack.h中
intmain(){
Stack<int>intStack(10);//实例化一个处理整数的栈
Stack<char>charStack(20);//实例化一个处理字符的栈
//使用intStack
intStack.push(5);
intStack.push(10);
std::cout<<"TopelementofintStack:"<<intStack.peek()<<std::endl;
//使用charStack
charStack.push('a');
charStack.push('b');
std::cout<<"TopelementofcharStack:"<<charStack.peek()<<std::endl;
return0;
}在这个例子中,我们实例化了两个Stack对象:intStack和charSStack用于处理整数,而charStack用于处理字符。通过这种方式,我们能够使用同一个类结构来处理不同类型的元素,极大地提高了代码的复用性和灵活性。9.2.2注意事项实例化时的类型检查:编译器会在实例化模板类时进行类型检查,确保提供的类型参数是有效的。模板实例化与链接:如果模板类的成员函数在类定义外部定义,那么在实例化模板类时,这些成员函数也必须被实例化。这意味着,如果在不同的源文件中实例化同一个模板类,每个源文件都必须包含模板成员函数的定义,或者将定义放在头文件中,以确保链接时不会出现错误。模板特化:模板特化允许我们为特定的类型参数提供不同的实现,这在处理如char或string等特殊类型时非常有用,可以提供更优化的代码。通过上述示例和解释,我们可以看到模板类与对象在C++中的强大功能,它们能够帮助我们编写更加通用和灵活的代码,同时保持代码的简洁性和可维护性。10异常处理与类10.1异常处理的基本语法在C++中,异常处理是一种用于处理程序运行时错误的机制,它允许程序在遇到错误时,通过抛出异常来中断正常的执行流程,并将控制权转移到专门的异常处理代码块。异常处理的基本语法包括try、catch和throw关键字。10.1.1try块try块用于包围可能抛出异常的代码段。如果try块中的代码抛出了异常,控制权将立即转移到与该try块相关的catch块。try{
//可能抛出异常的代码
}
catch(ExceptionTypee){
//处理异常的代码
}10.1.2catch块catch块用于捕获并处理由try块抛出的异常。catch块可以指定捕获特定类型的异常,也可以捕获所有类型的异常。catch(conststd::exception&e){
std::cerr<<"Caughtexception:"<<e.what()<<std::endl;
}10.1.3throw表达式throw表达式用于从程序的任何位置抛出异常。当程序检测到错误时,可以使用throw来抛出异常,中断当前的执行流程。throwstd::runtime_error("Anerroroccurred.");10.2类中异常的抛出与捕获在类的成员函数中使用异常处理,可以更精细地控制错误处理流程,特别是在处理资源管理、错误状态和复杂逻辑时。10.2.1示例:银行账户类假设我们正在设计一个银行账户类,其中包含存款和取款功能。为了确保账户余额不会变为负数,我们可以在取款函数中使用异常处理。#include<iostream>
#include<stdexcept>
classBankAccount{
private:
doublebalance;
public:
BankAccount(doubleinitialBalance):balance(initialBalance){}
//存款函数
voiddeposit(doubleamount){
balance+=amount;
}
//取款函数,如果余额不足则抛出异常
voidwithdraw(doubleamount){
if(amount>balance){
throwstd::runtime_error("Insufficientfunds");
}
balance-=amount;
}
//获取余额
doublegetBalance()const{
returnbalance;
}
};
intmain(){
BankAccountaccount(1000.0);
try{
account.withdraw(1500.0);//尝试取款超过余额
}
catch(conststd::runtime_error&e){
std::cerr<<"Error:"<<e.what()<<std::endl;
}
try{
account.deposit(600.0);
account.withdraw(500.0);//正常取款
}
catch(conststd::runtime_error&e){
std::cerr<<"Error:"<<e.what()<<std::endl;
}
std::cout<<"Currentbalance:"<<account.getBalance()<<std::endl;
return0;
}10.2.2解释在这个例子中,BankAccount类的withdraw成员函数检查取款金额是否超过当前余额。如果是,它将抛出一个std::runtime_error异常,包含错误信息”Insufficientfunds”。在main函数中,我们使用try块来调用withdraw函数,并在catch块中捕获并处理异常。这样,即使取款失败,程序也能继续执行,而不会因为错误而完全崩溃。10.2.3异常的层次结构C++标准库提供了一系列异常类,它们构成了一个层次结构,std::exception是所有异常类的基类。在设计自己的异常类时,通常会从std::exception派生,以便于在catch块中捕获和处理。classInsufficientFundsException:publicstd::runtime_error{
public:
InsufficientFundsException(doubleamount,doublebalance)
:std::runtime_error("Insufficientfunds:triedtowithdraw"+std::to_string(amount)+",butbalanceis"+std::to_string(balance)){}
};10.2.4在类中使用自定义异常使用自定义异常可以提供更具体和更详细的错误信息,这有助于在捕获异常时进行更精确的错误处理。#include<iostream>
#include<stdexcept>
#include<string>
classBankAccount{
private:
doublebalance;
public:
BankAccount(doubleinitialBalance):balance(initialBalance){}
//存款函数
voiddeposit(doubleamount){
balance+=amount;
}
//取款函数,如果余额不足则抛出自定义异常
voidwithdraw(doubleamount){
if(amount>balance){
throwInsufficientFundsException(amount,balance);
}
balance-=amount;
}
//获取余额
doublegetBalance()const{
returnbalance;
}
};
classInsufficientFundsException:publicstd::runtime_error{
public:
InsufficientFundsException(doubleamount,doublebalance)
:std::runtime_error("Insufficientfunds:triedtowithdraw"+std::to_string(amount)+",butbalanceis"+std::to_string(balance)){}
};
intmain(){
BankAccountaccount(1000.0);
try{
account.withdraw(1500.0);//尝试取款超过余额
}
catch(constInsufficientFundsException&e){
std::cerr<<"Error:"<<e.what()<<std::endl;
}
try{
account.deposit(600.0);
account.withdraw(500.0);//正常取款
}
catch(constInsufficientFundsException&e){
std::cerr<<"Error:"<<e.what()<<std::endl;
}
std::cout<<"Currentbalance:"<<account.getBalance()<<std::endl;
return0;
}在这个版本中,我们定义了一个InsufficientFundsException类,它继承自std::runtime_error。当withdraw函数检测到余额不足时,它将抛出这个自定义异常,包含具体的取款金额和当前余额信息。在catch块中,我们可以捕获这个异常并输出详细的错误信息,而不是使用通用的异常类型。10.2.5异常安全的构造函数和析构函数在类的构造函数和析构函数中正确处理异常对于确保资源的正确管理至关重要。构造函数应该能够处理初始化过程中可能出现的任何异常,而析构函数则应该能够清理在异常发生时可能未被正确释放的资源。#include<iostream>
#include<fstream>
#include<stdexcept>
classFileHandler{
private:
std::ifstreamfile;
public:
FileHandler(conststd::string&filename){
file.open(filename);
if(!file.is_open()){
throwstd::runtime_error("Failedtoopenfile");
}
}
~FileHandler(){
if(file.is_open()){
file.close();
}
}
//读取文件内容的函数
std::stringreadFileContent(){
std::stringcontent((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());
returncontent;
}
};
intmain(){
try{
FileHandlerhandler("example.txt");
std::strin
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024-2030年婴儿纸尿布市场发展现状调查及供需格局分析预测报告
- 2024-2030年声孔行业市场现状供需分析及投资评估规划分析研究报告
- 2024-2030年城市轨道交通行业发展前景分析及市场需求现状预测研究报告
- 2024-2030年图文书籍行业市场深度分析及竞争格局与投资价值研究报告
- 2024-2030年国内数码照相机行业市场发展现状及发展前景与投资机会研究报告
- 2024-2030年国内宠物罐头行业市场发展分析及发展前景与投资机会研究报告
- 2024-2030年国内喷雾干燥产品行业市场发展分析及发展前景与投资机会研究报告
- 2024-2030年国内农用无人机行业市场发展分析及竞争策略与投资发展研究报告
- 2024-2030年口腔诊所行业市场发展分析与发展趋势及投资前景预测报告
- 2024-2030年口服葡萄糖行业市场发展分析及前景趋势与投资研究报告
- 时间管理技巧 课件
- 外科休克病人的护理-完整版课件
- 师德师师风考核表(样表1、2)
- 组织行为学之动机与激励课件
- 最全《中国中铁集团有限公司工程项目管理手册》
- 一8学生德育量化考核标准
- 格宾网石笼检验批验收记录表【范本模板】
- 学校水电检查记录表
- 监控录像调取申请表
- 10以内的点数数学课件
- 化验室培训记录
评论
0/150
提交评论