C++核心编程学习笔记

1.内存分区模型

C++在执行程序时,将内存大方向划分为4个区域:

  • 代码区:存放函数体的二进制代码,由操作系统进行管理
  • 全局区:存放全局变量、静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

不同区域存放的数据,赋予不同的周期,使编程更加灵活

1.1 程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:

代码区:

  • 存放CPU执行的机器指令
  • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
  • 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区:

  • 全局变量和静态变量存放在此
  • 全局区还包含了常量区,字符串常量和其他常量也存放在此
  • 该区域的数据在程序结束后由操作系统释放
# include<iostream>
using namespace std;

// 全局变量
int g_a = 10;
int g_b = 10;

// const修饰的全局变量(全局常量)
const int c_g_a = 10;
const int c_g_b = 10;

int main()
{
	// 普通局部变量
	int a = 10;
	int b = 10;
	cout << "局部变量a的地址:" << (int)&a << endl;
	cout << "局部变量b的地址:" << (int)&b << endl;

	cout << "全局变量g_a的地址:" << (int)&g_a << endl;
	cout << "全局变量g_b的地址:" << (int)&g_b << endl;

	// 静态变量:在普通变量前加关键字static即为静态变量
	static int s_a = 10;
	static int s_b = 10;
	cout << "静态变量s_a的地址:" << (int)&s_a << endl;
	cout << "静态变量s_b的地址:" << (int)&s_b << endl;

	// 常量:常量包括字符串常量和const修饰的变量
	// const修饰的变量包括局部和全局
	cout << "字符串常量的地址:" << (int)&"hello world" << endl;
	
	// const修饰的局部变量(局部常量)
	const int c_l_a = 10;
	const int c_l_b = 10;
	cout << "局部常量c_l_a的地址:" << (int)&c_l_a << endl;
	cout << "局部常量c_l_b的地址:" << (int)&c_l_b << endl;

	cout << "全局常量c_g_a的地址" << (int)&c_g_a << endl;
	cout << "全局常量c_g_b的地址" << (int)&c_g_b << endl;

	system("pause");
	return 0;
}

局部变量、const修饰的局部变量(局部常量)不在全局区中

全局变量、静态变量、常量、字符串常量、const修饰的全局变量(全局常量)在全局区中

1.2 程序运行后

栈区:由编译器自动分配释放,存放函数的参数值(形参)、局部变量等

注:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

# include<iostream>
using namespace std;

int* func()
{
	int a = 10; // 局部变量 存放在栈区,栈区的数据在函数执行完毕后自动释放
	return &a; // 返回局部变量的地址
}

int main()
{
	// 接收func函数的返回值
	int* p = func();
	cout << *p << endl; // 第一次可以打印正确的数字,是因为编译器做了保留
	cout << "Hello World" << endl;
	cout << *p << endl; // 第二次这个数据就不再保留了

	system("pause");
	return  0;
}

堆区:

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

在C++中主要利用new在堆区中开辟内存

# include<iostream>
using namespace std;

int* func()
{
	// 利用new关键字将局部变量开辟到堆区
	int* p = new int(10); // 指针本身也是局部变量,放在栈区,但指针保存的数据放在堆区
	return p;
}

int main()
{
	int* p = func();
	cout << *p << endl;
	cout << "Hello World" << endl;
	cout << *p << endl;
	cout << *p << endl;
	cout << *p << endl;

	system("pause");
	return 0;
}

1.3 new操作符

C++利用new操作符在堆区开辟区域

堆区开辟的数据,由程序员手动开辟,手动释放,释放用操作符delete

语法:new 数据类型

利用new创建的数据,会返回该数据对应的类型的指针

# include<iostream>
using namespace std;

// 1.new的基本语法
int* func()
{
	int* p = new int(10);
	return p;
}

void test01()
{
	int* p = func();
	cout << *p << endl;
	cout << "Hello World" << endl;
	cout << *p << endl;
	cout << *p << endl;

	// 释放堆区的数据 用关键字delete
	delete p;
	// cout << *p << endl;  内存已经被释放,再访问就是非法操作,会报错
}

// 2.在堆区利用new开辟数组
void test02()
{
	int* arr = new int[10]; // 10代表的是数组中的元素个数

	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}

	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}

	// 释放堆区数组 需要加[]
	delete[]arr;
}

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

2.引用

2.1 引用的基本语法

作用:给变量起别名

语法 &别名 = 原名;

即通过“别名”和“原名”访问同一块内存

# include<iostream>
using namespace std;

int main()
{

	int a = 10;
	int& b = a;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	b = 100;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	system("pause");
	return 0;
}

2.2 引用的注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变

2.3 引用做函数参数

作用:函数传参时,可以利用引用让形参修饰实参

优点:可以简化指针修改实参

# include<iostream>
using namespace std;

// 交换函数

// 1.值传递
void myswap01(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
}

