【C++】九、类和对象(封装)知识点

文章内容是把黑马的课堂笔记选择性的复制过来,便于后期学习,非原创!

封装、对象的初始化和清理

1、构造函数和析构函数

构造函数和析构函数会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。编译器提供的构造函数和析构函数是空实现。

2、C++中拷贝构造函数调用时机通常有三种情况
  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象
class Person {
public:
	Person() {
		cout << "无参构造函数!" << endl;
		mAge = 0;
	}
	Person(int age) {
		cout << "有参构造函数!" << endl;
		mAge = age;
	}
	Person(const Person& p) {
		cout << "拷贝构造函数!" << endl;
		mAge = p.mAge;
	}
	//析构函数在释放内存之前调用
	~Person() {
		cout << "析构函数!" << endl;
	}
public:
	int mAge;
};

//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
	Person man(100); //p对象已经创建完毕
	Person newman(man); //调用拷贝构造函数
	Person newman2 = man; //拷贝构造
	//Person newman3;
	//newman3 = man; //不是调用拷贝构造函数,赋值操作
}

//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
	Person p; //无参构造函数
	doWork(p);//值传递时就是产生一个拷贝 一个副本  实参传给形参时调用拷贝构造函数,改调用后的值不改变原来的值
}

//3. 以值方式返回局部对象
Person doWork2()
{
	Person p1;
	cout << (int *)&p1 << endl;//看看对象的地址
	return p1;//return局部对象值返回时是拷贝了一个新的对象来返回
}
void test03()
{
	Person p = doWork2();
	cout << (int *)&p << endl;//拷贝的对象的地址
}

int main() {
	//test01();
	//test02();
	test03();
	system("pause");
	return 0;
}
3、默认情况下,c++编译器至少给一个类添加3个函数
  1. 默认构造函数(无参,函数体为空)(空实现)
  2. 默认析构函数(无参,函数体为空)(空实现)
  3. 默认拷贝构造函数,对属性进行值拷贝(值拷贝)(浅拷贝)

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
    在这里插入图片描述
    如果提供有参,左边
    如果提供拷贝,右边
4、深浅拷贝

浅拷贝:简单的赋值拷贝操作 如拷贝构造函数
深拷贝:在堆区重新申请空间,进行拷贝操作

