C++核心编程


typora-copy-images-to: photos

C++核心编程

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

1、内存分区模型

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

(1)代码区:存放函数体的二进制代码,由操作系统进行管理(存放代码,操作系统管理)

(2)全局区:存放全局变量、静态变量以及常量(存放全局、静态以及常量)

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

(4)堆区:由程序员分配与释放,若程序员不释放,程序结束时由操作系统回收

内存分区意义:(不同区域,数据不同,周期不同,编程灵活)

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.1 程序运行前

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

代码区:只读、共享

存放CPU执行的机器指令

特点:

(1)代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可(每次执行,执行的是内存中的那份代码,不必要继续把代码加载到程序中)

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

全局区:

(1)全局变量与静态变量存放在此

(2)全局区还包含了常量区、字符串常量和其他常量存放在此

#include <iostream>
using namespace std;

//全局变量
int ga = 10;
int gb = 10;

//const修饰的全局变量
const int gc = 10;

int main() {
	//全局区

	//全局变量、静态变量、常量

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

	cout << "全局变量ga的地址为:" << (int)&ga << endl;
	cout << "全局变量gb的地址为:" << (int)&gb << endl;


	//静态变量
	static int sa = 10;
	static int sb = 10;
	cout << "静态变量sa的地址为:" << (int)&sa << endl;
	cout << "静态变量sb的地址为:" << (int)&sb << endl;

	//常量
	//字符串常量
	cout << "字符串常量的地址为:"<<(int)&"hello world" << endl;

	//const修饰的变量
	//const修饰的全局变量
	cout << "const修饰的全局变量的地址为:" << (int)&gc<< endl;
	//const修饰的局部变量
	const int cgc = 10;
	cout << "const修饰的局部变量的地址为:" << (int)&cgc << endl;
	system("pause");
	return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

PS:该区域的数据在程序结束之后由操作系统释放

总结:

(1)c++中在程序运行前分为全局区与代码区

(2)代码区的特点是共享和只读

(3)全局区中存放全局变量、静态变量、常量

(4)常量区中存放const修饰的全局常量 和 字符串常量

1.2 程序运行后
栈区:

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

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

#include<iostream>
using namespace std;

/*
栈区注意事项:
(1)不要返回局部变量的地址
(2)栈区的数据由编译器管理开辟与释放
*/

int*  func(int b) {//形参数据也会放在栈区

	b = 100;
	int a = 10;//局部变量,存放在栈区,栈区的数据在函数执行完后自动释放

	return &a;//返回局部变量的地址
}

int main() {
	int *p = func(1);
	cout << *p <<endl;
	cout << *p << endl;

	system("pause");
	return 0;
}
堆区:

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

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

int *p=new int(10);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<iostream>
using namespace std;

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

int main() {

	//在堆区开辟数据
	int* p = func();
	cout<<*p<<endl;


	system("pause");
	return 0;
}

总结:

(1)堆区数据由程序员管理开辟与释放

(2)堆区数据利用new关键字进行开辟内存

1.3 new操作符

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

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

语法:

new 数据类型

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

#include<iostream>
using namespace std;
//利用new创建的数据,会返回该数据对应的类型的指针。
//1.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;//内存已经被释放,再次访问是非法操作
}
/*
int *p=new int[10];//返回int类型的数组
*/
//2.在堆区利用new开辟数组
void test02() {
	//创建10整型数据的数组,在堆区
	int* arr = new int[10];//10代表数组有10元素
	for (int i = 0; i < 10; i++) {
		arr[i] = i + 100;//给10个元素赋值,100~109
	}
	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");
}
2.2 引用注意事项

(1)引用必须初始化

int &b;//这是错误的

(2)引用在初始化后就不可以改变(类似于int const *p(指针指向不可以更改))

#include<iostream>
using namespace std;

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

	//2、引用在初始化后,不可以更改
	int c = 20;
	b = c;//赋值操作,不是引用更改
	system("pause");
}
2.3 引用做函数参数

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

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<iostream>
using namespace std;
//交换函数
//1、值传递
void mySwap01(int a, int b) {
	int temp = a;
	a = b;
	b = temp;

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	//a=20,b=10
}
//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 a = 10;
	int b = 20;
	//mySwap01(a, b);//(1)值传递,形参不会修饰实参
	//mySwap02(&a, &b);//(2)地址传递,形参会修饰实参
	mySwap03(a, b);//(3)引用传递,形参会修饰实参
	
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	//a=10,b=20

	system("pause");
}

总结:通过引用参数产生的效果同地址传递是一样的,引用的语法更简单。

2.4 引用做函数返回值
2.5 引用的本质

本质:引用的本质在c++内部实现 是一个指针常量(指针常量不可更改,因此引用不可更改)(类似于int const *p)

实例:

void func(int &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);
  return 0;
}
2.6 常量引用
#include<iostream>
using namespace std;

int main() {
	//常量引用
	//使用场景:用来修饰形参,防止误操作

	//int a = 10;
	//加上const之后,编译器将代码修改 int temp=10;const int &ref=temp;

	const int& ref = 10;//引用必须引用一块合法的内存空间
	ref = 20;//加入const之后变为只读,不可以修改

	system("pause");
	return 0;
}

3、函数提高

3.1 函数的默认参数

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

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

示例:

#include<iostream>

using namespace std;

//函数默认参数
//如果传入数据,就用自己的数据,如果没有就用默认值
//语法:返回值类型 函数名(形参=默认值){}
//注意事项:
//(1)如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
//(2)如果函数声明有默认参数,函数实现不能带有默认参数(声明和实现只能有一个带默认参数)
int func2(int a = 10, int b = 20);//函数声明
int func2(int a = 10, int b = 20) {//函数实现
	return a + b;
}
int func(int a, int b=20, int c=30) {
	return a + b + c;
}

int main() {

	cout << func(10,30) << endl;
	system("pause");
	return 0;
}
3.2 函数占位参数

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

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

现阶段函数的占位参数意义不大,但是后面的课程会用到该技术

示例:

#include<iostream>
#include "01默认函数参数.cpp"

using namespace std;

//占位参数(现阶段用不到,后续会用)
//占位参数可以有默认参数
void fun(int a,int =10) {
	cout<<"this is func"<<a << endl;
}

int main() {

	fun(10, 10);
	
	system("pause");
	return 0;
}
3.3 函数重载
1、函数重载概述

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

函数重载满足条件:

(1)同一个作用域下

(2)函数名称相同

(3)函数参数类型不同/个数不同/顺序不同

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