// 2.地址传递
void myswap02(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

// 3.引用传递
void myswap03(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

int main()
{
	int x = 10;
	int y = 20;

	myswap01(x, y); // 值传递,形参不会修饰实参 此时,x = 10,y = 20
	cout << "x = " << x << endl;
	cout << "y = " << y << endl;

	myswap02(&x, &y); // 地址传递,形参会修饰实参 此时,x = 20,y = 10
	cout << "x = " << x << endl;
	cout << "y = " << y << endl;

	myswap03(x, y); // 引用传递,形参会修饰实参 此时,x = 10,y = 20
	cout << "x = " << x << endl;
	cout << "y = " << y << endl;
	/*
	引用传递,形参可以修饰实参的原因:
	引用是变量的别名,别名和原名修饰的是同一块内存
	即 myswap03 中的 a和b 与 x、y 修饰的是同一块内存
	故通过引用传递,形参可以修饰实参
	*/

	system("pause");
	return 0;
}

2.4 引用做函数返回值

用法:函数调用作为左值

注:不要返回局部变量引用

# include<iostream>
using namespace std;

// 1.不要返回局部变量引用
int& test01()
{
	int a = 10; // 局部变量,存放在栈区,在函数执行完毕后由编译器自动释放
	return a;
}

// 2.函数调用作为左值
int& test02()
{
	static int a = 10; // 静态局部变量,存放在全局区,程序结束后由操作系统释放
	return a;
}

int main()
{
	// 不要返回局部变量引用
	int ref1 = test01();
	cout << "ref1 = " << ref1 << endl;
	cout << "Hello World" << endl;
	cout << "ref1 = " << ref1 << endl;

	// 如果函数返回值是引用,那么这个函数调用可以作为左值
	int ref2 = test02();
	cout << "ref2 = " << ref2 << endl;
	cout << "Hello World" << endl;
	cout << "ref2 = " << ref2 << endl;

	ref2 = 1000;
	cout << "ref2 = " << ref2 << endl;
	cout << "Hello World" << endl;
	cout << "ref2 = " << ref2 << endl;

	system("pause");
	return 0;
}

2.5 引用的本质

引用的本质在C++内部实现是一个指针常量

(指针常量:指针的指向不可以修改,但指针指向的值可以修改)

# include<iostream>
using namespace std;

// 发现是引用,转换为 int* const ref = &a
void func(int& ref)
{
	ref = 100; // ref是引用,转换为*ref = 100
}

int main()
{
	int a = 10;
	// 自动转换为int* const ref = &a;指针常量是指针指向不可以修改,也说明引用为什么不可更改
	int& ref = a;
	ref = 20; // 内部发现ref是引用,自动转换为*ref = 20

	cout << "a = " << a << endl;
	cout << "ref = " << ref << endl;

	func(a);

	system("pause");
	return 0;
}

2.6 常量引用

作用:常量引用常用来修饰形参,防止误操作

在函数形参列表中,可以加const修饰形参,防止形参改变实参

# include<iostream>
using namespace std;

void showValue(const int& val)
{
	// val = 1000; 会报错,const修饰的形参,不可修改
	cout << "val = " << val << endl;
}

int main()
{
	// int& ref = 10; 不可以,引用必须引一块合法的内存空间,10是一个常量
	// 加上const之后,编译器将代码修改为 int temp = 10; const int& ref = temp;
	// 加入const之后变为只读,不可修改
	const int& ref = 10;

	int a = 10;
	showValue(a);
	cout << "a = " << a << endl;

	system("pause");
	return 0;
}

3.函数提高

3.1 函数默认参数

在C++中,函数形参列表中的形参是可以有默认值的

语法:返回值类型 函数名(参数 = 默认值){}

  • 如果自己传入了数据,就用自己的数据;如果没有,就用默认值
  • 如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须有默认值(从右往左赋默认参数)
  • 如果函数声明有默认参数,则函数实现就不能有默认参数了,即声明和实现只能有一个有默认参数(二义性)
# include<iostream>
using namespace std;

// 有默认参数的函数
// 默认参数从右往左赋值:实参传入形参从左往右,如果实参个数少于形参个数,为防止出错,要保证右侧的形参有默认值
int func01(int a, int b = 20, int c = 30)
{
	return a + b + c;
}

// 声明和实现只能有一个有默认参数
int func02(int a, int b = 10); // 声明已有默认参数
int func02(int a, int b)	  // 实现就不能有默认参数了
{
	return a + b;
}

int main()
{
	// 如果有自己传入的参数,用传入的参数,否则用默认参数
	cout << func01(10) << endl;
	cout << func01(10, 50) << endl;

	cout << func02(10) << endl;

	system("pause");
	return 0;
}

3.2 函数的占位参数

C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

语法:返回值类型 函数名 (数据类型){}

占位参数也可以有默认参数

# include<iostream>
using namespace std;

// 占位参数
void func03(int a, int)
{
	cout << "This is a func" << endl;
}

// 占位参数可以有默认参数
void func04(int a, int = 10)
{
	cout << "This is a func" << endl;
}

int main()
{
	func03(10, 10);

	func04(20);

	system("pause");
	return 0;
}

3.3 函数重载

3.3.1 基本语法

作用:函数名可以相同,提高复用性

函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同个数不同顺序不同

注:函数的返回值类型不可以作为函数重载的条件

# include<iostream>
using namespace std;

/*
	函数重载的3个条件:
	1.同一个作用域下(此处为同在全局作用域下)
	2.函数名称相同
	3.函数参数类型不同或个数不同或顺序不同
*/

void func05()
{
	cout << "函数重载" << endl;
}

void func05(int a)
{
	cout << "函数重载-参数个数不同" << endl;
}

void func05(double a)
{
	cout << "函数重载-参数类型不同" << endl;
}

void func05(double a, int b)
{
	cout << "函数重载-参数顺序不同_double_int" << endl;
}

void func05(int a, double b)
{
	cout << "函数重载-参数顺序不同_int_double" << endl;
}

int main()
{
	func05();
	func05(10);
	func05(3.14);
	func05(3.14, 10);
	func05(10, 3.14);

	system("pause");
	return 0;
}

3.3.2 注意事项

  • 引用作为重载条件
  • 函数重载碰到函数默认参数
# include<iostream>
using namespace std;

// 1.引用作为重载条件
void func1(int& a) // int& a = 10; // 不合法
{
	cout << "func1(int& a)" << endl;
}

void func1(const int& a) // const int& a = 10; // 合法
{
	cout << "func1(const int& a)" << endl;
}

// 2.函数重载碰到函数默认参数
void func2(int a, int b = 10)
{
	cout << "func2(int a, int b = 10)" << endl;
}

void func2(int a)
{
	cout << "func2(int a)" << endl;
}

int main()
{
	int a = 10;
	func1(a);
	func1(10);
	// func2(10); 函数重载碰到函数默认参数,可能会出现二义性

	system("pause");
	return 0;
}

4.类和对象

C++面向对象的三大特征:封装、继承、多态

C++认为万事万物皆为对象,对象上有其属性和行为

4.1 封装

4.1.1 封装的意义

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

1.在设计类的时候,属性和行为写在一起,表现事物

语法:class 类名{访问权限:属性/行为};

<例1>设计一个圆类,求圆的周长

# include<iostream>
using namespace std;

const double PI = 3.14; // 圆周率

// 创建一个圆类
class Circle
{
	// 访问权限
public: // 公共权限
	// 属性
	int m_r;
	// 行为
	double caculateZC() // 计算圆的周长
	{
		return 2 * PI * m_r;
	}
};

int main()
{
	// 通过圆类,创建一个具体的圆即对象(实例化)
	Circle c1;

	// 给对象赋值
	c1.m_r = 10;
	// 计算圆的周长
	cout << "圆的周长:" << c1.caculateZC() << endl;

	system("pause");
	return 0;
}

<例2>设计一个学生类:属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号

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

// 创建学生类
class Student
{
public:
	// 属性
	string m_Name;
	int m_ID;

	// 行为
	// 显示姓名和学号
	void showStudent()
	{
		cout << "姓名:" << m_Name << '\t';
		cout << "学号:" << m_ID << endl;
	}
	// 给姓名赋值
	void setName(string name)
	{
		m_Name = name;
	}
	// 给学号赋值
	void setID(int id)
	{
		m_ID = id;
	}
};

int main()
{
	// 创建学生对象(实例化)
	Student s1;
	// 赋值
	s1.setName("张三");
	s1.setID(12345);
	// 输出信息
	s1.showStudent();
	cout << endl;

	system("pause");
	return 0;
}

注:类中的属性和行为统一称为成员

属性又称为成员属性或成员变量

行为又称成员函数或成员方法

2.类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:

  1. public      公共权限:成员类内可以访问,类外也可以访问
  2. protected 保护权限:成员类内可以访问,类外不可以访问(儿子可以访问父亲的保护内容)
  3. private     私有权限:成员类内可以访问,类外不可以访问(儿子不可以访问父亲的私有内容)
# include<iostream>
using namespace std;
# include<string>

// 创建一个人类
class Person
{
public: // 公共权限
	string m_Name;
protected: // 保护权限
	string m_car;
private: // 私有权限
	int password;
public:
	void func()
	{
		m_Name = "张三";
		m_car = "拖拉机";
		password = 123456;
	}
};

int main()
{
	// 实例化对象
	Person p1;
	p1.m_Name = "李四";
	// p1.m_car = "奔驰";
	// p1.password = 123;

	system("pause");
	return 0;
}

4.1.2 stuct和class区别

在C++中struct和class的唯一区别在于默认的访问权限不同:

  • struct默认访问权限是公共
  • class默认权限为私有

4.1.3 成员属性设置为私有

优点:

  1. 将所有成员属性设置为私有,可以自己控制读写权限
  2. 对于写权限,可以检测数据的有效性
# include<iostream>
using namespace std;
# include<string>

// 人类
class Person
{
public:
	// 设置姓名
	void setName(string name)
	{
		m_Name = name;
	}
	// 获取姓名
	string getName()
	{
		return m_Name;
	}

	// 获取年龄
	int getAge()
	{
		return m_Age;
	}

	// 设置偶像
	void setIdol(string name)
	{
		m_Idol = name;
	}

	// 设置年龄(0-150)
	void setAge(int age)
	{
		if (age < 0 || age>150)
		{
			cout << "年龄 " << age << " 输入有误,赋值失败" << endl;
			return;
		}
		m_Age = age;
	}

private:
	string m_Name; // 姓名 可读可写
	int m_Age = 18; // 年龄 只读  也可以写(0-150)
	string m_Idol; // 偶像 只写
};

int main()
{
	Person p;
	// 姓名设置
	p.setName("张三");
	// 获取姓名
	cout << "姓名:" << p.getName() << endl;

	// 获取年龄
	cout << "年龄:" << p.getAge() << endl;
	// 设置年龄
	p.setAge(160);

	// 偶像设置
	p.setIdol("小明");


	system("pause");
	return 0;
}

<案例>设计立方体类

设计立方体类(Cube)

求出立方体的面积和体积

分别用全局函数和成员函数判断两个立方体是否相等

# include<iostream>
using namespace std;

// 立方体类
class Cube
{
public:
	// 设置与获取长宽高
	void setL(int l)
	{
		m_L = l;
	}
	int getL()
	{
		return m_L;
	}

	void setW(int w)
	{
		m_W = w;
	}
	int getW()
	{
		return m_W;
	}

	void setH(int h)
	{
		m_H = h;
	}
	int getH()
	{
		return m_H;
	}

	// 计算面积与体积
	int caculateS()
	{
		return 2 * m_L * m_W + 2 * m_L * m_H + 2 * m_W * m_H;
	}

	int caculateV()
	{
		return m_L * m_W * m_H;
	}

	// 利用成员函数判断两个立方体是否相等
	bool isSameByClass(Cube& c)
	{
		if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH())
		{
			return true;
		}
		else
			return false;
	}

private:
	int m_L; // 长
	int m_W; // 宽
	int m_H; // 高
};

// 全局函数判断两个立方体是否相等
bool isSame(Cube& c1, Cube& c2)
{
	if (c1.getL() == c2.getL () && c1.getW() == c2.getW() && c1.getH () == c2.getH())
	{
		return true;
	}
	return false;
}

int main()
{
	// 实例化立方体对象
	Cube c1;
	c1.setL(10);
	c1.setW(10);
	c1.setH(10);
	// 计算面积与体积
	cout << "c1的面积:" << c1.caculateS() << endl;
	cout << "c1的体积:" << c1.caculateV() << endl;

	Cube c2;
	c2.setL(10);
	c2.setW(10);
	c2.setH(11);

	// 通过全局函数判断两个立方体是否相等
	bool ret = isSame(c1, c2);
	if (ret == 1)
	{
		cout << "全局函数判断:c1 = c2" << endl;
	}
	else
		cout << "全局函数判断:c1 ≠ c2" << endl;

	// 通过成员函数判断两个立方体是否相等
	ret = c1.isSameByClass(c2);
	if (ret == 1)
	{
		cout << "成员函数判断:c1 = c2" << endl;
	}
	else
		cout << "成员函数判断:c1 ≠ c2" << endl;

	system("pause");
	return 0;
}

<案例>点和圆的关系

设计一个圆形类(Circle)和一个点类(Point),计算点和圆的关系

# include<iostream>
using namespace std;

// 点类
class Point
{
public: // 设置与获取点的位置
	void setX(int x)
	{
		m_X = x;
	}
	int getX()
	{
		return m_X;
	}

	void setY(int y)
	{
		m_Y = y;
	}
	int getY()
	{
		return m_Y;
	}

private: // 圆心
	int m_X;
	int m_Y;
};

// 圆类
class Circle
{
public: 
	// 设置与获取半径
	void setR(int r)
	{
		m_R = r;
	}
	int getR()
	{
		return m_R;
	}

	// 设置与获取圆心
	void setCenter(Point center)
	{
		m_Center = center;
	}
	Point getCenter()
	{
		return m_Center;
	}
	

private:
	int m_R; // 半径
	Point m_Center; // 圆心
};

// 判断点和圆的关系
void isInCircle(Circle& c, Point& p)
{
	// 两点之间的距离的平方:(x1 - x2)^2 + (y1 - y2)^2
	int distance =
		(p.getX() - c.getCenter().getX()) * (p.getX() - c.getCenter().getX()) +
		(p.getY() - c.getCenter().getY()) * (p.getY() - c.getCenter().getY());
	// 圆半径的平方
	int cDistance = c.getR() * c.getR();

	// 判断点和圆的关系
	if (distance == cDistance)
	{
		cout << "点在圆上" << endl;
	}
	else if (distance > cDistance)
	{
		cout << "点在圆外" << endl;
	}
	else
	{
		cout << "点在圆内" << endl;
	}
}

int main()
{
	// 圆对象
	Circle c;
	c.setR(10); // 半径
	// 圆心
	Point center;
	center.setX(10);
	center.setY(0);
	c.setCenter(center);
	
	Point p;
	p.setX(10);
	p.setY(10);

	// 判断关系
	isInCircle(c, p);

	system("pause");
	return 0;
}

通常将类写到不同的头文件和源文件中

点类

头文件:point.h

#pragma once // 防止头文件重复包含
#include<iostream>
using namespace std;

// 点类
class Point
{
public: // 设置与获取点的位置
	void setX(int x);
	int getX();
	void setY(int y);
	int getY();

private: // 圆心
	int m_X;
	int m_Y;
};

源文件:point.cpp

#include"point.h"

void Point:: setX(int x)
{
	m_X = x;
}
int Point:: getX()
{
	return m_X;
}

void Point:: setY(int y)
{
	m_Y = y;
}
int Point:: getY()
{
	return m_Y;
}

圆类

头文件:cicle.h

#pragma once
#include<iostream>
#include"point.h"
using namespace std;

// 圆类
class Circle
{
public:
	// 设置与获取半径
	void setR(int r);
	int getR();
	// 设置与获取圆心
	void setCenter(Point center);
	Point getCenter();


private:
	int m_R; // 半径
	Point m_Center; // 圆心
};

源文件:circle.cpp

#include "circle.h"

	void Circle:: setR(int r)
	{
		m_R = r;
	}
	int Circle:: getR()
	{
		return m_R;
	}

	// 设置与获取圆心
	void Circle:: setCenter(Point center)
	{
		m_Center = center;
	}
	Point Circle:: getCenter()
	{
		return m_Center;
	}

主文件:

# include<iostream>
using namespace std;
#include "circle.h"
#include "point.h"

// 判断点和圆的关系
void isInCircle(Circle& c, Point& p)
{
	// 两点之间的距离的平方:(x1 - x2)^2 + (y1 - y2)^2
	int distance =
		(p.getX() - c.getCenter().getX()) * (p.getX() - c.getCenter().getX()) +
		(p.getY() - c.getCenter().getY()) * (p.getY() - c.getCenter().getY());
	// 圆半径的平方
	int cDistance = c.getR() * c.getR();

	// 判断点和圆的关系
	if (distance == cDistance)
	{
		cout << "点在圆上" << endl;
	}
	else if (distance > cDistance)
	{
		cout << "点在圆外" << endl;
	}
	else
	{
		cout << "点在圆内" << endl;
	}
}

int main()
{
	// 圆对象
	Circle c;
	c.setR(10); // 半径
	// 圆心
	Point center;
	center.setX(10);
	center.setY(0);
	c.setCenter(center);
	
	Point p;
	p.setX(10);
	p.setY(10);

	// 判断关系
	isInCircle(c, p);

	system("pause");
	return 0;
}

4.2 对象的初始化和清理

C++每个对象都有初始设置以及对象销毁前的清理数据的设置

4.2.1 构造函数和析构函数

对象的初始化和清理是两个非常重要的安全问题:

  • 一个对象或者变量没有初始状态,对其使用后果是未知
  • 使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

C++使用构造函数和析构函数解决上述问题,这两个函数会被编译器自动调用,完成对象初始化和清理工作

对象的初始化和清理工作是编译器强制要求我们做的事情,因此,如果我们不提供构造和析构,编译器会提供

编译器提供的构造函数和析构函数是空实现

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数编译器自动调用,无需手动调用
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作

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

  1. 构造函数,没有返回值也不写void
  2. 函数名与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次

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

  1. 析构函数,没有返回值也不写void
  2. 函数名与类名相同,在前面加上~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
#include <iostream>
using namespace std;

class Person
{
public:
	// 构造函数
	Person()
	{
		cout << "Person 构造函数的调用" << endl;
	}
	// 析构函数
	~Person()
	{
		cout << "Person 析构函数的调用" << endl;
	}
};

void test01()
{
	Person p; // 创建在栈上,函数调用完毕后释放
}

int main()
{
	test01();

	system("pause");
	return 0;
}

4.2.2 构造函数的分类及调用

两种分类方式:

  1. 按参数分:有参构造和无参构造(默认构造)
  2. 按类型分:普通构造和拷贝构造

三种调用方式:

  1. 括号法
  2. 显示法
  3. 隐式转换法
#include<iostream>
using namespace std;

class Person02
{
	// 分类
public:
	// 无参构造函数
	Person02()
	{
		cout << "Person02无参构造函数的调用" << endl;
	}
	
	// 有参构造函数
	Person02(int a)
	{
		age = a;
		cout << "Person02有参构造函数的调用" << endl;
	}

	// 拷贝构造函数
	Person02(const Person02& p)
	{
		age = p.age;
		cout << "Person02拷贝构造函数的调用" << endl;
	}

	int age;
};

// 调用
void test02()
{
	// 括号法
	cout << "调用-括号法:" << endl;
	Person02 p1;
	Person02 p2(10);
	Person02 p3(p2);

	// 显示法
	cout << "调用-显示法:" << endl;
	Person02 p4;
	Person02 p5 = Person02(10); // 显示法调用有参构造 Person02(10)为匿名对象,当前行执行结束后,系统会立即回收匿名对象
	Person02 p6 = Person02(p5); // 显示法调用拷贝构造

	// 隐式转换法
	cout << "调用-隐式转换法:" << endl;
	Person02 p7 = 10; // 隐式转换法调用有参构造 相当于显示法中的 Person02 p7 = Person02(10)
	Person02 p8 = p7; // 隐式转换法调用拷贝构造
}

int main()
{
	test02();

	system("pause");
	return 0;
}

注:

调用默认构造函数时不要加(),编译器会认为这是函数声明,不会认为这是在创建对象

不要利用拷贝构造函数初始化匿名对象,编译器会去掉(),认为这是一个对象声明

4.2.3 拷贝构造函数调用时机

通常有3种情况:

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象
#include <iostream>
using namespace std;

class Person03
{
public:
	Person03()
	{
		cout << "Person03默认构造函数的调用" << endl;
	}
	Person03(int a)
	{
		m_Age = a;
		cout << "Person03有参构造函数的调用" << endl;
	}
	Person03(const Person03& p)
	{
		m_Age = p.m_Age;
		cout << "Person03拷贝构造函数的调用" << endl;
	}
	~Person03()
	{
		cout << "Person03析构函数的调用" << endl;
	}

	int m_Age;
};

// 使用一个已经创建完毕的对象来初始化一个新对象
void test03()
{
	Person03 p1(20);
	Person03 p2(p1);
	cout << "p2的年龄:" << p2.m_Age << endl;
}

// 值传递的方式给函数参数传值
void doWork01(Person03 p)
{

}

void test04()
{
	Person03 p;
	doWork01(p); // 实参传递给形参时会调用拷贝构造函数
}

// 以值方式返回局部对象
Person03 doWork02()
{
	Person03 p1;
	cout << (int*)&p1 << endl;
	return p1;
}

void test05()
{
	Person03 p = doWork02();
	cout << (int*)&p << endl; // 这里把对象看作指针,因为通过p.调用对象中不同的函数及成员
}

int main()
{
	cout << "使用一个已经创建完毕的对象来初始化一个新对象:" << endl;
	test03();
	cout << endl;

	cout << "值传递的方式给函数参数传值:" << endl;
	test04();
	cout << endl;

	cout << "以值方式返回局部对象:" << endl;
	test05();

	system("pause");
	return 0;
}

4.2.4 构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数:

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,C++不再提供无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数

以下为有拷贝构造函数的代码:

// 创建一个类,C++编译器会给每个类都至少添加3个函数
#include <iostream>
using namespace std;

class Person
{
public:
	// 1.默认构造(空实现)
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}

	// 有参构造
	Person(int age)
	{
		m_Age = age;
		cout << "Person的有参构造函数调用" << endl;
	}

	// 2.析构函数(空实现)
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}
	// 3.拷贝构造(值拷贝)
	Person(const Person& p)
	{
		m_Age = p.m_Age;
		cout << "Person的拷贝构造函数调用" << endl;
	}

	int m_Age;
};

