首页 > 其他分享 >第七章 类_Part1

第七章 类_Part1

时间:2023-02-13 23:56:53浏览次数:43  
标签:初始化 函数 Person int Part1 编译器 第七章 构造函数

1.类和对象

1.1类和对象的基本概念

1.1.1C和C++中struct区别

  • c语言struct只有变量
  • c++语言struct 既有变量,也有函数

1.1.2 类的封装

​ 我们编写程序的目的是为了解决现实中的问题,而这些问题的构成都是由各种事物组成,我们在计算机中要解决这种问题,首先要做就是要将这个问题的参与者:事和物抽象到计算机程序中,也就是用程序语言表示现实的事物。
​ 那么现在问题是如何用程序语言来表示现实事物?现实世界的事物所具有的共性就是每个事物都具有自身的属性,一些自身具有的行为,所以如果我们能把事物的属性和行为表示出来,那么就可以抽象出来这个事物。
​ 比如我们要表示人这个对象,在c语言中,我们可以这么表示:

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数据,那么万一调用错误,将会导致问题发生。
​ 从这个案例我们应该可以体会到,属性和行为应该放在一起,一起表示一个具有属性和行为的对象。
​ 假如某对象的某项属性不想被外界获知,比如说漂亮女孩的年龄不想被其他人知道,那么年龄这条属性应该作为女孩自己知道的属性;或者女孩的某些行为不想让外界知道,只需要自己知道就可以。那么这种情况下,封装应该再提供一种机制能够给属性和行为的访问权限控制住。
​ 所以说封装特性包含两个方面,一个是属性和变量合成一个整体,一个是给属性和函数增加访问权限。

  • 封装(Encapsulation)
    • 把变量(属性)和函数(操作)合成一个整体,封装在一个类中
    • 对变量和函数进行访问控制
  • 访问权限
    1. 在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问
    2. 在类的外部(作用域范围外),访问权限才有意义:public,private,protected
    3. 在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时,private和protected是同等级的,外部不允许访问

protected:子类可以访问
private:子类不可以访问

//封装两层含义
//1. 属性和行为合成一个整体
//2. 访问控制,现实事物本身有些属性和行为是不对外开放
class Person{
//人具有的行为(函数)
public:
	void Dese(){ cout << "我有钱,年轻,个子又高,就爱嘚瑟!" << endl;}
//人的属性(变量)
public:
	int mTall; //多高,可以让外人知道
protected:
	int mMoney; // 有多少钱,只能儿子孙子知道
private:
	int mAge; //年龄,不想让外人知道
};

int main(){

	Person p;
	p.mTall = 220;
	//p.mMoney 保护成员外部无法访问
	//p.mAge 私有成员外部无法访问
	p.Dese();

	return EXIT_SUCCESS;
}

**[struct和class的区别?] **

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

class A{
	int mAge;
};
struct B{
	int mAge;
};

void test(){
	A a;
	B b;
	//a.mAge; //无法访问私有成员
	b.mAge; //可正常外部访问
}

4.1.3 将成员变量设置为private

  1. 可赋予客户端访问数据的一致性。

    ​ 如果成员变量不是public,客户端唯一能够访问对象的方法就是通过成员函数。如果类中所有public权限的成员都是函数,客户在访问类成员时只会默认访问函数,不需要考虑访问的成员需不需要添加(),这就省下了许多搔首弄耳的时间。

  2. 可细微划分访问控制。

    ​ 使用成员函数可使得我们对变量的控制处理更加精细。如果我们让所有的成员变量为public,每个人都可以读写它。如果我们设置为private,我们可以实现“不准访问”、“只读访问”、“读写访问”,甚至你可以写出“只写访问”。

class AccessLevels{
public:
	//对只读属性进行只读访问
	int getReadOnly(){ return readOnly; }
	//对读写属性进行读写访问
	void setReadWrite(int val){ readWrite = val; }
	int getReadWrite(){ return readWrite; }
	//对只写属性进行只写访问
	void setWriteOnly(int val){ writeOnly = val; }
private:
	int readOnly; //对外只读访问
	int noAccess; //外部不可访问
	int readWrite; //读写访问
	int writeOnly; //只写访问
};

代码示例:

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

class Person
{
public:
	void setAge(int age)
	{
		if (age < 0 || age > 100)
		{
			cout << "呔,妖精" << endl;
			return;
		}
		m_Age = age;
	}
	//获取年龄,读权限
	int getAge()
	{
		return m_Age;
	}
	//获取姓名,读权限
	string getName()
	{
		return m_Name;
	}
	//写姓名
	void setName(string name)
	{
		m_Name = name;
	}
	//只写的权限
	void setLover(string lover)
	{
		m_lover = lover;
	}
  
private:
	int m_Age = 0;//年龄,只读
	string m_Name;//读写
	string m_lover;//
};

