类与对象

一、什么是对象?

事件万物都可以看成一个对象,
每个事物都可以当成一个对象来处理

1.1、如何描述对象?

通过对对象的属性(名词,量词,形容词)(变量)
行为(动词)来描述这个对象。(函数/方法)

人对象:
// 属性:名字,身高,年龄,性别
// 行为:吃喝拉撒睡

1.2、面向对象的程序设计

将这个对象的共性的东西提取出来,
抽象东西剥离出去,只考虑有规律性和统一性。

二、什么是类?

将对象的属性和行为剥离出来之后,重新定义
一个新的数据类型,管这个类型叫做类 类型。

类是用于描述这个对象的。

使用类重新定义一个类 类型的变量--->叫实例化一个类 类型的对象

eg: 
现实世界			类			虚拟世界
对象--->数据抽象 --->属性和行为--->实例化对象
class human{};  定义类 类型
human zhoukai;  实例化 对象
    
1. 类(粽子、月饼、汤圆)
   指的是一类事物,抽象的

2. 对象(南北的甜粽肉粽)
   具体的实物

笔试题:面向对象语言的三大特征:

// 封装、继承、多态
封装:	--> 将某个对象抽象成一个类
继承:	--> 
	human : 姓名 年龄 身高 体重 
	老师类:姓名 年龄 身高 体重  授课 工资
	学生类: 姓名 年龄 身高 体重  学习 
	
	老师类(派生类):继承human(基类) 授课 工资
	学生类(派生类): 继承human(基类) 学习 
	
	父类/子类
	基类/子类
	基类/派生类
	
多态: -->事物的多种状态

三、C++中声明类 类型的语法格式

class  类名{
	访问控制属性: (用于修饰类中的成员)
	成员变量; 	-->(属性)
	成员函数{} 	-->(方法)
};
	
访问控制属性: 
public: 公有的成员
	类的内部,类的外部,派生类中都可以直接访问。
protected: 保护的成员
	类的内部,和派生类中都可以访问,类的外部不可以访问。
private: 私有的成员
	类的内部可以访问,类的外部和派生类中都不可以访问。
	
class  类名{
public:
	成员变量1;  //公有的
	成员函数1{} //公有的
protected:
	成员变量2;  // 保护的
	成员函数2{} // 保护的
private:
	成员变量3;  // 私有的
	成员函数3{} // 私有的
public:
	成员变量4;  //公有的
	成员函数4{} //公有的
};
	
参考案例: 
	10class.cpp  --> public 
	11class.cpp  --> protected 
	12class.cpp  --> private 

.3.1、 class 和 struct 的区别

1. 默认访问权限不同
	class 默认访问权限为 private
	struct 默认访问权限为 public
2. 成员的作用域	
	1> 类成员的作用域都在类内,外部无法访问,成员函数可以直接访问和调用成员变量
	2> 类的外部可以通过public访问到类内的成员
	3> 类成员的作用域与访问级别没有关系
	4> struct默认访问权限为public

四、类中的特殊成员函数

4.1、构造函数

1. 成员变量的初始值

在全局数据区,默认值为0
在局部变量,默认值为随机值
在堆区,默认值为0

初始值不确定或者不统一

2. 解决方案,

可以定义个一初始化函数
但是初始化函数需要显示调用,使用起来不方便
C++提供了类的构造函数 ,在对象创建的时候自动调用

3. 构造函数

1. 构造函数的函数名和类名一致
2. 没有返回值
3. 并且对象创建的时候自动调用

4. 有参数的构造函数

1. 构造函数也支持重载
2. 可以定义传递参数的构造函数,用来实现用户自定义的初始化

5. 语法格式:

class 类名{
public: 
	// 构造函数
	类名(形参表){
		函数体;
	}	
};
构造函数的总结: 
0> 构造函数用来申请资源,和初始化类对象的。
1> 构造函数的名字和类名一致
2> 构造函数没有返回类型
3> 构造函数的调用时机 
	栈区分配对象:实例化对象时自动调用构造函数
		类名 对象名(构造函数的实参表);
		类名 对象名 = 类名(构造函数的实参表);
	堆区分配对象:new时自动调用构造函数
		类名 *指针对象名 = new 类名(构造函数的实参表);
