后槽牙都要给我干稀碎之C++入门学习笔记

C++入门学习笔记-牙齿碎了也得给我咽下去!

  • C++入门学习笔记-牙齿碎了也得给我咽下去!
    • 基础之旅
      • C++编译软件安装
      • 变量Variables
      • 常量
      • 标识符命名规则
      • 数据的输入cin
      • 方法Function
        • 方法定义默认值
        • 方法的占位参数
      • 头文件Header Files
      • 使用Visual Studio 调试(Debug)程序
      • 循环
      • 条件分支
      • 三目运算符
      • switch语句
      • 数组
      • 指针
      • 随机数
      • 类class
        • 封装
        • 继承
          • 继承方式
          • 继承中的对象模型
          • 继承的构造和析构执行顺序
          • 同名成员处理
          • 多继承语法
          • 菱形继承问题以及解决方式
        • 多态
          • 纯虚函数和抽象类
          • 虚析构和纯虚析构
        • 静态成员
      • 类和对象
        • 成员函数和成员变量分开存储
        • this指针概念
          • this指针的用途
          • 空指针访问成员函数
        • const修饰成员函数
        • frend 友元
        • 运算符重载
          • 加号运算符重载
          • 左移运算符重载
          • 递增运算符重载
          • 赋值运算符重载
          • 关系运算符重载
          • 函数调用运算符重载
      • 结构体Structs
      • static
      • 枚举Enums
      • 构造函数Constructors
        • 构造函数的分类
        • 拷贝构造函数使用时机
        • 构造函数调用规则
        • 深拷贝与浅拷贝
        • 初始化列表
        • 类对象作为类成员
      • 析构函数Destructors
      • 继承Inheritance
      • 虚函数Virtual Functions
      • 纯虚函数(接口)
      • 访问控制
        • private 私有
        • public 公有
        • protect 保护
      • C++文件操作
        • 读文件
          • 读文本文件
          • 读二进制文件
        • 写文件
          • 写文本文件
          • 写二进制文件
    • 进阶内容
      • 内存分区模型
        • 程序运行前
        • 程序运行后
          • 栈区
          • 堆区
        • new操作符
      • 引用
        • 引用的注意事项
        • 引用做函数参数
        • 引用做函数返回值
        • 引用的本质
        • 常量引用
    • 练习案例
      • 案例1:基于结构体的控制台通讯录管理系统

C++入门学习笔记-牙齿碎了也得给我咽下去!

基础之旅

C++编译软件安装

变量Variables

变量存在的意义:方便我们管理内存空间
变量创建的语法:数据类型 变量名称 = 变量初始值;

int main()
{
	char c;
	unsigned char uc;
	short s;
	int i;
	long l;
	long long ll;
	float f;
	double d;
}
数据类型 占用空间 取值范围
short 2字节 (-2^15-2^15-1) 即(-32768,32767)
int 4字节 (-2^31-2^31-1)
long windows 为4字节,linux为4字节(32位)、8字节(64位)节 (-2^31-2^31-1)
long long 2字节 (-2^63-2^63-1)
float 4字节 7位有效数字
double 8字节 15-16位有效数字

常量

用于记录程序中不可更改的数据
C++定义常量的两种方式:
①:#define 宏常量:#define 常量名 常量值,通常定义在文件上方

#include 

#define PI 3.14

int main()
{
	std::cout << PI << std::endl;
}

②:const修饰的常量:const 数据类型 常量名 = 常量值,通常在变量定义前加关键字const,修饰该变量为常量,不可修改

#include 

int main()
{
	const float Pi = 3.14;
	std::cout << Pi << std::endl;
}

标识符命名规则

  • 标识符不能是关键字
  • 标识符只能由字母、数字、下划线组成
  • 第一个字符必须是字母或者下划线
  • 标识符中字母区分大小写

数据的输入cin

#include 

int main()
{
	int a;
	std::cout << "请输入一个整数值:" << std::endl;
	std::cin >> a;
	std::cout << "您输入的值为:" << a << std::endl;
}

方法Function

将一段经常使用的代码封装起来,减少重复代码
语法:

返回值类型 函数名(参数列表)
{
函数体语句
返回值语句
}

方法定义默认值
  • 如果一个函数某个位置参数有默认值,那么从这个位置往后所有的参数都必须设定默认值
  • 如果一个函数声明时有默认值,那么函数实现的时候就不能有默参数
方法的占位参数

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

  • 函数占位参数,占位参数也可以有默认值
  • 占位参数调用时,没有默认值则必须填补

头文件Header Files

使用Visual Studio 调试(Debug)程序

循环

while循环

#include 

int main()
{
	std::cout << "模拟while循环,循环输出倒三角,请您输入要输出多少行的三角:" << std::endl;
	int i_line;
	std::cin >> i_line;
	while (i_line > 0)
	{
		int count = i_line;
		while (count >0)
		{
			std::cout << '*';
			count--;
		}
		std::cout <<std::endl;
		i_line--;
	}
}

while循环模拟一个控制台菜单的选择,选择退出后菜单退出,否则仅输出选择了菜单的什么项:

#include 

int main()
{
	while (true)
	{
		std::cout << "请选择:\n a:开始游戏\n b:继续游戏\c c:退出" << std::endl;
		char chose;
		std::cin >> chose;
		if (chose == 'a') {
			std::cout << "您选择了开始游戏" << std::endl;
		}
		if (chose == 'b') {
			std::cout << "您选择了继续游戏" << std::endl;
		}
		if (chose == 'c') {
			std::cout << "您选择了退出" << std::endl;
			break;
		}
	}
}

案例:水仙花数

#include 

int main()
{
	std::cout << "水仙花数案例实现:输出1-10000中所有数的水仙花数" << std::endl;
	int max = 10000;
	int number = 1;
	while (number <= max)
	{
		int sub_number = number;
		int digit_number = 0;
		while (sub_number>0)
		{
			sub_number = sub_number / 10;
			digit_number++;
		}
		int count = 0;
		int digit = digit_number;
		sub_number = number;
		while (digit_number > 0)
		{
			int sub = sub_number - sub_number / 10 * 10;
			count += pow(sub, digit);
			sub_number = sub_number / 10;
			digit_number--;
		}
		if (count == number) {
			std::cout << number << std::endl;
		}
		number++;
	}
}

for循环

#include 

int main()
{
	std::cout << "输出九九乘法表" << std::endl;
	for (int line = 1; line <= 9; line++) 
	{
		for (int col = 1; col <= line; col++)
		{
			std::cout << col << "*" << line << " = " << line * col << "  ";
		}
		std::cout << std::endl;
	}
}

案例:敲桌子,从1开始数到数字100,如果数字个位含有7,或者十位含有7,或者该数字是7的倍数,我们打印敲桌子,否则仅输出该数字

#include 