void test01()
{
	Person p1;
	p1.setName("老王");
	p1.setLover("马冬梅");
  
	//为啥先输出的是呔,妖精?不应该是 p1的年龄是老王 吗,
	//因为之前p1.setAger()放前面了
	cout << "p1的姓名:" << p1.getName() << endl;
	p1.setAge(180);
	cout << "p1的年龄:" << p1.getAge() << endl;
}
int main()
{
	test01();
  
	system("pause");
	return 0;
}

image

4.2 对象的构造和析构

​ 对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未知,同样的使用完一个变量,没有及时清理,也会造成一定的安全问题。c++为了给我们提供这种问题的解决方案,构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和对象清理工作。
​ 无论你是否喜欢,对象的初始化和清理工作是编译器强制我们要做的事情,即使你不提供初始化操作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类就应该顺便提供初始化函数。
​ 为什么初始化操作是自动调用而不是手动调用?既然是必须操作,那么自动调用会更好,如果靠程序员自觉,那么就会存在遗漏初始化的情况出现。

4.2.1 构造函数和析构函数(作用域都是public)

​ 构造函数和析构函数,这两个函数将会被编译器自动调用。

​ 构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
构造函数语法:
​ 构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数,可以发生重载。
​ 只能调用一次
​ ClassName(){}

​ 析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。
析构函数语法:
​ 析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。
​ ~ClassName(){}

代码示例:

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
	Person()
	{
		cout << "构造函数调用: " << endl;
		pName = (char *)malloc(sizeof("John"));
		strcpy(pName, "John");
		mTall = 150;
		mMonkey = 100;
	}

	~Person()
	{
		cout << "析构函数调用:" << endl;
		if (pName != NULL)
		{
			free(pName);
			pName = NULL;
		}
	}

public:
	char *pName;
	int mTall;
	int mMonkey;
};

void test()
{
	Person person;
	cout << person.pName << endl;
	cout << person.mMonkey << endl;
}

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

image

4.3.1 构造函数的分类及调用

1.构造函数分类:

  • 按参数类型:分为无参构造函数和有参构造函数
  • 按类型分类:普通构造函数和拷贝构造函数(复制构造函数)

2、构造函数的调用规则:

  • 编译器会给一个类,至少添加三个函数 :

    默认构造(无参,函数体为空)
    析构函数(无参,函数体为空)
    拷贝构造(对类中非静态成员属性简单值拷贝)

  • 如果我们自己提供了有参构造函数,编译器就不会提供默认构造函数,但是依旧会提供默认的拷贝构造函数,进行简单的值拷贝

  • 如果我们自己提供了拷贝构造函数,编译器就不会提供其他任何默认构造函数。

  • 析构函数是一定会提供的

class Person{
public:
	Person(){//1.无参构造函数
		cout << "no param constructor!" << endl;
		mAge = 0;
	}
	//2.有参构造函数
	Person(int age){
		cout << "1 param constructor!" << endl;
		mAge = age;
	}
	//3.拷贝构造函数(复制构造函数) 使用另一个对象初始化本对象
	Person(const Person& person){
		cout << "copy constructor!" << endl;
		mAge = person.mAge;
	}
	//打印年龄
	void PrintPerson(){
		cout << "Age:" << mAge << endl;
	}
private:
	int mAge;
};

//1. 无参构造调用方式
void test01(){
	//调用无参构造函数
	Person person1; //默认构造函数不要加(),不然编译器会误认为是函数声明。
	person1.PrintPerson();

	//无参构造函数错误调用方式
	//Person person2();
	//person2.PrintPerson();
}

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

	//调用拷贝构造函数
	Person person02(person01);//或者Person p1 = Person(p2);
	person02.PrintPerson();

	//第二种 匿名对象(显示调用构造函数)
	Person(200); //匿名对象,没有名字的对象
     //如果编译器发现了对象是匿名的,那么在这行代码结束后就释放这个对象。

    //cout << "aaaa" << endl;//验证匿名对象释放时机
  
	Person person03 = Person(300);
	person03.PrintPerson();

    //不能用拷贝构造函数初始化匿名对象
    //Person(p5);//如果这么写,编译器认为你写成了 Person p5,对象的声明;
    //而如果写成右值,那么可以 
    Person p6 = Person(p5);

	//注意: 使用匿名对象初始化判断调用哪一个构造函数,要看匿名对象的参数类型
	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();
}

int main()
{
	test01();
	test02();

	system("pause");
	return 0;
}

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

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;


class MyClass
{
public:
	/*MyClass()
	{
		cout << "默认构造函数" << endl;
	}*/

	MyClass(int a)
	{
		cout << "有参构造函数" << endl;
	}

	int m_A;
};

class MyClass2
{
	MyClass2(const MyClass &a)
	{
		cout << "拷贝构造函数" << endl;
	}

	int m_A;
};

void test01()
{
	//MyClass c1;//这个调用默认构造的就调用不了了,要想调用默认的构造函数,需要将其先写出来
	MyClass c1(1);//调用有参构造
	c1.m_A = 10;
	MyClass c2(c1);//调用拷贝构造
	cout << c2.m_A << endl;
}


