文章目录
- 常量
- 标识符命名规则
- 数据类型
- sizeof关键字
- 浮点数
- 字符型
- 转义字符
- 字符串型
- 布尔类型bool
- 比较运算符
- switch-case语句
- rand()
- 随机数种子srand()
- goto语句
- 一维数组
- 函数
- 函数的声明
- 函数的分文件编写
- 指针
- 指针所占内存空间
- 空指针
- 野指针
- const修饰指针
- 1、常量指针
- 2、指针常量
- 3、const即修饰指针,又修饰常量
- 结构体
- 结构体中 const使用场景
- 通讯录管理系统
- 菜单功能
- 退出功能
- 添加联系人功能
- 显示联系人功能
- 删除联系人功能
- 查找联系人功能
- 修改联系人功能
- 清空联系人功能
- 内存分区模型
- 程序运行前
- 程序运行后
- new操作符
- new一个普通变量
- new开辟数组
- 引用
- 引用的基本使用
- 引用注意事项
- 引用做函数参数
- 引用做函数返回值
- 引用的本质
- 常量引用
- 函数提高
- 函数默认参数
- 函数占位参数
- 函数重载
- 函数重载概述
- 函数重载注意事项
- 类和对象
- 封装
- 案例:设计一个学生类
- 访问权限
- struct和class区别
- 成员属性设置为私有
- 练习案例1:设计立方体类
- 练习案例2:点和圆的关系
- 将代码拆分为头文件,源文件,main函数文件进行工程化
- 对象的初始化和清理
- 构造函数和析构函数
- 构造函数的分类及调用
- 拷贝构造函数调用时机
- 构造函数调用规则
- 深拷贝与浅拷贝
- 初始化列表
- 类对象作为类成员
- 静态成员
- 静态成员变量
- 静态成员函数
- C++对象模型和this指针
- 成员变量和成员函数分开存储
- this指针概念
- 空指针访问成员函数
- const修饰成员函数
- 友元
- 全局函数做友元
- 类做友元
- 成员函数做友元
- 运算符重载
- 加号运算符重载
- 左移运算符重载
- 递增运算符重载
- 赋值运算符重载
- 关系运算符重载
- 函数调用运算符重载
- 继承
- 继承的基本语法
- 普通实现
- 继承实现
- 总结
- 继承方式
- 继承中的对象模型
- 开发人员命令提示符工具
- 继承中构造和析构顺序
- 继承同名成员处理方式
- 总结
- 继承同名静态成员处理方式
- 多继承语法
- 虚继承解决菱形继承问题
- 总结
- 多态
- 多态的基本概念
- 多态的原理剖析
- 父类函数前面没有virtual
- 父类函数前面有virtual
- 子类函数里没有重写父类的虚函数时
- 子类函数里面重写了父类的虚函数
- 多态案例一:计算器类
- 纯虚函数和抽象类
- 多态案例二:制作饮品
- 虚析构和纯虚析构
- 父类没有虚析构函数时,可能造成内存泄露,因为没有把子类堆区释放干净
- 父类有虚析构函数后,可以把子类的堆区释放干净
- 纯虚析构也需要实现
- 总结
- 多态案例三:电脑组装
- 文件
- 文本文件
- 文件打开方式
- 写文件
- 总结
- 读文件
- 总结
- 二进制文件
- 写文件
- 读文件
常量
#include<iostream>int main() {std::cout << "Enter two numbers:" << std::endl;int v1 = 0, v2 = 0;std::cin >> v1 >> v2;std::cout << "The sum of " << v1 << " and " << v2 << " is " << v1 + v2 << std::endl;return 0;
}
标识符命名规则
数据类型
sizeof关键字
浮点数
字符型
转义字符
字符串型
布尔类型bool
比较运算符
switch-case语句
rand()
随机数种子srand()
不加srand()的rand()生成的是伪随机数,一直都是42
#include<iostream>
#include<ctime>int main() {srand((unsigned int)time(NULL));int num = rand() % 100 + 1;std::cout << num << std::endl;return 0;
}
goto语句
#include<iostream>
#include<ctime>int main() {std::cout << 1 << std::endl;std::cout << 2 << std::endl;std::cout << 3 << std::endl;goto FLAG;std::cout << 4 << std::endl;std::cout << 5 << std::endl;FLAG:std::cout << 6 << std::endl;return 0;
}
一维数组
函数
函数的声明
函数的分文件编写
指针
指针所占内存空间
总结:所有指针类型在32位操作系统下是4个字节
空指针
野指针
野指针:指针变量指向非法的内存空间
总结:空指针和野指针都不是我们申请的空间,因此不要访问
const修饰指针
1、常量指针
2、指针常量
3、const即修饰指针,又修饰常量
技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
结构体
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
结构体中 const使用场景
用const来防止误操作
通讯录管理系统
菜单功能
/*
封装函数显示该界面 如 void showMenu()
在main函数中调用封装好的函数
*/
#include<iostream>
#include<string>
using namespace std;// 菜单界面
void showMenu() {cout << "*********************************" << endl;cout << "******** 1、添加联系人 ********" << endl;cout << "******** 2、显示联系人 ********" << endl;cout << "******** 3、删除联系人 ********" << endl;cout << "******** 4、查找联系人 ********" << endl;cout << "******** 5、修改联系人 ********" << endl;cout << "******** 6、清空联系人 ********" << endl;cout << "******** 0、退出通讯录 ********" << endl;cout << "*********************************" << endl;
}int main() {showMenu();system("pause");return 0;
}
退出功能
/*
封装函数显示该界面 如 void showMenu()
在main函数中调用封装好的函数
*/
#include<iostream>
#include<string>
using namespace std;// 菜单界面
void showMenu() {cout << "*********************************" << endl;cout << "******** 1、添加联系人 ********" << endl;cout << "******** 2、显示联系人 ********" << endl;cout << "******** 3、删除联系人 ********" << endl;cout << "******** 4、查找联系人 ********" << endl;cout << "******** 5、修改联系人 ********" << endl;cout << "******** 6、清空联系人 ********" << endl;cout << "******** 0、退出通讯录 ********" << endl;cout << "*********************************" << endl;
}int main() {// 用户的选择int select = 0;while (true) {// 调用封装好的菜单界面函数showMenu();cin >> select;switch (select){//1、添加联系人case 1: break;//2、显示联系人case 2: break;//3、删除联系人case 3: break;//4、查找联系人case 4: break;//5、修改联系人case 5: break;//6、清空联系人case 6: break;//0、退出通讯录case 0: cout << "欢迎下次使用" << endl;// 按任意键退出系统system("pause");return 0;default:break;}}system("pause");return 0;
}
添加联系人功能
/*
封装函数显示该界面 如 void showMenu()
在main函数中调用封装好的函数
*/
#include<iostream>
#include<string>
using namespace std;#define MAX 1000// 设计联系人结构体
struct Person {// 姓名string m_Name;// 性别 1 男 2 女int m_Sex;// 年龄int m_Age;// 电话string m_Phone;// 住址string m_Addr;
};// 设计通讯录结构体
struct Addressbooks {//通讯录中保存的联系人数组struct Person personArray[MAX];//通讯录中当前记录联系人个数int m_Size;
};// 1、添加联系人
void addPerson(Addressbooks* abs) {// 判断通讯录是否已满,如果满了就不再添加if (abs->m_Size == MAX) {cout << "通讯录已满,无法添加!" << endl;return;}else {// 添加具体联系人// 姓名string name;cout << "请输入姓名:" << endl;cin >> name;abs->personArray[abs->m_Size].m_Name = name;//性别 1男 2女int sex = 0;cout << "请输入性别(1男 2女):" << endl;while (true) {// 如果输入的是 1 或者 2 可以退出循环,因为输入的是正确值// 如果输入有误,重新输入cin >> sex;if (sex == 1 || sex == 2) {abs->personArray[abs->m_Size].m_Sex = sex;break;}cout << "输入有误,请重新输入!" << endl;}//年龄int age = 0;cout << "请输入年龄(0~300):" << endl;while (true) {cin >> age;if (age > 0 && age < 300) {abs->personArray[abs->m_Size].m_Age = age;break;}cout << "输入有误,请重新输入!" << endl;}//电话string phone;cout << "请输入电话:" << endl;cin >> phone;abs->personArray[abs->m_Size].m_Phone = phone;//住址string addr;cout << "请输入住址:" << endl;cin >> addr;abs->personArray[abs->m_Size].m_Addr = addr;// 更新通讯录人数abs->m_Size++;cout << name << "添加成功!" << endl;//请按任意键继续system("pause");//清屏操作system("cls");}
}// 菜单界面
void showMenu() {cout << "*********************************" << endl;cout << "******** 1、添加联系人 ********" << endl;cout << "******** 2、显示联系人 ********" << endl;cout << "******** 3、删除联系人 ********" << endl;cout << "******** 4、查找联系人 ********" << endl;cout << "******** 5、修改联系人 ********" << endl;cout << "******** 6、清空联系人 ********" << endl;cout << "******** 0、退出通讯录 ********" << endl;cout << "*********************************" << endl;
}int main() {// 创建通讯录结构体变量Addressbooks abs;// 初始化通讯录中当前人员个数abs.m_Size = 0;// 用户的选择int select = 0;while (true) {// 调用封装好的菜单界面函数showMenu();cin >> select;switch (select){//1、添加联系人case 1: addPerson(&abs); break;//2、显示联系人case 2: break;//3、删除联系人case 3: break;//4、查找联系人case 4: break;//5、修改联系人case 5: break;//6、清空联系人case 6: break;//0、退出通讯录case 0: cout << "欢迎下次使用" << endl;// 按任意键退出系统system("pause");return 0;default:break;}}system("pause");return 0;
}
显示联系人功能
/*
封装函数显示该界面 如 void showMenu()
在main函数中调用封装好的函数
*/
#include<iostream>
#include<string>
using namespace std;#define MAX 1000// 设计联系人结构体
struct Person {// 姓名string m_Name;// 性别 1 男 2 女int m_Sex;// 年龄int m_Age;// 电话string m_Phone;// 住址string m_Addr;
};// 设计通讯录结构体
struct Addressbooks {//通讯录中保存的联系人数组struct Person personArray[MAX];//通讯录中当前记录联系人个数int m_Size;
};// 1、添加联系人
void addPerson(Addressbooks* abs) {// 判断通讯录是否已满,如果满了就不再添加if (abs->m_Size == MAX) {cout << "通讯录已满,无法添加!" << endl;return;}else {// 添加具体联系人// 姓名string name;cout << "请输入姓名:" << endl;cin >> name;abs->personArray[abs->m_Size].m_Name = name;//性别 1男 2女int sex = 0;cout << "请输入性别(1男 2女):" << endl;while (true) {// 如果输入的是 1 或者 2 可以退出循环,因为输入的是正确值// 如果输入有误,重新输入cin >> sex;if (sex == 1 || sex == 2) {abs->personArray[abs->m_Size].m_Sex = sex;break;}cout << "输入有误,请重新输入!" << endl;}//年龄int age = 0;cout << "请输入年龄(0~300):" << endl;while (true) {cin >> age;if (age > 0 && age < 300) {abs->personArray[abs->m_Size].m_Age = age;break;}cout << "输入有误,请重新输入!" << endl;}//电话string phone;cout << "请输入电话:" << endl;cin >> phone;abs->personArray[abs->m_Size].m_Phone = phone;//住址string addr;cout << "请输入住址:" << endl;cin >> addr;abs->personArray[abs->m_Size].m_Addr = addr;// 更新通讯录人数abs->m_Size++;cout << name << "添加成功!" << endl;//请按任意键继续system("pause");//清屏操作system("cls");}
}// 2、显示联系人
void showPerson(Addressbooks* abs) {//判断通讯录中人数是否为0,如果为0,提示记录为空//如果不为0,显示记录的联系人信息if (abs->m_Size == 0) {cout << "当前记录为空" << endl;}else {for (int i = 0; i < abs->m_Size; i++) {cout << "姓名:" << abs->personArray[i].m_Name << "\t";cout << "性别:" << (abs->personArray[i].m_Sex == 1 ? "男" : "女") << "\t";cout << "年龄:" << abs->personArray[i].m_Age << "\t";cout << "电话:" << abs->personArray[i].m_Phone << "\t";cout << "住址:" << abs->personArray[i].m_Addr << endl;cout << "----------------------------------------------------------------------------------------------------" << endl;}}//请按任意键继续system("pause");//清屏操作system("cls");
}// 菜单界面
void showMenu() {cout << "*********************************" << endl;cout << "******** 1、添加联系人 ********" << endl;cout << "******** 2、显示联系人 ********" << endl;cout << "******** 3、删除联系人 ********" << endl;cout << "******** 4、查找联系人 ********" << endl;cout << "******** 5、修改联系人 ********" << endl;cout << "******** 6、清空联系人 ********" << endl;cout << "******** 0、退出通讯录 ********" << endl;cout << "*********************************" << endl;
}int main() {// 创建通讯录结构体变量Addressbooks abs;// 初始化通讯录中当前人员个数abs.m_Size = 0;// 用户的选择int select = 0;while (true) {// 调用封装好的菜单界面函数showMenu();cin >> select;switch (select){//1、添加联系人case 1: addPerson(&abs); break;//2、显示联系人case 2: showPerson(&abs);break;//3、删除联系人case 3: break;//4、查找联系人case 4: break;//5、修改联系人case 5: break;//6、清空联系人case 6: break;//0、退出通讯录case 0: cout << "欢迎下次使用" << endl;// 按任意键退出系统system("pause");return 0;default:break;}}system("pause");return 0;
}
删除联系人功能
/*
封装函数显示该界面 如 void showMenu()
在main函数中调用封装好的函数
*/
#include<iostream>
#include<string>
using namespace std;#define MAX 1000// 设计联系人结构体
struct Person {// 姓名string m_Name;// 性别 1 男 2 女int m_Sex;// 年龄int m_Age;// 电话string m_Phone;// 住址string m_Addr;
};// 设计通讯录结构体
struct Addressbooks {//通讯录中保存的联系人数组struct Person personArray[MAX];//通讯录中当前记录联系人个数int m_Size;
};// 1、添加联系人
void addPerson(Addressbooks* abs) {// 判断通讯录是否已满,如果满了就不再添加if (abs->m_Size == MAX) {cout << "通讯录已满,无法添加!" << endl;return;}else {// 添加具体联系人// 姓名string name;cout << "请输入姓名:" << endl;cin >> name;abs->personArray[abs->m_Size].m_Name = name;//性别 1男 2女int sex = 0;cout << "请输入性别(1男 2女):" << endl;while (true) {// 如果输入的是 1 或者 2 可以退出循环,因为输入的是正确值// 如果输入有误,重新输入cin >> sex;if (sex == 1 || sex == 2) {abs->personArray[abs->m_Size].m_Sex = sex;break;}cout << "输入有误,请重新输入!" << endl;}//年龄int age = 0;cout << "请输入年龄(0~300):" << endl;while (true) {cin >> age;if (age > 0 && age < 300) {abs->personArray[abs->m_Size].m_Age = age;break;}cout << "输入有误,请重新输入!" << endl;}//电话string phone;cout << "请输入电话:" << endl;cin >> phone;abs->personArray[abs->m_Size].m_Phone = phone;//住址string addr;cout << "请输入住址:" << endl;cin >> addr;abs->personArray[abs->m_Size].m_Addr = addr;// 更新通讯录人数abs->m_Size++;cout << name << "添加成功!" << endl;//请按任意键继续system("pause");//清屏操作system("cls");}
}// 2、显示联系人
void showPerson(Addressbooks* abs) {//判断通讯录中人数是否为0,如果为0,提示记录为空//如果不为0,显示记录的联系人信息if (abs->m_Size == 0) {cout << "当前记录为空" << endl;}else {for (int i = 0; i < abs->m_Size; i++) {cout << "姓名:" << abs->personArray[i].m_Name << "\t";cout << "性别:" << (abs->personArray[i].m_Sex == 1 ? "男" : "女") << "\t";cout << "年龄:" << abs->personArray[i].m_Age << "\t";cout << "电话:" << abs->personArray[i].m_Phone << "\t";cout << "住址:" << abs->personArray[i].m_Addr << endl;cout << "----------------------------------------------------------------------------------------------------" << endl;}}//请按任意键继续system("pause");//清屏操作system("cls");
}// 检测联系人是否存在,如果存在,返回联系人所在数组中的具体位置,不存在返回-1
// 参数1 通讯录 参数2 对比姓名
int isExist(Addressbooks* abs, string name) {for (int i = 0; i < abs->m_Size; i++) {//找到用户输入的姓名if (abs->personArray[i].m_Name == name) {return i; //找到了,返回这个人在数组中的下标编号}}return -1; //如果遍历结束都没找到,返回-1
}// 3、删除联系人
void deletePerson(Addressbooks* abs) {cout << "请输入您要删除的联系人" << endl;string name;cin >> name;// ret == -1 未查到// ret != -1 查到了int ret = isExist(abs, name);if (ret != -1) {//查到此人,进行删除操作for (int i = ret; i < abs->m_Size - 1; i++) {//数据前移abs->personArray[i] = abs->personArray[i + 1];}abs->m_Size--; //更新通讯录中的人员数cout << "删除成功" << endl;}else {cout << "未查到此人" << endl;}system("pause");system("cls");
}// 菜单界面
void showMenu() {cout << "*********************************" << endl;cout << "******** 1、添加联系人 ********" << endl;cout << "******** 2、显示联系人 ********" << endl;cout << "******** 3、删除联系人 ********" << endl;cout << "******** 4、查找联系人 ********" << endl;cout << "******** 5、修改联系人 ********" << endl;cout << "******** 6、清空联系人 ********" << endl;cout << "******** 0、退出通讯录 ********" << endl;cout << "*********************************" << endl;
}int main() {// 创建通讯录结构体变量Addressbooks abs;// 初始化通讯录中当前人员个数abs.m_Size = 0;// 用户的选择int select = 0;while (true) {// 调用封装好的菜单界面函数showMenu();cin >> select;switch (select){//1、添加联系人case 1: addPerson(&abs); break;//2、显示联系人case 2: showPerson(&abs);break;//3、删除联系人case 3: deletePerson(&abs);break;//4、查找联系人case 4: break;//5、修改联系人case 5: break;//6、清空联系人case 6: break;//0、退出通讯录case 0: cout << "欢迎下次使用" << endl;// 按任意键退出系统system("pause");return 0;default:break;}}system("pause");return 0;
}
查找联系人功能
/*
封装函数显示该界面 如 void showMenu()
在main函数中调用封装好的函数
*/
#include<iostream>
#include<string>
using namespace std;#define MAX 1000// 设计联系人结构体
struct Person {// 姓名string m_Name;// 性别 1 男 2 女int m_Sex;// 年龄int m_Age;// 电话string m_Phone;// 住址string m_Addr;
};// 设计通讯录结构体
struct Addressbooks {//通讯录中保存的联系人数组struct Person personArray[MAX];//通讯录中当前记录联系人个数int m_Size;
};// 1、添加联系人
void addPerson(Addressbooks* abs) {// 判断通讯录是否已满,如果满了就不再添加if (abs->m_Size == MAX) {cout << "通讯录已满,无法添加!" << endl;return;}else {// 添加具体联系人// 姓名string name;cout << "请输入姓名:" << endl;cin >> name;abs->personArray[abs->m_Size].m_Name = name;//性别 1男 2女int sex = 0;cout << "请输入性别(1男 2女):" << endl;while (true) {// 如果输入的是 1 或者 2 可以退出循环,因为输入的是正确值// 如果输入有误,重新输入cin >> sex;if (sex == 1 || sex == 2) {abs->personArray[abs->m_Size].m_Sex = sex;break;}cout << "输入有误,请重新输入!" << endl;}//年龄int age = 0;cout << "请输入年龄(0~300):" << endl;while (true) {cin >> age;if (age > 0 && age < 300) {abs->personArray[abs->m_Size].m_Age = age;break;}cout << "输入有误,请重新输入!" << endl;}//电话string phone;cout << "请输入电话:" << endl;cin >> phone;abs->personArray[abs->m_Size].m_Phone = phone;//住址string addr;cout << "请输入住址:" << endl;cin >> addr;abs->personArray[abs->m_Size].m_Addr = addr;// 更新通讯录人数abs->m_Size++;cout << name << "添加成功!" << endl;//请按任意键继续system("pause");//清屏操作system("cls");}
}// 2、显示联系人
void showPerson(Addressbooks* abs) {//判断通讯录中人数是否为0,如果为0,提示记录为空//如果不为0,显示记录的联系人信息if (abs->m_Size == 0) {cout << "当前记录为空" << endl;}else {for (int i = 0; i < abs->m_Size; i++) {cout << "姓名:" << abs->personArray[i].m_Name << "\t";cout << "性别:" << (abs->personArray[i].m_Sex == 1 ? "男" : "女") << "\t";cout << "年龄:" << abs->personArray[i].m_Age << "\t";cout << "电话:" << abs->personArray[i].m_Phone << "\t";cout << "住址:" << abs->personArray[i].m_Addr << endl;cout << "----------------------------------------------------------------------------------------------------" << endl;}}//请按任意键继续system("pause");//清屏操作system("cls");
}// 检测联系人是否存在,如果存在,返回联系人所在数组中的具体位置,不存在返回-1
// 参数1 通讯录 参数2 对比姓名
int isExist(const Addressbooks* abs, string name) {for (int i = 0; i < abs->m_Size; i++) {//找到用户输入的姓名if (abs->personArray[i].m_Name == name) {return i; //找到了,返回这个人在数组中的下标编号}}return -1; //如果遍历结束都没找到,返回-1
}// 3、删除联系人
void deletePerson(Addressbooks* abs) {cout << "请输入您要删除的联系人" << endl;string name;cin >> name;// ret == -1 未查到// ret != -1 查到了int ret = isExist(abs, name);if (ret != -1) {//查到此人,进行删除操作for (int i = ret; i < abs->m_Size - 1; i++) {//数据前移abs->personArray[i] = abs->personArray[i + 1];}abs->m_Size--; //更新通讯录中的人员数cout << "删除成功" << endl;}else {cout << "未查到此人" << endl;}system("pause");system("cls");
}//4、查找指定联系人信息
void findPerson(const Addressbooks* abs)
{cout << "请输入您要查找的联系人" << endl;string name;cin >> name;int ret = isExist(abs, name);if (ret != -1){cout << "姓名:" << abs->personArray[ret].m_Name << "\t";cout << "性别:" << abs->personArray[ret].m_Sex << "\t";cout << "年龄:" << abs->personArray[ret].m_Age << "\t";cout << "电话:" << abs->personArray[ret].m_Phone << "\t";cout << "住址:" << abs->personArray[ret].m_Addr << endl;}else{cout << "查无此人" << endl;}system("pause");system("cls");
}// 菜单界面
void showMenu() {cout << "*********************************" << endl;cout << "******** 1、添加联系人 ********" << endl;cout << "******** 2、显示联系人 ********" << endl;cout << "******** 3、删除联系人 ********" << endl;cout << "******** 4、查找联系人 ********" << endl;cout << "******** 5、修改联系人 ********" << endl;cout << "******** 6、清空联系人 ********" << endl;cout << "******** 0、退出通讯录 ********" << endl;cout << "*********************************" << endl;
}int main() {// 创建通讯录结构体变量Addressbooks abs;// 初始化通讯录中当前人员个数abs.m_Size = 0;// 用户的选择int select = 0;while (true) {// 调用封装好的菜单界面函数showMenu();cin >> select;switch (select){//1、添加联系人case 1: addPerson(&abs); break;//2、显示联系人case 2: showPerson(&abs);break;//3、删除联系人case 3: deletePerson(&abs);break;//4、查找联系人case 4: findPerson(&abs);break;//5、修改联系人case 5: break;//6、清空联系人case 6: break;//0、退出通讯录case 0: cout << "欢迎下次使用" << endl;// 按任意键退出系统system("pause");return 0;default:break;}}system("pause");return 0;
}
修改联系人功能
/*
封装函数显示该界面 如 void showMenu()
在main函数中调用封装好的函数
*/
#include<iostream>
#include<string>
using namespace std;#define MAX 1000// 设计联系人结构体
struct Person {// 姓名string m_Name;// 性别 1 男 2 女int m_Sex;// 年龄int m_Age;// 电话string m_Phone;// 住址string m_Addr;
};// 设计通讯录结构体
struct Addressbooks {//通讯录中保存的联系人数组struct Person personArray[MAX];//通讯录中当前记录联系人个数int m_Size;
};// 1、添加联系人
void addPerson(Addressbooks* abs) {// 判断通讯录是否已满,如果满了就不再添加if (abs->m_Size == MAX) {cout << "通讯录已满,无法添加!" << endl;return;}else {// 添加具体联系人// 姓名string name;cout << "请输入姓名:" << endl;cin >> name;abs->personArray[abs->m_Size].m_Name = name;//性别 1男 2女int sex = 0;cout << "请输入性别(1男 2女):" << endl;while (true) {// 如果输入的是 1 或者 2 可以退出循环,因为输入的是正确值// 如果输入有误,重新输入cin >> sex;if (sex == 1 || sex == 2) {abs->personArray[abs->m_Size].m_Sex = sex;break;}cout << "输入有误,请重新输入!" << endl;}//年龄int age = 0;cout << "请输入年龄(0~300):" << endl;while (true) {cin >> age;if (age > 0 && age < 300) {abs->personArray[abs->m_Size].m_Age = age;break;}cout << "输入有误,请重新输入!" << endl;}//电话string phone;cout << "请输入电话:" << endl;cin >> phone;abs->personArray[abs->m_Size].m_Phone = phone;//住址string addr;cout << "请输入住址:" << endl;cin >> addr;abs->personArray[abs->m_Size].m_Addr = addr;// 更新通讯录人数abs->m_Size++;cout << name << "添加成功!" << endl;//请按任意键继续system("pause");//清屏操作system("cls");}
}// 2、显示联系人
void showPerson(Addressbooks* abs) {//判断通讯录中人数是否为0,如果为0,提示记录为空//如果不为0,显示记录的联系人信息if (abs->m_Size == 0) {cout << "当前记录为空" << endl;}else {for (int i = 0; i < abs->m_Size; i++) {cout << "姓名:" << abs->personArray[i].m_Name << "\t";cout << "性别:" << (abs->personArray[i].m_Sex == 1 ? "男" : "女") << "\t";cout << "年龄:" << abs->personArray[i].m_Age << "\t";cout << "电话:" << abs->personArray[i].m_Phone << "\t";cout << "住址:" << abs->personArray[i].m_Addr << endl;cout << "----------------------------------------------------------------------------------------------------" << endl;}}//请按任意键继续system("pause");//清屏操作system("cls");
}// 检测联系人是否存在,如果存在,返回联系人所在数组中的具体位置,不存在返回-1
// 参数1 通讯录 参数2 对比姓名
int isExist(const Addressbooks* abs, string name) {for (int i = 0; i < abs->m_Size; i++) {//找到用户输入的姓名if (abs->personArray[i].m_Name == name) {return i; //找到了,返回这个人在数组中的下标编号}}return -1; //如果遍历结束都没找到,返回-1
}// 3、删除联系人
void deletePerson(Addressbooks* abs) {cout << "请输入您要删除的联系人" << endl;string name;cin >> name;// ret == -1 未查到// ret != -1 查到了int ret = isExist(abs, name);if (ret != -1) {//查到此人,进行删除操作for (int i = ret; i < abs->m_Size - 1; i++) {//数据前移abs->personArray[i] = abs->personArray[i + 1];}abs->m_Size--; //更新通讯录中的人员数cout << "删除成功" << endl;}else {cout << "未查到此人" << endl;}system("pause");system("cls");
}//4、查找指定联系人信息
void findPerson(const Addressbooks* abs)
{cout << "请输入您要查找的联系人" << endl;string name;cin >> name;int ret = isExist(abs, name);if (ret != -1){cout << "姓名:" << abs->personArray[ret].m_Name << "\t";cout << "性别:" << abs->personArray[ret].m_Sex << "\t";cout << "年龄:" << abs->personArray[ret].m_Age << "\t";cout << "电话:" << abs->personArray[ret].m_Phone << "\t";cout << "住址:" << abs->personArray[ret].m_Addr << endl;}else{cout << "查无此人" << endl;}system("pause");system("cls");
}// 5、修改联系人
void modifyPerson(Addressbooks* abs) {cout << "请输入您要修改的联系人" << endl;string name;cin >> name;// 判断指定的联系人是否存在通讯录中int ret = isExist(abs, name);if (ret != -1) { // 找到联系人// 姓名string name;cout << "请输入姓名:" << endl;cin >> name;abs->personArray[ret].m_Name = name;//性别 1男 2女int sex = 0;cout << "请输入性别(1男 2女):" << endl;while (true) {// 如果输入的是 1 或者 2 可以退出循环,因为输入的是正确值// 如果输入有误,重新输入cin >> sex;if (sex == 1 || sex == 2) {abs->personArray[ret].m_Sex = sex;break;}cout << "输入有误,请重新输入!" << endl;}//年龄int age = 0;cout << "请输入年龄(0~300):" << endl;while (true) {cin >> age;if (age > 0 && age < 300) {abs->personArray[ret].m_Age = age;break;}cout << "输入有误,请重新输入!" << endl;}//电话string phone;cout << "请输入电话:" << endl;cin >> phone;abs->personArray[ret].m_Phone = phone;//住址string addr;cout << "请输入住址:" << endl;cin >> addr;abs->personArray[ret].m_Addr = addr;cout << "修改成功" << endl;}else { // 未找到联系人cout << "查无此人" << endl;}system("pause");system("cls");
}// 菜单界面
void showMenu() {cout << "*********************************" << endl;cout << "******** 1、添加联系人 ********" << endl;cout << "******** 2、显示联系人 ********" << endl;cout << "******** 3、删除联系人 ********" << endl;cout << "******** 4、查找联系人 ********" << endl;cout << "******** 5、修改联系人 ********" << endl;cout << "******** 6、清空联系人 ********" << endl;cout << "******** 0、退出通讯录 ********" << endl;cout << "*********************************" << endl;
}int main() {// 创建通讯录结构体变量Addressbooks abs;// 初始化通讯录中当前人员个数abs.m_Size = 0;// 用户的选择int select = 0;while (true) {// 调用封装好的菜单界面函数showMenu();cin >> select;switch (select){//1、添加联系人case 1: addPerson(&abs); break;//2、显示联系人case 2: showPerson(&abs);break;//3、删除联系人case 3: deletePerson(&abs);break;//4、查找联系人case 4: findPerson(&abs);break;//5、修改联系人case 5: modifyPerson(&abs);break;//6、清空联系人case 6: break;//0、退出通讯录case 0: cout << "欢迎下次使用" << endl;// 按任意键退出系统system("pause");return 0;default:break;}}system("pause");return 0;
}
清空联系人功能
/*
封装函数显示该界面 如 void showMenu()
在main函数中调用封装好的函数
*/
#include<iostream>
#include<string>
using namespace std;#define MAX 1000// 设计联系人结构体
struct Person {// 姓名string m_Name;// 性别 1 男 2 女int m_Sex;// 年龄int m_Age;// 电话string m_Phone;// 住址string m_Addr;
};// 设计通讯录结构体
struct Addressbooks {//通讯录中保存的联系人数组struct Person personArray[MAX];//通讯录中当前记录联系人个数int m_Size;
};// 1、添加联系人
void addPerson(Addressbooks* abs) {// 判断通讯录是否已满,如果满了就不再添加if (abs->m_Size == MAX) {cout << "通讯录已满,无法添加!" << endl;return;}else {// 添加具体联系人// 姓名string name;cout << "请输入姓名:" << endl;cin >> name;abs->personArray[abs->m_Size].m_Name = name;//性别 1男 2女int sex = 0;cout << "请输入性别(1男 2女):" << endl;while (true) {// 如果输入的是 1 或者 2 可以退出循环,因为输入的是正确值// 如果输入有误,重新输入cin >> sex;if (sex == 1 || sex == 2) {abs->personArray[abs->m_Size].m_Sex = sex;break;}cout << "输入有误,请重新输入!" << endl;}//年龄int age = 0;cout << "请输入年龄(0~300):" << endl;while (true) {cin >> age;if (age > 0 && age < 300) {abs->personArray[abs->m_Size].m_Age = age;break;}cout << "输入有误,请重新输入!" << endl;}//电话string phone;cout << "请输入电话:" << endl;cin >> phone;abs->personArray[abs->m_Size].m_Phone = phone;//住址string addr;cout << "请输入住址:" << endl;cin >> addr;abs->personArray[abs->m_Size].m_Addr = addr;// 更新通讯录人数abs->m_Size++;cout << name << "添加成功!" << endl;//请按任意键继续system("pause");//清屏操作system("cls");}
}// 2、显示联系人
void showPerson(Addressbooks* abs) {//判断通讯录中人数是否为0,如果为0,提示记录为空//如果不为0,显示记录的联系人信息if (abs->m_Size == 0) {cout << "当前记录为空" << endl;}else {for (int i = 0; i < abs->m_Size; i++) {cout << "姓名:" << abs->personArray[i].m_Name << "\t";cout << "性别:" << (abs->personArray[i].m_Sex == 1 ? "男" : "女") << "\t";cout << "年龄:" << abs->personArray[i].m_Age << "\t";cout << "电话:" << abs->personArray[i].m_Phone << "\t";cout << "住址:" << abs->personArray[i].m_Addr << endl;cout << "----------------------------------------------------------------------------------------------------" << endl;}}//请按任意键继续system("pause");//清屏操作system("cls");
}// 检测联系人是否存在,如果存在,返回联系人所在数组中的具体位置,不存在返回-1
// 参数1 通讯录 参数2 对比姓名
int isExist(const Addressbooks* abs, string name) {for (int i = 0; i < abs->m_Size; i++) {//找到用户输入的姓名if (abs->personArray[i].m_Name == name) {return i; //找到了,返回这个人在数组中的下标编号}}return -1; //如果遍历结束都没找到,返回-1
}// 3、删除联系人
void deletePerson(Addressbooks* abs) {cout << "请输入您要删除的联系人" << endl;string name;cin >> name;// ret == -1 未查到// ret != -1 查到了int ret = isExist(abs, name);if (ret != -1) {//查到此人,进行删除操作for (int i = ret; i < abs->m_Size - 1; i++) {//数据前移abs->personArray[i] = abs->personArray[i + 1];}abs->m_Size--; //更新通讯录中的人员数cout << "删除成功" << endl;}else {cout << "未查到此人" << endl;}system("pause");system("cls");
}//4、查找指定联系人信息
void findPerson(const Addressbooks* abs)
{cout << "请输入您要查找的联系人" << endl;string name;cin >> name;int ret = isExist(abs, name);if (ret != -1){cout << "姓名:" << abs->personArray[ret].m_Name << "\t";cout << "性别:" << abs->personArray[ret].m_Sex << "\t";cout << "年龄:" << abs->personArray[ret].m_Age << "\t";cout << "电话:" << abs->personArray[ret].m_Phone << "\t";cout << "住址:" << abs->personArray[ret].m_Addr << endl;}else{cout << "查无此人" << endl;}system("pause");system("cls");
}// 5、修改联系人
void modifyPerson(Addressbooks* abs) {cout << "请输入您要修改的联系人" << endl;string name;cin >> name;// 判断指定的联系人是否存在通讯录中int ret = isExist(abs, name);if (ret != -1) { // 找到联系人// 姓名string name;cout << "请输入姓名:" << endl;cin >> name;abs->personArray[ret].m_Name = name;//性别 1男 2女int sex = 0;cout << "请输入性别(1男 2女):" << endl;while (true) {// 如果输入的是 1 或者 2 可以退出循环,因为输入的是正确值// 如果输入有误,重新输入cin >> sex;if (sex == 1 || sex == 2) {abs->personArray[ret].m_Sex = sex;break;}cout << "输入有误,请重新输入!" << endl;}//年龄int age = 0;cout << "请输入年龄(0~300):" << endl;while (true) {cin >> age;if (age > 0 && age < 300) {abs->personArray[ret].m_Age = age;break;}cout << "输入有误,请重新输入!" << endl;}//电话string phone;cout << "请输入电话:" << endl;cin >> phone;abs->personArray[ret].m_Phone = phone;//住址string addr;cout << "请输入住址:" << endl;cin >> addr;abs->personArray[ret].m_Addr = addr;cout << "修改成功" << endl;}else { // 未找到联系人cout << "查无此人" << endl;}system("pause");system("cls");
}//6、清空所有联系人
void cleanPerson(Addressbooks* abs)
{while (1) {cout << "确定清空所有联系人?" << endl;cout << "请输入:(1表示确定, 2表示取消)" << endl;int ret;cin >> ret;if (ret == 1) {//将当前记录联系人数量置为0,做逻辑清空操作abs->m_Size = 0;cout << "通讯录已清空" << endl;break;}else if (ret == 2) {break;}else {cout << "输入格式不正确,请重新输入!" << endl;system("cls");}}system("pause");system("cls");
}// 菜单界面
void showMenu() {cout << "*********************************" << endl;cout << "******** 1、添加联系人 ********" << endl;cout << "******** 2、显示联系人 ********" << endl;cout << "******** 3、删除联系人 ********" << endl;cout << "******** 4、查找联系人 ********" << endl;cout << "******** 5、修改联系人 ********" << endl;cout << "******** 6、清空联系人 ********" << endl;cout << "******** 0、退出通讯录 ********" << endl;cout << "*********************************" << endl;
}int main() {// 创建通讯录结构体变量Addressbooks abs;// 初始化通讯录中当前人员个数abs.m_Size = 0;// 用户的选择int select = 0;while (true) {// 调用封装好的菜单界面函数showMenu();cin >> select;switch (select){//1、添加联系人case 1: addPerson(&abs); break;//2、显示联系人case 2: showPerson(&abs);break;//3、删除联系人case 3: deletePerson(&abs);break;//4、查找联系人case 4: findPerson(&abs);break;//5、修改联系人case 5: modifyPerson(&abs);break;//6、清空联系人case 6: cleanPerson(&abs);break;//0、退出通讯录case 0: cout << "欢迎下次使用" << endl;// 按任意键退出系统system("pause");return 0;default:break;}}system("pause");return 0;
}
内存分区模型
程序运行前
#include<iostream>
#include<string>
using namespace std;//全局变量
long long g_a = 10;
long long g_b = 10;//全局常量
const long long c_g_a = 10;
const long long c_g_b = 10;int main() {//局部变量// 只要写在函数体内的变量都是局部变量long long a = 10;long long b = 10;//打印地址cout << "局部变量a地址为: " << (long long)&a << endl;cout << "局部变量b地址为: " << (long long)&b << endl;cout << "全局变量g_a地址为: " << (long long)&g_a << endl;cout << "全局变量g_b地址为: " << (long long)&g_b << endl;//静态变量 // 在普通变量前面加上static属于静态变量static long long s_a = 10;static long long s_b = 10;cout << "静态变量s_a地址为: " << (long long)&s_a << endl;cout << "静态变量s_b地址为: " << (long long)&s_b << endl;//常量//①字符串常量cout << "字符串常量地址为: " << (long long)&"hello world" << endl;cout << "字符串常量地址为: " << (long long)&"hello world1" << endl;//②const修饰的全局变量属于常量cout << "全局常量c_g_a地址为: " << (long long)&c_g_a << endl;cout << "全局常量c_g_b地址为: " << (long long)&c_g_b << endl;//③const修饰的局部变量属于常量const long long c_l_a = 10;const long long c_l_b = 10;cout << "局部常量c_l_a地址为: " << (long long)&c_l_a << endl;cout << "局部常量c_l_b地址为: " << (long long)&c_l_b << endl;system("pause");return 0;
}
程序运行后
#include<iostream>
#include<string>
using namespace std;int* func()
{// new int(10) 是int数据类型的初始值是10// 指针 本质是 变量,放在栈上,指针保存的数据放在堆区int* a = new int(10); // 不是把数据本身返回而是返回new出来的内存地址编号return a;
}int main() {// 在堆区开辟数据int *p = func();cout << *p << endl; // 10cout << *p << endl; // 10system("pause");return 0;
}
new操作符
new一个普通变量
#include<iostream>
#include<string>
using namespace std;int* func() {int* a = new int(10);return a;
}void test01() {int* p = func();cout << *p << endl;cout << *p << endl;cout << *p << endl;delete p;
}int main() {test01();system("pause");return 0;
}
#include<iostream>
#include<string>
using namespace std;int* func() {int* a = new int(10);return a;
}void test01() {int* p = func();cout << *p << endl;cout << *p << endl;cout << *p << endl;delete p;cout << *p << endl;
}int main() {test01();system("pause");return 0;
}
new开辟数组
#include<iostream>
#include<string>
using namespace std;int* func() {int* a = new int(10);return a;
}void test01() {int* p = func();cout << *p << endl;cout << *p << endl;cout << *p << endl;delete p;//cout << *p << endl;
}void test02() {int* arr = new int[10];for (int i = 0; i < 10; i++) {arr[i] = i + 100;}for (int i = 0; i < 10; i++) {cout << arr[i] << endl;}delete[] arr;
}int main() {//test01();test02();system("pause");return 0;
}
引用
引用的基本使用
#include<iostream>
#include<string>
using namespace std;int main() {int a = 10;// 创建引用int& b = a;cout << "a = " << a << endl;cout << "b = " << b << endl;b = 100;cout << "a = " << a << endl;cout << "b = " << b << endl;system("pause");return 0;
}
引用注意事项
#include<iostream>
#include<string>
using namespace std;int main() {int a = 10;int &b = a;//int &c; //错误,引用必须初始化int c = 20; //一旦初始化后,就不可以更改b = c; //这是赋值操作,不是更改引用cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;system("pause");return 0;
}
引用做函数参数
#include<iostream>
#include<string>
using namespace std;/*交换函数
*///1. 值传递
void mySwap01(int a, int b) {int temp = a;a = b;b = temp;
}//2. 地址传递
void mySwap02(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}//3. 引用传递 与值传递相比,在形参加了& 引用
void mySwap03(int& a, int& b) {int temp = a;a = b;b = temp;
}int main() {int a = 10;int b = 20;mySwap01(a, b); // 值传递,形参不会修饰实参 【形参发生交换,实参并没有发生交换】cout << "a:" << a << " b:" << b << endl; // 打印结果是 a:10 b:20mySwap02(&a, &b); // 地址传递,形参会修饰实参 【形参发生交换,实参发生交换】cout << "a:" << a << " b:" << b << endl; // 打印结果是 a:20 b:10mySwap03(a, b); // 引用传递,形参会修饰实参 【形参发生交换,实参发生交换】cout << "a:" << a << " b:" << b << endl; // 打印结果是 a:20 b:10system("pause");return 0;
}
总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
引用做函数返回值
#include<iostream>
#include<string>
using namespace std;//返回局部变量引用
// int& 返回值类型后面加上& 表示引用方式返回
int& test01() {int a = 10; //局部变量,存放在四区中的栈区,栈区上的数据在函数执行完后释放return a;
}//返回静态变量引用
int& test02() {static int a = 20; // 静态变量,存放在全局区,全局区上的数据在程序结束后系统释放return a;
}int main() {//1、不能返回局部变量的引用//int& ref = test01(); // 相当于给返回的a 取别名是ref//cout << "ref = " << ref << endl; // 第一次结果正确为10,是编译器做了保留//cout << "ref = " << ref << endl; // 第二次结果错误,因为a的内存已经释放//2、如果函数做左值[左值指 等号的左边],那么必须返回引用int& ref2 = test02();cout << "ref2 = " << ref2 << endl; // 正确 10cout << "ref2 = " << ref2 << endl; // 正确 10// 函数做左值[左值指 等号的左边]// test02() 返回a的引用 相当于a的变量做一个返回。然后它再等于1000 相当于a=1000test02() = 1000;// ref2 相当于就是 a的别名// 即调用后用原名做赋值[test02() = 1000;],然后别名去访问内存cout << "ref2 = " << ref2 << endl; // 1000cout << "ref2 = " << ref2 << endl; // 1000system("pause");return 0;
}
引用的本质
本质:引用的本质在c++内部实现是一个指针常量.
结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
常量引用
#include<iostream>
#include<string>
using namespace std;//引用使用的场景,通常用来修饰形参,防止误操作
void showValue(const int& v) {//v = 1000; // 报错 const修饰,只读cout << v << endl;
}int main() {//int& ref = 10; 引用必须引一块合法的内存空间,因此这行错误//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;// 现在的引用 引的是一块临时空间 这块空间想要操作它,找不到它的原名,原名temp是编译器写好,只能用别名refconst int& ref = 10;//ref = 100; //加入const后变为只读,不可以修改变量cout << ref << endl;//函数中利用常量引用防止误操作修改实参int a = 10;showValue(a);system("pause");return 0;
}
函数提高
函数默认参数
#include<iostream>
#include<string>
using namespace std;int func(int a, int b = 10, int c = 10) {return a + b + c;
}//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
// 报错 b有默认值,那么b后面的c也必须都要有默认值
//int func2(int a, int b = 2,int c) {
// return a + b + c;
//}//2. 如果函数声明有默认值,函数定义的时候就不能有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {return a + b;
}int main() {// 自己传入了数据,就用自己的数据,如果没有,那么用默认值cout << "ret = " << func(20, 20) << endl; // 20+20+10=50cout << "ret = " << func(100) << endl; // 100+10+10=120system("pause");return 0;
}
函数占位参数
#include<iostream>
#include<string>
using namespace std;//函数占位参数
void func(int a, int) {cout << "this is func" << endl;
}// 占位参数也可以有默认参数
void func02(int a, int = 10) {cout << "this is func02" << endl;
}int main() {func(10, 10); //占位参数必须填补func02(10);system("pause");return 0;
}
函数重载
函数重载概述
#include<iostream>
#include<string>
using namespace std;//函数重载需要函数都在同一个作用域下【现在都在全局作用域下】
void func()
{cout << "func 的调用!" << endl;
}
void func(int a)
{cout << "func (int a) 的调用!" << endl;
}
void func(double a)
{cout << "func (double a)的调用!" << endl;
}
void func(int a, double b)
{cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a, int b)
{cout << "func (double a ,int b)的调用!" << endl;
}//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
// cout << "func (double a ,int b)的调用!" << endl;
//}int main() {func();func(10);func(3.14);func(10, 3.14);func(3.14, 10);system("pause");return 0;
}
函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
#include<iostream>
#include<string>
using namespace std;// 函数重载注意事项// 1、引用作为重载条件
void func(int& a)
{cout << "func (int &a) 调用 " << endl;
}void func(const int& a)
{cout << "func (const int &a) 调用 " << endl;
}// 2、函数重载碰到函数默认参数
void func2(int a, int b = 10)
{cout << "func2(int a, int b = 10) 调用" << endl;
}void func2(int a)
{cout << "func2(int a) 调用" << endl;
}int main() {int a = 10;func(a); //调用无const。因为a是变量,可读可写。func(10);//调用有const 1、int &a = 10; 不合法 2、const int &a = 10;合法//func2(10); //这样上面两个func2都可以调用 碰到默认参数产生歧义,需要避免func2(10, 20); //可以!system("pause");return 0;
}
类和对象
封装
#include<iostream>
#include<string>
using namespace std;//圆周率
const double PI = 3.14;//1、封装的意义
//将属性和行为作为一个整体,用来表现生活中的事物//封装一个圆类,求圆的周长
//圆求周长的公式:2 * PI * 半径
//class代表设计一个类,后面跟着的是类名
class Circle
{//访问权限 公共的权限
public://属性//半径int m_r;//行为//获取到圆的周长double calculateZC(){//2 * pi * r//获取圆的周长return 2 * PI * m_r;}
};int main() {//通过圆类,创建具体的圆(对象)// c1就是一个具体的圆(对象)// 实例化 (通过一个类 创建一个对象的过程)Circle c1;//给圆对象的半径 进行赋值操作c1.m_r = 10;//2 * pi * 10 = = 62.8cout << "圆的周长为: " << c1.calculateZC() << endl;system("pause");return 0;
}
案例:设计一个学生类
设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
#include<iostream>
#include<string>
using namespace std;class Student {
public: string m_Name;int m_stuID;void setName(string name) {m_Name = name;}
public:void setID(int id) {m_stuID = id;}void showStu() {cout << "姓名: " << m_Name << "学号: " << m_stuID << endl;}
};int main() {Student s1;s1.setName("Richard");s1.setID(22);s1.showStu();Student s2;s2.setName("Jackson");s2.setID(30);s2.showStu();system("pause");return 0;
}
访问权限
#include<iostream>
#include<string>
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 = 123456;}
};int main() {// 实例化具体对象Person p;p.m_Name = "李四";//p.m_Car = "奔驰"; //保护权限类外访问不到//p.m_Password = 123; //私有权限类外访问不到system("pause");return 0;
}
struct和class区别
#include<iostream>
#include<string>
using namespace std;class C1
{int m_A; //默认是私有权限
};struct C2
{int m_A; //默认是公共权限
};int main() {C1 c1;//c1.m_A = 10; //错误,访问权限是私有,因此在类外不可以访问C2 c2;c2.m_A = 10; //正确,访问权限是公共system("pause");return 0;
}
成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
#include<iostream>
#include<string>
using namespace std;class Person {
public:void setName(string name) {m_Name = name;}void setIdol(string idol) {m_Idol = idol;}string getName() {return m_Name;}int getAge() {return m_Age;}private:string m_Name;int m_Age = 18;string m_Idol;
};int main() {Person p;p.setName("Richard");cout << "姓名: " << p.getName() << endl;cout << "年龄: " << p.getAge() << endl;system("pause");return 0;
}
#include<iostream>
#include<string>
using namespace std;class Person {
public:void setName(string name) {m_Name = name;}void setIdol(string idol) {m_Idol = idol;}string getName() {return m_Name;}int getAge() {return m_Age;}void setAge(int age) {if (age < 0 || age > 200) {cout << "年龄" << age << "输入有误,赋值失败!" << endl;return;}m_Age = age;}private:string m_Name;int m_Age = 18;string m_Idol;
};int main() {Person p;p.setName("Richard");cout << "姓名: " << p.getName() << endl;p.setAge(310);cout << "年龄: " << p.getAge() << endl;system("pause");return 0;
}
练习案例1:设计立方体类
#include<iostream>
using namespace std;
/*立方体类设计
*/ //1、创建立方体类
class Cube {//3、设计行为 获取立方体面积和体积
public://设置长void setL(int l) {m_L = l;}//获取长int getL() {return m_L;}//设置宽void setW(int w) {m_W = w;}//获取宽int getW() {return m_W;}//设置高void setH(int h) {m_H = h;}//获取高int getH() {return m_H;}//获取立方体面积int calculateS() {return 2 * m_H * m_L + 2 * m_H * m_W + 2 * m_L * m_W;}//获取立方体体积int calculateV() {return m_H * m_L * m_W;}//4、利用成员函数 判断两个立方体是否相等 【类里面东西叫成员】bool isSameByClass(Cube& c) { // 自身的 和 传入的进行比较if (m_H == c.getH() && m_L == c.getL() && m_W == c.getW()) {return true;}return false;}//2、设计属性
private:int m_L; //长int m_W; //宽int m_H; //高
};//4、利用全局函数 判断两个立方体是否相等//用引用的方式传入数据,避免值传递要拷贝一份数据出来
bool isSame(Cube& c1, Cube& c2) { if (c1.getH() == c2.getH() && c1.getL() == c2.getL() && c1.getW() == c2.getW()) {return true;}return false;
}int main() {//创建立方体对象Cube c1;c1.setL(10);c1.setW(10);c1.setH(10);//获取立方体面积和体积cout << "c1的面积为:" << c1.calculateS() << endl;cout << "c1的体积为:" << c1.calculateV() << endl;//创建第二个立方体对象Cube c2;c2.setL(10);c2.setW(10);c2.setH(10);// 利用全局函数bool ret = isSame(c1,c2);if (ret) {cout << "全局函数 c1和c2相等" << endl;}else {cout << "全局函数 c1和c2不相等" << endl;}// 利用成员函数ret = c1.isSameByClass(c2);if (ret) {cout << "成员函数 c1和c2相等" << endl;}else {cout << "成员函数 c1和c2不相等" << endl;}system("pause");return 0;
}
#include<iostream>
#include<string>
using namespace std;class Cube {
public:void setL(int l) {m_L = l;}int getL() {return m_L;}void setW(int w) {m_W = w;}int getW() {return m_W;}void setH(int h) {m_H = h;}int getH() {return m_H;}int calculateS() {return 2 * m_H * m_L + 2 * m_H * m_W + 2 * m_L * m_W;}int calculateV() {return m_H * m_L * m_W;}bool isSameByClass(Cube& c) {if (m_H == c.getH() && m_L == c.getL() && m_W == c.getW()) {return true;} else {return false;}}private:int m_L;int m_W;int m_H;
};bool isSame(Cube& c1, Cube& c2) {if (c1.getH() == c2.getH() && c1.getW() == c2.getW() && c1.getL() == c2.getL()) {return true;}else {return false;}
}int main() {Cube c1;c1.setL(10);c1.setH(10);c1.setW(10);cout << "c1的面积为: " << c1.calculateS() << endl;cout << "c1的体积为: " << c1.calculateV() << endl;Cube c2;c2.setL(10);c2.setH(10);c2.setW(11);bool ret = isSame(c1, c2);if (ret) {cout << "全局函数中 c1和c2相等" << endl;}else {cout << "全局函数中 c1和c2不相等" << endl;}ret = c1.isSameByClass(c2);if (ret) {cout << "成员函数中 c1和c2相等" << endl;} else { cout << "成员函数中 c1和c2不相等" << endl;}system("pause");return 0;
}
练习案例2:点和圆的关系
#include<iostream>
using namespace std;/*点和圆关系案例
*/ //点类
class Point {public://行为//设置xvoid setX(int x) {m_X = x;}//获取xint getX() {return m_X;}//设置yvoid setY(int y) {m_Y = y;}//获取yint getY() {return m_Y;}private://属性int m_X;int m_Y;
};//圆类
class Circle {public://行为//设置半径void setR(int r) {m_R = r;}//获取半径int getR() {return m_R;}//设置圆心void setC(Point c) {m_Center = c;}//获取圆心Point getC() {return m_Center;}
private://属性int m_R; //半径//在类中可以让另一个类 作为本来中的成员Point m_Center;//圆心
};//判断点和圆心关系
void isInCircle(Circle& c, Point& p) {//先计算两点之间距离的平方int distance =(c.getC().getX() - p.getX()) * (c.getC().getX() - p.getX()) +(c.getC().getY() - p.getY()) * (c.getC().getY() - p.getY());//再计算半径的平方int rDistance = c.getR() * c.getR();//最后判断关系if (distance == rDistance) {cout << "点在圆上" << endl;}else if (distance > rDistance) {cout << "点在圆外" << endl;}else {cout << "点在圆内" << endl;}
}int main() {//创建圆Circle c;//设置半径c.setR(10);//设置圆心Point center;center.setX(10);center.setY(0);c.setC(center);//创建点Point p;p.setX(10);p.setY(10);//判断关系isInCircle(c, p);system("pause");return 0;
}
在类中可以让另一个类 作为本来中的成员
将代码拆分为头文件,源文件,main函数文件进行工程化
#pragma once是一种简单的方式来防止头文件的重复包含。它告诉编译器只需包含和编译一次头文件,即使它在同一个.cpp文件中多次被包含
代码架构如下:
point.h
#pragma once
#include<iostream>
#include<string>
using namespace std;class Point {
public:void setX(int x);int getX();void setY(int y);int getY();private:int m_X;int m_Y;
};
circle.h
#pragma once
#include<iostream>
#include<string>
#include"point.h"using namespace std;class Circle {
public:void setR(int r);int getR();void setC(Point c);Point getC();private:int m_R;Point m_Center;
};
point.cpp
#include"point.h"void Point::setX(int x) {m_X = x;
}int Point::getX() {return m_X;
}void Point::setY(int y) {m_Y = y;
}int Point::getY() {return m_Y;
}
circle.cpp
#include"circle.h"void Circle::setR(int r) {m_R = r;
}int Circle::getR() {return m_R;
}void Circle::setC(Point c) {m_Center = c;
}Point Circle::getC() {return m_Center;
}
main.cpp
#include<iostream>
#include<string>
#include"point.h"
#include"circle.h"using namespace std;void isInCircle(Circle& c, Point& p) {int distance = (c.getC().getX() - p.getX()) * (c.getC().getX() - p.getX()) + (c.getC().getY() - p.getY()) * (c.getC().getY() - p.getY());int rDistance = c.getR() * c.getR();if (distance == rDistance) {cout << "点在圆上" << endl;}else if (distance > rDistance) {cout << "点在圆外" << endl;}else {cout << "点在圆内" << endl;}
}int main() {Circle c;c.setR(10);Point center;center.setX(10);center.setY(0);c.setC(center);Point p;p.setX(10);p.setY(10);isInCircle(c, p);system("pause");return 0;
}
对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置
构造函数和析构函数
#include<iostream>
#include<string>using namespace std;class Person
{
public://构造函数 进行初始化操作Person(){cout << "Person的构造函数调用" << endl;}//析构函数 进行清理操作~Person(){cout << "Person的析构函数调用" << endl;}};//析构和构造都是必须要有的函数,如果我们自己不提供,编译器会提供一个空实现[里面一行代码也没有]的构造和析构
void test01()
{//只创建了函数对象 并没有调用 但main函数调用测试方法时 自动调用 构造函数//在栈上的数据,test01执行完毕后,释放这个对象,就会自动调用 析构函数Person p;
}int main() {test01(); // 自动调用Person p; //在main函数中创建对象,调用时这个对象并没有被释放,故只调用了构造函数system("pause");return 0;
}
构造函数的分类及调用
#include<iostream>
#include<string>using namespace std;//1、构造函数分类
// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造class Person {
public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int a) {age = a;cout << "有参构造函数!" << endl;}//拷贝构造函数Person(const Person& p) { //将传入的人 身上的所有属性 拷贝到我身上 加上const是为了限制只能拷贝读别人的数据不能修改age = p.age;cout << "拷贝构造函数!" << endl;}//析构函数~Person() {cout << "析构函数!" << endl;}
public:int age;
};//2、构造函数的调用
//调用无参构造函数
void test01() {Person p; //调用无参构造函数
}//调用有参的构造函数
void test02() {//2.1 括号法,常用Person p; //调用无参(默认)构造函数Person p1(10); //调用有参构造函数Person p6(p1); //调用拷贝构造函数cout << "p1的年龄" << p1.age << endl;cout << "p6的年龄" << p6.age << endl;//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明,不会在认为是创建对象//Person p2();//2.2 显式法Person p2 = Person(10); //有参构造Person p3 = Person(p2); //拷贝构造//Person(10); 单独写就是匿名对象 特点:当前行结束之后,系统会立即回收匿名对象 马上析构//注意2:不能利用 拷贝构造函数 初始化匿名对象 //因为编译器认为是对象声明【Person(p3) ——> Person p3】 然后上面已经有Person p3 报错重定义//Person(p3);//2.3 隐式转换法Person p4 = 10; // Person p4 = Person(10); Person p5 = p4; // Person p5 = Person(p4); }int main() {//test01();test02();system("pause");return 0;
}
拷贝构造函数调用时机
#include<iostream>
#include<string>using namespace std;class Person {
public:Person() {cout << "Person的默认构造函数调用" << endl;}Person(int a) {m_Age = a;cout << "Person的有参构造函数调用" << endl;}Person(const Person& p) {m_Age = p.m_Age;cout << "拷贝构造函数的调用" << endl;}~Person() {cout << "Person的析构函数调用" << endl;}public:int m_Age;
};//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {Person p1(20);Person p2(p1);cout << "p2的年龄为: " << p2.m_Age << endl;
}void doWork(Person p) {}//2. 值传递的方式给函数参数传值
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;
}int main() {//test01();//test02();test03();system("pause");return 0;
}
构造函数调用规则
#include<iostream>
#include<string>using namespace std;//创建一个类,C++编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数 (空实现)
//拷贝构造 (值拷贝)class Person {
public://无参(默认)构造函数Person() {cout << "Person的默认构造函数调用" << endl;}//有参构造函数Person(int a) {age = a;cout << "Person的有参构造函数调用" << endl;}//拷贝构造函数Person(const Person& p) {age = p.age;cout << "Person的拷贝构造函数" << endl;}//析构函数~Person() {cout << "Person的析构函数调用" << endl;}
public:int age;
};void test01()
{Person p;p.age = 18;//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作Person p2(p);cout << "p2的年龄为: " << p2.age << endl;
}void test02()
{// 如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造Person p1; // 报错 如果用户提供有参构造,编译器则不提供无参构造,然后用户也不提供无参构造Person p2(10); // 用户提供的有参Person p3(p2); // 此时如果用户没有提供拷贝构造,编译器会提供//如果用户提供拷贝构造,编译器不会提供其他构造函数Person p4; //此时如果用户自己没有提供默认构造,会出错Person p5(10); //此时如果用户自己没有提供有参,会出错Person p6(p5); //用户自己提供拷贝构造
}int main() {test01();system("pause");return 0;
}
深拷贝与浅拷贝
#include<iostream>
#include<string>using namespace std;class Person {
public://无参(默认)构造函数Person() {cout << "Person的默认构造函数调用" << endl;}//有参构造函数Person(int age, int height) {m_Age = age;m_Height = new int(height);cout << "Person的有参构造函数调用" << endl;}//自己实现拷贝构造函数 解决浅拷贝带来的问题Person(const Person& p) {cout << "拷贝构造函数!" << endl;//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题m_Age = p.m_Age;//m_height = p.m_height; 编译器默认实现就是这行代码//深拷贝操作m_Height = new int(*p.m_Height);}//析构函数~Person() {//析构函数 将堆区开辟的数据做释放操作if (m_Height != NULL) {delete m_Height;m_Height = NULL;}cout << "Person的析构函数调用" << endl;}
public:int m_Age;int* m_Height;
};void test01()
{// 先进的数据后被释放,所以p1最后释放Person p1(18, 170);cout << "p1的年龄: " << p1.m_Age << " 身高: " << *p1.m_Height << endl;Person p2(p1);cout << "p2的年龄: " << p2.m_Age << " 身高: " << *p2.m_Height << endl;
}int main() {test01();system("pause");return 0;
}
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
初始化列表
#include<iostream>
#include<string>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(1), m_B(2), m_C(3) {}【调用时直接实例化就成 Person p;】Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}public:int m_A;int m_B;int m_C;
};void PrintPerson() {Person p(30, 20, 10);cout << "mA:" << p.m_A << endl;cout << "mB:" << p.m_B << endl;cout << "mC:" << p.m_C << endl;
}int main() {PrintPerson();system("pause");return 0;
}
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
例如:
#include<iostream>
#include<string>using namespace std;//手机类
class Phone
{public:Phone(string name){m_PhoneName = name;cout << "Phone构造" << endl;}~Phone(){cout << "Phone析构" << endl;}//手机名称string m_PhoneName;};//人类
class Person
{
public://初始化列表可以告诉编译器调用哪一个构造函数//构造函数中告诉你这个人叫什么且手机是什么牌子的Person(string name, string pName) :m_Name(name), m_Phone(pName){cout << "Person构造" << endl;}~Person(){cout << "Person析构" << endl;}void playGame(){cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;}//姓名string m_Name;//手机Phone m_Phone;};
void test01()
{//当类中成员是其他类对象时,我们称该成员为 对象成员//构造的顺序是 :先调用对象成员的构造,再调用本类构造//析构顺序与构造相反Person p("张三", "苹果X");p.playGame();}int main() {test01();system("pause");return 0;
}
静态成员
静态成员变量
#include<iostream>
#include<string>using namespace std;//静态成员变量
class Person
{public://1、所有对象都共享同一份数据//2、在编译阶段分配内存//3、类内声明,类外初始化操作 【必须有这步操作,不然会出错!】static int m_A; //3.1 类内声明private:static int m_B; //静态成员变量也是有访问权限的
};//3.2 类外初始化操作
//Person作用域下的静态成员m_A
int Person::m_A = 10;
int Person::m_B = 10;void test01()
{Person p;cout << "p.m_A = " << p.m_A << endl; //10//静态成员变量两种访问方式//1、通过对象Person p1;p1.m_A = 100;cout << "p1.m_A = " << p1.m_A << endl; //100Person p2;p2.m_A = 200;//静态成员变量 不属于某个对象上,所有对象都共享同一份数据cout << "p1.m_A = " << p1.m_A << endl; //200cout << "p2.m_A = " << p2.m_A << endl; //200//2、通过类名cout << "m_A = " << Person::m_A << endl; //200//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}int main() {test01();system("pause");return 0;
}
静态成员函数
#include<iostream>
#include<string>using namespace std;class Person
{public://静态成员函数特点://1 所有对象共享一个函数//2 静态成员函数只能访问静态成员变量static void func(){cout << "func调用" << endl;m_A = 100; //这个数据是共享的,大家都用一份,不需要区分是那个对象调用它//m_B = 200; //错误,不可以访问非静态成员变量 只能通过对象去访问m_B//原因:无法区分到底是那个对象的m_B属性}static int m_A; //静态成员变量int m_B; // 非静态成员变量
private://静态成员函数也是有访问权限的static void func2(){cout << "func2调用" << endl;}
};int Person::m_A = 10;void test01()
{//静态成员变量两种访问方式//1、通过对象Person p1;p1.func();//2、通过类名Person::func();//Person::func2(); //类外访问不到私有权限静态成员函数
}int main() {test01();system("pause");return 0;
}
C++对象模型和this指针
成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
#include<iostream>
#include<string>using namespace std;class Person {
public://非静态成员变量 属于类的对象上 4个字节int mA;//静态成员变量 不属于类的对象上 4个字节static int mB;//非静态成员函数也 不属于类的对象上,所有函数共享一个函数实例 4个字节void func() {cout << "mA:" << this->mA << endl;}//静态成员函数 不属于类的对象上 4个字节static void sfunc() {}
};
int Person::mB = 0;void test01() {Person p;//空对象占用的内存空间为:1个字节//C++编译器也会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置//每个空对象也应该有一个独一无二的内存地址cout << "size of p = " << sizeof(p) << endl;
}void test02() {Person p1;//空对象占用的内存空间为:1个字节//C++编译器也会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置//每个空对象也应该有一个独一无二的内存地址cout << sizeof(p1) << endl;
}int main() {test01();test02();system("pause");return 0;
}
this指针概念
#include<iostream>
#include<string>using namespace std;class Person
{
public:Person(int age){//age = age; // 出错//1、解决名称冲突//this指针指向 被调用的成员函数 所属的对象//这里是Person p1(10); //p1在调用这个函数 this就指向p1 那么p1赋值10this->age = age; //左边age与属性age是一回事,右边与形参age是一回事}// void PersonAddPerson(Person &p)// {// this->age += p.age; //自身年龄加等于传入人的年龄// }//如果把这里的引用Person& PersonAddPerson(Person &p)改成值Person PersonAddPerson(Person &p)//下面这个test01中的p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);输出是多少?//结果是20//【原因如下:】//当调用完第一次p2.PersonAddPerson(p1)之后 这时p2已经加了10岁 它返回的不是p2的本体 //它按照本体创建了一个新的数据调用拷贝构造函数[用值的方式返回,它会复制一份新的数据]//【即按照自身拷贝构造一个新的数据作为一个返回值p2'】//Person与自身*this已经不一样了//p2'.PersonAddPerson(p1)又返回了一个p2''//整体再来p2''.PersonAddPerson(p1)返回p2'''//每次返回都是一个新的对象与原来的都不一样了Person& PersonAddPerson(Person& p){this->age += p.age; //自身年龄加等于传入人的年龄//2、返回对象本身用*this//this是指向p2的指针,而*this指向的就是p2这个对象本体return *this;}int age;
};void test01()
{//1、解决名称冲突Person p1(10);cout << "p1.age = " << p1.age << endl; // 10Person p2(10);p2.PersonAddPerson(p1);cout << "p2.age = " << p2.age << endl; // 10+10=20//2、返回对象本身用*this//返回的是void就会报错,那么如果p2.PersonAddPerson(p1);返回的是p2对象?//链式编程思想 无限追加p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);cout << "p2.age = " << p2.age << endl; // 40
}int main() {test01();system("pause");return 0;
}
如果Person后面没有加&,结果就是20
如果是一个值的方式返回,它一定创建新的一个对象。
如果是引用的方式返回,它不会创建新的对象,会一直返回那个对象。
空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include<iostream>
#include<string>using namespace std;//空指针访问成员函数
class Person {
public:void ShowClassName() {cout << "我是Person类!" << endl;}void ShowPerson() {if (this == NULL) {return;}//保错原因是因为传入的指针是为NULL,拿空指针访问里面的属性是有问题的,相当于无中生有//必须进行上面判断代码 以防程序崩cout << mAge << endl; //属性前面都默认加了this指针 this->mAge 告诉是当前对象的属性}public:int mAge;
};void test01()
{Person* p = NULL; //类型是Person的空指针pp->ShowClassName(); //正常调用 空指针,可以调用成员函数p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
}int main() {test01();system("pause");return 0;
}
const修饰成员函数
#include<iostream>
#include<string>using namespace std;class Person {
public:Person() {m_A = 0;m_B = 0;}//常函数// this指针的本质是一个指针常量Person *const this,// 指针的指向不可修改, 指向的值可以修改// 如果想让指针指向的值也不可以修改,需要声明常函数// 在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改// 即const Person* const thisvoid ShowPerson() const {//m_A = 100; //函数不加const,可以修改 加上const,报错//this = NULL; //this指针不能修改指针的指向 Person* const this;//this->m_A = 100; //但是this指针指向的对象的数据是可以修改的//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量//const Type* const pointer;this->m_B = 100;}void MyFunc() {m_A = 100; //普通函数可以修改属性}public:int m_A;mutable int m_B; //可修改 可变的
};//常对象//const修饰对象
void test01() {const Person p; //在对象前加const,变为常对象 cout << p.m_A << endl;//p.m_A = 100; //常对象不能修改成员变量的值,但是可以访问p.m_B = 100; //但是常对象可以修改mutable修饰成员变量//常对象只能调用常函数p.ShowPerson();//p.MyFunc(); //报错 常对象不可以调用普通成员函数,因为普通成员函数可以修改属性//普通函数可以修改属性(m_A),但常对象本身不允许修改属性 //如果可以调用则不是侧面说明可以修改 矛盾了}int main() {test01();system("pause");return 0;
}
友元
全局函数做友元
#include<iostream>
#include<string>using namespace std;#include<string>
//建筑物类
class Building
{//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容friend void goodGay(Building* building);public:Building(){this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";}public:string m_SittingRoom; //客厅 公共权限属性private:string m_BedRoom; //卧室 私有权限属性
};//全局函数
void goodGay(Building* building) //这里不用指针,用引用传入也行 &building
{cout << "好基友正在访问: " << building->m_SittingRoom << endl;cout << "好基友正在访问: " << building->m_BedRoom << endl;
}void test01()
{//首先实例化一个building对象 //building对象在创建的同时 就已经把里面的两个属性进行了一个赋初值操作(自己设定好的) //然后再把building对象传入goodGay里面Building b;goodGay(&b); //指针 传入对象地址
}int main() {test01();system("pause");return 0;
}
类做友元
#include<iostream>
#include<string>using namespace std;class Building;
class goodGay
{
public:goodGay();void visit(); //参观函数 访问building中的属性private:Building* building;
};class Building
{//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building本类中私有内容friend class goodGay;public:Building();public:string m_SittingRoom; //客厅
private:string m_BedRoom;//卧室
};//类外写成员函数
Building::Building()
{this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";
}goodGay::goodGay()
{//创建建筑物Building对象building = new Building; //在堆区创建对象 让Building *building指向这个对象
}void goodGay::visit()
{cout << "好基友正在访问" << building->m_SittingRoom << endl;cout << "好基友正在访问" << building->m_BedRoom << endl;
}void test01()
{//首先 创造了goodGay对象 先调用goodGay里面的构造函数(里面是创建Building对象)//即又调用Building构造函数 里面是属性赋初值goodGay gg;//调用visit函数 访问Building维护的属性gg.visit();}int main() {test01();system("pause");return 0;
}
成员函数做友元
#include<iostream>
#include<string>using namespace std;class Building;
class goodGay
{
public:goodGay();void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容void visit2(); //visit2函数不能访问Building中私有内容private:Building* building;
};class Building
{//告诉编译器 goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容friend void goodGay::visit();public:Building();public:string m_SittingRoom; //客厅
private:string m_BedRoom;//卧室
};Building::Building()
{this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";
}goodGay::goodGay()
{building = new Building;
}void goodGay::visit()
{cout << "好基友正在访问" << building->m_SittingRoom << endl;cout << "好基友正在访问" << building->m_BedRoom << endl;
}void goodGay::visit2()
{cout << "好基友正在访问" << building->m_SittingRoom << endl;//cout << "好基友正在访问" << building->m_BedRoom << endl;
}void test01()
{goodGay gg;gg.visit();}int main() {test01();system("pause");return 0;
}
运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载
作用:实现两个自定义数据类型相加的运算
#include<iostream>
#include<string>using namespace std;// 加号运算符重载class Person {
public:// 1、成员函数重载+号/*Person operator+(Person& p) {Person tmp;tmp.m_A = this->m_A + p.m_A;tmp.m_B = this->m_B + p.m_B;return tmp;}*/int m_A;int m_B;
};//2、全局函数实现 + 号运算符重载
Person operator+(Person& p1, Person& p2) {Person tmp;tmp.m_A = p1.m_A + p2.m_A;tmp.m_B = p1.m_B + p2.m_B;return tmp;
}//3、运算符重载 可以发生函数重载
Person operator+(Person& p1, int num) {Person tmp;tmp.m_A = p1.m_A + num;tmp.m_B = p1.m_B + num;return tmp;
}void test() {Person p1;p1.m_A = 10;p1.m_B = 10;Person p2;p2.m_A = 10;p2.m_B = 10;// 成员函数重载本质调用//Person p3 = p1.operator+(p2);// 全局函数重载本质调用Person p3 = operator+(p1, p2);//Person p3 = p1 + p2;// 运算符重载,也可以发生函数重载Person p4 = p1 + 100; // Person + intcout << "p3.m_A = " << p3.m_A << endl;cout << "p3.m_B = " << p3.m_B << endl;cout << "p4.m_A = " << p4.m_A << endl;cout << "p4.m_B = " << p4.m_B << endl;
}int main() {test();system("pause");return 0;
}
总结1:对于内置的数据类型的表达式的的运算符是不可能改变的
总结2:不要滥用运算符重载
左移运算符重载
作用:可以输出自定义数据类型
#include<iostream>
#include<string>using namespace std;// 左移运算符重载
class Person {friend ostream& operator<<(ostream& cout, Person& p);
public:Person(int a, int b) {m_A = a;m_B = b;}
private://1、不会利用成员函数重载<<运算符,无法实现 cout在左侧// p.operator<<(p) 这里不对,Person调用函数之后还要传入Person对象 // 这里首先要有个对象,还要传入一个对象 有两个对象了 现在我们这里只有一个对象 不符合//void operator<<(Person& p){ //}// p.operator<<(cout) 本质简化版本 p << cout 但我们想要的是cout << p//void operator<<(cout){ //}int m_A;int m_B;
};//2、只能利用全局函数重载左移运算符
//ostream对象只能有一个 故 ostream& cout 加上 &
//本质operator<<(cout,p) 简化 cout << p
//引用本身起别名 这里out就是cout的别名
ostream& operator<<(ostream &cout, Person &p) {cout << "m_A = " << p.m_A << " m_B = " << p.m_B;return cout;
}void test01() {Person p(10, 30);cout << p << endl;;
}int main() {test01();system("pause");return 0;
}
总结:重载左移运算符配合友元可以实现输出自定义数据类型
递增运算符重载
作用: 通过重载递增运算符,实现自己的整型数据
#include<iostream>
#include<string>using namespace std;// 重载递增运算符// 自定义整型
class myInteger {friend ostream& operator<<(ostream& cout, const myInteger &myint);
public:myInteger() {m_Num = 0;}// 重载++运算符// 重载前置++//myInteger& 返回引用 为了一直对一个数据进行递增操作myInteger& operator++() {m_Num++;return *this;}// 重载后置++//myInteger operator++(int) 里面的int 表示占位参数 可以用于区分前置和后置递增myInteger operator++(int) {myInteger tmp = *this;m_Num++;return tmp;}
private:int m_Num;
};// 重载左移运算符
//将operator << 的第二个参数改为const引用,这样它就可以接受临时对象了
ostream& operator<<(ostream& cout, const myInteger &myint) {cout << myint.m_Num;return cout;
}void test01() {myInteger myint;cout << ++myint << endl;
}void test02() {myInteger myint;cout << myint++ << endl;cout << myint << endl;
}int main() {test01();test02();system("pause");return 0;
}
总结: 前置递增返回引用,后置递增返回值
赋值运算符重载
#include<iostream>
#include<string>using namespace std;// 赋值运算符重载
class Person {
public:Person(int age) {m_Age = new int(age);}~Person() {if (m_Age != NULL) {delete m_Age;m_Age = NULL;}}// 重载 赋值运算符Person& operator=(Person &p) {// 编译器提供浅拷贝// m_Age = p.m_Age;//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝//Person p2(20); //p2已经有一块堆区内存[存放20] if (m_Age != NULL) {delete m_Age;m_Age = NULL;}// 深拷贝m_Age = new int(*p.m_Age);//返回自身return *this;}int* m_Age;
};void test01() {Person p1(24);Person p2(20);Person p3(30);p3 = p2 = p1;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 Person {
public:Person(string name, int age) {m_Name = name;m_Age = age;}// 重载 == 号bool operator==(Person &p) {if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {return true;} return false;}// 重载 != 号bool operator!=(Person& p) {if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {return false;}return true;}string m_Name;int m_Age;
};void test01() {Person p1("Tom", 18);Person p2("Tom", 18);if (p1 == p2) {cout << " p1 和 p2 是相等的!" << endl;}else {cout << " p1 和 p2 是不相等的!" << endl;}if (p1 != p2) {cout << " p1 和 p2 是不相等的!" << endl;}else {cout << " p1 和 p2 是相等的!" << endl;}
}int main() {test01();system("pause");return 0;
}
函数调用运算符重载
- 函数调用运算符 () 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
#include<iostream>
#include<string>using namespace std;//重载函数调用运算符// 打印输出类
class myPrint {
public:void operator()(string test) {cout << test << endl;}
};void test01() {//重载了()之后,让对象使用重载后的() //由于使用起来非常类似于函数调用,因此称为仿函数myPrint myPrint;myPrint("hello world");
}//仿函数非常灵活,没有固定写法
//加法类class Myadd {
public:int operator()(int num1, int num2) {return num1 + num2;}
};void test02() {Myadd myadd;int ret = myadd(100, 200);cout << "ret = " << ret << endl;// 匿名函数对象cout << Myadd()(100, 200) << endl;
}int main() {test01();test02();system("pause");return 0;
}
继承
我们发现,定义这些类,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码
继承的基本语法
例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同
接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处
普通实现
#include<iostream>
#include<string>using namespace std;//Java页面
class Java
{
public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "JAVA学科视频" << endl;}
};//Python页面
class Python
{
public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "Python学科视频" << endl;}
};
//C++页面
class CPP
{
public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "C++学科视频" << endl;}
};void test01()
{//Java页面cout << "Java下载视频页面如下: " << endl;Java ja;ja.header();ja.footer();ja.left();ja.content();cout << "--------------------" << endl;//Python页面cout << "Python下载视频页面如下: " << endl;Python py;py.header();py.footer();py.left();py.content();cout << "--------------------" << endl;//C++页面cout << "C++下载视频页面如下: " << endl;CPP cp;cp.header();cp.footer();cp.left();cp.content();}int main() {test01();system("pause");return 0;
}
继承实现
#include<iostream>
#include<string>using namespace std;// 公共页面
class BasePage
{
public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}};// Java页面
class Java : public BasePage {
public:void content() {cout << "JAVA学科视频" << endl;}
};//Python页面
class Python : public BasePage
{
public:void content(){cout << "Python学科视频" << endl;}
};//C++页面
class CPP : public BasePage
{
public:void content(){cout << "C++学科视频" << endl;}
};void test01()
{//Java页面cout << "Java下载视频页面如下: " << endl;Java ja;ja.header();ja.footer();ja.left();ja.content();cout << "--------------------" << endl;//Python页面cout << "Python下载视频页面如下: " << endl;Python py;py.header();py.footer();py.left();py.content();cout << "--------------------" << endl;//C++页面cout << "C++下载视频页面如下: " << endl;CPP cp;cp.header();cp.footer();cp.left();cp.content();}int main() {test01();system("pause");return 0;
}
总结
继承方式
#include<iostream>
#include<string>using namespace std;// 继承方式// 公共继承
class Base1 {
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son1 :public Base1 {
public:void func() {m_A = 10; //父类中的公共权限成员 到子类中依然是公共权限 public权限m_B = 10; //父类中的保护权限成员 到子类中依然是保护权限 protected权限//m_C; 父类中的私有权限成员 子类不可访问}
};void test01() {Son1 s1;s1.m_A = 100; //可以访问 公共权限属性 类内可以访问,类外也可以访问//s1.m_B = 100; 报错 保护权限属性 类内可以访问,类外是不可以访问的
}// 保护继承
class Base2 {
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son2 :protected Base2 {
public:void func() {m_A; //父类中的公共权限成员 到子类中变为保护权限 protected权限m_B; //父类中的保护权限成员 到子类中依然保护权限 protected权限//m_C; //父类中的私有权限成员 子类不可访问}
};void test02() {Son2 s1;//s1.m_A; //在Son2中m_A变为保护权限属性 类外不可访问//s1.m_B; //在Son2中m_B是保护权限属性 类外不可访问
}// 私有继承
class Base3 {
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son3 :private Base3 {
public:void func() {m_A; //父类中的公共权限成员 到子类中变为私有权限 private权限m_B; //父类中的保护权限成员 到子类中变为私有权限 private权限//m_C; //父类中的私有权限成员 子类不可访问}
};class GrandSon :public Son3 {
public:void func() {//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到//m_A; //报错 到了Son3中 m_A变为私有,即使是儿子,也是访问不到//m_B; //报错//m_C; //报错}
};void test03() {Son3 s;//s.m_A = 1000;
}int main() {system("pause");return 0;
}
继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
#include<iostream>
#include<string>using namespace std;class Base
{
public:int m_A;
protected:int m_B;
private:int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};//公共继承
class Son :public Base
{
public:int m_D;
};void test01()
{//父类中所有非静态成员属性都会被子类继承下去//父类中私有成员属性 是被编译器给隐藏 因此是被访问不到 但是确实被继承下去了cout << "sizeof Son = " << sizeof(Son) << endl; // 16
}int main() {test01();system("pause");return 0;
}
结论: 父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
开发人员命令提示符工具
继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
#include<iostream>
#include<string>using namespace std;class Base
{
public:Base(){cout << "Base构造函数!" << endl;}~Base(){cout << "Base析构函数!" << endl;}
};class Son : public Base
{
public:Son(){cout << "Son构造函数!" << endl;}~Son(){cout << "Son析构函数!" << endl;}};void test01()
{//继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反Son s;
}int main() {test01();system("pause");return 0;
}
总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
继承同名成员处理方式
#include<iostream>
#include<string>using namespace std;// 继承中同名成员处理
class Base {
public:Base(){m_A = 100;}void func(){cout << "Base - func()调用" << endl;}void func(int a){cout << "Base - func(int a)调用" << endl;}public:int m_A;
};class Son : public Base {
public:Son(){m_A = 200;}//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域void func(){cout << "Son - func()调用" << endl;}
public:int m_A;
};// 同名成员属性处理
void test01()
{Son s;cout << "Son下的m_A = " << s.m_A << endl; //200//如果通过子类对象 访问到父类中同名成员,需要加作用域 Base::cout << "Base下的m_A = " << s.Base::m_A << endl;
}void test02() {Son s;s.func(); //直接调用 调用的是子类中的同名成员 Son - func()调用s.Base::func(); //调用的是父类中的同名成员 Base - func()调用//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数//如果想访问到父类中被隐藏的同名成员函数,需要加作用域s.Base::func(10);
}int main() {test01();test02();system("pause");return 0;
}
总结
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
继承同名静态成员处理方式
#include<iostream>
#include<string>using namespace std;class Base {
public:static void func(){cout << "Base - static void func()" << endl;}static void func(int a){cout << "Base - static void func(int a)" << endl;}static int m_A;
};int Base::m_A = 100;class Son : public Base {
public:static void func(){cout << "Son - static void func()" << endl;}static int m_A;
};int Son::m_A = 200;//同名静态成员属性
void test01()
{//通过对象访问cout << "通过对象访问: " << endl;Son s;cout << "Son 下 m_A = " << s.m_A << endl;cout << "Base 下 m_A = " << s.Base::m_A << endl;//通过类名访问cout << "通过类名访问: " << endl;cout << "Son 下 m_A = " << Son::m_A << endl;// 第一个::代表通过类名方式访问 第二个::代表访问父类作用域下cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}//同名静态成员函数
void test02()
{//通过对象访问cout << "通过对象访问: " << endl;Son s;s.func();s.Base::func();cout << "通过类名访问: " << endl;Son::func();Son::Base::func();//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问Son::Base::func(100);
}
int main() {//test01();test02();system("pause");return 0;
}
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和 通过类名)
多继承语法
#include<iostream>
#include<string>using namespace std;class Base1 {
public:Base1(){m_A = 100;}
public:int m_A;
};class Base2 {
public:Base2(){m_A = 200; //开始是m_B 不会出问题,但是改为m_A就会出现不明确//m_B = 200;}
public:int m_A;// int m_B;
};//语法:class 子类:继承方式 父类1 ,继承方式 父类2...
class Son : public Base2, public Base1
{
public:Son(){m_C = 300;m_D = 400;}
public:int m_C;int m_D;
};//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{Son s;cout << "sizeof Son = " << sizeof(s) << endl; // 16cout << s.Base1::m_A << endl;cout << s.Base2::m_A << endl;
}int main() {test01();system("pause");return 0;
}
总结: 多继承中如果父类中出现了同名情况,子类使用时候要加作用域
虚继承解决菱形继承问题
#include<iostream>
#include<string>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 = 100; //报错 因为属性m_Age羊类有一份 驼类有一份 不明确//加作用域明确是哪个类里面的st.Sheep::m_Age = 100;st.Tuo::m_Age = 200;//当菱形继承,两个父类拥有相同数据,需要加以作用域区分cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl; //100 虚继承后结果为 200cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl; //200 虚继承后结果为 200//这份数据我们知道 只要一份就可以 菱形继承导致数据有两份 资源浪费cout << "st.m_Age = " << st.m_Age << endl; //虚继承后 这里不会出现不明确情况了 结果为200
}int main() {test01();system("pause");return 0;
}
总结
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题
多态
多态的基本概念
#include<iostream>
#include<string>using namespace std;// 多态// 动物类
class Animal {
public:// Speak函数就是虚函数// 函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。virtual void speak() {cout << "动物在说话" << endl;}
};// 猫类
class Cat : public Animal {
public:// 重写 函数返回值类型 函数名参数列表 完全相同// 子类重写时 virtual关键字可写可不写void speak() {cout << "小猫在说话" << endl;}
};// 狗类
class Dog : public Animal {
public:void speak() {cout << "小狗在说话" << endl;}
};// 执行说话的函数
// 地址早绑定 在编译阶段就确定函数地址
// 如果想执行让猫说话, 那么这个函数地址就不能提前绑定, 需要在运行阶段进行绑定, 地址晚绑定//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void doSpeak(Animal& animal) {// Animal& animal = cat;animal.speak();
}//动态多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数//重写:函数返回值类型 函数名参数列表 完全相同
//动态多态使用:
//父类指针或引用 指向子类对象//Animal & animal = cat; Animal &父类引用 指向子类传入对象catvoid test01() {Cat cat;doSpeak(cat);Dog dog;doSpeak(dog);
}int main() {test01();system("pause");return 0;
}
多态的原理剖析
父类函数前面没有virtual
#include<iostream>
#include<string>using namespace std;// 多态// 动物类
class Animal {
public:// Speak函数就是虚函数// 函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。void speak() {cout << "动物在说话" << endl;}
};// 猫类
class Cat : public Animal {
public:// 重写 函数返回值类型 函数名参数列表 完全相同// 子类重写时 virtual关键字可写可不写void speak() {cout << "小猫在说话" << endl;}
};// 狗类
class Dog : public Animal {
public:void speak() {cout << "小狗在说话" << endl;}
};// 执行说话的函数
// 地址早绑定 在编译阶段就确定函数地址
// 如果想执行让猫说话, 那么这个函数地址就不能提前绑定, 需要在运行阶段进行绑定, 地址晚绑定//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void doSpeak(Animal& animal) {// Animal& animal = cat;animal.speak();
}//动态多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数//重写:函数返回值类型 函数名参数列表 完全相同
//动态多态使用:
//父类指针或引用 指向子类对象//Animal & animal = cat; Animal &父类引用 指向子类传入对象catvoid test01() {Cat cat;doSpeak(cat);Dog dog;doSpeak(dog);
}void test02() {cout << "size of Animal = " << sizeof(Animal) << endl;
}int main() {//test01();test02();system("pause");return 0;
}
cl /d1 reportSingleClassLayoutAnimal main.cpp
父类函数前面有virtual
#include<iostream>
#include<string>using namespace std;// 多态// 动物类
class Animal {
public:// Speak函数就是虚函数// 函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。virtual void speak() {cout << "动物在说话" << endl;}
};// 猫类
class Cat : public Animal {
public:// 重写 函数返回值类型 函数名参数列表 完全相同// 子类重写时 virtual关键字可写可不写void speak() {cout << "小猫在说话" << endl;}
};// 狗类
class Dog : public Animal {
public:void speak() {cout << "小狗在说话" << endl;}
};// 执行说话的函数
// 地址早绑定 在编译阶段就确定函数地址
// 如果想执行让猫说话, 那么这个函数地址就不能提前绑定, 需要在运行阶段进行绑定, 地址晚绑定//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void doSpeak(Animal& animal) {// Animal& animal = cat;animal.speak();
}//动态多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数//重写:函数返回值类型 函数名参数列表 完全相同
//动态多态使用:
//父类指针或引用 指向子类对象//Animal & animal = cat; Animal &父类引用 指向子类传入对象catvoid test01() {Cat cat;doSpeak(cat);Dog dog;doSpeak(dog);
}void test02() {cout << "size of Animal = " << sizeof(Animal) << endl;
}int main() {//test01();test02();system("pause");return 0;
}
子类函数里没有重写父类的虚函数时
#include<iostream>
#include<string>using namespace std;// 多态// 动物类
class Animal {
public:// Speak函数就是虚函数// 函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。virtual void speak() {cout << "动物在说话" << endl;}
};// 猫类
class Cat : public Animal {
public:// 重写 函数返回值类型 函数名参数列表 完全相同// 子类重写时 virtual关键字可写可不写/*void speak() {cout << "小猫在说话" << endl;}*/
};// 狗类
class Dog : public Animal {
public:void speak() {cout << "小狗在说话" << endl;}
};// 执行说话的函数
// 地址早绑定 在编译阶段就确定函数地址
// 如果想执行让猫说话, 那么这个函数地址就不能提前绑定, 需要在运行阶段进行绑定, 地址晚绑定//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void doSpeak(Animal& animal) {// Animal& animal = cat;animal.speak();
}//动态多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数//重写:函数返回值类型 函数名参数列表 完全相同
//动态多态使用:
//父类指针或引用 指向子类对象//Animal & animal = cat; Animal &父类引用 指向子类传入对象catvoid test01() {Cat cat;doSpeak(cat);Dog dog;doSpeak(dog);
}void test02() {cout << "size of Animal = " << sizeof(Animal) << endl;
}int main() {//test01();test02();system("pause");return 0;
}
cl /d1 reportSingleClassLayoutCat main.cpp
子类函数里面重写了父类的虚函数
#include<iostream>
#include<string>using namespace std;// 多态// 动物类
class Animal {
public:// Speak函数就是虚函数// 函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。virtual void speak() {cout << "动物在说话" << endl;}
};// 猫类
class Cat : public Animal {
public:// 重写 函数返回值类型 函数名参数列表 完全相同// 子类重写时 virtual关键字可写可不写void speak() {cout << "小猫在说话" << endl;}
};// 狗类
class Dog : public Animal {
public:void speak() {cout << "小狗在说话" << endl;}
};// 执行说话的函数
// 地址早绑定 在编译阶段就确定函数地址
// 如果想执行让猫说话, 那么这个函数地址就不能提前绑定, 需要在运行阶段进行绑定, 地址晚绑定//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void doSpeak(Animal& animal) {// Animal& animal = cat;animal.speak();
}//动态多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数//重写:函数返回值类型 函数名参数列表 完全相同
//动态多态使用:
//父类指针或引用 指向子类对象//Animal & animal = cat; Animal &父类引用 指向子类传入对象catvoid test01() {Cat cat;doSpeak(cat);Dog dog;doSpeak(dog);
}void test02() {cout << "size of Animal = " << sizeof(Animal) << endl;
}int main() {//test01();test02();system("pause");return 0;
}
多态案例一:计算器类
#include<iostream>
#include<string>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;}}//如果要提供新的运算,需要修改源码//在真实开发中 提倡 开闭原则//开闭原则:对扩展进行开发,对修改进行关闭int m_Num1;int m_Num2;
};void test01() {// 创建计算器对象Calculator c;c.m_Num1 = 10;c.m_Num2 = 2;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;
}//利用多态实现计算器
//计算器抽象类(基类)
//多态优点:
//1、代码组织结构清晰,
//2、可读性强,
//3、利于前期和后期的扩展以及维护
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 SubCalculator : public AbstractCalculator {
public:int getResult() {return m_Num1 - m_Num2;}
};// 乘法计算器类
class MulCalculator : public AbstractCalculator {
public:int getResult() {return m_Num1 * m_Num2;}
};void test02() {// 多态使用条件// 父类指针或者引用指向子类对象// 加法运算AbstractCalculator* abc = new AddCalculator;abc->m_Num1 = 30;abc->m_Num2 = 60;cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;delete abc; //用完了记得销毁 因为是在堆区数据//创建减法计算器abc = new SubCalculator;abc->m_Num1 = 100;abc->m_Num2 = 6;cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;delete abc;//创建乘法计算器abc = new MulCalculator;abc->m_Num1 = 31;abc->m_Num2 = 10;cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;delete abc;
}int main() {//test01();test02();system("pause");return 0;
}
总结:C++开发提倡利用多态设计程序架构,因为多态优点很多
纯虚函数和抽象类
#include<iostream>
#include<string>using namespace std;// 纯虚函数和抽象类
class Base {
public:// 纯虚函数//类中只要有一个纯虚函数就称为抽象类//抽象类特点://1、抽象类无法实例化对象//2、子类必须重写父类中的纯虚函数,否则也属于抽象类virtual void func() = 0;
};class Son : public Base {
public:virtual void func() {cout << "func函数调用" << endl;}
};void test01() {//Base b; // 错误,抽象类无法实例化对象//base = new Base; // 错误,抽象类无法实例化对象//Son s; //子类必须重写父类中的纯虚函数,否则无法实例化对象Base* base = new Son;base->func();delete base;
}int main() {test01();system("pause");return 0;
}
多态案例二:制作饮品
案例描述:
制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶
#include<iostream>
#include<string>using namespace std;//抽象制作饮品
class AbstractDrinking {
public:// 煮水virtual void Boil() = 0;// 冲泡virtual void Brew() = 0;// 倒入杯中virtual void PourInCup() = 0;// 加入辅料virtual void PutSomething() = 0;// 制作饮品void makeDrink() {Boil();Brew();PourInCup();PutSomething();}
};// 制作咖啡
class Coffee :public AbstractDrinking {
public://烧水virtual void Boil() {cout << "煮农夫山泉!" << endl;}//冲泡virtual void Brew() {cout << "冲泡咖啡!" << endl;}//倒入杯中virtual void PourInCup() {cout << "将咖啡倒入杯中!" << endl;}//加入辅料virtual void PutSomething() {cout << "加入牛奶!" << endl;}
};//制作茶水
class Tea : public AbstractDrinking {
public://烧水virtual void Boil() {cout << "煮自来水!" << endl;}//冲泡virtual void Brew() {cout << "冲泡茶叶!" << endl;}//倒入杯中virtual void PourInCup() {cout << "将茶水倒入杯中!" << endl;}//加入辅料virtual void PutSomething() {cout << "加入枸杞!" << endl;}
};//制作业务函数
void doWork(AbstractDrinking * abs) {//AbstractDrinking* drink = new Coffee 父类指针指向子类对象abs->makeDrink();delete abs;
}void test01() {// 制作咖啡doWork(new Coffee);cout << "--------------------------" << endl;doWork(new Tea);
}int main() {test01();system("pause");return 0;
}
多态:一个接口多个形态【传入对象不同,但都是同一个接口会制作不同的饮品】
虚析构和纯虚析构
父类没有虚析构函数时,可能造成内存泄露,因为没有把子类堆区释放干净
#include<iostream>
#include<string>using namespace std;// 虚析构和纯虚析构
class Animal {
public:Animal() {cout << "Animal 构造函数调用" << endl;}//析构函数加上virtual关键字,变成虚析构函数//利用虚析构可以解决 父类指针释放子类对象时不干净的问题~Animal() {cout << "Animal 析构函数调用" << endl;}
};class Cat :public Animal {
public:Cat(string name) {cout << "Cat构造函数调用" << endl;m_Name = new string(name);}virtual void speak() {cout << *m_Name<< "小猫在说话" << endl;}~Cat() {if (m_Name != NULL) {cout << "Cat析构函数调用" << endl;delete m_Name;m_Name = NULL;}}string* m_Name;
};void test01() {Animal* animal = new Cat("Tom");animal->speak();//父类指针在析构时候 不会调用子类中析构函数 导致子类如果有堆区属性 会出现内存泄漏情况 //怎么解决?给基类增加一个虚析构函数//虚析构函数就是用来解决通过父类指针释放子类对象delete animal;
}int main() {test01();system("pause");return 0;
}
父类有虚析构函数后,可以把子类的堆区释放干净
#include<iostream>
#include<string>using namespace std;// 虚析构和纯虚析构
class Animal {
public:Animal() {cout << "Animal 构造函数调用" << endl;}//析构函数加上virtual关键字,变成虚析构函数//利用虚析构可以解决 父类指针释放子类对象时不干净的问题virtual~Animal() {cout << "Animal 析构函数调用" << endl;}
};class Cat :public Animal {
public:Cat(string name) {cout << "Cat构造函数调用" << endl;m_Name = new string(name);}virtual void speak() {cout << *m_Name<< "小猫在说话" << endl;}~Cat() {if (m_Name != NULL) {cout << "Cat析构函数调用" << endl;delete m_Name;m_Name = NULL;}}string* m_Name;
};void test01() {Animal* animal = new Cat("Tom");animal->speak();//父类指针在析构时候 不会调用子类中析构函数 导致子类如果有堆区属性 会出现内存泄漏情况 //怎么解决?给基类增加一个虚析构函数//虚析构函数就是用来解决通过父类指针释放子类对象delete animal;
}int main() {test01();system("pause");return 0;
}
纯虚析构也需要实现
#include<iostream>
#include<string>using namespace std;// 虚析构和纯虚析构
class Animal {
public:Animal() {cout << "Animal 构造函数调用" << endl;}//析构函数加上virtual关键字,变成虚析构函数//利用虚析构可以解决 父类指针释放子类对象时不干净的问题//virtual ~Animal()//{// cout << "Animal虚析构函数调用!" << endl;//}//纯虚析构函数 虚析构与纯虚析构只能有一个//需要声明也需要实现//有了纯虚析构后 这个类也属于抽象类 无法实例化对象virtual~Animal() = 0;// 纯虚函数virtual void speak() = 0;
};Animal::~Animal() {cout << "Animal纯虚析构函数调用!" << endl;
}class Cat :public Animal {
public:Cat(string name) {cout << "Cat构造函数调用" << endl;m_Name = new string(name);}virtual void speak() {cout << *m_Name<< "小猫在说话" << endl;}~Cat() {if (m_Name != NULL) {cout << "Cat析构函数调用" << endl;delete m_Name;m_Name = NULL;}}string* m_Name;
};void test01() {Animal* animal = new Cat("Tom");animal->speak();//父类指针在析构时候 不会调用子类中析构函数 导致子类如果有堆区属性 会出现内存泄漏情况 //怎么解决?给基类增加一个虚析构函数//虚析构函数就是用来解决通过父类指针释放子类对象delete animal;
}int main() {test01();system("pause");return 0;
}
和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。
总结
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
多态案例三:电脑组装
电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include<iostream>
#include<string>using namespace std;//抽象不同零件类
//抽象CPU类
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 work() {//让零件工作起来,调用接口m_cpu->calculate();m_vc->display();m_mem->storage();}//提供析构函数 释放3个电脑零件~Computer() {// 释放CPU零件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;} }private:CPU* m_cpu; //CPU的零件指针VideoCard* m_vc; //显卡零件指针Memory* m_mem; //内存条零件指针
};//具体厂商
//Intel厂商
class IntelCPU :public CPU {
public:IntelCPU() {cout << "IntelCPU构造函数" << endl;}virtual void calculate() {cout << "Intel的CPU开始计算了!" << endl;}
};class IntelVideoCard :public VideoCard
{
public:virtual void display(){cout << "Intel的显卡开始显示了!" << endl;}
};class IntelMemory :public Memory
{
public:virtual void storage(){cout << "Intel的内存条开始存储了!" << endl;}
};//Lenovo厂商
class LenovoCPU :public CPU
{
public:virtual void calculate(){cout << "Lenovo的CPU开始计算了!" << endl;}
};class LenovoVideoCard :public VideoCard
{
public:virtual void display(){cout << "Lenovo的显卡开始显示了!" << endl;}
};class LenovoMemory :public Memory
{
public:virtual void storage(){cout << "Lenovo的内存条开始存储了!" << endl;}
};void test01() {//第一台电脑零件CPU* intelCpu = new IntelCPU;VideoCard* intelCard = new IntelVideoCard;Memory* intelMem = new IntelMemory;//创建第一台电脑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 LenovoCPU, new IntelVideoCard, new LenovoMemory);;computer3->work();delete computer3;
}int main() {test01();system("pause");return 0;
}
总的来说,确实需要在 CPU, VideoCard, Memory 这样的抽象基类中提供虚析构函数,以确保通过这些基类指针管理的派生类对象能够在其生命周期结束时正确释放资源。这是良好的C++编程习惯,是保证资源正确管理的重要部分。
文件
文本文件
文件打开方式
写文件
写文件步骤如下:
- 包含头文件
#include - 创建流对象
ofstream ofs; - 打开文件
ofs.open(“文件路径”,打开方式); - 写数据
ofs << “写入的数据”; - 关闭文件
ofs.close();
#include <iostream>
#include <string>
#include <fstream>using namespace std;//文本文件 写文件void test01() {ofstream ofs;ofs.open("test.txt", ios::out);ofs << "姓名:张三" << endl;ofs << "性别:男" << endl;ofs << "年龄:18" << endl;ofs.close();
}int main() {test01();system("pause");return 0;
}
总结
读文件
读文件与写文件步骤相似,但是读取方式相对于比较多
读文件步骤如下:
- 包含头文件
#include - 创建流对象
ifstream ifs; - 打开文件并判断文件是否打开成功
ifs.open(“文件路径”,打开方式); - 读数据
四种方式读取 - 关闭文件
ifs.close();
#include <iostream>
#include <string>
#include <fstream>using namespace std;//文本文件 读文件void test01() {// 创建流对象ifstream ifs;//打开文件 并且判断是否打开成功ifs.open("test.txt", ios::in);if (!ifs.is_open()) {cout << "文件打开失败" << endl;return;}// 读数据//第一种方式 字符数组//char buf[1024] = { 0 };//while (ifs >> buf) {// cout << buf << endl;//}//第二种方式 一行一行数据放入字符数组/*char buf[1024] = { 0 };while (ifs.getline(buf, sizeof(buf))) {cout << buf << endl;}*///第三种方式 字符串/*string buf;while (getline(ifs, buf)) {cout << buf << endl;}*///第四种方式 不推荐! 一个个字符读 一个个数据放到字符c中char c;// EOF end of file 文件尾部标识while ((c = ifs.get()) != EOF) {cout << c;}// 关闭文件ifs.close();
}int main() {test01();system("pause");return 0;
}
总结
二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为 ios::binary
写文件
#include <iostream>
#include <string>
#include <fstream>using namespace std;// 二进制文件 写文件
class Person {
public:char m_Name[64];int m_Age;
};void test01() {//2、创建输出流对象ofstream ofs("person.txt", ios::out | ios::binary);//3、打开文件//ofs.open("person.txt", ios::out | ios::binary);//4、写文件Person p = { "张三" , 18 };ofs.write((const char*)&p, sizeof(p)); //&p 返回的是person* 但要const char型 所以强转//5、关闭文件ofs.close();
}int main() {test01();system("pause");return 0;
}
文件输出流对象 可以通过write函数,以二进制方式写数据
读文件
#include <iostream>
#include <string>
#include <fstream>using namespace std;// 二进制文件 写文件
class Person {
public:char m_Name[64];int m_Age;
};void test01() {ifstream ifs("person.txt", ios::in | ios::binary);if (!ifs.is_open()) {cout << "文件打开失败" << endl;return;}Person p;ifs.read((char*)&p, sizeof(p));cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;ifs.close();
}int main() {test01();system("pause");return 0;
}
文件输入流对象 可以通过read函数,以二进制方式读数据
之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!