示例:

#include<iostream>

using namespace std;

//函数重载,可以让函数名相同,提高复用性

/*
函数重载的满足条件
(1)同一个作用域下
(2)函数名称一样
(3)函数参数类型不同/个数不同/顺序不同
*/
void func() {
	cout<<"调用func函数!" << endl;
}

void func(int a) {
	cout << "调用func(int a)函数" << endl;
}
void func(int a, double b) {
	cout<<"调用func(int a,double b)函数" << endl;
}

int main() {

	func();
	system("pause");
	return 0;
}
2、函数重载注意事项

(1)引用作为重载条件

(2)函数重载碰到函数默认参数

#include<iostream>
using namespace std;
/*
函数重载注意事项:
(1)引用作为重载条件
(2)函数重载碰到函数默认参数
*/
void fun(int &a) {
	cout<<"fun(int &a)的调用" << endl;
}
void fun(const int& a) {
	cout << "fun(const int &a)的调用" << endl;
}
int main() {
	int a = 10;
	fun(a);
	const int b = 20;
	fun(b);
	system("pause");
	return 0;
}

4、类和对象

c++面向对象三大特征:封装,继承,多态

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

例如:

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

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

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

4.1封装
1、封装的意义

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

封装的意义:

(1)将属性和行为作为一个整体,表现生活中的事物

(2)将属性和行为加以权限控制

封装的意义1:

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

语法:

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

示例1:设计一个圆类,求圆的周长

#include<iostream>
using namespace std;

const double PI = 3.14;
/*
设计一个圆类,并求其周长
公式:d=2*PI*r

*/
//class 代表设计一个类,class后紧跟类名
class Circle{
	//访问权限(公共)
public:
		//属性:变量
		int m_r;
	    //行为(获取圆的周长):函数
		double calculateZC() {
			return 2 * PI * m_r;
		}
};
int main() {
	//通过圆类创造具体的圆(对象)
	Circle c1;
	c1.m_r = 10;
	//
	cout<<"圆的周长为:"<<c1.calculateZC() << endl;
	system("pause");
	return 0;
}

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

#include<iostream>
using namespace std;

class Student{
  public:
  int id;
  string name;
  void Add(int id,string name){
    id=id;
    name=name;
  }
  void Show(){
    cout<<"学号+姓名:"<<id<<"+"<<name<<endl;
  }
}
int main(){
  Student s;
  s.Add(1,"zhangsan");
  s.Show();
  return 0;
}
封装意义2:

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

三种访问权限:
(1)public:公共权限 成员类内可以访问,类外可以访问

(2)protected:保护权限 成员类内可以访问,类外不可以访问

(3)private:保护权限 成员类内可以访问,类外不可以访问

#include<iostream>
using namespace std;
/*
三种访问权限:
(1)public:公共权限          成员类内可以访问,类外可以访问
(2)protected:保护权限   成员类内可以访问,类外不可以访问(儿子可以访问父亲中的保护内容)
(3)private:保护权限        成员类内可以访问,类外不可以访问(儿子不可以访问父亲的私有内容)
*/
class Person {
	//公共权限
public:
	string m_Name;
	//保护权限
protected:
	string m_Car;
	//私有权限(默认私有)
private:
	int m_Password;

public:
	void func() {
		m_Name = "张三";
		m_Car = "拖拉机";
		m_Password = 123456;
	}
};
int main() {

	Person p1;
	p1.m_Name = "李四";
	//p1.m_Car = "奔驰";  保护权限类容,类外不可以访问
	p1.func();
	system("pause");
	return 0;
}
4.2 struct和class的区别

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

区别:

struct 默认权限为公共

class 默认权限为私有


/*
在c++中,struct和class的唯一区别就在于默认的访问权限不同
区别:
struct  默认权限为公共
class  默认权限为私有
*/
class C1 {
	int m_A;//默认私有
};
struct C2 {
	int m_A;//默认公共
};
int main() {


	C1 c1;
	C2 c2;
	c2.m_A = 100;//c1不可以访问m_A
	system("pause");
	return 0;
}
4.3 成员属性设置为私有
优点:

(1)将成员属性设置为私有,可以自己控制读写权限

(2)对于写权限,我们可以检测数据的有效性

示例:

#include<iostream>
using namespace std;
#include<string.h>
/*
##### 成员属性设置为私有

###### 优点:

(1)将成员属性设置为私有,可以自己控制读写权限

(2)对于写权限,我们可以检测数据的有效性
*/

class Person {
public:
	void setName(string name) {
		m_Name = name;
	}
	string getName() {
		return m_Name;
	}

	int getAge() {
		m_Age = 10;
		return m_Age;
	}
	void setLover(string name) {
      
		m_Lover = name;
	}

private:
  //访问权限,自己设置
	string m_Name;//可读可写
	int m_Age;//只读
	string m_Lover;//只写
};

int main() {
	Person p;
	p.setName("zhangsan");
	cout<<"姓名为:"<<p.getName() << endl;
	cout<<"年龄为:"<<p.getAge() << endl;
	p.setLover("lisi");
	system("pause");
	return 0;
}

练习案例:

设计立方体类Cube{m_H,m_L,m_W}

求出立方体的面积与体积

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

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

class Cube {
private:
	int m_L;//长
	int m_W;//宽
	int m_H;//高
public:
	//设置长、宽、高
	void setData(int l,int w,int h) {
		m_L = l;
		m_W = w;
		m_H = h;
	}
	//获取面积
	int getMianji() {
		return 2 * (m_L * m_W + m_L * m_H + m_H * m_W);
	}
	//获取体积
	int getTiji() {
		return m_H * m_L * m_W;
	}
};
bool isSame(Cube& c1, Cube& c2) {//不用 Cube c1,因为值传递需要另外的内存空间,引用不需要
	if (c1.getTiji() == c2.getTiji()) {
		return true;
	}
	else {
		return false;
	}
}

int main() {
	
	cout<<"*************************" << endl;
	Cube c1;
	Cube c2;
	bool ret = isSame(c1, c2);
	if (ret) {
		cout<<"c1和c2是相等的" << endl;
	}
	else {
		cout << "c1和c2是bu相等的" << endl;
	}
	c1.setData(10, 8, 9);
	c2.setData(8, 9, 10);
	cout<<"c1面积为:"<<c1.getMianji() << endl;
	cout << "c1体积为:" << c1.getTiji() << endl;
	system("pause");
	return 0;
}
4.2 对象的初始化和清理

(1)生活中我们买的电子产品基本都会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全