4> 构造函数的作用完成对类中成员的初始化	
5> 构造函数只在实例化对象时被调用一次,且只能被调用一次。

4.2、构造函数的深化

1. 构造函数可以重载
	作用域相同;
	函数名相同;
	函数的参数不同。
2. 构造函数可以提供缺省的参数 	Student(int a,int b,int c=12345,int d=99){函数体;};
	1> 优先使用传递的实参值
	2> 缺省的参数遵循靠右原则
	3> 构造函数声明和定义分开写,缺省值写到声明的位置。
3. 构造函数可以有哑元参数	Student(int a,int b,int c,int d,char){函数体;};
//参考案例:02constr.cpp
#include <iostream>
using namespace std;
class Student{
public:
	Student(const string &name,int id, int age, int score)
	{
		cout << "Student(string&,int,int,int)" << endl;
		m_name = name;
		m_id = new int(id);
		m_age = new int(age);
		m_score = new int(score);
	}
	// 缺省的参数
	Student(const string &name, int id, int age = 50)
	{
		cout << "Student(string&,int,int)" << endl;
		m_name = name;
		m_id = new int(id);
		m_age = new int(age);
		m_score = new int(90);	
	}
	// 哑元参数 构造函数的重载
	Student(const string &name,int id, int age, int score, char)
	{
		cout << "Student(string&,int,int,int,char)" << endl;
		m_name = name;
		m_id = new int(id);
		m_age = new int(age);
		m_score = new int(score);
	}
	~Student(void) 
	{
		cout << "~Student(void)" << endl;
		delete m_id;
		m_id = NULL;
		delete m_age;
		m_age = NULL;
		delete m_score;
		m_score = NULL;
	}
	void show(void)
	{
		cout << "名字:" << m_name << endl;
		cout << "学号:" << *m_id << endl;
		cout << "年龄:" << *m_age << endl;
		cout << "成绩:" << *m_score << endl;
	}
private:
	string m_name;
	int *m_id;
	int *m_age;
	int *m_score;
};

int main(int argc, const char *argv[])
{
	do {
		// 在栈区实力化一个对象 
		Student stu1("zhangsan",1111,28,60);
		stu1.show();

		Student stu2 = Student("lisi", 2222, 30, 70,'i');
		stu2.show();
	}while(0);
	// while(1);
	 
	// 在堆区实例化一个对象
	Student *stu3 = new Student("wangermazi",8888, 31, 60);
	stu3->show();
//	delete stu3;
//	stu3 = NULL;

	Student stu4("千年王八", 1000);
	stu4.show();

	Student stu5("万年乌龟", 10000, 8000);
	stu5.show();

	return 0;
}

//结果:
Student(string&,int,int,int)
名字:zhangsan
学号:1111
年龄:28
成绩:60
Student(string&,int,int,int,char)
名字:lisi
学号:2222
年龄:30
成绩:70
~Student(void)
~Student(void)
Student(string&,int,int,int)
名字:wangermazi
学号:8888
年龄:31
成绩:60
Student(string&,int,int)
名字:千年王八
学号:1000
年龄:50
成绩:90
Student(string&,int,int)
名字:万年乌龟
学号:10000
年龄:8000
成绩:90
~Student(void)
~Student(void)


4. 缺省的构造函数 
	1> 如果一个类中没有显示的定义任何的构造函数,编译器默认会提供一个缺省的构造函数:
		类名(void){空}
	缺省的构造函数,什么也不干。
	
	2> 如果类中显示的定义了构造函数,编译器不在提供任何缺省的构造函数。
//参考案例: 03constr.cpp
#include <iostream>
using namespace std;

class A{
public:
	// 使用缺省的构造函数 	
	void show(void)
	{
		cout << m_data << endl;
		cout << m_p << endl;
	}
private:
		int m_data;
		int *m_p;
};

class B{
public:
	// 显示的定义一个构造函数
	// 不在提供缺省的构造函数
	B(int data, int a) 
	{
		cout << "B(int, int)" << endl;
		m_data = data;
		m_p = new int(a);
	}
	B(void)
	{
		m_data = 100;
		m_p = new int(200);
	}
	void show(void)
	{
		cout << m_data << endl;
		cout << *m_p << endl;
	}
private:
		int m_data;
		int *m_p;
};

