C++学习笔记(4)---this指针,const修饰,友元,运算符重载

目录

# 1 C++对象模型和this指针

2 友元---friend

3 运算符重载


 1 C++对象模型和this指针

1.1 成员变量和成员函数分开存储

(1)在C++中,类内的成员变量和成员函数分开存储;

(2)只有非静态成员变量才属于类的对象上;

//-----------练习10:成员变量和成员函数存储------------------
class Person
{
	int m_A;//非静态成员变量,属于类的对象,输出sizeof=4;
	static int m_B;//输出仍为4,静态成员变量不属于类的对象上
	void func(){};//非静态成员函数,不属于类对象,输出4
	static void func01(){};//静态成员函数,不属于类对象,输出4
};
void test01()
{
	Person P1;//空对象占用内存空间为:1字节
	//C++编译器会给每一个空对象也分配一个子节空间,为了区分空对象占内存的位置
	//每个空对象应有一个独一无二的内存地址
	cout<<"size of p= "<<sizeof(P1)<<endl;//空类创建一个对象,输出结果为1字节
}
void test02()
{
	Person P2;
	cout<<"size of p= "<<sizeof(P2)<<endl;
}
int main()
{
	test02();
	return 0;
}

1.2 this指针的概念

1、每一个非静态成员函数只有一份函数实例,多个同类型的对象会共用一块代码;

2、C++通过提供特殊的对象指针,this指针,this指针指向被调用的成员函数所属的对象;

注意:(1)this指针是隐含每一个非静态成员函数内的一种指针;

        (2)this指针不需要定义,直接使用即可;

3、this指针的用途

(1)解决形参和成员变量名称冲突,可用this指针来区分;

(2)在类的非静态成员函数中返回对象本身,可使用retrun *this;

//-------------练习11:this指针---------------------------
//1、解决形参和成员变量名称冲突
//2、返回对象本身,*this
class Person
{
public:
	Person(int age)
	{
		this->age=age;//此时可以区分形参和成员变量,this指针指向被调用的成员函数所属的对象;
	}
	Person& PersonAdd(Person &p)//成员函数,要是用引用&
	{
		this->age+=p.age;//this对象P3本身的指针
		return *this;//返回对象P3本身
	}
	int age;
};
int main()
{
	Person P1(10);
	cout<<P1.age<<endl;//10
	Person P2(20);
	Person P3(20);
	//P3.PersonAdd(P2);//40
	P3.PersonAdd(P2).PersonAdd(P2).PersonAdd(P2);//80,链式思想,返回的仍是P3的指针
	//如果Person PersonAdd(Person &p)//没有返回引用&,而是值返回,
//则会调用拷贝函数,复制出一份新的数据,返回一个新的指针本体,输出为40
	cout<<P3.age<<endl;
	return 0;
}

1.3 空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用this指针;

如果用到this指针,需要加以判断保证代码的健壮性;

//--------练习12:空指针访问成员函数-------------------
class Person
{
public:
	void Show()
	{
		cout<<"Show成员函数调用"<<endl;
	}
	void Test()
	{
		if(this==NULL)
		{
			return;//防止空指针操作,提高代码的健壮性
		}
		cout<<"成员变量m_age"<<m_age<<endl;//此处,默认为this->m_age,this为成员函数所属对象的指针
	}
	int m_age;
};
int main()
{
	Person *p=NULL;//设置一个Person类型的指针
	p->Show();//NULL也可以正常运行
	p->Test();//NULL不可以
	return 0;
}

1.4 const修饰成员函数

1、常函数---成员函数后加const称之为常函数

(1)常函数内不可以修改成员属性;

(2)成员属性声明时加关键字mutable后,在常函数中可以修改;

2、常对象---声明对象前加const称该对象为常对象

(1)常对象只能调用常函数;

//-----------练习13:const修饰常函数/常变量-----------
class Person
{
public:
	//this指针本质是,指针常量,指针的指向是不可以修改的
	//(const) Person* const this,()中的const是成员函数后的const
	//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
	void ShowPerson()const
	{
		//m_A=100;//此处会报错==this->m_A=100;
		m_B=200;
	}
	void func(){}
	int m_A;
	mutable int m_B;//特殊变量,在前面加关键字Mutable,即使在常函数中,也可以修改这个值
};
int main()
{
	Person P1;
	const Person P2;//常对象,常对象只可以调用常函数
	//P2.m_A=0;//报错
	P2.m_B=10;//可以
	P2.ShowPerson();//可以
	//P2.func();//报错
	return 0;
}

