C++核心编程笔记,附代码

 

目录

1.内存分区模型

 1.1程序运行前

代码区:

全局区: 

1.2程序运行后 

栈区:

堆区:

1.3new操作符

2.引用

2.1引用的基本使用 

2.2引用的注意事项

2.3引用做函数参数

2.4引用做函数返回值

 2.5引用的本质

2.6常量引用

3.函数提高

3.1函数默认参数 

3.2函数的占位参数

3.3函数重载

 3.3.1函数重载概述

 3.3.2函数重载的注意事项

4.类和对象

4.1封装 

4.1.1封装的意义 

 4.1.2struct和class的区别

 4.1.3成员属性设置为私有

练习案例1:设计立方体类

 练习案例2:点和圆的关系

 类的拆分

 4.2对象的初始化和清理

 4.2.1构造函数和析构函数

4.2.2构造函数的分类及调用

4.2.3拷贝构造函数调用时机

4.2.4构造函数的调用规则

 4.2.5深拷贝与浅拷贝

4.2.6初始化列表

4.2.7类对象作为类成员

 4.2.8静态成员

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

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

4.3.2this指针概念

4.3.3空指针访问成员函数

 4.3.4cosnt修饰成员函数 

4.4友元

4.5运算符重载

4.5.1加号运算符重载

4.5.2左移运算符重载

4.5.3递增运算符重载

 4.5.4赋值运算符重载

 4.5.5关系运算符重载

4.5.6函数调用运算符重载

4.6继承

4.6.1继承的基本语法 

4.6.2继承方式

 4.6.3继承中的对象模型

4.6.4继承中构造和析构顺序

4.6.5继承同名成员处理方式

4.6.6继承同名静态成员处理方式

 4.6.7多继承语法

 4.6.8菱形继承

4.7多态

 4.7.1多态的基本概念

 4.7.2多态案例一计算器类

4.7.3纯虚函数和抽象类 

 4.7.5虚析构和纯虚析构

5.文件操作 

5.1文件文本 

 5.1.1写文件

5.1.2读文件

 5.2二进制文件

5.2.1写文件 

5.2.2读文件


本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓

1.内存分区模型

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

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

内存四区的意义:

        不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程 

 1.1程序运行前

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

代码区

        存放CPU执行的机器指令

        代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

        代码区是只读的,使其只读的原因是防止程序意外的修改了他的指令 

全局区: 

        全局变量和静态变量存放在此

        全局区还包括了常量区,字符串常量和其他常量也存放在此

         该区域的数据在程序结束后由操作系统施放

 局部变量与const修饰的局部变量不在全局区

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

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

//全局变量
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修饰的变量
	//字符串常量
	cout << "字符串常量地址:" << (int)&"hello world" << endl;//与全局在同一区段的不同区域
	
	//const修饰的变量
	//const修饰的全局变量,const修饰的局部变量
	cout << "const修饰的全局变量的地址:" << (int) & c_g_a << endl;
	cout << "const修饰的全局变量的地址:" << (int)&c_g_b << endl;
	//const修饰的局部变量
	const int c_l_a = 10;
	const int c_l_b = 10;
	cout << "const修饰的局部变量的地址:" << (int)&c_l_a << endl;//也不在全局区
	cout << "const修饰的局部变量的地址:" << (int)&c_l_b << endl;//带有局部的不在全局区
	system("pause");
	return 0;
}

总结:

  • C++在程序运行前分为全局区和代码区
  • 代码区的特点是只读和共享
  • 全局区中存放全局变量、静态变量、常量
  • 常量区中存放const修饰的全局变量和字符串常量

1.2程序运行后 

栈区

         由编译器自动分配释放,存放函数的参数值,局部变量等

        注意事项:不要反悔局部变量的地址,栈区开辟的数据由编译器自动释放

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

//栈区数据的注意事项——不要返回局部变量的地址
//栈区的数据由编译器管理开辟和释放

int* func(int b)//形参数据也会放在栈区
{
	b = 100;
	int a = 10;//局部变量 存放在栈区,栈区的数据在函数执行完后自动释放
	return &a;//返回局部变量的地址
}
int main()
{
	//接收func函数的返回值
	int* p = func(1);
	cout << *p << endl;//第一次能打印正确数字是因为编译器做了保留
	cout << *p << endl;//第二次这个数据就不再保留了(vs2022的X86会显示异常,X64依旧正确)
	system("pause");
	return 0;
}

堆区

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

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

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

int* func()//指针类型
{
	//利用new关键字,可以将数据开辟到堆区
	//指针本质也是局部变量,也是放在栈上,指针保存的数据放在堆区
	int* p=new int(10);//new返回的是地址,用指针p来接收
	return p;//返回p地址
}

int main()
{
	//堆区开辟数据
	int* p = func();//指针p接收func返回的地址
	
	cout << *p << endl;//*p解引用出p地址的值,该值在堆区
	system("pause");
	return 0;
}

1.3new操作符

 C++中利用new操作符在堆区开辟数据

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

语法:new 数据类型 

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

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

//new的基本语法
int* func()
{
	//在堆区创建衣蛾整型数据
	//new返回的是该数据类型的指针
	int* p=new int(10);
	return p;
}
void test01()
{
	int* p = func();
	cout << *p << endl;
	cout << *p << endl;
	//堆区的数据由程序员开辟释放
	//如果想释放,运用delete
	delete p;//释放该内存
	//cout << *p << endl;//内存已被释放,再次访问就会报错

}
//在堆区利用new开辟数组
void test02()
{
	//创建10个整型数据的数组在堆区
	int*arr =new int[10];//用中括号创建数组,数组有十个元素,返回的依旧是地址
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;//赋值与栈区一样
	}
	delete []arr;//释放堆区数组,需要加[]
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

2.引用

2.1引用的基本使用 

作用:给变量起别名

语法:数据类型 &别名 = 原名 

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


int main()
{
	//引用的基本语法
	//数据类型 &别名=原名
	int a = 10;
	int &b = a;
	b = 20;
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	system("pause");
	return 0;
}

2.2引用的注意事项

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

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


int main()
{
	int a = 10;
	
	//引用必须初始化
	//int& b;//错误的,必须要初始化
	int& b = a;

	//引用在初始化后,就不可以发生改变
	int c = 20;
	//int& b = c;//错误的,不可以发生改变
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl;
	system("pause");
	return 0;
}

2.3引用做函数参数

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

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

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

//交换函数

//值传递
void myswap01(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
	cout << "a=" << a << "\tb=" << b << endl;
}
//地址传递
void myswap02(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
	cout << "a=" << *a << "\tb=" << *b << endl;
}
//引用传递
void  myswap03(int&a,int&b)//别名与原名一样
{
	int temp = a;
	a = b;
	b = temp;
	cout << "a=" << a << "\tb=" << b << endl;
}
int main()
{
	int a = 10 ;
	int b = 20;
	//myswap01(a, b);//值传递,形参不会修饰实参
	cout << "a=" << a << "\tb=" << b << endl;

	//myswap02(&a, &b);//地址传递,形参会修饰实参
	cout << "a=" << a << "\tb=" << b << endl;

	myswap03(a, b);//引用传递,形参会修饰实参
	cout << "a=" << a << "\tb=" << b << endl;
	system("pause");
	return 0;
}