int main(int argc, const char *argv[])
{
	A a;
	a.show();
	
	B b(10086, 10010); 
	b.show();

	B c;  // 应该调用无参构造函数
		// 需要类类中显示定义一个无参构造函数
	c.show();
	return 0;
}
//结果:
-1007131648
0
B(int, int)
10086
10010
100
200
5. 类中包含类 类型的成员变量,默认会调用自己类中的构造函数,完成对自己类中成员的初始化。
eg:
class A{

};

class B{
private:
	A a;  // 类 类型的成员变量(成员子对象)
};

B b;
	B类中的对象完成对B类中普通成员的初始化,
	B类中的成员A类对象会调用A类中的构造函数,
	完成对A类中成员的初始化。
	构造函数的调用顺序: 从里往外依次调用。
参考案例: 04constr.cpp 
#include <iostream>
using namespace std;
class A{
	public:
	A(void){
		cout << "A(void)" << endl;
	}
};
class B{
	public:
	B(int b) {
		m_b = b;
		cout << "B(int)" << endl;
	}
	private:
	A a;  // 成员 子对象
	int m_b;
};
int main(int argc, const char *argv[])
{
	B b(10086);  // 查看构造函数如何调用
	return 0;
}
//结果:
A(void)
B(int)

4.3、析构函数

语法格式: 
class 类名{
public: 
	// 析构函数
	~类名(void){
		函数体;
	}	
};
析构函数总结:
0> 析构函数用来释放资源,
1> 析构函数的函数名是类名前加~
2> 析构函数没有返回类型,形参是void类型
3> 析构函数的调用时机 
	栈区对象:作用域终止时自动调用析构函数,释放空间
	堆区对象: delete时自动调用析构函数,释放空间
    1. delete 关键字,C++中的一部分,会自动调用对象的析构函数
    2. free() 标准C库内定义的函数,不会调用析构函数,而且必须手动将指针置空
4> 析构函数只在销毁对象时被调用一次,且只能被调用一次。

4.4、析构函数的深化

1>作用:释放资源
2>语法格式: 
	~类名(void) {函数体;}		
3>析构函数调用时机: 
	栈区: 作用域结束
	堆区: delete对象
4>析构函数只会在销毁对象时被调用一次。
5>析构函数不可以重载。
6>如果没有显示的定义析构函数,编译器默认会提供一个缺省的析构函数,如果显示定义了析构函数,编译器不在提供缺省的析构函数
7>类中包含类类型的成员对象,成员对象会调用自己类中的析构函数,完成对自己类中成员的资源的释放。
8>析构函数的调用顺序:从外向里依次调用。
//参考案例: 05distconst.cpp 
#include <iostream>
using namespace std;
class A{
	public:
	A(void){
		cout << "A(void)" << endl;
	}
	~A(void){
		cout << "~A(void)" << endl;
	}
};
class B{
	public:
	B(int b) {
		m_b = b;
		cout << "B(int)" << endl;
	}
	~B(void)
	{
		cout << "~B(void)" << endl;
	}
	private:
	A a;  // 成员 子对象
	int m_b;
};
int main(int argc, const char *argv[])
{
	B b(10086);  // 查看构造函数如何调用

	return 0;
}
//结果:
A(void)
B(int)
~B(void)
~A(void)

五、对象的创建和销毁(作业)

5.1、在栈区分配一个对象

类名 对象名(实参表);  // 调用对应的构造函数
类名 对象名 = 类名(实参表);  // 调用对应的构造函数
			临时对象

5.2、在栈区分配连续的多个对象

类名 对象名[index] = {类名(实参表), 类名(实参表),....};

5.3、在堆区分配一个对象

类名 *对象指针 = new 类名(实参表);
delete 对象指针;

5.4、在堆区分配连续的多个对象

类名 *对象指针 = new 类名[index]{类名(实参表), 类名(实参表),....};
delete[] 对象指针;

六、多文件的编程

1> 声明在.h文件中
2> 定义在.cpp文件中
3> 调用在main函数中		
//参考案例
    06modules/  teacher.h main.cpp	
    07modules/  teacher.h teacher.cpp main.cpp
4> 变量定义   
	int a;        //不要写在头文件里
	extern int a; //可以写在头文件里的

	extern 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,
 	提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。
        
