知识梳理:类和对象

类和对象

基本概念
从客观事物中抽象出类:成员变量和成员函数统称为类的成员,类就像是带函数的结构。

class CReactangle
{//定义一个矩形的类
public:
	int w,h;//成员变量:矩形的宽和高
	int Area(){return w*h;}//成员函数,计算矩形的面积
	void Init(int w1,int h1){//成员函数,初始化矩形的高和宽
		w=w1;
		h=h1;
	}
}
int main()
{
	int w,h;
	CReactangle r;//定义一个矩形类的对象r
	cin>>w>>h;//输入变量w和h
	r.Init(w,h);//调用成员函数,初始化对象r的宽和高
	cout<<r.Area()<<endl;//调用成员函数,输出面积
	return 0;
}
  • 通过类可以定义变量,类定义出的变量也是类的实例,即对象。类的名字就是用户自定义的类型的名字,可以像使用基本类型那样使用它。
  • 对象所占用的内存空间大小等于所有成员变量的大小之和,对象只包括成员变量,不包括成员函数。每个对象都有各自的存储空间,一个对象的某个成员变量改变不会影响到另一个对象。
  • 对象之间可以用“=”进行赋值,但是不能用"==", “!=”, “>”, "<"等进行比较,除非这些运算符经过了重载。
使用类的成员变量和成员函数的方式:
(1)对象名.成员名
(2)使用指针或者引用

类的成员函数和类的定义分开写:在类的内部写成员函数的声明,在外部写成员函数的定义,如:
int CReactangle::Area()
{//两个冒号,表示不是全局函数,而是类的内部的成员函数的定义,一定要通过对象或对象的指针或对象的引用才能调用
	return w*h;
}

类成员的可访问范围

  • private: 私有成员,只能在成员函数内访问
  • public: 共有成员,可以在任何地方访问
  • protected: 保护成员??

三个关键字出现的次数和先后次序都没有限制,如果某个成员变量前面没有上述关键字,则被默认为private类型。

在类的成员函数内部能够访问 当前对象的全部属性和函数,同类的其他对象的全部属性和函数。

在类的成员函数以外的地方,只能访问该类对象的公有成员。设置私有成员的机制称为“隐藏”,隐藏的目的是强制对成员变量的访问一定要通过成员函数进行,则以后成员变量的类型等属性修改后,只需要修改成员函数即可。否则,所有直接访问该成员变量的语句都需要修改。

成员函数的重载和参数缺省

  • 成员函数也可以重载
  • 成员函数也可以带缺省参数
  • 使用缺省参数时要避免有函数重载时的二义性

构造函数

构造函数是成员函数的一种,名字和类名一样,可以有参数,不能有返回值,void类型也不行。构造函数执行必要的初始化工作,有了构造函数就不用专门写初始化函数,也不用担心忘记调用初始化函数。

如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数,默认构造函数无参数,不做任何操作。如果定义了构造函数,则编译器不生成默认的无参数的构造函数。

class Complex
{
private:
	double real,imag;
public:
	void Set(double r,double i);
};//编译器将自动生成默认构造函数
Complex c1;//默认构造函数被调用
Complex *pc=new Complex;//默认构造函数被调用

对象生成时构造函数自动被调用,对象一旦生成就再也不再其上执行构造函数。一个类可以有多个构造函数,参数个数或类型不同。

class Complex
{
private:
	double real,imag;
public:
	Complex(double r,double i=0);//自定义的构造函数
	Complex(double r);
	Complex(Complex c1,Complex c2);
};
Complex::Complex(double r,double i){
	real=r;imag=i;//自定义构造函数的初始化
}
Complex c1;//×,缺少构造函数的参数
Complex *pa=new Complex;//error,没有参数
Complex c1(2);//ok,对应缺省的构造函数已经初始化
Complex c1(2,4),c2(3,5);//ok
Complex *pc=new Complex(3,4);//ok,动态生成的对象的初始化方式

构造函数在数组中的应用

对应调用数组大小次构造函数,根据参数情况调用对应的构造函数