2.4引用做函数返回值

作用:引用是可以作为函数的返回值存在的

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

用法:函数调用作为左值 

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


//引用做函数的返回值
//1.不要返回局部变量的引用
int& test01()
{
	int a = 10;//局部变量,存放在栈区
	return a;
}
//2.函数的调用可以作为左值
int& test02()//返回的是a的引用,即a变量
{
	static int a = 10;//静态变量,全局区
	return a;
}
int main()
{
	int &ref = test01();
	cout << "ref=" << ref << endl;//正确,因为编译器做了保留(x86与x64不一样)
	cout << "ref=" << ref << endl;//错误,因为内存已经释放

	int& ref2 = test02();//ref2是a的别名
	cout << "ref2=" << ref2 << endl;//正确的,10
	cout << "ref2=" << ref2 << endl;//正确的,10

	//如果函数的返回值是引用,这个函数调用可以作为左值
	test02() = 1000;//变量的返回值=1000,即a=1000;的操作
	cout << "ref2=" << ref2 << endl;//1000
	cout << "ref2=" << ref2 << endl;//1000


	system("pause");
	return 0;
}

 2.5引用的本质

 本质:引用的本质在c++内部实现是一个指针常量(指向不可改,值可改)

 c++推荐使用引用计数,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了

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

void func(int& ref)
{
	ref = 100;
}
int main()
{
	int a = 10;

	//自动转换为 int* const ref = &a;指针常量是指针指向不可改,也说明为什么引用不可改
	int& ref = a;//内部发现ref是引用,自动帮我们转换为*ref=20;
	ref = 20;

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

	func(a);
	cout << "a=" << a << endl;
	cout << "ref=" << ref << endl;
	system("pause");
	return 0;
}

2.6常量引用

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

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

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

//打印数据函数
void showvalue(const int& val)
{
	//val = 1000;
	cout << "val=" << val << endl;
}
int main()
{
	//常量引用
	//使用场景:用来修饰形参,防止误操作

	int a = 10;
	//加上const之后,编译器将代码修改为 int temp = 10;const int& ref = temp;
	//const int& ref = 10;//引用必须引一块合法的内存空间
	//ref = 20;//加入const变为只读,不可修改

	int b = 100;
	showvalue(b);
	cout << "b=" << b << endl;
	system("pause");
	return 0;
}

3.函数提高

3.1函数默认参数 

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

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

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

//函数的默认参数

//如果传入了数据,就用传入的,如果没有传入,就用默认值
//语法:返回值类型 函数名(形参=默认值){  }
int func(int a, int b=10, int c=10)
{
	return a + b + c;
}


//注意事项
//1.如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须有默认值
	//int func2(int a, int b = 5, int c),错误的,b赋值了,c也要赋值

//2.如果函数的声明有了默认参数,那么这个函数的实现就不能有默认参数
//声明和实现只能有一个有默认参数
int func2(int a, int b);//函数声明

int func2(int a=20, int b=20)
{
	return a + b  ;
}
int main()
{
	func(1);
	cout << func2() << endl;
	system("pause");
	return 0;
}

3.2函数的占位参数

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

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

//占位参数

void func(int)//仅仅写一个数据类型
{
	printf("this is func");
}