(2)c++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置

4.2.1 构造函数和析构函数

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

(1)一个对象或者变量没有初始状态,对其使用后果未知

(2)同样的使用完一个对象或者变量之后,没有及时清理,也会造成一些安全问题

c++使用了构造函数与析构函数解决上述问题,这两个函数将会被编译器自动调用。完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事,因此如果我们不提供构造函数与析构,编译器会提供(但是编译器提供的构造与析构是空实现的)

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

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

构造函数语法:
类名(){}
1、构造函数,没有返回值也不写void
2、函数名称与类名相同
3、构造函数可以有参数,因此可以发生重载
4、程序在调用对象时会自动调用构造函数,无需手动调用,而且只会调用一次
析构函数语法:
~类名(){}
1、析构函数,没有返回值也不写void
2、函数名称与类名相同,在名称前加上符号~
3、析构函数不可以有参数,因此不可以发生重载
4、程序在对象销毁前会自动调用析构函数,无需手动调用,而且只会调用一次

示例:

#include<iostream>
using namespace std;
/*
对象的初始化和清理
1、构造函数:进行初始化操作
2、析构函数:进行清理操作
*/
class Person {
public:
	//构造函数自动调用,只调用一次;
	//不写的话,编译器会自动调用默认的构造函数
	Person() {
		cout<<"Person构造函数的调用" << endl;
	}

	~Person()
	{
		cout<<"Person析构函数的调用" << endl;
	}
};
//构造和析构必须有的,我们不写,编译器调用默认的构造析构
void test01() {
	Person p;
}
int main() {
	test01();
	//Person p;
	system("pause");
	return 0;
}
4.2.2 构造函数的分类以及调用
两种分类方式:

按参数分类:有参构造与无参构造

按类型分类:普通构造和拷贝构造

三种调用方式:

括号法

显示法

隐式转换法

示例:

#include<iostream>
using namespace std;
/*
构造函数的分类及调用
按参数分类:无参构造(默认构造)和有参构造
按类型分类:普通构造和拷贝构造函数
*/
class Person {
public:
	int age;
	//构造函数
	Person() {
		cout<<"Person无参构造函数的调用" << endl;
	}
	Person(int a) {
		age = a;
		cout << "Person有参构造函数的调用" << endl;
	}
	//拷贝构造函数(除拷贝之外都是普通构造)(将传入参数的属性拷贝到我身上)
  //拷贝构造函数将传入的人身上的所有属性,拷贝到自己身上;语法:const Person &p
	Person(const Person &p) {//记住拷贝构造函数写法
		//将传入的人身上所有的属性,拷贝到我身上
		age = p.age;
		cout << "Person拷贝构造函数的调用" << endl;
	}

	~Person()
	{
		cout<<"Person析构函数的调用" << endl;
	}
};
//构造和析构必须有的,我们不写,编译器调用默认的构造析构
//调用
void test01() {
	//1、括号法
	//Person p1;//默认构造函数
	//Person p2(10);//有参构造函数
	//Person p3(p2);//拷贝构造函数
	//cout <<  "p2的年龄为:" <<p2.age<< endl;
	//cout <<  "p3的年龄为:" << p3.age << endl;

	注意事项:调用默认构造参数的时候,不要加(),因为加了之后编译器会认为是一个函数的声明,不会认为在创建对象
	//Person p1();
	//void func();

	//2、显示法
	Person p1;
	Person p2 = Person(10);//有参构造
	Person p3 = Person(p2);//拷贝构造
	匿名对象;用Person p2 = Person(10)中的p2来接收这个匿名对象
	//Person(10);//匿名对象特点:当本行执行结束后,系统会立即回收掉匿名对象
	//cout<<"aaaaa" << endl;
	//不要利用拷贝构造函数初始化匿名对象,编译器会认为Person (p3)===Person p3;
	//Person(p3);

	//3、隐式转换法
	Person p4 = 10;//相当于写了 Person p4=Person(10);
}
int main() {
	test01();
	//Person p;
	system("pause");
	return 0;
}
4.2.3 拷贝构造函数调用时机

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

(1)使用一个已经创建完毕的对象来初始化一个新对象

(2)值传递的方式给函数参数传值

(3)以值方式返回局部对象(vs2022已经优化,不会调用拷贝构造)

示例:

#include<iostream>
using namespace std;
/*
拷贝构造函数调用时机通常有三种情况

(1)使用一个已经创建完毕的对象来初始化一个新对象
(2)值传递的方式给函数参数传值
(3)以值方式返回局部对象
*/

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

	int m_Age;
};
//(1)使用一个已经创建完毕的对象来初始化一个新对象

void test02() {
	Person p1(20);
	Person p2(p1);
	cout << "p2的年龄为:" <<p2.m_Age<< endl;
}

//2)值传递的方式给函数参数传值
void doWork(Person p) {

}
void test03() {
	Person p;
	doWork(p);
}
//(3)以值方式返回局部对象(vs2022已经优化,不会调用拷贝构造)
Person doWork2() {
	Person p1;
	cout<<(int*)&p1<<endl;//输出地址
	return p1;
}
void test04() {
	Person p = doWork2();
	cout << (int*)&p << endl;//与上述地址一样,没有调用拷贝构造函数
}
int main() {
	//test02();
	//test03();
	test04();
	system("pause");
	return 0;
}
4.2.4 构造函数调用规则
默认情况下,c++编译器至少给一个类添加三个函数

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

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

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

构造函数调用规则如下:

(1)如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造

(2)如果用户定义拷贝构造函数,c++不会再提供其他构造函数

示例:

#include<iostream>
using namespace std;

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

	int m_Age;
};

void test02() {
	Person p(10);
	p.m_Age = 18;
	Person p2(p);
	cout<<"p2的年龄为:" <<p2.m_Age<< endl;
}

int main() {
	test02();
	system("pause");
	return 0;
}
4.2.5 深拷贝和浅拷贝

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

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

(1)带来的问题:堆区的内存会重复释放

(2)解决方案:用深拷贝来解决(在堆区开辟两个空间,不会产生堆区重复释放问题)

深拷贝:在堆区利用new重新申请空间,进行拷贝操作

示例:

