C++入门基础教程:类和对象(中)

Posted Zhi Zhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++入门基础教程:类和对象(中)相关的知识,希望对你有一定的参考价值。

前言

博主通过对C++基础知识的总结,有望写出深入浅出的C++基础教程专栏,并分享给大家阅读,今后的一段时间我将持续更新C++入门系列博文,想学习C++的朋友可以关注我,希望大家有所收获。

2 对象特性

续接上一节:C++入门基础教程(四):类和对象(上)

2.5 深拷贝与浅拷贝

浅拷贝:简单的复制拷贝操作(由编译器提供),易导致堆区的内存重复释放;

#include <iostream>
using namespace std;

class Person
{
public:
	Person(int age,int height)    // 有参构造函数
	{
		m_Age = age;
		m_Height = new int(height);  // 堆区开辟的数据,需要手动释放
		cout << "Person有参构造函数的调用" << endl;
	}

	~Person()   // 析构函数
	{
		// 将堆区开辟的数据做释放操作
		if (m_Height != NULL)
		{
			delete m_Height;
			m_Height = NULL;
		}
		cout << "Person析构函数的调用" << endl;
	}

	int m_Age;        // 年龄
	int *m_Height;    // 身高
};

void test()
{
	Person p1(18, 170);
	cout << "p1的年龄为:" << p1.m_Age << 
		" 身高:" << *p1.m_Height << endl;
	
	Person p2(p1);   // 会调用编译器提供的拷贝构造函数
	cout << "p2的年龄为:" << p2.m_Age << 
		" 身高:" << *p2.m_Height << endl;
}

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

执行语句 Person p2(p1); 会调用编译器提供的拷贝构造函数,该函数做的是浅拷贝,即将p1这个对象的属性复制一份给p2。由于new int(height) 的数据会存放在堆区,需要程序员手动释放,因此在上述析构函数中做数据的释放操作时,先释放p2的指针m_Height指向的内存,再释放p1的指针m_Height指向的内存(p1和p2是局部变量,存放在栈区,遵循后进先出的原则),但是这两个对象的指针m_Height指向的是同一块内存地址,这样就导致堆区的内存重复释放,程序不能成功运行。

浅拷贝

 深拷贝:在堆区重新申请空间,进行拷贝操作。

做深拷贝操作,此时p1和p2的指针m_Height指向的地址不同,但是获取的内容是相同的。这样既达到了拷贝对象的作用,又避免了内存重复释放。

// 自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person &p)
{
	cout << "Person拷贝构造函数的调用" << endl;
	m_Age = p.m_Age;
	// m_Height = p.m_Height; 编译器默认实现的就是这行代码
	// 深拷贝操作
	m_Height = new int(*p.m_Height);
}

2.6 初始化列表:给变量赋初值

语法:构造函数():属性1(值1),属性2(值2),...{} 

class Person
{
public:

	Person(int a,int b,int c):m_A(a),m_B(b),m_C(c)
	{

	}
	int m_A;
	int m_B;
	int m_C;
};

void test()
{
	Person p(13,34,18);
	cout << "m_A= " << p.m_A << endl;
	cout << "m_B= " << p.m_B << endl;
	cout << "m_C= " << p.m_C << endl;
}
int main()
{
	test();
	system("pause");
	return 0;
}

2.7 类对象作为类成员

当类中成员是其他类的对象时,称该成员是对象成员。

构造函数的调用顺序是,先调用对象成员的构造,在调用本类构造;构造函数的调用顺序与构造函数的调用顺序相反。

2.8 静态成员变量-静态成员函数

静态成员变量:

1)所有对象共享同一份数据;

2)在编译阶段分配内存;

3)类内声明,类外初始化。

静态成员函数:

1)所有对象共享同一个函数;

2)静态成员函数只能访问静态变量。

注意:非静态成员变量引用必须与特定对象对应,由于静态成员函数不知道该变量是哪个对象的属性,因而不可以访问非静态成员变量。

#include <iostream>
using namespace std;

class Person
{
public:
	static void func()
	{
		m_A = 100;   //静态成员函数可以访问静态成员变量
		// m_B = 200; 静态成员函数不可以访问非静态成员变量,无法区分到底是哪个对象的属性
		cout << "static void func调用" << endl;
	}

	static int m_A;   // 静态成员变量
	int m_B;      // 非静态成员变量

private:
	static void func2()
	{
		cout << "static void func2调用" << endl;
	}
};

int Person::m_A = 0;   // 类外初始化

void test()
{
	//1.通过对象访问
	Person p;
	p.func();
	//2.通过类名进行访问
	Person::func();
	//Person::func2();  类外访问不到私有静态成员函数
}

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

3 C++对象模型

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

在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。

空对象占用内存空间为1个字节;C++编译器给每个空对象分配一个字节空间,是为了区分空对象占内存的位置;每个空对象也应该有一个独一无二的内存地址。

class Person
{
	int m_A;         // 非静态成员变量  属于类对象上
	static int m_B;  // 静态成员变量  不属于类对象上

	void func() {}  // 非静态成员函数 不属于类对象上
	static void func2() {}   // 静态成员函数 不属于类对象上
};

int Person::m_B = 0;   // 类外初始化

void test()
{
	Person p;
	cout << "size of p= " << sizeof(p) << endl;  
}

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

上述代码中,Person类中加上静态成员变量、静态成员函数和非静态成员函数后,p的大小也一直为4个字节,即非静态成员变量m_A所占的内存大小。

