C++类和对象——封装、初始化和清理

目录

(1),基本概念

(2),构造函数的分类及调用

(3),拷贝构造函数调用时机

(4),构造函数调用规则

(5),深拷贝和浅拷贝

(6),初始化列表

(7),类对象做类成员-对象成员 

(8),静态成员

(9),拷贝构造函数形参类型 


将事物的属性和行为封装到一个类中,属性和行为作为一个整体,称为封装。

类中的属性称为成员属性或数据成员;

类中的行为称为成员函数或成员方法;

类中的属性和行为有三种访问权限:public、protected、private,默认的访问权限为private。

public权限成员类内可以访问,类外也可以访问;

protected权限成员类内可以访问,类外不可以访问;

private权限成员类内可以访问,类外不可以访问;

protected权限成员和private权限成员区别是,子类可以访问父类protected权限成员,而不可以访问父类private权限成员;

此处的类内指类定义的内部,类外指类定义的对象;

class和struct的区别:class的默认权限为private,struct默认权限为public;

成员属性私有化:即将类中的属性权限设置为private,通过public权限的成员函数控制成员属性的读或写,有两个优点:可以控制成员属性的读写权限;检测数据的有效性;

(1),基本概念

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

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

构造函数语法:类名(){}

构造函数没有返回值,也不写void;

函数名与类名相同;

构造函数可以有参数,可以发生重载;

程序在调用对象时候会自动调用构造函数,无需手动调用,而且只会调用一次;

析构函数语法:~类名(){}

析构函数没有返回值也不写void;

函数名称与类名相同,在名称前加上~;

析构函数不可以有参数,因此不可以重载;

程序在对象销毁前自动调用析构函数,无需手动调用,而且只会调用一次;

注意:构造函数和析构函数都是在public权限下。

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

class person 
{
public:
	person()
	{
		cout << "person的构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void test()
{
	person p;
}

int main()
{
	test();

	system("pause");
	return 0;
}

运行结果:

person的构造函数调用
person的析构函数调用
请按任意键继续. . .

(2),构造函数的分类及调用

按是否有参数分为:无参构造函数和有参构造函数;

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

构造函数调用方式:括号法、显示法、隐式转换法;

括号法语法:

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

class person 
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void test()
{
	//括号法
	person p1; //无参构造函数
	person p2(10);//有参构造函数
	person p3(p2);//拷贝构造函数
}

int main()
{
	test();

	system("pause");
	return 0;
}

运行结果:

person的无参构造函数调用
person的有参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. .

注意:使用括号法时,无参构造函数的调用不能加括号,如person p();此种语法编译器会认为是函数的声明,函数返回类型是person,函数名是p。

显示法:

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

class person 
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void test()
{
	//显示法
	person p1;
	person p2 = person(10); //有参构造函数定义
	person p3 = person(p2); //拷贝构造函数调用

}

int main()
{
	test();

	system("pause");
	return 0;
}

运行结果:

person的无参构造函数调用
person的有参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. . .

注意:

匿名对象:person(10),匿名对象的特点,当前行运行后,系统会立即回收匿名对象。

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

class person 
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void test()
{
	//显示法
    person(10); //匿名对象
	cout << "aaaaa" << endl;

}

int main()
{
	test();

	system("pause");
	return 0;
}

匿名对象的运行:

person的有参构造函数调用
person的析构函数调用
aaaaa
请按任意键继续. . .

注意:不要使用显示法调用拷贝构造函数初始化匿名对象 ,因为person(p2)编译器会识别为peson p2,如果p2已存在会出现二义性。

隐式转换法:

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

class person 
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void test()
{
	//隐式转换法
	person p1 = 10;//有参构造函数
	person p2 = p1;//拷贝构造函数

}

int main()
{
	test();

	system("pause");
	return 0;
}

运行结果:

person的有参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. . .

(3),拷贝构造函数调用时机

拷贝构造函数的调用时机分为三类:

第一类:使用一个已经创建的对象初始化一个新对象:

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

class person 
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void test()
{

	person p1(10);//有参构造函数
	person p2(p1);//拷贝构造函数

}

int main()
{
	test();

	system("pause");
	return 0;
}

第二类:值传递的方式给函数传值

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

class person 
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void dowork(person p)
{

}

void test()
{
	person p(10);
	dowork(p);
}

int main()
{
	test();

	system("pause");
	return 0;
}

运行结果:

person的有参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. . .

 需要注意的是,当函数的形参是引用类型时,不会调用到拷贝构造函数,下边代码中将函数dowork的形参类型修改为person& p

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

class person
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void dowork(person& p)
{

}

void test()
{
	person p(10);
	dowork(p);
}

int main()
{
	test();

	system("pause");
	return 0;
}

运行结果:

person的有参构造函数调用
person的析构函数调用
请按任意键继续. . .

 

第三类:值方式返回局部对象

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

class person 
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

person dowork()
{
	person p;
	return p;
}

void test()
{
	dowork();
}

int main()
{
	test();

	system("pause");
	return 0;
}

运行结果:

person的无参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. . .

同样的,如果函数的返回值类型是引用,也不会调用到拷贝构造函数,下边代码中将函数dowrok的返回值类型设置为person&

 

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

class person
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

person& dowork()
{
	person p;
	return p;
}

void test()
{
	dowork();
}

int main()
{
	test();

	system("pause");
	return 0;
}

运行结果:

person的无参构造函数调用
person的析构函数调用
请按任意键继续. . .

 只在创建类对象p时调用了无参构造函数。

(4),构造函数调用规则

默认情况下,C++编译器给一个类至少提供三个函数:

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

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

3,拷贝构造函数,对属性进行值拷贝;

如果用户定义了有参构造函数,C++编译器就不提供默认构造函数,但是会提供拷贝构造函数;

如果用户定义了拷贝构造函数,C++编译器就不提供有参构造函数和默认构造函数(无参构造函数);

注意:编译器本身不会提供有参构造函数。

(5),深拷贝和浅拷贝

浅拷贝:编译器提供的拷贝构造函数进行的对象属性拷贝操作;

浅拷贝带来的问题:会出现堆区内存的重复释放。因此需要使用深拷贝进行属性的拷贝操作,即自定义拷贝构造函数在堆区创建内存。

深拷贝:即在拷贝构造函数调用时,重新在堆区申请内存,将被拷贝对象存放在堆区的数据拷贝到重新申请的堆区内存中。

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

class person 
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int age,int height)
	{
		m_Age = age;
		m_Height = new int(height);
		cout << "person的有参构造函数调用"<< endl;
	}