//占位参数也可以有默认值
void func2(int = 10)
{

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

3.3函数重载

 3.3.1函数重载概述

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

函数重载满足条件:

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

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

                比如将 void函数换做int类型函数,函数名不变,也无法函数重载

#include<iostream>
using namespace std;

void func()
{
	printf("调用");
}

void func(int a)
{
	printf("调用!");
}

void func(double a)//类型不同
{
	printf("类型不同");
}

void func(int a,double b)//个数不同
{
	printf("个数不同");
}

void func(double a, int b)//顺序不同
{
	printf("顺序不同");
}
int main()
{
	func(1,1.1);
	system("pause");
	return 0;
}

 3.3.2函数重载的注意事项

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

//函数重载的注意事项
//1.引用作为重载的条件
void func(int &a)
{
	cout << "func(int &a)调用" << endl;
}

void func(const int& a)//const int &a = 10;合理
{
	cout << "func(const int &a)调用" << endl;
}

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

void func2(int a)
{
	cout << "func2(int a=10)的调用" << endl;
}
int main()
{
	int a = 10;
	func(a);//a是变量,可读可写

	func(10);//10是常量,调用第二个

	func2(20);//函数重载碰到函数默认参数 ,报错,有二义性
	system("pause");
	return 0;
}

4.类和对象

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

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

例如:

        人可以作为对象,属性有姓名、年龄、身高、体重...,行为有走、跑、跳、吃饭、唱歌...

        车也可以作为对象,属性有轮胎、方向盘、车灯..,行为有载人、放音乐、放空调...

        具有相同性质的对象,我们可以抽象称为,人属于人类,车属于车雷类

4.1封装 

4.1.1封装的意义 

封装是C++面向对象三大特性之一

封装的意义:

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

封装意义一

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

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

  •     类中的属性和行为统一成为  成员
  •     属性  分为成员属性 成员变量
  •     行为   分为成员函数  成员函数

 给属性赋值也可以在类中,参考该节例题

#include<iostream>
using namespace std;

const double PI = 3.14;
//设计一个圆类,求圆的周长
//圆求周长的公式:2*PI*R
//class代表类,后面紧跟着就是类名
class  circle
{
	//访问权限
		//公共权限
public:
	//属性
		//半径
	int m_R;
	//行为,通常用函数
		//获取圆的周长
	double calculate()
	{
		return 2 * PI * m_R;
	}
};
int main()
{
	//通过圆类,创建具体的圆(对象)
    //实例化:通过一个类,创建一个对象的过程
	circle c1;
	//给圆对象 的属性进行赋值,通过 .访问属性
	c1.m_R = 10;
	cout << "圆的周长是:" << c1.calculate() << endl;

	system("pause");
	return 0;
}

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

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

//设计学生类
class student
{
	//访问权限
public://公共权限

	//类中的属性和行为统一成为  成员
	// 属性  分为成员属性 成员变量
	// 行为   分为成员函数  成员函数
	//属性
	string name;//姓名
	int number;//学号
	//行为
	void show()//显示姓名学号
	{
		cout << name <<":"<< number << endl;
	}

	//给属性赋值**
	void setname(string name1,int number1)
	{
		name = name1;
		number = number1;
	}
};
int main()
{
	//实例化:创建具体学生
	student s1;
	//给s1进行属性赋值
	s1.name = "张三";
	s1.number = 2022;
	//显示学生信息
	s1.show();
	
	//实例化s2
	student s2;
	s2.setname("里斯",2023);
	s2.show();
	system("pause");
	return 0;
}

封装意义二

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

访问权限有三种:

  • 1.public        公共权限       

        成员在类内可以访问,类外也可以访问

  • 2.protected        保护权限        

        类内可以访问,类外不可以访问(继承中:父类权限子类可以访问)

  • 3.private        私有权限        

         类内可以访问,类外不可以访问(继承中:父类权限子类不可访问)

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


class person
{	
public://公共权限
	string name;//姓名

protected://保护权限
	int age;//年龄

private://私有权限
	int password;//密码
public:
	void func()//类内访问,均无问题
	{
		name = "张三";
		age = 18;
		password = 123456;
	}
};
int main()
{
	//实例化具体对象
	person p1;
	p1.name = "里斯";//公共,可以访问
	p1.age = 20;//保护,无法访问
	p1.password = 654321;//私有,不可访问
	system("pause");
	return 0;
}

 4.1.2struct和class的区别

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

区别:

  • struct 默认权限为公共
  • class 默认权限为私有 
#include<iostream>
using namespace std;
#include"string"

struct c1
{
	int A;//默认权限是公共
};

class c2
{
	int B;//默认权限是私有
};
int main()
{
	c1 s1;
	s1.A = 100;//正确的

	c2 s2;
	s2.B = 50;//错误的,私有不可访问
	system("pause");
	return 0;
}

 4.1.3成员属性设置为私有

 优点1:将所有成员属性设置为私有,可以自己控制读写权限

优点2:对于写权限,我们可以检测数据的有效性

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

class person
{
	string name;//姓名
	int age;//年龄
	string wife;//妻子
public://对外接口
	//姓名可读可写
		//写姓名(设置姓名)
	void setname(string name1)
	{
		name = name1;
	}
		//读姓名(获取姓名)
	string getname()
	{
		return name;
	}
	
	//年龄读写
		//写
	void setage(int age1)
	{
		if (age1 < 0 || age1>150)//对于写权限,我们可以检测数据的有效性
		{
			age = 0;
			return;
		}
		age = age1;
	}
		//读
	int getage()
	{
		return age;
	}

	//妻子只写
	void setwife(string wife1)
	{
		wife = wife1;
	}
};
int main()
{
	person p1;
	p1.setname("张三");//正确的,可写
	cout << p1.getname() << endl;//正确的,可读

	p1.setage(12);//正确,可写
	cout << p1.getage() << endl;//正确的,可读

	p1.setwife("里斯");
	//cout <<p1.setwife() << endl;//错误的,不可读

	system("pause");
	return 0;
}

练习案例1:设计立方体类

 设计立方体类(cube)

求出立方体的面积和体积

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

 成员函数判断时候只需要传入一个

全局函数判断时候需要传入两个

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

//设计立方体类
class cube
{
public:
	double m_L;//长
	double m_H;//高
	double m_W;//宽

	double mianji()//求出立方体面积
	{
		return (m_L * m_H+ m_L * m_W+ m_W * m_H) * 2;
	}
	double tiji()//求出立方体体积
	{
		return m_L * m_H * m_W;
	}
	double fuzhi(double L, double H, double W)//赋值
	{
		m_L = L;
		m_H = H;
		m_W = W;
		return (m_L, m_H, m_W);
	}
	void panduan(cube& c2)//成员函数判断两个立方体是否相等
	{
		if (m_L== c2.m_L&&m_H==c2.m_H&&m_W==c2.m_W)
		{
			cout << "两个立方体相等" << endl;
		}
		else
		{
			cout << "两个立方体不相等" << endl;
		}
	}

};

//利用全局函数判断两个立方体是否相等
void panduan(cube &c1,cube &c2)//用引用传递,不会拷贝数据
{
	if (c1.fuzhi(1, 2, 3) == c2.fuzhi(1, 2, 3))
	{
		cout << "两个立方体相等" << endl;
	}
	else
	{
		cout << "两个立方体不相等" << endl;
	}
}
int main()
{
	cube c1;
	cube c2;
	c1.fuzhi(1, 2, 3);
	cout << "c1面积" << c1.mianji() << "c1体积" << c1.tiji() << endl;
	c2.fuzhi(1, 2, 3);
	cout << "c2面积" << c2.mianji() << "c2体积" << c2.tiji() << endl;

	panduan(c1, c2);//传入两个做对比

	c1.panduan(c2);//c1的内属性与c2做对比
	system("pause");
	return 0;
}

 练习案例2:点和圆的关系

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

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

class  circle//圆类
{
public:
	double m_R;//半径
	double O[2];//圆心
	double setR(double R)//读写圆的半径
	{
		m_R = R;
		return m_R;
	}
	double setO(double x, double y)//读写圆心
	{
		O[0] = x;
		O[1] = y;
		return(O[0], O[1]);
	}
};
class point//点类
{
public:
	double m_P[2];//点的位置
	double setP(double x, double y)//读写点的位置
	{
		m_P[0] = x;
		m_P[1] = y;
		return(m_P[0], m_P[1]);
	}
	void panduan(circle& c)//成员函数判断点处于圆的位置
	{
		if (m_P[0] * m_P[0] + m_P[1] * m_P[1] > c.m_R * c.m_R)//点到圆的位置大于半径
		{
			cout << "点在圆外" << endl;
		}
		else if (m_P[0] * m_P[0] + m_P[1] * m_P[1] == c.m_R * c.m_R)//点到圆的位置等于半径
		{
			cout << "点在圆上" << endl;
		}
		else//点到圆的位置小于半径
		{
			cout << "点在圆内" << endl;
		}
	}
};
int main()
{
	circle c;//实例化圆
	point p;//实例化点
	c.m_R = 10;//半径
	c.setO(0, 0);//圆心
	p.setP(10, 0);//点的坐标
	p.panduan(c);//比较
	system("pause");
	return 0;
}

 类的拆分

 对于点和圆的关系的代码中,类出现了两个,在代码很大的时候,可以进行类的拆分

1.新建一个头文件,命名为类名.h,如circle.h

 2.新建一个源文件,与头文件同名,如circle.cpp

3. 同様に、別のクラス (ここではポイントクラス: point) は、最初のステップと 2 番目のステップと同じです。

 

 4. ヘッダファイル内にオリジナルのソースファイルを記述可能

 4.2 オブジェクトの初期化とクリーンアップ

  •  私たちが生活の中で購入する電子製品は基本的に工場出荷時の設定がされており、いつか使わなくなると安全性を確保するために私たち自身の情報やデータの一部が削除されます。
  • オブジェクト指向のソースと C++ のライフスタイル。各オブジェクトには初期設定と、オブジェクトが破棄される前にデータをクリーニングするための設定もあります。

 4.2.1 コンストラクターとデストラクター

 オブジェクトの初期化とクリーンアップも 2 つの非常に重要なセキュリティ問題です 

        オブジェクトまたは変数には初期状態がなく、それを使用した場合の結果は不明です

        同様に、オブジェクトまたは変数を使用した後、時間内にクリーンアップしないと、特定のセキュリティ上の問題が発生します。

C++ はコンストラクターとデストラクターを使用して上記の問題を解決し、これら 2 つの関数はコンパイラーによって自動的に呼び出され、オブジェクトの初期化とクリーンアップを完了します。

オブジェクトの初期化とクリーンアップはコンパイラによって強制されるものであるため、構築と破棄を提供しない場合は、コンパイラが提供します。

コンパイラによって提供されるコンストラクタとデストラクタは空の実装です

  • コンストラクター: 主な機能は、オブジェクトの作成時にオブジェクトのメンバー プロパティに値を割り当てることです。コンストラクターは手動で呼び出すことなく、コンパイラーによって自動的に呼び出されます。
  • デストラクター: 主な機能は、オブジェクトが破棄される前にシステムが自動的に呼び出して 、クリーニング作業を実行することです。

 コンストラクターの構文: クラス名 () {}

  • 1. コンストラクター、戻り値なし、void なし
  • 2. 関数名はクラス名と同じです
  • 3. コンストラクターにはパラメーターを含めることができるため、オーバーロードが発生する可能性があります
  • 4. オブジェクトを呼び出すときにプログラムは自動的にコンストラクターを呼び出します。手動で呼び出す必要はなく、一度だけ呼び出されます。

 デストラクター構文: ~class name(){}

  • 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;//栈上的数据,test01执行完毕后,释放这个对象,所以会调用析构
}
int main()
{
	test01();
	//person p;//main函数执行结束才会释放,所以界面不显示析构
	system("pause");
	return 0;
}