5> 定义和声明的最大的区别在于,是否开辟内存空间

6.1、Makefie编写

# = := ?= +=
CROSS_COMPILE := g++
EXEC := output 	# 输出目标
CTAGS := -c 
INC = ./inc 	# 头文件目录

#OBJS += ./src/student.o
#OBJS += ./src/teacher.o
#OBJS += ./main.o

# wildcard  patsubst
# wildcard 函数将目录下的文件展开
OBJSs := $(wildcard ./src/*.cpp)
OBJSs += $(wildcard ./*.cpp)
# patsubst 将OBJSs变量中所有以.cpp结尾的字符都替换成.o
OBJS = $(patsubst %.cpp,%.o,$(OBJSs))

all:$(OBJS)
	@echo $@ 	# $@ 目标
	@echo $< 	# $< 第一个依赖
	@echo $^ 	# $^ 全部依赖

	@$(CROSS_COMPILE) $^ -o $(EXEC)
%.o:%.cpp
	@$(CROSS_COMPILE) $(CTAGS) $< -o $@ -I$(INC)

ifdef DEBUG  	# 注释开始
./src/student.o:./src/student.cpp
	$(CROSS_COMPILE) $(CTAGS) $< -o $@ -I$(INC)
	@#g++ -c ./src/student.cpp -o ./src/student.o -I./inc

./src/teacher.o:./src/teacher.cpp
	$(CROSS_COMPILE) $(CTAGS) $< -o $@ -I$(INC)

./main.o:./main.cpp
	$(CROSS_COMPILE) $(CTAGS) $< -o $@ -I$(INC)
endif 			# 注释结束

clean:
	rm ./*.o ./src/*.o $(EXEC)

七、构造函数的初始化列表(重要)

7.1、 语法格式:

class 类名{
public: 
	// 实例化对象时,先个成员变量分配空间
	// 在构造函数内完成对每个成员的初始化
	类名(形参表){
		成员变量名 = 形参名;
	}
	// 分配空间的同时进行初始化
	// 类名(形参表):初始化表{}
	类名(形参表):成员变量名(形参名),成员变量名(常量),....{}
	};
//参考案例:09initlist.cpp
#include <iostream>
using namespace std;

class Integer{
	public:
	Integer(int a, int b, int p, int &r);
	Integer(int data);
	~Integer(void);
	void show(void);
	private:
	int m_a;
	int m_b;
	int *m_p;
	int &m_r;
};
Integer::Integer(int a, int b, int p, int &r):m_a(a),m_b(b),m_p(new int(p)),m_r(r) 	//初始化表
{
}
Integer::Integer(int data):m_a(data),m_b(1111),m_p(new int(data)),m_r(data) 		//初始化表
{
}
Integer::~Integer(void)
{
	delete m_p;
}
void Integer::show(void)
{
	cout << m_a <<":"<< m_b << ":" 
		<< *m_p << ":" << m_r << endl;
}

int main(int argc, const char *argv[])
{
	int r = 4444;
	Integer i(1111,2222,3333,r);
	i.show();

	Integer i2(6666);
	i2.show();

    return 0;
}
//结果:
1111:2222:3333:4444
6666:1111:6666:6666

7.2、 实际开发中必须使用初始化表的场景

1> 类中包含const修饰的成员时,必须使用初始化表
	const int a;
	int *const p;
	const int *const p;
常成员变量,必须在初始化列表里初始化,之后无法改变其值
//参考案例:10initlist.cpp
#include <iostream>
using namespace std;
class Integer{
public:
	Integer(int data):m_a(data),m_p1(new int(data)),m_p2(new int(data))		//初始化表
    {
	//	m_a = data;		
		m_p3 = new int(data);
	}
	~Integer(void){
		delete m_p1;
		delete m_p2;
	}
	void show(void)
	{
		cout << m_a << ":" << *m_p1 << ":" << *m_p2 << endl; 
	}
	private:
	const int m_a;
	int *const m_p1;
	const int *const m_p2;
	const int *m_p3;
};

int main(int argc, const char *argv[])
{
	Integer i(6666);
	i.show();
	return 0;
}
//结果:
6666:6666:6666
2> 类中的成员包含引用类型的成员,必须使用初始化表,定义引用类型变量时,必须指定引用的目标 
3> 类中的成员变量名和构造函数的形参名一致时,需要使用初始化表,但是不是必须使用(可以使用 this 指针)。
	eg:
	class A{
	public:
		A(int data):data(data){
			// 局部优先原则
			// data = data;
		}
	private:
		int data;
		};
4> 类中包含类 类型的成员对象时,需要对类 类型中的成员对象中的成员完成初始化时,必须使用初始化表的方式,显示的调用类类型成员对象中的构造函数,完成对其成员的初始化。

7.3、初始化列表注意事项

1. 成员变量的顺序必须和声明的顺序一致
2. 成员初始化顺序与初始化列表中的位置相关
3. 初始化列表先于构造函数执行

八、拷贝构造函数

8.1、拷贝构造函数重载

拷贝构造函数是一个特殊的构造函数,他可以和其他的构造函数构成重载的关系

8.2、语法格式:

参数类型为 const 类名& 的构造函数,叫拷贝构造函数
    
class 类名{  
public:
	类名(const 类名& other):初始化表{
		函数体;
	}
};

8.3、拷贝构造函数的调用时机

//用已经初始化的对象,对未初始化的对象,进行赋值(初始化)操作,此时会调用拷贝构造函数。

eg: 
class Integer{
public:
	// 构造函数
	Integer(int data):m_data(data){}
	// 拷贝构造函数 
	Integer(const Integer& other):m_data(other.m_data){}		
	Integer(const Integer& other){
		m_data = other.m_data
	}
private:
	int m_data;
};

// a.Integer(100);
Integer a(100);  // 构造函数 
// a.Integer(200);
Integer b = Integer(200); // 构造函数
	
// c.Integer(a);
Integer c(a);   // 调用拷贝构造函数
// d.Integer(b);
Integer d = Integer(b) // 调用拷贝构造函数

8.5、缺省的拷贝构造函数

1> 如果类中不显示的定义拷贝构造函数,编译器默认会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数是对类中的成员一对一的拷贝。
    
2> 如果在类中显示的定义拷贝构造函数,编译器不在提供缺省的拷贝构造函数。
//缺省的格式:
类名(const 类名& other){
	成员变量名1 = other.成员变量名1;
	成员变量名2 = other.成员变量名2;
	......
}

(作业)

//如果类中包含 类 类型的成员,默认会调用自己类中的拷贝构造函数完成对自己类中所有的成员的拷贝。
    
//参考案例:01copycons.cpp
#include <iostream>
using namespace std;
class Integer{
public:
	Integer(int data):m_data1(data),m_data2(data)
    {
		cout << "Integer(int)" << endl;	
	}
#if 0
	// 拷贝构造函数
	Integer(const Integer& other):m_data1(other.m_data1),m_data2(other.m_data2)
    {
		cout << "Integer(Integer)" << endl;	
	}
#endif
	~Integer(void){}
	void show()
	{
		cout << m_data1 << endl;
		cout << m_data2 << endl;
	}
private:
	int m_data1;
	int m_data2;
};


int main(int argc, const char *argv[])
{
	// 调用构造函数
	Integer i1(10086);
	Integer i2 = Integer(10010);
	// 调用构造函数, 只限于构造函数右一个参数
	Integer i3 = 10001;

	// 调用拷贝构造函数 
	Integer i4(i1);
	i4.show();
	Integer i5 = Integer(i2);
	i5.show();
	Integer i6 = i3;
	i6.show();
	
	return 0;
}
//结果:
Integer(int)
Integer(int)
Integer(int)
10086
10086
10010
10010
10001
10001

8.5、 深浅拷贝问题

在类中添加一个指针类型的成员,使用缺省的拷贝构造函数,编译查看结果。

//参考案例:02copycons.cpp
#include <iostream>
using namespace std;
class Integer{
	public:
		Integer(int data):
			m_data1(data),m_data2(data),
			m_p(new int(data))
		{
			cout << "Integer(int)" << endl;	
		}
#if 0
		// 拷贝构造函数
		Integer(const Integer& other):
			m_data1(other.m_data1),
			m_data2(other.m_data2){
			m_p = other.m_p;
			cout << "Integer(Integer)" << endl;	
		}
#endif
		~Integer(void){
			cout << "~Integer(void)" << endl;
			delete m_p;
			m_p = NULL;
		}
		void show()
		{
			cout << m_data1 << endl;
			cout << m_data2 << endl;
			cout << *m_p << endl;
		}
	private:
		int m_data1;
		int m_data2;
		int *m_p;
};


int main(int argc, const char *argv[])
{
#if 0
	// 调用构造函数
	Integer i1(10086);
	// 调用拷贝构造函数 
	Integer i4(i1);
	i4.show();
#endif 
#if 1
	Integer i2 = Integer(10010);
	Integer i5 = Integer(i2);
	i5.show();
#endif 
#if 0
	Integer i3 = Integer(10001);
	Integer i6 = i3;
	i6.show();
#endif
	
	return 0;
}
//结果:
free(): double free detected in tcache 2
/usercode/script.sh: line 62:    11 Aborted    $output - < "/usercode/inputFile"
会报 double free 的错误,因为缺省的拷贝构造函数都是浅拷贝
//参考图:拷贝构造函数的深浅拷贝问题.png
class Integer{
public:
	// 缺省的拷贝构造函数原型
	/*
	Integer(const Integer& other):
		m_data1(other.m_data1),
		m_data2(other.m_data2),
		m_p(other.m_p){
	}
	*/
	~Integer(void){
		delete m_p;
		m_p = NULL;
	}
private:
	int m_data1;
	int m_data2;
	int *m_p;
};
总结: 
1> 如果类中有指针变量成员
	使用浅拷贝时,拷贝的是存放数据的地址,没有重新开辟一段内存空间,
	使用深拷贝时,是重新开辟一段空间,将数据复制一份放入这个空间。