class Sample
{
	int X;
public:
	Sample(){//没有参数的构造函数
		cout<<"Constructor 1 Called"<<endl;
	}
	Sample(int n){//有参数的构造函数
		X=n;
		cout<<"Constructor 2 Called"<<endl;
	}
};
Sample array1[2];//使用午餐构造函数初始化
Sample array2[2]={4,5};//使用有参构造函数初始化
Sample array3[2]={3};//第一个对象调用有参,第二个调用无参
Sample *array4=new Sample[2];//动态分配的数组,两个都调用无参构造函数
delete []array4;

指针数组,元素是指针,不是对象,所以不会引发任何对象的生成

class Test
{
public:
	Test(int n){};//1,一个参数的构造函数
	Test(int n,int m){}//2,两个参数
	Test(){}//3,无参构造函数
}
Test arrat1[3]={1,Test(1,2)};//分别用函数1,2,3初始化三个对象
Test array2[3]={Test(2,3),Test(1,2),1};//分别用函数2,2,1初始化
Test *pArray[3]={new Test(4),new Test(1,2)};//指针数组,分别用1,2生成对象,第三个无对象生成

复制构造函数

基本概念
只有一个参数,即只对同类对象的引用。形如 X::X(X &)或 X::X(const X&)两者选一,后者能以常量对象作为参数。如果没有定义复制构造函数,则编译器生成默认的复制构造函数,完成复制功能。如果定义自己的复制构造函数,则默认的复制构造函数不存在。不允许有形如X::X(X)的构造函数,参数必须是引用。

复制构造函数的应用情形

  • 用一个对象去初始化同类的另一个对象
Complex c2(c1);//ok
Complex c2=c1;//ok
  • 如果某个函数有一个参数是类A的对象,则该函数被调用时,类A的复制构造函数将被调用
class A
{
public:
	A(){}
	A(A &a){//自定义的复制构造函数
		cout<<"Copy Constructor called"<<endl;
	}
};	
void Func(A a1){}//自定义函数,通过复制构造函数将实参赋给形参
Func(a2);//调用该函数时,对应的复制构造函数也被调用
  • 如果函数的返回值是类的对象,则函数返回时,类的复制构造函数会被调用,对象间的赋值并不导致复制构造函数被调用。
A Func(){//自定义函数,函数类型和返回值为类A的对象
	A b(4);
	return b;
}
int main(){
	cout<<Func().v<<endl;//返回值对象是类A,对象用复制构造函数初始化,复制构造函数的参数是b,返回对象是b的复制品
	return;
}

常量引用参数的使用
这类函数调用时会生成形参会引发复制构造函数的调用开销较大;可以考虑改为使用MyClass &引用作为参数。若希望确保是实参的值在函数中不被改变,可以加const关键字。

void func(MyClass obj){
	cout<<"fun"<<endl;
}	

类型转换构造函数

目的是实现类型的自动转换。只有一个参数而且不是复制构造函数的构造函数,一般就可以看成是转换构造函数。当需要的时候,编译系统会自动条用转换构造函数,建立一个无名的临时对象或临时变量。

class Complex
{
public:
	double real,imag;
	Complex(int i){//类型转换构造函数
		cout<<"IntConstructor called"<<endl;
		real=i;imag=0;
	}
	Complex(double r,double i){
		real=r;imag=i;//用于初始化的自定义构造函数
	}
};
Complex c1(7,8);//调用构造函数
Complex c2=12;//调用类型转换构造函数
c1=9;//调用类型转换构造函数,9被自动转换成一个临时的Complex变量

析构函数

基本概念
名字和类名相同,在前面加上“~”,没有参数和返回值,一个类最多只有一个析构函数。析构函数在对象消亡时自动被调用,可以定义析构函数在对象消亡前做善后工作,比如释放分配的空间等。如果定义类时没有写析构函数,编译器生成缺省的析构函数,缺省析构函数什么也不做。

析构函数和数组
对象数组生命周期结束时,数组中的每个元素的析构函数都会被调用