#include<iostream>
using namespace std;
/*
深拷贝与浅拷贝
*/
class Person {
public:
	Person() {
		cout << "Person无参构造函数的调用" << endl;
	}
	Person(int age,int height) {
		cout << "Person有参构造函数的调用" << endl;
		m_Age = age;
		m_Height= new int(height);//堆区创建数据,手动开辟与释放
	}
	/*Person(const Person &p) {
		cout << "Person拷贝构造函数的调用" << endl;
		m_Age = p.m_Age;
	}*/
	//自己实现拷贝构造函数,解决浅拷贝带来的问题
	Person(const Person &p) {
		cout << "Person拷贝构造函数的调用" << endl;
		m_Age = p.m_Age;
		//m_Height = p.m_Height;   编译器默认实现就是这行代码

		//深拷贝操作
		m_Height=new int(*p.m_Height);//利用new在堆区重新生成新的空间
	}
	~Person()
	{
		//析构函数,将堆区开辟数据做释放操作
		if (m_Height!=NULL) {
			delete m_Height;
			m_Height = NULL;
		}
		cout << "Person析构函数的调用" << endl;
	}

	int m_Age;//年龄
	int *m_Height;//身高
};

void test01() {
	Person p(10,160);
	//p.m_Age = 18;
	Person p2(p);
	cout<<"p2的年龄为:" <<p2.m_Age<< endl;
	cout << "p2的身高为:" << *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() :m_A(10),m_B(20),m_C(30) {
	//		}
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {

	}
	int m_A;
	int m_B;
	int m_C;
};

void test01() {
	//Person p(10, 20, 30);
	Person p(20,30,40);
	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?

先有A,再有B;当其他类对象作为本类的成员时,构造时候,先构造其他类对象,再构造自身

析构的顺序?

先调用本类对象,再调用其他类对象。(析构顺序与构造相反)

总结:构造由内向外,析构由外向内

示例:

#include<iostream>
using namespace std;
/*
类对象作为类成员
构造:由内向外
析构:由外向内
*/
//手机类
class Phone {
	
public:
	//手机牌
	string m_Pname;
	Phone(string name) {
		m_Pname = name;
	}
};
//人类
class Person {
public:
	//姓名
	string m_Name;
	//手机
	Phone m_Phone;

	Person(string name, string pname):m_Name(name),m_Phone(pname) {

	}
	
};

void test01() {
	Person p("zhangsan", "pingguo");
	cout<<p.m_Name<<p.m_Phone.m_Pname << endl;
}

int main() {
	test01();
	system("pause");
	return 0;
}
4.2.8 静态成员

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

静态成员分为:

(1)静态成员变量

所有对象共享同一份数据

在编译阶段分配内存

类内声明,类外初始化

#include<iostream>
using namespace std;
/*
静态成员变量
*/
class Person {
public:
	//1、所有对象共享同一份数据
	//2、编译阶段分配内存
	//3、类内声明,类外初始化
	static int m_A;

};
int Person::m_A = 100;
void test01() {
	Person p;
	cout<<p.m_A<<endl;

	Person p2;
	p2.m_A = 200;
	cout << p.m_A << endl;

 }

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

(2)静态成员函数

所有对象共享同一个函数

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

#include<iostream>
using namespace std;
/*
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
*/
class Person {
public:
	//静态成员函数
	static void func() {
		cout<<"static void func的调用" << endl;
		m_A = 200;
		//m_B = 20;报错,因为静态成员函数不允许访问非静态成员变量
	}
	static int m_A;//静态成员变量
	int m_B = 10;//非静态成员变量

	//静态成员函数是有访问权限的
};
int Person::m_A = 100;

void test01() {
	//1、通过对象调用
	Person p;
	p.func();
	//2、通过类名进行访问
	Person::func();

	
 }

int main() {
	test01();
	system("pause");
	return 0;
}
4.3 C++对象模型和this指针
4.3.1 成员变量和成员函数分开存储

在c++中,类内的成员变量和成员函数分开存储;只有非静态成员变量才属于类的对象上

#include<iostream>
using namespace std;
//成员变量和成员函数分开存储的
class Person {
	int m_A;//非静态成员变量,属于类的对象上
	//静态成员变量(类内声明,类外初始化)不属于某一个对象上,不属于类的对象
	static int m_B;//静态成员变量,不属于类的对象上
	void func() {};//非静态成员函数,不属于类的对象上
	static void func2() {};//静态成员函数,不属于类的对象上
};
int Person::m_B = 10;

void test01() {
	Person p;
	//空对象占用内存空间为:0/4/1?  是1
	//c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
	cout<<"sizeof(p)="<<sizeof(p) << endl;
}
void test02() {
	Person p;
	//空对象占用内存空间为:0/4/1?  是1
	//c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
	cout << "sizeof(p)=" << sizeof(p) << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}
4.3.2 this指针概念

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

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

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

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

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

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

this指针的用途:

当形参和成员变量同名时,可用this指针来区分

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

#include<iostream>
using namespace std;
/*
1、解决名称冲突
2、返回对象本身用*this
*/
class Person {
public:
	Person(int age) {
		//this指针指向的是被调用的成员函数所属的对象
		this->age = age;
	}
	//Person PersonAddAge值的方式返回,返回的是p2的一个复制体,并不是本体
	Person& PersonAddAge(Person &p) {
		this->age += p.age;
		//this是指向p2的指针,而*this指向的就是p2这个对象本体
		return *this;
	}
	int age;
};
//1、解决名称冲突
void test01() {
	Person p1(18);
	cout<<"p1的年龄为:"<<p1.age << endl;
}
//2、返回对象本身用*this
void test02() {
	Person p1(10);
	Person p2(10);
	//链式编程
	p2.PersonAddAge(p1).PersonAddAge(p1);
	cout << "p2的年龄为:" << p2.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 class" << endl;
	}
	void showPersonAge() {
		//报错原因是因为传入的指针为NULL
		// 
		//增加一个if判断,提高代码的健壮性
		if (this == NULL) {
			return;
		}
		cout<<"age=" <<this->m_Age<< endl;
	}
	int m_Age;
};
void test01() {
	Person* p = NULL;
	p->showClassName();
	//p->showPersonAge();
}

int main() {
	test01();
	system("pause");
	return 0;
}
4.3.4 const修饰成员函数
常函数:

(1)成员函数后加const后我们称这个函数为常函数

(2)常函数内不可以修改成员属性

(3)成员属性声明时加关键字mutable,在常函数中依然可以修改

常对象:

(1)声明对象前加const称该对象为常对象

(2)常对象只能调用常函数

#include<iostream>
using namespace std;
//常函数
class Person {
public:
	//this指针的本质是一个指针常量,指针的指向是不可以修改的
	//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改。
	void showPerson() const{
		//m_A = 100;此时不可以进行修改
		//this=NULL,this指针不可以修改指针的指向的
		m_B = 100;//mutable修饰的是可以修改的
	}
	void showName() {

	}
	int m_A;
	mutable int m_B;//特殊变量,即使在常函数中,也可以修改这个值,加关键字mutable
};
//常对象
void test02() {
	const Person p;//在对象前加const,变为常对象
	//p.m_A = 100;不可以修改
	p.m_B = 100;//m_B是特殊值,在常对象下也可以进行修改
	
	//常对象只能调用常函数
	p.showPerson();
	//p.showName();
}

void test01() {
	Person p;
	p.showPerson();
}
int main() {
	test01();
	system("pause");
	return 0;
}
4.4 友元

生活中你的家有客厅(public),有你的卧室(private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去。

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

友元的关键字:friend

友元的三种实现:

(1)全局函数做友元

(2)类做友元

(3)成员函数做友元

4.4.1 全局函数做友元
#include<iostream>
using namespace std;

class Building {
	//goodGay全局函数是building的好朋友,可以访问私有成员
	friend void goodGay(Building* building);
public:
	//构造
	Building() {
		m_SittingRoom = "客厅";
		m_LivingRoom = "卧室";
	}
	string m_SittingRoom;//客厅
private:
	string m_LivingRoom;//卧室
};

//全局函数
void goodGay(Building *building) {
	cout<<"好基友全局函数正在访问:"<<building->m_SittingRoom << endl;
	cout << "好基友全局函数正在访问:" << building->m_LivingRoom << endl;
}
void test01() {
	Building b;
	goodGay(&b);
}
int main() {

	test01();
	system("pause");
	return 0;
}
4.4.2 类做友元
#include<iostream>
using namespace std;
#include<string>
//类做友元

class Building {
	//GoodGay类是本类的好朋友,可以访问本类的私有成员
	friend class GoodGay;

public:
	string m_SittingRoom;//客厅

	Building() {
		m_SittingRoom = "客厅";
		m_LivingRoom = "卧室";
	};
private:
	string m_LivingRoom;//卧室
};
class GoodGay {
public:
	Building *building;
	void visit();//参观函数,访问building中的属性

	GoodGay() {
		//创建建筑物对象
		building = new Building;
	}
	
	
};



//类外去写成员函数
void GoodGay::visit() {
	cout<<"好基友正在访问:" <<building->m_SittingRoom<< endl;
	cout << "好基友正在访问:" << building->m_LivingRoom<< endl;
}

void test01() {
	GoodGay g;
	g.visit();
}
int main() {
	test01();
	system("pause");
	return 0;
}
4.4.3 成员函数做友元
#include<bits/stdc++.h>
using namespace std;

class GoodGay; //类声明
class Building;//类声明



class GoodGay{
      public:
            GoodGay();//构造函数声明后类外实现
            ~GoodGay();//析构函数声明后类外实现

            void visit();
            /*
            visit()函数声明后要类外实现,
            因为visit里面访问了Building类的成员变量
            所以visit()函数的实现要写在Buliding类定义的后面 

            但是Building类里面又要求GoodGay的定义在Building类前面
            因为这样Building类里的友元声明才能知道GoodGay类里面是不是
            真的有visit()这个成员函数

            所以类内定义类外实现的作用就体现在这里了
             */
      private:
            Building * building;
};



class Building{
      friend void GoodGay::visit();
      /* 
      虽然GoodGay类已经声明了
      但是Building类还不知道GoodGay类具体有哪个函数,
      所以要把Building类的定义放在GoodGay类的定义下面
       */
      public:
            Building(){
                  m_SittingRoom = "客厅";
                  m_BedRoom = "卧室";
            }
      public:
            string m_SittingRoom;
      private:
            string m_BedRoom;
      
};

void GoodGay::visit(){//函数类型要写在前面
      cout << "好友真正访问你家的: " << building->m_SittingRoom << '\n';
      cout << "好友真正访问你家的: " << building->m_BedRoom << '\n';
}

GoodGay::GoodGay(){//GoodGay构造函数类外实现
      //创建建筑物对象
      building = new Building;
}

GoodGay::~GoodGay(){
      delete building;
}


void test1(){
      GoodGay gg;
      gg.visit();
}

int main(){
      
      test1();
      return 0;
}
#include<bits/stdc++.h>
using namespace std;

class GoodGay; //类声明
class Building;//类声明



class GoodGay{
      public:
            GoodGay();//构造函数声明后类外实现
            ~GoodGay();//析构函数声明后类外实现

            void visit();
            /*
            visit()函数声明后要类外实现,
            因为visit里面访问了Building类的成员变量
            所以visit()函数的实现要写在Buliding类定义的后面 

            但是Building类里面又要求GoodGay的定义在Building类前面
            因为这样Building类里的友元声明才能知道GoodGay类里面是不是
            真的有visit()这个成员函数

            所以类内定义类外实现的作用就体现在这里了
             */
      private:
            Building * building;
};



class Building{
      friend void GoodGay::visit();
      /* 
      虽然GoodGay类已经声明了
      但是Building类还不知道GoodGay类具体有哪个函数,
      所以要把Building类的定义放在GoodGay类的定义下面
       */
      public:
            Building(){
                  m_SittingRoom = "客厅";
                  m_BedRoom = "卧室";
            }
      public:
            string m_SittingRoom;
      private:
            string m_BedRoom;
      
};

void GoodGay::visit(){//函数类型要写在前面
      cout << "好友真正访问你家的: " << building->m_SittingRoom << '\n';
      cout << "好友真正访问你家的: " << building->m_BedRoom << '\n';
}

GoodGay::GoodGay(){//GoodGay构造函数类外实现
      //创建建筑物对象
      building = new Building;
}

GoodGay::~GoodGay(){
      delete building;
}


void test1(){
      GoodGay gg;
      gg.visit();
}

int main(){
      
      test1();
      return 0;
}

4.5 运算符重载

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

4.5.1 加号运算符重载

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<iostream>
using namespace std;
//加号运算符重载

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;

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

}



int main() {
	test01();
	return 0;
}

注意:

(1)对于内置的数据类型的表达式的运算符是不可能改变的

(2)不要滥用运算符重载

4.5.2 左移运算符重载
4.6 继承
继承是面向对象的三大特性之一

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.6.2 继承方式

继承语法:

class 子类:继承方式 父类

继承方式三种:

公共继承

保护继承

私有继承

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1)父类中私有内容,任何继承都访问不到