	person(const person& a)
	{
		m_Age = a.m_Age;
		m_Height = new int(*a.m_Height);
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
		if (m_Height != NULL)
		{
			delete m_Height;
			m_Height = NULL;
		}
	}

	int m_Age;
	int* m_Height;

};



void test()
{
	person p1(18, 160);
	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;
}

 下图中为浅拷贝存在问题:

通过深拷贝解决问题释义图:

(6),初始化列表

C++提供初始化列表语法,用来对属性初始化;

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

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

class person 
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a ,int b, int c):m_A(a),m_B(b),m_C(c)
	{
		cout << "person的有参构造函数调用"<< endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

	int m_A;
	int m_B;
	int m_C;
};



void test()
{
	person p(10, 20, 30);
	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;
}

运行结果:

person的有参构造函数调用
m_A = 10
m_B = 20
m_C = 30
person的析构函数调用
请按任意键继续. . .

(7),类对象做类成员-对象成员 

一个类的对象作为另一个类的成员,如下边语法中A类的对象a作为B类的成员。

语法:

class A{};

class B

{

        A a;

}

类对象做类成员的应用场景:

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

class phone
{
public:
	phone(string brand)
	{
		m_Brand = brand;
		cout << "phone构造函数执行" << endl;
	}

	~phone()
	{
		cout << "phone析构函数执行" << endl;
	}

	string m_Brand;
};

class person
{
public:
	person(string Name, string Phone) :m_Name(Name), m_Phone(Phone)
	{
		cout << "person构造函数执行" << endl;
	}

	~person()
	{
		cout << "person析构函数执行" << endl;
	}

	string m_Name;
	phone m_Phone;
};

void test()
{
	person p("张三", "huawei");
}
int main()
{
	test();

	system("pause");
	return 0;
}

 运行结果:

phone构造函数执行
person构造函数执行
person析构函数执行
phone析构函数执行
请按任意键继续. . .

可以看到,phone类对象做类person成员时, 先创建phone类对象再创建person类对象,析构时先析构person类对象再析构phone类对象。

(8),静态成员

在成员变量和成员函数之前加上static关键字,称为静态成员;

静态成员变量:

所有对象共享同一份数据,因此一个对象赋值后,其他对象的这个变量也为同样的值;

在编译阶段分配内存,在全局区分配内存;

