一、跳转语句(goto 语句)
作用:可以无条件跳转语句
语法: goto 标记;
解释:如果标记的名称存在、执行到goto语句时。会跳转到标记的位置。
#include<iostream>
using namespace std;
int mian(){
cout << "1" << endl;
goto flag;
//下面三行代码将不执行
cout << "2" << endl;
cout << "3" << endl;
cout << "4" << endl;
flag: // 将跳到这里执行 注意这里是冒号
cout << "5" << endl;
system("pause");
return 0;
}
结果为:
1
5
二.数组
所谓数组,就是一个集合,里面存放了相同类型的数据元素。
特点1:数组中的每个数据元素都是相同的数据类型
特点2:数据是由连续的内存位置组成的
### 一维数组
## 一维数组的定义方式
一维数组的定义的三种方式:
1. 数据类型 数组名[数组长度];
2. 数据类型 数组名[数组长度] ={值1,值2,...};
3. 数据类型 数组名[] = {值1,值2,...};
#include<iostream>
using namespace std;
int main() {
//第一种定义方法: 数据类型 数组名[数组长度];
int arr[3];
// 给数组种的元素赋值
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
//遍历数据元素
for (int i = 0; i < 3; i++) { //注意: 数组的下标是从0开始索引的
cout << arr[i]<< " "; //结果为:10 20 30
}
cout << endl;
// ----------------------------------------
//第二种定义方式: 数据类型 数组名[数组长度] ={值1,值2,...};
int arr2[3] = {10,20,30}; // 如果{}内的数据个数不足数组的下标数,剩余数组数据用0补全
//遍历数据元素
for (int i = 0; i < 3; i++) { //注意: 数组的下标是从0开始索引的
cout << arr2[i]<< " "; //结果为:10 20 30
}
cout << endl;
//---------------------------------------
// 第三种定义方式: 数据类型 数组名[] = {值1,值2,...};
int arr3[] = {10,20,30};
//遍历数据元素
for (int i = 0; i < 3; i++) { //注意: 数组的下标是从0开始索引的
cout << arr3[i]<< " "; //结果为:10 20 30
}
cout << endl;
system("pause");
return 0;
}
## 一维数组数组名
一维数组名称的用途:
可以统计整个数组在内存中的长度
可以获取数组在内存中的首地址
#include<iostream>
using namespace std;
int main() {
// 数组名的用途
// 1.可以获取整个占用内存空间大小
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// sizeof用法:
// 在 C/C++ 中,sizeof() 是一个判断数据类型或者表达式长度的运算符。
// 详细请到 https://blog.csdn.net/weixin_39598135/article/details/111011490
cout << "整个数组所占内存空间为: " << sizeof(arr) << endl; // 40
cout << "每个元素所占的空间为: " << sizeof(arr[0]) << endl; // 4
cout << "数组的元素的个数:" << sizeof(arr) / sizeof(arr[0]) << endl; // 10
// 2. 可以通过数组名获取到数组首地址
cout << "数组首地址为: " << (int)arr << endl; // arr数组首地址
cout << "数组中第一个元素地址为: "<< (int)&arr[0] <<endl;
// 两个结果相同
system("pause");
return 0;
}
//注:数组名是常量,不可以进行赋值操作
如: arr = 100; //错误
## 练习案例
# 1. 五只小猪称体重
// 题目:五只小猪称体重
// 描述:在一个数组中记录了五只小猪的体重, 如:int arr[5] = {300,350,200,400,250};
// 找出并打印最重的小猪体重。
#include<iostream>
using namespace std;
int main() {
int arr[5] = { 300,350,200,400,250 };
int max=0;
for (int i = 0; i < 5; i++) {
if (max < arr[i]) {
max = arr[i];
}
}
cout << "最重的小猪: " << max << endl; // 400
system("pause");
return 0;
}
# 2. 数组元素逆置
//题目: 数组元素逆置
//描述: 请声明一个5个元素的数组,并且将元素逆置
// 如原数组元素为:1,2,3,4,5; 逆置后输出结果为:4,5,2,3,1;
#include<iostream>
using namespace std;
int main(){
int arr[5] = { 1,2,3,4,5 };
int start = 0; //起始元素下标
int end = sizeof(arr) / sizeof(arr[0]) - 1; //末尾数组下标
int temp = 0; //中间变量
for (int i = 0; i < (sizeof(arr) / sizeof(arr[0]) /2); i++) {
temp = arr[i];
arr[i] = arr[end - i];
arr[end - i] = temp;
}
for (int i = 0; i < end + 1; i++) {
cout << arr[i] << " ";
} // 5 4 3 2 1
system("pause");
return 0;
}
## 冒泡排序
// 利用冒泡排序实现升序序列
#include<iostream>
using namespace std;
int main() {
int arr[] = { 4,2,1,3,6,7,9,8,5 };
int temp = 0;
int number = sizeof(arr) / sizeof(arr[0]);
cout << "排序前:" << endl;
for (int i = 0; i < number; i++) {
cout << arr[i] << " "; //1 2 3 4 5 6 7 8 9
}
for (int i = 0; i < number - 1; i++) { // 排序轮数
for (int j = 0; j < number - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
cout << endl;
cout << "排序后:" << endl;
for (int i = 0; i < number; i++) {
cout << arr[i] << " "; //1 2 3 4 5 6 7 8 9
}
system("pause");
return 0;
}
### 二维数组
## 二维数组 的定义方式
二维数组定义的四种方式:
数据类型 数组名[行数][列数];
数据类型 数组名[行数][列数] = { {数据1,数据2},{数据3,数据四 }};
数据类型 数组名[行数][列数] = { 数据1,数据2,数据3,数据四 };
数据类型 数组名[][列数] = { 数据1,数据2,数据3,数据四 };
注1:数组行数、列数的下标是从第0行第0列开始的;
注2:在定义二维数组时,如果初始化了数据,可以省略行数;
## 二维数组数组名
1.查看二维数组所占内存空间
2.获取二维数组的首地址
#include<iostream>
using namespace std;
int main() {
int arr[2][3] = {
{1,2,3},
{4,5,6}
};
cout << "二维数组所占空间大小: " << sizeof(arr) << endl; //4*6 =24
cout << "二维数组一行所占空间大小: " << sizeof(arr[0]) << endl; // 4*3 =12
cout << "二维数组一个元素所占空间大小: " << sizeof(arr[0][0]) << endl; //4
// 可以查看二维数组的首地址
cout << "二维数组首地址: " << (int)arr << endl;
cout << "二维数组的第一行首地址: " << (int)arr[0] << endl;
cout << "二维数组的第一个元素地址: " << (int)&arr[0][0] << endl;
// 结果相同
system("pause");
return 0;
}
## 练习案例
// 题目: 考试成绩统计
// 描述: 有三名同学(张三,李四,王五),在一次考试中的成绩分别如下表;
语文 数学 英语
张三 {100,100,100},
李四 {90, 50, 100},
王五 {60, 70, 80}
//请分别输出 三名同学的总成绩;
#include<iostream>
using namespace std;
int main() {
int student[3][3] = {
{100,100,100},
{90,50,100},
{60,70,80}
};
string name[3] = { "张三","李四","王五" };
for (int i = 0; i < 3; i++) {
int sum = 0;
for (int j = 0; j < 3; j++) {
sum += student[i][j];
}
cout << name[i] << "的总成绩:"<< sum << endl;
//张三的总成绩:300
//李四的总成绩:240
//王五的总成绩:210
}
system("pause");
return 0;
}
三.函数
## 值传递
所谓值传递,就是函数调用时实参将数值传入给形参
值传递时,如果形参发生改变,并不影响实参
#include<iostream>
using namespace std;
// 当函数不要返回值,定义的时候可以写void
void swap(int num1, int num2) {
cout << "交换前:" << endl;
cout << "num1 = " << num1 << endl; //10
cout << "num2 = " << num2 << endl; //20
int temp = 0;
temp = num1;
num1 = num2;
num2 = temp;
cout << "交换后:" << endl;
cout << "num1 = " << num1 << endl; //20
cout << "num2 = " << num2 << endl; //10
//return;当定义函数时候,不需要返回值。可以不写return。
}
int main() {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
cout << "----------------------------"<<endl;
cout << "num1 = " << num1 << endl; //10
cout << "num2 = " << num2 << endl; //20
//实参num1 和num2的值没有发生交换
system("pause");
return 0;
}
## 函数的分文件编写
作用: 让代码结构更加清晰
函数的分文件编写一般有4个步骤
1.创建后缀名为.h的头文件
2.创建后缀名为.cpp的源文件
3.在头文件中写函数的声明
4.在源文件中写函数的定义
// swap.h文件
#include<iostream>
using namespace std;
void swap(int a, int b);
//swap.cpp文件
#include "swap.h"
void swap1(int num1, int num2) {
cout << "交换前:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
int temp = 0;
temp = num1;
num1 = num2;
num2 = temp;
cout << "交换后:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
//return ;当函数声明时候,不需要返回值。可以不写return。
}
int main() {
int a = 10, b = 20;
swap(a, b);
system("pause");
return 0;
}
四.指针
指针的基本概念
指针的作用:可以通过指针间接访问内存
指针是一个记录地址的特殊变量
#include<iostream>
using namespace std;
int main() {
//1.定义一个指针
int a = 10;
//指针定义的语法:数据类型 * 指针变量名
int* p;
//让指针记录变量a的地址
p = &a;
//2.使用指针
//通过解引用的方式来找到指针指向的内存。
//指针前加 * 代表解引用, 找到或修改指针指向的内存的数据
cout << a << endl; //10
cout << *p << endl; //10
*p = 1000;
cout << a << endl; //1000
cout << *p << endl; //1000
system("pause");
return 0;
}
## 指针所占内存空间
在32位操作系统下,指针是占4个字节空间大小,不管是什么数据类型
在64位操作系统下,指针是占8个字节空间大小,不管是什么数据类型
## 空指针和野指针
### 空指针
空指针:指针变量指向内存中的编号为0的空间
用途: 初始化指针变量
注意:空指针指向的内存是不可以访问的
#include<iostream>
using namespace std;
int main() {
//指针变量p指针指向内存地址编号的空间
int* p = NULL;
//访问空指针报错
// 内存编号0-255为系统占用内存,不允许用户访问
//cout << *p << endl; //报错
system("pause");
return 0;
}
### 野指针
野指针:指针变量指向非法的内存空间
int main() {
//指针变量p指向内存地址编号为Ox1100
int* p = (int*)0x1100; //报错
cout << *p << endl;
system("pause");
return 0;
}
总结:空指针和野指针都不是我们申请空间。因此不要访问。
## const 修饰指针
const 修饰指针有三种情况:
1.const 修饰指针 --- 常量指针
特点:指针的指向可以修改,但是指针指向的值不可以改。
2.const 修饰常量 ---指针常量
特点:指针的指向不可以改,指针指向的值可以改。
3.const 即修饰指针又修饰常量
特点:指针的指向不可以改,指针指向的值也不可以改。
技巧:看const 右侧紧跟的是指针还是常量,是指针就是常量指针,是常量就是指针常量
#include<iostream>
using namespace std;
int main() {
//◦ 1.const 修饰指针 --- 常量指针
//特点:指针的指向可以修改,但是指针指向的值不可以改。
int a = 10;
int b = 20;
const int* p = &a;
// *p = 20;//不行
p = &b;
cout << *p << endl; //20
//2.const 修饰常量 ---指针常量
//特点:指针的指向不可以改,指针指向的值可以改。
int* const p2 = &a;
*p2 = 20;
//p2 = &b; //不行
cout << *p2 << endl; //20;
//3.const 即修饰指针又修饰常量
//特点:指针的指向不可以改,指针指向的值也不可以改。
const int* const p3 = &a;
//*p3 = 20; //不行
//p3 = &b; //不行
system("pause");
return 0;
}
## 指针和数组
#include<iostream>
using namespace std;
int main() {
//指针和数组
//利用指针访问数组中的元素
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr; //arr就是数组的首地址
for (int i = 0; i < 10; i++) {
cout << *p << endl;
p++;
}
system("pause");
return 0;
}
## 指针和函数
#include<iostream>
using namespace std;
//值传递
void swap01(int a, int b) {
int temp;
temp = a;
a = b;
b = temp;
}
//地址传递
void swap02(int* a, int* b) {
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int main() {
// 1.值传递
int a = 10;
int b = 20;
swap01(a, b);
cout << a << endl; //10
cout << b << endl; //20
//2.地址传递
//交换前:
cout << a << endl; //10
cout << b << endl; //20
//交换后:
swap02(&a, &b);
cout << a << endl; //20
cout << b << endl; //10
system("pause");
return 0;
}
## 案例
#include<iostream>
using namespace std;
void bubbleSort(int* arr, int len) { // int *arr 也可以写为int arr[]
//冒泡排序
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
void printArray(int *arr,int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
}
int main() {
int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
int len;
len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len);
//打印数组
printArray(arr,len);
system("pause");
return 0;
}
五.结构体
## 结构体定义和使用
结构体基本概念
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
结构体定义的使用
语法: struct 结构体名 {结构体成员列表}
通过结构体创建变量的方式有三种:
struct 结构体 变量名
struct 结构体 变量名 = {成员1值,成员2值}
定义结构体时顺便创建变量
#include<iostream>
#include<string>
using namespace std;
// 创建学生数据类型: 学生包括(姓名,年龄,分数)
typedef struct Student {
// 成员列表
string name;
int age = 0;
int score = 0;
}STU ;
struct Student1 {
// 成员列表
string name;
int age = 0;
int score = 0;
}s3;
int main() {
STU s1;
s1.name = "张三";
s1.age = 18;
s1.score = 100;
cout << "姓名: " << s1.name << endl;
cout << "年龄: " << s1.age << endl;
cout << "分数: " << s1.score << endl;
STU s2={"李四",19,80};
cout << "姓名: " << s2.name << endl;
cout << "年龄: " << s2.age << endl;
cout << "分数: " << s2.score << endl;
s3.name = "刘备";
s3.score = 12;
cout << "----------------" << endl;
cout << "姓名: " << s3.name << endl;
cout << "分数: " << s3.score << endl;
// struct 可以省略。
//struct Student1 s4;
//Student1 s4;
system("pause");
return 0;
}
总结1: 定义结构体时的关键词是struct,不可省略
总结2:创建结构体变量时,关键词struct 可以省略
总结3:结构体变量利用操作符"."访问成员
## 结构体数组
作用:将自定义的结构体放入数组中方便维护。
语法:struct 结构体 数组名[元素个数] = {{},{},{}}
#include<iostream>
using namespace std;
struct Student {
string name; // 姓名
int age; // 年龄
int score; // 分数
};
int main() {
struct Student arr[3] = {
{"张三",18,80},
{"李四",19,23},
{"王五",20,12}
};
// 给结构体数组中的元素赋值
arr[2].name = "赵六";
arr[2].age = 12;
arr[2].score = 212;
int len = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < len; i++) {
cout << "姓名: " << arr[i].name << " 年龄: " << arr[i].age << " 分数: " <<arr[i].score << endl;
}
system("pause");
return 0;
}
//结果:
//姓名: 张三 年龄: 18 分数: 80
//姓名: 李四 年龄: 19 分数: 23
//姓名: 赵六 年龄: 12 分数: 212
## 结构体指针
作用:通过指针访问结构体的成员。
利用操作符->可以通过结构体指针访问结构体属性。
#include<iostream>
#include<string>
using namespace std;
typedef struct student {
string name;
int age;
int score;
}STU;
int main() {
STU s1 = { "张三", 18,100 };
STU* p = &s1;
cout << "姓名: " << p->name
<< " 年龄: " << p->age
<< " 分数: " << p->score <<endl;
system("pause");
return 0;
}
## 结构体做函数参数
作用:将结构体作为参数向函数中传递
传递方式有两种:
值传递
地址传递
#include<iostream>
#include<string>
using namespace std;
typedef struct student {
string name;
int age;
int score;
}STU;
//值传递
void printStudent(STU stu) {
stu.age = 10;
cout << "值传递中函数中年龄: " << stu.age << endl; //10
}
//地址传递
void printStudent1(STU* p) {
p->age = 112;
cout << "地址传递中子函数中年龄: " << p->age << endl; //112
}
int main() {
STU stu = { "张三",12,345 };
printStudent(stu);
cout << "主函数中年龄: " << stu.age << endl; //12 != 10 改变形参不改变实参
printStudent1(&stu);
cout << "主函数中年龄: " << stu.age << endl; //112 改变形参改变实参
system("pause");
return 0;
}
总结: 如果不想修改主函数中的数据,用值传递,反之用地址传递。
## 结构体中const 使用场景
作用:用const来防止误操作
#include<iostream>
using namespace std;
struct student {
string name; //姓名
int age; //年龄
int socre; // 分数
};
//将函数中的形参改成指针,可以减少内存空间。
void printfStudent(const student* stu) {
//stu->age =100;//操作失败,因为加了const修改
cout << "姓名: " << stu->name << " 年龄: " << stu->age << " 分数: " << stu->socre << endl;
}
int main() {
student stu = { "张三",100,12 };
printfStudent(&stu);
system("pause");
return 0;
}
## 结构体案例
案例描述:
学校正在做毕设项目,每个老师带领5个学生,总共有3名老师,需求如下:
设计学生和老师的结构体,其中在老师的结构体中,有老师姓名和一个存放5名学生的数组作为成员
学生的成员有姓名、考试分数,创建数组存放3名老师,通过函数给每个老师及所带的学生赋值
最终打印出老师数据以及老师所带的学生数据。
#include<iostream>
#include<string>
#include<ctime>
using namespace std;
struct Student {
string sname;
int score=0;
};
struct Teacher {
string tname;
struct Student stuAArray[5];
};
void allocatespace(struct Teacher tArray[], int len) {
string nameSeed = "ABCDE";
for (int i = 0; i < len; i++) {
tArray[i].tname = "Teacher_";
tArray[i].tname += nameSeed[i];
for (int j = 0; j < 5; j++) {
tArray[i].stuAArray[j].sname = "Student_";
tArray[i].stuAArray[j].sname += nameSeed[j];
int random = rand() % 60 +40 ;
tArray[i].stuAArray[j].score = random;
}
}
}
void printInfo(struct Teacher *tArray, int len) {
for (int i = 0; i < len; i++) {
cout << "老师姓名: " << tArray[i].tname << endl;
for (int j = 0; j < 5; j++) {
cout << "\t学生名字: " << tArray[i].stuAArray[j].sname
<< " 学生分数: " << tArray[i].stuAArray[j].score << endl;
}
}
}
int main() {
//随机数种子
srand((unsigned int)time(NULL));
struct Teacher tArray[3];
int len = sizeof(tArray) / sizeof(tArray[0]);
allocatespace(tArray, len);
printInfo (tArray,len);
system("pause");
return 0;
}
六 .内存分区模型
c++程序在执行时,将内存大方向划分4个区域
代码区: 存放函数体的二进制代码,有操作系统进行管理的
全局区:存放全局变量和静态变量以及常量
栈区:由编译区自动分配释放,存放函数的参数值,局部变量等。
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
内存四区意义:
不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。
## 1.程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
存放cpu执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令。
全局区:
全局变量和静态变量存放在此。
全局区还包括了常量区,字符串常量和const 修饰的全局变量也存放在此。
该区域的数据在程序结束后操作系统释放。
总结:
c++中在程序运行前分为全局区和代码区;
代码区特点是共享和只读
## 2.程序运行后
栈区:
由编译区自动分配释放,存放函数的参数值 局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。
堆区:
由程序员分配释放,若程序员不释放,程序结束时,有操作系统回收
在c++中主要利用new在堆区开辟内存。
#include<iostream>
using namespace std;
int* func() {
//利用new关键字 可以将数据开辟到堆区
//指针 本质也是局部变量,放在栈上,指针保存的数据放在堆区
int* p = new int(10);
return p;
}
int main() {
//在堆区开辟数据
int* p = func();
cout << *p << endl; //10
system("pause");
return 0;
}
## 3.new操作符
c++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法: new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针。
#include<iostream>
using namespace std;
int* func() {
//在堆区创建整型数据
//new 返回是 该数据类型的指针
int* a = new int(10);
return a;
}
//2.在堆区利用new开辟数组
void test02() {
//创建10个整型数据的数组,在堆区
int *p = new int[10];
for (int i = 0; i < 10; i++) {
p[i] = i + 100;
}
for (int i = 0; i < 10; i++) {
cout << p[i] << endl;
}
//释放堆区数组
//释放数组的时候 要加[] 才可以
delete[]p;
}
int main() {
int* p = func();
cout << *p << endl;
//如果想释放堆区的数据,利用关键词delete
delete p;
// cout << *p << endl;
test02();
system("pause");
return 0;
}
七 .引用
## 1 引用的基本使用
作用:给变量起别名
语法:数据类型 &别名 = 原名
#include<iostream>
using namespace std;
int main() {
//引用的基本语法
//数据类型 &别名 = 原名
int a = 10;
int& b = a;
cout << "a: " << a << endl; //10
cout << "b: " << b << endl; //10
b = 20;
cout << "a: " << a << endl; //20
cout << "b: " << b << endl; //20
system("pause");
return 0;
}6-5
## 2.引用注意事项
.引用必须初始化
.引用在初始化后,不可以改变
#include<iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
//int &c; //错误,引用必须初始化
int& c = a;// 一旦初始化后,就不更改
c = b; //这是赋值操作,不是更改引用
cout << "a=" << a << endl; //20
cout << "b=" << b << endl; //20
cout << "c=" << c << endl;//20
system("pause");
return 0;
}
## 3.引用做函数参数
作用:函数传递时,可以利用引用的技术让形参修饰实参
优点:可以简化指针-修改实参
#include<iostream>
using namespace std;
//值传递
void mySwap01(int a,int b) {
int temp = a;
a = b;
b = temp;
}
//地址传递
void mySwap02(int* a, int* b) {
int temp;
temp = *a;
*a = *b;
*b = temp;
}
//引用传递
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 << endl; //10
cout << "b = " << b << endl; //20
mySwap02(&a, &b);
cout << "a = " << a << endl; //20
cout << "b = " << b << endl; //10
mySwap03(a, b);
cout << "a = " << a << endl; //10
cout << "b = " << b << endl; //20
system("pause");
return 0;
}
总结: 通过引用参数产生的效果同按地址传递是一样。引用的语法更清楚简单。
## 4.引用做函数的返回值
作用:引用是可以作为函数的返回存在的。
注意:不要返回局部变量引用
用法:函数调用作为左值
#include<iostream>
using namespace std;
//不要返回局部变量的引用
int& test01() {
int a = 200; //局部变量 放在四区中的栈区
return a;
}
// 函数的调用可以作为左值。
int& test02() {
static int a = 20; // 静态变量,存放在全局区,全局区上的数据在程序结束后系统释放
return a;
}
int main() {
int& ref = test01();
cout << "ref = " << ref << endl; //-858993460 不要返回局部变量的引用
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl; //20
cout << "ref2 = " << ref2 << endl; //20
//如果函数的返回值是引用,这个函数调用可以作为左值
test02() = 1000;
cout << "ref2 = " << ref2 << endl; //1000
cout << "ref2 = " << ref2 << endl; //1000
system("pause");
return 0;
}
## 2.5引用的本质
本质:引用的本质在c++内部实现是一个指针常量
#include<iostream>
using namespace std;
// 发现是引用,转换为int* const ref = &a;
void func(int &ref) {
ref = 100; //ref 是引用,转换为*ref =100
}
int main() {
int a = 10;
//自动转换为:int*const ref =&a; 指针常量是指针指向不可改。也说明为什么引用不可更改
int& ref = a;
ref = 20; //内部发现ref 是引用,自动帮我们转换为:*ref =20;
cout << "a: " << a << endl; //20
cout << "ref: " << ref << endl; //20
func(a);
cout << "a: " << a << endl; //100
cout << "ref: " << ref << endl; //100
system("pause");
return 0;
}
## 2.6常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参中,可以加const 修饰形参,防止形参改变实参。
#include<iostream>
using namespace std;
int main() {
//常量引用
//使用场景:用来修饰操作,防止误操作
int a = 10;
// int& ref = 10;// 引用必须引一块合法的内存空间
const int& ref = 10;
//ref = 10; //加入const 之后变为只读,不可以修改
system("pause");
return 0;
}
八 .函数提高
## 1.函数默认参数
在c++中,函数的形参列表中的形参是可以有默认值的
语法: 返回值类型 函数名 (参数 = 默认值){}
注意事项:
1. 如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
2.如果函数声明有默认参数,函数实现就不能有默认参数 声明和实现只能有一个有默认参数
#include<iostream>
using namespace std;
//函数默认参数
//语法: 返回值类型 函数名(形参 = 默认值)
int func(int a,int b ,int c) {
return a + b + c;
}
int func2(int a, int b = 10, int c = 20) {
return a + b + c;
}
//注意事项:
//1. 如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
//
//int func3(int a, int b = 10, int c) { //报错
// return a + b + c;
//}
//2.如果函数声明有默认参数,函数实现就不能有默认参数
//声明和实现只能有一个有默认参数
int func3(int a, int b); //声明函数
int func3(int a, int b) {
return a + b;
}
//int func4(int a=10, int b=10); //声明函数 //报错
//int func4(int a=10, int b=10) {
// return a + b;
//}
int func5(int a = 10, int b = 10);
int func5(int a,int b) {
return a + b;
}
int main() {
int sum = func(10, 20, 30);
cout << "sum = " << sum << endl; //60
int sum1 = func2(10);
cout << "sum1 = " << sum1 << endl; //40
//如果我们自己传入了数据,就用自己的数据,如果没有,那么用默认值。
int sum2 = func2(10,20);
cout << "sum1 = " << sum2 << endl; //50
//cout << func4(10, 10);
cout << func5() << endl;
system("pause");
return 0;
}
## 2.函数默认参数
c++ 中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置。
语法: 返回值类型 函数名 (数据类型){}
#include<iostream>
using namespace std;
//占位参数, 还可以有默认参数。
void func(int a,int = 20) {
cout << "this is func " << endl;
}
int main() {
func(10);
system("pause");
return 0;
}
## 3.函数重载
函数重载概述
作用: 函数名可以相同,提高复用性
函数重载满足条件:
同一个作用域下
函数名称相同
函数参数类型不同 或者 个数相同 或者 顺序不同
注意: 函数的返回值不可以作为函数重载的条件
#include<iostream>
using namespace std;
void chongzan() {
cout << "chongzan" << endl;
}
//函数的返回值不可以作为函数重载的条件
//int chongzan() {
// cout << "chongzan" << endl;
//}
void chongzan(int a) {
cout << "chongzan(int a)" << endl;
}
void chongzan(double a) {
cout << "chongzan(double a)" << endl;
}
int main() {
chongzan();
chongzan(10);
chongzan(10.0);
system("pause");
return 0;
}
函数重载注意事项
引用作为重载条件
函数重载到函数默认参数
#include<iostream>
using namespace std;
//函数重载的注意事项
//1.引用作为重载的条件
void fun(int& a) {
cout << "func() 调用" << endl;
}
void fun(const int& a) {
cout << "func(const int& a) 调用" << endl;
}
//2.函数重载碰到默认参数
void fun2(int a) {
cout << "func2(int a) 的调用" << endl;
}
void fun2(int a,int b=10) {
cout << "func2(int a,int b) 的调用" << endl;
}
int main() {
int a = 10;
fun(a); // 调用fun(int &a);
fun(10); //调用fun(const int &a);
//fun2(10); 当函数重载碰到默认参数,已经二义性,报错,尽量避免这种情况
system("pause");
return 0;
}
九 .类和对象
c++面向对象的三大特性为: 封装、继承、多态
c++ 认为万事万物皆为对象,对象上有其属性和行为。
例如:
人可以作为对象,属性有姓名、年龄、身高、体重..., 行为有走、跑、跳、吃饭、唱歌。
车也可以作为对象,属性有轮胎、方向盘、车灯、行为有载人、放音乐、放空调...
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类。
##1. 封装
##1.1 封装的意义
封装是c++ 面向对象三大特性之一
封装的意义:
将属性和行为作为一个整体,表现生活中的事物
将属性和行为加以权限控制
1.封装意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法: class 类名{访问权限:属性/行为};
//示例1:设计一个圆类,求圆的周长
#include<iostream>
using namespace std;
//圆周率
const double PI = 3.14;
//设计一个圆类,求圆的周长
//圆求周长的公式: 2*PI*半径
class Circle {
//访问权限
//公共权限
public:
//属性(变量)
double m_r; //半径
//行为(函数)
double calculateZC() { //获取圆的周长
return 2 * PI * m_r;
}
double area() { //获取圆的面积
return PI * m_r * m_r;
}
};
int main() {
// 通过圆类 创建具体的值(对象)
Circle cl;
cl.m_r = 10;
cout << "圆的周长: = " << cl.calculateZC() << endl;
cout << "圆的面积: = " << cl.area() << endl;
system("pause");
return 0;
}
//示例2:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
//类中的属性和行为 我们统一称为 成员
属性 成员属性 成员变量
行为 成员函数 成员方法
#include<iostream>
#include<string>
using namespace std;
class Student {
public: //公共权限
//属性:
string m_name;
int m_id=0;
//行为
void setName(string name) {
m_name = name;
}
void setId(int id) {
m_id = id;
}
void showStudent() { //显示姓名和学号
cout << "姓名: " << m_name << endl;
cout << "学号: " << m_id << endl;
}
};
int main() {
Student stu;
//stu.m_name = "张三";
//stu.m_id = 1;
stu.setName("张三");
stu.setId(1);
stu.showStudent();
system("pause");
return 0;
}
2.封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
1.public 公共权限
2.protected 保护权限
3.private 私有权限
#include<iostream>
using namespace std;
//访问权限
//三种
//公共权限 public 成员 类内可以访问 类外可以访问
//保护权限 protected 成员 类内可以访问 类外不可以访问 儿子可以访问父亲中的保护内容
//私有权限 private 成员 类内可以访问 类外不可以访问 儿子不可以访问父亲的私有内容
class Person {
public:
//公共权限
string m_Name; // 姓名
protected:
//保护权限
string m_Car; //汽车
private:
// 私有权限
int m_Password=0; //银行卡密码
public:
void func() {
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 12234;
cout << "m_car " << m_Car << endl;
}
};
int main() {
//实例化具体对象
Person p1;
p1.m_Name = "李四";
//p1.m_car = "奔驰"; //保护权限内容,在类外访问不到;
//p1.Password = 12234; //私有权限内容,在类外访问不到
p1.func();
system("pause");
return 0;
}
##1.2 struct 和class 区别
在c++中struct 和class 唯一的区别就在于默认的访问权限。
区别:
struct默认权限为公共 public
class默认权限为私有 private
#include<iostream>
using namespace std;
class C1 {
int m_A; //默认权限 是 私有
};
struct C2 {
int m_A; // 默认权限 是 公共
};
int main() {
C1 c1;
//c1.m_A = 100; //在class里默认权限不可访问
C2 c2;
c2.m_A = 100; //在struct 默认的权限为公共,因此可以访问
system("pause");
return 0;
}
## 1.3成员属性设置为私有
优点1: 将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性。
#include<iostream>
#include<string>
using namespace std;
class Per {
public:
//设置姓名
void setName(string name) {
m_Name = name;
}
//获取姓名
string getName() {
return m_Name;
}
//获取年龄
int getAge() {
return m_Age;
}
void setAge(int age) {
if (age < 150 && age >= 0) {
m_Age = age;
}else {
m_Age = 0;
cout << "你这个老妖精: " << endl;
return;
}
}
// 设置情人
void setLover(string Lover) {
m_Lover = Lover;
//cout << "m_Lover: " << m_Lover << endl;
}
private:
//姓名 可读可写
string m_Name;
//年龄 只读
int m_Age=0;
//情人 只读
string m_Lover;
};
int main() {
Per p;
p.setName("张三");
string name = p.getName();
cout << "姓名: " << name << endl;
p.setAge(1000);
int age = p.getAge();
cout << "年龄: " << age << endl;
p.setLover("tangcyp");
system("pause");
return 0;
}
##1.4 案例: 设计立方体
设计立方体类(Cube)
求出立方体的面积和体积
分别用全局函数和成员函数判断两个立方体是否相等。
#include<iostream>
using namespace std;
class Cube {
public:
//设置长
void setL(double L) {
m_L = L;
}
//获取长
double getL() {
return m_L;
}
//设置宽
void setW(double W) {
m_W = W;
}
//获取宽
double getW() {
return m_W;
}
//设置高
void setH(double H) {
m_H = H;
}
//获取高
double getH() {
return m_H;
}
//求面积
double Area() {
return 2 * (m_L * m_W + m_W * m_H + m_L * m_H);
}
//求体积
double volume() {
return m_L * m_W * m_H;
}
//利用成员函数判断两个立方体是否相等
bool isSameByClass(Cube& c) {
if ((int)Area() == (int)c.Area() && (int)volume() == (int)c.volume()) {
return true;
}
else {
return false;
}
}
private:
//属性
double m_L=0.0;
double m_H=0.0;
double m_W=0.0;
};
//利用全局函数判断 两个立方体是否相等
//bool isSame(Cube & c1, Cube & c2) {
// if ((int)c1.Area() == (int)c2.Area() && (int)c1.volume() == (int)c2.volume() ) {
// return true;
// }
// return false;
//}
int main() {
Cube c1;
c1.setL(10.4); //长
c1.setW(1.4); //宽
c1.setH(10.2); //高
//cout << c1.Area() << endl; //面积
//cout << c1.volume() << endl; //体积
Cube c2;
c2.setL(10.4); //长
c2.setW(10.2); //宽
c2.setH(1.4); //高
//bool ret = isSame(c1, c2);
bool ret = c1.isSameByClass(c2);
if (ret) {
cout << "c1和c2是相等的 " << endl;
}
else {
cout << "c1和c2不是相等的 " << endl;
}
system("pause");
return 0;
}
##1.5 案例: 点和圆的关系
设计一个圆形类(Circle),和一个点(Point),计算点到圆的关系
//Cricle.h文件
#pragma once
#include<iostream>
#include "point.h"
using namespace std;
//圆类
class Circle {
public:
//设置半径
void setR(int r);
//获取半径
int getR();
//设置圆心
void setCenter(Point center);
//获取圆心
Point getCenter();
private:
int m_R = 0; //半径
//在类中可以让另一个类 作为本类中的成员
Point m_Center; //圆心
};
//Cricle.cpp文件
#include "Circle.h"
//设置半径
void Circle::setR(int r) {
m_R = r;
}
//获取半径
int Circle::getR() {
return m_R;
}
//设置圆心
void Circle::setCenter(Point center) {
m_Center = center;
}
//获取圆心
Point Circle::getCenter() {
return m_Center;
}
//ppoint.h文件
#pragma once
#include<iostream>
using namespace std;
//点类
class Point {
public:
//设置X
void setX(int x);
//获取X
int getX();
//设置Y
void setY(int y);
//获取Y
int getY();
private:
int m_X = 0; //x
int m_Y = 0; //y
};
//point.cpp文件
#include "point.h"
//设置X
void Point::setX(int x) {
m_X = x;
}
//获取X
int Point::getX() {
return m_X;
}
//设置Y
void Point::setY(int y) {
m_Y = y;
}
//获取Y
int Point::getY() {
return m_Y;
}
//main文件
#include<iostream>
#include"Circle.h"
using namespace std;
void isInCircle(Circle& c, Point& p) {
//计算两点之间距离 平方
int distance = pow(c.getCenter().getX() - p.getX(), 2) + pow(c.getCenter().getY() - p.getY(), 2);
//计算半径的平方
int rDistance = c.getR()*c.getR();
//判断关系
if (distance == rDistance) {
cout << "点在圆上" << endl;
}
else if (distance > rDistance) {
cout << "点在圆外" << endl;
}
else {
cout << "点在圆内" << endl;
}
}
int main() {
Point po;
po.setX(10);
po.setY(11);
Point center;
center.setX(10);
center.setY(0);
Circle c1;
c1.setR(10);
c1.setCenter(center);
isInCircle(c1, po);
system("pause");
return 0;
}
##2.对象的初始化和清理
生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候删除一些自己信息数据保证安全。
C++中的面向对象来源于生活,每个对象也都有初始设置以及对象销毁前的清理数据的设置。
## 2.1构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知
同时的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++ 利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。编译器提供的构造函数和析构函数是空实现
构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用于对象销毁前 系统自动调用,执行一些清理工作。
构造函数语法: 类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次。
析构函数语法:~ 类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前加上符号~
3.析构函数不可以有参数,因此不可以发生重载
4.程序在调用对象时候会自动调用析构,无须手动调用,而且只会调用一次。
#include<iostream>
using namespace std;
//对象的初始化和清理
class Person {
public:
//1.构造函数 进行初始化操作
Person() {
cout << "Person 构造函数的调用" << endl;
}
//2.析构函数 进行清理的操作
~Person() {
cout << "~Person 析构函数的调用" << endl;
}
};
void test01() {
Person p; //在栈上的数据,test01执行完毕后,释放这个对象
}
int main() {
test01();
//Person p;
system("pause");
return 0;
}
## 2.2构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造(默认构造)
按类型分为:普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
#include<iostream>
using namespace std;
//构造函数的分类即调用
//分类
class Per {
//构造函数
public:
//无参构造(默认构造)
Per() {
cout << "Person的无参构造函数调用" << endl;
}
//有参构造
Per(int a) {
age = a;
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Per(const Per &p) {
//将传入的人身上的属性,拷贝到我身上。
age = p.age;
cout << "Person的拷贝构造函数调用" << endl;
}
~Per() {
cout << "Person的析构函数调用" << endl;
}
int age=0;
};
void test02() {
//括号法:
//Per p; //默认构造函数调用
//Per p1(10); // 有参构造函数
//Per p2(p1);// 拷贝构造函数
//注意事项1
// 调用默认构造函数时候,不要加()
// 因为下面这行代码,编译器会认为是一个函数的声明,不会认为是在创建对象
//Per p();
//cout << "p1的年龄为: " << p1.age << endl;
//cout << "p2的年龄为: " << p2.age << endl;
//显示法
//Per p1;
//Per p2 = Per(10); //有参构造 Per(10);// 匿名对象 特点:当前执行结束后,系统会立即回收掉匿名对象。
//Per p3 = Per(p2); //拷贝构造
//注意事项2
//不要利用拷贝构造函数 初始化匿名对象
//Per(p3); //重定义报错 编译器会认为Per(p3) === Per p3; 对象声明
//隐式转换法
//Per p1;
//Per p2 = 10; //有参构造 相当于写了 Per p2 = Per(10);
//Per p3 = p2; // 拷贝构造 Per p3 = Per(p2);
}
int main() {
test02();
system("pause");
return 0;
}
## 2.3拷贝构造函数的调用时机
c++中构造函数的调用时机通常有三种情况
使用一个已经创建完毕的对象来初始化一个新对象。
值传递的方式给函数参数传值
以值方式返回局部对象
#include<iostream>
using namespace std;
class Person {
public:
//默认构造
Person() {
cout << "Person默认构造函数调用 " << endl;
}
Person(int age) {
cout << "Person有参构造函数调用 " << endl;
m_Age = age;
}
//拷贝函数
Person(const Person& p) {
cout << "Person拷贝构造函数调用 " << endl;
m_Age = p.m_Age;
}
//析构函数
~Person() {
cout << "Person析构构造函数调用 " << endl;
}
int m_Age=0;
};
//拷贝构造函数调用时机
//1.使用一个已经创建完毕的对象来初始化一个新对象
void test03() {
Person p1(20);
Person p2(p1);
cout << "p1的年龄为: " << p1.m_Age << endl;
cout << "p2的年龄为: " << p2.m_Age << endl;
}
//2.值传递的方式给函数参数传值
void doWork(Person p) {
}
void test04() {
Person p;
doWork(p); //会调用拷贝构造函数
}
//3.值方式返回局部对象
Person doWork2() {
Person p1;
cout << (int*)&p1 << endl; //000000E5F7AFF844
return p1; //会调用拷贝构造函数
}
void test05() {
Person p = doWork2(); ///Person p = p1;
cout << (int*)&p << endl; //000000E5F7AFF984
}
int main() {
//test03();
//test04();
test05();
system("pause");
return 0;
}
## 2.4 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参数构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数。
拷贝构造函数 > 有参构造函数 > 无参构造函数
#include<iostream>
using namespace std;
class Person {
public:
//Person() {
// cout << "Person的默认构造函数构造 " << endl;
//}
//Person(int age) {
// cout << "Person的有参构造函数构造 " << endl;
// m_Age = age;
//}
Person(const Person& p) {
cout << "Person的拷贝构造函数构造 " << endl;
m_Age = p.m_Age;
}
~Person() {
cout << "Person的默认析构函数构造 " << endl;
}
int m_Age=0;
};
//void test06() {
// Person p;
// p.m_Age = 18;
// Person p2(p);
// cout << "p2的年龄为: " << p2.m_Age << endl;
//}
void test07() {
//Person p; //如果用户定义有参数构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
//Person p(10);
//Person p1(p);//由于用户没有定义拷贝构造函数,会调用编译器添加的默认拷贝构造函数
//cout << "p1的年龄为: " << p1.m_Age << endl; //10
}
int main() {
//test06();
test07();
system("pause");
return 0;
}
## 2.5 深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include<iostream>
using namespace std;
//深拷贝与浅拷贝
class Person1 {
public:
Person1() {
cout << "Person的默认构造函数的调用" << endl;
}
Person1(int age,int height) {
m_Height = new int(height); //new 返回是 该数据类型的指针
m_age1 = age;
cout << "Person的有参构造函数的调用" << endl;
}
//自己实现一个拷贝构造函数,解决浅拷贝带来的问题
Person1(const Person1& p) {
cout << "Perosn拷贝构造函数调用 " << endl;
m_age1 = p.m_age1;
//m_Height = p.m_Height; //编译器默认实现的就是这行代码
//深拷贝操作
m_Height = new int(*p.m_Height);
}
~Person1() {
//析构代码,将堆区开辟数据做释放操作
if (m_Height != NULL) {
delete m_Height;
m_Height = NULL;
}
cout << "Person的析构函数的调用" << endl;
}
int m_age1 = 0;
int *m_Height = 0;
};
void test10() {
//浅拷贝带来的问题是堆区的内存重复释放(利用深拷贝进行解决)
//堆栈数据的进出原则是先进后出;其中栈是一种数据结构,
//它按照先进后出的原则存储数据,先进入的数据被压入栈底,
//最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。
Person1 p5(10,160);
cout << "p5的年龄为: " << p5.m_age1 << endl;
cout << "p5的身高为: " << *p5.m_Height << endl;
Person1 p6(p5);
cout << "p6的年龄为: " << p6.m_age1 << endl;
cout << "p6的身高为: " << *p6.m_Height << endl;
}
int main() {
test10();
system("pause");
return 0;
}
总结: 如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
## 2.6 初始化列表
作用:c++提供了初始化列表语法,用来初始化属性
语法: 构造函数():属性1(值1),属性2(值2)...{}
在C++中,对象的成员变量可以使用初始化列表进行初始化,也可以在构造函数中使用传统方式进行初始化。
使用初始化列表可以在构造函数被调用之前对成员变量进行初始化。这种方式可以更方便的初始化const变量,以及具有复杂类型的成员变量(比如指针、数组等)。同时,初始化列表还可以同时初始化多个成员变量,在代码的可读性和效率上都更好。
使用传统的初始化方式可以在构造函数中进行更多的逻辑处理。可以根据需要在其内部进行一些初始化的操作,比如读取文件、计算等等。
两种初始化方式各有优劣,可以根据情况选择使用。但现代C++编程更倾向于使用初始化列表来初始化成员变量,因为它可以提高代码的可读性和效率,同时可以避免一些错误。
#include<iostream>
using namespace std;
class Person2 {
public :
// 传统初始化操作
//Person(int a,int b,int c) {
// m_A = a;
// m_B = b;
// m_C = c;
// cout << "Person的有参构造函数的调用 " << endl;
//}
// 初始化列表进行初始化属性
Person2(int a,int b,int c) : m_A(a), m_B(b), m_C(c) {
}
int m_A;
int m_B;
int m_C;
};
void test11() {
//Person p1(1, 2, 3);
Person2 p3(10,20,30);
cout << "m_A = " << p3.m_A << endl;
cout << "m_B = " << p3.m_B << endl;
cout << "m_C = " << p3.m_C << endl;
}
int main() {
test11();
system("pause");
return 0;
}
## 2.7 类对象作为类成员
c++ 类中的成员可以是另一个类的对象,我们称该成员为对象成员。
class A{}
class B{
A a;
}
B类中有对象作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
#include<iostream>
#include<string>
using namespace std;
//类对象作为类成员
class Phone {
public:
//利用初始化列表进行初始化属性
Phone(string pName) :m_PName(pName) {
cout << "Phone的构造函数的调用" << endl;
}
~Phone() {
cout << "Phone析构函数的调用" << endl;
}
string m_PName;
};
class Person4 {
public:
// Phone m_phone = PName //隐式转换
Person4(string name, string PName) : m_Name(name), m_Phone(PName) {
cout << "Person4的构造函数的调用" << endl;
}
~Person4() {
cout << "Perosn4析构函数的调用" << endl;
}
//姓名
string m_Name;
//手机
Phone m_Phone;
};
//当其他类对象作为本类成员,构造时候 ,先构造其他类对象,再构造自身
//当其他类对象作为本类成员,析构时候 ,先析构自身,再析构其他类对象
void test12() {
Person4 p("张三", "三星");
cout << "姓名 " << p.m_Name << endl; //姓名
cout << "手机品牌 " << p.m_Phone.m_PName << endl; //手机品牌
}
int main() {
test12();
system("pause");
return 0;
}
//运行结果:
//Phone的构造函数的调用
//Person4的构造函数的调用
//姓名 张三
//手机品牌 三星
//Perosn4析构函数的调用
//Phone析构函数的调用
## 2.8 静态成员
静态成员就是在成员变量和成员函数加上关键词static,称为静态成员
静态成员:
静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明、类外初始化
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
静态成员变量
#include<iostream>
using namespace std;
//静态成员变量
class Person5 {
public:
//▪ 所有对象共享同一份数据
//▪ 在编译阶段分配内存
// 类内声明、类外初始化
//类内声明
static int m_A;
// 静态成员变量也是有访问权限的
private:
static int m_B;
};
//类外初始化
int Person5::m_A = 100;
int Person5::m_B = 200;
void test14() {
Person5 p;
cout << p.m_A << endl; //100
Person5 p2;
p2.m_A = 200;
cout << p2.m_A << endl; //200
cout << p.m_A << endl; //200
}
void test15() {
//静态成员变量 不属于某个对象上, 所有对象都共享同一份数据
//因此静态成员变量有两种访问方式
//1.通过对象进行访问
Person5 p;
cout << p.m_A << endl;
//2.通过类名进行访问
cout << Person5::m_A << endl;
//cout << Person5::m_B << endl; //类外访问不到私有的静态成员变量
}
int main() {
//test14();
cout << "--------------" << endl;
test15();
system("pause");
return 0;
}
静态成员函数:
#include<iostream>
using namespace std;
//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量
class Person {
public:
//静态成员函数
static void func() {
//m_B = 0;// 静态成员函数不可以访问 非静态成员变量,无法区别到底是那个对象的m_B属性
m_A = 100; //静态成员函数可以访问 静态成员变量
cout << "static void func调用" << endl;
}
//静态成员变量
static int m_A;
int m_B=0;
//静态成员函数也是有访问权限的
private:
static void func2() {
cout << "static void func2调用" << endl;
}
};
//类外初始化
int Person::m_A = 0;
void test16() {
//1.通过对象访问
Person p;
p.func();
//2.通过类名访问
Person::func();
//Person::func2(); 类外访问不到私有静态成员函数
}
int main() {
test16();
system("pause");
return 0;
}
##3.c++对象模型和this指针
## 3.1 成员变量和成员函数分开存储
在c++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
#include<iostream>
using namespace std;
//成员变量和成员函数分开存储
class Person {
int m_A; //非静态成员变量 属于类的对象上
static int m_B; //静态成员变量 不属于类对象上
void func(){} //非静态成员函数 不属于类对象上
static void func2() {} //静态成员函数 不属于类对象上
};
int Person::m_B = 0;
//void test01() {
// Person p;
//空对象占用的内存空间为1
//c++编译器会把每个空对象也分配一个字节空间,是为了区分空对象占内存的位置。
//每个空对象也应该有一个独一无二的内存地址。
// cout << "sizeof p = " << sizeof(p) << endl;
//}
void test02() {
Person p;
cout << "sizeof p = " << sizeof(p) << endl; //4
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
## 3.2this指针概念
通过3.1我们知道在c++中成员变量和成员函数是分开存储的。
每一个非静态成员函数只会诞生一份函数实例。也就是说多个同类型的对象会公用一块代码。
那些问题是:这一块代码是如何区分那个对象调用自己的呢?
c++ 通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。
this指针是隐含每一个非静态成员函数内的一种指针。
this指针不需要定义。直接使用即可。
this指针的用途:
当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象,可使用return *this.
#include<iostream>
using namespace std;
class Person {
public:
//1.解决名称冲突
Person(int age) {
//this指针指向 被调用的成员函数 所属的对象。
this-> age = age;
}
Person& PersonAddAge(Person& p) {
this->age += p.age;
//this指向p2的指针,而*this指向的就是p2这个对象本体
return *this;
}
int age;
};
//2.返回对象本身用*this
void test04() {
Person p1(10);
Person p2(10);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年龄为: " << p2.age << endl; //40
}
void test03() {
Person p1(18);
cout << "p1的年龄为: " << p1.age << endl; //18
}
int main() {
test03();
test04();
system("pause");
return 0;
}
9.9.3 空指针访问成员函数
C++ 中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针。
如果用到this指针,需要加以判断保证代码的健壮性。
#include<iostream>
using namespace std;
// 空指针调用成员函数
class Person {
public:
void showClassName() {
cout << "this is Person class" << endl;
}
void showClassAge() {
//报错原因是因为传入的指针为NULL
if (this == NULL) {
return; // 提高代码的健壮性
}
cout << "Age = "<< m_Age << endl;
}
int m_Age;
};
void test05() {
Person* p = NULL;
p->showClassAge();
p->showClassName();
}
int main() {
test05();
system("pause");
return 0;
}
9.3.4 const修饰成员函数
常函数:
成员函数后加const 后我们称为这个函数为常函数。
常函数内不可以修改成员属性。
成员属性声明时加关键字mutable后,在常函数中依然可以修改。
常对象:
声明对象前加const称该对象为常对象
常对象只能调用常函数
#include<iostream>
using namespace std;
// 常函数
class Person {
public:
//this指针的本质 是指针常量 指针常量的指向是不可以修改的
//在成员函数后面加const,修饰的是this指向, 让指针指向的值也不可以修改。
void showPerson() const{
//this->m_A = 100;
this->m_B = 100;
}
int m_A;
mutable int m_B; // 特殊变量,即使在常函数中,也可以修改这个值, 加关键词mutable
};
void test06() {
Person p;
}
void test07() {
// 常对象
const Person p1; //在对象前加const ,变为常对象
p1.m_B = 100;
p1.showPerson(); // 常对象只能调用常函数
}
int main() {
test07();
test06();
system("pause");
return 0;
}
9.4友元
在程序中,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类访问另一个类中私有成员。
友元的关键词为friend
友元的三种实现
全局函数做友元
类做友元
成员函数做友元
// 全局函数做友元
#include<iostream>
#include<string>
using namespace std;
class Building { // 房屋类
friend void goodGay(Building* building);
public:
Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom;// 客厅
private:
string m_BedRoom; //卧室
};
// 全局函数
void goodGay(Building* building) {
cout << "好基友全局函数正在访问: " << building->m_SittingRoom << endl;
cout << "好基友全局函数正在访问: " << building->m_BedRoom << endl;
}
void test01() {
Building building;
goodGay(&building);
}
int main() {
test01();
system("pause");
return 0;
}
//类做友元
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//类做友元
class Building;
class GoodGay {
public:
GoodGay();
void visit(); //参观函数 访问Building中的属性
Building* building;
};
class Building {
friend class GoodGay; // GoodGay类是本来的好朋友,可以访问本类中私有的成员
public:
Building();
string m_SittingRoom;// 客厅
private:
string m_BedRoom; //卧室
};
//类外写成员函数
Building::Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay() {
//创建建筑物对象
building = new Building;
}
void GoodGay::visit() {
cout << "好基友类正在访问: " << building->m_SittingRoom << endl;
cout << "好基友类正在访问: " << building->m_BedRoom << endl;
}
void test09() {
GoodGay gg;
gg.visit();
}
int main() {
test09();
system("pause");
return 0;
}
//成员函数做友元
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
class Building;
class GoodGay {
public:
GoodGay();
void visit(); // 让visit函数可以访问Building中私有成员
void visit2();// 让visit函数不可以访问Building中私有成员
Building* building;
};
class Building {
friend void GoodGay::visit();
public:
Building() { // 构造函数
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom; // 客厅
private:
string m_BedRoom; // 卧室
};
//类外实现成员函数
GoodGay::GoodGay() {
building = new Building;
}
void GoodGay::visit() {
cout << "visit函数正在访问: " << building->m_SittingRoom << endl;
cout << "visit函数正在访问: " << building->m_BedRoom << endl;
}
void GoodGay::visit2() {
cout << "visit函数正在访问: " << building->m_SittingRoom << endl;
//cout << "visit函数正在访问: " << building->m_BedRoom << endl;
}
void test10() {
GoodGay gg;
gg.visit();
gg.visit2();
}
int main() {
test10();
system("pause");
return 0;
}
9.5 运算符重载
运算符重载的概念: 对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
9.5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
// 1.成员函数重载+号
//Person operator+(Person& p) {
// Person temp;
// temp.m_A = this->m_A + p.m_A;
// temp.m_B = this->m_B + p.m_B;
// return temp;
//}
int m_A;
int m_B;
};
//2.全局函数重载+号
Person operator+(Person &p1, Person &p2) {
Person temp;
temp.m_B = p1.m_B + p2.m_B;
temp.m_A = p1.m_A + p2.m_A;
return temp;
}
Person operator+(Person& p1, int num) {
Person temp;
temp.m_B = p1.m_B + num;
temp.m_A = p1.m_A;
return temp;
}
void test01() {
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
//成员函数重载本质调用
//Person p3 = p1.operator+(p2);
//全局函数重载本质调用
//Person p3 = p1.operator+(p2,p1);
//运算符重载也可以发生函数重载
Person p4 = p1 + 10; // Person operator+(Person& p1, int num)
cout << "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() {
test01();
system("pause");
return 0;
}
9.5.2 左移运算符重载
作用:可以输出自定义数据类型
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
// 左移运算符重载
class Person {
friend ostream& operator<<(ostream& cout, Person& p);
public:
Person(int a, int b) {
this->m_A = a;
this->m_B = b;
}
private:
// 1.利用成员函数重载左移运算符 p.operator 简化版本p<<cout
// 不会利用成员函数重载<< 运算符, 因为无法实现cout 在左侧
//void operator<<(cout) {
//
//}
int m_A;
int m_B;
};
//只能利用全局函数重载左移运算符
ostream & operator<<(ostream &cout, Person& p) {
cout << "m_A = " << p.m_A << " m_B = " << p.m_B ;
return cout;
}
void test02() {
Person p(10,10);
//cout << p.m_A << endl;
cout << p << endl;
}
int main() {
test02();
system("pause");
return 0;
}
9.5.3 递增运算符重载
通过重载运算符,实现自己的整型数据
总结: 前置递增返回引用,后置递增返回值
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
// 重载递增运算符
class MyInteger {
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}
// 重载前置++运算符
MyInteger& operator++() {
this->m_Num++;
return *this;
}
// 重载后置++运算符
//void operator++(int) int代表展位参数,可以用于区分前置和后置递增
MyInteger operator++(int) {
MyInteger temp = *this;
this->m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, MyInteger myint) {
cout << myint.m_Num;
return cout;
}
void test03() {
MyInteger myint;
cout << ++(++myint) << endl;
cout <<myint << endl;
}
void test04() {
MyInteger myint;
cout << (myint++)++ << endl; //0
cout << myint << endl;//1
}
int main() {
test03();
test04();
system("pause");
return 0;
}
9.5.4 赋值运算符重载
c++编译器至少给一个类添加4个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
赋值运算符operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作也会出现深浅拷贝问题。
#define _CRT_SECURE_NO_WARNINGS
#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
//应该先判断是否有属性在堆区,如果有先释放干净,然后在深拷贝
if (m_Age != NULL) {
delete m_Age;
m_Age = NULL;
}
// 深拷贝
m_Age = new int(*p.m_Age);
return *this;
}
int* m_Age;
};
void test05() {
Person p1(18);
cout << "p1的年龄为: " << *p1.m_Age << endl;
Person p2(20);
Person p3(30);
p3 = p2 = p1; //赋值操作
cout << "p2的年龄为: " << *p2.m_Age << endl;
cout << "p3的年龄为: " << *p3.m_Age << endl;
}
int main() {
test05();
int a = 10;
int b = 20;
int c = 30;
c = b = a;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "c=" << c << endl;
system("pause");
return 0;
}
9.5.5 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型的对象进行对比操作
#define _CRT_SECURE_NO_WARNINGS
#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_Age == p.m_Age && this->m_Name == p.m_Name) {
return true;
}
return false;
}
// 重载关系!=运算符
bool operator!=(Person & p) {
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name) {
return false;
}
return true;
}
string m_Name;
int m_Age;
};
void test07() {
Person p1("TOM", 12);
Person p2("TMD", 12);
if (p1 == p2) {
cout << "p1和p2是相等的" << endl;
}else {
cout << "p1和p2不是相等的" << endl;
}
if (p1 != p2) {
cout << "p1和p2bu是相等的" << endl;
}
else {
cout << "p1和p2是相等的" << endl;
}
}
int main() {
test07();
system("pause");
return 0;
}
9.5.6 函数调用运算符重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
//函数调用运算符重载
//打印输出类
class MyPrint {
public:
//重载函数调用运算符
void operator()(string test) {
cout << test << endl;
}
};
void test08() {
MyPrint myPrint;
myPrint("hello world");
// 匿名函数调用
MyPrint()("hello world");
}
int main() {
test08();
system("pause");
return 0;
}
9.6 继承
继承是面向对象三大特性之一
我们发现,定义这些类时,下级别的成员除了拥有上一级的特性,还有自己的特性
这个时候我们就可以考虑利用继承的技术,减少重复代码。
9.6.1 继承的基本用法
//继承的好处:减少重复代码
//语法: class 子类: 继承方式 父类
//子类 也称派生类
//父类 也成为基类
// 派生类中的成员,包含两大部分:一类是从基类继承过来,一类是自己增加的成员,从基类继承过来的
//表现其共性, 而新增的成员体现了其个性。
#define _CRT_SECURE_NO_WARNINGS
#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;
}
};
class Python : public BasePage {
public:
void content() {
cout << "Python学科视频" << endl;
}
};
class C__ : public BasePage {
public:
void content() {
cout << "C__ 学科视频" << endl;
}
};
void test01() {
cout << "Java页面如下" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "_____________" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "_____________" << endl;
C__ c;
c.header();
c.footer();
c.left();
c.content();
}
int main() {
test01();
system("pause");
return 0;
}
9.6.2 继承的方式
继承方式一共有三种:
公共继承
保护继承
私有继承
![](https://i-blog.csdnimg.cn/blog_migrate/67d96d45661922fb1dbc580e4f929166.jpeg)
#define _CRT_SECURE_NO_WARNINGS
#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;
m_B = 10;
//m_C = 10;//访问不到
}
};
//保护继承
class Son2 :protected Base1 {
public:
void func() {
m_A = 10;
m_B = 10;
//m_C = 10;//访问不到
}
};
//私有继承
class Son3 :private Base1 {
public:
void func() {
m_A = 10;
m_B = 10;
//m_C = 10;//访问不到
}
};
void test02() {
Son1 s1;
//cout << "m_A = "<< s1.m_A << endl;
s1.m_A = 100;
cout << "m_A = " << s1.m_A << endl;
//s1.m_B = 100; // 到Son1中 m_B是保护权限 类外访问不到
//Son2 s2;
//s2.m_A = 100;/ 到Son2中 m_A m_B是保护权限 类外访问不到
//Son3 s3;
//到Son2中 m_A m_B是私有权限 类外访问不到
}
int main() {
test02();
system("pause");
return 0;
}
9.6.3 继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去, 只是由编译器给隐藏后访问不到
//利用开发人员命令提示工具查看对象模型
//跳转盘符 F:
//跳转文件路径 cd 具体路径下
//查看命名 cl /d1 reportSingleClassLayout类名 文件名
9.6.4 继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题: 父类和子类的构造和析构顺序谁先谁后
继承中构造和析构顺序如下:
先构造父类,在构造子类,析构的顺序与构造的顺序相反。
#define _CRT_SECURE_NO_WARNINGS
#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 test04() {
Son s;
}
int main() {
test04();
system("pause");
return 0;
}
//Base构造函数!
//Son构造函数!
//~Son的析构函数!
//~Base的析构函数!
9.6.5 继承同名成员处理方式
问题: 当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
访问子类同名成员,直接访问即可
访问父类同名成员,需要加作用域
当子类与父类拥有同名的成员函数, 子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
// 继承中同名成员处理
class Base {
public:
Base() {
m_A = 100;
}
void func() {
cout << "Base func()调用" << endl;
}
void func(int) {
cout << "Base func(int)调用" << endl;
}
int m_A;
};
class Son :public Base {
public:
Son() {
m_A = 10;
cout << "m_A = " << this->m_A << endl;
}
void func() {
cout << "son func()调用" << endl;
}
int m_A;
};
// 同名成员的属性的处理方式
void test05() {
Son s;
cout << "m_A = " << s.m_A << endl;
//如果 通过子类对象, 访问到父类中同名成员,需要加作用域
cout << "Base m_A = " << s.Base::m_A << endl;
}
// 同名函数处理方式
void test06() {
Son s;
s.func();
s.Base::func();
// 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中的所有同名成员函数(重载函数)
// 如果想访问到父类中被隐藏的同名函数,需要加作用域
s.Base::func(100);
}
int main() {
test05();
test06();
system("pause");
return 0;
}
9.6.6 继承同名静态成员处理方式
问题: 继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致。
访问子类同名成员,直接访问即可
访问父类同名成员,需要加作用域
有两种访问的方式(通过对象和通过类名)
9.6.7 多继承语法
C++允许一个类继承多个类
语法: class 子类:继承方式 父类1, 继承方式2 父类2....
多继承可能会引起父类中有同名成员出现, 需要加作用域区分
C++实际开发中不建议用多继承
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
//多继承的语法
class Base1 {
public:
Base1() {
m_A = 100;
}
int m_A;
};
//多继承的语法
class Base2 {
public:
Base2() {
m_A = 200;
}
int m_A;
};
class Son :public Base1, public Base2 {
public:
Son() {
this->m_C = 300;
this->m_D = 400;
}
int m_C;
int m_D;
};
void test07() {
Son s;
cout << "sizeof = " << sizeof(s) << endl;
// 当父类中出现同名成员,需要加作用域区分
cout << "Base1 m_A = " << s.Base1::m_A << endl;
cout << "Base2 m_A = " << s.Base2::m_A << endl;
}
int main() {
test07();
system("pause");
return 0;
}
9.6.8 菱形继承
菱形继承的概念:
两个派生类继承用一个基类
又有某个类同时继承着两个派生类
这种继承被称为菱形继承、或者钻石继承
总结:
菱形继承带来的问题是子类继承两份相同的数据, 导致资源浪费以及毫无意义
利用虚继承可以解决菱形继承问题 //继承之前 加上关键词 virtual 变为虚继承
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
// 动物类
class Animal {
public:
int m_Age;
};
// 利用虚继承 解决菱形继承的问题
// 继承之前 加上关键词 virtual 变为虚继承
// Animal类称为虚基类
//
//羊类
class Sheep :virtual public Animal {};
//驼类
class Alpaca :virtual public Animal {};
//羊驼类
class SheepAlpaca :public Sheep, public Alpaca {};
void test09() {
SheepAlpaca st;
st.Sheep::m_Age = 100;
st.Alpaca::m_Age = 10;
// 当菱形继承,两个父类拥有相同数据,需要加以作用域区分
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Alpaca::m_Age = " << st.Alpaca::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}
int main() {
test09();
system("pause");
return 0;
}
9.7 多态
9.7.1 多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类
静态多态: 函数重载和运算符重载属于静态多态,复用函数名
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
静态多态的函数地址早绑定-编译阶段确定函数地址
动态多态的函数地址晚绑定-运行阶段确定函数地址
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
//多态
//动物类
class Animal {
public:
virtual void speak() {
cout << "动物在说话" << endl;
}
};
//猫类
class Cat :public Animal {
public:
virtual void speak() {
cout << "小猫 喵喵苗" << endl;
}
};
//狗类
class Dog :public Animal {
public:
virtual void speak() {
cout << "小狗 汪汪汪" << endl;
}
};
// 地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
//
//动态多态满足的条件
//1.有继承关系
//2.子类重写父类的虚函数
//重写 函数返回值类型 函数名 参数列表 完全相同
// 动态多态的使用
// 父类的指针或者引用 指向子类对象
void doSpeak(Animal &animal) {
animal.speak();
}
void tests01() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
tests01();
system("pause");
return 0;
}
9.7.2 多态的原理剖析
空类的大小为1
![](https://i-blog.csdnimg.cn/blog_migrate/4da55efb9f4ac53a917ee211771a78e6.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/337fce18c867835f9c595dad0ec14982.jpeg)
9.7.3 多态案例 -计算机类
多态的优点:
代码组织结构清晰
可读性强
利于前期和后期的扩展以及维护
#define _CRT_SECURE_NO_WARNINGS
#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;
}
//如果想扩展新的功能,需要修改源码
//在真实开发中 提倡 开闭原则
//开闭原则: 对扩展进行开放,对修改进行关闭
return 0;
}
int m_Num1;// 操作数1
int m_Num2;//操作数2
};
void test01() {
//创建计算机对象
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
//利用多态
//实现计算机抽象类
class AbstractCalculator {
public:
AbstractCalculator() {
m_Num1 = 0;
m_Num2 = 0;
}
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 test03() {
//多态使用条件
//父类指针或者引用指向子类对象
//加法运算
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
//用完记得销毁
delete abc;
//减法运算
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
//用完记得销毁
delete abc;
//乘法运算
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
//用完记得销毁
delete abc;
}
int main() {
//test01();
test03();
system("pause");
return 0;
}
9.7.4 纯虚函数和抽象类
在多态中, 通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法: virtual 返回值类型 函数名 (参数列表) = 0
当类中有了纯虚函数,这个类也称为抽象类
抽象类的特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
// 纯虚函数和抽象类
class Base {
public:
//纯虚函数
virtual void func() = 0;
};
void test01() {
//Base s; // 抽象类无法实例化对象
}
int main() {
test01();
system("pause");
return 0;
}
9.7.5 多态案例2 制作饮品
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
//多态的案例2制作饮品
class AbstractDrinking {
public:
// 1.煮水
virtual void Boil() = 0;
//2. 冲泡
virtual void Brew() = 0;
//3. 倒入杯中
virtual void PourInCup() = 0;
//4.加入辅料
virtual void PutSomething() = 0;
//制作饮品
void makeDrink() {
this->Boil();
this->Brew();
this->PourInCup();
this->PutSomething();
}
};
//制作咖啡
class Coffee :public AbstractDrinking {
public:
// 1.煮水
virtual void Boil() {
cout << "煮农夫山泉" << endl;
}
//2. 冲泡
virtual void Brew() {
cout << "冲泡咖啡" << endl;
}
//3. 倒入杯中
virtual void PourInCup() {
cout << "倒入杯中" << endl;
}
//4.加入辅料
virtual void PutSomething() {
cout << "加入糖和牛奶" << endl;
}
};
//制作茶
class Tea :public AbstractDrinking {
public:
// 1.煮水
virtual void Boil() {
cout << "煮矿泉水" << endl;
}
//2. 冲泡
virtual void Brew() {
cout << "冲泡茶叶" << endl;
}
//3. 倒入杯中
virtual void PourInCup() {
cout << "倒入杯中" << endl;
}
//4.加入辅料
virtual void PutSomething() {
cout << "加入柠檬" << endl;
}
};
//制作函数
void doWork(AbstractDrinking* abs) {
abs->makeDrink();
delete abs;
}
void test06() {
// 制作咖啡
doWork(new Coffee);
cout << "___________" << endl;
//制作茶叶
doWork(new Tea);
}
int main() {
test06();
system("pause");
return 0;
}
9.7.6 虚析构和纯虚析构
多态使用时, 如果子类中有属性开辟到堆区, 那么父类指针在释放时无法调用到子类的析构代码
解决方式: 将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯析构语法:
virtual ~类名()=0;
类名::类名(){}
总结:
虚析构或纯虚析构就是用来解决通过父类指针释放子类对象出现的问题
如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
拥有纯虚析构的类也属于抽象类
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
// 虚析构和纯虚析构
class Animals {
public:
//纯虚函数
Animals() {
cout << "Animal构造函数调用" << endl;
}
// 利用虚析构可以解决 父类指针释放子类对象时不干净的问题
//virtual ~Animals() {
// cout << "Animal析构函数调用" << endl;
//}
//纯虚析构 类内声明 类外实现
virtual ~Animals() = 0;
virtual void speak() = 0;
//virtual ~Animal() = 0;
};
//
Animals::~Animals(){ cout << "Animal纯虚析构函数调用" << endl; }
class Cats :public Animals {
public:
Cats(string name) {
cout << "Cat构造函数的调用" << endl;
this->m_Name = new string(name);
}
~Cats() {
if (this->m_Name != NULL) {
cout << "Cat析构函数" << endl;
delete this->m_Name;
}
}
virtual void speak() {
cout << *m_Name<< " 小猫在说话" << endl;
}
string* m_Name;
};
void test09() {
Animals* animal = new Cats("汤姆");
animal->speak();
//父类指针在析构时候 不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄露
delete animal;
}
int main() {
test09();
system("pause");
return 0;
}
9.7.7 多态案例3 电脑组装
案例描述:
电脑主要组成部件为CPU(用于计算), 显卡(用于显示),内存条(用于存储)
将每一个零件封装出抽象类,并且提供不同的厂商生产不同零件,例如lntel厂商和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作。
#define _CRT_SECURE_NO_WARNINGS
#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 Computers {
public:
Computers(CPU* cpu, VideoCard* lenovoMemory, Memory* memory) {
this->m_cpu = cpu;
this->m_memory = memory;
this->m_videocard = lenovoMemory;
}
~Computers() {
if (m_cpu != NULL) {
delete m_cpu;
m_cpu = NULL;
}
if (m_memory != NULL) {
delete m_memory;
m_memory = NULL;
}
if (m_videocard != NULL) {
delete m_videocard;
m_videocard = NULL;
}
}
//提供工作的函数
void work() {
m_cpu->calculate();
m_memory->storage();
m_videocard->display();
}
private:
CPU* m_cpu; // CPU的零件指针
VideoCard* m_videocard; //显卡的零件指针
Memory* m_memory; // 存储的零件指针
};
// 具体厂商
// Intel厂商
class IntelCPU :public CPU {
virtual void calculate() {
cout << "Intel的CPU开始计算了" << endl;
}
};
class IntelVideoCard :public VideoCard {
virtual void display() {
cout << "Intel的显卡开始工作了" << endl;
}
};
class IntelMemory :public Memory {
virtual void storage() {
cout << "Intel的内存条开始工作了" << endl;
}
};
// Lenovo 厂商
class LenovoCPU :public CPU {
virtual void calculate() {
cout << "Lenovo的CPU开始计算了" << endl;
}
};
class LenovoVideoCard :public VideoCard {
virtual void display() {
cout << "Lenovo的显卡开始工作了" << endl;
}
};
class LenovoMemory :public Memory {
virtual void storage() {
cout << "Lenovo的内存条开始工作了" << endl;
}
};
void test10() {
//第一台电脑零件
CPU* intelCpu = new IntelCPU;
VideoCard* intelvideocard = new IntelVideoCard;
Memory* intelmemory = new IntelMemory;
// 创建第一台电脑
Computers* computers1 = new Computers(intelCpu, intelvideocard, intelmemory);
computers1->work();
delete computers1;
cout << "------------------" << endl;
//第二台电脑零件
CPU* lenovoCPU = new LenovoCPU;
VideoCard* lenovoVideoCard = new LenovoVideoCard;
Memory* lenovoMemory = new LenovoMemory;
// 创建第二台电脑
Computers* computers2 = new Computers(lenovoCPU, lenovoVideoCard, lenovoMemory);
computers2->work();
delete computers2;
cout << "------------------" << endl;
// 创建第三台电脑
Computers* computers3 = new Computers(new LenovoCPU, new IntelVideoCard, new LenovoMemory);
computers3->work();
delete computers3;
}
int main() {
test10();
system("pause");
return 0;
}
十 .文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会释放。
通过文件可以将数据持久化
C++中对文件操作需要包含头文件<fstream>
文件类型分为两种:
文本文件: 文件以文本的ASCII码形式存储在计算机中。
二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们
操作文件的三大类:
ofstream:写操作
ifstream: 读操作
fstream: 读写操作
10.1 写文件
写文件的步骤:
包含头文件 #include<fstream>
创建流对象 ofstream ofs;
打开文件 ofs.open("文件路径", 打开方式)
写数据ofs<<"写入的数据";
关闭文件 ofs.close();
文件打开方式:
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,在创建
ios::binary 二进制方式
注意: 文件打开可以配合使用, 利用|操作符
总结:
文件操作必须包含头文件fstream
读文件可以利用ofstream,或者fstream类
打开文件时需要指定操作文件的路径,以及打开方式
利用<< 可以向文件中写数据
操作完毕,要关闭文件
//写文件
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
#include<fstream>
//写文件
void test01(){
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "年龄:12" << endl;
ofs << "性别: 男" << endl;
//文件关闭
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
10.2 读文件
读文件的步骤:
包含头文件 #include<fstream>
创建流对象 ifstream ifs;
打开文件 ifs.open("文件路径", 打开方式)
读数据: 四种方式读取
关闭文件 ifs.close();
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<fstream>
using namespace std;
//读文件
void test02() {
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, 1024)) {
// cout << buf << endl;
//}
//第三种
// string buf;
//while (getline(ifs, buf)) {
// cout << buf << endl;
//}
//第四种
//char c;
//while ((c = ifs.get()) != EOF) {
// cout << c;
//}
//关闭文件
ifs.close();
}
int main() {
test02();
system("pause");
return 0;
}
10.3 二进制文件
打开方式要指定为: ios::binary
10.3.1 写文件
二进制方式写文件主要利用对象调用成员函数write
函数原型: ostream& write(const char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
#include<fstream>
class Person {
public:
char m_Name[64];// 姓名
int m_Age; //年龄
};
void test03() {
ofstream ofs("person.txt", ios::out | ios::binary);
Person p = { "张三",18 };
ofs.write((const char*) & p, sizeof(Person));
ofs.close();
}
int main() {
test03();
system("pause");
return 0;
}
10.3.2 读文件
二进制方式读文件主要利用对象调用成员函数read
函数原型: ostream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
#include<fstream>
class Person {
public:
char m_Name[64];// 姓名
int m_Age; //年龄
};
void test04() {
ifstream ifs;
ifs.open("person.txt", ios::out | ios::binary);
if (!ifs.is_open()) {
cout << "文件打开失败" << endl;
return;
}
Person p;
ifs.read((char*) & p, sizeof(Person));
cout << "姓名:" << p.m_Name << endl;
cout << "年龄:" << p.m_Age << endl;
ifs.close();
}
int main() {
test04();
system("pause");
return 0;
}