(2)私有继承:父类中public与protected变为private权限

(3)保护继承:父类中public与protected变为protected权限

(4)公有继承:父类中什么权限,子类中什么权限

#include<iostream>
using namespace std;
//继承方式

//公共继承
class Base1 {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
//go公共继承
class Son1 :public Base1 {
	void func() {
		m_A = 10;//父类中公共权限到子类中依然为公共权限
		m_B = 10;//父类中保护权限到子类中依然为保护权限
		//m_C = 10;//父类中私有权限子类中访问不到
	}
};

//保护继承
class Son2 :protected Base1 {
public:
	void func2() {
		m_A = 100;//父类中公共权限的内容,到子类中变为保护权限内容
		m_B = 100;
	}
};
//私有继承
class Son2 :private Base1 {
public:
	void func3() {
		m_A = 100;//父类中公共权限的内容,到子类中变为私有权限内容
		m_B = 100;//父类中保护权限的内容,到子类中变为私有权限内容
		
	}
};

void test01() {
	Son1 s1;

	s1.m_A = 100;
	//s1.m_B = 100;到Son1中m_B是保护权限,类外不可以访问
}
int main() {

	
	return 0;
}
4.6.3 继承中的对象模型

问题:从父类中继承过来的成员,哪些属于子类对象中?


4.6.4 继承中构造和析构顺序
省流:父构,子构,子析,父析

构造由内向外,析构由外向内(类中包含类时)

构造:内外,父子

析构:外内,子父

4.6.5 继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或者父类中同名的数据呢?

访问子类同名成员,直接访问即可;

访问父类同名成员,需要加作用域。

实例:

#include<iostream>
using namespace std;
/*
ji继承中同名处理方式
*/
class Base {
public:
	Base() {
		m_A = 100;
	}
	void func() {
		cout << "Base func" << 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;//子类中直接访问即可
	cout<<s.Base::m_A<<endl;//父类中加作用域才可以访问
}
//同名成员函数处理
void test02() {
	Son s;
	//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数(包括重载的)
	//如果想访问父类中被隐藏掉的成员函数,必须加作用域
	s.func();
	s.Base::func();
}
int main() {

	test01();
	test02();
	return 0;
}

总结:

(1)子类对象可以直接访问到子类中同名成员

(2)子类对象加作用域可以直接访问到父类同名成员

(3)当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

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

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

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

访问子类同名成员,直接访问即可

访问父类同名成员,需要加作用域

示例:

c
#include<iostream>
using namespace std;
//继承中的同名静态成员处理方式(静态成员,类内声明,类外初始化)
class Base {
public:
	static int m_A;
};
int Base::m_A = 100;

class Son :public Base {
public:
	static int m_A;
};
int Son::m_A = 200;

//同名静态成员属性处理
void test01() {
	//1、通过对象访问
	Son s;
	cout<<s.m_A<<endl;
	cout<<s.Base::m_A<<endl;

	//2、通过类名访问
	cout<<Son::m_A << endl;
	//第一个::代表通过类名方式访问,第二个::代表访问父类作用域下
	cout<<Son::Base::m_A<<endl;
}

int main() {

	test01();
	return 0;
}

总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类名)

4.6.7 多继承语法

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

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

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

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

示例:

#include<iostream>
using namespace std;

//多继承语法
class Base1 {
public:
	Base1() {
		m_A = 100;
	}
	int m_A;
};

class Base2 {
public:
	Base2() {
		m_A = 500;
		m_B = 200;
	}
	int m_A;
	int m_B;
};

//子类,需要继承Base1,Base2
class Son :public Base1, public Base2 {
public:
	Son() {
		m_C = 300;
		m_D = 400;
	}
	int m_C;
	int m_D;
};

void test01() {
	Son s;
	cout<<sizeof(s)<<endl;

	//cout <<s.m_A<<endl;会产生指向不明确问题,因此需要加作用域
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}
int main() {
	test01();
	return 0;
}

总结:父类中有同名成员出现,子类需要加作用域区分

4.6.8 菱形继承

概念:两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承方式被称为菱形继承,或者钻石继承。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

菱形继承的问题:

(1)羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性

(2)草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份即可

#include<iostream>
using namespace std;
//动物类
class Animal {
public:
	int m_Age;
};
//利用虚继承,解决菱形继承的问题(继承之前,加上关键字virtual变为虚继承)
// Animal类称为虚基类
//羊类
class Sheep :virtual public Animal {

};

//驼类
class Tuo :virtual public Animal {

};

//羊驼类
class SheepTuo :public Sheep, public Tuo {

};
void test01() {
	SheepTuo st;
	//st.m_Age = 18;不明确,会报错
	//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
	st.Sheep::m_Age = 19;
	st.Tuo::m_Age = 29;

	cout<<st.Sheep::m_Age << endl;
	cout << st.Tuo::m_Age << endl;
	cout<<st.m_Age<<endl;
	//这份数据我们知道,只有一份即可,菱形继承导致数据有两份,资源浪费
}
int main() {
	test01();
	return 0;
}

总结:

(1)菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义

(2)利用虚继承可以解决菱形继承问题

4.7 多态
4.7.1 多态的基本概念

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

多态分为两类

静态多态:函数重载和运算符重载属于静态多态,复用函数名

动态多态:派生类和虚函数实现运行时多态

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

(1)静态多态的函数地址早绑定-编译阶段确定函数地址

(2)动态多态的函数地址晚绑定-运行阶段确定函数地址

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

//执行说话的函数
//地址早绑定,在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,也就是地址晚绑定

/*
动态多态满足条件:
1、有继承关系
2、子类要重写父类的虚函数

动态多态的使用:
父类的指针或者引用,指向子类的对象
*/
void doSpeak(Animal &animal) {//Animal &animal=cat,引用指向子类对象
	animal.speak();
}

void test01() {
	Cat cat;
	doSpeak(cat);

	Dog dog;
	doSpeak(dog);
}
int main() {
	test01();
	return 0;
}

总结:

(1)多态满足条件:

有继承关系

子类重写父类中的虚函数

(2)多态使用条件:
父类指针或者引用指向子类对象

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

4.7.2 多态案例—计算器类

案例描述:分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算机类

多态的优点:

(1)代码组织结构清晰

(2)可读性强

(3)利于前期和后期的扩展以及维护

示例:

#include<iostream>
using namespace std;
/*
普通写法与多态写法实现计算器
*/

//普通写法
class Calculator {
public:

