c++复习第五章:类与对象的封装

C和C++中struct区别

  1. c语言struct只有变量

  2. c++语言struct 既有变量,也有函数

typedef struct _Person{
	char name[64];
	int age;
}Person;
typedef struct _Aninal{
	char name[64];
	int age;
	int type; //动物种类
}Ainmal;

void PersonEat(Person* person){
	printf("%s在吃人吃的饭!\n",person->name);
}
void AnimalEat(Ainmal* animal){
	printf("%s在吃动物吃的饭!\n", animal->name);
}

int main(){

	Person person;
	strcpy(person.name, "小明");
	person.age = 30;
	AnimalEat(&person);

	return EXIT_SUCCESS;
}

在c语言中,行为和属性是分开的,也就是说吃饭这个属性不属于某类对象,而属于所有的共同的数据,所以不单单是PeopleEat可以调用Person数据,AnimalEat也可以调用Person数据,那么万一调用错误,将会导致问题发生。

封装

  1. 把变量(属性)和函数(操作)合成一个整体,封装在一个类中

  2. 对变量和函数进行访问控制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7EWfbO4X-1595337986759)(file:///C:\Users\Godfiry\AppData\Local\Temp\ksohtml12228\wps1.jpg)]

*[struct和class的区别?]*

class默认访问权限为private,struct默认访问权限为public.

/*请设计一个Person类,Person类具有name和age属性,提供初始化函数(Init),并提供对name和age的读写函数(set,get),但必须确保age的赋值在有效范围内(0-100),超出有效范围,则拒绝赋值,并提供方法输出姓名和年龄.*/

构造函数

构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

语法:构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数。

按参数类型:分为无参构造函数和有参构造函数

按类型分类:普通构造函数和拷贝构造函数

//构造函数的几种调用方式
//1. 无参构造调用方式
void test01()
{
	
	//调用无参构造函数
	Person person1; 
	person1.PrintPerson();

	//无参构造函数错误调用方式
	//Person person2();
	//person2.PrintPerson();
}
//2. 调用有参构造函数
void test02()
{
	
	//第一种 括号法,最常用
	Person person01(100);
	person01.PrintPerson();

	//调用拷贝构造函数
	Person person02(person01);
	person02.PrintPerson();

	//第二种 匿名对象(显示调用构造函数)
	Person(200); //匿名对象,没有名字的对象

	Person person03 = Person(300);
	person03.PrintPerson();

	//注意: 使用匿名对象初始化判断调用哪一个构造函数,要看匿名对象的参数类型
	Person person06(Person(400)); //等价于 Person person06 = Person(400);
	person06.PrintPerson();

	//第三种 =号法 隐式转换
	Person person04 = 100; //Person person04 =  Person(100)
	person04.PrintPerson();

	//调用拷贝构造
	Person person05 = person04; //Person person05 =  Person(person04)
	person05.PrintPerson();
}
//不能调用拷贝构造函数去初始化匿名对象,也就是说以下代码不正确:

class Teacher{
public:
	Teacher(){
		cout << "默认构造函数!" << endl;
	}
	Teacher(const Teacher& teacher){
		cout << "拷贝构造函数!" << endl;
	}
public:
	int mAge;
};
void test(){
	
	Teacher t1;
	//error C2086:“Teacher t1”: 重定义
	Teacher(t1);  //此时等价于 Teacher t1;
}

/*   b为A的实例化对象,A a = A(b) 和 A(b)的区别?
   当A(b) 有变量来接的时候,那么编译器认为他是一个匿名对象,当没有变量来接的时候,编译器认为你A(b) 等价于 A b.*/

拷贝构造的调用时机

  1. 对象以值传递的方式传给函数参数
  2. 函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
  3. 用一个对象初始化另一个对象
//1. 旧对象初始化新对象
void test01(){

	Person p(10);
	Person p1(p);
	Person p2 = Person(p);
	Person p3 = p; // 相当于Person p2 = Person(p);
}

//2. 传递的参数是普通对象,函数参数也是普通对象,传递将会调用拷贝构造
void doBussiness(Person p){}

void test02(){
	Person p(10);
	doBussiness(p);
}

//3. 函数返回局部对象
Person MyBusiness(){
	Person p(10);
	cout << "局部p:" << (int*)&p << endl;
	return p;
}
void test03() //编译器进行了优化
{
	//vs release、qt下没有调用拷贝构造函数
	//vs debug下调用一次拷贝构造函数
	Person p = MyBusiness();
	cout << "局部p:" << (int*)&p << endl;
}

第三种情况的解释

编译器存在一种对返回值的优化技术,RVO(Return Value Optimization).在vs debug模式下并没有进行这种优化,所以函数MyBusiness中创建p对象,调用了一次构造函数,当编译器发现你要返回这个局部的对象时,编译器通过调用拷贝构造生成一个临时Person对象返回,然后调用p的析构函数。

我们从常理来分析的话,这个匿名对象和这个局部的p对象是相同的两个对象,那么如果能直接返回p对象,就会省去一个拷贝构造和一个析构函数的开销,在程序中一个对象的拷贝也是非常耗时的,如果减少这种拷贝和析构的次数,那么从另一个角度来说,也是编译器对程序执行效率上进行了优化。

所以在这里,编译器偷偷帮我们做了一层优化: 当我们这样去调用: Person p = MyBusiness(); 编译器偷偷将我们的代码更改为:

void MyBussiness(Person& _result)
{
    _result.X:X(); //调用Person默认拷贝构造函数
    //.....对_result进行处理
    return;
}

构造函数的注意点:

默认情况 c++为我们提供三个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造(简单的值拷贝)

用户定义了拷贝构造,c++不会提供默认构造函数的拷贝构造

定义了构造函数,c++不会提供默认无参构造,但会提供拷贝构造

深拷贝和浅拷贝

浅拷贝

同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝

问题:

浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。(两个对象的指针同一个内存空间),当空间的释放的时候,只能释放一次,出现运行时错误,)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6Wmo0Ck-1595337986763)(file:///C:\Users\Godfiry\AppData\Local\Temp\ksohtml5504\wps1.jpg)]

深拷贝(有指针时,自定义拷贝构造,析构函数)

类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imMW5yOw-1595337986766)(file:///C:\Users\Godfiry\AppData\Local\Temp\ksohtml5504\wps2.jpg)]

#include<iostream>

using namespace std;

class person
{
public:
	person(char *name,int age);
	~person();
	person(const person &person);

private:
	char *m_name;
	int m_age;
	
};

person::person(char *name,int age)
{
	char *m_name = (char*)malloc(sizeof(char)*100);
	strcpy(m_name,name);
	m_age = age;
}

person::person(const person &person)
{
	char *m_name = (char*)malloc(sizeof(char)*100);
	strcpy(m_name,person.m_name);
	m_age = person.m_age;
}

person::~person()
{
	if(m_name != NULL)
	{
		free(m_name);
	}
}

int main()
{
	person p1("zhang3",5);
	person p2 = p1;
	return 0;
}

析构函数

析构函数主要用于对象****销毁前****系统自动调用,执行一些清理工作。

  1. 析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。

多个对象的构造和析构

初始化列表

(初始化成员列表(参数列表)只能在构造函数使用。)

//传统方式初始化
	Person(int a,int b,int c)
    {
		mA = a;
		mB = b;
		mC = c;
	}
//初始化列表方式初始化
	Person(int a, int b, int c):mA(a),mB(b),mC(c)
    {
        
    }

类对象作为成员

一个类可以作为其他类的成员

先调用对象成员的构造函数,再调用本身的构造函数。

析构函数和构造函数调用顺序相反,先构造,后析构。

#include<iostream>

using namespace std;

class person
{
public:
	person()
	{
		cout << "person  new" <<endl;
	}
	~person()
	{
		cout << "person delete" <<endl;
	}


};

class man
{
public:
	man()
	{
		cout << "man  new" <<endl;
	}
	~man()
	{
		cout << "man  delete" <<endl;
	}

private:
	person p;

};

int main()
{
	man m1;

	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oXNwLKWE-1595337986771)(C:\Users\Godfiry\AppData\Roaming\Typora\typora-user-images\image-20200719103418272.png)]