int main()
{
	std::cout << "从1开始数到数字100,如果数字个位含有7,或者十位含有7,或者该数字是7的倍数,我们打印敲桌子,否则仅输出该数字" << std::endl;
	for (int number = 1; number <= 100; number++) {
		//该数字是7的倍数,我们打印敲桌
		if (number % 7 == 0) 
		{
			std::cout << "敲桌子" << std::endl;
		}
		else if (number % 10 == 7) 
		{
			std::cout << "敲桌子" << std::endl;
		}
		else if ((number/10) % 10 == 7)
		{
			std::cout << "敲桌子" << std::endl;
		}
		else
		{
			std::cout << number << std::endl;
		}
	}
}

条件分支

案例:有三个数字,使用条件分支实现获得三个数字中的最大值

#include 

int main()
{
	int a, b, c;
	std::cout << "请输入第一个数字的值" << std::endl;
	std::cin >> a;
	std::cout << "请输入第二个数字的值" << std::endl;
	std::cin >> b;
	std::cout << "请输入第三个数字的值" << std::endl;
	std::cin >> c;
	std::cout << "您输入的三个数值为" << a <<"," << b << "," << c << std::endl;
	
	//使用条件分支获取最大的数值
	if (a > b) 
	{
		if (a > c) 
		{
			std::cout << "您输入的最大值是:" << a  << std::endl;
		}
		else 
		{
			std::cout << "您输入的最大值是:" << c << std::endl;
		}
	}
	else
	{
		if (b > c) 
		{
			std::cout << "您输入的最大值是:" << b<< std::endl;
		}
		else
		{
			std::cout << "您输入的最大值是:" << c << std::endl;
		}
	}
}

C++中三目运算符返回的是变量,可以继续赋值

#include 

int main()
{
	int a, b, c;
	std::cout << "请输入第一个数字的值" << std::endl;
	std::cin >> a;
	std::cout << "请输入第二个数字的值" << std::endl;
	std::cin >> b;
	
	c = a > b ? a : b = 100;
	std::cout << "a = " << a << ",b = " << b << std::endl;
	std::cout << "您输入的最大的数值为" << c << std::endl;
}

三目运算符

#include 

int main()
{
	int a, b, c;
	std::cout << "请输入第一个数字的值" << std::endl;
	std::cin >> a;
	std::cout << "请输入第二个数字的值" << std::endl;
	std::cin >> b;
	
	c = a > b ? a : b;
	std::cout << "您输入的最大的数值为" << c << std::endl;
}

switch语句

#include 

int main()
{
	char chose;
	std::cout << "请输入您的选择" << std::endl;
	std::cout << "a:仅输出字符a;\nb:输出字符b与字符c;\nc:输出字符c;\n 其它输入则提示错误" << std::endl;
	std::cin >> chose;

	switch (chose)
	{
		case 'a': std::cout << "您输入的是字符a" << std::endl; break;
		case 'b': std::cout << "您输入的是字符b" << std::endl;
		case 'c': std::cout << "您输入的是字符c" << std::endl; break;
	default:
		std::cout << "您输入的字符不符合规范" << std::endl;
	}
}

数组

#include 

void Print(int* array, int length);
void Bubble_Sort(int* array, int length);
void Selection_Sort(int* array, int len);
void Insertion_Sort(int* array, int length);
void Swap(int* array, int index_f, int index_e);

int main()
{
	//一维数组
	int array_1[3];
	array_1[0] = 2;
	array_1[1] = 3;
	array_1[2] = 4;
	int array_2[3] = {1,2,3};
	int array_3[10] = {8,4,3,7,0,2, 1,2,3,4};
	Print(array_3,sizeof(array_3)/sizeof(int));
	Insertion_Sort(array_3, sizeof(array_3) / sizeof(int));
	Print(array_3, sizeof(array_3) / sizeof(int));
	//二维数组 必须指定内部维度数组的项数
	int array_4[][3] = { {1,2},{2,3},{3,4} ,{3,4,3} };
}

/*
 数组打印功能
*/
void Print(int* array,int length)
{
	for (int i = 0; i < length; i++)
	{
		std::cout << array[i] << " ";
	}
	std::cout << std::endl;
}

/*
 冒泡排序
*/
void Bubble_Sort(int* array, int length)
{
	for (int i = 0; i < length; i++)
	{
		for (int j = 0; j < length-1; j++)
		{
			if (array[j] < array[j + 1])
			{
				Swap(array, j, j + 1);
			}
		}
	}
}

/*
 选择排序
*/
void Selection_Sort(int* array, int len)
{
	for (int i = 0; i < len-1; i++)
	{
		int swap_index = i;
		for (int j = i+1; j < len; j++)
		{
			if (array[swap_index] > array[j])
			{
				swap_index = j;
			}
		}
		if (i != swap_index)
		{
			Swap(array, i, swap_index);
		}
	}
}

/*
 插入排序
*/
void Insertion_Sort(int* array, int length)
{
	for (int i = 0; i < length; i++)
	{
		int key = array[i];
		int j = i - 1;
		while (j >= 0 && array[j] > key)
		{
			array[j + 1] = array[j];
			j--;
		}
		array[j + 1] = key;
	}
}

/*
 交换数组两个元素的位置
*/
void Swap(int* array, int index_f, int index_e)
{
	int cache_number = array[index_f];
	array[index_f] = array[index_e];
	array[index_e] = cache_number;
}

案例:一维数组元素逆置

#include 

void Swap(int* array, int index_f, int index_e);
void Print(int* array, int len);

int main()
{
	//案例 一维数组元素逆置
	int array[] = { 1,2,3,4,5,6,7,8,9,10,11,12};
	int len = sizeof(array) / sizeof(array[0]);
	Print(array, len);
	for (int i = 0; i < len; i++)
	{
		if (i >= len - i -1)
		{
			break;
		}
		Swap(array, i, len - i -1);
	}
	Print(array, len);
}

void Swap(int* array, int index_f, int index_e)
{
	std::cout << "swap" << std::endl;
	int cache_number = array[index_f];
	array[index_f] = array[index_e];
	array[index_e] = cache_number;
}

void Print(int* array,int len)
{
	for (int i = 0; i <len; i++)
	{
		std::cout << array[i] << " ";
	}
	std::cout << std::endl;
}

指针

指针的作用:可以通过指针间接访问内存

  • 内存编号是从0开始记录的,一版用十六进制数字表四
  • 可以利用指针变量保存地址
#include 
using namespace std;

int main()
{
	int a = 10;
	int* pointer_a = &a;
	std::cout << &a << std::endl;
	std::cout << "变量a的地址:" << pointer_a  << " 指针pointer_a记录的地址的值为:" << *pointer_a << std::endl;
	std::cout << "指针pointer_a占用的内存:" << sizeof(pointer_a) << std::endl;

}

指针所占的内存大小跟操作系统位数有关:32位操作系统占用4字节,64位操作系统占用8字节

空指针:
指针变量指向内存中的编号为0的空间,用于初始化指针变量,空指针指向的内存是不可以访问到

#include 
using namespace std;

int main()
{
	int* pointer = 0;
	//int* pointer = nullptr;
	//int* pointer = NULL;
	std::cout << pointer << std::endl;
	//运行下面这行代码时,会报错,因为空指针指向的内存是不可以访问的
	//std::cout << *pointer << std::endl;
}