void test01()
{
	Person p1;
	p1.m_Age = 18; // 默认
	Person p2(p1); // 拷贝
	cout << "p2的年龄:" << p2.m_Age << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

以下为无拷贝构造函数的代码:

// 创建一个类,C++编译器会给每个类都至少添加3个函数
#include <iostream>
using namespace std;

class Person
{
public:
	// 1.默认构造(空实现)
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}

	// 有参构造
	Person(int age)
	{
		m_Age = age;
		cout << "Person的有参构造函数调用" << endl;
	}

	// 2.析构函数(空实现)
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}
	
	int m_Age;
};

void test01()
{
	Person p1;
	p1.m_Age = 18; // 默认
	Person p2(p1);
	cout << "p2的年龄:" << p2.m_Age << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

这时的拷贝构造函数为系统提供

以下为仅有有参构造和析构函数代码:

// 如果用户定义有参构造函数,C++不再提供无参构造,但是会提供默认拷贝构造
#include <iostream>
using namespace std;

class Person
{
public:
	// 有参构造
	Person(int age)
	{
		m_Age = age;
		cout << "Person的有参构造函数调用" << endl;
	}

	// 2.析构函数(空实现)
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}

	int m_Age;
};

void test02()
{
	Person p1(28); // 有参构造
	Person p2(p1); // 拷贝构造
	cout << "p2的年龄:" << p2.m_Age << endl;
}