expilt关键字(保留)

c++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转换中使用。

注意点

  1. explicit用于修饰构造函数,防止隐式转化。
  2. 是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)而言。

动态对象的创建(堆空间创建对象)

C语言中使用malloc和free实现空间的申请和释放

但在初始化一个对象时 使用malloc 和free时出现以下问题:

  1. 程序员必须确定对象的长度。
  2. malloc返回一个void指针,c++不允许将void赋值给其他任何指针,必须强转。
  3. malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功。
  4. 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。
//c语言实例化一个对象
class Person{
public:
	Person(){
		mAge = 20;
		pName = (char*)malloc(strlen("john")+1);
		strcpy(pName, "john");
	}
	void Init(){
		mAge = 20;
		pName = (char*)malloc(strlen("john")+1);
		strcpy(pName, "john");
	}
	void Clean(){
		if (pName != NULL){
			free(pName);
		}
	}
public:
	int mAge;
	char* pName;
};
int main(){

	//分配内存
	Person* person = (Person*)malloc(sizeof(Person));
	if(person == NULL){
		return 0;
	}
	//调用初始化函数
	person->Init();
	//清理对象
	person->Clean();
	//释放person对象
	free(person);

	return EXIT_SUCCESS;
}
//c的动态内存分配函数太复杂,容易令人混淆,是不可接受的,c++中我们推荐使用运算符new 和 delete.