野指针:指针指向非法的内存空间

const修饰指针:

  • const修饰指针 — 常量指针(指针指向的地址可以改变,指向地址的值不可以改变)
  • const修饰常量 —指针常量(指针指向的地址不可以改变,指针指向地址的值可以改变)
  • const既修饰指针,又修饰常量(均不可以更改)

指针和数组

#include 
using namespace std;

int main()
{
	int array[] = { 1,2,3,4,5,6,7,8,9 };
	int* pointer_array = array;
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
	{
		std::cout << pointer_array << " ";
		pointer_array++;
	}
}

指针和函数
值传递(形参会改变,实参不会改变)和地址传递(形参和实参均改变)

随机数

#include 
#include 

int main()
{
	//随机数种子
	srand((unsigned int)time(NULL));
	//循环生成随机数
	for (int i = 0; i < 10; i++)
	{
		//范围 [40,99]
		std::cout << rand() % 60 + 40 << " ";
	}
}

类class

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

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

语法:

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

案例:设计一个圆类,求取圆的周长和面积

#include 

#define PI 3.14

class Circle
{
private:
	float radius;

public:

	//设定圆的半径
	void set_radius(float radius)
	{
		this->radius = radius;
	}

	//求取圆的周长
	float round()
	{
		return 2 * this->radius * PI;
	}

	//求取圆的面积
	float area()
	{
		return this->radius * this->radius * PI;
	}
};

int main()
{
	Circle circle;
	circle.set_radius(12);
	std::cout << "圆的周长:" << circle.round() << std::endl;
	std::cout << "圆的面积:" << circle.area() << std::endl;
}

继承

继承可以减少代码量
class A : public B

A类称之为子类或派生类
B类称之为父类或基类

派生类中的成员包含两部分:

  • 一类是从基类继承过来的,一类是自己增加的成员
  • 从基类继承过来的表现其共性,而新增的成员体现了其个性

继承的语法:

class 子类 : 继承方式 class 父类

继承方式:

  • 公共继承
  • 保护继承
  • 私有继承
继承方式
  • 公共继承
    父类的私有属性不可以被访问
    父类公共属性在子类中依然是公共属性
    父类中保护属性在子类中依然是保护属性
  • 保护继承
    父类的私有属性不可以被访问
    父类公共属性在子类中是保护属性
    父类中保护属性在子类中是保护属性
  • 私有继承
    父类的私有属性不可以被访问
    父类公共属性在子类中是私有属性
    父类中保护属性在子类中是私有属性
#include 
#include 
using namespace std;

class Parent
{
private:
	string password;
public:
	string name;
protected:
	string money;
};

class Children_1 : public Parent
{
public:
	void printf();
};

class Children_2 : protected Parent
{
public:
	void printf();
};

class Children_3 : private Parent
{
public:
	void printf();
};

void Children_1::printf()
{
	std::cout << this->name << std::endl;
	std::cout << this->money << std::endl;
}

void Children_2::printf()
{
	std::cout << this->name << std::endl;
	std::cout << this->money << std::endl;
}

void Children_3::printf()
{
	std::cout << this->name << std::endl;
	std::cout << this->money << std::endl;
}

void printf()
{
	Children_1 children;
	children.name;
	Children_1 children_2;
	children_2.name;
	Children_3 children_3;
}
继承中的对象模型
#include 
#include 
using namespace std;

class Parent
{
private:
	int name;
public:
	int gender;
public:
	//Parent()
	//{
	//	std::cout << "父类的构造函数执行" << std::endl;
	//}
	//~Parent()
	//{
	//	std::cout << "父类的析构函数执行" << std::endl;
	//}
};

class Children : public Parent
{
public:
	
};

int main()
{
	Children chi;
	std::cout << "sizeof chi:" << sizeof(chi) << std::endl;
}

私有成员只是被隐藏了,但是还是会继承下去
父类中所有非静态的成员都会被继承下去,包括同名的成员

查看类的分布情况:

c1 /d1 reportSingleClassLayout + 类名 类所在的文件名

需要打开vs编辑器中开发者提示工具 Developer Command Prompt for VS 2022

cl /d1 reportSingleClassLayoutChildren ConsoleApplication1.cpp

后槽牙都要给我干稀碎之C++入门学习笔记_第1张图片

继承的构造和析构执行顺序
#include 
#include 
using namespace std;

class Parent
{
public:
	Parent()
	{
		std::cout << "父类的构造函数执行" << std::endl;
	}
	~Parent()
	{
		std::cout << "父类的析构函数执行" << std::endl;
	}
};

class Children : public Parent
{
public:
	Children()
	{
		std::cout << "子类的构造函数执行" << std::endl;
	}
	~Children()
	{
		std::cout << "子类的析构函数执行" << std::endl;
	}
};

int main()
{
	Children chi;

}

继承的构造函数和析构函数执行过程图

同名成员处理
  • 访问子类同名成员,直接访问即可
  • 访问父类童名成员,需要添加作用域
#include 
#include 
using namespace std;

class Parent
{
public:
	int gender;
	int name;
};

class Children : public Parent
{
private:
	int name;
public:
	
};

int main()
{
	Children chi;
	//访问父类的同名属性,需要指定作用域
	chi.Parent::name = 123;
}

成员函数同上调用方式!
如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉所有的父类同名的成员函数

多继承语法

C++允许一个类继承多个类,多个类之间使用逗号隔开
语法:class 子类 : 继承方式 父类1, 继承方式 父类2 …

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

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

菱形继承问题以及解决方式

两个派生类继承同一个基类
又有个派生类同时继承两个派生类
这种继承方式称为菱形继承,又称为钻石继承

vbptr 指向虚基类表

#include 
#include 
using namespace std;

class Parent
{
public:
	int gender;
	int name;
};

class Children :virtual public Parent
{
};

class Children_2 :virtual public Parent
{
};

class Grandson : public Children, public Children_2
{
};

int main()
{
	Grandson gd;
	//会存在两份同样的数据,需要加以作用域区分
	//gd.name = 12;
	//菱形继承导致了数据有两份,资源浪费
	//利用虚继承可以解决菱形继承的问题
	//在继承前加上关键字 virtual 变成虚继承
	gd.name = 12;
}
多态

多态分为两类:

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

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

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行时阶段确定函数地址
#include 
#include 
using namespace std;

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


class Cat : public Animal
{
public:
	void speak()
	{
		std::cout << "Cat speak" << std::endl;
	}
};

//地址早绑定,在编译阶段就已经确定了地址
//如果想执行Cat speak,需要地址晚绑定
void demo(Animal& animal)
{
	animal.speak();
}

int main()
{
	Cat cat;
	demo(cat);
}

通过关键字virtual ,方法的地址将实现晚绑定,实现子类的动作行为实现
虚函数子类重写方法可加virtual 关键字也可不加,不影响最终结果

动态多态的满足条件:

  • 有继承关系
  • 子类重写父类的虚函数

虚函数表指针:
父类的虚函数会创建一个虚函数指针,指向虚函数表,表内会记录一个虚函数地址

