IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 2. 内存分区 引用 类 -> 正文阅读

[C++知识库]2. 内存分区 引用 类


黑马程序员课程学习笔记

内存分区

分四区:代码区、全局区、栈区、堆区

代码区

程序运行前就存在,存放二进制文件,共享的,只读

全局区

  • 程序运行前就存在,存放全局变量,静态变量和常量(其中常量包括字符串常量、全局常量
  • 程序运行结束后,由操作系统释放
  • 注意,局部变量加const修饰为局部常量,局部常量和局部变量都不在全局区
  • 此外,string str=“abc”;str和"abc"地址不一样,str是局部变量,"abc"是字符串常量
#include <iostream>
using namespace std;

/*全局区,存放:全局变量,静态变量,常量,
程序运行结束后,由操作系统释放*/

//函数体外部定义全局变量
int g_c = 30;
int g_d = 40;
//全局常量
const int c_g_a = 40;
const int c_g_b = 40;

int main() {

	//函数体内部定义局部变量
	int a = 10;//局部变量
	int b = 10;//局部变量
	cout << "局部变量a地址:" << (int)&a << endl;	
	cout << "局部变量b地址:" << (int)&b << endl << endl;

	//打印全局变量
	cout << "全局变量g_c地址" << (int)&g_c << endl;
	cout << "全局变量g_d地址" << (int)&g_d << endl<<endl;

	//静态变量
	static int s_e = 10;
	static int s_f = 10;
	cout << "静态全局变量s_e地址" << (int)&s_e << endl ;
	cout << "静态全局变量s_f地址" << (int)&s_f << endl << endl;

	//常量
	//字符串常量
	string str = "hello world" ;
	cout << "字符串常量地址" << (int)&"hello world" << endl;
	//这里如果用string定义,就是局部变量了,单个的字符串就是常量
	cout << "局部变量str地址" << (int)&str << endl << endl;

	//全局常量
	//局部常量
	const int c_l_a = 1;
	const int c_l_b = 1;
	cout << "全局常量c_g_a地址" << (int)&c_g_a << endl;
	cout << "全局常量c_g_b地址" << (int)&c_g_b << endl;
	cout << "局部常量c_l_a地址" << (int)&c_l_a << endl;
	cout << "局部常量c_l_b地址" << (int)&c_l_b << endl;

	return 0;
}


从上述地址可以看到,圈出的三个地址很近,离其他的很远,这三个都不在全局区

栈区

  • 存放局部变量和形参
  • 栈区的数据由编译器管理开辟和释放,先进后出
  • 不要返回局部变量的地址
#include <iostream>
using namespace std;

/*栈区,存放局部变量和形参
* 栈区的数据由编译器管理开辟和释放
* 不要返回局部变量的地址*/

int* fun(int b) {
	b = 100;
	int a = 10;//这里的a和b都在栈区
	return &a;
}

int main() {

	int* p = fun(1);
	cout << *p << endl;//第一次打印正确是编译器做了保留
	cout << *p << endl;//第二次不再保留,打印失败,

	return 0;
}

堆区 new

  • 由程序员分配和释放,若程序员未释放,程序结束时,由系统回收
  • 利用new在堆区开辟内存
  • 利用delete释放堆区数据
  • int * arr=new int[10];在堆区开辟一个长度为10的数组,new int(10),是开辟一个整型数据
  • 开辟的数据用什么接收?new返回的是该数据类型的指针,所以要用指针来接收
  • 利用delete[] 数组名释放堆区数组
#include <iostream>
using namespace std;

/*堆区
由程序员分配和释放,若程序员未释放,程序结束时,由系统回收
利用new在堆区开辟内存
利用delete释放*/

int*fun(){
	//int a = 10;
	//括号里面是初始化值,可以是字符型,字符串型以及整数型
	int* a = new int(123);
	//int *a = new int(10);

	return a;
}

void fun2() {
	//中括号表示一组数据,10表示数组有10个元素,()表示一个数据}
	int *arr = new int[10];
	for (int i = 0; i < 10; i++){
		arr[i] = rand() % 50;
	}
	for (int i = 0; i <10; i++){
		cout << arr[i] << "  ";
	}

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

int main() {

	int* p = fun();
	cout << "*p=" << *p << endl;
	cout << "*p=" << *p << endl;
	
	delete p;
	//cout << "*p=" << *p << endl;//内存已被释放,不能再访问
	fun2();
	return 0;
}

引用

基本用法

  • 作用:给变量起别名
  • 定义:数据类型 &别名 = 原名;
  • 注意:引用必须初始化,初始化之后不可再修改
#include <iostream>
using namespace std;

//引用:给变量起别名
//数据类型 &别名=原名
//注意:引用必须初始化
//初始化之后不可以再改变
int main() {

	int a = 10;
	int c= 30;
	cout << "a=" << a << endl;	

	int& b = a;//引用之后,操作的是同一块内存
	cout << "b=" << b << endl;
	cout << "a=" << a << endl;

	b = 20;
	b=c;//这样写是把c的值赋予b,而不是改变引用
	cout << "b=" << b << endl;
	cout << "a=" << a << endl;

	return 0;
}

引用作为函数参数

  • 三种函数传参方式:值传递,地址传递,引用
  • 地址传递和引用均可以修饰实参,但是引用可能操作更简单一点
#include <iostream>
using namespace std;

void test01(int a, int b);//值传递
void test02(int* a, int* b);//地址传递 
void test03(int& a, int& b);//引用

//地址传递和引用均可以修饰实参,即操作的是一块内存

int main() {

	int a = 10, b = 30;
	cout << "交换前:" << endl;
	cout << "a=" << a << ",b=" << b << endl << endl;;
	
	test01(a, b);
	cout << "a=" << a << ",b=" << b << endl;

	a = 10;
	b = 30;
	test02(&a, &b);
	cout << "a=" << a << ",b=" << b << endl;

	a = 10;
	b = 30;
	test03(a, b);
	cout << "a=" << a << ",b=" << b << endl;
	
	return 0;
}

void test01(int a, int b) {

	cout << "值传递:" << endl;
	int temp = a;//把a的值给temp
	a = b;
	b = temp;

}
void test02(int* a, int* b) {

	cout << "\n地址传递:" << endl;
	int temp = *a;//a是地址,a解引用后的值给temp
	*a = *b;
	*b = temp;

}
void test03(int& aa, int& bb) {

	cout << "\n引用:" << endl;
	int temp = aa;//aa是a的引用,就是a换了一个名字,操作的是一块内存
	aa = bb;
	bb = temp;

}

引用作为函数返回值

1.不要返回局部变量的引用
2.引用的函数调用可以作为左值

#include <iostream>
using namespace std;

int& test01();
int& test02();

int main() {

	int r1 = test01();
	cout << "r1=" << r1 << endl;//这里接收的是r1
	cout << "r1=" << r1 << endl;
	int & r2 = test01();
	cout << "r2=" << r2 << endl;
	cout << "r2=" << r2 << endl;//这里就有问题了,

	int& r3 = test02();
	cout << "r3=" << r3 << endl;
	cout << "r3=" << r3 << endl;
	cout << "r3=" << r3 << endl;

	test02() = 100;//函数调用可以作为左值
	//test02()返回的是a的引用,因此可以作为左值
	cout << "r3=" << r3 << endl;
	cout << "r3=" << r3 << endl;	

	return 0;
}

int& test01() {

	int a = 10;//局部变量,栈区
	return a;//用引用的方式返回a,是不合法的

}
int& test02() {

	static int b = 20;//静态变量,全局区,程序结束后由系统释放
	return b;

}

引用的本质

  • 指针常量:*+const,指针的指向不可以修改
  • 引用的本质是指针常量,故初始化之后就不能修改指向,但是值可以改。
  • int &a=b;等价于int * const a = &b;

常量引用

在函数传参时,地址传递和引用的形参都能修饰实参,如果不想实参别修改,可以价格const,前面的指针常量就有这个用处,这里用const修饰引用也是一样的。

void test(const int&a){}

此外

const int& a = 10;
//是合法的,编译器做了如下工作,
//int temp = 10;
//const int& a = temp;

类和对象

对象那个的三大特性:封装、继承、多态

封装

将属性和行为写到一起,来表现事物
语法:class 类名{ 访问权限 属性 \ 行为};

类的案例1:一个圆类

设计一个圆类,求圆的周长和面积

#include <iostream>
using namespace std;

const double PI = 3.1415926;

//设计一个圆类,求圆的周长
class Circle{

public://访问权限

	//属性
	double m_r;
	//行为
	double calculateC() {
		return 2 * PI * m_r;
	}
	double calculateS() {
		return PI * m_r* m_r;
	}
};

int main() {

	//通过圆类,创建具体的圆对象,即实例化
	Circle yuan;
	cout << "请输入圆的半径" << endl;
	cin >> yuan.m_r;

	cout << "半径:" << yuan.m_r 
		<< ",周长:" << yuan.calculateC() 
		<< ",面积:" << yuan.calculateS()<< endl;


	return 0;
}

类的案例2:学生类

设计一个学生类,包含姓名和学号,且能通过成员函数赋值

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

//设计一个学生类,包含姓名和学号,且能赋值
class Student{

public://访问权限

	//属性=>成员变量
	string m_Name;
	int m_ID;

	//行为=>成员函数
	void setValue() {
		cout << "请输入学生姓名和学号" << endl;
		cin >> m_Name>>m_ID;		
	}
	void showValue() {
		cout << "姓名:" << m_Name<<"\t学号:"<<m_ID<<endl;
	}
	void modifyValue(string name, int id) {
		m_Name = name;
		m_ID = id;
	}
};

int main() {

	//实例化学生对象
	Student s1;

	//这里主要展示两种赋值方法
	//方法1
	s1.setValue();
	s1.showValue();
	//方法2
	s1.modifyValue("李四",2020225030);
	s1.showValue();
	
	return 0;
}

访问权限

访问权限共有三种:公有、保护、私有

类型成员的访问权限子类
公有类内可以访问,类外也能访问
保护类内可以访问,类外不可以访问子类可以访问
私有类内可以访问,类外不可以访问子类不可以访问
#include <iostream>
using namespace std;

//类的访问权限共有三种:
//公共权限 public   成员 类内可以访问,类外也能访问
//保护权限 protected  成员 类内可以访问,类外不可以访问 子类可以访问
//私有权限 private  成员 类内可以访问,类外不可以访问 子类不可以访问

class Person {
	//这里面的就是类内,包括成员变量和成员函数

public:
	string m_Name;//姓名
protected:
	string m_Car;//汽车
private:
	int m_Password;//银行卡密码

public:
	//类内,三个都可以访问
	void func() {
		m_Name = "张三";
		m_Car = "拖拉机";
		m_Password = 123123;
	}

};


int main() {

	Person p1;
	p1.m_Name = "李四";//类外,只有公有权限下的姓名可以访问
	p1.func();//虽然类外不能访问另外两种权限的变量,但是public下的函数提供了接口

	return 0;
}

class 与strut区别

class默认访问权限是私有的,而class是公有的

#include<iostream>
using namespace std;

//class 默认公有
//struct 默认私有

class C1 {
	int m_A;//默认私有
};

struct S1{
	int m_B;//默认公有
};

int main() {

	C1 c1;
	S1 s1;
	s1.m_B = 1;
	//c1.m_A = 1;//m_A是私有,类外不可访问

	return 0;
}

成员属性设为私有

成员属性设为私有,类外通过公有成员函数对私有成员变量进行读写操作,优势如下:

  1. 可以自己控制读写权限
  2. 对于写权限,可以检测写入数据的有效性
#include <iostream>
using namespace std;

/*成员属性私有的优势
1.可以自己控制读写权限
2.对于写权限,可以检测写入数据的有效性*/

class Person{

	//姓名
	string m_Name;
	//年龄
	int m_Age;

public://对外的接口,用以设置和获取成员变量值
	void setName(string name) {
		m_Name = name;
	}
	void setAge(int age) {

		//这里可以判断输入数据是否有效
		if (age < 0 || age>120) {
			age = 0;
			cout << "输入失败" << endl;
			//也可以只要上面的age=0;错误输入,就赋值0,
			return;//这里如果是无效数据,会return掉,相当于没赋值
		}
		m_Age = age;
	}
	void showPerson() {
		cout << "姓名:" << m_Name << "\t年龄:" << m_Age << endl;
	}
	string getName() {
		return m_Name;
	}
	int getAge() {
		return m_Age;
	}

};

int main() {

	Person p;
	p.setAge(200);
	p.setName("张三");
	//p.showPerson();
	cout << p.getName() << "的年龄为" << p.getAge() << endl;

	return 0;
}

案例:点类和圆类

设计一个点类和圆类,判断点和圆的关系。
要点:类的分文件编写

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

//头文件的最上面要防止头文件重复包含
//头文件需要类、成员变量定义以及成员函数的声明即可

class Point {

	double m_X, m_Y;//成员变量

public:
	void setPoint(double x, double y);//成员函数声明
	double getPoint_X();
	double getPoint_Y();
};
  • Point.cpp
#include "Point.h"

//源文件需要包含头文件
//只需要成员函数的实现即可,类和成员函数定义都不需要
//但还是不够,要在函数前加上作用域Point::,否则不认识成员函数

void Point::setPoint(double x, double y) {//源文件只需要成员函数实现
	m_X = x;
	m_Y = y;
}
double Point::getPoint_X() {
	return m_X;
}
double Point::getPoint_Y() {
	return m_Y;
}

接着是距离计算函数,用来计算两点之间的距离

  • getDistance.h
#pragma once
#include "Point.h"

double getDistance(Point p1, Point p2);
  • getDistance.cpp
#include "getDistance.h"

double getDistance(Point p1, Point p2) {
	double x, y;
	x = pow((p1.getPoint_X() - p2.getPoint_X()), 2);
	y = pow((p1.getPoint_Y() - p2.getPoint_Y()), 2);
	return sqrt(x + y);
}
  • Circle.h
#pragma once
#include <iostream>
#include "Point.h"
#include "getDistance.h"

using namespace std;

class Circle {

	Point m_Pc;
	double m_Ra;

public:

	void setPc(Point p);

	void setRadius(double r);
	Point getPc();
	double getRa();

	void Point2Circle(Point p);
};

  • Circle.cpp
#include "circle.h"

//注意作用域circle::
void Circle::setPc(Point p) {
	m_Pc.setPoint(p.getPoint_X(), p.getPoint_Y());
}

void Circle::setRadius(double r) {
	m_Ra = r;
}
Point Circle::getPc() {
	return m_Pc;
}
double Circle::getRa() {
	return m_Ra;
}

void Circle::Point2Circle(Point p) {

	int ref = getDistance(this->m_Pc, p);

	cout << "点的坐标:(" << p.getPoint_X() << "," << p.getPoint_Y() << ")" << endl;
	cout << "圆心坐标:(" << this->m_Pc.getPoint_X() << "," << this->m_Pc.getPoint_Y() << ")" << endl;
	cout << "圆的半径:" << this->m_Ra << endl;
	cout << "点到圆心的距离:" << ref << endl;
	if (ref > this->m_Ra) {
		cout << "点在圆外" << endl;
	}
	else if (ref == this->m_Ra) {
		cout << "点在圆上" << endl;;
	}
	if (ref < this->m_Ra) {
		cout << "点在圆内" << endl;;
	}
}
  • main函数
#include <iostream>
#include "Circle.h"//这里是分文件编写之后包含的圆类头文件
using namespace std;

int main() {

	Point p1,p2;
	p1.setPoint(0, 0);

	p2.setPoint(20,0);

	Circle c1;
	c1.setPc(p2);
	c1.setRadius(20);
	c1.Point2Circle(p1);


	return 0;
}

对象的初始化和清理

  • 构造函数:创建对象时为对象的成员属性赋值,构造函数有编译器自动调用,无须手动调用
  • 析构函数:对象销毁前,系统自动调用,执行一些清理工作
构造函数析构函数
没有返回值,不写void没有返回值,不写void
函数名与类名相同函数名与类名不同
调用对象时由程序自动调用,且只调用一次对象销毁时由程序自动调用,且只调用一次
可以有参数,可重载不可以有参数,不能重载

构造函数 类名(){}

无参构造函数,如果是空实现,未对成员变量初始化,会有如下报错。
解决方式:初始化成员变量
在这里插入图片描述

  • 按照有无参数分为无参构造和有参构造,其中无参构造又称为默认构造
  • 按类型可分为普通构造和拷贝构造
  • 三种调用方法,推荐括号法
#include <iostream>
using namespace std;

/*构造函数*/

class Person {

public:
	Person() {
		cout << "Person无参构造函数调用" << endl;
	}
	Person(int a) {
		this->m_Age = a;
		cout << "Person有参构造函数调用" << endl;
	}

	Person(const Person &p) {
		this->m_Age = p.m_Age;
		cout << "Person拷贝构造函数调用" << endl;
	}

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

	int m_Age;
};

void test01() {

	//调用:
	//1.括号法
	//Person p;//无参构造函数调用
	//Person p2(20);//有参构造函数调用
	//Person p3(p2);//拷贝构造函数,把p2的数据拷贝到p3

	//cout << "p2年龄:" << p2.m_Age << endl;
	//cout << "p3年龄:" << p3.m_Age << endl;

	//注意事项1,调用无参构造不要加()
	//Person p();// 编译器会以为是函数声明

	//2.显示法
	//Person p4;//无参构造
	//Person p5 = Person(3);//有参构造,p5是对象的名
	//Person p6 = Person(p5);//拷贝构造

	//Person(55);//匿名对象,当前行结束后,就被释放

	//注意事项2,不要用拷贝构造初始化匿名对象
	//Person(P5);//编译器会以为是实例化了一个对象 Person p5;

	//3.隐式构造法
	Person p7 = 10; //相当于Person p7 = Person(10);
}

int main() {

	test01();

	system("pause");
	return 0;
}

拷贝构造函数调用时机

拷贝函数调用时机

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

/*拷贝函数调用时机:
1.使用一个已经创造完毕的对象来初始化一个新对象
2.值传递的方式给函数参数传值
3.值方式返回局部对象*/

class Person {

public:
	Person() {
		cout << "Person默认构造函数" << endl;
	}
	Person(int age) {
		this->m_Age = age;
		cout << "Person有参构造函数" << endl;
	}
	Person(const Person &p) {
		this->m_Age = p.m_Age;
		cout << "Person拷贝构造函数" << endl;
	}
	~Person() {
		cout << "Person析构造函数" << endl;
	}
	int m_Age;
	
};

//1.使用一个已经创造完毕的对象来初始化一个新对象
void test01() {
	Person p1(20);
	Person p2(p1);
}
//2.值传递的方式给函数参数传值、

void doWork(Person p) {
	//这里值传递的时候发生了拷贝,加了&就没有
}
void test02() {
	Person p;
	doWork(p);
}


//3.值方式返回局部对象 
Person dowork2() {
	Person p1;
	cout << (int)&p1 << endl;
	return p1;
}
void test03() {
	Person p = dowork2();
	cout <<(int) &p << endl;//p1和p地址不同,说明不是一个东西,发生了拷贝
}

int main() {

	//test01();
	//test02();
	test03();

	system("pause");
	return 0;
}

构造函数调用规则

  1. 默认情况下,编译器提供默认构造函数(无参,函数体为空)、默认析构函数(无参,函数体为空)和默认拷贝构造函数。
  2. 无参构造<有参构造<拷贝构造,如果用户定义了有参构造,则c++不提供无参构造,但还会提供拷贝构造;如果用户定义了拷贝构造,则编译器不提供构造函数。如果编译器不提供,自己又不去定义,就会报错。
#include <iostream>
using namespace std;

/*这是一个错误示范!*/

class Person {

public:
	int m_Age;
	//Person() {
	//	cout << "Person默认构造函数调用" << endl;
	//}
	Person(int age) {
		this->m_Age = age;
		cout << "Person有参构造函数调用" << endl;
	}
	~Person() {
		cout << "Person默认析构函数调用" << endl;
	}

};

void test01() {

	Person p;//因为自己定义了有参构造,所以系统不再提供无参构造,在注释掉自己写的无参构造之后,会报错找不到可用的无参构造。
}

int main() {

	test01();

	system("pause");
	
	return 0;
}

深拷贝与浅拷贝

  • 浅拷贝:编译器提供的那种简单的等号赋值操作,存在堆区内存重复释放的缺点
  • 深拷贝:在堆区重新申请一块内存空间,进行拷贝操作
    因此,如果有属性在读取开辟,要自己提供深拷贝,防止堆区内存重复释放
#include <iostream>
using namespace std;

/*深拷贝与浅拷贝
浅拷贝:编译器提供的那种简单的等号赋值操作,存在堆区内存重复释放的缺点
深拷贝:在堆区重新申请一块内存空间,进行拷贝操作*/

class Person {

public:
	int m_Age;//年龄
	int *m_Height;//身高
	Person() {
		cout << "Person默认构造函数调用" << endl;
	}
	Person(int age,int height) {
		this->m_Age = age;
		//int *m_Height=new int(height);
		this->m_Height = new int(height);
		cout << "Person有参构造函数调用" << endl;
	}
	//这里如果没有提供拷贝构造函数,由编译器提供,则为浅拷贝
	Person(const Person& p) {
		this->m_Age = p.m_Age;
		//this->m_Height = p.m_Height;//编译器默认的写法,是浅拷贝
		//*p.m_Height,地址解引用得到身高数据
		//在堆区开辟一块内存存储这个数据,返回的是一个指针
		//再用this的m_Height去接收这个指针
		//这样两个对象就不再指向一块地址
		this->m_Height = new int(*p.m_Height);

	}
	~Person() {
		//析构函数可以释放堆区开辟的数据

		if (m_Height!=NULL){

			delete m_Height;
			m_Height = NULL;
		}		
		
		cout << "Person默认析构函数调用" << endl;
	}

};

void test01() {

	Person p1(15,160);
	//m_Height是一个指针,需要解引用
	cout << "p1年龄:" << p1.m_Age << ",p1的身高:" << *p1.m_Height << endl;
	Person p2(p1);
	cout << "p2年龄:" << p2.m_Age << ",p2的身高:" << *p2.m_Height << endl;
}

int main() {

	test01();

	system("pause");
	
	return 0;
}

在这里插入图片描述

初始化列表

就是给属性赋初值的一种方法,个人感觉传统的有参构造更好用

#include <iostream>
using namespace std;

/*初始化属性
构造函数():属性1(值1),属性2(值2)···{}*/

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

void test02() {
	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();
	test02();

	system("pause");
	return 0;
}

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

结论:如果A类中由B类成员,则会先构造B,再构造A,析构时则相反
这里两个程序,主要展示了初始化成员列表以及构造函数调用法则。

  • 初始化成员列表,没有用到Person默认构造
#include <iostream>
#include <string>
using namespace std;

//类对象作为类成员
//类A嵌套类B,先构造B,再构造A,析构时则相反。

class Phone {

public:
	Phone() {
		cout << "Phone默认构造" << endl;
	}

	Phone(string brand) {
		cout << "phone有参构造" << endl;
		m_Brand = brand;
	}
	~Phone() {
		cout << "Phone析构函数调用" << endl;
	}

	//属性
	string m_Brand;
};
class Person {

public:

	//Person(string name, string brand) {
	//	cout << "Person有参构造函数调用" << endl;
	//	m_Phone = Phone(brand);
	//	m_Name = name;
	//}
	~Person() {
		cout << "Person析构函数调用" << endl;
	}

	Person(string name, string brand):m_Name(name), m_Phone(brand){
		cout << "Person参数列表初始化" << endl;
		//原理如下
		//m_Phone = brand;//赋值的对应关系
		//Phone m_Phone = brand;//隐式构造法
		//Phone m_Phone = Phone(brand);//显示法
		//Phone m_Phone(brand);//括号法
	}


	//属性
	string m_Name;
	Phone m_Phone;

};

void test01() {
	Person p("张三", "诺基亚");
	cout << p.m_Name << "拿着" << p.m_Phone.m_Brand << endl;
}

int main() {

	test01();


	system("pause");
	return 0;
}
  • 不用初始化成员列表也能写,只用到了Phone的默认构造
#include <iostream>
#include <string>
using namespace std;

//类对象作为类成员

class Phone {

public:
	Phone() {
		cout << "Phone默认构造" << endl;
	}

	Phone(string brand) {
		cout << "phone有参构造" << endl;
		m_Brand = brand;
	}
	~Phone() {
		cout << "Phone析构函数调用" << endl;
	}

	//属性
	string m_Brand;
	};
	class Person {

	public:

	Person(string name, string brand){
		cout << "Person有参构造函数调用" << endl;
		m_Phone = Phone(brand);
		m_Name = name;
	}
	~Person() {
		cout << "Person析构函数调用" << endl;
	}
	
	//Person(string name, string brand):m_Name(name), m_Phone(brand){
	//	//原理如下
	//	//m_Phone = brand;//赋值的对应关系
	//	//Phone m_Phone = brand;//隐式构造法
	//	//Phone m_Phone = Phone(brand);//显示法
	//	//Phone m_Phone(brand);//括号法
	//}


	//属性
	string m_Name;
	Phone m_Phone;
	
};

void test01() {
	Person p("张三", "水果");
	cout << p.m_Name << "拿着" << p.m_Phone.m_Brand << endl;
}

int main() {

	test01();


	system("pause");
	return 0;
}

静态成员,static

包括静态成员变量和静态成员函数

静态成员变量

  1. 所有对象共享一份数据
  2. 在编译阶段分配内存,全局区
  3. 类内声明,类外初始化
  4. 依然受到访问权限约束
#include <iostream>
using namespace std;

/*静态成员变量
1.所有对象共享一份数据
2.在编译阶段分配内存,全局区
3.类内声明,类外初始化
4.依然有访问权限*/

class Person {

public:
	//静态变量,类内声明,
	static int m_A;
};

//类外初始化,去掉static,加上作用域Person::

int Person:: m_A=100;

void test01() {

	Person p;
	cout << "p.m_A=:" << p.m_A << endl;

	Person p2;
	p2.m_A = 200;//所有对象共享一份数据
	cout << "p.m_A=:" << p.m_A << endl;

	//也能通过作用域访问
	cout << "m.A=" << Person::m_A << endl;
	
}

int main() {

	test01();


	system("pause");
	return 0;
}

静态成员函数

成员函数前缀statice

  1. 所有对象共享同一个函数
  2. 静态成员函数只能访问静态成员变量
  3. 也受到访问权限限制
#include <iostream>
using namespace std;

//静态成员函数,成员函数前缀statice
//1.所有对象共享同一个函数
//2.静态成员函数只能访问静态成员变量
//3.也受到访问权限限制

class Person {

public:

	static void fun() {

		m_A = 100;//因为静态成员函数是共享的,而非静态属于特定的对象
		//m_B = 200;//静态成员函数只能访问静态成员变量

		cout << "static void fun()调用" << endl;
	}
	//类内声明
	static int m_A;
	int m_B;
};
//类外初始化
int Person::m_A = 100;

void test01() {

	//静态成员函数两种访问方式
	//1.对象访问
	Person p;
	p.fun();

	//2.类名访问
	Person::fun();
}

int main() {

	test01();

	system("pause");
	return 0;
}

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

  • 空的类,就是{}里面啥也不写,实例化的对象占空间大小为1
  • 实例化对象所占空间大小只和非静态成员变量有关,本例占8,是因为两个int
#include <iostream>
using namespace std;

//成员函数和成员变量分开存储
//空的类,就是{}里面啥也不写,实例化的对象占空间大小为1
//实例化对象所占空间大小只和非静态成员变量有关,本例占8,是因为两个int


class Person {

public:
	int m_A = 1;//非静态成员变量,属于类对象
	int m_b = 1;//非静态成员变量,属于类对象
	static int m_B;//静态成员变量,不属于类对象

	void func1() {}//非静态成员函数,不属于类对象
	static void func2() {}//静态成员函数,不属于类对象
};
int Person::m_B = 1;

void test01() {

	Person p;
	//空对象占用内存空间大小为1,且每个空对象内存空间是独一无二的
	cout << "p占用内存空间:" << sizeof(p) << endl;

}

int main() {

	test01();

	system("pause");
	return 0;
}

this指针

谁调用就指向谁

  1. 当形参变量和成员变量重名时,用this加以区分,最好还是做好命名规范,别给自己找麻烦
  2. 在类的非静态成员函数中返回对象本身,可用return *this,且函数返回值类型是类名&,也就是引用的方式
  3. 如果2中没有引用的方式返回,则发生了拷贝,返回的不是自身
#include <iostream>
using namespace std;

//1.解决名称冲突,最好做好命名规范,方便区分
//2.返回对象本身

/*!!注意,例子里面有个Person &,简单理解就是,return * 返回的是一个自身一样的类,
加&,就是更新了原件的值,后面继续对原件操作,没有&就是复制了一份,后面就是对副本操作,而不是原件*/

class Person {

public:

	Person(int age) {
		//this指针用来区分成员变量和形参
		this->age = age;
	}
	//这种写法,只能加一次
	//void PersonAddAge(Person& p) {
	//	this->age += p.age;
	//}
	//返回的是自身,Person后面如果不加&,则会调用拷贝构造,返回的不再是自身
	Person & PersonAddAge(Person& p) {
		this->age += p.age;
		return *this;//this是个指针,*this解引用
	}

	int age;

};

void test01() {

	Person p1(1);
	cout << "p1年龄:" << p1.age << endl;

	Person p2(1);
	cout << "p2年龄:" << p2.age << endl;

	//把p1的年龄加到p2,加一次可以成功,但是多加几次就不行了
	//如果p2.PersonAddAge(p1)返回的还是p2,就能一直加
	p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
	cout << "p2年龄:" << p2.age << endl;	

}

int main() {

	test01();

	system("pause");
	return 0;
}

空指针访问成员函数

如果定义了一个类的空指针,用空指针调用成员函数可能出现报错。为了防止成员崩溃,应防止传入空指针。if (this == NULL) return;//防止传入的是空指针

#include <iostream>
using namespace std;

//防止传入空指针

class Person {

public:

	void showClassName() {
		cout << "Person类" << endl;
	}
	void showPersonAge() {

		if (this == NULL) return;//防止传入的是空指针
		
		cout << "age:" << this->m_Age << endl;
	}

	int m_Age;
};

void test01() {

	Person* p = NULL;
	p->showPersonAge();//传入的指针是NULL
	p->showClassName();
}

int main() {

	test01();

	system("pause");
	return 0;
}

const修饰成员函数,常函数

  • 常函数 函数名() const {}
  • 常函数中不能修改成员变量的值
  • 如果要改值,在定义成员变量时,前缀关键字mutable
  • 实例化对象时前缀const,常对象,只能调用常函数
  • 同样的,常对象只能修改mutable修饰的成员变量
#include <iostream>
using namespace std;

//常函数  函数名() const {}
//常函数中不能修改成员变量的值
//如果要改值,在值定义时,前缀关键字mutable
class Person {

public:

	//加cosnt防止属性被修改
	//this的本质是指针常量,*+const,指向不能改,但是值能改
	//相当于Person *const this,前缀const,值也不能改了
	void showPerson()const{
		//this = NULL;//指针指向不能修改
		//this->m_A = 100;//加了const,指针指向的值也不能改了
		this->m_B = 200;//该变量受到mutable作用可以改
	}

	void fun() {}//不是常函数

	int m_A;
	mutable int m_B;//特殊变量,即使在常函数中,该值也能修改
};

//常对象
void test01() {
	const Person p;
	//p.m_A = 100;//m_A值不可以修改
	p.m_B = 20;
	p.showPerson();//常对象只能调用常函数
	//p.fun();//常对象不可调用非常函数

	Person p2;
	p2.fun();
	p2.m_A = 100;
	//P2.m_B = 20;//常量值依然不能改,其他不受限制
	p2.showPerson();
	
}


int main() {

	test01();


	system("pause");
	return 0;
}

友元

类里面的访问权限中,private作用下的成员变量是类外访问不到的。但是,如果是友元就可以给他访问权限,有以下两种情况:

全局函数作友元

你有一个建筑类,里面有客厅和卧室,普通客人只能进客厅,不能访问卧室。但是你可以让好朋友进去,好朋友全局函数就是你建筑类的友元。

  • 在类里面声明全局函数,且前缀friend,不用加public作用域
#include <iostream>
using namespace std;

//类作友元、成员函数作友元、类外定义成员函数

class Building;//先声明有这样一个类
class Goodgay {
public:
	//构造函数
	Goodgay();

	//加一个Goodgay的析构函数
	~Goodgay();

	void visit();//参观函数,访问Building中的属性

	Building* m_building;
};

class Building {
	//包含友元类
	friend class Goodgay;
	//如果只要赋予Goodgay中的某个成员函数访问权限,则
	friend void Goodgay::visit();

	//无参构造声明
	Building();
	//析构声明
	~Building();

public:
	string m_SettingRoom;
private:
	string m_BedRoom;
};

//类外定义所有成员函数
Goodgay::Goodgay() {
	cout << "Goodgay构造函数" << endl;
	//new一个建筑物,返回指针,用成员变量m_building去接收
	m_building = new Building;
}
//自己加了段析构函数
Goodgay::~Goodgay() {
	cout << "Goodgay析构函数" << endl;
	if (m_building != NULL) {
		delete m_building;
		m_building = NULL;
	}
}

void Goodgay::visit() {
	cout << "好基友类正在访问" << m_building->m_BedRoom << endl;
	cout << "好基友类正在访问" << m_building->m_SettingRoom << endl;
}

//Building无参构造赋初值
Building::Building() {
	cout << "Building构造函数" << endl;
	m_BedRoom = "卧室";
	m_SettingRoom = "客厅";
}
Building::~Building() {
	cout << "Building析构函数" << endl;
}

//开始调用
void test01() {
	//创建gg对象,会调用Goodgay的构造函数
	//Goodgay的构造函数里面new了一个Building对象
	//又会调用Building的构造函数,赋初值
	Goodgay gg;
	//gg对象调用visit成员函数
	gg.visit();

}

int main() {

	test01();

	system("pause");
	return 0;
}
  • 根据运行结果显示,先构造了goodgay,再构造了building;释放时也是先释放的goodgay,再释放的building
  • 栈的数据是先进后出的,这里开辟了堆区数据,所以产生了差异
    在这里插入图片描述

类和成员函数作友元

  • 让一个类可以访问另一个类中的私有属性
  • 让一个类中的单个成员函数可以访问另一个类中私有属性
  • 成员函数的特殊写法:类内声明,类外加作用域实现。
  • 特别注意这种嵌套类,声明定义时候的先后顺序
#include <iostream>
using namespace std;

//类作友元、成员函数作友元、类外定义成员函数

class Building;//先声明有这样一个类
class Goodgay {
public:
	//构造函数
	Goodgay();

	void visit();//参观函数,访问Building中的属性

	Building* m_building;
};

class Building {
	//包含友元类
	friend class Goodgay;
	//如果只要赋予Goodgay中的某个成员函数访问权限,则
	friend void Goodgay::visit();

	//无参构造声明
	Building();

public:
	string m_SettingRoom;
private:
	string m_BedRoom;
};

//类外定义所有成员函数
Goodgay::Goodgay() {
	//new一个建筑物,返回指针,用成员变量m_building去接收
	m_building = new Building;
}

void Goodgay::visit() {
	cout << "好基友类正在访问" << m_building->m_BedRoom << endl;
	cout << "好基友类正在访问" << m_building->m_SettingRoom << endl;
}

//Building无参构造赋初值
Building::Building() {
	m_BedRoom = "卧室";
	m_SettingRoom = "客厅";
}

//开始调用
void test01() {
	//创建gg对象,会调用Goodgay的构造函数
	//Goodgay的构造函数里面new了一个Building对象
	//又会调用Building的构造函数,赋初值
	Goodgay gg;
	//gg对象调用visit成员函数
	gg.visit();

}

int main() {

	test01();

	system("pause");
	return 0;
}

运算符重载

重新定义运算符,进行自定义的数据类型的运算

加号运算符重载

比如两个类相加,普通的"+"是无法实现的。可行的做法是,在类里面写一个成员函数,传入一个类,实现类间相加。现在编译器给这个成员函数取了一个名字,叫operator+,当然,而也可以通过全局函数实现加号运算符重载。

#include <iostream>
using namespace std;

//加号运算符重载
//1.通过成员函数
//2.通过全局函数
//实质就是定义了名为operator+的函数,调用时可以按函数来调用,
//也可以直接+

class Person {

public:
	Person() {
		//这是Person的无参构造,如果不提供,会找不到合适的默认构造
		//如果只是声明,不写大括号也不行
	}

	Person(int a,int b) {
		m_A = a;
		m_B = b;
	}
	//成员函数实现加号运算符重载
	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;

};

//全局函数实现运算符重载
//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;
//}

void test01() {

	Person p1(10, 20);
	Person p2(30, 40);
	//成员函数加号运算符重载实质上是这样
	//Person p3 = p1.operator+(p2);
	
	//全局函数运算符重载实质
	//Person p5 = operator+(p1, p2);

	//以上两种方式选一个,同时写会冲突

	//简写就直接加
	Person p4 = p1 + p2;

	cout << "p4.m_A=" << p4.m_A << endl;
	cout << "p4.m_B=" << p4.m_B << endl;
	//cout << "p5.m_A=" << p5.m_A << endl;
	//cout << "p5.m_B=" << p5.m_B << endl;
	
}

int main() {

	test01();

	system("pause");
	return 0;
}

左移运算符重载

  • 左移运算符重载只能利用全局函数实现
  • cout,右击定义,属于ostream,标准的输出流类
#include <iostream>
using namespace std;

//左移运算符重载 operator<<
//只能利用全局函数实现
class Person {

public:
	int m_A;
	int m_B;
};

//转到cout的定义,知其属于ostream,标准输出流类
//cout在全局中只能有一个,所以要引用
ostream& operator<<(ostream &cout,Person &p){
	//这里如果是void不返回,加上endl会报错,所以要返回cout,
	cout << "p.m_A=" << p.m_A <<"\tp.m_B="<<p.m_B<<endl;
	return cout;//这样每次调用返回的都是cout
}

void test01() {

	Person p;
	p.m_A = 1;
	p.m_B = 2;

	cout << p << endl;
}

int main() {

	test01();
	
	system("pause");
	return 0;
}

递增运算符重载

  • 如果一个在类内,一个在内外,占位参数无法区分
  • 前置递增返回的是引用,而后置递增是值,因为后置递增,如果对象被销毁,会出现非法访问的情况,只能返回值
  • cout那里第二个形参不能加引用,否则后置递增报错,原因未知
#include <iostream>
using namespace std;

class MyInt {

public:

	//构造函数,初始化成员变量为0
	MyInt() {
		m_Int = 0;
	}

	//前置递增
	//引用,保证以后操作的都是一个数据,而不是拷贝的数据
	MyInt& operator++() {

		this->m_Int++;//先加1
		return *this;//再返回自身
	}


	//后置递增
	//加个占位参数,实现函数重载,区分前置和后置
	MyInt operator++(int) {
		//先返回结果,但不能直接return,先记录一下
		MyInt temp = *this;		
		//后递增
		this->m_Int++;
		return temp;//再return存储的值
	}

	int m_Int;
};

//注意这里的第二个参数不是引用,如果引用,再后置递增出问题
ostream& operator<<(ostream& cout, MyInt num) {
	cout << num.m_Int << endl;
	return cout;
}

void test01() {
	MyInt p;
	p.m_Int = 0;
	cout << p;
	cout << ++p;
	cout << p;

}

void test02() {
	MyInt myint;
	cout << myint;
	//先运算再加1,这里的运算就是参与输出,直接把myint输出了
	cout << (myint++);
	cout << myint;
}

int main() {

	test01();
	test02();
	
	system("pause");
	return 0;
}

赋值运算符重载

堆区的数据手动开辟,手动释放。编译器提供的直接等号赋值操作,是浅拷贝,对于堆区数据利用浅拷贝,会出现堆区内存重复释放的问题,因此要用深拷贝,重新开辟一块空间,不再指向同一块内存。

  • 两块堆区数据,再赋值前,先将被复制的那一块进行了释放操作。
  • 连等操作要返回自身
#include <iostream>
using namespace std;

//赋值运算符重载,主要利用深拷贝
class Person {

public:

	Person(int age) {
		//把年龄数据开辟到堆区,用指针接收
		m_Age = new int(age);
	}

	//如果返回的是void,则不能实现连等操作
	//必须返回自身
	Person& operator=(Person &p) {
		//这种直接等于就是浅拷贝,
		//this->m_Age = p.m_Age;
		//先判断自己是否有堆区数据,如果有就先释放,再存入
		if (m_Age!=NULL){

			delete m_Age;
			m_Age = NULL;

		}
		m_Age = new int(*p.m_Age);//深拷贝,两个m_Age不指向同一块内存
		return *this;
		
	}

	~Person() {
		//堆区数据手动开辟,手动释放
		if (m_Age != NULL) {
			delete m_Age;
			m_Age = NULL;
		}
	}

	int* m_Age;

};

void test01() {

	Person p1(18);
	cout << "p1的年龄:" << *p1.m_Age << endl;

	Person p2(20);
	cout << "p2的年龄:" << *p2.m_Age << endl;

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

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


int main() {
	
	test01();

	system("pause");
	return 0;
}

函数调用运算符重载 仿函数

对()重载,使用起来像是函数调用,故名仿函数,仿函数使用灵活,可以通过对象调用,也可以通过类名加括号调用,这种调用方式也被称为匿名对象。

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

//使用时非常像函数,因此也叫仿函数
//仿函数使用非常灵活,没有固定写法

class myAdd{

public:

	//重载函数调用运算符
	void operator()(int num1, int num2) {
		cout << num1 + num2 << endl;
	}

};

class myCout {

public:
	void operator()(string str) {
		cout << str << endl;
	}
};

void test01() {
	//实例化对象之后调用
	myAdd myadd;
	myadd(100, 100);

	//匿名对象直接调用
	myAdd()(20, 20);
}

void test02() {
	myCout mycout;
	mycout("hello world!");
}

int main() {

	test02();
	//test01();

	system("pause");
	return 0;
}

继承

继承的作用是可以省去很多重复代码

基本语法

  • class 子类:继承方式 父类
  • 子类也叫派生类,父类也叫基类
  • 先构造父类,再构造子类;析构时顺序相反

下面有个案例,小白和小黑是兄弟,每天一起吃饭睡觉。但是小白喜欢唱歌,小黑喜欢跳舞。吃饭睡觉是两人共有的,作为父类。每个人有自己的喜好,独立完成,作为子类

#include <iostream>
using namespace std;

//继承可以节省很多重复代码的写法
//语法:class 子类:继承方式 父类
//子类 也叫派生类
//父类 也叫基类

//举个例子,小白和小黑是兄弟,每天一起吃饭睡觉。
//但是小白喜欢唱歌,小黑喜欢跳舞
//吃饭睡觉是两人共有的,作为父类
//每个人有自己的喜好,独立完成,作为子类

//先构造父类,再构造子类,析构时顺序相反

//创建一个父类
class Base {

public:
	Base() {
		cout << "父类构造" << endl;
	}
	~Base() {
		cout << "父类析构" << endl;
	}

	void eat() {
		cout << "吃饭" << endl;
	}
	void sleep() {
		cout << "睡觉" << endl;
	}
};

//创建一个小白类,以公有的方式继承父类中的类容
class Bai :public Base {

public:
	Bai() {
		cout << "子类构造" << endl;
	}
	~Bai() {
		cout << "子类析构" << endl;
	}
	void sing() {
		cout << "唱歌" << endl;
	}
};
//创建一个小黑类,以公有的方式继承父类中的类容
class Hei :public Base {

public:
	void dance() {
		cout << "跳舞" << endl;
	}
};

void test01() {
	
	//实例化对象,调用
	Bai bai;
	bai.eat();
	bai.sing();
	bai.sleep();

	//Hei hei;
	//hei.eat();
	//hei.dance();
	//hei.sleep();
	
}

int main () {

	test01();

	system("pause");
	return 0;
}

继承方式

如下表所示:横向为父类中属性的访问权限,纵向为子类的继承方式,表格中为子类继承后属性的访问权限。

继承方式 \父类公有保护私有
公有公有保护无权访问
保护保护保护无权访问
私有私有私有无权访问

父类中的元素,有三种访问权限:公有、保护、私有
子类的继承方式也有三种:公有、保护、私有

  • 父类中的私有内容,无论那种继承方式,子类都访问不到
  • 公有继承,父类 公有和保护 子类 仍保持原访问权限
  • 保护继承,父类 共有和保护 子类 全变成保护
  • 私有继承、父类 公有和保护 子类 全变成私有
#include <iostream>
using namespace std;

//父类中的元素,有三种访问权限:公有、保护、私有
//子类的继承方式也有三种:公有、保护、私有
//父类中的私有内容,无论那种继承方式,子类都访问不到
//公有继承,父类 公有和保护 子类 仍保持原访问权限
//保护继承,父类 共有和保护 子类 全变成保护
//私有继承  父类 公有和保护 子类 全变成私有

class A {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class B1 :public A {
	//不能直接赋值,得通过函数
	void fun() {
		m_A = 100;//公有
		m_B = 100;//保护,类内可以访问
		//m_C = 100;//无权访问
	}
};

class B2 :protected A {

	void fun() {
		m_A = 100;//保护,类内可以访问
		m_B = 100;//保护,类内可以访问
	}

}; 

class B3 :private A {

	void fun() {
		m_A = 100;//私有,类内可以访问
		m_B = 100;//私有,类内可以访问
	}

};

class C :public B3 {

	void fun() {
		//m_A = 100;//无权访问
		//m_B = 100;//无权访问
	}
};

void test01() {
	B1 b1;
	b1.m_A = 100;//只能访问b1

	B2 b2;
	B3 b3;
	
	
	
}


int main() {


	system("pause");
	return 0;
}

继承中的对象模型

  • 父类中所有非静态成员都被子类继承下来,
  • 父类中的私有属性,子类访问不到,但是也被继承了下来
  • 利用vs自带工具查看类
#include <iostream>
using namespace std;

//父类中所有非静态成员都被子类继承下来,
//父类中的私有属性,子类访问不到,但是也被继承了下来

//利用开发人员命令提示工具查看对象模型
//跳转盘符  E:
//跳转到文件路径 cd 文件路径
//查看 cl /d1 reportSingleClassLayout类名 "c27 继承中的对象模型.cpp"
//注意 第一个是字母l,第二个是数字1 报告单个类布局类名 加具体文件名,按Tab补齐

class A {

	int m_A;
public:
	int m_B;
protected:
	int m_C;
};

class B :public A {

public:
	int m_D;
};

void test01() {

	B b;
	cout << "b占用和内存:" << sizeof(b) << endl;
}

int main() {

	test01();

	system("pause");
	return 0;
}

工具截图,类B占16字节,父类是A,继承了m_A、m_B、m_C,定义了m_D
在这里插入图片描述

同名成员

如果子类和父类中有同名成员,包括变量和函数

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

同名静态成员

至于非静态成员,既可以通过对象调用,也可以类名的方式访问。在通过类名的方式访问的时候,子类名::父类名::同名静态成员

多继承

一个子类可以继承多个父类,但是可能引起重名问题,需通过作用域加以区分。不推荐使用这种方式。

class 子类:继承方式 父类1,继承方式 父类2···{}

菱形继承与虚继承

一个父类派生两个子类,又有一个(孙子)类同时继承了这两个子类,就是菱形继承,也叫钻石继承。

  1. 如果两个子类有同名成员,可以通过作用域区分,但是也造成了无意义的资源浪费
  2. 利用虚继承可以解决1的问题,在继承方式和父类名之间加关键字virtual
#include <iostream>
using namespace std;

//虚继承实质是一个虚基类指针vbptr

//基类 动物类,虚继承之后就是虚基类
//
class Animal{

public:
	int m_Age;
};

//在继承方式和父类名之间加关键字virtual
//子类1 🐏
class Sheep :public virtual Animal {};
//子类2 驼
class Tuo :public virtual Animal {};

//孙子类同时继承了两个子类
class SheepTuo :public Sheep, public Tuo {};

void test01() {
	SheepTuo st;
	//不采用虚继承,就继承了两份,且访问时要加作用域
	//st.Sheep::m_Age = 8;
	//st.Tuo::m_Age = 10;
	//cout << "st.Sheep::m_Age:" << st.Sheep::m_Age << endl;
	//cout << "st.Tuo::m_Age:" << st.Tuo::m_Age << endl;

	//加了虚继承之后,就只剩一份数据了,不要作用域就可以访问
	st.m_Age = 6;
	cout << "st.m_Age:" << st.m_Age << endl;
}

int main() {

	test01();

	system("pause");
	return 0;
}

不用虚继承
在这里插入图片描述

虚基类指针,通过指针指向数据
在这里插入图片描述

多态

多态是c++面向对象三大特性之一。
多态分为两类:

  • 静态多态:函数重载和运算符重载,
  • 动态多态:派生类和虚函数实现运行时多态
    两类多态区别:
  • 静态多态:函数地址早绑定,编译阶段确定函数地址
  • 动态多态:函数地址晚绑定,运行阶段确定函数地址

动态多态

动态多态条件:

  1. 有继承关系
  2. 子类重写父类虚函数,重写不是重载,重写是函数返回值类型 函数名 参数列表完全相同

一个案例:
父类是动物类,猫狗是子类,有同名函数。有一个函数,父类指针指向子类对象,传入猫狗,就运行猫狗的函数,而不是父类中的函数

#include <iostream>
using namespace std;

//父类是动物类,猫狗是子类,有同名函数
//一个函数,父类指针指向子类对象,传入猫狗,就运行猫狗的函数


class Animal {

public:
	//加了virtual就是虚函数,地址晚绑定,传谁就是谁说话
	virtual void speak() {
		cout << "动物在说话" << endl;
	}
};

class Cat:public Animal{

public:
	//Cat 里面有个同名函数
	//子类重写父类虚函数,函数返回值类型 函数名 参数列表完全相同
	void speak() {
		cout << "猫在说话" << endl;
	}

};

class Dog :public Animal {

public:
	//Dog 里面有个同名函数
	void speak() {
		cout << "狗在说话" << endl;
	}

};


//执行说话的函数,要求传入动物
//Animal &animal=cat;
//父类引用指向子类对象,运行父子之间的类型转换
void doSpeak(Animal &animal) {
	//父类中没有虚函数,就是地址早绑定,不管传猫传狗,最后都是调用的animal里的speak
	animal.speak();
}

void test01() {
	Cat cat;
	//cat.speak();
	//cat.Animal::speak();
	doSpeak(cat);

	Dog dog;
	doSpeak(dog);
}

int main() {

	test01();

	system("pause");
	return 0;
}

案例 多态计算机

普通的计算器写法和多态计算器写法
多态调用:父类指针或者引用指向子类对象

#include <iostream>
using namespace std;

//分别用普通写法和多态实现计算器
//开发中讲求开闭原则:对扩展开放,对修改关闭

//普通写法
class Calculator {

public:

	int getResult(char oper) {

		switch (oper)
		{
		case '+':
			return m_Num1 + m_Num2;
			break;
		case '-':
			return m_Num1 - m_Num2;
			break;
		case '*':
			return m_Num1 * m_Num2;
			break;
		case '/':
			return m_Num1 / m_Num2;
			break;
		default:
			break;
		}
	}

	//两个操作数
	int m_Num1;
	int m_Num2;

	//操作符
	char oper;

};

//2.多态写法
class Base_JiSuanQi {

public:
	//多态不是这样写的,都不用传oper
	//virtual void getResult(char oper) {
	//	return m_Num1 oper m_Num2;
	//}
	virtual int getResult() {
		return 0;//先输出一个0
	}
	//两个操作数
	int m_Num1;
	int m_Num2;
};

//专门的加法类
class son_Add :public Base_JiSuanQi {
public:
	//子类重写父类虚函数,关键字virtual可以不要
	int getResult() {
		return m_Num1+m_Num2;
	}
};

//专门的减法类
class son_Sub :public Base_JiSuanQi {
public:
	//子类重写父类虚函数,关键字virtual可以不要
	int getResult() {
		return m_Num1 - m_Num2;
	}
};

//专门的乘法类
class son_Mul :public Base_JiSuanQi {
public:
	//子类重写父类虚函数,关键字virtual可以不要
	int getResult() {
		return m_Num1 * m_Num2;
	}
};

//专门的除法类
class son_Div :public Base_JiSuanQi {
public:
	//子类重写父类虚函数,关键字virtual可以不要
	int getResult() {
		return m_Num1 / m_Num2;
	}
};

void test01() {

	cout << "**计算器普通写法**" << endl;

	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;
	cout << c.m_Num1 << "/" << c.m_Num2 << "=" << c.getResult('/') << endl;

}

void test02() {

	cout << "**多态计算器**" << endl;

	//多态使用条件:
	//父类指针或引用指向子类对象
	
	//引用的方式
	/*son_Add son;
	Base_JiSuanQi& js = son;
	js.m_Num1 = 10;
	js.m_Num2 = 10;
	cout << js.m_Num1 << "+" << js.m_Num2 << "=" << js.getResult() << endl;*/
	
	//父类指针指向子类对象
	Base_JiSuanQi* js = new son_Add;
	js->m_Num1 = 10;
	js->m_Num2 = 10;
	cout << js->m_Num1 << "+" << js->m_Num2 << "=" << js->getResult() << endl;
	//用完销毁
	delete js;

	//再指向减法
	js = new son_Sub;
	//数据被清空,重新赋值
	js->m_Num1 = 10;
	js->m_Num2 = 10;
	cout << js->m_Num1 << "-" << js->m_Num2 << "=" << js->getResult() << endl;
	delete js;

	//再指向乘法
	js = new son_Mul;
	//数据被清空,重新赋值
	js->m_Num1 = 10;
	js->m_Num2 = 10;
	cout << js->m_Num1 << "*" << js->m_Num2 << "=" << js->getResult() << endl;
	delete js;

	//再指向除法
	js = new son_Div;
	//数据被清空,重新赋值
	js->m_Num1 = 10;
	js->m_Num2 = 10;
	cout << js->m_Num1 << "/" << js->m_Num2 << "=" << js->getResult() << endl;
	delete js;

}

int main() {

	//test01();
	test02();

	system("pause");
	return 0;
}

纯虚函数和多态类 虚析构与纯虚析构

因为父类中的虚函数实现毫无意义,基本用不到,主要调用的是子类中的内容。因此可以将虚函数写为纯虚函数。
纯虚函数语法:

virtual 返回值类型 函数名 (参数列表)= 0

当函数中有了纯虚函数,这个类也叫抽象类
抽象类特点:

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

虚析构与纯虚析构
虚析构稍微方便一点

  • 如果子类有数据开辟到堆区,父类的指针无法释放子类对象,解决办法是在父类析构函数前缀virtual。
  • 纯虚析构在父类中声明之后,类外要实现。且纯虚析构,类也是抽象类
  • 二者只能有一个
#include <iostream>
using namespace std;
#include <string>

//因为父类中虚函数用不到,直接写成纯虚函数

class baseAnimal {

public:
	baseAnimal() {
	
		cout << "baseAnimal构造函数" << endl;
	}
	//虚析构,如果没有虚析构,不会执行~Cat
	//virtual ~baseAnimal() {

	//	cout << "baseAnimal析构函数" << endl;
	//}

	//纯虚析构声明
	virtual ~baseAnimal() = 0;
	virtual void speak() = 0;
};

//纯虚析构也要实现,防止父类也有对象开辟到堆区
baseAnimal::~baseAnimal() {

	cout << "baseAnimal纯虚析构函数" << endl;
}

class Cat :public baseAnimal {

public:

	Cat(string name) {
		cout << "Cat构造函数" << endl;
		m_Name = new string(name);
	}
	~Cat() {
		//因为Cat里面开辟了堆区数据,所以要手动释放
		cout << "Cat析构函数" << endl;
		if (m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}

	//Cat 里面有个同名函数
	void speak() {
		cout <<*m_Name<< "猫在说话" << endl;
	}
	string* m_Name;

};

class Dog :public baseAnimal {

public:
	//Dog 里面有个同名函数
	void speak() {
		cout << "狗在说话" << endl;
	}

};
void doSpeak(baseAnimal* animal) {
	
	animal->speak();
	delete animal;
}

void test01() {

	doSpeak(new Cat("Tom"));



	//Animal animal;//抽象类无法实例化对象,堆区栈区都不行
	//new Animal;
	//doSpeak(animal);
}

int main() {

	test01();

	system("pause");
	return 0;
}

案例 制作饮品 父类指针或引用指向子类对象

主要展示了纯虚函数,以及父类指针或者引用指向子类对象的具体实现方式

#include <iostream>
using namespace std;

//一个制作饮品的案例
//父类指针或者引用指向子类对象

class baseDrink {

public:
	//一系列纯虚函数

	//烧水
	virtual void boilWater() = 0;
	//冲泡
	virtual void brew() = 0;
	//倒入杯中
	virtual void pour() = 0;
	//加入辅料
	virtual void add() = 0;	

	//整合一下,就不用虚函数了
	void makeDrink() {
		boilWater();
		brew();
		pour();
		add();
	}
};

//泡茶
class Tea:public baseDrink {
	
public:

	//重写纯虚函数
	void boilWater() {
	
		cout << "第一步:烧水" << endl;
	}
	void brew() {
	
		cout << "第二步:冲泡茶叶" << endl;
	}
	void pour() {
	
		cout << "第三步:倒入杯中" << endl;
	}
	void add() {
	
		cout << "第四步:加点枸杞" << endl;
	}
};

//泡咖啡
class Coffee :public baseDrink {

public:
	void boilWater() {

		cout << "第一步:烧水" << endl;
	}
	void brew() {

		cout << "第二步:冲泡咖啡" << endl;
	}
	void pour() {

		cout << "第三步:倒入杯中" << endl;
	}
	void add() {

		cout << "第四步:加点牛奶糖" << endl;
	}
};

//1.父类指针指向子类对象
void doMakeDrink(baseDrink* bd) {
	bd->makeDrink();
	delete bd;//调用完就删除堆区数据
}

//2.父类引用指向子类对象
void doMakeDrink(baseDrink& bd) {
	bd.makeDrink();
}

void test01() {

	//相当于:baseDrink* bd=new Tea;
	doMakeDrink(new Tea);
	cout << "----------------" << endl;
	doMakeDrink(new Coffee);
	cout << "----------------" << endl;
	//相当于:baseDrink& bd=tea;
	Tea tea;
	doMakeDrink(tea);
	cout << "----------------" << endl;
	Coffee coffee;
	doMakeDrink(coffee);
	cout << "----------------" << endl;


}

int main() {

	test01();

	system("pause");
	return 0;
}

案例 组装电脑

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

//一台电脑有cpu,显卡 内存 三个零件
//创建三个零件对象,再分别创建不同厂商生成零件的子类对象
//组装电脑

/// <summary>
/// 注意,computer不是子类,里面new的对象,只要在其析构函数中释放即可,不需要父类虚析构函数。
/// </summary>

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) {

		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}
	
	//提供工作的函数
	void doWork() {

		m_cpu->calculate();
		m_vc->display();
		m_mem->storage();
	}

	//三个零件的指针
	CPU* m_cpu;
	VideoCard* m_vc;
	Memory* m_mem;

	~Computer() {

		if (m_cpu!=NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}

		if (m_vc != NULL)
		{
			delete m_vc;
			m_vc = NULL;
		}

		if (m_mem != NULL)
		{
			delete m_mem;
			m_mem = NULL;
		}
	}

	
};

//具体的零件厂商
class InterCpu :public CPU {
	
public:
	void calculate() {
	
		cout << "InterCpu正在计算" << endl;
	}
};

class InterVideoCard :public VideoCard {

public:
	void display() {

		cout << "Inter显卡正在显示" << endl;
	}
};

class InterMemory :public Memory {

public:
	void storage() {

		cout << "Inter内存正在存储" << endl;
	}
};

class LenovoCpu :public CPU {

public:
	void calculate() {

		cout << "LenovoCpu正在计算" << endl;
	}
};

class LenovoVideoCard :public VideoCard {

public:
	void display() {

		cout << "Lenovo显卡正在显示" << endl;
	}
};

class LenovoMemory :public Memory {

public:
	void storage() {

		cout << "Lenovo内存正在存储" << endl;
	}
};

void test01() {

	//开始电脑组装

	//第一台电脑零件
	CPU* intercpu = new InterCpu;
	VideoCard* intercard = new InterVideoCard;
	Memory* intermem = new InterMemory;

	//new第一台电脑,放在堆区
	Computer* pc1 = new Computer(intercpu, intercard, intermem);
	pc1->doWork();
	delete pc1;

	cout << "--------------------" << endl;

	//创建第二台电脑
	Computer* pc2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
	pc2->doWork();
	delete pc2;

	cout << "--------------------" << endl;

	//创建第三台电脑
	Computer* pc3 = new Computer(new InterCpu, new LenovoVideoCard, new LenovoMemory);
	pc3->doWork();
	delete pc3;
	
	
}

int main() {

	test01();

	system("pause");
	return 0;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-19 07:48:45  更:2021-09-19 07:50:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 14:08:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计