	int getResult(string oper) {
		if (oper == "+") {
			return m_Num1 + m_Num2;
		}else if (oper == "-") {
			return m_Num1 - m_Num2;
		}else if (oper == "*") {
			return m_Num1 * m_Num2;
		}
	}
	//操作数1,2
	int m_Num1;
	int m_Num2;
};

void test01() {
	//创建一个计算器对象
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;

	cout<<c.m_Num1<<"+" <<c.m_Num2<<"=" <<c.getResult("+") << endl;
	cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult("-") << endl;
	cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult("*") << endl;
}


//利用多态实现计算器类
//实现计算器抽象类
class AbstractCalculator {
public:
	virtual int getResult() {
		return 0;
	}
	int m_Num1;
	int m_Num2;
};
//加法计算器类
class AddCalculator :public AbstractCalculator {
public:
	int getResult() {
		return m_Num1 + m_Num2;
	}
};

//减法计算器类
class JCalculator :public AbstractCalculator {
public:
	int getResult() {
		return m_Num1 - m_Num2;
	}
};

//cheng法计算器类
class CCalculator :public AbstractCalculator {
public:
	int getResult() {
		return m_Num1 * m_Num2;
	}
};

void test02() {
	//多态使用条件:父类指针或者引用指向子类对象
	AbstractCalculator *abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
	delete abc;

	
	abc = new JCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
	delete abc;
}
int main() {
	//test01();
	test02();
	return 0;
}
4.7.3 纯虚函数与抽象类

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

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

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

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

抽象类的特点:

(1)无法实例化对象

(2)子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例:

#include<iostream>
using namespace std;
/*
纯虚函数与抽象类
*/
class Base {

public:
	//表示纯虚函数
	//只要有一个纯虚函数,这个类代表抽象类
	virtual void func() = 0;