当子类重写父类的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址
当父类的指针或引用指向子类对象时候,发生多态
后槽牙都要给我干稀碎之C++入门学习笔记_第2张图片
当子类Cat没有重写父类的虚函数时:
后槽牙都要给我干稀碎之C++入门学习笔记_第3张图片

当子类Cat重写了父类的虚函数时:
后槽牙都要给我干稀碎之C++入门学习笔记_第4张图片
案例:使用多态实现一个模拟的计算器类

#include 
#include 
using namespace std;

//计算器类 - 多态实现
class AbstractCalculator
{
public:
	int num_1;
	int num_2;

public:
	virtual int getResult()
	{
		return 0;
	}
};

class AddCalculator : public AbstractCalculator
{
public:
	int getResult()
	{
		return this->num_1 + this->num_2;
	}
};

class SubstructCalculator : public AbstractCalculator
{
public:
	int getResult()
	{
		return this->num_1 - this->num_2;
	}
};

int main()
{
	AbstractCalculator* calculator = new AddCalculator();
	calculator->num_1 = 10;
	calculator->num_2 = 20;

	std::cout << "Calculator result = " << calculator->getResult() << std::endl;

	AbstractCalculator* subcCalculator = new SubstructCalculator();
	subcCalculator->num_1 = 10;
	subcCalculator->num_2 = 20;

	std::cout << "Calculator result = " << subcCalculator->getResult() << std::endl;
}
纯虚函数和抽象类

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

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

class AbstractCalculator
{
public:
	int num_1;
	int num_2;

public:
	//纯虚函数
	//只要类中有一个纯虚函数,那么这个类就是一个抽象类
	virtual int getResult() = 0;
};

抽象类:

  • 无法实例化对象
  • 抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类
    案例:模拟煮茶水和泡咖啡的流程
#include 
#include 
using namespace std;

//计算器类 - 多态实现
class AbstractDrink
{

public:
	virtual void Boid() = 0;

	virtual void Brew() = 0;

	virtual void PourInCub() = 0;

	virtual void PutSomething() = 0;

	void DoDrink()
	{
		Boid();
		Brew();
		PourInCub();
		PutSomething();
	}
};

class TeaDrink : public AbstractDrink
{
public:
	void Boid()
	{
		std::cout << "煮茶水" << std::endl;
	 }

	void Brew() {
		std::cout << "冲泡茶叶" << std::endl;
	 }

	void PourInCub() 
	{
		std::cout << "导入茶杯中" << std::endl;
	 }

	void PutSomething()
	{
		std::cout << "加柠檬" << std::endl;
	 }
};

class CoffeeDrink : public AbstractDrink
{
	void Boid()
	{
		std::cout << "煮开水" << std::endl;
	}

	void Brew() {
		std::cout << "冲泡咖啡" << std::endl;
	}

	void PourInCub()
	{
		std::cout << "导入咖啡杯中" << std::endl;
	}

	void PutSomething()
	{
		std::cout << "加糖和牛奶" << std::endl;
	}
};

int main()
{
	std::cout << "制作茶:" << std::endl;
	AbstractDrink* drink = new TeaDrink;
	drink->DoDrink();
	//堆区的内存需要程序员手动释放
	delete drink;
	std::cout << "制作咖啡:" << std::endl;
	//释放堆区内存后,drink指针的指引还在,可以再赋值
	drink = new CoffeeDrink;
	drink->DoDrink();
	delete drink;
}
虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父亲中的析构函数改为虚析构或者纯虚析构

#include 
#include 
using namespace std;

//计算器类 - 多态实现
class AbstractDrink
{

public:
	virtual void Boid() = 0;

	void DoDrink()
	{
		Boid();
	}

	virtual ~AbstractDrink()
	{
		std::cout << "父类析构函数调用" << std::endl;
	}
};

class TeaDrink : public AbstractDrink
{
public:
	void Boid()
	{
		std::cout << "煮茶水" << std::endl;
	 }
	~TeaDrink()
	{
		std::cout << "子类析构函数调用" << std::endl;
	}
};

class CoffeeDrink : public AbstractDrink
{
	void Boid()
	{
		std::cout << "煮开水" << std::endl;
	}
	~CoffeeDrink()
	{
		std::cout << "子类析构函数调用" << std::endl;
	}
};

int main()
{
	std::cout << "制作茶:" << std::endl;
	AbstractDrink* drink = new TeaDrink;
	drink->DoDrink();
	//堆区的内存需要程序员手动释放
	delete drink;
	std::cout << "制作咖啡:" << std::endl;
	//释放堆区内存后,drink指针的指引还在,可以再赋值
	drink = new CoffeeDrink;
	drink->DoDrink();
	delete drink;
}

虚析构和纯虚析构共性:

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

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

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

案例:实现一个计算机的运作过程,展示不同的设备与厂家之间的关系

#include 
#include 
using namespace std;

class Cpu
{
public:
	virtual void work() = 0;
};


class Gpu
{
public:
	virtual void work() = 0;
};

class Memory
{
public:
	virtual void work() = 0;
};

class Computer
{
public:
	Cpu* cpu;
	Gpu* gpu;
	Memory* memory;
	
public:
	void Work()
	{
		gpu->work();
		cpu->work();
		memory->work();
	}
};



class IntelCpu : public Cpu
{
public:
	IntelCpu()
	{

	}
	void work()
	{
		std::cout << "intel cpu work!" << std::endl;
	 }
};

class IntelGpu : public Gpu
{
public:
	IntelGpu()
	{

	}
	void work()
	{
		std::cout << "intel Gpu work!" << std::endl;
	}
};

class IntelMemory : public Memory
{
public:
	IntelMemory()
	{

	}
	void work()
	{
		std::cout << "intel Memory work!" << std::endl;
	}
};

class LenoveCpu : public Cpu
{
public:
	void work()
	{
		std::cout << "Lenove cpu work!" << std::endl;
	}
};

class LenoveGpu : public Gpu
{
public:
	void work()
	{
		std::cout << "Lenove Gpu work!" << std::endl;
	}
};

class LenoveMemory : public Memory
{
public:
	void work()
	{
		std::cout << "Lenove Memory work!" << std::endl;
	}
};

int main()
{
	Computer c1;
	c1.cpu = new IntelCpu;
	c1.gpu = new IntelGpu;
	c1.memory = new IntelMemory;
	c1.Work();
}
静态成员

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

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
#include 
#include "string"
using namespace std;

class StaticClass
{
public :
	 static string static_str;

private:
	string name;

public:
	static void printStaticStr()
	{
		std::cout << "static str = " << static_str << std::endl;
	}

};

//类内声明,类外初始化
string StaticClass::static_str = "111";

int main()
{
	StaticClass staticStr;
	staticStr.printStaticStr();
	staticStr.static_str = "222";
	staticStr.printStaticStr();
	StaticClass staticStr2;
	//所有对象共享同一份数据
	staticStr2.printStaticStr();
	//通过类名访问静态变量
	std::cout << "static str = " << StaticClass::static_str << std::endl;
}

静态成员变量也是有访问权限的

  • 静态成员函数
    • 所有对象共享一个函数
    • 静态成员函数只能访问静态成员变量