c++创建一个对象

//用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。
Person* person = new Person;
//相当于:
Person* person = (Person*)malloc(sizeof(Person));
	if(person == NULL){
		return 0;
	}
person->Init();

解释:

  1. New操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。
  2. 它带有内置的长度计算、类型转换和安全检查。

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

静态成员变量

在一个类中,若将一个成员变量声明为static,这种成员称为静态成员变量。与一般的数据成员不同,无论建立了多少个对象,都只有一个静态数据的拷贝。静态成员变量,属于某个类,所有对象共享。

在编译阶段就进行分配空间,对象阿hi没有创建,就已经分配了空间

注意:

  1. 静态成员变量必须在类中声明,在类外定义。
  2. 静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间。
  3. 静态数据成员可以通过类名或者对象名来引用。
class Person
{
public:
	//类的静态成员属性
	static int sNum;
private:
	static int sOther;
};

//类外初始化,初始化时不加static
int Person::sNum = 0;
int Person::sOther = 0;

int main()
{


	//1. 通过类名直接访问
	Person::sNum = 100;
	cout << "Person::sNum:" << Person::sNum << endl;//100

	//2. 通过对象访问
	Person p1, p2;
	p1.sNum = 200;

	cout << "p1.sNum:" << p1.sNum << endl;//200  共享数据
	cout << "p2.sNum:" << p2.sNum << endl;//200

	//3. 静态成员也有访问权限,类外不能访问私有成员
	//cout << "Person::sOther:" << Person::sOther << endl;
	Person p3;
	//cout << "p3.sOther:" << p3.sOther << endl;

	system("pause");
	return 0;
}

静态成员函数

在类定义中,前面有static说明的成员函数称为静态成员函数。静态成员函数使用方式和静态变量一样,同样在对象没有创建前,即可通过类名调用。静态成员函数主要为了访问静态变量,但是,不能访问普通成员变量。

静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。