2> 调用拷贝构造函数
	如果类中包含指针类型的成员时,在调用拷贝构造函数的过程中,必须显示的定义拷贝构造函数实现深拷贝,
	如果调用缺省的拷贝构造函数,最终会报 double free 的错误,缺省的拷贝构造函数都是浅拷贝。
3> 如果类中没有指针类型的成员,可以直接使用缺省的拷贝构造函数。
练习:
3. 实现mystring类 
class myString{
public:
	// 构造函数 
	// 析构函数 
	// 拷贝构造函数
	// size() / length()
private:
	char *m_str;
	unsigned int strLen;
};  	
	
string str = "hello";
string str2("hello");
string str2 = string("hello");
//参考案例: 04myString.cpp
#include <iostream>
#include <cstring>
using namespace std;
class myString{
public:
	myString(const char *s){
		strLen = strlen(s);
		str = new char[strLen];
		strcpy(str, s);
	}
	// 拷贝构造函数 
	myString(const myString& other):
		strLen(other.strLen),
		str(strcpy(new char[other.strLen], other.str))
	{
		/*
		strLen = other.strLen;
		str = new char[strLen];
		strcpy(str,other.str);
			
str = strcpy(new char[other.strLen], other.str);
			*/
	}
	~myString(void){
		delete[] str;
		str = NULL;
	}
	unsigned int size(void)
	{
		return strLen;
	}
	unsigned int length(void)
	{
		return strLen;
	}
	char *c_str(void)
	{
		return str;
	}

private:
	char *str;
	unsigned int strLen;
};
int main(int argc, const char *argv[])
{
	myString str1 = "hello world";	

	cout << str1.c_str() << endl;
	cout << str1.size() << endl;
	cout << str1.length() << endl;

	myString str2(str1); // 拷贝构造函数
	cout << str2.c_str() << endl;
	return 0;
}
//结果:
hello world
11
11
hello world