2 友元---friend

目的:让一个函数或类访问另一个类的私有成员

友元的三种实现:(1)全局函数做友元;(2)类做友元;(3)成员函数做友元;

2.1 全局函数做友元

//------------练习14:友元-全局函数----------------
class Building
{
	friend void GoodGay(Building *B);//友元函数在类声明
public:
	Building()//构造函数初始化
	{
		m_SittingRoom="客厅";
		m_BedRoom="卧室";
	}
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};
void GoodGay(Building *B)//全局函数作为友元
{
	cout<<"友元全局函数访问:"<<B->m_SittingRoom<<endl;//公共访问
	cout<<"友元全局函数访问:"<<B->m_BedRoom<<endl;//此时访问类的私有成员
}
int main()
{
	Building Build;
	GoodGay(&Build);//指针调用,传入地址
	return 0;
}

2.2 类做友元

//创建一个GoodGay类访问Building类的私有成员
class Building
{
	friend class GoodGay;
public:
	Building();//构造函数
	string m_SittingRoom;
private:
	string m_BedRoom;
};
Building::Building()//类外实现成员函数,Building的初始化
{
	m_SittingRoom="客厅";
	m_BedRoom="卧室";
}
class GoodGay
{
public:
	GoodGay();//构造函数
	void GayVisit();//访问函数
	Building* Build;
};
GoodGay::GoodGay()//类外实现构造函数,GoodGay的初始化
{
	Build=new Building;//在堆区创建一个建筑类,并返回类指针
}
void GoodGay::GayVisit()//类外实现GoodGay的成员函数
{
	cout<<"GoodGay正在访问:"<<Build->m_SittingRoom<<endl;
	cout<<"GoodGay正在访问:"<<Build->m_BedRoom<<endl;
}
int main()
{
	GoodGay G1;
	G1.GayVisit();
	return 0;
}

2.3 成员函数做友元

//代码同上,上述GoodGay类中的成员函数都可以访问私有成员,修改如下:
firend void GooGay::GayVisit();

3 运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

3.1 加号运算符重载

作用:实现两个自定义数据类型的相加运算

//----------练习16:运算符加号重载----------------
class Person
{
public:
	Person(int a,int b)//构造函数
	{
		m_A=a;
		m_B=b;
	}
	//Person operator+(Person &p)//成员函数+号重载
	//{
	//	Person temp(0,0);
	//	temp.m_A=this->m_A+p.m_A;
	//	temp.m_B=this->m_B+p.m_B;
	//	return temp;
	//};
	int m_A;
	int m_B;
};
Person operator+(Person &p1,Person &p2)//全局函数+号重载
{
	Person temp(0,0);
	temp.m_A=p1.m_A+p2.m_A;
	temp.m_B=p1.m_B+p2.m_B;
	return temp;
}
int main()
{
	Person P1(10,10);
	Person P2(10,10);
	Person P3=P2+P1;//成员函数==P2.operator+(P1);//全局函数==operator+(P1,P2)
	cout<<P3.m_A<<endl;
	cout<<P3.m_B<<endl;
	return 0;
}

总结:(1)对于内置的数据类型的表达式的运算符及其规则是不可能改变的;

(2)不要滥用运算符重载(不能+变-或者*变/,造成代码逻辑混乱);

3.2 左移运算符<<重载

作用:可以输出自定义数据类型

//----------练习17:左移运算符“<<”重载-----------------
class Person
{
friend ostream& operator<<(ostream& cout,Person& p);
public:
	Person(int a,int b)//构造函数
	{
		m_A=a;
		m_B=b;
	}
	//左移运算符<<不可以进行成员函数重载,因为无法实现cout在左侧,
    //p.opertor(cout)简化:p<<cout
    //void operator<<(ostream cout){}
private:
	int m_A;
	int m_B;
};
//ostream一个类
ostream& operator<<(ostream& cout,Person& p)//链式思想,返回cout指针
{
	cout<<"p.m_A= "<<p.m_A<<" p.m_B= "<<p.m_B;
	return cout;
}
int main()
{
	Person P(20,30);
	cout<<P<<endl;//直接输出P.m_A,P.m_B
	return 0;
}