注意:

  1. n 静态成员函数只能访问静态变量,不能访问普通成员变量

    解释:(各个对象的普通对象值不同 共有函数无法访问各个对象变量

  2. 静态成员函数的使用和静态成员变量一样

  3. 静态成员函数也有访问权限

  4. 普通成员函数可访问静态成员变量、也可以访问非经常成员变量

class Person
{
public:
	//普通成员函数可以访问static和non-static成员属性
	void changeParam1(int param){
		mParam = param;
		sNum = param;
	}
	//静态成员函数只能访问static成员属性
	static void changeParam2(int param)
    {
		//mParam = param; //无法访问
		sNum = param;
	}
private:
	static void changeParam3(int param)
    {
		//mParam = param; //无法访问
		sNum = param;
	}
public:
	int mParam;
	static int sNum;
};

//静态成员属性类外初始化
int Person::sNum = 0;

int main(){

	//1. 类名直接调用
	Person::changeParam2(100);

	//2. 通过对象调用
	Person p;
	p.changeParam2(200);

	//3. 静态成员函数也有访问权限
	//Person::changeParam3(100); //类外无法访问私有静态成员函数
	//Person p1;
	//p1.changeParam3(200);
	return 0;
}

const 静态成员变量

如果一个类的成员,既要实现共享,又要实现不可改变,那就用 static const 修饰。****定义静态const数据成员时,最好在类内部初始化****。

class Person
{
public:
	//static const int mShare = 10;
	const static int mShare = 10; //只读区,不可修改
};
int main()
{

	cout << Person::mShare << endl;
	//Person::mShare = 20;

	return EXIT_SUCCESS;
}

单例模式

Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其默认构造函数和拷贝构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。

class Printer
{
public:
	static Printer* getInstance(){ return pPrinter;}//返回一个对象
	void PrintText(string text){
		cout << "打印内容:" << text << endl;
		cout << "已打印次数:" << mTimes << endl;
		cout << "--------------" << endl;
		mTimes++;
	}
private:
	Printer(){ mTimes = 0; }
	Printer(const Printer&){}
private:
	static Printer* pPrinter;//私有成员
	int mTimes;
};

Printer* Printer::pPrinter = new Printer;//编译阶段创建一个对象

void test(){
	Printer* printer = Printer::getInstance();//共有函数获取
	printer->PrintText("离职报告!");
	printer->PrintText("入职合同!");
	printer->PrintText("提交代码!");
}

this指针

成员变量和函数的存储

c++通过封装,把数据和函数放到了一起,**但是成员函数(member function)虽然内含在class声明之内,却不出现在对象中。(不占对象空间)**n

每一个非内联成员函数(non-inline member function)只会诞生一份函数实例.

class MyClass01{
public:
	int mA;
};

class MyClass02{
public:
	int mA;
	static int sB;
};

class MyClass03{
public:
	void printMyClass(){
		cout << "hello world!" << endl;
	}
public:
	int mA;
	static int sB;
};

class MyClass04{
public:
	void printMyClass(){
		cout << "hello world!" << endl;
	}
	static void ShowMyClass(){
		cout << "hello world!" << endl;
	}
public:
	int mA;
	static int sB;
};

int main(){

	MyClass01 mclass01;
	MyClass02 mclass02;
	MyClass03 mclass03;
	MyClass04 mclass04;

	cout << "MyClass01:" << sizeof(mclass01) << endl; //4
	//静态数据成员并不保存在类对象中
	cout << "MyClass02:" << sizeof(mclass02) << endl; //4
	//非静态成员函数不保存在类对象中
	cout << "MyClass03:" << sizeof(mclass03) << endl; //4
	//静态成员函数也不保存在类对象中
	cout << "MyClass04:" << sizeof(mclass04) << endl; //4

	return EXIT_SUCCESS;
}

C++类对象中的变量和函数是分开存储。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iQ3zdWzx-1595337986775)(file:///C:\Users\Godfiry\AppData\Local\Temp\ksohtml15096\wps1.jpg)]

代码是如何区分那个对象调用自己的呢?

通过this指针

this指针说明

c++通过提供特殊的对象指针,this指针,解决上述问题。This指针指向被调用的成员函数所属的对象。

c++规定,this指针是隐含在对象成员函数内的一种指针。当一个对象被创建后,它的每一个成员函数都含有一个系统自动生成的隐含指针this,用以保存这个对象的地址,也就是说虽然我们没有写上this指针,编译器在编译的时候也是会加上的。因此****this也称为“指向本对象的指针”,****this指针并不是对象的一部分,不会影响sizeof(对象)的结果。

this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数连接在一起,在外部看来,每一个对象都拥有自己的函数成员。一般情况下,并不写this,而是让系统进行默认设置。

说明:

  1. this指针永远指向当前对象
  2. 隐含于每个类的非静态成员函数中
  3. 静态成员函数没有this指针,不能操作非静态成员变量

this指针使用

  1. 当形参和成员变量同名时,可用this指针来区分
  2. 在类的非静态成员函数中返回对象本身,可使用return *this.
class Person{
public:
	//1. 当形参名和成员变量名一样时,this指针可用来区分
	Person(string name,int age){
		//name = name;
		//age = age; //输出错误
		this->name = name;
		this->age = age;
	}
	//2. 返回对象本身的引用
	//重载赋值操作符
	//其实也是两个参数,其中隐藏了一个this指针
	Person PersonPlusPerson(Person& person){
		string newname = this->name + person.name;
		int newage = this->age + person.age;
		Person newperson(newname, newage);
		return newperson;
	}
	void ShowPerson(){
		cout << "Name:" << name << " Age:" << age << endl;
	}
public:
	string name;
	int age;
};

//3. 成员函数和全局函数(Perosn对象相加)
Person PersonPlusPerson(Person& p1,Person& p2){
	string newname = p1.name + p2.name;
	int newage = p1.age + p2.age;
	Person newperson(newname,newage);
	return newperson;
}

int main(){

	Person person("John",100);
	person.ShowPerson();

	cout << "---------" << endl;
	Person person1("John",20);
	Person person2("001", 10);
	//1.全局函数实现两个对象相加
	Person person3 = PersonPlusPerson(person1, person2);
	person1.ShowPerson();
	person2.ShowPerson();
	person3.ShowPerson();
	//2. 成员函数实现两个对象相加
	Person person4 = person1.PersonPlusPerson(person2);
	person4.ShowPerson();

	system("pause");
	return EXIT_SUCCESS;
}

const修饰成员函数时

用const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量,

当成员变量类型符前用mutable修饰时例外。

#include<iostream>

using namespace std;
//const修饰成员函数
class Person{
public:
	Person(){
		this->mAge = 0;
		this->mID = 0;
	}
	//在函数括号后面加上const,修饰成员变量不可修改,除了mutable变量
	void sonmeOperate() const
	{
		//没有加mutable
		this->mAge = 200; //mAge不可修改   error: assignment of data-member `Person::mAge' in read-only structure

		this->mID = 10;
	}
	void ShowPerson(){
		cout << "ID:" << mID << " mAge:" << mAge << endl;
	}
private:
	int mAge;
	mutable int mID;
};

int main(){

	Person person;
	person.sonmeOperate();
	person.ShowPerson();

	system("pause");
	return 0;
}

const修饰对象

  1. 常对象只能调用const的成员函数
  2. 常对象可访问 const 或非 const 数据成员,不能修改,除非成员用mutable修饰
class Person{
public:
	Person(){
		this->mAge = 0;
		this->mID = 0;
	}
	void ChangePerson() const{
		mAge = 100;
		mID = 100;
	}
	void ShowPerson(){
        this->mAge = 1000;
		cout << "ID:" << this->mID << " Age:" << this->mAge << endl;
	}

public:
	int mAge;
	mutable int mID;
};

void test(){	
	const Person person;
	//1. 可访问数据成员
	cout << "Age:" << person.mAge << endl;
	//person.mAge = 300; //不可修改
	person.mID = 1001; //但是可以修改mutable修饰的成员变量
	//2. 只能访问const修饰的函数
	//person.ShowPerson();
	person.ChangePerson();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值