类内声明,类外初始化;

静态成员函数:

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

静态成员函数只能访问静态成员变量;

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

//静态成员

//静态属性-对象共享同一份数据

class Student
{
public:
	static string Sex;

	int Age;

	//静态函数,只能访问静态属性,不能访问非静态属性
	static void function()
	{
		cout << "static函数运行" << endl;
		Sex = "Male";
	}
};

string Student::Sex = "Female";   //静态属性,类内声明,类外要初始化

void test01()
{
	Student stu1;
	stu1.Age = 10;

	//cout << "stu1的年龄:" <<stu1.Age<< endl;
	//cout<< "stu1的性别:" << stu1.Sex<<endl;

	Student stu2;
	stu2.Age = 11;
	stu2.Sex = "Male";

	//cout << "stu2的年龄:" << stu2.Age << endl;
	//cout << "stu2的性别:" << stu2.Sex << endl;

	//cout << "stu1的年龄:" << stu1.Age << endl;
	//cout << "stu1的性别:" << stu1.Sex << endl;

	//静态属性访问方式,直接通过类名+::+属性名进行访问
	cout << "stu1的性别:" << Student::Sex << endl;
	cout << "stu2的性别:" << Student::Sex << endl;
}

void main()
{
	test01();
	system("pause");
	return;
}

运行结果:

stu1的性别:Male
stu2的性别:Male
请按任意键继续. . .

注意:静态成员变量需要在类内声明,类外初始化。 

从所占内存角度分析静态成员:

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

class A{};  //不包括任何成员的类,所占内存空间为1

class B     //只包含一个静态成员变量的类
{
	static int a;
};

int B::a = 0;

class C    //包含一个非静态成员变量的类
{
	int b;
};

class D     //包含一个静态成员函数的类
{
	static void func()
	{
		
	}
};

class E      //包含一个非静态成员函数的类
{
	void func()
	{

	}
};

void test()
{
	A a;
	cout << "不包括任何成员的类对象所占空间:" << sizeof(a) << endl;

	B b;
	cout << "只包含一个静态成员变量的类对象所占空间:" << sizeof(b) << endl;

	C c;
	cout << "只含一个非静态成员变量的类对象所占空间:" << sizeof(c) << endl;

	D d;
	cout << "只含一个静态成员函数的类对象所占空间:" << sizeof(d) << endl;

	E e;
	cout << "只含一个非静态成员函数的类对象所占空间:" << sizeof(e) << endl;
}

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

运行结果:

不包括任何成员的类对象所占空间:1
只包含一个静态成员变量的类对象所占空间:1
只含一个非静态成员变量的类对象所占空间:4
只含一个静态成员函数的类对象所占空间:1
只含一个非静态成员函数的类对象所占空间:1
请按任意键继续. . .

因为所有对象共享同一个静态成员变量和静态成员函数,因此可以通过以下格式访问

类名:: 静态成员名;

注意:

静态成员也有权限,类外访问不到私有的静态成员;

静态成员函数只能访问静态成员变量,不能访问非静态成员变量;

(9),拷贝构造函数形参类型 

 下边代码中的拷贝构造函数的形参类型为当前类的应用,并且是const引用。

为什么拷贝构造函数的形参类型是当前类的const引用呢?

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

class person
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void test()
{
	person p1(10);

	person p2(p1);
}

int main()
{
	test();

	system("pause");
	return 0;
}

 第一个问题:为什么必须是当前类的引用呢?

如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。
只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 C++ 语法的要求。

第二个问题: 为什么是 const 引用呢?

拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 const 限制后,这个含义更加明确了。
另外一个原因是,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型。如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了。

如果将拷贝构造函数的形参的类型修改,去掉const,则不能对const类型的对象进行拷贝,如果同时存在拷贝构造函数的形参既有const引用,又有普通的引用,即拷贝构造函数发生重载,则可以对const和非const类型对象进行拷贝操作,如下代码,但我们一般只需要定义形参是const引用类型的拷贝构造函数即可。

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

class person
{
public:
	person()
	{
		cout << "person的无参构造函数调用" << endl;
	}

	person(int a)
	{
		cout << "person的有参构造函数调用" << endl;
	}

	person(person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	person(const person& a)
	{
		cout << "person的拷贝构造函数调用" << endl;
	}

	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};

void test()
{
	const person p1(10);

	person p2(p1);

	person p3(20);

	person p4(p3);
}

int main()
{
	test();

	system("pause");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值