class Person {
public:
	//无参(默认)构造函数
	Person() {
		cout << "无参构造函数!" << endl;
	}
	//有参构造函数
	Person(int age ,int height) {
		
		cout << "有参构造函数!" << endl;

		m_age = age;
		m_height = new int(height);//堆区开辟的数据由程序员手动开辟 手动释放,对象销毁前把堆区释放
	}
	//拷贝构造函数  
	Person(const Person& p) {
		cout << "拷贝构造函数!" << endl;
		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_age = p.m_age;//编译器提供:m_age = p.m_age;m_height = p.m_height;
		m_height = new int(*p.m_height);//用深拷贝,创建新内存
	}
	//析构函数
	~Person() {
		cout << "析构函数!" << endl;//将堆区数据释放,p1 p2都会执行析构,规则是先进后出,p2先被释放,然后在释放p1 堆区内存被重复释放
		if (m_height != NULL)
		{
			delete m_height;
            m_height = NULL;//防止野指针出现
		}
	}
public:
	int m_age;
	int* m_height;//为指针
};
void test01()
{
	Person p1(18, 180);
	Person p2(p1);//把m_height开辟的地址逐字节拷贝
	cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;

	cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述

5、类对象作类成员
  • 当类中成员是其他类对象时,我们称该成员为 对象成员
    构造的顺序是 :先调用对象成员的构造,再调用本类构造
    析构顺序与构造相反
6、静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:

  • 静态成员变量
    • 所有对象共享同一份数据,不属于某一个对象
    • 在编译阶段分配内存//全局区
    • 类内声明,类外初始化
      静态成员变量也是有访问权限的
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
class Person
{
public:
	static int m_A; //静态成员变量 类内声明
	int m_C; // 非静态成员变量
	static void func()
	{
		cout << "func调用" << endl;
		m_A = 100;//静态成员函数可以访问静态成员变量
		//m_C = 100; //错误,不可以访问非静态成员变量,因为每个对象都有一份m_c非静态成员变量,而静态成员函数只有一份,没有体现属于哪个对象,所以不知道要修改哪个。比如当用类调用时,person::func(),不知道是哪个。
	}
private:
	static int m_B; //静态成员变量也是有访问权限的,类内声明
	static void func2()//静态成员函数也是有访问权限的
	{
		cout << "func2调用" << endl;
	}
};
int Person::m_A = 10;//成员变量类外初始化
int Person::m_B = 10;

void test01()
{
	//静态成员变量、成员函数两种访问方式
	//1、通过对象
	Person p1;
	p1.m_A = 100;//
	cout << "p1.m_A = " << p1.m_A << endl;
	p1.func();
	Person p2;
	p2.m_A = 200;
	cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据,都会变成200
	cout << "p2.m_A = " << p2.m_A << endl;
	//2、通过类名
	cout << "m_A = " << Person::m_A << endl;//因为不属于任意一个对象,person作用域下
	//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}
	Person::func();
	//Person::func2(); //私有权限访问不到
int main() {
	test01();
	system("pause");
	return 0;
}

C++对象模型和this指针

7、成员变量和成员函数分开存储
  • 在C++中,类内的成员变量和成员函数分开存储,虽然作为一个整体表现事物进行封装
  • 只有非静态成员变量才属于类的对象上(静态成员对象、静态成员函数、非静态成员函数都不是,都只有一份)
class Person {
public:
	Person() {
		mA = 0;
	}
	//非静态成员变量占对象空间,按int分,4个字节
	int mA;
	//静态成员变量不占对象空间
	static int mB; 
	//函数也不占对象空间,所有函数共享一个函数实例
	void func() {//非静态成员函数只有一份,通过this来区分谁在调用。
		cout << "mA:" << this->mA << endl;
	}
	//静态成员函数也不占对象空间
	static void sfunc() {
	}
};
int person::mb=10;不占对象空间
int main() {
    //person p;
    //cout<<sizeof(p)<<endl;//4字节
	cout << sizeof(Person) << endl;//4字节
	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述

8、this指针
  • 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
  • c++通过提供特殊的对象指针,this指针,解决区分哪个对象调用自己的问题。this指针指向被调用的成员函数所属的对象
  • this指针是隐含每一个非静态成员函数内的一种指针
  • this指针不需要定义,直接使用即可

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
	Person(int age)
	{
		//1、当形参和成员变量同名时,可用this指针来区分,this指针指向的是被调用的成员函数所属对象。即p1、p2等
		this->age = age;//如果不带this  两边都是形参age  age=age,要么加m_,要么this指针
	}
	Person& PersonAddPerson(Person p)//以引用的方式返回,如果不是引用,是值传递,
	//结果是20,因为调用拷贝构造函数,复制一份,p2‘,假如是值传递,拷贝出另一个,创建出一个新的对象,
	//p2本身没有继续相加,所以要用引用的方式返回
	{
		this->age += p.age;
		//返回对象本身
		return *this;
	}
	int age;
};
void test01()
{
	Person p1(10);
	cout << "p1.age = " << p1.age << endl;
	Person p2(10);
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);//链式编程思想
	cout << "p2.age = " << p2.age << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}
9、空指针
  • C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
  • 如果用到this指针,需要加以判断保证代码的健壮性
//空指针访问成员函数
class Person {
public:
	void ShowClassName() {
		cout << "我是Person类!" << endl;
	}
	void ShowPerson() {
		if (this == NULL) {
			return;
		}
		cout << mAge << endl;//默认都是this->mage,但由于Person * p = NULL;,所以this是空指针,没有对象,所以无法访问年龄
	}
public:
	int mAge;//非静态成员变量
};
void test01()
{
	Person * p = NULL;
	p->ShowClassName(); //空指针,可以调用成员函数,注意指针调用函数或访问属性时用箭头
	p->ShowPerson();  //但是如果成员函数中用到了this指针,就不可以了
}
int main() {
	test01();
	system("pause");
	return 0;
}
10、常函数和常对象

常函数:

  • 成员函数后加const后我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数,都只读
  • 常对象不能修改成员变量的值,但是可以访问
    常对象可以修改mutable修饰成员变量

this指针的本质是一个指针常量

class Person {
public:
	Person() {
		m_A = 0;
		m_B = 0;
	}
	//this指针的本质是一个指针常量,指针的指向不可修改
	//如果想让指针指向的值也不可以修改,需要声明常函数
	void ShowPerson() const {
		//const Type* const pointer;
		//this = NULL; //函数一旦被调用,this指针指向就确定了,不能修改指针的指向 Person* const this;
		//this->mA = 100; //ma=100;等价于this->mA = 100;。this指针本质是指针常量,指针指向不可修改,但是this指针指向的对象的数据是可以修改的
		//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
		this->m_B = 100;
	}
	void MyFunc() const {
		//mA = 10000;//如果没有const,内部能够修改属性,那么常对象是不能修改属性的,所以矛盾了
	}
public:
	int m_A;
	mutable int m_B; //特殊变量,即使在常函数中也可以修改,常对象也可以修改,可修改 可变的
};
//const修饰对象  常对象
void test01() {
	const Person person; //常量对象  
	cout << person.m_A << endl;
	//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
	person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
	//常对象访问成员函数
	person.MyFunc(); //常对象不能调用普通成员函数,因为普通成员函数可以修改属性,常对象只能调用常函数
}
int main() {
	test01();
	system("pause");
	return 0;
}

友元

11、友元
  • 友元的目的就是让一个函数或者类 访问另一个类中私有成员
  • 友元的关键字为 friend
  • 友元的三种实现
  1. 全局函数做友元
  2. 类做友元
  3. 成员函数做友元

全局函数做友元

class Building
{
	//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
	friend void goodGay(Building * building);
public:
	Building()
	{
		this->m_SittingRoom = "客厅";
		this->m_BedRoom = "卧室";
	}
public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom; //卧室
};
void goodGay(Building * building)
{
	cout << "好基友正在访问: " << building->m_SittingRoom << endl;
	cout << "好基友正在访问: " << building->m_BedRoom << endl;
}
void test01()
{
	Building b;
	goodGay(&b);
}
int main(){
	test01();
	system("pause");
	return 0;
}

类做友元

class Building;//声明
class goodGay
{
public:
	goodGay();
	void visit();

private:
	Building *building;
};

class Building
{
	//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
	friend class goodGay;
public:
	Building();
public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};
Building::Building()//类外写成员函数
{
	this->m_SittingRoom = "客厅";
	this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
	building = new Building;//在堆区创建一个对象,并且让building*building指向新对象,返回的是地址。 
}
void goodGay::visit()
{
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
	goodGay gg;//创建goodgay对象,构造函数先会创建一个building,会调用building构造函数,会给building对象赋初值。
	gg.visit();//调用时就能访问,但bedroom是私有,用friend。

}
int main(){
	test01();
	system("pause");
	return 0;
}

成员函数做友元

class Building;
class goodGay
{
public:
	goodGay();
	void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
	void visit2(); 
private:
	Building *building;
};
class Building
{
	//告诉编译器  goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
	friend void goodGay::visit();
public:
	Building();
public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};
Building::Building()
{
	this->m_SittingRoom = "客厅";
	this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
	building = new Building;
}
void goodGay::visit()//必须定义在被设置为友元的类的后面。
{
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void goodGay::visit2()
{
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	//cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
	goodGay  gg;
	gg.visit();

}
int main(){
	test01();
	system("pause");
	return 0;
}

运算符重载

12、运算符重载
  • 对于内置的数据类型的表达式的的运算符是不可能改变的(如1+1=2)
  • 左移运算符属于ostream
  • 占位参数的(int)使用,可以区分两个函数名相同的函数,重载
  • 出现深拷贝时析构函数要发生变化
  • 函数调用运算符,仿函数
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值