class Test
{
public:
	~Test(){cout<<"destructor called"<<endl;}//自定义的析构函数
};
int main()
{//main函数结束,对应对象要消亡
	Test array[2];//数组中的每个对象都要消亡,析构函数调用2次
	return 0;
}

析构函数和运算符delete
delete运算符导致析构函数被调用;若new一个对象数组,那么用delete释放时应该加【】,否则只delete数组中的一个对象

Test *pTest=new Test;//调用构造函数
delete pTest;//调用析构函数
pTest=new Test[3];//调用三次构造函数
delete []pTest;	//调用三次析构函数

对象作为函数返回值返回后也会调用析构函数
因为对应的函数被调用、返回时生成了返回值对应的临时对象,临时对象消亡会调用对应的析构函数。

class C
{//类C
public:
	~C(){cout<<"destructor"<<endl;}//自定义的析构函数
};

C obj;//全局变量
C func(C sobj){
	return sobj;//函数调用返回时生成临时对象返回
}
int main(){
	obj=func(obj);//函数调用的返回值(临时对象)被调用后消亡
	return 0;//程序结束时,全局变量消亡,调用析构函数
}//程序一共调用两次析构函数

this指针

指向成员函数所作用的对象。

非静态成员函数中可以直接使用this指针来代表指向该函数作用对象的指针。静态成员函数不能使用this指针,因为静态成员函数并不作用于具体某个对象,所以静态成员函数的真实参数个数,就是程序中所写出的参数的个数。

class Complex
{
public:
	double real,imag;
	void Print(){cout<<"read"<<","<<imag;}
	Complex AddOne(){
		this->real++;//调用this指针,等价于real++
		this->Print();//等价于Print()
		return *this;
	}
};

静态成员变量和静态成员函数

在定义前加了static关键字的成员。普通成员变量每个对象有各自的一份,静态成员变量一共就一份,是为所有的对象所共享。普通成员函数必须作用于具体某个对象,而静态成员函数并不。所以静态成员并不需要通过对象就能访问类。

class CRectangle
{
private:
	int w,h;//普通成员变量
	static int nTotalArea;//静态成员变量
	static int nTotalNumber;
public:
	static void PrintTotal();//静态成员函数
}

sizeof运算符不会计算静态成员变量,因为静态成员变量实际上不是放在对象内部而是放在对象外部为所有对象共享。

class A{
	int n;//普通成员变量
	static int s;//静态成员变量
}
sizeof(A)=4;//不会把静态成员变量的大小算进去

如何访问静态成员

(1)类名::成员名
CRectangle::PrintTotal();

(2)对象名.静态成员名
CRectangle r;//该类下的一个对象
r.PrintTotal();//PrintTotal并不是作用于r上,而是所有对象上,这里只是一表现形式

(3)指针->成员名
CRectangle *p=&r;//p为指向该类对象的一个指针
p->PrintTotal();//同样不是作用于指针上,而是作用于所有变量上

(4)引用.成员名
CRectangle &ref=r;
int n=ref.nTotalNumber;

注意
静态成员变量本质上是全局变量,哪怕一个对象也不存在,类的静态成员变量也存在。静态成员函数本质上是全局函数。

设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,形成一个整体,易于维护和理解。

定义类时需要对静态成员变量进行一次说明或初始化,否则编译能通过,链接不能通过。声明的时候可以不初始化,也可以初始化。

int CRectangle::nTotalNumber=0;//初始化总数
int CRectangle::nTotalArea=0;//初始化面积

在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数,因为静态成员函数不是作用于某一个对象上的,它不知道访问哪一个且在非静态成员函数中是有可能访问到非静态成员变量的,所以也不能调用非静态成员函数。

成员对象和封闭类

有成员对象的类叫做封闭类,如

class Tyre
{
private:
	int radius,width;//轮胎类:半径和宽度
public:
	Tyre(int r,int w):radius(r),width(w){}//初始化列表,构造函数初始化
};
class Engine{};//引擎类
class Car//汽车类
{
private:
	int price;
	Tyre tyre;//汽车类内部的成员对象,轮胎对象
	Engine engine;//内部成员对象,引擎对象
public:
	Car(int p,int tr,int tw):price(p),tyre(tr,tw);//构造函数初始化的方式
};//这里,如果Car类不定义构造函数,则语句Car car;会出错,因为编译器不明白car.tyre如何初始化。Car。engine的初始化用默认构造函数即可