	//抽象类特点:
	//1、无法实例化对象
	//2、抽象类的子类必须重写父类中的纯虚函数,否则也属于抽象类
};
class Son :public Base {
public:
	void func() {
		cout<<"子类重写父类中的虚函数" << endl;
	}
};
void test01() {
	//Base b;抽象类
  //多态使用条件:父类指针或者引用指向子类对象
	Base* b = new Son;
	
	b->func();
}
int main() {
	test01();
	return 0;
}
4.7.4 多态案例2:制作饮品

案例描述:
制作饮品的大致流程:煮水-冲泡(咖啡/茶叶)-倒入杯中-加入辅料

利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

#include<iostream>
using namespace std;
/*
多态案例2:制作饮品
*/
class AbstractDrinking {
public:
	//煮水
	virtual void Boil() = 0;
	//冲泡
	virtual void Brew() = 0;
	//装杯
	virtual void Pour() = 0;
	//加入辅料
	virtual void PutSomeThing() = 0;
	//制作饮品
	void Make() {
		Boil();
		Brew();
		Pour();
		PutSomeThing();
	}
};

//制作咖啡
class Coffee :public AbstractDrinking {
public:
	//煮水
	virtual void Boil() {
		cout<<"煮纯净水" << endl;
	}
	//冲泡
	virtual void Brew() {
		cout << "冲泡咖啡" << endl;
	}
	//装杯
	virtual void Pour() {
		cout << "倒入咖啡杯中" << endl;
	}
	//加入辅料
	virtual void PutSomeThing() {
		cout << "加入糖和牛奶" << endl;
	}
};

//制作茶叶
class Tea:public AbstractDrinking {
public:
	//煮水
	virtual void Boil() {
		cout << "煮纯净水(茶叶)" << endl;
	}
	//冲泡
	virtual void Brew() {
		cout << "冲泡茶叶" << endl;
	}
	//装杯
	virtual void Pour() {
		cout << "倒入茶叶杯中" << endl;
	}
	//加入辅料
	virtual void PutSomeThing() {
		cout << "加入茶叶辅料" << endl;
	}
};
//制作饮品
void doWork(AbstractDrinking *abs) {
	abs->Make();
	delete abs;
}
void test01() {
	//制作咖啡
	doWork(new Coffee);
	cout<<"******************" << endl;
	doWork(new Tea);
}
int main() {
	test01();
	return 0;
}
4.7.5 虚析构与纯虚析构

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

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

虚析构和纯虚析构共性:

(1)可以解决父类指针释放子类对象

(2)都需要有具体的函数实现

虚析构与纯虚析构的区别:如果是纯虚析构,该类属于抽象类,无法实例化对象

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

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

示例:

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

class Animal {
public:
	Animal() {
		cout << "Ani构造函数的调用" << endl;
	}
	//利用虚析构可以解决父类指针释放子类对象时不干净的问题
	/*virtual ~Animal() {
		cout << "Ani析构函数的调用" << endl;
	}*/
	//纯虚析构(需要声明也需要实现;类内声明,类外实现)
	//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
	virtual ~Animal() = 0;
	virtual void speak() = 0;
};
Animal::~Animal() {
	cout << "Ani析构函数的调用" << endl;
}
class Cat :public Animal {
public:
	Cat(string name) {
		cout << "Cat构造函数的调用" << endl;
		m_Name=new string(name);
	}