4.2.2 コンストラクタの分類と呼び出し

2 つの分類方法:

        パラメータによる分割: パラメータを使用した構築とパラメータを使用しない構築 person()/person(int a)

        種類別:通常施工者とコピー施工者(const者&p)

3 つの呼び出し方法:

        括号法            person p2(10);//有参构造函数
                               person p3(p2);//拷贝构造函数

        显示法            person p5 = person(10);//有参构造
                               person p6=person(p5); // 拷贝构造

        隐式转换法     person p7 = 10;//相当于 person p7 = person(10);
                               person p8 = p7;//拷贝构造

#include<iostream>
using namespace std;

class person
{
public:
	person()//构造函数
	{
		cout << "person的无参构造函数调用" << endl;
	}
	person(int a)//有参构造函数
	{
		age = a;
		cout << "person的有参构造函数调用" << endl;
	}
	person(const person &p)//拷贝构造函数
	{
		//将传入的人身上的所有属性,拷贝到当前对象身上
		age = p.age;
		cout << "person的拷贝函数调用" << endl;
	}
	~person()//析构函数
	{
		cout << "person的析构函数调用" << endl;
	}
	int age;
};
//调用
void test01()
{
	//括号法
	person p1;// 默认构造函数的调用
	person p2(10);//有参构造函数
	person p3(p2);//拷贝构造函数
	//注意事项:
	// 调用默认构造函数的时候,不要加()
	//因为下面这行代码,编译器会认为是函数声明,不会认为在创建对象
	//person p1();
	
	//显示法
	person p4;
	person p5 = person(10);//有参构造
	person p6=person(p5); // 拷贝构造

	person(10);//匿名对象、特点:当前行执行结束后,系统会立即回收匿名对象
	//注意事项2
	// 不要利用拷贝构造函数来初始化匿名对象,编译器会认为person (p5)===person 5;对象声明
	person(p5);

	//隐式转换法
	person p7 = 10;//相当于 person p7 = person(10);
	person p8 = p7;//拷贝构造

}

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

4.2.3拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况

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


class person
{
public:
	person()
	{
		cout << "person默认构造函数调用" << endl;
	}
	person(int age) 
	{
		m_age = age;
		cout << "person有参构造函数调用" << endl;
	}
	person(const person& p)
	{
		m_age = p.m_age;
		cout << "person拷贝构造函数调用" << endl;
	}
	~person()
	{
		cout << "person析构函数调用" << endl;
	}
	int m_age;
};
//使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
	person p1(20);
	person p2(p1);
	cout << "p2年龄" << p2.m_age << endl;
}
//值传递的方式给函数参数传值
void dowork(person p)
{

}
void test02()
{
	person p;
	dowork(p);

}
//以值方式返回局部对象 
person dowork2()
{
	person p5;
	return p5;
}
void test03()
{
	person p6 = dowork2();
}
int main()
{
	test01();
	test02();
	test03();
	system("pause");
	return 0;
}

4.2.4构造函数的调用规则

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

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

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

3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

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

 4.2.5深拷贝与浅拷贝

深浅拷贝是面试经典问题,也是常见的一个坑

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

深拷贝:在堆区重新申请空间。进行拷贝操作

         浅拷贝带来的问题:堆区内存重复释放,可以利用深拷贝进行解决

如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

#include<iostream>
using namespace std;


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& p)
	{
		cout << "person拷贝构造函数调用" << endl;
		m_age = p.m_age;
		//m_height=p.m_height//编译器默认实现的就是这行代码
		//深拷贝操作

		m_height=new int(*p.m_height);
	}
	~person()
	{
		//析构代码,将堆区开辟数据做释放操作
		if (m_height != NULL)
		{
			delete m_height;
			m_height = NULL;
		}
		cout << "person析构函数调用" << endl;
	}
	int m_age;
	int* m_height;
};