任何生成封闭类对象的语句都要让编译器明白,对象中成员对象是如何初始化的,通过封闭类的构造函数的初始化列表进行初始化即可。成员对象初始化列表中的参数可以是任意复杂的表达式,甚至是函数、变量,只要表达式中的函数或变量有定义即可。

封闭类构造函数和析构函数的执行顺序

  1. 封闭类对象生成时,先执行所有对象成员的构造函数,然后菜执行封闭类的构造函数
  2. 对象成员构造函数调用次序和对象成员在类中的声明次序一致,与他们在成员初始化列表中出现的次序无关
  3. 当封闭类的对象消亡时,先执行封闭类的析构函数,再执行成员对象的析构函数,次序和构造函数的调用次序相反。一般情况下,先构造的后析构。

常量对象/常量成员函数/常引用

常量对象
如果不希望某个对象的只被改变,定义该对象时可加上const关键字

class A{};//定义类A
const A x;//定义常量对象x

常量成员函数
在类的成员函数声明后加上const关键字,则该成员函数称为常量成员函数。常量成员函数执行期间不应该修改其所作用的对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量成员函数(静态成员函数除外)。

class Sample
{
public:
	int value;
	int GetValue() const;//常量成员函数
	void Func(){}//普通成员函数
	Sample(){}//无参构造函数
}
void Sample::GetValue() const
{//类内的常量函数定义
	value=0;//error,常量成员函数不能修改成员变量的值
	func();//error,常量成员函数不能调用普通成员函数
}
const Sample p;//定义常量对象p
p.value=100;//error,常量对象不可以被修改
p.func();//error,常量对象上不能执行非常量成员函数
p.GetValue();//ok,常量对象上可以执行常量成员函数

常量成员函数的重载
两个成员函数,名字和参数表都一样,但是一个有const,一个没有,算是重载。

常引用
引用前面加上const关键字即成为常引用,不能通过常引用修改其引用的变量。

对象作为函数的参数时,生成该参数需要调用复制构造函数,效率较低。用指针作为参数,代码不好看,如何解决?

  • 可以使用对象的引用作为参数
  • 对象的引用作为函数的参数有一定的风险性,若函数中不小心修改了形参,则实参也跟着变化,可以改用该对象的常引用作为参数,就能确保不会出现无意中改变实参的值了。

友元

友元函数
一个类的友元函数可以访问该类的私有成员,这个友元函数可以不是这个类的成员函数。可以将一个类的成员函数包括构造和析构函数声明为另一个类的友元;可以将一个普通的不是类的成员的函数,声明为一个类的友元。

class Car;//提前声明类,便于后面使用
class Driver
{
public:
	void ModifyCar(Car *pCar);//改装汽车
};
class Car
{
private:
	int price;
	friend int MostExpensiveCar(Car cars[],int total);//将普通函数声明为友元
	friend void Driver::ModifyCar(Car *pCar);//将别的类中的成员函数声明为友元
};

void Driver::ModifyCar(Car *pCar)
{//Driver类的友元函数 是Car类中的成员函数, 可以访问Driver中的成员变量price
	pCar->price+=1000;
}
int MostExpensiveCar(Car cars[],int total)
{//Driver类的友元函数 是类外定义的普通函数,可以访问Driver内的私有成员变量price
	int tmpMax=-1;
	for(int i=0;i<total;i++)//求最贵的汽车价格
		if(cars[i].price>tmpMax)
			tmpMax=cars[i].price;//直接访问类内的私有成员变量
	return tmpMax;
}

友元类
如果A是B的友元类,则A的所有成员函数可以访问B的私有成员

class Car
{
private:
	int price;
	friend class Driver;//Driver是Car的友元类,可以访问类内私有成员price
};

朋友的朋友未必是自己的朋友,所以,友元类之间的关系不能传递,不能继承

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值