//2.如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
void test02()
{
	//MyClass2 c1;//调用拷贝构造//error:不存在默认的构造函数;那么,此时要创建对象应该咋办
	
}

int main()
{
	test01();

	system("pause");
	return 0;
}

//析构函数是一定会提供的

4.3.2 拷贝构造函数的调用时机

  • 把对象以值传递的方式传给函数参数
  • 函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
  • 用一个对象初始化另一个对象
class Person{
public:
	Person(){
		cout << "no param contructor!" << endl;
		mAge = 10;
	}
	Person(int age){
		cout << "param constructor!" << endl;
		mAge = age;
	}
	Person(const Person& person){
		cout << "copy constructor!" << endl;
		mAge = person.mAge;
	}
	~Person(){
		cout << "destructor!" << endl;
	}

public:
	int mAge;
};

//1. 旧对象初始化新对象
void test01(){

	Person p(10);//调用有参构造函数,创建对象
	Person p1(p);//用已经创建的对象,调用拷贝构造函数
     //初始化新的对象
	Person p2 = Person(p);
	Person p3 = p; // 相当于Person p2 = Person(p);
}
![image](/i/l/?n=23&i=blog/2297893/202302/2297893-20230213232601535-1432427047.png)


//2. 传递的参数是普通对象,函数参数也是普通对象,传递将会调用拷贝构造
//值传递都会开辟一块内存,而引用传递则不会
void doBussiness(Person p){}//Person p = Person(p1);

void test02(){
	Person p1(10);
	doBussiness(p1);
}
![image](/i/l/?n=23&i=blog/2297893/202302/2297893-20230213232607174-1316578052.png)


//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;
}

image
image

*> [Test03结果说明:]

编译器存在一种对返回值的优化技术,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;
   }
int main(){
   Person p; //这里只分配空间,不初始化
   MyBussiness(p);
}
```***

4.3.4 多个对象构造和析构

4.3.4.1 初始化列表

构造函数和其他函数不同,除了有名字,参数列表,函数体之外还有初始化列表。
初始化列表简单使用:

#if 0
	是对中间这段代码的屏蔽,当#if 0修改成#if 1的时候,中间这段代码就能使用了
#endif

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

class Person{
public:
#if 0
	//传统方式初始化
	Person(int a,int b,int c){
		mA = a;
		mB = b;
		mC = c;
	}
#endif
	//初始化列表方式初始化
	Person(int a, int b, int c):mA(a),mB(b),mC(c){}
	void PrintPerson(){
		cout << "mA:" << mA << endl;
		cout << "mB:" << mB << endl;
		cout << "mC:" << mC << endl;
	}
private:
	int mA;
	int mB;
	int mC;
};

代码示例:

点击查看代码 ``` #define _CRT_SECURE_NO_WARNINGS #include using namespace std;

class Person
{
public:
//Person(){}
//有参构造和拷贝构造都是初始化数据用的
/Person(int a,int b,int c)
{
m_A = a;
m_B = b;
m_C = c;
}
/

//默认构造,写死了的
Person() :m_A(10), m_B(20), m_C(30){}

//利用初始化列表来初始化数据
//形式:构造函数后面 + 属性(参数) + 属性(参数) + ...{}
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 test01()
{
Person p1(100, 200, 300);
cout << "p1的m_A: " << p1.m_A << endl;
cout << "p1的m_B: " << p1.m_B << endl;
cout << "p1的m_A: " << p1.m_C << endl;

Person p2;

cout << "p2的m_A: " << p1.m_A << endl;
cout << "p2的m_B: " << p1.m_B << endl;
cout << "p2的m_A: " << p1.m_C << endl;

}

int main()
{
test01();

system("pause");
return 0;

}

</details>

#include<iostream>
using namespace std;

class Person
{
public:
	//Person(){}
	//有参构造和拷贝构造都是初始化数据用的
	/*Person(int a,int b,int c)
	{
		m_A = a;
		m_B = b;
		m_C = c;
	}*/

	//默认构造,写死了的
	Person() :m_A(10), m_B(20), m_C(30){}

	//利用初始化列表来初始化数据
	//形式:构造函数后面 + 属性(参数) + 属性(参数) + ...{}
	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 test01()
{
	Person p1(100, 200, 300);
	cout << "p1的m_A: " << p1.m_A << endl;
	cout << "p1的m_B: " << p1.m_B << endl;
	cout << "p1的m_A: " << p1.m_C << endl;

	Person p2;

	cout << "p2的m_A: " << p1.m_A << endl;
	cout << "p2的m_B: " << p1.m_B << endl;
	cout << "p2的m_A: " << p1.m_C << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

标签:初始化,函数,Person,int,Part1,编译器,第七章,构造函数
From: https://www.cnblogs.com/Epiephany/p/17118341.html

相关文章