总结:非静态成员变量属于类对象上;静态成员变量、静态成员函数和非静态成员函数均不属于类的对象上。

3.2 this指针概念

this指针指向被调用的成员函数所属的对象。

用途:

1)当形参和成员变量同名时,可用this指针来区分;

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

#include <iostream>
using namespace std;

class Person
{
public:
	Person(int age)
	{
		// 当形参和成员变量同名时,可用this指针来区分
		this->age = age;  // this指针指向被调用的成员函数所属的对象
	}

	Person & Personaddperson(Person p)
	{
		this->age += p.age;
		// this指向p2的指针,而*this指向的就是p2这个对象本体
		return *this;   //返回对象本身
	}
	int age;
};

void test()
{
	Person p1(10);
	cout << "p1.age= " << p1.age << endl;
	Person p2(18);

	// 链式编程思想
	p2.Personaddperson(p1).Personaddperson(p1);
	cout << "p2.age= " << p2.age << endl;
}

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

注意:Personaddperson函数一定要用引用的方式返回,因为用值的方式返回,调用拷贝构造函数时会复制一份新的对象,而引用的方式返回的一直是p2这个本体对象。

3.3 空指针访问成员函数

C++中空指针可以调用成员函数,但是成员函数中用到了this指针,就需要加以判断,保证代码的健壮性。

3.4 const修饰成员函数

常函数:成员函数后加const,称之为常函数;常函数内部不可以修改成员属性;成员属性声明时加关键字mutable后,在常函数中就可以修改。

常对象:声明对象前加const,称之为常对象;常对象只能调用常函数;常对象不可以调用普通成员函数,因为普通成员函数可以修改属性。

#include <iostream>
using namespace std;

class Person
{
public:

	// this指针的本质是指针常量,
	// 等价于Person *const this;
	// 在成员函数后面加上const,相当于 const Person *const this,修饰的是this指针,让指针指向的值也不可以修改
	void showPerson() const   // 常函数
	{
		// this->m_A = 100 ;  错误
		this->m_B = 200; // 正确
	}

	void func()
	{
		m_A = 100;  // 可修改成员属性
	}

	int m_A;
	mutable int m_B; // 加关键字mutable后,在常函数中就可以修改
};

void test01()
{
	Person p;
	p.showPerson;
}

void test02()
{
	const Person p;    // 常对象
	p.showPerson();    // 常对象只能调用常函数
	p.func();  // 错误,常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}

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

4 友元

友元的目的是让一个函数或者类访问另一个类中的私有成员。

关键字:friend

友元的三种实现:全局函数做友元、友元类、成员函数做友元。

4.1 全局函数做友元

#include <iostream>
using namespace std;
#include<string>

class Building
{
	friend void GoodGay(Building *building);
public:
	Building()
	{
		m_Sittingroom = "客厅";
		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 test()
{
	Building building;
	GoodGay(&building);
}

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

4.2 类做友元

#include <iostream>
using namespace std;
#include<string>

// 类作友元
class Building;
class GoodGay
{
public:
	GoodGay();
	void visit();  // 访问Building中的属性
	Building * building;
};

class Building
{
	// GoodGay是本类的好朋友,可以访问本类中的私有成员
	friend class GoodGay;
public:
	Building();
public:
	string m_Sittingroom;

private:
	string m_Bedroom;
};

// 类外写成员函数
Building::Building()
{
	m_Sittingroom = "客厅";
	m_Bedroom = "卧室";
}

GoodGay::GoodGay()
{
	// 创建建筑物对象
	building = new Building;
}

void GoodGay::visit()
{
	cout << "好朋友类正在访问:" << building->m_Sittingroom << endl;
	cout << "好朋友类正在访问:" << building->m_Bedroom << endl;
}

void test()
{
	GoodGay gg;
	gg.visit();
}

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

4.3 成员函数做友元

#include <iostream>
using namespace std;
#include<string>

// 成员函数做友元
class Building;
class GoodGay
{
public:
	GoodGay();
	void visit();  // 只让该函数访问GoodGay的私有属性
	void visit2();
	
private:
	Building *building;
};

class 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 << "visit函数正在访问:" << building->m_Sittingroom << endl;
	cout << "visit函数正在访问:" << building->m_Bedroom << endl;
}

void GoodGay::visit2()
{
	cout << "visit2函数正在访问:" << building->m_Sittingroom << endl;
	//cout << "visit2函数正在访问:" << building->m_Bedroom << endl;
}

void test()
{
	GoodGay gg;
	gg.visit();
	gg.visit2();
}

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

未完待续,敬请期待下一篇博客:C++入门基础教程(六):类和对象(下)

结束语

大家的点赞和关注是博主最大的动力,博主所有博文中的代码文件都可分享给您(除了少量付费资源),如果您想要获取博文中的完整代码文件,可通过C币或积分下载,没有C币或积分的朋友可在关注、点赞和评论博文后,私信发送您的邮箱,我会在第一时间发送给您。博主后面会有更多的分享,敬请关注哦!

以上是关于C++入门基础教程:类和对象(中)的主要内容,如果未能解决你的问题,请参考以下文章

C++入门基础教程:类和对象(下)

C++从入门到入土第二篇:类和对象基础

C++入门基础知识:类和引用

黑马程序员 C++教程从0到1入门编程笔记5C++核心编程(类和对象——继承多态)

黑马程序员 C++教程从0到1入门编程笔记5C++核心编程(类和对象——继承多态)

JAVA入门零基础小白教程day06-类和对象