int main()
{
	test02();

	system("pause");
	return 0;
}

4.2.5 深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间

浅拷贝带来的问题:堆区的内存重复释放

解决方式:深拷贝

#include <iostream>
using namespace std;

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

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

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

		// 深拷贝操作
		m_Height = new int(*p.m_Height);
	}

	~Person2()
	{
		// 析构代码 将堆区开辟的数据做释放操作
		if (m_Height != NULL)
		{
			delete m_Height;
			m_Height = NULL;
		}

		cout << "Person2默认构造函数的调用" << endl;
	}

	int m_Age;
	int* m_Height;
};

void test02()
{
	Person2 p1(18, 160);
	Person2 p2(p1);
	cout << "p1的年龄:" << p1.m_Age << "	p1的身高:" << *p1.m_Height << endl;
	cout << "p2的年龄:" << p2.m_Age << "	p2的身高:" << *p2.m_Height << endl;
}

int main()
{
	test02();

	system("pause");
	return 0;
}

4.2.6 初始化列表

作用:C++提供了初始化列表语法,用来初始化属性

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

#include <iostream>
using namespace std;

class Person3
{
public:
	// 初始化列表
	Person3(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 test03()
{
	Person3 p(30, 20, 10);
	cout << "m_A = " << p.m_A << endl;
	cout << "m_B = " << p.m_B << endl;
	cout << "m_C = " << p.m_C << endl;
}

int main()
{
	test03();

	system("pause");
	return 0;
}

4.2.7 类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员

当其他类的对象作为本类的成员时,先构造其他类的对象,再构造本类的对象,析构的顺序与构造相反

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

// 手机类
class Phone
{
public:
	string m_PName; // 手机品牌名称
	Phone(string pName)
	{
		m_PName = pName;
		cout << "Phone构造函数的调用" << endl;
	}

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

// 人类
class Person4
{
public:
	string m_Name; // 姓名
	Phone m_Phone; // 手机
	Person4(string name, string pName) :m_Name(name), m_Phone(pName)
	{
		cout << "Person4构造函数调用" << endl;
	}

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

void test04()
{
	Person4 p("张三", "苹果MAX");
	cout << p.m_Name << "拿着:" << p.m_Phone.m_PName << endl;
}

int main()
{
	test04();

	system("pause");
	return 0;
}

4.2.8 静态成员

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

静态成员分为:

静态成员变量

  • 所有对象共享一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化

静态成员变量,不属于某个对象,所有对象都共享一份数据

因此,静态成员变量有两种访问方式:

  • 通过对象进行访问
  • 通过类名进行访问

静态成员函数

  • 所有对象共享一个函数
  • 静态成员函数只能访问静态成员变量

静态成员函数访问方式与静态成员变量的相同:

  • 通过对象进行访问
  • 通过类名进行访问

静态成员变量示例:

#include <iostream>
using namespace std;

// 静态成员变量
class Person5
{
public:
	static int m_A; // 类内声明
private:
    static int m_B;
};

int Person5::m_A = 100; // 类外初始化
int Person5::m_B = 100; // 私有 类外不可访问
void test05()
{
	Person5 p1;
	p1.m_A = 100;
	cout << p1.m_A << endl; // 通过对象访问静态成员变量
	Person5 p2;
	p2.m_A = 200; // 所有对象共享一份数据
	cout << Person5::m_A << endl; // 通过类名访问静态成员变量
}

int main()
{
	test05();

	system("pause");
	return 0;
}

静态成员函数示例:

#include <iostream>
using namespace std;

// 静态成员函数
class Person6
{
public:
	static void func1()
	{
		m_A = 0; // 静态成员函数只能访问静态成员变量
		cout << "static void func1的调用" << endl;
	}

	static int m_A;

private:
	static void func2()
	{
		// 静态成员函数也是有访问权限的
	}
};

int Person6::m_A = 100;

void test06()
{
	// 通过对象进行访问
	Person6 p1;
	p1.func1();

	// 通过类名进行访问
	Person6::func1();
}

int main()
{
	test06();

	system("pause");
	return 0;
}

4.3 C++对象模型和this指针

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

在C++种,类的成员函数和成员变量分开存储

只有非静态成员变量才属于类的对象上

C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置

每个空对象也应该有一个独一无二的内存地址

#include <iostream>
using namespace std;

class Person01
{

};

void test01()
{
	Person01 p1;
	cout << "size of p1 = " << sizeof(p1) << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

加入整型非静态成员变量后:

#include <iostream>
using namespace std;

class Person01
{
public:
	int m_A;
};

void test01()
{
	Person01 p1;
	cout << "size of p1 = " << sizeof(p1) << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

加入整型静态变量后:

#include <iostream>
using namespace std;

class Person01
{
public:
	int m_A; // 非静态成员变量 属于类的对象上
	static int m_B; // 静态成员变量 不属于类的对象上
};

int Person01::m_B = 0;

void test01()
{
	Person01 p1;
	cout << "size of p1 = " << sizeof(p1) << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

加入非静态成员函数后:

#include <iostream>
using namespace std;

class Person01
{
public:
	int m_A; // 非静态成员变量 属于类的对象上
	static int m_B; // 静态成员变量 不属于类的对象上
	void func1(){} // 非静态成员函数 不属于类的对象上
};

int Person01::m_B = 0;

void test01()
{
	Person01 p1;
	cout << "size of p1 = " << sizeof(p1) << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

加入静态成员函数后:

#include <iostream>
using namespace std;

class Person01
{
public:
	int m_A; // 非静态成员变量 属于类的对象上
	static int m_B; // 静态成员变量 不属于类的对象上
	void func1(){} // 非静态成员函数 不属于类的对象上
	static void func2(){} // 静态成员函数 不属于类的对象上
};

int Person01::m_B = 0;

void test01()
{
	Person01 p1;
	cout << "size of p1 = " << sizeof(p1) << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

4.3.2 this指针的用途

通过4.3.1知道C++成员变量和成员函数是分开存储的

每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码

那么问题是:这一块代码是如何区分那个对象调用自己的呢?

C++通过提供特殊的对象指针,this指针,解决上述问题

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

this指针是隐含每一个非静态成员函数内的一种指针

this指针不需要定义,直接使用即可

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可用return* this
#include <iostream>
using namespace std;

class Person02
{
public:
	Person02(int age)
	{
		// 当形参和成员变量同名时 可用this指针来区分
		this->age = age;
	}

	Person02& PersonAddAge(Person02 p)
	{
		this->age += p.age;
		return*this;
	}

	int age;
};

void test02()
{
	Person02 p1(18);
	cout << "p1的年龄:" << p1.age << endl;
}

// 在类的非静态成员函数中返回对象本身,可用retun* this

void test03()
{
	Person02 p2(10);
	Person02 p3(10);

	// 链式编程思想
	p3.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(10);
	cout << "p3的年龄:" << p3.age << endl;
}

int main()
{
	test02();
	test03();

	system("pause");
	return 0;
}

4.3.3 空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性

#include <iostream>
using namespace std;

class Person03
{
public:
	void ShowClass()
	{
		cout << "this is Person03 class" << endl;
	}

	void ShowAge()
	{
		// 注意判断如果用到this指针,需要加以判断以保证代码的健壮性
		if (this == NULL)
		{
			return;
		}
		cout << "age = " << this->m_Age << endl;
	}

	int m_Age;
};

void test04()
{
	Person03* p = NULL;
	// 空指针也可以调用成员函数
	p->ShowClass();
	// 注意判断如果用到this指针,需要加以判断以保证代码的健壮性
	p->ShowAge();
}

int main()
{
	test04();

	system("pause");
	return 0;
}

4.3.4 const修饰成员函数

常函数:

  • 成员函数后加const后称这个函数为常函数
  • 常函数不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数

this指针的本质:指针常量,即指针的指向是不可以修改的

this指针是不可以修改指向的

在成员函数后面加const本质修饰的是this指针,让指针指向的值也不可以修改

#include <iostream>
using namespace std;

class Person7
{
public:
	// 常函数
	void showPerson()const
	{
		m_B = 100;
	}

	int m_A; // 常函数不可以修改成员属性
	mutable int m_B; // 成员属性声明时加关键字mutable后,在常函数中依然可以修改
};

void test07()
{
	const Person7 p;
	p.showPerson(); // 常对象只能调用常函数

}

int main()
{

	system("pause");
	return 0;
}

4.4 友元

在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术

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

友元的关键字:friend

友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

4.4.1 全局函数做友元

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

// 建筑物类
class Building1
{
	// 全局函数GoodGay是Building类的友元函数 可以访问Building类的私有成员
	friend void GoodGay(Building1* building);

public:
	Building1()
	{
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}

	string m_SittingRoom;

private:
	string m_BedRoom;
};

// 全局函数
void GoodGay(Building1* building)
{
	cout << "全局函数好基友函数 正在访问:" << building->m_SittingRoom << endl;
	cout << "全局函数好基友函数 正在访问:" << building->m_BedRoom << endl;
}

void test01()
{
	Building1 building;
	GoodGay(&building);
}

int main()
{
	test01();

	system("pause");
	return 0;
}

4.4.2 友元类

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

class Building;

class GoodGay
{
public:
	GoodGay();
	void visit();
private:
	Building* building;
};

class Building
{
	friend class GoodGay;  //声明GoodGay是Building的友元类,可以访问Building类的私有成员
public:
	Building();

	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 test02()
{
	GoodGay gg;
	gg.visit();
}

int main()
{
	test02();

	system("pause");
	return 0;
}

4.4.3 成员函数做友元

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

class Building3;

class GoodGay3
{
public:
	GoodGay3();
	void visit1(); // visit1可以访问Building3中的私有成员
	void visit2(); // visit2不可以访问Building3中的私有成员
private:
	Building3* building;
};

class Building3
{
	friend void GoodGay3::visit1();
public:
	Building3();
	string m_SittingRoom;
private:
	string m_BedRoom;
};

GoodGay3::GoodGay3()
{
	building = new Building3;
}

void GoodGay3::visit1()
{
	cout << "好基友GoodGay正在访问" << building->m_SittingRoom << endl;
	cout << "好基友GoodGay正在访问" << building->m_BedRoom << endl;
}

void GoodGay3::visit2()
{
	cout << "好基友GoodGay正在访问" << building->m_SittingRoom << endl;
}

Building3::Building3()
{
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}

void test02()
{
	GoodGay3 gg;
	gg.visit1();
	gg.visit2();
}

int main()
{
	test02();

	system("pause");
	return 0;
}

4.5 运算符重载

定义:对已有的运算符进行重新定义,赋予其另一种功能,以适应不同的数据类型

4.5.1 加号运算符重载

 作用:实现两个自定义数据类型相加的运算

  • 通过成员函数实现
#include <iostream>
using namespace std;

class Person1
{
public:
	int m_A;
	int m_B;

	// 成员函数实现运算符+重载
	Person1 operator+(Person1& p)
	{
		Person1 temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}
};

void test01()
{
	Person1 p1;
	p1.m_A = 10;
	p1.m_B = 10;

	Person1 p2;
	p2.m_A = 10;
	p2.m_B = 10;

	Person1 p3 = p1 + p2;
	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

  •  通过全局函数实现
#include <iostream>
using namespace std;

class Person2
{
public:
	int m_A;
	int m_B;
};

Person2 operator+ (Person2 & p1, Person2 & p2)
{
	Person2 temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}

void test02()
{
	Person2 p1;
	p1.m_A = 10;
	p1.m_B = 10;

	Person2 p2;
	p2.m_A = 10;
	p2.m_B = 10;

	Person2 p3 = p1 + p2;
	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;
	
}

int main()
{
	test02();

	system("pause");
	return 0;
}

  • 运算符重载,也可以发生函数重载
#include <iostream>
using namespace std;

class Person3
{
public:
	int m_A;
	int m_B;
};

Person3 operator + (Person3 & p, int num)
{
	Person3 temp;
	temp.m_A = p.m_A + num;
	temp.m_B = p.m_B + num;
	return temp;
}

void test03()
{
	Person3 p1;
	p1.m_A = 10;
	p1.m_B = 15;

	Person3 p2;
	p2.m_A = 10;
	p2.m_B = 15;

	Person3 p3 = p1 + 10;
	Person3 p4 = p2 + 20;

	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;

	cout << "p4.m_A = " << p3.m_A << endl;
	cout << "p4.m_B = " << p3.m_B << endl;
}

int main()
{
	test03();

	system("pause");
	return 0;
}

4.5.2 左移运算符重载

作用:可以输出自定义数据类型

注:通常不会利用成员函数重载左移运算符,因为无法实现cout在左侧

cout类型:ostrem即标准输出流对象

#include <iostream>
using namespace std;

class Person4
{
	friend ostream& operator<<(ostream& cout, Person4& p);
public:
	Person4(int a, int b)
	{
		m_A = a;
		m_B = b;
	}
private:
	int m_A;
	int m_B;
};

// 只能通过全局函数对左移运算符进行重载
ostream& operator<<(ostream& cout, Person4& p)
{
	cout << "m_A = " << p.m_A << "	m_B = " << p.m_B;
	return cout;
}

void test04()
{
	Person4 p(10, 10);
	cout << p << endl;
}

int main()
{
	test04();

	system("pause");
	return 0;
}

4.5.3 递增运算符重载

 作用:通过重载递增运算符,实现自己的整型数据

#include <iostream>
using namespace std;

class MyInteger
{
	friend ostream& operator<<(ostream& cout, MyInteger myint);

public:
	MyInteger()
	{
		m_Num = 0;
	}

	// 重载前置++运算符
	MyInteger& operator++() // 前置递增返回引用是为了一直对一个数据进行自增操作
	{
		// 先进行自增
		m_Num++;
		// 再返回
		return *this;
	}

	// 重载后置运算符
	MyInteger operator++(int) // int代表占位参数(只能用int),用于区分前置和后置
	{
		// 先记录原来的值
		MyInteger temp = *this;
		// 后递增
		m_Num++;
		// 最后返回原来的值
		return temp;
	}

private:
	int m_Num;
};

// 重载左移运算符
ostream& operator<<(ostream& cout, MyInteger myint)
{
	cout << myint.m_Num;
	return cout;
}

void test05()
{
	MyInteger myint;
	cout << ++myint << endl;
}

void test06()
{
	MyInteger myint;
	cout << myint++ << endl;
	cout << myint << endl;
}

int main()
{
	test05();
	test06();

	system("pause");
	return 0;
}

4.5.4 赋值运算符重载 

C++编译器至少给一个类添加4个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 负值运算符operator=,对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

#include <iostream>
using namespace std;

class Person6
{
public:
	Person6(int age)
	{
		m_Age = new int(age);
	}
	~Person6()
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}

	}

	Person6& operator=(Person6& p)
	{
		// 编译器是浅拷贝 m_Age = p.m_Age
		// 应该先判断是否有属性在堆区 如果有 先进行释放
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		m_Age = new int(*p.m_Age);
		return*this;
	}

	int* m_Age;
};

void test07()
{
	Person6 p1(18);
	Person6 p2(19);
	Person6 p3(20);

	p3 = p2 = p1;
	cout << "p1的年龄:" << *p1.m_Age << endl;
	cout << "p2的年龄:" << *p2.m_Age << endl;
	cout << "p3的年龄:" << *p3.m_Age << endl;
}

int main()
{
	test07();

	system("pause");
	return 0;
}

4.5.5 关系运算符重载

 作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

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

class Person1
{
public:
	Person1(string name, int age)
	{
		m_Name = name;
		m_Age = age;
	}

	string m_Name;
	string m_Age;

	bool operator == (Person1& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		return false;
	}

	bool operator != (Person1& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return false;
		}
		return true;
	}
};

void test08()
{
	Person1 p1("Tom", 18);
	Person1 p2("Jerry", 18);
	if (p1 == p2)
	{
		cout << "p1 和 p2 是相等的!" << endl;
	}
	else
	cout << "p1 和 p2 是不相等的!" << endl;
}

void test09()
{
	Person1 p1("Tom", 18);
	Person1 p2("Jerry", 18);
	if (p1 != p2)
	{
		cout << "p1 和 p2 是不相等的!" << endl;
	}
	else
	cout << "p1 和 p2 是相等的!" << endl;
}

int main()
{
	test08();
	test09();

	system("pause");
	return 0;
}

4.5.6 函数调用运算符重载

  •  函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
#include <iostream>
using namespace std;
#include <string>

class MyPrint
{
public:
	void operator()(string test)
	{
		cout << test << endl;
	}
};

void test01()
{
	MyPrint myprint;
	myprint("hello world"); // 函数调用重载
}

void _MyPrint(string test)
{
	cout << test << endl;
}

class MyAdd
{
public:
	// 仿函数没有固定写法,非常灵活
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test02()
{
	MyAdd myadd;
	int ret = myadd(10, 20);
	cout << "ret = " << ret << endl;
	cout << MyAdd()(20, 30) << endl; // 匿名函数对象
}

int main()
{
	test01();
	_MyPrint("Hello World"); // 函数调用
	test02();

	system("pause");
	return 0;
}

4.6 继承

 继承是面向对象三大特性之一

定义类时,下级别的成员除了拥有上一级的共性,还带有自己的特性

这时候可以考虑利用继承的技术,减少重复代码

4.6.1 继承的基本语法

例:很多网站中都有公共的头部、公共的底部甚至公共的左侧列表,只有中心内容不同

下面分别用普通写法和继承写法来实现网页中的内容,看一下继承存在的意义及好处

普通写法:

 

#include <iostream>
using namespace std;

// 普通写法
class Java
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册……(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++、……(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "Java学科视频" << endl;
	}
};

class Python
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册……(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++、……(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};

class Cpp
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册……(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++、……(公共分类列表)" << endl;
	}
	void content()
	{
		cout << "Cpp学科视频" << endl;
	}
};

void test01()
{
	cout << "Java下载视频页面如下:" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------------" << endl;

	cout << "Python下载视频页面如下:" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------------" << endl;

	cout << "Cpp下载视频页面如下:" << endl;
	Cpp cpp;
	cpp.header();
	cpp.footer();
	cpp.left();
	cpp.content();
	cout << "--------------------------" << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

继承写法:

 

#include <iostream>
using namespace std;

// 继承写法
class BasePage
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册……(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++、……(公共分类列表)" << endl;
	}
};

// Java页面
class Java : public BasePage
{
public:
	void content()
	{
		cout << "Java学科视频" << endl;
	}
};
// Python页面
class Python : public BasePage
{
public:
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
// C++页面
class CPP : public BasePage
{
public:
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
	cout << "Java下载视频页面如下:" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------------" << endl;

	cout << "Python下载视频页面如下:" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------------" << endl;

	cout << "Cpp下载视频页面如下:" << endl;
	CPP cpp;
	cpp.header();
	cpp.footer();
	cpp.left();
	cpp.content();
	cout << "--------------------------" << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

继承的好处:减少重复代码

语法:class 子类 :继承方式  父类

子类也称为派生类,父类也成为基类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值