void test01()
{
	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()
{
	test01();
	system("pause");
	return 0;
}

4.2.6初始化列表

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

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

#include<iostream>
using namespace std;

class person
{
public:
	//传统初始化操作
	//person(int a, int b, int c)
	//{
	//	m_a = a;
	//	m_b = b;
	//	m_c = c;
	//}
	//初始化列表
	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 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()
{
	test01();
	system("pause");
	return 0;
}

4.2.7类对象作为类成员

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

例如:

class A ()

class B

{

        A  a;

B类中有对象A作为成员,A为对象成员 

那么当创建B对象时。A与B的构造和析构的顺序是谁先谁后? 

  •          当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造是相反的
#include<iostream>
using namespace std;
#include"string"

class phone
{
public:
	phone(string pname)
	{
		p_name = pname;
		cout << "phone的构造函数调用" << endl;
	}
	~phone()
	{
		cout << "phone的析构函数调用" << endl;
	}
	string p_name;//品牌名
};
class person
{
public:
	person(string name, string pname):m_name(name),m_phone(pname)
	{
		cout << "person的构造函数调用" << endl;
	}
	~person()
	{
		cout << "person的析构函数调用" << endl;
	}
	string m_name;//姓名

	phone m_phone;//手机
};
//当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序?
void test01()
{
	person p("张三", "苹果");
	cout << p.m_name <<p.m_phone.p_name << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

 4.2.8静态成员

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

静态成员分为:

  • 静态成员变量
  1.         所有对象共享同一份数据
  2.         在编译阶段分配内存
  3.         类内声明,类外初始化
  • 静态成员函数
  1.         所有对象共享同一个函数
  2.         静态成员函数只能访问静态成员变量
#include<iostream>
using namespace std;

class person
{
public:
	static int m_a;//静态成员变量,类内声明

	//静态成员变量也是有访问权限的
private:
	static int m_b;
};
int person::m_a = 100;//类外初始化,避免全局变量,需要声明person作用域下的变量
int person::m_b = 200;
void test01()
{

	person p;
	cout << p.m_a << endl;//100
	person p2;
	p2.m_a = 200;//p2对象的值改为200
	cout << p.m_a << endl;//p的值也为200,所有对象共享同一份数据
}
void test02()
{
	//因此静态成员变量有两种访问方式
	//1.通过对象进行访问
	person p3;
	cout << p3.m_a << endl;
	//2.通过类名进行访问
	cout << person::m_a << endl;

	//cout << person::m_b << endl;//报错,不可访问,私有作用域
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

静态成员函数 

#include<iostream>
using namespace std;

//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量
class person
{
public:
	static void func()//静态成员函数
	{
		m_b = 100;
		//m_a = 50;//报错,非静态成员变量,静态成员函数无法访问,无法区分是那个对象的属性
		cout << "static void func 的调用" << endl;
	}
	int m_a;
	static int m_b;
	//静态成员函数也是有访问权限的
private:
	static void func1()
	{
		cout << "static void func1的调用" << endl;
	}
};
int person::m_b=0;
//有两种访问方式
void test01()
{
	//1.通过对象调用
	person p;
	p.func();
	//2.通过类名调用
	person::func();

	//person::func1();//报错,无法访问
}
int main()
{
	test01();
	system("pause");
	return 0;
}

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

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

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

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

tip:空类对象占字节大小是1,

#include<iostream>
using namespace std;


class person//空类
{

};
class person1
{
public:
	int m_a;//非静态成员变量  属于类的对象上的
};
class person2
{
	static int m_b;//静态成员变量,不属于类的对象上的
	void func(){}//添加非静态成员函数,不属于类的对象上
	static void func1(){}//添加静态成员函数,不属于类的对象上
};
int person2::m_b = 5;

void test01()
{
	person p;
	//空对象占用的内存空间
	cout << "size of p = " << sizeof(p) << endl;//结果是1,C++编译器会给每个空对象也分配一个字节的空间是为了区分空对象占内存的位置
}
void test02()
{
	person1 p1;
	cout << "size of p1 = " << sizeof(p1) << endl;//结果是4个字节
}
void test03()
{
	person2 p2;
	cout << "size of p2 = " << sizeof(p2) << endl;//仍然是1,静态成员变量不属于对象上
	//添加非静态成员函数后,结果仍然是1,因为成员变量与成员函数是分开存储的
}
int main()
{
	test01();
	test02();
	test03();
	system("pause");
	return 0;
}

4.3.2this指针概念

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

每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码,那么问题是:这一块代码是如何区分那个对象调用自己的呢?

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

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

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

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this

#include<iostream>
using namespace std;

class person
{
public:
	person(int age) 
	{
		//this指向的是被调用的成员函数所属的对象
		this->age=age;
	}
	person& personaddage(person &p)//用引用接受指针,不用引用的话就是返回的值,会调用拷贝构造函数,创建一个新的对象,无法链式
	{
		this->age += p.age;//自身的年龄=别人的年龄+自身的
		return *this;
	}
	int age;
};

//解决名称冲突
void test01()
{
	person p1(18);
	cout << "p1的年龄" << p1.age << endl;
}
//返回对象本身用 *this
void test02()
{
	person p2(10);
	person p3(10);
	p3.personaddage(p2).personaddage(p2);//链式编程思想
	cout << "p3的年龄是:" << p3.age << endl;
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

4.3.3空指针访问成员函数

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

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

#include<iostream>
using namespace std;


class person
{
public:
	void showclassname()
	{
		cout << "this is person chass" << endl;
	}
	void showpersonage()
	{
		if (this == NULL)//加个判断保持健壮性
		{
			return;
		}
		cout << "age=" << m_age << endl;
	}
	int m_age;
};
void test01()
{
	person* p = NULL;
	p->showclassname();
	p->showpersonage();//m_age默认是this->m_age;因为没有确定的对象,是空指针,访问不到里面的属性
}
int main()
{
	test01();
	system("pause");
	return 0;
}

 4.3.4cosnt修饰成员函数 

 常函数

  • 成员函数后加const后我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加换酱紫mutable后,在常函数中依然可以修改

常对象

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

#include <iostream>
using namespace std;

//常函数
class person
{
public:
	//this指针的本质是 指针常量person * const this 指针的指向是不可以修改的
	void showperson()const//该处的const相当于 const person* const this;值也不可修改
	{
		//this = NULL;//this指针的指向是不可修改的
		//this->m_a = 100;//值是可以修改的
		this->m_b = 100; //依然可以修改值
	}
	void func()
	{

	}
	int m_a;
	mutable int m_b;//特殊变量,即使在常函数中也可以修改这个值
};
void test01()
{
	person p;
	p.showperson();
}
//常对象
void test02()
{
	const person p;//在对象前加const,成为常对象
	//p.m_a = 100;//不可修改
	p.m_b = 100;//可以修改

	//常对象只能调用常函数
	p.showperson();//可以调用
	//p.func();//不能调用,因为普通成员函数可以修改属性
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

4.4友元

生活中你的家有客厅(public),有卧室(private)

客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去

但是呢,你也可以允许你的好闺蜜好基友进去

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

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

友元的关键字为 friend

友元的三种实现

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

 1.全局函数做友元

        只需要将全局函数的声明卸载该类中的第一行,然后加上友元关键字friend

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

class building
{
	//goodgay全局函数是building的好朋友,可以访问building的私有成员
	friend void goodgay(building* building);
public:
	building():m_settingroom("客厅"),m_bedroom("卧室")
	{
	}
public:
	string m_settingroom;//客厅

private:
	string m_bedroom;//卧室
};
void goodgay(building *building)
{
	cout << "好基友的全局函数 正在访问:" << building->m_settingroom << endl;
	cout << "好基友的全局函数 正在访问:" << building->m_bedroom << endl;
}
void test01()
{
	building building;
	goodgay(&building);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

2.类做友元

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

class building;//声明有个building
class goodgay
{
public:
	goodgay();
	void visit();//参观函数,访问building中的属性
	building* building1;
};
class building
{
	//goodgay类是building类的好朋友,可以访问私有成员
	friend class goodgay;
public:
	building();
public:
	string m_sittingroom;
private:
	string m_bedroom;
};
//类外写成员函数
building::building():m_sittingroom("客厅"),m_bedroom("卧室")
{
}
goodgay::goodgay():building1(new building)
{
}
void goodgay::visit()
{
	cout << "好基友类正在访问:" << building1->m_sittingroom << endl;
	cout << "好基友类正在访问:" << building1->m_bedroom << endl;
}
void test01()
{
	goodgay hh;
	hh.visit();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

3.成员函数做友元

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

class building;//声明有个building
class goodgay
{
public:
	goodgay();
	void visit();//参观函数,访问building中的私有成员
	void visit2();//参观函数,不能访问building中的私有成员
	building* building1;
};
class building
{
	//goodgay类下的visit函数可以访问私有成员
	friend void goodgay::visit();
public:
	building();
public:
	string m_sittingroom;
private:
	string m_bedroom;
};
//类外写成员函数
building::building() :m_sittingroom("客厅"), m_bedroom("卧室")
{
}
goodgay::goodgay() :building1(new building)
{
}
void goodgay::visit()
{
	cout << "visit正在访问:" << building1->m_sittingroom << endl;
	cout << "visit正在访问:" << building1->m_bedroom << endl;
}
void goodgay::visit2()
{
	cout << "visit2正在访问:" << building1->m_sittingroom << endl;
	cout << "visit2正在访问:" << building1->m_bedroom << endl;
}
void test01()
{
	goodgay hh;
	hh.visit();
	hh.visit2();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

4.5运算符重载

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

4.5.1加号运算符重载

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

    1.成员函数重载+号
    person operator+(person& p)
    {
        person temp;
        temp.m_A = this->m_A + p.m_A;
        temp.m_B = this->m_B + p.m_B;
        return temp;
    }

 2. グローバル関数オーバーロード + 数値
person 演算子+(person& p1, person& p2)
{     person temp;     temp.m_A = p1.m_A + p2.m_A;     temp.m_B = p1.m_B + p2.m_B;     return temp; }




3. 演算子のオーバーロード、関数のオーバーロードも発生する可能性あり
personoperator+(person& p1, int num)
{     person temp;     temp.m_A = p1.m_A + num;     temp.m_B = p1.m_B +num;     return temp; }




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

//加号运算符重载
class person
{
public:
	//1.成员函数重载+号
	//person operator+(person& p)
	//{
	//	person temp;
	//	temp.m_A = this->m_A + p.m_A;
	//	temp.m_B = this->m_B + p.m_B;
	//	return temp;
	//}
	int m_A;
	int m_B;
};
//2.全局函数重载+号
person operator+(person& p1, person& p2)
{
	person temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
//函数重载的版本
person operator+(person& p1, int num)
{
	person temp;
	temp.m_A = p1.m_A + num;
	temp.m_B = p1.m_B +num;
	return temp;
}
void test01()
{
	person p1;
	p1.m_A = 10;
	p1.m_B = 10;

	person p2;
	p2.m_A = 10;
	p2.m_B = 10;

	//成员函数重载本质调用
	//person p3 = p1.operator+(p2);

	//全局函数重载的本质调用
	//person p3 = operator+  (p1, p2);

	person p3 = p1 + p2;//两种均可以简化成此种形式

	//运算符重载,也可以发生函数重载
	person p4 = p1 + 100;//person+int

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

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

要約 1: 組み込みデータ型の式の演算子を変更することは不可能

要約 2: 演算子のオーバーロードを乱用しないでください

4.5.2 左シフト演算子のオーバーロード

役割: カスタム データ型を出力できます

     1. メンバー関数を使用して左シフト演算子をオーバーロードします
。     通常は、左シフト演算子をメンバー関数でオーバーロードしないでください。これは、cout を左上で実現できないためです。 void
    Operator<<(cout){} //p.operator<<(cout) == p<<cout

2. 左シフト演算子 cout をオーバーロードするには、グローバル関数のみを使用できます
        。 ostream に属します: 出力ストリーム タイプ
ostream &operator<<(ostream &cout,person p)//Essence:operator<<(cout,p)==cout< <p;
{     cout << "m_A=" << p.m_A << "\nm_B=" << p.m_B;     return cout;// チェーン プログラミングを続行するには cout タイプを返す必要があります}


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

class person
{
	friend ostream& operator<<(ostream& cout, person p);
	friend void test01();
	//利用成员函数重载左移运算符
	//通常不用成员函数重载左移运算符,因为无法实现cout在左侧
	//void operator<<(cout){}  //p.operator<<(cout)==p<<cout

	int m_A;
	int m_B;

};
//只能利用全局函数重载左移运算符
//cout属于ostream:输出流类型
ostream &operator<<(ostream &cout,person p)//本质:operator<<(cout,p)==cout<<p;
{
	cout << "m_A=" << p.m_A << "\nm_B=" << p.m_B;
	return cout;//需要返回cout类型,才能继续链式编程
}
void test01()
{
	person p;
	p.m_A = 10;
	p.m_B = 10;
	cout << p << endl;//
}
int main()
{
	test01();
	system("pause");
	return 0;
}

概要: 左シフト演算子をフレンドでオーバーロードすると、カスタム データ型の出力を実現できます

4.5.3 インクリメント演算子のオーバーロード

役割: インクリメント演算子をオーバーロードすることで独自のプラスチック データを実現します 

事前インクリメント: 返されるのは参照です

myinteger&operator++() //戻り参照は常にデータをインクリメントすることです
    {         m_num = this->m_num++;

        return *this; //this はそれ自体を指し、*this は参照を解除します
    }

ポストインクリメント: 値を返します

    myinteger operator++(int)//int 占位参数,可以用于区分前置和后置递增,后置递增返回的是值而非引用
    {
        myinteger temp = *this; 
       //先 记录当时结果
        m_num++;        //后  递增
        return temp;        //最后,将记录的结果返回
    } 

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

class myinteger
{
	friend ostream& operator<<(ostream& cout, myinteger myint);
public:
	myinteger():m_num(0){}
	//重载前置++运算符
	myinteger& operator++()//返回引用是为了一直对一个数据进行递增操作
	{
		m_num = this->m_num++;
		return *this;//this指向自身,*this解引用
	}
	//重载后置++运算符
	myinteger operator++(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 test01()
{
	myinteger myint;
	cout << myint << endl;
	cout << ++(++myint) << endl;

	cout << myint << endl;
}
void test02()
{
	myinteger myint;
	cout<< myint++ << endl;
	cout << myint << endl;
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

 4.5.4赋值运算符重载

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

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

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

#include <iostream>
using namespace std;

class person
{
public:
	person(int age)
	{
		m_age = new int(age);//创建到堆区
	}
	~person()//堆区数据由程序员开辟释放,在析构时释放
	{
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
	}
	//重载  赋值运算符
	person& operator=(person& 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 test01()
{
	person p1(18);
	person p2(20);
	person p3(30);
	p2 = p1;//赋值运算操作
	p3 = p2 = p1;//连等赋值
	cout << "p1的年龄时:" << *p1.m_age<<endl;
	cout << "p2的年龄时:" << *p2.m_age << endl;
	cout << "p3的年龄时:" << *p3.m_age << endl;
}
int main()
{
	test01();
	//int a = 10;
	//int b = 20;
	//int c = 30;
	//c = b = a;
	//cout << "a=" << a << "b=" << b << "c=" << c << endl;//内置赋值允许连等
	system("pause");
	return 0;
}

 4.5.5关系运算符重载

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

#include <iostream>
using namespace std;

class person
{
public:
	person(string name,int age):name(name),age(age){}
	//重载关系运算符==
	bool operator==(person &p)
	{
		if (this->name == p.name && this->age == p.age)
		{
			return true;
		}
		return false;
	}
	//重载!=
	bool operator!=(person& p)
	{
		if (this->name != p.name || this->age != p.age)
		{
			return true;
		}
		return false;
	}
	string name;
	int age;
};
void test01()
{
	person p1("张三", 18);
	person p2("张三", 8);
	if (p1 == p2)
	{
		cout << "p1,p2相等" << endl;
	}
	else 
	{
		cout << "p1,p2不相等" << endl;
	}
	if (p1 != p2)
	{
		cout << "p1,p2不相等" << endl;
	}
	else
	{
		cout << "p1,p2相等" << endl;
	}
}
int main()
{
	test01();
	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");
}
//仿函数非常灵活,没有固定写法
class myadd//加法类
{
public:
	int operator()(int num1,int num2) 
	{
		return num1 + num2;
	}
};
void test02()
{
	myadd add;
	int a=add(1, 2);
	cout << a << endl;
	//匿名函数对象
	cout << myadd()(100, 100) << endl;
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

4.6继承

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

 下级别成员除了拥有上级别的共性,还具有自己的特性,这时就可以考虑继承,减少重复代码

4.6.1继承的基本语法 

 好处:减少重复代码

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

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

子类(派生类)中的成员,包含两大部分:

  • 一类是从父类继承的,一类是自己增加的
  • 继承的表现共性,自己的表现个性 
#include <iostream>
using namespace std;

//继承实现页面
class basepage//公共页面
{
public:
	void header()
	{
		cout << "首页。公开课。登录。。。" << endl;
	}
	void footer()
	{
		cout << "交流合作。站内地图。。。" << endl;
	}
	void left()
	{
		cout << "java,python,c++。。。" << endl;
	}
};
class python :public basepage//pyhton继承
{
public:
	void content()
	{
		cout << "python学习" << endl;
	}
};
class java :public basepage//java继承
{
public:
	void content()
	{
		cout << "java学习" << endl;
	}
};
class CPP :public basepage//C++继承
{
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 << "这是C++的内容" << endl;
	CPP c;
	c.header();
	c.footer();
	c.left();
	c.content();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

4.6.2继承方式

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

继承方式一共有三种:

  1. 公共继承:父公共,子公共,父保护,子保护,父隐私,子不可访问
  2. 保护继承:父公共,子保护,父保护,子保护,父隐私,子不可访问
  3. 私有继承 :父公共,子隐私,父保护,子隐私,父隐私,子不可访问

 4.6.3继承中的对象模型

質問?親クラスから継承されたメンバーのうち、サブクラス オブジェクトに属するメンバーはどれですか? 

 以下のコードからわかるように、親クラスのすべての非静的メンバーはサブクラスに継承され、プライベート メンバーも継承されますが、アクセスできず、コンパイラによって隠蔽されます。

#include <iostream>
using namespace std;

class base
{
public:
	int m_a;//4字节
protected:
	int m_b;//4字节
private:
	int m_c;//4字节

};
class son :public base
{
public:
	int m_d;//4字节
};
void test01()
{
	cout << "size of son=" << sizeof(son) << endl;//16字节
}
int main()
{
	test01();
	system("pause");
	return 0;
}

オブジェクト モデルは、開発者コマンド プロンプト ツールを使用して表示することもできます。

 vs の開発者コマンドラインを開きます。

ファイルのパスにジャンプします

cl /d1 reportSingleClassLayout クラス名 ファイル名を入力します

4.6.4 相続における建設と破壊の順序

サブクラスが親クラスを継承した後、サブクラスのオブジェクトが作成されると、親クラスのコンストラクターも呼び出されます。

質問?親クラスとサブクラスの構築と破壊の順序はどちらが先ですか?

 答え: 最初に基地を建設し、次に息子を建設し、次に息子を破壊し、最後に基地を破壊します。

#include <iostream>
using namespace std;

class base
{
public:
	base()
	{
		cout << "base构造" << endl;
	}
	~base()
	{
		cout << "base析构" << endl;
	}
};
class son :public base
{
public:
	son()
	{
		cout << "son构造" << endl;
	}
	~son()
	{
		cout << "son析构"<<endl;
	}
};
void test01()
{
	son s;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

4.6.5 同名のメンバーを継承する方法

 質問: サブクラスと親クラスに同じ名前のメンバーが存在する場合、サブクラス オブジェクトを通じてサブクラスまたは親クラスの同じ名前のデータにアクセスするにはどうすればよいですか?

  • サブクラスの同じ名前のメンバーにアクセスするには、直接アクセスできます。
  • 親クラスと同じ名前のメンバーにアクセスするには、スコープを追加する必要があります

    cout << s.m_a << endl; //同じ名前の息子、直接アクセス
    cout << s.base::m_a << endl; //同じ名前の親クラス、スコープを追加

    s.func(); //サブクラスの同名の関数、直接アクセス
    s.base::func(); //親クラスの同名の関数、スコープを追加

    //s.func(1); //サブクラスに親クラスと同じ名前のメンバー関数がある場合、サブクラスのメンバーは親クラスの同じ名前の関数をすべて非表示にします
    s.base ::func(1); // スコープを追加、アクセスのみ可能 

要約:

  • サブクラス オブジェクトは、同じ名前のサブクラスのメンバーに直接アクセスできます。
  • サブクラス オブジェクトとスコープは、親クラスの同じ名前のメンバーにアクセスできます。
  • サブクラスと親クラスに同じ名前のメンバー関数がある場合、サブクラスは親クラスの同じ名前のメンバー関数を非表示にし、スコープにのみアクセスできます。 
#include <iostream>
using namespace std;

class base
{
public:
	base()
	{
		m_a = 100;
	}
	void func()//父类同名无参函数
	{
		cout << "base的func" << endl;
	}
	void func(int a)//父类同名有参函数
	{
		cout << "base的func(int a)" << endl;
	}
	int m_a;
};
class son :public base
{
public:
	son()
	{
		m_a = 200;
	}
	void func()//子类同名无参函数
	{
		cout << "son的func" << endl;
	}
	int m_a;

};
void test01()
{
	son s;
	cout << s.m_a << endl;//son的同名,直接访问
	cout << s.base::m_a << endl;//父类同名,加作用域

	s.func();//子类的同名函数,直接访问
	s.base::func();//父类的同名函数,加作用域

	//s.func(1);//子类出现和父类同名的成员函数,那子类的成员会隐藏掉父类的所有同名的函数
	s.base::func(1);//加作用域,才可访问
}
int main()
{
	test01();
	system("pause");
	return 0;
}

4.6.6 同名静的メンバの継承処理方法

问题?继承中同名的静态成员在子类对象上如何进行访问? 

 静态成员和非静态成员出现同名,处理方式一致

 可以通过对象访问,也可以通过类名方式访问

s.m_a;                        对象访问

s.base::m_a;

s.func();

s.base::func();

son::m_a;                     类名访问

son::base::m_a;

son::func();

son::base::func();

  • 子类对象可以直接访问到子类同名成员
  • 子类对象加作用域可以访问到父类同名成员
  • 当子类和父类有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域才可访问 

 4.6.7多继承语法

 C++允许一个类继承多个类

语法:class 子类 : 继承方式 父类1,继承方式  父类2...

 多继承可能会引发父类中有同名成员出现,需要加作用域区分

C++实际开发中不建议用多继承

 4.6.8菱形继承

 菱形继承概念:

  • 两个派生类继承同一个基类
  • 又有某个类同时继承这两个派生类
  • 这种继承被称为菱形继承或钻石继承

菱形继承会导致子类继承两份相同的数据 ,导致资源浪费以及毫无意义

利用虚继承可以解决菱形继承的问题
继承之前加上关键字 virtual
base 类称为虚基类 

#include <iostream>
using namespace std;

class base//爷爷类
{
public:
	int money;
};
//利用虚继承可以解决菱形继承的问题
//继承之前加上关键字 virtual
//base 类称为虚基类
class son1 :virtual public base//大儿子
{
};
class son2 :virtual public base//二儿子
{
};
class sonson :public son1, public son2//孙子
{
};
void test01()
{
	sonson ss;
	//ss.money = 5000;//不明确
	ss.son1::money = 1000;
	ss.son2::money = 2000;
	cout << "ss.son1::money=="<<ss.son1::money << endl;
	cout << "ss.son2::money=="<<ss.son2::money << endl;
	cout <<"ss.money=="<< ss.money << endl;
	cout << ss.base::money << endl;//2000
}
int main()
{
	test01();
	system("pause");
	return 0;
}

4.7多态

 4.7.1多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态 

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址 

 动态多态满足条件

  1. 有继承关系
  2. 子类要重写父类的虚函数

动态多态使用 

  • 父类的指针或者引用指向子类对象 

重写:函数返回值类型,函数名 参数列表 完全一致称为重写 

多态优点:

  1. 代码组织结构清晰
  2. 可读性强
  3. 利于前期和后期的扩展及维护 

 

#include <iostream>
using namespace std;

class Animal
{
public:
	virtual void speak()//虚函数
	{
		cout << "动物说话" << endl;
	}
};

class cat:public Animal
{
public:
	void speak()
	{
		cout<< "猫在说话" << endl;
	}
};
class dog :public Animal
{
public:
	void speak()
	{
		cout << "狗在说话" << endl;
	}
};
//地址早绑定,编译阶段就确定地址
//若想猫说,需要地址晚绑定,只需加virtual,虚函数
void dospeak(Animal &animal)//父类引用指向子类对象Animal &animal=cat
{
	animal.speak();
}
void test01()
{
	cat c;
	dospeak(c);
	dog d;
	dospeak(d);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

多态原理剖析

父类的内部结构被子类继承(内有虚函数指针,虚函数表)

当子类重写父类的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址,父类的不改变

当父类的指针或者引用指向 子类对象的时候,发生多态

	cout << "size of animal==" << sizeof(Animal) << endl;//加virtual为8字节,不加为1字节,多了一个指针

 4.7.2多态案例一计算器类

 

4.7.3纯虚函数和抽象类 

在多态中,通常父类中虚函数的实现时毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0; 

 当类中有了纯虚函数,这个类也称为抽象类

 抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

 

 4.7.5虚析构和纯虚析构

 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构护着纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构的区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:virtual ~类名(){} 

纯虚析构语法:virtual ~类名() = 0; 

类名::~类名(){}

总结:

  •  1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  • 2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  • 3.拥有纯虚析构函数的类也属于抽象类

5.文件操作 

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放

通过文件可以将数据持久化 

C++中对文件操作需要包含头文件<fstream>

 文件类型分为两种:

  • 1. テキスト ファイル - ファイルは ASCII テキストとしてコンピュータに保存されます。
  • 2. バイナリ ファイル - ファイルはバイナリ形式のテキストでコンピュータに保存され、通常はユーザーが直接読み取ることはできません。

次の 3 つのカテゴリのファイルが操作されます。

  1. ofstream: 書き込み操作
  2. ifstream: 読み取り操作
  3. fstream: 読み取りおよび書き込み操作 

5.1 文書本文 

 5.1.1 ファイルの書き込み

ファイルを書き込む手順は次のとおりです。

  • 1.ヘッダー ファイル  #include<fstream>をインクルードします。
  • 2.ストリーム オブジェクトのストリーム オブジェクトを作成します  。
  • 3.ファイルを開きます  ofs.open("ファイルパス", open メソッド);
  • 4.書き込みデータ <<「書き込みデータ」;
  • 5.ファイル  ofs.close() を閉じます。

 ファイルを開く方法:

オープンメソッド 説明
ios::で 読み取り用にファイルを開く
ios::out 書き込み用にファイルを開く
ios::食べた 初期位置: ファイルの終わり
ios::アプリ ファイルを書き込むための追加
ios::トランク ファイルが存在する場合は、まずそれを削除してから作成します
ios::バイナリ バイナリモード

注: ファイルを開くメソッドは | 演算子と組み合わせて使用​​できます。

例: バイナリ モードでファイルを書き込むios::binary | ios::out

#include<iostream>
using namespace std;
#include<fstream>//1

void test01()
{
	ofstream ofs;//2

	ofs.open("test.txt",ios::out );//3

	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;//4

	ofs.close();//5
}
int main()
{
	test01();
	system("pause");
	return 0;
}

 要約:

  • ファイル操作にはヘッダー ファイル fstream が含まれている必要があります
  • 読み取りファイルは ofstream または fstream クラスを使用できます
  • ファイルを開くときに、操作ファイルのパスと開く方法を指定することを選択します
  • データをファイルに書き込むには << を使用します
  • 操作が完了したら、ファイルを閉じます

5.1.2 ファイルの読み込み

ファイルを読み取る手順は次のとおりです。

  • 1.ヘッダー ファイル  #include<fstream>をインクルードします。
  • 2.ストリーム オブジェクト  ifstream ifs を作成します。
  • 3.ファイルを開き、ファイルが正常にオープンしたかどうかを判定します。  ifs.open("ファイルパス", open メソッド);
  • 4. 4 つの方法でデータを読み取る
  • 5.ファイルを閉じます  ifs.close();

 

#include<iostream>
using namespace std;
#include<fstream>//1
#include<string>
void test01()
{
	ifstream ifs;//2

	ifs.open("test.txt", ios::in);//3
	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}

	char buf[1024] = { 0 };//第一种方式
	while (ifs >> buf)
	{
		cout << buf << endl;
	}
	 
	char buf[1024] = { 0 };//第二种方式
	while (ifs.getline(buf, sizeof(buf)))
	{
		cout << buf << endl;
	}

	string buf;//第三种方式
	while (getline(ifs, buf))
	{
		cout << buf << endl;
	}

	char c;//第四种方式
	while ((c=ifs.get())!=EOF)//EOF : end  of  file
	{
		cout << c ;
	}
	ifs.close();//5
}
int main()
{
	test01();
	system("pause");
	return 0;
}

要約:

  • 読み取りファイルは ifstream または fstream クラスを使用できます
  • is_open 関数を使用して、ファイルが正常に開かれたかどうかを確認します。
  • close ファイルを閉じます

 5.2 バイナリ

バイナリモードでファイルを読み書きする

開くメソッドはios::binary として指定する必要があります。

5.2.1 ファイルの書き込み 

バイナリ モードでのファイルの書き込みでは、主にストリーム オブジェクトを使用してメンバー関数 write を呼び出します。

関数プロトタイプ: ostream& write (const *buffer, int len);

パラメータの説明: 文字ポインタ バッファはメモリ内の記憶領域を指し、len は読み書きされるバイト数です。 

 

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

class person
{
public:
	char m_name[64];
	int m_age;
};
void test01()
{
	ofstream ofs;

	ofs.open("person.txt", ios::out | ios::binary);//二进制

	person p={ "张三", 18 };
	ofs.write((const char *)&p, sizeof(person));

	ofs.close();

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

5.2.2 ファイルの読み込み

 バイナリ モードでのファイルの読み取りでは、主にストリーム オブジェクトを使用してメンバー関数 read を呼び出します。

関数プロトタイプ: istream& read(char *buffer, int len);

 パラメータの説明: 文字ポインタ バッファはメモリ内の記憶領域を指し、len は読み書きされるバイト数です。

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

class person
{
public:
	char m_name[64];
	int m_age;
};
void test01()
{
	ifstream ifs;

	ifs.open("person.txt", ios::in | ios::binary);//二进制
	if (!ifs.is_open())
	{
		cout << "文件打开失败";
		return;
	}

	person p;
	ifs.read((char*)&p, sizeof(person));

	cout << "姓名" << p.m_name << "  年龄" << p.m_age << endl;
	ifs.close();

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

 

おすすめ

転載: blog.csdn.net/weixin_58176527/article/details/127426074