#include 
#include "string"
using namespace std;

class StaticClass
{
public :
	 static string static_str;

private:
	string name;

public:
	static void printStaticStr()
	{
		std::cout << "static str = " << static_str << std::endl;
	}

};

//类内声明,类外初始化
string StaticClass::static_str = "111";

int main()
{
	//通过类名调用
	StaticClass::printStaticStr();

	//通过类实例调用
	StaticClass staticClass;
	staticClass.printStaticStr();
}

类和对象

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

空对象占用内存为1个字节
C++编译器会为每个空对象也分配一个字节的空间,是为了区分空对象占用内存的位置
每个空对象也应该有一个独一无二的内存地址

#include 
#include "string"
using namespace std;

class Circle
{
};

int main()
{
	Circle circle;
	std::cout << "size of Circle = " << sizeof(circle) << std::endl;
}

非静态成员变量内存属于类的对象上
静态成员变量内存不属于类的对象上
成员函数内存不属于类的对象上

#include 
#include "string"
using namespace std;

class Circle
{
public:
	int m_A;
	static int s_A;

public:
	void print()
	{

	}
};

int main()
{
	Circle circle;
	std::cout << "size of Circle = " << sizeof(circle) << std::endl;
}
this指针概念

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

C++通过提供特殊的对象指针,this指针,解决是哪个对象调用成员函数的
this指针指向被调用的成员函数所属的对象

this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可

this指针的用途
  • 当形参和成员变量同名时,可以用this指针来区分
#include 
#include "string"
using namespace std;

class Circle
{
public:
	int m_A;
	static int s_A;

public:
	void print(int m_A)
	{
		this->m_A = m_A;
	}
};
  • 在类的非静态成员函数中返回对象本身,可以使用 return *this
#include 
#include "string"
using namespace std;

class Circle
{
public:
	int m_A;
	static int s_A;

public:
	Circle print(int m_A)
	{
		this->m_A = m_A;
		return *this;
	}
};
空指针访问成员函数

空指针可以访问成员函数,但是当访问到的成员函数有调用this时,将会出现异常
在成员函数中进行判空的操作可以加强程序的健壮性

const修饰成员函数

常函数:

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

常对象:

  • 声明对象前加const称为该对象为常对象
  • 常对象只能调用常函数
#include 
#include "string"
using namespace std;

class Circle
{
public:
	//成员变量使用 mutable 修饰后,成员函数可以修改该值
	mutable int m_A;
	static int s_A;

public:
	Circle()
	{
		this->m_A = 12;
	}
	//常函数  成员函数后加const
	Circle print(int m_A) const
	{
		this->m_A = m_A;
		return *this;
	}
	void print()
	{

	}
};

int main()
{
	Circle circle;
	circle.print(13);
	std::cout << circle.m_A << std::endl;
	const Circle circle_const;
	//const修饰的类实例只能调用常函数
	//circle_const.print();
}

frend 友元

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

友元的三种实现:

  • 全局函数做友元
#include 
#include "string"
using namespace std;

//全局函数做友元
class Circle
{
	//全局函数作为友元,将全局函数定义到类内
	friend void print(Circle& circle);
public:
	string name;
private:
	string password;

public:
	Circle()
	{
		this->name = "username";
		this->password = "password";
	}
};

void print(Circle& circle)
{
	std::cout << "全局函数访问" << std::endl;
	std::cout << circle.password << std::endl;
}
  • 类做友元
#include 
#include "string"
using namespace std;

//类做友元
class Circle 
{
	friend class Rrectangle;
public:
	string name;
private:
	string password;

public:
	Circle()
	{
		this->name = "username";
		this->password = "password";
	}
};

class Rrectangle
{
public:
	void print(Circle& circle);
};

void Rrectangle::print(Circle& circle)
{
	std::cout << "类函数访问" << std::endl;
	std::cout << circle.password << std::endl;
}

int main()
{
	Rrectangle r;
	Circle circle;
	r.print(circle);
}
  • 成员函数做友元
#include 
#include "string"
using namespace std;


class Rrectangle
{
public:
	Rrectangle();
private:
	Circle circle;

public:
	void print();
};

//成员函数做友元
class Circle 
{
	friend void Rrectangle::print();
public:
	string name;
private:
	string password;

public:
	Circle()
	{
		this->name = "username";
		this->password = "password";
	}
};

Rrectangle::Rrectangle()
{
	Circle circle;
	this->circle = circle;
}

void Rrectangle::print()
{
	std::cout << "成员函数访问" << std::endl;
	std::cout << this->circle.password << std::endl;
}

int main()
{
	Rrectangle r;
	
	r.print();
}
运算符重载

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

加号运算符重载

实现两个自定义数据类型相加的运算
运算符重载也可以发生函数重载

#include 
#include "string"
using namespace std;

//成员函数做友元
class Circle 
{
private:
	int c_A;
	int c_B;
public:
	Circle()
	{
		this->c_A = 12;
		this->c_B = 13;
	}
public:
	//通过成员函数重载
	/*Circle operator+(Circle& circle)
	{
		Circle temp_circle;
		temp_circle.c_A = this->c_A + circle.c_A;
		temp_circle.c_B = this->c_B + circle.c_A;
		return temp_circle;
	}*/
	void print()
	{
		std::cout << this->c_A << std::endl;
		std::cout << this->c_B << std::endl;
	}
	int getC_A()
	{
		return this->c_A;
	}
	int getC_B()
	{
		return this->c_B;
	}
	void setC_A(int c_a)
	{
		this->c_A = c_a;
	}
	void setC_B(int c_b)
	{
		this->c_B = c_b;
	}
};

//通过全局函数重载
Circle operator+(Circle& circle, Circle& circle_2)
{
	Circle temp_circle;
	temp_circle.setC_A(circle_2.getC_A() + circle.getC_A());
	temp_circle.setC_B(circle_2.getC_B() + circle.getC_B());
	return temp_circle;
}

int main()
{
	Circle a;
	Circle b;
	Circle c = b + a;
	c.print();
}
左移运算符重载

//

递增运算符重载

//

赋值运算符重载

//

关系运算符重载

//

函数调用运算符重载

//

结构体Structs

结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
语法:

struct 结构体名称 { 结构体成员列表 };

通过结构体创建变量的方式有三种:

  • struct 结构体名称 变量名称 stu
  • struct 结构体名称 变量名 = { 成员1值,成员2值 } stu_2
  • 定义结构体时顺便创建变量stu3
#include 
using namespace std;

struct Student
{
public:
	const char* name;
	int cno;
} stu3;

int main()
{
	Student stu;
	stu.cno = 123;
	stu.name = "ssss";
	Student stu_2 = { "stu_2",123 };
}

结构体数组:

#include 
using namespace std;

struct Student
{
public:
	const char* name;
	int cno;
};

void Print(Student* stu)
{
	std::cout << "name = " << stu->name << " cno = " << stu->cno << std::endl;
}

void Print(Student stu)
{
	std::cout << "name = " << stu.name<< " cno = " << stu.cno << std::endl;
}