九、拷贝赋值函数/等号运算符的重载

9.1、语法格式

class 类名{
	类名 &operator=(const 类名& other){
		// 防止自己给自己赋值
		if (&other != this){
			// 实现拷贝赋值
		}
		// 返回自身
		return *this;
	}
};
// i1.operator=(i1);
i1 = i1;
operator属于实现运算符重载的关键字。

9.2、 拷贝赋值函数的调用时机

两个已经初始化的对象之间进行赋值操作时,会调用拷贝赋值函数。
eg:
	Integer i1(100,200);
	Integer i2(200,300);
	
	// 本质:i1.operator=(i2);
	i1 = i2; // 调用拷贝赋值函数
//参考案例: 05operator=.cpp
#include <iostream>
using namespace std;
class Integer{
	private:
	int m_a;
	int m_b;
	public:
	Integer(int a, int b):m_a(a),m_b(b){}
	~Integer(void){}

	// 拷贝赋值函数 
	Integer& operator=(const Integer& other)
	{
		if(&other != this){
			cout << "拷贝赋值函数" << endl;
			m_a = other.m_a;
			m_b = other.m_b;
		}
		return *this;
	}
	void show()
	{
		cout << m_a << ":" << m_b << endl;
	}

};
int main(int argc, const char *argv[])
{
	
	Integer i1(100,200);
	Integer i2(888,999);
	i1 = i2; // 拷贝赋值函数

	i1.show();
	return 0;
}
//结果:
拷贝赋值函数
888:999
例子: 
复数 : 实部 + 虚部i
	