总结:重载左移运算符配合友元可以实现输出自定义数据类型

3.3 递增++运算符重载

作用:通过重载递增运算符,实现自己的整型数据

//------------练习18:递增运算符“++”重载-----------------
class MyInteger
{
	friend ostream& operator<<(ostream& cout,MyInteger& myint);
public:
	MyInteger()//构造函数初始化
	{
		m_Num=0;
	}
	MyInteger& operator++()//重载++运算符-前置,函数返回值不可以作为重载条件
	{
		this->m_Num++;
		return *this;
	}
	MyInteger operator++(int)//重载++运算符-后置,此处Int为占位参数,为了和前置函数重载
	{
		MyInteger temp=*this;
		this->m_Num++;
		return temp;//此处为值返回,如果返回引用,相当于是局部变量的引用,造成非法操作
	}
private:
	int m_Num;
};
//全局函数实现<<的重载
ostream& operator<<(ostream& cout,MyInteger& myint)//链式存储
{
	cout<<myint.m_Num;
	return cout;
}
int main()
{
	MyInteger myint;
	cout<<myint<<endl;//0
	cout<<++(++myint)<<endl;//2
	cout<<myint<<endl;//2
	cout<<((myint++)++)++<<endl;//2,因此后置返回的是值,
    //因此不可以后置中链式递增,只会重新创建局部变量,进行+1//内置类型后置++也只可以一次
	cout<<myint<<endl;//3
    //int a=0;
	cout<<(a++)++<<endl;//报错,表达式必须为可以修改的左值(a++)
	return 0;
}

3.4 赋值=运算符重载

C++编译器至少会给一个类添加4个函数:

(1)默认构造函数(无参,函数体为空);(2)默认析构函数(无参,函数体为空);

(3)默认拷贝函数,对属性进行值拷贝;(4)赋值运算符operator=,对属性进行值拷贝;

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

//-------------练习19:赋值运算符=重载---------
class Person
{
public:
	Person(int age)//构造函数初始化
	{
		m_Age=new int(age);
	}
	Person& operator=(Person &p)//赋值运算符重载,链式思想,返回引用
	{
		//编译器提供浅拷贝,m_Age=p.m_Age
		//应该先判断是否有成员属性处于堆区,如果有先释放干净,再深拷贝
		if(m_Age!=NULL)
		{
			delete m_Age;
			m_Age=NULL;
		}
		m_Age=new int(*p.m_Age);//进行深拷贝操作
		return *this;//返回对象本身
	}
	~Person()//如果有堆区数据,应该在析构函数进行释放
	{
		if(m_Age!=NULL){
			delete m_Age;
			m_Age=NULL;
		}
	}
	int* m_Age;
};
int main()
{
	Person p1(18);
	Person p2(20);
	Person p3(30);
	p3=p2=p1;
	cout<<*p3.m_Age<<*p1.m_Age<<*p2.m_Age<<endl;//181818
	return 0;
}

3.5 关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

//---------------练习20:关系运算符=重载------------
class Person
{
public:
	Person(string name,int age)//构造函数初始化
	{
		m_Name=name;
		m_Age=age;
	}
	bool operator==(Person& p)
	{
		if(m_Name==p.m_Name && m_Age==p.m_Age)
			return true;
		else
			return false;
	}
	string m_Name;
	int m_Age;
};
int main()
{
	Person P1("张三",20);
	Person P2("张三",20);
	if(P1==P2)
		cout<<"相等"<<endl;
	else
		cout<<"不相等"<<endl;
	return 0;
}

3.6 函数调用运算符重载

(1)函数调用运算符()也可以重载;

(2)由于重载后使用的方式非常像函数的调用,因此成为仿函数;

(3)仿函数没有固定写法,非常灵活;

//------------练习21:函数调用符重载--------------------
class MyPrint
{
public:
	void operator()(string test)
	{
		cout<<test<<endl;
	}
};
class AddClass
{
public:
	int operator()(int a,int b)
	{
		return a+b;
	}
};//仿函数十分灵活,没有固定形式
int main()
{
	MyPrint myprint;
	myprint("hello");//由于使用与函数调用类似,因此成为仿函数
	cout<<AddClass()(20,30)<<endl;
	//AddClass()创建一个匿名对象,类名+(),执行完立即释放,最后的括号是重载的()
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值