int main()
{
	Student stu_array[3] = { {"张三",12},{"李四",13},{"王二",14} };
	Print(stu_array[0]);
	Print(&stu_array[0]);
}

结构体指针:
利用操作符 -> 可以通过结构体指针访问结构体属性

结构体嵌套:

#include 
using namespace std;

struct  Teacher
{
public:
	const char* name;
	int tno;
};

struct Student
{
public:
	const char* name;
	int cno;
	Teacher teacher;
};


void Print(Student* stu)
{
	std::cout << "name = " << stu->name << " cno = " << stu->cno << std::endl;
	std::cout << "teacher name = " << stu->teacher.name<< "teacher cno = " << stu->teacher.tno << std::endl;
}

void Print(Student stu)
{
	std::cout << "name = " << stu.name<< " cno = " << stu.cno << std::endl;
}

int main()
{
	Student stu_array[3] = { {"张三",12,{"李老师",12}},{"李四",13},{"王二",14}};
	Print(&stu_array[0]);
}

结构体中使用const关键字:
节省内存且防止修改结构体的内容信息

static

枚举Enums

构造函数Constructors

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

#include 

#define PI 3.14

class Circle
{
private:
	float radius;

public:

	Circle(float radius)
	{
		this->radius = radius;
	}

	//设定圆的半径
	void set_radius(float radius)
	{
		this->radius = radius;
	}

	//求取圆的周长
	float round()
	{
		return 2 * this->radius * PI;
	}

	//求取圆的面积
	float area()
	{
		return this->radius * this->radius * PI;
	}
};

int main()
{
	Circle circle(12);
	std::cout << "圆的周长:" << circle.round() << std::endl;
	std::cout << "圆的面积:" << circle.area() << std::endl;
}

构造函数的分类

两种分类方式:

  • 按照参数分为:有参构造和无参构造
  • 按照类型分为:普通构造和拷贝构造
#include 

class Circle
{
private:
	float radius;

public:

	Circle()
	{
		//默认构造函数,不写该函数,编译器会提供一个
		std::cout << "无参构造函数调用" << std::endl;
	}

	Circle(float radius)
	{
		this->radius = radius;
		std::cout << "有参构造函数调用" << std::endl;
	}

	//拷贝构造函数 按照引用传参模式传递参数,且不能修改原本用于拷贝的类
	Circle(const Circle& circle)
	{
		this->radius = circle.radius;
	}

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

三种调用方式:

  • 括号法
  • 显示法
  • 隐式转换法
int main()
{
	//括号法调用
	Circle circle_default; //默认构造
	Circle circle(12); //有参构造
	Circle circle_copy(circle); //拷贝构造

	//显示法调用
	Circle circle_2 = Circle(12);
	Circle(12); //匿名对象 当前行执行结束后,系统会立即回收掉匿名对象

	//隐式法调用
	Circle circle_3 = 10;
	Circle circle_4 = { 10 };
	Circle circle_5 = circle_3; //显示法调用拷贝构造
}

注意事项:

  • 调用默认构造函数时,不要在调用后面加(),因为编译器会认为该调用方式是函数声明
  • 不要用显示构造来调用拷贝构造函数,初始化匿名对象,编译器会认为是对象的声明
拷贝构造函数使用时机
  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递方式给函数参数传值
  • 以值方式返回局部对象
#include 

#define PI 3.14

class Circle
{
private:
	float radius;

public:
	Circle()
	{
		this->radius = 12;
	}
	//拷贝构造函数
	Circle(const Circle& circle)
	{
		this->radius = circle.radius;
		std::cout << "拷贝构造函数调用" << std::endl;
	}
};

void do_work(Circle circle)
{

}

Circle return_c()
{
	Circle circle;
	std::cout << &circle << std::endl;
	return circle;
}

int main()
{
	Circle circle;
	//使用一个已经创建完毕的对象来初始化一个新对象
	Circle circle_copy(circle);
	//值传递方式给函数参数传值
	do_work(circle);
	//值方式返回局部对象
	Circle c1 = return_c();
	std::cout << &c1 << std::endl;
}

构造函数调用规则

C++创建一个类,编译会给每个类至少添加三个函数

  • 默认构造(空实现)
  • 析构函数(空实现)
  • 拷贝构造(值拷贝)

如果写了构造函数编译器将不再提供默认构造函数,但会提供拷贝构造函数;如果提供了拷贝构造,编译器将不再提供其它构造函数

深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作,浅拷贝带来的问题就是堆区的内存重复释放
深拷贝:在堆区重新申请空间,进行拷贝操作

#include 
using namespace std;

class Circle
{
private:
	float radius;
	int* m_name;

public:
	Circle()
	{
		this->radius = 12;
		std::cout << "无参构造函数调用" << std::endl;
	}
	Circle(float radius,int* name)
	{
		this->radius = radius;
		this->m_name = name;
		std::cout << "有参构造函数调用" << std::endl;
	}
	//拷贝构造函数
	Circle(const Circle& circle)
	{
		this->radius = circle.radius;
		this->m_name = new int(*circle.m_name);
		std::cout << "拷贝构造函数调用" << std::endl;
	}
	~Circle()
	{
		if (this->m_name != NULL)
		{
			delete this->m_name;
			this->m_name = NULL;
		}
		std::cout << "析构函数调用" << std::endl;
	}

	void print()
	{
		std::cout << "radius = " << this->radius << "name = " << *this->m_name << std::endl;
	}
};

void test_01()
{
	int a = 123;
	Circle* circle = new Circle(12.0, &a);
	circle->print();
	Circle circle_2(*circle);
	circle_2.print();
}

int main()
{
	test_01();
}

初始化列表
#include 
using namespace std;

class Circle
{
private:
	float radius;
	int* m_name;

public:
	Circle() :radius(10.2), m_name(new int(12))
	{

	}
	Circle(float radius, int* m_name) :radius(radius), m_name(m_name)
	{

	}
	float get_Radius()
	{
		return this->radius;
	}
	int* get_M_Name()
	{
		return this->m_name;
	}
};

void test_01()
{
	Circle circle;
	std::cout << "radius = " << circle.get_Radius() << std::endl;
	std::cout << "m_name = " << *circle.get_M_Name() << std::endl;

	int a = 20;
	Circle circle_2((float)20,&a);
	std::cout << "circle_2 radius = " << circle_2.get_Radius() << std::endl;
	std::cout << "circle_2 m_name = " << *circle_2.get_M_Name() << std::endl;
}

int main()
{
	test_01();
}

类对象作为类成员

B类中有A类的属性,其析构函数与构造函数执行顺序如下:

#include 
using namespace std;

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

class B
{
private:
	A a;
public:
	B()
	{
		cout << "B 类的默认构造调用" << endl;
	}
	~B()
	{
		cout << "B 类的析构函数调用" << endl;
	}
};

int main()
{
	B b;
}

类对象作为成员析构函数与构造函数调用顺序

析构函数Destructors

主要作用于对象销毁前系统自动调用,执行一些清理工作

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

继承Inheritance

虚函数Virtual Functions

纯虚函数(接口)

访问控制

private 私有

成员在类内可以访问,类外不可以访问
子类不可以访问父类的保护内容

public 公有

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

protect 保护

成员在类内可以访问,内外不可以访问
子类可以访问父类的保护内容

C++文件操作