class Complex{
public:
	Complex(const Complex& other)
	{		
	}
	Complex& operator=(const Complex& other)
	{	
	}
	void show()
	{
		cout << m_r << " + " << m_i << "i" << endl;
	}
private:
	int m_r;
	int m_i;
};
	
Complex c1(1,2);
Complex c2(3,4);
	
c1 = c2;
Complex c3(c1);

9.3、 默认缺省的拷贝赋值函数

如果没有显示的定义一个拷贝赋值函数,编译器默认会提供一个缺省的拷贝赋值函数,缺省的拷贝赋值函数是一个浅拷贝。缺省的拷贝赋值函数是对类中的成员一对一的拷贝。

注意: 编译器只提供了等号运算符的重载,其他的运算符不提供缺省的运算符重载函数。

9.4、 类中包含类 类型的成员

如果类中包含类 类型的成员时,会调用自己类中的拷贝赋值函数,完成对自己类中成员的拷贝赋值操作。(作业)
class A{
private:
	int m_a;
};

class B{		
private:
	A aa;
	int m_b;
};
B b1;
B b2;
b1 = b2;
//参考案例: 06complex.cpp
#include <iostream>
using namespace std;
class Complex{
public:
	Complex(int r, int i):m_r(new int(r)),m_i(i){}
	Complex(const Complex& other):m_r(new int(*other.m_r)),		m_i(other.m_i){}

	~Complex(void)
	{
		delete m_r;
		//delete m_i;
	}
	// 实现拷贝赋值函数 深拷贝 
	Complex& operator=(const Complex& other){
		if(&other != this)
		{
			delete m_r;
			m_r = new int(*other.m_r);
			m_i = other.m_i;
			cout << "m_r = " << m_r << endl;
			cout << "other.m_r = " << other.m_r << endl;
		}
		return *this;
	}
	void show()
	{
		cout << *m_r << "+" << m_i << "i" << endl;
	}
private:
	int *m_r;
	int m_i;
};

int main(int argc, const char *argv[])
{
	Complex c1(1,2);
	Complex c2(3,4);
	c1 = c2;  // 拷贝赋值函数 c1.operator=(c2);
	c1.show();
	return 0;
}
//结果:
m_r = 0x1f06e70
other.m_r = 0x1f06e90
3+4i

9.5、 拷贝赋值函数的深浅拷贝问题

如果类中包含指针类型的成员时,在调用拷贝赋值函数的过程中,必须显示的定义拷贝赋值函数实现深拷贝,
如果调用缺省的拷贝赋值函数,最终会报 double free 的错误,缺省的拷贝赋值函数都是浅拷贝。
如果类中没有指针类型的成员,可以直接使用缺省的拷贝赋值函数。

拷贝赋值函数的深浅拷贝问题参考图:拷贝赋值函数的深浅拷贝问题.png

十、this指针(重中之重)

10.1、 定义(方便理解)

类中的特殊成员函数和普通的成员函数,在形参中都隐藏这一个自身 类 类型 的指针形参,形参的名字就是 this 
this 指针定义:是类中指向类对象自身首地址的一个指针变量
成员函数通过 this 指针,可以通过 this->成员变量 的方式访问到自己的成员变量
eg:
class Integer{
public: 
	Integer(int data):m_data(data){}
	// 可以看成 Integer(int data, Integer *this):m_data(data){}
		
	void show(void){
		cout << this->m_data << endl;
	}
	// 可以看成 void show(Integer *this){}
private:
	int m_data;
};
	