	virtual void speak() {
		cout<<*m_Name<<" small cat is speaking" << endl;
	}
	~Cat() {
		if (m_Name != NULL) {
			cout<<"Cat析构函数的调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}

	string *m_Name;
};
void test01() {
	Animal* a = new Cat("tom");
	a->speak();
	//父类指针在析构时候,不会调用子类中析构函数,导致子类中如果有堆区属性,会出现内存泄漏
	delete a;
}
int main() {
	test01();
	return 0;
}

总结:

1、虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

2、如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3、拥有纯虚析构函数的类也属于抽象类

4.7.6 多态案例3-电脑组装

案例描述:

电脑主要部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)

将每个零件封装为抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

示例:

#include<iostream>
using namespace std;

//抽象不同零件类
class CPU {
public:
	//抽象计算函数
	virtual void calculate() = 0;
};

class VideoCard {
public:
	//抽象显示函数
	virtual void display() = 0;
};

class Memory {
public:
	//抽象存储函数
	virtual void storage() = 0;
};


class Computer {
public:
	Computer(CPU *cpu,VideoCard *vc,Memory *mem) {
		mcpu = cpu;
		mvc = vc;
		mmem = mem;
	}
	//提供工作函数
	void work() {
		//让零件工作起来
		mcpu->calculate();
		mvc->display();
		mmem->storage();
	}
	//提供析构函数,释放三个电脑零件
	~Computer() {
		if (mcpu != NULL) {
			delete mcpu;
			mcpu = NULL;
		}
		if (mvc != NULL) {
			delete mvc;
			mvc = NULL;
		}
		if (mmem != NULL) {
			delete mmem;
			mmem = NULL;
		}
	}

private:
	CPU* mcpu;//cpu指针
	VideoCard* mvc;//显卡指针
	Memory* mmem;//内存条指针
};

//具体厂商
//Intel
class IntelCPU :public CPU {
public:
	void calculate() {
		cout<<"Intel的CPU开始计算了" << endl;
	}
};
class IntelVideoCard :public VideoCard {
public:
	void display() {
		cout << "Intel的显卡开始显示了" << endl;
	}
};
class IntelMemory :public Memory {
public:
	void storage() {
		cout << "Intel的内存条开始存储了" << endl;
	}
};

//Lenovo
class LenovoCPU :public CPU {
public:
	void calculate() {
		cout << "Lenovo的CPU开始计算了" << endl;
	}
};
class LenovoVideoCard :public VideoCard {
public:
	void display() {
		cout << "Lenovo的显卡开始显示了" << endl;
	}
};
class LenovoMemory :public Memory {
public:
	void storage() {
		cout << "Lenovo的内存条开始存储了" << endl;
	}
};
void test01() {
	//第一台电脑零件
	CPU* intelCpu = new IntelCPU;
	VideoCard* intelCard = new IntelVideoCard;
	Memory* intelMem = new IntelMemory;

	//创建第一台电脑
	cout<<"第一台电脑工作" << endl;
	Computer* computer1 = new Computer(intelCpu,intelCard,intelMem);
	computer1->work();
	delete computer1;

	cout << endl;
	//创建第二台电脑
	cout << "第二台电脑工作" << endl;
	Computer* computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
	computer2->work();
	delete computer2;

	cout << endl;
	//创建第三台电脑
	cout << "第三台电脑工作" << endl;
	Computer* computer3 = new Computer(new IntelCPU, new LenovoVideoCard, new LenovoMemory);
	computer3->work();
	delete computer3;

}
int main() {
	test01();
	return 0;
}

5、文件操作

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

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

c++中对文件操作需要包含头文件

文件类型分为两种:

(1)文本文件:文件以文本的ASCII码形式存储在计算机中

(2)二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们

操作文件的三大类:
(1)ofstream:写操作

(2)ifstream:读操作

(3)fstream:读写操作

5.1 文本文件
5.1.1 写文件

写文件步骤:

1、包含头文件
#include<fstream>
2、创建流对象
ofstream  ofs;
3、打开文件
ofs.open("文件路径",打开方式);
4、写数据
ofs<<"写入的数据"5、关闭文件
ofs.close();
打开方式解释
ios::in为读文件而打开文件
ios::out为写文件而打开文件
ios::trunc如果文件存在先删除,再创建
ios::binary二进制文件
注意:

(1)文件打开方式可以配合使用,利用|操作符

#include<iostream>
#include<fstream>
using namespace std;
/*
文本文件
*/

void test01() {
	//1、包含头文件fstream

	//2、创建流对象
	ofstream ofs;

	//3、指定打开方式
	ofs.open("test.txt",ios::out);

	//4、写内容
	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;

	//5、关闭文件
	ofs.close();
}
int main() {

	test01();
	return 0;
}
总结:

(1)文件操作必须包含头文件fstream

(2)读文件可以利用ofstream或者fstream

(3)打开文件时候需要指定操作文件路径,以及打开方式

(4)利用<<可以向文件中写数据

(5)操作完毕,需要关闭文件

5.1.2 读文件

读文件与写文件步骤类似,但是读取方式相对较多

读文件步骤如下:

1、包含头文件
#include<fstream>

2、创建流对象
ifstream ifs;

3、打开文件并判断文件是否打开成功
ifs.open("文件路径",打开方式);

4、读数据
四种读取方式

5、关闭文件
ifs.close();

示例:

#include<iostream>
#include<fstream>
#include<string.h>
using namespace std;
/*
文本文件,读文件
*/

void test01() {
	//1、包含头文件
     //#include<fstream>

		//2、创建流对象
	ifstream ifs;

		//3、打开文件并判断文件是否打开成功
		ifs.open("test.txt",ios::in);

		if (!ifs.is_open()) {
			cout<<"文件打开失败" << endl;
			return;
		}

	    //4、读数据
		//四种读取方式
		//第一种
		/*char buf1[1024] = { 0 };
		while (ifs >> buf1) {
			cout<<buf1<<endl;
		}*/
		//第二种
		/*char buf2[1024] = { 0 };
		while (ifs.getline(buf2,sizeof(buf2))) {
			cout << buf2 << endl;
		}*/
		//第三种
		/*string buf;
		while (getline(ifs, buf)) {
			cout << buf << endl;
		}*/
		//第四种
		char  c;
		while ((c=ifs.get())!=EOF) {//EOF  end of line
			cout<<c;
		}

		//5、关闭文件
		ifs.close();
}
int main() {

	test01();
	return 0;
}
总结:

(1)读文件可以利用ifstream,或者fstream

(2)利用is_open函数可以判断文件是否打开成功

(3)close关闭文件

5.2 二进制文件

以二进制的方式对文件进行读写操作

打开方式要指定为ios::binary

5.2.1 写文件

二进制方式写文件主要利用流对象调用成员函数write

函数原型:
ostream&  write(const char * buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
#include<iostream>
#include<fstream>
using namespace std;
/*
二进制文件
*/

class Person {
public:
	char m_Name[64];//姓名
	int m_Age;//年龄
};

void test01() {
	ofstream ofs;
	ofs.open("person.txt",ios::out|ios::binary);

	Person p = { "zhangsan",19 };
	ofs.write((const char *)&p,sizeof(Person));

	ofs.close();
}
int main() {

	test01();
	return 0;
}
5.2.2 读文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型:
istream &read(char *buffer,int len)

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

示例:

#include<iostream>
#include<fstream>
using namespace std;
/*
二进制文件
*/

class Person {
public:
	char m_Name[64];//姓名
	int m_Age;//年龄
};

void test01() {
	ifstream ifs;
	ifs.open("person.txt",ios::out|ios::binary);

	if (!ifs.is_open()) {
		cout<<"failure" << endl;
		return;
	}
	Person p;
	ifs.read((char *)&p,sizeof(Person));
	cout<<p.m_Name<<"+"<<p.m_Age << endl;
	ifs.close();
}
int main() {

	test01();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值