  • 文本文件 文件以文本的ASCII码形式存储在计算机中
  • 二进制文件 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

操作文件的三大类:

  • ofstream:写操作
  • ifstream:读操作
  • fstream:读写操作
读文件

读文件的基本步骤如下:

①包含头文件 #include
②创建流对象 ifstream ifs;
③打开文件并判断文件是否打开成功 ifs.open(“file uri”,打开方式);
④读数据 四种方式读取
⑤关闭文件 ifs.close()

读文本文件
读二进制文件
写文件

写文件的基本步骤如下:

①包含头文件 #include
②创建流对象 ofstream ofs;
③打开文件 ofs.open(“file uri”);
④写数据 ofs << “写入的数据”;
⑤关闭文件 ofs.close();

文件打开方式:

  • ios::in 为读文件而打开文件
  • ios::out 为写文件而打开文件
  • ios::ate 初始位置:文件尾
  • ios::app 追加方式写文件
  • ios::trunc 如果文件存在先删除再创建
  • ios::binary 二进制方式

打开文件方式可以配合使用,利用 | 操作符: ios::binary | ios::out

写文本文件
#include 
#include 
using namespace std;

int main()
{
	//创建流对象
	ofstream ofs;
	//指定打开访问
	ofs.open("C:/Users/25763/Desktop/test.txt", ios::out);
	//写内容
	ofs << "姓名 : 张三" << std::endl;
	//关闭文件
	ofs.close();
}
写二进制文件

进阶内容

内存分区模型

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

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

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

  • 代码区:存放CPU执行的机器指令;代码区是共享的,共享的目的是对于频繁被执行的程序只需要在内存中有一份代码即可;代码区是只读的,使其只读的原因是防止程序意外的修改了它的指定
  • 全局区:全局变量和静态变量存放在此;全局区还包含了常量区,字符串常量和其它常量也存放在此;该区域的数据在程序结束后由操作系统释放
#include 

int q_a = 10;
int q_b = 10;

static int s_a = 10;
static int s_b = 10;

const int c_a = 10;

int main()
{
	//全局区内存分配解析
	//局部变量的内存不在全局区
	int a = 10;
	int b = 10;
	std::cout << "局部变量 a 的地址:" << &a << std::endl;
	std::cout << "局部变量 b 的地址:" << &b << std::endl;

	//全局变量
	std::cout << "全局变量 q_a 的地址:" << &q_a << std::endl;
	std::cout << "全局变量 q_b 的地址:" << &q_b << std::endl;

	//静态变量
	static int s_c = 100;
	static int s_d = 100;
	std::cout << "全局静态变量 s_a 的地址:" << &s_a << std::endl;
	std::cout << "全局静态变量 s_b 的地址:" << &s_b << std::endl;
	std::cout << "局部静态变量 s_c 的地址:" << &s_c << std::endl;
	std::cout << "局部静态变量 s_d 的地址:" << &s_d << std::endl;

	//常量 --- 字符串常量 / const修饰的常量
	std::cout << "字符串常量的地址:" << &"hello" << std::endl;
	const int c_b = 10;
	std::cout << "const修饰的全局常量 c_a 的地址:" << &c_a << std::endl;
	std::cout << "const修饰的局部常量 c_b 的地址:" << &c_b << std::endl;
}

后槽牙都要给我干稀碎之C++入门学习笔记_第5张图片
全局区包含有:全局变量、静态变量(static关键字修饰)、const修饰的全局常量、字符串常量
不在全局区的有:局部变量,const修饰的局部变量(局部常量)

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

有编译器自动分配释放,存放函数的参数值、局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

#include 

int* func(int c = 10)  //形参数据也会放在栈区
{
	int a = 10;
	//局部变量存放在栈区,执行结束后程序自动释放
	return &a;
}

int main()
{
	int* func_a = func();
	//编译器做了保留,第一次可以正常访问该变量
	std::cout << *func_a << std::endl;
	//后续可能无法正常访问该变量
	std::cout << *func_a << std::endl;
}
堆区

由程序员分配释放,若是程序员不释放,程序结束后由操作系统回收
在C++中主要利用 new 关键字在堆区开辟内存

#include 

int* func()
{
	int* a = new int(10);
	return a;
}

int main()
{
	int* func_a = func();
	std::cout << *func_a << std::endl;
	std::cout << *func_a << std::endl;
}
new操作符

C++利用new操作符在堆区开辟内存
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:

new 数据类型

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

#include 

int main()
{
	int* a = new int(10);
	std::cout << *a << std::endl;
	delete a;
	//执行该行时,会报错,内存访问无权限
	std::cout << *a << std::endl;
}

引用

给变量起别名

数据类型& 别名 = 原名

#include 

int main()
{
	int a = 10;
	int& b = a;
	b = 11;
	std::cout << a << std::endl;
}
引用的注意事项
  • 引用必须要初始化
  • 引用初始化后不可以改变
#include 

int main()
{
	int a = 10;
	int c = 11;
	int& b = a;
	//赋值操作,而不是更改引用
	b = c;
	b = 9;
	std::cout << c << std::endl;
	std::cout << a << std::endl;
}

引用注意事项代码执行结果

引用做函数参数

函数传参时,可以利用引用的技术让形参修饰实参,可以简化指针修改实参

#include 

//值传递
void swap(int a, int b);

//地址传递
void swap(int* a, int* b);

//引用传递
void swap_quote(int& a, int& b);

int main()
{
	int a = 10;
	int b = 20;

	swap(a, b);
	std::cout << "a=" << a << std::endl;
	std::cout << "b=" << b << std::endl;

	swap(&a, &b);
	std::cout << "a=" << a << std::endl;
	std::cout << "b=" << b << std::endl;

	swap_quote(a, b);
	std::cout << "a=" << a << std::endl;
	std::cout << "b=" << b << std::endl;
}

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

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

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

通过引用参数产生的效果和按地址传递的效果是一样的,引用的语法更清楚简单

引用做函数返回值

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

#include 

int& add(int a, int b);

int main()
{
	int c = add(3, 4);
	std::cout << c << std::endl;
	std::cout << c << std::endl;
}

//返回局部变量的引用时,可能会出现无法访问的问题
int& add(int a, int b)
{
	int c = a + b;
	int& result = c;
	return result;
}

函数的调用可以作为左值

#include 

int& add(int a, int b);

int main()
{
	int c = add(3, 4);
	std::cout << c << std::endl;
	std::cout << c << std::endl;
	//函数引用的可以作为左值进行赋值操作
	c = add(4, 5) = 900;
	std::cout << c << std::endl;
	std::cout << c << std::endl;
}

//返回局部变量的引用时,可能会出现无法访问的问题
int& add(int a, int b)
{
	int c = a + b;
	int& result = c;
	return result;
}
引用的本质

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

int& a = b 等效为 int* const a = &b

常量引用

常量引用主要是用来修饰形参,防止误操作
在函数形参列表中,可以添加const修饰形参,防止形参改变实参

练习案例

案例1:基于结构体的控制台通讯录管理系统

通讯录是一个可以记录亲人、好友信息的工具,系统中需要实现的功能如下:

  • 添加联系人:向通讯录中添加新人,信息包括(姓名、性别、年龄、联系电话、家庭住址)最多记录1000人
  • 显示联系人:显示通讯录中所有的联系人信息
  • 删除联系人:按照姓名进行删除指定联系人信息
  • 查找联系人:按照姓名查看指定联系人信息
  • 修改联系人:按照姓名重新修改指定联系人
  • 清空联系人:清空通讯录中所有的信息
  • 退出通信录:退出当前使用的通讯录

使用结构体记录联系人信息,在控制台中展示所有的操作流程

系统代码结构:
控制台通讯录管理系统文件结构

  • ad_book.h
#pragma once
#include 
#include "string"

using namespace std;

struct Address_Book;

//添加联系人方法
void Add();

//打印共有多少联系人方法
void Print_Info();

//显示通讯录所有联系人信息
void Show();

//显示目标Address_Book结构体中的联系人信息
void Show(Address_Book* address_book);

//查找联系人信息
void Find();

//根据联系人姓名查找联系人信息
int Find(string name);

//删除联系人方法
void Delete();

//清空联系人
void Clear();

//修改联系人信息
void Update();

//菜单展示
void Show_Menu();

  • ad_book.cpp
#include 
#include "string"
#include "ad_book.h"

using namespace std;

//联系人最大支持的数量定义
#define MAX_LEN 1000

//通讯录记录信息的结构体
struct Address_Book
{
	//姓名
	string name;

	//性别
	string gender;

	//年龄
	int age = 0;

	//联系电话
	string phone;

	//家庭住址
	string address;

};

//联系人记录结构
static Address_Book address_book_array[MAX_LEN];

//当前共有联系人数量
static int Now_Size = 0;

//添加联系人方法
void Add()
{
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
	Address_Book address_book;
	//打印输出添加各项信息的提示
	std::cout << "请输入您要添加的联系人姓名:" << std::endl;
	std::cin >> address_book.name;
	std::cout << "请输入您要添加的联系人性别:" << std::endl;
	std::cin >> address_book.gender;
	std::cout << "请输入您要添加的联系人年龄:" << std::endl;
	std::cin >> address_book.age;
	std::cout << "请输入您要添加的联系人联系电话:" << std::endl;
	std::cin >> address_book.phone;
	std::cout << "请输入您要添加的联系人家庭住址:" << std::endl;
	std::cin >> address_book.address;
	address_book_array[Now_Size++] = address_book;
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
	Print_Info();
}

//打印输出当前共有多少的联系人信息
void Print_Info()
{
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
	std::cout << "当前共有:" << Now_Size << " 个联系人 " << std::endl;
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
}

//显示通讯录所有联系人信息
void Show()
{
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
	for (int i = 0; i < Now_Size; i++)
	{
		Show(&(address_book_array[i]));
	}
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
}

//显示目标Address_Book结构体中的联系人信息
void Show(Address_Book* address_book)
{
	std::cout << "姓名: " << address_book->name << " 性别: " << address_book->gender << " 年龄: " 
		<< address_book->age << " 联系电话: " << address_book->phone << "家庭住址: " << address_book->address << std::endl;
}

//查找联系人信息
void Find()
{
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
	std::cout << "请输入您要查找的目标联系人姓名:" << std::endl;
	string wait_find_name;
	std::cin >> wait_find_name;
	Find(wait_find_name);
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
}

//根据联系人姓名查找联系人信息
int Find(string name)
{
	for (int i = 0; i < Now_Size; i++)
	{
		if (address_book_array[i].name == name)
		{
			Show(&address_book_array[i]);
			return i;
		}
	}
	std::cout << "用户姓名:" << name << " 对应的联系人信息未查找到!" << std::endl;
	return -1;
}

//删除联系人方法
void Delete()
{
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
	std::cout << "请输入您要删除的目标联系人姓名:" << std::endl;

	string wait_delete_name;
	std::cin >> wait_delete_name;

	int wait_delete_index = Find(wait_delete_name);

	if (wait_delete_index >= 0)
	{
		//把后面的内容交换到前面
		for (int i = wait_delete_index; i < Now_Size; i++)
		{
			address_book_array[i] = address_book_array[i + 1];
		}
	}
	Now_Size--;
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
}

//清空联系人
void Clear()
{
	*address_book_array = {};
	Now_Size = 0;
	std::cout << "联系人清空完毕!" << std::endl;
}

//修改联系人信息
void Update()
{
	std::cout << "-------------------------------------------------------------------------------" << std::endl;
	std::cout << "请输入您要修改的目标联系人姓名:" << std::endl;
	string wait_update_name;
	std::cin >> wait_update_name;

	int wait_update_index = Find(wait_update_name);
	if (wait_update_index >= 0)
	{
		Address_Book address_book;
		std::cout << "请输入您要修改的联系人姓名:" << std::endl;
		std::cin >> address_book.name;
		std::cout << "请输入您要修改的联系人性别:" << std::endl;
		std::cin >> address_book.gender;
		std::cout << "请输入您要修改的联系人年龄:" << std::endl;
		std::cin >> address_book.age;
		std::cout << "请输入您要修改加的联系人联系电话:" << std::endl;
		std::cin >> address_book.phone;
		std::cout << "请输入您要修改的联系人家庭住址:" << std::endl;
		std::cin >> address_book.address;
		address_book_array[wait_update_index] = address_book;
	}

	std::cout << "-------------------------------------------------------------------------------" << std::endl;
}

//菜单展示
void Show_Menu()
{
	while (true)
	{
		std::cout << "-------------------------------------------------------------------------------" << std::endl;
		std::cout << "欢迎来到通讯录管理系统,当前通讯录共有 " << Now_Size << " 个联系人!" << std::endl;
		std::cout << "请选择您当前要进行的操作:" << std::endl;
		std::cout << "A:添加联系人" << std::endl;
		std::cout << "S:显示所有联系人" << std::endl;
		std::cout << "D:删除联系人" << std::endl;
		std::cout << "F:查找联系人" << std::endl;
		std::cout << "U:修改联系人" << std::endl;
		std::cout << "C:清空联系人" << std::endl;
		std::cout << "E:退出当前系统" << std::endl;
		char input;
		std::cin >> input;

		switch (input)
		{
		case 'A': Add(); break;
		case 'S': Show(); break;
		case 'D': Delete(); break;
		case 'F': Find(); break;
		case 'U': Update(); break;
		case 'C': Clear(); break;
		case 'E': exit(0);
		}
		std::cout << "-------------------------------------------------------------------------------" << std::endl;
	}
}
  • Main.cpp
#include 
#include 

#include "ad_book.h"

int main()
{

	Show_Menu();
}

你可能感兴趣的:(C++入门与进阶内容专栏,c++,学习,笔记)