Integer i1(1000);

对象的地址 == this指针
总结:
	1. 初始化表中不可以使用this指针
	2. this指针不要显示的写到函数的形参表中,
		this是C++中的一个关键字。
	
//参考案例:07this.cpp 
#include <iostream>
using namespace std;
class Integer{
	public:
	Integer(int data)/*:this->m_data(data)*/{
		/* this->可以省略 */
#if 0
		this->m_data = data;
		this->show();
#else 
		m_data = data;
		show();
#endif
	}
	void show()
	{
		cout << m_data << endl;
		cout << "this = " << this << endl;
	}
	private:
	int m_data;
};

int main(int argc, const char *argv[])
{
	Integer i1(100);
	cout << "&i1 = " << &i1 << endl;
	return 0;
}
//结果:
100
this = 0x7fff4ea2f1ac
&i1 = 0x7fff4ea2f1ac

10.2、 必须显示使用this指针的场景

1> 区分作用域
class A{
public:
	A(int a)/*:a(a)*/{
	this->a = a;
}
private:
	int a;
};
2> 返回自身引用时,必须使用this指针可以看:拷贝赋值函数
3> 在类的内部销毁一个堆区对象时必须使用this指针
作业1: 
class Human{
public: 
	// 构造函数 
	// 析构函数 
	// 拷贝构造函数 
	// 拷贝赋值函数
private: 
	char *m_name;
	int *m_age;
};
	
class Student{
public:
	// 构造函数 
	// 析构函数 
	// 拷贝构造函数 
	// 拷贝赋值函数
private:
	Human m_p;
	int *m_score;
};

总结:

1. 引用 
2. 函数重载 函数缺省值 哑元参数 
3. 动态内存分配 new/delete
4. 类的声明 
5. 类中的构造函数和析构函数 
6. 拷贝构造函数 
	1> 类名(const 类名& other):初始化表{
	
	}
	2> 深浅拷贝
        
7. 拷贝赋值函数/等号运算符的重载
	1> 类名& operator=(const 类名& other)
	{
		if(&other == this){
		}
		return *this;
	}
	2> 深浅拷贝
        
8. this指针
	1>执行对象自身的指针,叫做this指针
	2>需要使用this指针的场景 
		1> 区分作用域,可以通过this指针限定访问自己的成员变量
		2> 返回自身
		3> 类的内部销毁对象

作业:

声明一下类 
class  Student{
public:
	// 实现构造函数:
	构造函数可以重载,
	可以提供缺省值
	可以有哑元参数
	Student (int date)
	{
		m_data = date;
	}
	// 实现析构函数:
	析构函数有且只有一个
	// 其他的函数自行实现
	private: 
	string m_name;
	int *m_id;
	int *m_age;
	int *m_score;
	int &m_data;  //  暂时不可以是引用类型的变量
};
使用以上类实例化两个对象,一个在栈区,一个在堆区。
1. 对象的创建和销毁(作业)
2. 在栈区分配连续的多个对象 
	类名 对象名[index] = {类名(实参表), 类名(实参表),....};	
3. 在堆区分配连续的多个对象
	类名 *对象指针 = new 类名[index]{类名(实参表), 类名(实参表),....};
	delete[] 对象指针;
4. 08modules工程的Makefile 
5. 实现mystring类 
class myString{
public:
	// 构造函数 
	// 析构函数 
	// 拷贝构造函数  (double free错误)
	// size() / length()
	private:
	char *m_str;
	unsigned int strLen;
};  
	
类名 对象名(形参表);
类名 对象名 = 类名(形参表);
	
myString name = "zhoukai";  // 构造函数
	
eg:
class  Integer{
public: 
	Integer(int a):m_a(a){}
private:
	int m_a;
};
// 构造函数只有一个形参可以这样写
// i1.Integer(100);
7. 深化myString类,实现拷贝赋值函数
Integer i1 = 100;  // 构造函数 
	  
8. 为07modules代码,编译一个通用的Makefile编译一个通用版本的Makefile, 即使我在工程中添加很多个类,使用Makefile编译工程时,不需要对Makefile修改。
posted on 2020-12-07 21:07  八杯水  阅读(169)  评论(0)    收藏  举报