C++自学之旅开始!

视频链接

常量:

#define Day 7

const int a=12;

常量不可修改

系统随机生成数:

int num=rand()%100+1//生成1~100之间的随机数

求有7的数:

if(i%7==0||i%10==7||i/10==7)//倍数,个位,十位

乘法口诀表:

for(int i=1;i<=9;i++)

{

	for(int j=1;j<=i;j++)

	{

		cout<<j<<" * "<<i<<"="<<j*i<<" ";

	}

}

如果是奇数输出,偶数不输出:

for(int i=0;i<=100;i++)

{

	if(i%2==0)

	{

		continue;

	}

cout<<i<<endl;

}

goto

goto语句:无条件的跳转代码

goto后面是一个标记,如果标记存在则执行到goto语句时自动跳转到标记语句

#include <iostream>
using namespace std;
int main()
{
    cout << "1" << endl;
    /*
    goto语句:无条件的跳转代码
    goto后面是一个标记,如果标记存在则执行到goto语句时自动跳转到标记语句
    */
    goto FLAG; // goto会直接跳到FLAG语句执行FLAG下面的代码
    cout << "2" << endl;
    cout << "3" << endl;
FLAG:
    cout << "4" << endl;
    cout << "5" << endl;
    return 0;
}

数组找最大值:

int arr[5]={300,500,400,200,250};

int max=0;

for(int i=0;i<5;i++)

{

	if(arr[i]>max) max=arr[i];

}

cout<<max<<endl;

数组逆转:

int arr[5]={33,77,99,88,100};

int start=0;

int end=sizeof(arr)/sizeof(arr[0])-1;//数组长度

while(start<end){//只要起始位置小于结束位置

int temp=arr[start];

arr[start]=arr[end];

arr[end]=temp;

start++;

end--;

}

二维数组定义方式:

int arr[2][3]=//行数是可以省略的,列数不能

{

	{1,2,3},

	{4,5,6}

};

算出二维数组的行数和列数:

sizeof(arr)/sizeof(arr[0])//行数

sizeof(arr[0])/sizeof(arr[0][0])//列数

统计成绩:

int arr[3][3]=

{

	{100,100,100};

	{50,60,80};

	{60,70,80};

};

string names[3]={"张三","李四","王五"};

for(int i=0;I<3;i++)

{

	int sum=0;

	for(int j=0;j<3;j++)

	{

			sum+=arr[i][j];

	}

	cout<<names[i]<<":"<<sum<<endl;

}

函数的分文件:

创建一个.h的头文件,在头文件中写函数的声明,在分文件中写函数的具体实现:

在外部文件定义就可以使用了,先在.h文件声明:

day03_fun.h文件代码:

//这是.h文件
#include <iostream>//必须要包含这个引用这个文件的cpp
//另一个文件才能使用cout
using namespace std;
void fun(int a,int b);

然后在.cpp编写函数代码,再在主文件中引用并调用函数名称。

day03_fun.cpp文件代码:

//这是另一个.cpp文件
#include "day03_fun.h"//只能是""号不能是<>,""代表自定义的文件
void fun(int a,int b)
{
    int temp=a;
    a=b;
    b=temp;
    cout<<a<<endl<<b<<endl;
}

day03.cpp文件代码:

//这是主.cpp文件
#include <iostream>
#include "day03_fun.cpp"
using namespace std;
int main()
{
    int a = 10, b = 20;
    //在.h文件中定义函数声明,在.cpp文件中编写函数具体实现的代码,再在这个文件中引入.cpp文件就可以直接使用这个函数
    fun(a, b);

    return 0;
}

指针

指针就是一个地址,通过指针可以来保存地址

定义一个指针改变变量的值:

int a=10;

int * p;

p=&a;

*p=1000;

这时a的值已改变为1000;

输出a的值和*p的值都是1000

指针一般占4个字节,64位下占8个字节

空指针:

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

int * p=NULL;//这就是空指针

*p=100;//此条是错误的,空指针不可以访问,因为内存编号为0~255是系统占用内存,不可访问。

野指针:指针指向了没有权利操纵的一块内存

int * p=(int *)0x1110;//这就是野指针,指针指向了一块没有申请过的空间。

const修饰指针:常量指针

const int * p=&a;//这就是常量指针,在指针前面加上const就是常量指针,特点:指针的指向可以修改,但是指针指向的值不可以改:

*p=20;//这条是错的,指针指向的值不可以改

p=&b;//正确,指针的指向可以改

const修饰常量:指针常量

int * const p=&a;//这就是指针常量,特点:指针的指向不可以改,指针指向的值可以改

*p=20;//正确,指向的值可以改

p=&b;//这条是错误的,指针的指向不可以改

const即修饰指针又修饰常量

const int * const p=&a;//这就是即修饰指针又修饰常量,特点:指针的指向和指针指向的值都不可以改

指针访问数组:

int arr[10]={1,2,3,4,5,6,7,8,9,10};

int *p=arr;//用指针指向数组的首地址,也就是第一个元素

cout<<*p<<endl;//输出为数组第一个数,也就是1

p++;//让指针向后偏移4个字节

cout<<*p<<endl;//输出数组第二个数,也就是2

利用指针遍历数组:

int *p2=arr;//先拿到数组的首地址

for(int i=0;i<10;i++)

{

	cout<<*p2<<endl;//输出完后

	p2++;//指针向后偏移一位

}

利用指针实现函数的值的交换

int a=10;

int b=20;

swap01(&a,&b);//地址传递

void swap01(int *p1,int *p2)//利用指针实现值的改变

{

	int temp=*p1;

	*p1=*p2;

	*p2=temp;

}

cout<<a<<b<<endl;//这时a=20,b=10

指针复习强化:

//指针复习强化
#include <iostream>
using namespace std;
void test01();
void test02();
int main()
{
    //指针 = &变量
    //*指针 = 指针指定的变量的值
    int *p;
    int a = 5;
    // p指针指向a变量的地址
    p = &a;
    int *q;
    int b = 10;
    // q指针指向b变量的地址
    q = &b;
    int *r;

    //通过第三个指针将p和q指针指向的变量交换
    //让p指向b变量,q指向a变量
    // r=p 也就是让p指针指向的a变量的地址赋值给r指针
    r = p;
    // p=q 也就是让q指针指向的b变量的地址赋值给p指针
    p = q;
    // q=r 也就是让r指针指向的a变量的地址赋值给q指针
    q = r;

    //这时通过*p修改的变量不是a变量而是b变量
    //通过*q修改的不是b变量而是a变量
    *p = 99;
    *q = 100;
    cout << "*p = " << *p << " a = " << a << endl;
    cout << "*q = " << *q << " b = " << b << endl;
    cout << "*r = " << *r << endl;

    test01();
    test02();
    return 0;
}
void test01()
{
    //通过指针按先大后小的顺序输出两个整数
    int a, b, *p, *q, *r;
    printf("请输入两个整数:\n");
    scanf("%d %d", &a, &b);
    p = &a;
    q = &b;
    if (a < b)
    {
        r = p;
        p = q;
        q = r;
    }
    printf("较大的数为%d 较小的数为%d\n", *p, *q);
}
void test02()
{
    //指针变量与一维数组
    int *p, *q, a[3] = {5, 10, 15};

    //指针p指向数组a中的第一个元素a[0],a[0]里面存放的值为5
    p = &a[0];

    //指针q指向数组a中的第二个元素a[1],a[1]里面存放的值为10
    q = &a[1];

    //通过指针p修改数组a[0]的值,a[0]原本的值是5,*2之后变成10
    *p = *p * 2;

    //通过指针q修改数组a[2]的值,指针q原本指向的是a[1],但是当q+1之后指针往前移动一位,指针q的指向
    //就变成了a[2],而a[2]里面存放的值是15,当*p拿到a[0]的值10之后再*2得到20并赋值给*q指向的a[2]的值
    *(q + 1) = *p * 2;

    printf("通过指针修改后数组的值为: \n");
    for (int i = 0; i < 3; i++)
    {
        cout << a[i] << " ";
    }
}

封装一个函数对数组进行冒泡排序:

int arr[10]={4,3,6,7,9,2,10,5,100,88};

int len=sizeof(arr)/sizeof(arr[0]);

bubbleSort(arr,len);

void bubbleSort(int *arr,int len)

{

	for(int i=0;i<len-1;i++)//冒泡排序

	{

		for(int j=0;j<len-i-1;j++)

		{

			if(arr[j]>arr[j+1])

			{

				int temp=arr[j];

				arr[j]=arr[j+1];

				arr[j+1]=temp;

			}

		}

	}

}

这时数组就已经排好序了,只要输出就好

结构体:

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

创建学生数据类型:

struct Student

{

	string name//姓名

	int age;//年龄

	int score;//分数

}s3;

通过学生类型创建具体学生:

struct Student s1;//创建

s1.name="张三";//赋值

s1.age=18;//赋值

s1.score=100;//赋值

struct Student s2={"李四",19,90};//在创建的时候就赋值

s3.name="王五";//在定义时就已经创建了s3变量

s3.age=20;//在定义时就已经创建了s3变量

s3.score=60;//在定义时就已经创建了s3变量

结构体数组:

struct Student//定义一个结构体

{

	string name;

	int age;

	int score;

};

Student stuArray[3]=//创建结构体数组

{

		{"张三",18,100},

		{"李四",28,99},

		{"王五",38,66}

};

stuArray[2].name="赵六";//重新修改结构体数组的值

stuArray[2].age=80;

stuArray[2].score=60;

for(int i=0;i<3;i++)

{

cout<<stuArray[i].name<<stuArray[i].age<<stuArray[i].score<<endl;

}

结构体指针:

利用结构体指针来操作结构体成员变量

struct Student

{

	string name;

	int age;

	int score;

};

Student s={"张三",18,100};//创建结构体变量并赋值

Student *p=&s;//创建一个结构体指针指向结构体变量

cout<<p->name<<p->age<<p->score<<endl;//通过指针访问结构体里的变量并输出,访问需要用“->”号

结构体嵌套结构体:

struct Student

{

	string name;

	int age;

	int score;

};

struct teacher

{

	int id;

	string name;

	int age;

	int score;

	struct Student stu;//在结构体内部引用另一个结构体创建另一个结构体变量

};

teacher t;//创建老师结构体

t.id=1000;

t.name="老王";

t.age=50;

t.stu.name="小王"//表示调用了老师结构体当中的学生结构体

t.stu.age=18;

t.stu.score=70;

cout<<t.name<<" "<<t.stu.name<<endl;

将结构体做函数参数:

struct Student

{

	string name;

	int age;

	int score;

};

Student s={"张三",20,85};

printStudent1(s);

printStudent2(&s);

void printStudent1(struct Student s)//值传递

{

	s.age=200;//这里的修改只在函数内生效

	cout<<s.name<<s.age<<s.score<<endl;

}

void printStudent2(struct student *p)//地址传递

{

	p->age=100;//通过地址传递直接修改了值

	cout<<p->name<<p->age<<p->score<<endl;

}

结构体中const使用场景:

void printfStudent(const Student *s)

{

	s->age=150;//这条会报错,因为const不能修改

	cout<<s->name<<s->age<<endl;

}

结构体案例1:

创建三个老师结构体,每个老师下面有五个学生结构体,每个学生的分数是不同的。

#include <iostream>
using namespace std;
#include <ctime> //用来创建随机数
struct Student
{
    string sName;
    int score;
};
struct Teacher
{
    string tName;
    struct Student sArray[5];
};
void allocateSpace(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].sArray[j].sName = "Student_";   //给学生赋值
            tArray[i].sArray[j].sName += nameSeed[j]; //区别不同的学生
            int random = rand() % 61 + 40;            //随机数40~100
            tArray[i].sArray[j].score = random;
        }
    }
}
void printInfo(Teacher tArray[], int len)
{
    for (int i = 0; i < len; i++)
    {
        cout << tArray[i].tName << endl;
        for (int j = 0; j < 5; j++)
        {
            cout << tArray[i].sArray[j].sName << " "
                 << tArray[i].sArray[j].score << endl;
        }
    }
}
int main()
{
    srand((unsigned int)time(NULL));//创建随机数种子,以当前时间来创建随机数
    Teacher tArray[3]; //创建三个老师
    int len = sizeof(tArray) / sizeof(tArray[0]);
    allocateSpace(tArray, len); //在里面创建五个学生并赋值
    printInfo(tArray, len);
    return 0;
}

结构体案例2:

设计一个英雄结构体,包括姓名,年龄,性别,创建结构体数组,结构体存放5名英雄,通过冒泡排序的算法将数组中的英雄按照年龄进行升序排序。

#include <iostream>
using namespace std;
struct Hero //定义英雄的结构体
{
    string name; //姓名
    int age;     //年龄
    string sex;  //性别
};
void bubbleSort(Hero *p, int len) //利用冒泡交换结构体
{
    for (int i = 0; i < len - 1; i++)
    {
        for (int j = 0; j < len - i - 1; j++)
        {
            if (p[j].age > p[j + 1].age) //比较年龄
            {
                struct Hero temp = p[j]; //实现结构体交换
                p[j] = p[j + 1];
                p[j + 1] = temp;
            }
        }
    }
}
void printArray(Hero heroArray[], int len)
{
    for (int i = 0; i < len; i++)
    {
        cout << heroArray[i].name << " " << heroArray[i].age << " "
             << heroArray[i].sex << endl;
    }
}
int main()
{
    struct Hero heroArray[5] =
        {
            {"liubei", 23, "man"},
            {"guangyu", 22, "man"},
            {"zhangfei", 20, "man"},
            {"zhaoyue", 21, "man"},
            {"diaocan", 19, "woman"}
        };
    int len = sizeof(heroArray) / sizeof(heroArray[0]);
    //排序前:
    printArray(heroArray, len);
    bubbleSort(heroArray, len);
    //排序后:
    printArray(heroArray, len);
    return 0;
}

通讯录管理系统:

代码:

#include <iostream>
using namespace std;
#define MAX 1000
struct Person //设计联系人结构体
{
    string m_Name;  //姓名
    int m_Sex;      //性别
    int m_Age;      //年龄
    string m_Phone; //电话
    string m_Addr;  //住址
};
struct Addressbooks //设计通讯录结构体
{
    struct Person personArray[MAX]; //通讯录保存的联系人数组
    int m_Size;                     //通讯录当前记录联系人个数
};
void addPerson(Addressbooks *abs) //添加联系人
{
    if (abs->m_Size == MAX)
    {
        cout << "Address book full" << endl; //已满
        return;
    }
    else //如果没满才添加
    {
        string name;
        cout << "name:" << endl;
        cin >> name;
        abs->personArray[abs->m_Size].m_Name = name; //将输入的名字存入数组
        cout << "sex:" << endl;
        cout << "1 --- man" << endl;
        cout << "2 --- woman" << endl;
        int sex = 0;
        while (true)
        {
            cin >> sex;
            if (sex == 1 || sex == 2)
            {
                abs->personArray[abs->m_Size].m_Sex = sex; //将输入的名字存入数组
                break;
            }
            cout << "error" << endl;
        }
        cout << "age:" << endl;
        int age = 0;
        while (true)
        {
            cin >> age;
            if (age > 0 && age <= 150)
            {
                abs->personArray[abs->m_Size].m_Age = age; //将输入的年龄存入数组
                break;
            }
            cout << "error" << endl;
        }
        cout << "phone:" << endl;
        string phone;
        cin >> phone;
        abs->personArray[abs->m_Size].m_Phone = phone; //将输入的电话号码存入数组
        cout << "Addr:" << endl;
        string address;
        cin >> address;
        abs->personArray[abs->m_Size].m_Addr = address; //将输入的住址存入数组
        abs->m_Size++;                                  //更新通讯录人数
        cout << "successfully add !" << endl;           //添加成功

        system("pause"); //按任意键继续
        system("cls");   //清屏
    }
}
void showPerson(Addressbooks *abs) //显示所有联系人
{
    if (abs->m_Size == 0)
    {
        cout << "Address book NULL" << endl; //为空
        system("pause");                     //按任意键继续
        system("cls");                       //清屏
        return;                              //退出
    }
    else
    {
        for (int i = 0; i < abs->m_Size; i++) //遍历通讯录
        {
            cout << "name: " << abs->personArray[i].m_Name << '\t';
            cout << "sex: " << (abs->personArray[i].m_Sex == 1 ? "man" : "woman") << '\t';
            cout << "age: " << abs->personArray[i].m_Age << '\t';
            cout << "phone: " << abs->personArray[i].m_Phone << '\t';
            cout << "Addr: " << abs->personArray[i].m_Addr << endl;
        }
    }
    system("pause"); //按任意键继续
    system("cls");   //清屏
}
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
}
void deletePerson(Addressbooks *abs) //删除指定的联系人
{
    cout << "delete name:" << endl; //请输入要删除联系人的名字
    string name;
    cin >> name;
    /*之前前面加 &是要改变通讯录里面的信息,
    而这个函数只需要得到返回值,不需要对具体通讯录的东西进行修改*/
    int ret = isExist(abs, name); //检查联系人是否存在
    if (ret != -1)                //存在的话ret就是这个人的下标
    {
        /// cout << "found it!" << endl; //找到了
        for (int i = ret; i < abs->m_Size; i++)
        {
            //所谓删除其实是后面的数据把前面的数据覆盖掉
            abs->personArray[i] = abs->personArray[i + 1]; //将数据前移
        }
        abs->m_Size--;
        cout << "delete success!" << endl;
    }
    else
    {
        cout << "There is no such person!" << endl; //没有这个人
    }
    system("pause"); //按任意键继续
    system("cls");   //清屏
}
void findPerson(Addressbooks *abs) //查找联系人
{
    cout << "find name:" << endl; //请输入要查找的联系人的名字
    string name;
    cin >> name;
    int ret = isExist(abs, name); //检查联系人是否存在
    if (ret != -1)                //找到了
    {
        cout << "name: " << abs->personArray[ret].m_Name << "\t";                        //姓名
        cout << "sex: " << (abs->personArray[ret].m_Sex == 1 ? "man" : "woman") << "\t"; //性别
        cout << "age: " << abs->personArray[ret].m_Age << "\t";                          //年龄
        cout << "phone: " << abs->personArray[ret].m_Phone << "\t";                      //电话
        cout << "Addr: " << abs->personArray[ret].m_Addr << endl;                        //地址
    }
    else //没找到
    {
        cout << "There is no such person!" << endl; //没有这个人
    }
    system("pause"); //按任意键继续
    system("cls");   //清屏
}
void modifyPerson(Addressbooks *abs) //修改指定联系人信息
{
    cout << "modify name:" << endl;
    string name;
    cin >> name;
    int ret = isExist(abs, name);
    if (ret != -1)
    {
        string name;
        cout << "alert name:" << endl; //请输入要修改的姓名
        cin >> name;
        abs->personArray[ret].m_Name = name; //重新赋值
        cout << "alert sex:" << endl;        //请输入要修改的性别
        cout << "1 --- man" << endl;
        cout << "2 --- woman" << endl;
        int sex = 0;
        while (true) //输入对了就退出,错了就循环重新输入
        {
            cin >> sex;
            if (sex == 1 || sex == 2)
            {
                abs->personArray[ret].m_Sex = sex;
                break;
            }
            cout << "alert error" << endl;
        }
        cout << "alert age:" << endl; //请输入要修改的年龄
        int age = 0;
        while (true)
        {
            cin >> age;
            if (age > 0 && age <= 150)
            {
                abs->personArray[ret].m_Age = age;
                break;
            }
            cout << "alert error" << endl;
        }
        cout << "alert phone" << endl; //请输入要修改的电话
        string phone;
        cin >> phone;
        abs->personArray[ret].m_Phone = phone;
        cout << "alert Addr" << endl; //请输入要修改的住址
        string address;
        cin >> address;
        abs->personArray[ret].m_Addr = address;
        cout << "alert success!" << endl;
    }
    else
    {
        cout << "There is no such person!" << endl; //没有这个人
    }
    system("pause"); //按任意键继续
    system("cls");   //清屏
}
void cleanPerson(Addressbooks *abs)
{
    cout << "Really want to empty?" << endl;
    cout << "1 --- yes" << endl;
    cout << "2 --- no" << endl;
    int ret = 0;
    cin >> ret;
    if (ret == 1)
    {
        abs->m_Size = 0; //将当前记录联系人数量置为0,做逻辑上的清空
        cout << "Address book cleared NULL!" << endl;
    }
    system("pause"); //按任意键继续
    system("cls");   //清屏
}
void showMenu() //初始界面
{
    cout << "**********************" << endl;
    cout << "***** 1.add *****" << endl;    //添加
    cout << "***** 2.show *****" << endl;   //显示
    cout << "***** 3.delete *****" << endl; //删除
    cout << "***** 4.find *****" << endl;   //查找
    cout << "***** 5.alter *****" << endl;  //修改
    cout << "***** 6.empty *****" << endl;  //清空
    cout << "***** 0.exit *****" << endl;   //退出
    cout << "**********************" << endl;
}
int main()
{
    Addressbooks abs; //创建通讯录结构体
    abs.m_Size = 0;   //一开始通讯录人数为0
    int select = 0;
    while (true)
    {
        showMenu();
        cin >> select;
        switch (select)
        {
        case 1:              //添加
            addPerson(&abs); //利用地址传递修饰实参
            break;
        case 2: //显示
            showPerson(&abs);
            break;
        case 3: //删除
                // {//如果要在switch输入多行需要括号
                //     cout << "delete name:" << endl; //请输入要删除联系人的名字
                //     string name;
                //     cin >> name;
                //     if (isExist(&abs, name) == -1)
                //     {
                //         cout << "There is no such person!" << endl; //没有这个人
                //     }
                //     else
                //     {
                //         cout << "found it!" << endl; //找到了
                //     }
                // }
            deletePerson(&abs);
            break;
        case 4: //查找
            findPerson(&abs);
            break;
        case 5: //修改
            modifyPerson(&abs);
            break;
        case 6: //清空
            cleanPerson(&abs);
            break;
        case 0: //退出
            cout << "Welcome to use next time" << endl;
            system("pause");
            return 0;
            break;
        default:
            break;
        }
    }
    return 0;
}

内存四区:

代码区:在程序运行前执行的区

存放cpu执行的机器指令,其实就是你写的代码存放的exe文件,代码区是共享的,只读的,不可修改的

全局区:在程序运行前执行的区

存放全局变量和静态变量,字符串,常量,该区域的数据在程序结束后由操作系统释放,注意局部的变量不存在全局区中,main()里面的也是局部变量。

静态变量是 static int s=10;在变量前面加static就是静态变量。

cout<<(int)&s;//查看地址,"hello,world",此字符串存在全局区中,const修饰的常量才在全局区,const修饰的变量不在全局区。

栈区:

存放函数的参数和局部变量,如:

void fun(int b)//这个b是形参也是放在栈上

{

	int a=10;//这个就是存放在栈区的局部变量

	return &a;//这条是错误的,函数在执行完后会自动释放,地址不会存在,无法返回地址

}

堆区:由程序员分配释放,主要利用new在堆区开辟内存,程序结束时由操作系统回收,如:

int *fun()

{

	int *p=new int(10);//这就是用new开辟了一块int类型大小为10的空间,用指针接收这块内存的地址

return p;//这条是正确的,堆区的内存不会自动释放,可以返回,指针本质也是局部变量,保存在栈上

}

int *test01()

{

	int *arr=new int[10];//利用new关键字在堆区创建数组,返回的是数组的首地址,可以用指针接收

for(int i=0;i<10;i++)

{

	arr[i]=i+100;//给10个元素赋值

}

return arr;//返回数组的首地址

delete[ ] arr;//释放数组

}

int *p=fun();//用指针接收返回回来的堆区内存

如果要释放就利用delete操作符,如:

int *fun()

{

	int *p=new int(10);

	return p;

}

void test()

{

	int *p=fun();

	cout<<*p<<endl;//10,因为内存大小是10

	delete p;//这就是释放堆区的内存

	cout<<*p<<endl;//这条会报错,因为内存已经被释放了。

delete[ ] arr;//释放数组

}

总结:程序运行前分为全局区和代码区,程序运行后分为栈区和堆区

完整代码:

//程序运行前分为全局区和代码区,程序运行后分为栈区和堆区
#include <iostream>
using namespace std;
int *fun()
{
    //这就是用new开辟了一块int类型大小为10的空间,用指针接收这块内存的地址
    int *p = new int(10);

    //这条是正确的,堆区的内存不会自动释放,可以返回,指针本质也是局部变量,保存在栈上
    return p;
}
int *test()
{
    //利用new关键字在堆区创建数组,返回的是数组的首地址,可以用指针接收
    int *arr = new int[10];
    for (int i = 0; i < 10; i++)
    {
        //给10个元素赋值
        arr[i] = i + 100;
    }
    //返回数组的首地址
    return arr;
}
int main()
{
    //用指针接收返回回来的堆区内存
    //如果要释放就利用delete操作符
    int *p = fun();
    // 10,因为内存大小是10
    cout << *p << endl;
    int *arr = test();
    for (int i = 0; i < 10; i++)
    {
        cout << arr[i] << endl;
    }

    //这就是释放堆区的内存
    delete p;

    //这条会报错,因为内存已经被释放了。
    // cout << *p << endl;

    //释放数组
    delete[] arr;
    return 0;
}

引用:

给一个变量起别名,如:

int a=10;

int &b=a;//这就是起别名,将a的内存赋值给b,b就等于a,a也等于b

b=20;

cout<<a<<endl;//这时a是20,因为b和a操作的是同一块内存,b变了a也跟着变

int &c;//这条是错误的,引用必须初始化,不能光定义

int d=30;

&b=d;//这条是错误的,引用一旦初始化后就不能改变了,&b在上面已经指向了a那块内存,不能再重新指向d的内存了。

b=d;//这条是赋值操作,正确的,相当于把b原来那块内存变成c那块内存,那么a,b,c都会变成30

引用做函数参数:

可以用形参修饰实参,弱化指针的用途,如:

void mySwap(int &a,int &b)//利用引用改变两个变量的值

{

		int temp = a;

		a = b;

		b = temp;

}

int a=10,b=20;

mySwap(a,b);

引用做函数返回值:

int &test01()//创建了一个引用函数

{

	int a=10;

	return a;

}

int &ref=test01();//这条是错误的,引用返回的变量不能用引用接收,因为变量已经释放

int &test2()

{

	static int b=10;//静态变量存放在全局区,不会释放

	return b;

}

int &ref2=test2();//这条是正确的,因为b不会被栈释放

test2()=1000;//这条是正确的,返回的是b的引用,如果函数是左值那么必须返回引用,这时输出ref2的话都是1000。

引用的本质:

其实底层是指针常量,指针常量是指针的指向不可变,指针指向的值可变,如:

int &ref=a;//底层是:int *const ref=&a;

ref=20;//底层是:*ref=20;

常量引用:

int &ref=10;//这条是错误的,10是常量,不可以引用

const int &res=10;//这条是正确的,编译器在底层帮写了:int temp=10;const int &res=temp;

res=20;//这条是错误的,加入const之后变为只读,不可修改。

void showValue(const int &a)

{

	a=1000;//这条是错误的,const不可修改

	cout<<a<<endl;//100

}

int a=100

showValue(a);

cout<<a<<endl;//100,因为函数内部没有改变实参

完整代码:

#include <iostream>
using namespace std;
void fun(int &a, int &b) //利用引用改变两个变量的值
{
    int temp = a;
    a = b;
    b = temp;
}
int &test1() //创建了一个引用函数
{
    static int aq = 200; //静态变量存放在全局区,不会被栈释放
    return aq;
}
void showValue(const int &a)
{
    // a = 1000; //这条是错误的,const不可修改
    cout << a << endl; // 100
}
int main()
{
    int a = 10;
    int &b = a; //这就是起别名,将a的内存赋值给b,b就等于a,a也等于b
    b = 20;
    cout << a << endl; //这时a是20,因为b和a操作的是同一块内存,b变了a也跟着变
    // int &c;            //这条是错误的,引用必须初始化,不能光定义
    int d = 30;
    //&b = d; 这条是错误的,引用一旦初始化后就不能改变了,
    // &b在上面已经指向了a那块内存,不能再重新指向d的内存了。
    b = d; //这条是赋值操作,正确的,
    //相当于把b原来那块内存变成c那块内存,那么a,b,c都会变成30
    cout << a << endl;
    int a1 = 10, b1 = 20;
    fun(a1, b1); //利用引用改变两个变量的值
    cout << a1 << endl;
    cout << b1 << endl;
    int &res = test1();  //这条是正确的,因为aq不会被栈释放所以可以赋值
    cout << res << endl; // 200
    test1() = 1000;      //这条是正确的,返回的是b的引用,如果函数是左值那么必须返回引用
    cout << res << endl; // 1000
    // int &ref = 10;       //这条是错误的,10是常量,不可以引用
    const int &ress = 10; //这条是正确的,编译器在底层帮写了:int temp=10;const int &res=temp;
    // ress = 20;            //这条是错误的,加入const之后变为只读,不可修改。

    return 0;
}

函数提高:

函数的形参是可以有默认值的,如:

int func(int a, int b = 20, int c = 30) //函数的形参是可以有默认值的

{

    return a + b + c;

}

func(10);//因为在函数里定义了参数所以可以少传参数,如果传入了就用传入的数据,如果没有才用默认值。

int func2(int a, int b = 10, int c, int d)//这里是错误的,如果左边已经有默认值了那么右边也必须得有,从左往右

{

	return a+b+c+d;

}

如果函数声明有默认参数,函数实现就不能有默认参数,如:

int fun(int a=10,int b=20);//函数声明

int fun(int a=10;int b=20)//此条是错误的,声明和函数定义只能有一个有默认值

{

	return a+b;

}

函数占位参数:

void func(int a,int)//正确的,可以只写一个数据类型来占位

{

	cout<<a<<endl;

}

func(10,10);//函数调用的时候必须传两个值才行,因为有个数据类型占位了

void func(int a,int = 10)//占位参数也可以有默认值

函数重载:

函数名可以相同,提高复用性,需要满足条件:

同一个作用域下,函数名称相同,函数参数类型不同或者个数不同或者顺序不同

void func()

{

	cout<<""hello,world<<endl;

}

void func(int a)//正确的,只要参数有改变则可以定义名称一样的函数

{

	cout<<a<<endl;

}

void func(double a)//数据类型不一样也可以重复定义

{

	cout<<a<<endl;//正确的

}

void func(double a,int b);//正确的,数量不一样

void func(int a,double b);//正确的,顺序不一样

func();//默认是调第一个

func(10);//有参数则调用第二个整形的

func(3.14);//调用第三个浮点数

这些函数都放在全局作用域内,所以是同一作用域

注意:函数重载不可以有返回值,如:

void func(double a,int b);

int func(double a,int b);//错误,函数重载返回值必须是void。

引用作为重载的条件:

void fun(int &a);

void fun(const int &a);//正确的,引用类型不同

//调用:

int a=10;

fun(a);//默认是传第一个

fun(10);//直接传常量就会走第二个,因为const int &a=10是合法的,而int &a=10;是不合法的

函数重载碰到默认参数:

void fun(int a,int b=10);

void fun(int a);

fun(10);//错误,两个都能调会出现二义性

完整代码:

#include <iostream>
using namespace std;
int func(int a, int b = 20, int c = 30) //函数的形参是可以有默认值的
{
    return a + b + c;
}
// int func2(int a, int b = 10, int c, int d)//这里是错误的,如果左边已经有默认值了那么右边也必须得有
// {
//     return a + b + c + d;
// }
int fun3(int a = 10, int b = 10); //函数声明
//如果函数声明有默认参数, 函数实现就不能有默认参数,声明和实现只能一个有默认参数
void func3(int a, int = 10) //占位参数也可以传默认值
{
    cout << a << endl;
}
void fun(int a);           //正确的,函数重载只要参数有改变则可以定义名称一样的函数
void fun(double a);        //函数重载数据类型不一样也可以重复定义
void fun(int a, double b); //函数重载正确的,数量不一样
void fun(double a, int b); //函数重载正确的,顺序不一样
// int fun(double a, int b); //错误,函数重载返回值必须是void
void funw(int &a);
void funw(const int &a);      //正确的,引用类型不同
void funa(int a, int b = 10); //错误,函数重载尽量不要用默认值
void funa(int a);
int main()
{
    cout << func(10) << endl; //因为在函数里定义了参数所以可以少传参数
    //注意:如果传入了就用传入的数据,如果没有才用默认值
    // cout << func2(10) << endl;
    func3(10, 10); //函数调用的时候必须传两个值才行,因为有个数据类型占位了
    int a = 10;
    funw(a); //默认是传第一个

    funw(10); //直接传常量就会走第二个,因为const int &a=10是合法的
    //而int &a=10;是不合法的
    // funa(10); //错误,两个都能调会出现二意性
    return 0;
}
// int fun3(int a = 10, int b = 10) //错误,如果函数声明有默认参数,函数实现就不能有默认参数
// {
//声明和实现只能一个有默认参数
//     return a + b;
// }

类和对象:

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

对象拥有属性和行为。

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

公式:2 PI 半径

const double PI=3.14;

class Circle//class代表一个类,类后面紧跟着类名称

{

public:

		int m_r;//半径,属性

		double calculateZC()//求圆的一个行为

		{

			return 2 *PI *m_r;

		}

};

Circle c1;//通过类创建一个对象

c1.m_r = 10;//给圆的对象的属性进行赋值

cout<<c1.calculateZC()<<endl;//求周长

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

class Student

{

public://访问权限

		string m_Name;//姓名

		int m_Id;//学号

		void showStudent()

		{

			cout<<m_Name<<" "<<m_Id<<endl;

		}

		void setName(string name)

		{

			m_Name=name;

		}

		void setId(int id)

		{

			m_Id=id;

		}

};

Student s1;

s1.m_Name="张三";//给s1对象属性进行赋值操作

s1.m_Id=1;

s1.showStudent();//显示学生信息

Student s2;

s2.m_Name="李四";

s2.m_Id=2;

s2.showStudent();

案例3:设计一个通讯录

访问权限:

公告权限:public 成员 类内可以访问 类外可以访问

保护权限:protected 成员 类内可以访问 类外不可以访问 儿子可以访问父亲的保护内容

私有权限:private 成员 类内可以访问 类外不可以访问 儿子不可以访问父亲的私有内容

//访问权限
#include <iostream>
using namespace std;
class Student //如果class里面什么都不写那么它默认是私有权限
{
public:             //公共权限,类内类外都可以访问
    string m_Name;  //姓名
protected:          //保护权限,类内可以访问,类外不可以访问
    string m_Car;   //汽车
private:            //私有权限,类内可以访问,类外不可以访问
    int m_Password; //密码
    // protected与private的区别在于继承方面,儿子可以访问protected不可以访问private
public:
    void func()
    {
        m_Name = "zs";
        m_Car = "tlj";       //类内可以访问
        m_Password = 123456; //类内可以访问
    }
};
struct C1 //如果struct里面什么都不写那么它是公共权限
{
    int m_A;
};
// class和struct的区别就是一个权限的私有,一个权限是公共,如果是私有那么需要用public来变成公共
int main()
{

    Student s1;
    s1.m_Name = 'lisi'; //公共权限类外可以访问
    // s1.m_Car="bm";//保护权限在类外是不可以访问的
    // s1.m_Password=1267;//私有权限在类外也是不可以访问的
    C1 s2;
    s2.m_A = 100; //在struct默认的权限的公共,所以可以访问
    return 0;
}

将成员属性设置为私有

//将成员属性设置为私有
#include <iostream>
using namespace std;
class Person //设计人类
{
public:                       //在外面设置公共可以给私有权限可读可写
    void setName(string name) //设置姓名
    {
        m_Name = name;
    }
    string getName() //读取姓名
    {
        return m_Name;
    }
    int getAge() //获取年龄
    {
        // m_Age = 0; //初始化为0岁
        return m_Age;
    }
    void setAge(int age) //设置年龄 将年龄变为可读可写
    {
        if (age < 0 || age > 150) //判断年龄范围
        {
            age = 0;
            return;
        }
        m_Age = age;
    }
    void setLover(string lover) //设置情人 只写
    {
        m_Lover = lover;
    }

private:            //设置私有化
                    //优点:可以自己控制读写权限
                    //可以防止超出访问范围
    string m_Name;  //姓名 可读可写
    int m_Age;      //年龄 只读
    string m_Lover; //情人 只写
};
int main()
{
    Person p1;
    p1.setName("zs");             //设置姓名
    cout << p1.getName() << endl; //读取姓名
    p1.setAge(18);                //设置年龄
    cout << p1.getAge() << endl;  //读取年龄
    p1.setLover("xh");            //设置情人
    return 0;
}

案例:设计立方体

//案例:设计立方体
#include <iostream>
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_L * m_W + 2 * m_W * m_H + 2 * m_L * m_H;
    }
    int calculateV() //获取立方体体积
    {                //长乘宽乘高
        return m_L * m_W * m_H;
    }
    //使用内部类判断两个立方体是否相同
    bool isSameByClass(Cube &c) //因为是内部类所以只需要传一份参数
    {
        if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH())
        {
            return true;
        }
        return false;
    }

private:     //属性
    int m_L; //长
    int m_W; //宽
    int m_H; //高
};
//判断两个立方体是否相等
bool isSame(Cube &box1, Cube &box2) //通过引用的方式传递可以防止拷贝数据
{
    if (box1.getL() == box2.getL() && box1.getW() == box2.getW() && box1.getH() == box2.getH())
    {
        return true;
    }
    return false;
}
int main()
{
    Cube box1;
    box1.setL(10);                     //设置长
    box1.setW(10);                     //设置宽
    box1.setH(10);                     //设置高
    cout << box1.calculateS() << endl; //输出面积
    cout << box1.calculateV() << endl; //输出体积
    Cube box2;
    box2.setL(10);
    box2.setW(10);
    box2.setH(11);
    bool ret = isSame(box1, box2);
    if (ret) //判断是否相等
    {
        cout << "Yes" << endl;
    }
    else
        cout << "No" << endl;

    ret = box1.isSameByClass(box2); // box1调用box2与它对比
    if (ret)                        //利用成员函数判断是否相等
    {
        cout << "Yes" << endl;
    }
    else
        cout << "No" << endl;
    return 0;
}

案例:点和圆的关系(分文件编写):

主.cpp文件:

//案例:点和圆的关系
#include <iostream>
using namespace std;
#include "day21_point.cpp" //把点类和圆类都放到另一个文件中再引用
#include "day21_circle.cpp"
// class Point //点类
// {
// public:
//     void setX(int x) //设置X
//     {
//         m_X = x;
//     }
//     int getX() //获取X
//     {
//         return m_X;
//     }
//     void setY(int y) //设置y
//     {
//         m_Y = y;
//     }
//     int getY() //获取y
//     {
//         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 setCenter(Point center) //设置圆心
//     {
//         m_Center = center;
//     }
//     Point getCenter() //获取圆心
//     {
//         return m_Center;
//     }

// private:     //圆的属性
//     int m_R; //半径
//     //在一个类中可以让另一个类作为本类的核心成员
//     Point m_Center; //圆心
// };
//判断点和圆的关系
void isInCircle(Circle &c, Point &p)
{
    //计算两点之间的平方
    int distance = //公式:(x1-x2)^2+(y1-y2)^2
        (c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
        (c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
    //计算半径的平方
    int rDistance = c.getR() * c.getR(); //半径乘半径 公式:m_R^2
    //判断关系
    if (distance == rDistance)
    {
        cout << "Point == Circle" << endl; //点在圆上
    }
    else if (distance > rDistance)
    {
        cout << "Point > Circle" << endl; //点在圆外
    }
    else
        cout << "Point < Circle" << endl; //点在圆内
}
int main()
{
    Circle c; //创建圆
    c.setR(10);
    Point center; //创建圆内的点
    center.setX(10);
    center.setY(0);
    c.setCenter(center);
    Point p; //创建点
    p.setX(10);
    p.setY(10);
    //判断关系
    isInCircle(c, p);
    return 0;
}

分circle.h文件:

#pragma once //防止头文件重复包含
#include <iostream>
using namespace std;
#include "day21_point.h" //因为引用了point类所以要包含
class Circle //圆类
{
public:
    void setR(int r);             //设置半径
    int getR();                   //获取半径
    void setCenter(Point center); //设置圆心
    Point getCenter();            //获取圆心

private:     //圆的属性
    int m_R; //半径
    //在一个类中可以让另一个类作为本类的核心成员
    Point m_Center; //圆心
};

分circle.cpp文件:

#include "day21_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;
}

分point.h文件:

#pragma once //防止头文件重复包含
#include <iostream>
using namespace std;
class Point //点类
{
public:
    //只需要加声明,不需要写具体实现
    void setX(int x); //设置X
    int getX();       //获取X
    void setY(int y); //设置y
    int getY();       //获取y

private: //点的属性
    int m_X;
    int m_Y;
};

分point.cpp文件:

#include "day21_point.h"
//需要告诉它是Point作用域下的函数
void Point::setX(int x) //设置X
{
    m_X = x;
}
int Point::getX() //获取X
{
    return m_X;
}
void Point::setY(int y) //设置y
{
    m_Y = y;
}
int Point::getY() //获取y
{
    return m_Y;
}

构造函数

对象的初始化和清理

//对象的初始化和清理
#include <iostream>
using namespace std;
class Person
{
public:
    //构造函数:
    Person() //这就是构造函数,构造函数可以有参数也可以发生重载,但是不能有返回值也没有返回类型
    {        //创建对象的时候构造函数会被编译器自动调用一次不过里面语句为空
        cout << "Hello,Person" << endl;
    }
    //析构函数,用来进行清理操作
    ~Person() //这就是析构函数,函数名和类型名相同,在名称前面加~号
    {         //析构函数不可以有参数,不可以发生重载,在对象销毁前会自动调用一次析构函数
        cout << "end ~Person" << endl;
    }
};
void test01()
{
    Person p; //这里是自动调用了构造函数,明明只是创建了一个对象而已却执行了输出语句
    //因为p是在栈上的对象,函数调用完就自动释放了,所以会执行析构函数
}
int main()
{
    test01();
    Person p; //因为有断点所以析构不会执行析构函数,断点执行完了后才会执行
    return 0;
}

构造函数的分类和调用

//构造函数的分类和调用
#include <iostream>
using namespace std;
class Person
{
public:
    Person() //无参构造,默认构造
    {
        cout << "Hello,Person" << endl;
    }
    Person(int a) //有参构造
    {
        //这里的age不是没有数据类型,而是在第33行已经定义好了,
        //后定义age变量实际类内执行起来是先执行构造函数外的,所以写下面实际也是先定义。
        age = a;
        cout << "Hello,Person:" << a << endl;
        if (typeid(age) == typeid(int))
        {
            cout << "age is int" << endl;
        }
        else
        {
            cout << "age is not type" << endl;
        }
    }
    //拷贝构造函数
    Person(const Person &p) //将参数拷贝到构造函数里,也就是将右边的属性传给左边
    {
        //将传入的人身上所有的属性拷贝到 Person上
        age = p.age;
        cout << age << endl;
    }
    int age;
    ~Person() //析构函数
    {
        cout << "end ~Person" << endl;
    }
};
void test01()
{
    // 1.括号法:
    Person p1;     //默认构造函数的调用,没有参数,不用加括号
    Person p2(10); //有参构造函数的调用
    Person p3(p2); //拷贝函数的调用
    //注意:Person p1();//如果无参构造加了括号编译器会认为是一个函数声明
    // 2.显示法:
    Person p4;
    Person p5 = Person(10); //有参构造
    Person p6 = Person(p5); //拷贝构造
    // Person(10)              //匿名对象,当前行执行结束就会销毁
    // Person (p3);//不要利用拷贝构造函数 初始化匿名对象 编译器会认为 Person(p3)===Person p3;
    //而p3在上面就已经被定义了,所以会重名
    // 3.隐式转换法:
    Person p7 = 10; //等价于 Person p7=Person(10)有参构造
    Person p8 = p7; //拷贝构造
    //注意:Person (p8);//不能用拷贝构造函数初始化匿名对象,编译器会认为是对象声明,和51行同理
}
int main()
{
    test01();
    return 0;
}

构造函数的调用时机

//构造函数的调用时机
#include <iostream>
using namespace std;
// 1、使用一个已经创建完毕的对象来初始化一个新对象
// 2、值传递的方式给函数参数传值
// 3、值方式返回局部对象
class Person
{
public:
    Person() //无参构造
    {
        cout << "Hello,Person" << endl;
    }
    Person(int age) //有参构造
    {
        m_Age = age;
        cout << "Hello " << age << endl;
    }
    Person(const Person &p) //拷贝构造
    {
        m_Age = p.m_Age;
        cout << m_Age << endl;
    }
    int m_Age;
    ~Person() //析构
    {
        cout << "end ~Person" << endl;
    }
};
// 2.值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test02()
{

    Person p;
    doWork(p); //当作为值传递给另一个函数的时候其实也执行了一次拷贝
}
// 3.值方式返回局部对象
Person doWork2()
{
    Person p1;
    //这里拷贝出来的地址和返回给调用函数的地址是不一样的
    cout << (int *)&p1 << endl;
    return p1;
}
void test03()
{
    Person p = doWork2();
    //函数执行完就释放掉了,而拷贝的值会返回回来,但是地址不一样
    cout << (int *)&p << endl;
}
void test01()
{
    // 1.将一个已经创建的对象属性拷贝到另一个对象
    Person p1(20);
    Person p2(p1);
    cout << "P2 age:" << p2.m_Age << endl;
}
int main()
{
    // test01();
    // test02();
    test03();
    return 0;
}

构造函数的调用规则

//构造函数的调用规则
//默认情况下如果创建一个类那么C++就会至少给一个类添加三个函数
// 1、默认构造函数(无参,函数体为空)
// 2、默认析构函数(无参,函数体为空)
// 3、默认拷贝构造函数,(对属性进行值拷贝)
#include <iostream>
using namespace std;
//如果用户定义有参构造函数,那么C++就不再提供默认无参构造,但是会提供默认拷贝构造
//如果用户定义拷贝构造函数,那么C++就不再提供其他构造函数
class Person
{
public:
    //自己写的高级构造函数会屏蔽编译器提供的低级构造函数
    //如果这里注释掉了而下面定义了有参构造函数那么C++就不再提供默认构造函数,那么创建对象时就会报错
    // Person() // Person的默认构造函数调用
    // {
    //     cout << "Person moren" << endl;
    // }
    //有参构造函数
    //当用户定义了一个拷贝构造函数,那么C++就不会再提供其他构造函数
    // Person(int age)
    // {
    //     cout << "Person+age" << endl;
    //     m_Age = age;
    // }
    //就算把拷贝构造函数注释掉,C++也会默认提供一个拷贝构造函数
    Person(const Person &p)
    {
        // Person的拷贝构造函数调用
        cout << "Person+copy" << endl;
        m_Age = p.m_Age;
    }
    ~Person() // Person的析构函数调用
    {
        cout << "end ~Person" << endl;
    }
    int m_Age;
};
// void test01()
// {
//     //一开始的时候Person p 会执行一次默认构造函数,然后在Person p2(p)的时候会执行一次拷贝构造函数
//     //然后输出p2的年龄,然后默认会先销毁Person p,然后再销毁Person p2,所以会执行两次析构函数的调用
//     Person p;
//     p.m_Age = 18;
//     Person p2(p);
//     //因为C++提供了默认拷贝函数,所以这里p2的值也是18,不会是乱码
//     cout << "p2.age:" << p2.m_Age << endl;
// }
void test02()
{
    //这里会报错,因为如果用户定义了有参构造函数那么C++就不再提供默认构造函数了,而创建一个对象必须
    //要调用默认构造函数
    // Person p;
    // Person p(28);
    //虽然C++不再提供默认构造函数,但是会提供拷贝构造函数,所以拷贝依旧是生效的
    // Person p2(p);
    // cout << "p2.age:" << p2.m_Age << endl;
    //当用户定义了一个拷贝构造函数,那么C++就不会再提供其他构造函数
    //所以这时再调用默认构造函数或者有参构造函数都会报错
    // Person p;
}
int main()
{
    // test01();
    test02();
    return 0;
}

深拷贝与浅拷贝

//深拷贝与浅拷贝
//浅拷贝:简单的赋值拷贝操作
//深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream>
using namespace std;
class Person
{
public:
    Person() //默认构造函数
    {
        cout << "Person moren" << endl;
    }
    //有参构造函数
    Person(int age, int height)
    {
        //这是浅拷贝,只是接收赋值操作
        m_Age = age;
        // new一个int,把身高创建在堆区,用一个指针去接收返回回来的数据
        //这就是深拷贝需要在函数执行完,在对象销毁前提前在析构函数里释放,避免造成内存泄漏
        m_Height = new int(height);
        cout << "Person youcan" << endl;
    }
    ~Person()
    {
        //当有参构造函数里使用深拷贝new了一个参数的时候可以在析构函数里释放掉
        //判断是否为空是因为,如果new成功了那么指针的指向就不为空
        if (m_Height != NULL)
        {
            //利用delete释放堆区的内存
            delete m_Height;
            //将指针置为空以避免野指针的出现
            m_Height = NULL;
        }
        cout << "end ~Person" << endl;
        //注意:如果只是像上面这样写会出现异常,因为构造和拷贝构造会分别执行一次,也就是执行两次
        //那么就会调用两次析构函数,那么就会重复释放两次内存,所以会出现异常
        //当第一次释放的时候释放的是p2的内存,所以p1释放的时候依旧会判断不为空,而p1和p2的指针是指向的
        //同一块内存,所以同一块内存会经过两次释放,所以会出现异常
    }
    //解决方法:自己实现拷贝构造函数,解决浅拷贝带来的问题
    Person(const Person &p)
    {
        cout << "Person copy" << endl;
        m_Age = p.m_Age;
        //编译器默认实现就是这行代码,这是浅拷贝的代码
        // m_Height = p.m_Height;
        //我们要解决浅拷贝带来的问题,那么就要用深拷贝在重新在堆区开辟一块内存
        //这样的话p2和p1指向的就不是同一块内存了,各自释放自己的就不会出现异常
        m_Height = new int(*p.m_Height);
    }
    //年龄
    int m_Age;
    //指针指向的是身高
    int *m_Height;
};
void test01()
{
    //调用有参构造函数,输出p1的年龄,当函数执行完时会释放p1,也就会执行析构函数
    Person p1(18, 160);
    cout << "p1 age:" << p1.m_Age << " ,p1 height:" << *p1.m_Height << endl;
    //调用默认拷贝构造函数,就算没写拷贝构造函数,编译器也会提供默认的拷贝构造函数
    Person p2(p1);
    cout << "p2 age:" << p2.m_Age << " ,p2 height:" << *p2.m_Height << endl;
}
int main()
{
    test01();
    return 0;
}

初始化列表

//初始化列表
#include <iostream>
using namespace std;
class Person
{
public:
    //传统的初始化操作
    // Person(int a, int b, int c)
    // {
    //     //在有参构造时就把所有的属性赋初值了
    //     m_A = a;
    //     m_B = b;
    //     m_C = c;
    // }
    //用列表初始化属性,在构造函数创建时就自动赋值
    Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c)
    {
    }
    //这样写也可以,即使没有参数也能赋初值
    // Person() : m_A(10), m_B(20), m_C(30)
    // {
    // }
    int m_A;
    int m_B;
    int m_C;
};
void test01()
{
    // Person p(10, 20, 30);
    Person p(30, 20, 10);
    // Person p;
    cout << "m_A=" << p.m_A << "\n"
         << "m_B=" << p.m_B << "\n"
         << "m_C=" << p.m_C << endl;
}
int main()
{
    test01();
    return 0;
}

成员变量

类对象作为类成员

//类对象作为类成员
//其实也就是一个对象里面嵌套另一个对象
#include <iostream>
#include <string>
using namespace std;
//手机类
class Phone
{
public:
    Phone(string pName)
    {
        cout << "Phone" << endl;
        m_PName = pName;
    }
    ~Phone()
    {
        cout << "end ~Phone" << endl;
    }
    string m_PName;
};
//人的类
class Person
{
public:
    //这里的赋值操作可以理解为:Phone m_Phone = pName 相当于隐式转换法
    Person(string name, string pName) : m_Name(name), m_Phone(pName)
    {
        cout << "Person" << endl;
    }
    ~Person()
    {
        cout << "end ~Person" << endl;
    }
    //姓名
    string m_Name;
    //手机
    Phone m_Phone;
};
//当其他类对象作为本类成员,构造时会先构造类对象,再构造自身,也就是先构造手机再构造人
//析构函数的执行顺序是和构造的顺序是相反的,也就是先析构人,再析构手机
void test01()
{
    Person p("zs", "appleMAX");
    cout << p.m_Name << " need: " << p.m_Phone.m_PName << endl;
}
int main()
{
    test01();
    return 0;
}

静态成员变量

//静态成员变量
#include <iostream>
using namespace std;
class Person
{
public:
    //非静态成员变量
    int m_A;
    //静态成员变量
    //所有对象都共享同一份数据
    //编译阶段就分配内存
    //类内声明,类外要进行初始化操作
    static int m_B;
    //静态成员变量也是有访问权限的
private:
    static int m_C;
};
//类外初始化
int Person::m_B = 100;
int Person::m_C = 200;
void test01()
{
    Person p;
    cout << p.m_B << endl;
    Person p2;
    p2.m_B = 200;
    //因为静态成员变量的数据是共享的,所以在这里p2将数据改成200,那么p再去访问时就变成了200
    cout << p.m_B << endl;
}
void test02()
{
    //静态成员变量 不属于某个对象上,所有对象都共享同一份数据
    //因此静态成员变量有两种访问方式:
    // 1、通过对象进行访问
    Person p;
    cout << p.m_B << endl;
    // 2、通过类名进行访问
    // using namespace std就是起这个作用,否则要写成std::cout << std:: endl
    cout << Person::m_B << endl;
    //这里报错是因为,m_C是在私有作用域的权限下,私有作用域是只有在类内才可以访问,类内不可以访问
    // cout << Person::m_C << endl;
}
int main()
{
    // test01();
    test02();
    return 0;
}

静态成员函数

//静态成员函数
#include <iostream>
using namespace std;
//所有对象共享一个函数
//静态成员函数只能访问静态成员变量
class Person
{
public:
    //静态成员函数
    static void func()
    {
        //静态的成员函数是可以访问静态的成员变量
        m_A = 100;
        //这里报错是因为m_B是非静态的成员变量,而静态成员函数只能访问静态成员变量
        //无法区分到底是哪个对象的m_B属性,因为静态成员函数所有对象都可以访问
        // m_B = 200;
        cout << "static void func" << endl;
    }
    //静态成员变量
    static int m_A;
    //非静态的成员变量
    int m_B;
    //静态成员函数也是有访问权限的
private:
    static void func2()
    {
        cout << "static void func2()" << endl;
    }
};
int Person::m_A = 0;
void test01()
{
    //静态成员函数有两种访问方式
    // 1、通过对象访问
    Person p;
    p.func();
    // 2、通过类名访问
    Person::func();
    //这里报错是因为私有作用域类内可以访问,类外不可以访问,类外访问不到私有的静态成员函数
    // Person::func2();
}
int main()
{
    test01();
    return 0;
}

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

//成员变量和成员函数的分开存储
#include <iostream>
using namespace std;
class Person
{
public:
    //只有非静态成员变量属于静态上,剩下的都不属于对象上
    //非静态的成员变量
    int m_A;
    //静态成员变量
    //静态成员变量不属于类的对象上,所以在分配内存时不会算上
    static int m_B;
    //非静态成员函数,也不属于类的对象上,在分配内存时也不会算上,除非根据this指针调用
    void func()
    {
    }
    //静态的成员函数,也不属于类的对象上,在分配内存时也不会算上
    //不管是静态的还是非静态的成员,共享的都是同一块内存
    static void func2()
    {
    }
};
//静态成员变量需要声明
int Person::m_B = 0;
void test01()
{
    Person p;
    //空对象的占用内存空间为: 1
    // C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
    //每个空对象都会有一个独一无二的内存地址
    cout << "size of p = " << sizeof(p) << endl;
}
void test02()
{
    Person p;
    //如果对象不是空的,那么就会根据对象里面的值来分配内存
    cout << "size of p = " << sizeof(p) << endl;
}
int main()
{
    // test01();
    test02();
    return 0;
}

this指针

this指针概念

// this指针概念
// this指针指向被调用的成员函数所属对象
// this指针是隐含每一个非静态成员函数内的一种指针
#include <iostream>
using namespace std;
// this指针指向的就是对象,通过*this解引用可以访问该对象本身
//静态函数没有this指针,因为静态函数不属于某个对象
class Person
{
public:
    Person(int age)
    {
        //这里赋值不会成功,因为变量名重名了,和类里面的成员变量age没有任何关联
        // age = age;
        //加入this指针就可以解决这个命名冲突的问题
        // this指针指向的是被调用的成员函数所属的对象,也就是说p1在调用这个有参构造函数时
        // this所指向的就是p1,谁调用的这个函数,this就指向谁
        this->age = age;
    }
    void PersonAddAge(Person &p)
    {
        //将p1的年龄加到p2身上
        this->age += p.age;
    }
    //如果想要返回p2对象的本体,那么就需要在函数名前加&符号,如果不加&符号返回的就是其他Person对象
    //注意如果这里不加&符号,那么每次函数都在拷贝,最后拷贝的匿名函数不管执行操作多少次都变为20
    //也就是以值的方式返回,每次都返回一个新的数据,跟原来的数据没有关联
    Person &PersonAddAge2(Person &p)
    {
        this->age += p.age;
        //通过指针解引用的方式就可以返回当前这个对象
        // this指向p2的指针,而*this指向的就是p2这个对象的本体
        return *this;
    }
    int age;
};
void test01()
{
    Person p1(18);
    cout << "p1 age:" << p1.age << endl;
}
void test02()
{
    Person p1(10);
    Person p2(10);
    //这里通过this指针指向p2,然后将p1的值传过去,再把p1的值加到p2身上
    // p2.PersonAddAge(p1);
    //链式编程,当p2调用了一次函数后,返回的*this是加完p1的值的p2,那么就可以再调用很多次
    //当第一次调用加10,第二次调用时返回的是p2加了10后的本体也就是20,再加10也就是20+10=30,
    //然后30再调用加10变成40
    p2.PersonAddAge2(p1).PersonAddAge2(p1).PersonAddAge2(p1);
    // cout这个函数本质也是像上面一样的链式编程思想,可以不停的在后面追加
    cout << "p2 age:" << p2.age << endl;
}
int main()
{
    // test01();
    test02();
    return 0;
}

空指针访问成员函数

//空指针访问成员函数
#include <iostream>
using namespace std;
class Person
{
public:
    //成员函数
    void showClassName()
    {
        cout << "this is Person class" << endl;
    }
    void showPersonAge()
    {
        //解决空指针报错的问题:
        //传入空指针就会报错,所以要判断this指向的是不是空指针,如果是则直接终止函数
        if (this == NULL)
        {
            return;
        }
        //其实这里输出m_Age可以看作为:this->m_Age
        cout << "age = " << m_Age << endl;
    }
    int m_Age;
};
void test01()
{
    //创建一个空指针对象,
    Person *p = NULL;
    //这里不会报错,因为空指针没有访问成员变量
    p->showClassName();
    //这里会报错因为在函数内部使用了this->m_Age,而this指向的不是对象而是一个空指针,所以会报错
    p->showPersonAge();
}
int main()
{
    test01();
    return 0;
}

常函数和常对象

//常函数和常对象
#include <iostream>
using namespace std;
class Person
{
public:
    Person()
    {
    }
    // this指针的本质是指针常量 指针的指向是不可以修改的 指针指向的值可以修改
    void showPerson() const
    {
        //这里会报错,因为在成员函数后面加上const后就变成了常函数,就不允许修改属性的值了
        //因为下面可以看作是 this->m_A=100,而在函数后面加上const后,也就是在this前面
        //加上了const,变成了const Person * const this, 值和指向都不可以修改了
        // m_A = 100;
        this->m_B = 100;
    }
    void func()
    {
        m_A = 100;
        cout << m_A << endl;
    }
    int m_A;
    //如果加了mutable这个关键字之后就可以把普通成员变量变成常函数里可修改的变量
    mutable int m_B;
    //注意:常对象可以修改静态变量的值
};
void test01()
{
    Person p;
    p.showPerson();
}
void test02()
{
    //在创建对象前加上const,就可以变为常对象
    const Person p;
    //这里会报错,因为常对象中的普通成员变量不可直接修改
    // p.m_A = 100;
    //这里不会报错,因为m_B加上了mutable关键字变成了可修改的变量,在常对象上也可以修改
    p.m_B = 200;
    //这里会报错,因为常对象只能调用常函数,不可以调用普通成员函数
    // p.func();
}
int main()
{
    test01();
    return 0;
}

友元

友元函数

//友元函数
#include <iostream>
#include <string>
using namespace std;
//建筑物类
class Building
{
    //当在一个全局函数声明前面加上friend关键字,那么这个全局函数就可以访问这个对象的
    //所有私有属性,就像是goodGay这个函数是Building这个对象的好朋友一样,也就是友元函数
    friend void goodGay(Building *building);

public:
    Building()
    {
        m_SittingRoom = "keting";
        m_BedRoom = "woshi";
    }
    //客厅
    string m_SittingRoom;

private:
    //卧室
    string m_BedRoom;
};
//全局函数
void goodGay(Building *building)
{
    //对象是指针型时访问用箭头,对象是变量型时访问用点。
    //类比数据结构中结构体变量和结构体指针访问成员的区别
    cout << "firend :" << building->m_SittingRoom << endl;
    //这里会报错,因为私有属性不可以访问
    // cout << "firend :" << building->m_BedRoom << endl;
    //当在函数声明前加上friend变成友元函数的时候,这个函数就可以访问对象里的私有属性了
    cout << "firend :" << building->m_BedRoom << endl;
}
void test01()
{
    Building building;
    goodGay(&building);
}
int main()
{
    test01();
    return 0;
}

友元类

//友元类
#include <iostream>
#include <string>
using namespace std;
//声明一个类,暂时还实现不了这个类又需要在其他类中使用到它的时候可以先声明
//其实就是类内声明,然后在类外实现函数
class Building;
class GoodGay
{
public:
    //也写一个公开的构造函数
    GoodGay();
    //普通成员函数,通过这个函数来访问Building中的属性
    void visit();
    Building *building;
};
class Building
{
    //这样就把GoodGay这个类作为了友元类
    friend class GoodGay;

public:
    //写一个公开的构造函数
    Building();

public:
    //客厅
    string m_SittingRoom;

private:
    //卧室
    string m_BedRoom;
};
//类外写成员函数
//为了体现封装的思想,一般把类声明写在头文件中,将类成员函数的实现写在c文件中
Building::Building()
{
    m_SittingRoom = "keting";
    m_BedRoom = "woshi";
}
GoodGay::GoodGay()
{
    //创建一个建筑物对象
    //用new来创建building的话,灵活度就增大了,开发者随时用完这个变量,随时就可以将这块内存释放。
    //如果不用new,单纯只用Building building ;则在  goodGay gg ;  之后building将被释放
    //之后再调用building里的属性就会访问到空指针然后出错,所以要用new开辟堆区数据
    //其实也可以理解为:new什么类型就返回什么类型的指针
    //用类里面本来就定义好的building指针来接收维护这块内存
    building = new Building;
}
void GoodGay::visit()
{
    //访问客厅
    cout << "friend:" << building->m_SittingRoom << endl;
    //访问卧室,这里如果不把GoodGay类作为友元就会报错
    //也就是在Building类中将GoodGay类前面写上friend关键字
    // cout << "friend" << building->m_BedRoom << endl;
    //加了友元后,可以正常访问了
    cout << "friend:" << building->m_BedRoom << endl;
}
//其实可以将上面理解为,在类内声明了一个构造函数之后,就可以在类外通过::的方式控制这个函数
//并在里面写具体实现的方法,这和在类内成员函数里写是一样的
void test01()
{
    //创建一个好朋友对象
    GoodGay gg;
    //调用函数访问Building类中的属性
    //一个类的指针指向另一个类,另一个类含有这个类的友元就能访问了
    gg.visit();
}
int main()
{
    test01();
    return 0;
}

成员函数做友元

//成员函数做友元
#include <iostream>
#include <string>
using namespace std;
class Building;
class GoodGay
{
public:
    GoodGay();
    //让visit函数访问Building中私有成员
    void visit();
    //让visit2函数不可以访问Building中的私有成员
    void visit2();
    Building *building;
};
class Building
{
    //将visit函数声明为Building的友元,也就是将GoodGay下的visit函数作为Building的友元
    //之后visit就可以访问Building中的私有属性
    friend void GoodGay::visit();

public:
    //声明公开构造函数
    Building();

public:
    //客厅
    string m_SittingRoom;

private:
    //卧室
    string m_BedRoom;
};
//类外实现成员函数
//给Building里的属性赋值
Building::Building()
{
    m_SittingRoom = "keting";
    m_BedRoom = "woshi";
}
GoodGay::GoodGay()
{
    //创建一个Building对象在堆区,用指针维护这个对象
    building = new Building;
}
void GoodGay::visit()
{
    //访问客厅
    cout << "visit:" << building->m_SittingRoom << endl;
    //访问卧室
    cout << "visit:" << building->m_BedRoom << endl;
}
void GoodGay::visit2()
{
    //访问客厅
    cout << "visit2:" << building->m_SittingRoom << endl;
    //访问卧室,注意:如果不声明为友元那么就不可以访问
    // cout << "visit2:" << building->m_BedRoom << endl;
}
void test01()
{
    GoodGay gg;
    gg.visit();
    gg.visit2();
}
int main()
{
    test01();
    return 0;
}

运算符重载

加号运算符重载

//加号运算符重载
#include <iostream>
using namespace std;
//注意:对于内置的数据类型的表达式的运算符是不可能改变的
class Person
{
public:
    // 1、成员函数重载+号
    // operator是C++内置的自带函数重载运算
    // Person operator+(Person &p)
    // {
    //     Person temp;
    //     //这里this指向的是调用改函数的对象
    //     temp.m_A = this->m_A + p.m_A;
    //     temp.m_B = this->m_B + p.m_B;
    //     //将运算后的对象返回
    //     return temp;
    // }
    int m_A;
    int m_B;
};
// 2、全局函数重载+号
Person operator+(Person &p1, Person &p2)
{
    Person temp;
    temp.m_A = p1.m_A + p2.m_A;
    temp.m_B = p1.m_B + p2.m_B;
    return temp;
}
//函数重载的版本
Person operator+(Person &p1, int num)
{
    Person temp;
    temp.m_A = p1.m_A + num;
    temp.m_B = p1.m_B + num;
    return temp;
}
void test01()
{
    Person p1;
    p1.m_A = 10;
    p1.m_B = 10;
    Person p2;
    p2.m_A = 10;
    p2.m_B = 10;
    //这里会报错,因为还没有写运算符重载
    // Person p3 = p1 + p2;
    //当定义了函数重载之后就不会报错了
    // Person p3 = p1 + p2;
    //上面其实是下面的简化,成员函数重载的本质其实是:
    /// Person p3 = p1.operator+(p2);
    //全局函数重载的本质其实是:
    Person p3 = operator+(p1, p2);
    //运算符重载 也可以发生函数重载
    // Person类型+int类型
    Person p4 = p1 + 100;
    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();
    return 0;
}

左移运算符重载

//左移运算符重载
#include <iostream>
using namespace std;
//重载左移运算符配合友元可以实现输出自定义数据类型
class Person
{
    //将全局函数重载变成友元访问私有属性
    friend ostream &operator<<(ostream &out, Person &p);

public:
    Person(int a, int b)
    {
        m_A = a;
        m_B = b;
    }

private:
    //利用成员函数重载左移运算符
    //一般不会利用成员函数重载左移运算符,因为调用时会变成p.operator<<(cout)或者p<<cout
    //无法实现cout在左侧
    // void operator<<(cout)
    // {
    // }
    int m_A;
    int m_B;
};
//只能利用全局函数重载左移运算符
//本质其实是 operator<<(cout,p) 简化 cout<<p
// cout其实是ostream的对象,且这个对象全局只能有一个
// cout可以起别名为out,因为引用的本身就是起别名,因为cout是实参,而out是形参
// out和cout指向的都是同一块内存空间
ostream &operator<<(ostream &out, Person &p)
{
    out << "m_A= " << p.m_A << " m_B= " << p.m_B << endl;
    return out;
}
void test01()
{
    Person p(10, 10);
    //当运行到这句时,系统会自动去调用与cout<<p相关的函数重载左移运算符
    //只有在函数重载左移运算符里返回了cout后才能够继续链式调用<<endl;
    cout << p << "Hello,World" << endl;
}
int main()
{
    test01();
    return 0;
}

递增运算符重载

//递增运算符重载
#include <iostream>
using namespace std;
//自定义整形
class MyInteger
{
    //重载左移运算符做友元,这样的话左移运算函数就可以访问私有属性
    friend ostream &operator<<(ostream &cout, MyInteger myint);

public:
    //在构造函数中初始化私有成员变量m_Num为0
    MyInteger()
    {
        m_Num = 0;
    }
    //重载前置++运算符
    //如果不返回加了&的对象,就会一直对新的对象使用自增,那么就无法一直对原来的对象使用自增
    //无法实现链式编程
    MyInteger &operator++()
    {
        //先进行++运算
        m_Num++;
        //再将自身做返回
        return *this;
    }
    //重载后置++运算符
    // int 代表占位参数,可以用于区分前置和后置递增,也就是通过占位参数不同来区分函数重载
    //这里只返回值不返回引用&的原因是temp是一个局部的对象,局部的对象在函数执行完后就释放掉了
    //如果返回引用的话就会报错,所以后置递增一定是返回值不是引用
    MyInteger operator++(int)
    {
        //先记录当时的结果
        //*this指向的是调用这个函数的对象本身的值
        MyInteger temp = *this;
        //后递增
        m_Num++;
        //最后将记录结果做返回
        return temp;
    }

private:
    int m_Num;
};
//重载左移运算符
ostream &operator<<(ostream &cout, MyInteger myint)
{
    cout << myint.m_Num;
    return cout;
}
void test01()
{
    MyInteger myint;
    //这里如果不写重载左移运算符就会报错
    //其实可以理解为当程序执行到这句的时候就会找和<<有关的函数,然后控制这个函数,
    //执行函数里面的内容作为输出,因为operator可以省略,所以才能够用<<代替
    cout << ++myint << endl;
    cout << myint << endl;
}
void test02()
{
    MyInteger myint;
    cout << myint++ << endl;
    cout << myint << endl;
}
int main()
{
    // test01();
    test02();
    //普通的链式自增实现
    int a;
    // cout << ++(++a) << endl;
    //注意:c++本身不支持链式的后置递增
    return 0;
}

赋值运算符重载

//赋值运算符重载
#include <iostream>
using namespace std;
// C++编译器会至少给一个类添加4个函数
// 1、默认构造函数(无参,函数体为空)
// 2、默认析构函数(无参,函数体为空)
// 3、默认拷贝构造函数,对属性进行值拷贝
// 4、赋值运算符 operator=,对属性进行值拷贝
//注意,对象与对象之间做赋值操作的时候就会用到它默认提供的赋值运算符操作
class Person
{
public:
    //有参构造
    Person(int age)
    {
        //创建一个数据在堆区,并且用指针来维护这个数据
        m_Age = new int(age);
    }
    ~Person()
    {
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
    }
    //如果不自己定义拷贝构造函数就会出现内存重复释放的问题
    //释放过后就没有操作权限,容易引起非法操作,第二次释放就是危险操作
    //另一种办法就是重新定义重载赋值运算符操作
    // 1.注意:这里的重载函数的参数一定要以引用或者指针的形式传入!!
    // 2.不然在传入的时候进行了一次拷贝将赋值右边p2的值传入的时候临时变量记录的p2的属性m_Age的地址
    // 3.而出了赋值运算符重载函数会进行一次析构 这时p2的属性new出来的空间已经被释放了
    // 4.最后结束调用虽然你深拷贝了但是程序还是会崩
    Person &operator=(Person &p)
    {
        //编译器默认提供的是浅拷贝代码
        // m_Age = p.m_Age;
        //应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
        if (m_Age != NULL)
        {
            //必须要释放,因为此时是赋值,不是之前的拷贝构造了,所以只有清空了指针的内容
            //才可以赋值新的内容进去
            delete m_Age;
            m_Age = NULL;
        }
        //自己写深拷贝代码
        //通过解引用解出来对象原本传入的那个值(18)
        m_Age = new int(*p.m_Age);
        //如果要实现链式赋值的话就要返回这个对象的本身
        return *this;
        //实际上还是深拷贝,只不过之前的深拷贝是在拷贝构造函数中,这次是在赋值运算符重载函数中
    }
    //年龄的指针
    int *m_Age;
};
void test01()
{
    Person p1(18);
    Person p2(20);
    Person p3(30);
    //赋值运算的操作
    p3 = p2 = p1;
    //通过指针解引用的方式拿到堆区的数据
    cout << "p1 age:" << *p1.m_Age << endl;
    cout << "p2 age:" << *p2.m_Age << endl;
    cout << "p3 age:" << *p3.m_Age << endl;
}
int main()
{
    test01();
    //普通的链式赋值操作
    int a = 10;
    int b = 20;
    int c = 30;
    //赋值运算符的运算顺序是从右往左。
    c = b = a;
    // cout << "a=" << a << "\nb=" << b << "\nc=" << c << endl;
    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)
    {
        //如果传进来的m_Name==原本的m_Name,并且年龄也相等,那么这两个对象就是相等的
        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();
    return 0;
}

函数调用运算符重载

//函数调用运算符重载
#include <iostream>
#include <string>
using namespace std;
//打印输出的类
class MyPrint
{
public:
    //重载函数调用运算符,第一个小括号代表重载函数符号,第二个小括号代表形参列表
    //也可以将返回值返回MyPrint&(*this),可以链式调用myPrint("1")("2")
    void operator()(string test)
    {
        cout << test << endl;
    }
};
void MyPrint02(string test)
{
    cout << test << endl;
}
void test01()
{
    MyPrint myPrint;
    //这就是函数运算符重载,调用的是类里面的重载函数,也可以称为仿函数
    myPrint("Hello,World");
    //这是普通输出函数的写法
    MyPrint02("Hello,World");
}
//加法类
class MyAdd
{
public:
    //函数重载本身非常灵活,可以进行各种运算
    int operator()(int num1, int num2)
    {
        return num1 + num2;
    }
};
void test02()
{
    MyAdd myadd;
    int ret = myadd(100, 100);
    cout << "ret = " << ret << endl;
    //匿名函数对象,不需要创建对象也能调用类里面的函数重载,注意名字要与类名相同
    //其实也就是使用了函数里面的重载运算符,匿名对象运行完就会立即释放
    cout << MyAdd()(100, 100) << endl;
}
int main()
{
    test01();
    test02();
    return 0;
}

继承

继承的概念

//继承
#include <iostream>
#include <string>
using namespace std;
//编写一个java类
// class Java
// {
// public:
//     void header()
//     {
//         cout << "home,public,sogin,zc" << endl;
//     }
//     void footer()
//     {
//         cout << "help,bobom,dt" << endl;
//     }
//     void left()
//     {
//         cout << "Java,python,C++,...list" << endl;
//     }
//     void content()
//     {
//         cout << "Java video" << endl;
//     }
// };
// // python
// class Python
// {
// public:
//     void header()
//     {
//         cout << "home,public,sogin,zc" << endl;
//     }
//     void footer()
//     {
//         cout << "help,bobom,dt" << endl;
//     }
//     void left()
//     {
//         cout << "Java,python,C++,...list" << endl;
//     }
//     void content()
//     {
//         cout << "Python video" << endl;
//     }
// };
// // C++
// class CPP
// {
// public:
//     void header()
//     {
//         cout << "home,public,sogin,zc" << endl;
//     }
//     void footer()
//     {
//         cout << "help,bobom,dt" << endl;
//     }
//     void left()
//     {
//         cout << "Java,python,C++,...list" << endl;
//     }
//     void content()
//     {
//         cout << "C++ video" << endl;
//     }
// };
//继承实现
//公共页面类
class BasePage
{
public:
    void header()
    {
        cout << "home,public,sogin,zc" << endl;
    }
    void footer()
    {
        cout << "help,bobom,dt" << endl;
    }
    void left()
    {
        cout << "Java,python,C++,...list" << endl;
    }
};
//继承的好处:减少重复代码
//语法:class子类:继承方式 父类
//子类也称为派生类
//父类也称为基类
//继承Java页面
class Java : public BasePage
{
public:
    void content()
    {
        cout << "Java video" << endl;
    }
};
//继承python页面
class Python : public BasePage
{
public:
    void content()
    {
        cout << "Python video" << endl;
    }
};
//继承c++页面
class CPP : public BasePage
{
public:
    void content()
    {
        cout << "CPP video" << endl;
    }
};
void test01()
{
    cout << "java domdown" << endl;
    Java ja;
    ja.header();
    ja.footer();
    ja.left();
    ja.content();
    cout << "------------------" << endl;
    cout << "python domdown" << endl;
    Python py;
    py.header();
    py.footer();
    py.left();
    py.content();
    cout << "------------------" << endl;
    cout << "c++ domdown" << endl;
    CPP cpp;
    cpp.header();
    cpp.footer();
    cpp.left();
    cpp.content();
}
int main()
{
    test01();
    return 0;
}

继承的实现方式

//继承的实现方式
//注意:如果一个父类把属性设置为私密,那么无论什么子类都访问不到它(友元可以访问)
//如果父类中有保护权限protected和公开权限(public),那么保护权限的子类继承过来会把公开权限也变成保护权限
//如果父类中有保护权限和公开权限,那么私有权限的子类继承过来会把公开权限和保护权限都变成私有权限
// protected与private的区别在于继承方面,儿子可以访问protected不可以访问private
#include <iostream>
using namespace std;
//公共继承
//父类
class Base1
{
public:
    int m_A;

protected:
    int m_B;

private:
    int m_C;
};
//子类1,公共继承
class Son1 : public Base1
{
public:
    void func()
    {
        //父类中的公共成员变量,到子类中依然是公共权限
        m_A = 10;
        //父类中的保护权限成员,到子类中依然是保护权限
        m_B = 10;
        //这里会报错,因为父类中的私有权限成员,不可以被子类访问
        // m_C = 10;
    }
    //函数外只能定义全局变量或者对象 ,而不能执行语句及调用函数, 这句话一定要谨记
};
//父类2,保护继承
class Base2
{
public:
    int m_A;

protected:
    int m_B;

private:
    int m_C;
};
//子类2,保护继承
class Son2 : protected Base2
{
public:
    //如果没有void func() 成员函数,
    //编译器会自动认为m_A,m_B是子类成员,就继承不过来
    void func()
    {
        //父类中公共成员,到子类在变为保护权限
        m_A = 100;
        //父类中保护成员,到子类中变为保护权限
        m_B = 100;
        //这里会报错,因为父类中的私有权限成员,不可以被子类访问
        // m_C = 100;
    }
};
class Base3
{
public:
    int m_A;

protected:
    int m_B;

private:
    int m_C;
};
//子类3,私有继承
class Son3 : private Base3
{
public:
    void func()
    {
        //父类中公共和保护的成员到子类中会变为私有成员
        m_A = 100;
        m_B = 100;
        ////这里会报错,因为父类中的私有权限成员,不可以被子类访问
        // m_C = 100;
    }
};
//孙子类,孙子继承私有的子类
class GrandSon3 : public Son3
{
public:
    void func()
    {
        //这里依旧会报错,到了子类中属性变为私有,即使是孙子类也访问不到
        // m_A = 1000;
        // m_B = 1000;
    }
};
void test03()
{
    Son3 s1;
    //这里会报错,因为到子类中变为了私有的成员,类外访问不到
    // s1.m_A = 200;
}
void test02()
{
    Son2 s1;
    //这里不可以访问,因为到子类中m_A变为了保护权限,类外访问不到
    // s1.m_A = 1000;
    //这里不可以访问,因为到子类中m_B变为了保护权限,类外访问不到
    // s1.m_B = 1000;
}
void test01()
{
    Son1 s1;
    //这里可以访问是因为,m_A到子类后是公共权限,公共权限类内和类外都可以访问
    s1.m_A = 100;
    //这里会报错是因为m_B到子类后是保护权限,保护权限类内可以访问,类外不可以访问
    // s1.m_B = 100;
}
int main()
{
    test01();
    return 0;
}

继承当中的对象模型

//继承当中的对象模型
#include <iostream>
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()
{
    // 16 子类会继承父类的所有成员的属性的内存,并且加上自身的
    //父类中所有的非静态成员属性都会被子类继承下去
    //父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实是被继承下去了
    cout << "size of Son = " << sizeof(Son) << endl;
}
int main()
{
    test01();
    return 0;
}

继承中的构造和析构顺序

//继承中的构造和析构顺序
#include <iostream>
#include <string>
using namespace std;
//父类
class Base
{
public:
    //构造函数
    Base()
    {
        cout << "Base" << endl;
    }
    //析构函数
    ~Base()
    {
        cout << "end Base" << endl;
    }
};
//子类,通过公共方式继承父类
class Son : public Base
{
public:
    //构造函数
    Son()
    {
        cout << "Son" << endl;
    }
    //析构函数
    ~Son()
    {
        cout << "end Son" << endl;
    }
};
void test01()
{
    //当对象创建时会自动调用构造和析构函数
    // Base b;
    //当创建一个子类对象时会先创建一个父类对象,同时调用父类的构造函数
    //当函数要销毁时是先销毁子类的析构函数,再销毁父类的析构函数,析构的顺序和构造的顺序是相反的
    Son s;
}
int main()
{
    test01();
    return 0;
}

继承中同名成员的处理方式

//继承中同名成员的处理方式
//访问子类的同名成员,直接访问即可
//访问父类的同名成员,需要加作用域
#include <iostream>
using namespace std;
//父类
class Base
{
public:
    Base()
    {
        //在构造函数中给m_A属性赋初值
        m_A = 100;
    }
    void func()
    {
        cout << "Base-func()" << endl;
    }
    //函数重载
    //函数重载的条件是要在同一作用域下
    void func(int a)
    {
        cout << "Base-func(int a)" << endl;
    }
    int m_A;
};
//子类
class Son : public Base
{
public:
    //在子类构造函数中给m_A属性赋初值
    Son()
    {
        m_A = 200;
    }
    //同名函数
    void func()
    {
        cout << "Son-func()" << endl;
    }
    //同名成员
    int m_A;
};
void test01()
{
    Son s;
    //此时输出是200,因为是子类调用自己的函数
    cout << "m_A=" << s.m_A << endl;
    //如果要通过子类对象访问父类中的同名成员,需要加作用域
    cout << "Base = " << s.Base::m_A << endl;
}
//同名成员函数的处理方式
void test02()
{
    Son s;
    //如果子类中没有写同名的成员函数,那么子类访问函数时就可以直接访问父类的函数
    //否则调用的就是子类中的函数,当直接调用会优先访问子类的成员
    s.func();
    //通过加作用域的方式调用父类中的函数
    s.Base::func();
    //包括调用父类中的函数重载也是一样的需要加作用域
    //当子类出现和父类一样的同名成员时,子类中的成员会隐藏掉父类中所有的成员 函数
    s.Base::func(10);
}
int main()
{
    // test01();
    test02();
    return 0;
}

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

//继承中的同名静态成员处理方式
#include <iostream>
using namespace std;
//父类
class Base
{
public:
    //将变量变成静态成员变量
    //在对象创建前就分配内存,所有对象共享一份数据,类内声明,类外初始化
    static int m_A;
    //定义一个静态成员函数
    static void func()
    {
        cout << "Base - static void func()" << endl;
    }
};
//类外初始化
int Base::m_A = 100;
//子类
class Son : public Base
{
public:
    //同名静态成员属性
    static int m_A;
    //同名的静态成员函数
    static void func()
    {
        cout << "Son - static void func()" << endl;
    }
};
//类外初始化
int Son::m_A = 200;
//同名静态成员属性
void test01()
{
    // 1:通过对象访问
    Son s;
    //访问子类下的静态成员变量
    cout << "m_A = " << s.m_A << endl;
    //访问父类下的静态成员变量
    cout << "Base m_A = " << s.Base::m_A << endl;
    // 2:通过类名访问
    cout << "Son m_A = " << Son::m_A << endl;
    //通过类名的方式访问子类作用域下的父类的静态成员属性
    cout << "Base m_A = " << Son::Base::m_A << endl;
}
void test02()
{
    Son s;
    // 1.通过对象的方式访问
    //调用子类的函数
    s.func();
    //调用父类的函数
    s.Base::func();
    // 2.通过类名的方式访问
    Son::func();
    //静态成员函数只能访问静态成员变量,但因为是静态,再全局区,
    //所以可以通过类来访问,而非静态是栈区,运行完就直接释放
    Son::Base::func();
}
//同名静态成员函数

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

多继承语法

//多继承语法
#include <iostream>
using namespace std;
//第一个父类
class Base1
{
public:
    //构造函数
    Base1()
    {
        m_A = 100;
    }
    int m_A;
};
//第二个父类
class Base2
{
public:
    Base2()
    {
        m_A = 200;
    }
    //同名变量
    int m_A;
};
//子类,多继承,先继承Base1,再继承Base2
class Son : public Base1, public Base2
{
public:
    Son()
    {
        m_C = 300;
        m_D = 400;
    }
    int m_C;
    int m_D;
};
void test01()
{
    Son s;
    // 16个字节,因为继承了两个父亲的4个属性
    cout << "sizeof Son =  " << sizeof(s) << endl;
    //如果这里的父类里有两个同名的成员,需要加作用域区分,不然就会报错
    cout << s.Base1::m_A << endl;
    cout << s.Base2::m_A << endl;
}
int main()
{
    test01();
    return 0;
}

菱形继承

//菱形继承,也叫钻石继承
#include <iostream>
using namespace std;
//动物类
class Animal
{
public:
    int m_Age;
};
//羊类,继承了动物类,利用虚继承,解决菱形继承的问题
//加上关键字virtual就会使继承变成虚继承,加了虚继承之后原本的最大的父类就变成了虚基类
class Sheep : virtual public Animal
{
};
//驼类,继承了动物类,使其变成虚继承
//相当于开辟一个父类的变量名的类型空间存储,而子类继承的不是拷贝父类的内存空间,
//而是直接继承一个指针指向那块数据的存储空间
class Tuo : virtual public Animal
{
};
//羊驼类,既继承了羊类又继承了驼类
class SheepTuo : public Sheep, public Tuo
{
};
void test01()
{
    SheepTuo st;
    //这里会报错,因为这样的菱形继承会使SheepTuo类继承两份m_Age数据,系统不知道给哪一个赋值
    //出现这种情况可以用虚继承,也可以用作用域区分
    // st.m_Age = 18;
    st.Sheep::m_Age = 18;
    st.Tuo::m_Age = 28;
    //当两个父类有相同的数据,需要加以作用域区分
    cout << "Sheep::m_Age = " << st.Sheep::m_Age << endl;
    cout << "Tuo::m_Age = " << st.Tuo::m_Age << endl;
    //如果要拿到SheepTuo的年龄就要利用虚继承
    //当加了虚继承之后再直接访问SheepTuo的年龄就不会出现二义性了
    //因为加了虚继承之后数据就只有一份,不管怎么继承都只有一个数据可以输出
    //其实也可以理解为加了一个指针指向最开始的那个变量,当有赋值时全部的变量就都改变了
    //如上面一开始赋值为18,后面又重新赋值为28,因为指针赋值的关系把所有的数据都变成了28
    cout << "st.m_Age = " << st.m_Age << endl;
}
int main()
{
    test01();
    return 0;
}

多态

多态概念

//多态
#include <iostream>
using namespace std;
//动态多态的满足条件:
// 1、重写子类的虚函数
// 2、有继承关系
// 3、子类重写父类的虚函数

//动态多态的使用:
//父类的指针或者引用 指向子类对象
//动物类
class Animal
{
public:
    //虚函数
    virtual void speak()
    {
        cout << "Animal speak" << endl;
    }
};
//猫类
class Cat : public Animal
{
public:
    //同名的成员函数
    //重写要满足: 函数返回值类型 函数名 参数列表 完全相同
    void speak()
    {
        cout << "cat speak" << endl;
    }
};
//狗类
//必须有继承关系,才能保证传入的的类被引用。
//一个对父类执行操作的函数一定也可以对子类操作(子类继承了父类,所以父类有的子类肯定也有)
class Dog : public Animal
{
public:
    //同名的成员函数
    // c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。
    //因此,在子类重新声明该虚函数时,可以加virtual,也可以不加virtual
    void speak()
    {
        cout << "Dog speak" << endl;
    }
};
//执行说话的函数
//父类引用接收子类对象
//父子之间允许类型转换
//因为父类与子类的函数同名,地址早绑定,在编译阶段就确定了函数地址,
//所以不管传入的子类是什么,都会执行父类的函数
//如果想执行子类的同名函数那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,也就是地址晚绑定
//其实也就是在父类的同名函数前加上virtual变成虚函数,加了之后就可以实现地址晚绑定
//注意:只能传入父类的指针/引用 才能实现多态! 值传递是不可以滴
void doSpeak(Animal &animal) // Animal & animal = cat;
{
    animal.speak();
}
void test01()
{
    //创建一个猫类
    Cat cat;
    //把猫传进去
    doSpeak(cat);
    //创建一个狗类
    Dog dog;
    //把狗传进去
    doSpeak(dog);
}
void test02()
{
    //这里输出的4个字节,因为加了virtual虚函数指针
    //其实可以理解多态为:当子类继承父类时,子类会拷贝父类身上的所有数据和属性
    //那么当子类重写父类的函数时会被继承后的父类的函数给替换
    //所以访问子类的同名函数时会调用父类函数
    //解决办法就是加了虚指针virtual后子类重写的就是父类的虚函数
    //子类中的虚函数表 内部 会替换成 子类的虚函数地址,通过虚函数地址就可以找到子类的函数
    //就是说,子类从父类中继承了虚函数指针和虚函数表,当子类中发生重写时,虚函数表会发生变化
    cout << "sizeof Animal = " << sizeof(Animal) << endl;
}
int main()
{
    test01();
    test02();
    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;
        }
        else if (oper == "/")
        {
            return (m_Num1 * 1.0) / (m_Num2 * 1.0);
        }
    }
    //操作数1
    int m_Num1;
    //操作数2
    int m_Num2;
};
void test01()
{
    //创建计算器对象
    Calculator c;
    c.m_Num1 = 10;
    c.m_Num2 = 10;
    cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
    cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
    cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
    cout << c.m_Num1 << " / " << c.m_Num2 << " = " << c.getResult("/") << endl;
}
//利用多态来实现计算器
//实现计算器抽象类
class AbstractCalculator
{
public:
    //要想实现继承,父类中需要有一个虚函数,通过子类去继承重写
    virtual int getResult()
    {
        return 0;
    }
    int m_Num1;
    int m_Num2;
};
//设计一个加法的计算器类,继承上面的抽象类
class AddCalculator : public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 + m_Num2;
    }
};
//减法计算器类
class SubCalculator : public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 - m_Num2;
    }
};
//乘法计算器类
class MulCalculator : public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 * m_Num2;
    }
};
//除法计算器类
class ChuCalculator : public AbstractCalculator
{
public:
    int getResult()
    {
        return (m_Num1 * 1.0) / (m_Num2 * 1.0);
    }
};
void test02()
{
    //多态使用条件
    //父类指针或者引用指向子类对象
    //加法运算,可以理解为new(创建了)一个加法的对象,用父类的指针去指向这个对象
    AbstractCalculator *abc = new AddCalculator;
    abc->m_Num1 = 100;
    abc->m_Num2 = 100;
    //由于创建的是加法运算器,所以调用的肯定是加法对象里的函数,不需要传任何参数
    cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
    //用完后记得销毁
    delete abc;
    //创建一个减法运算对象
    //由于销毁的是指针指向的内存空间,但是指针本身并没有被销毁,而指针本身就是一个父类的对象指针
    //所以可以直接指向下一个new(创建)出来的对象,不需要重新定义
    abc = new SubCalculator;
    abc->m_Num1 = 100;
    abc->m_Num2 = 100;
    //由于创建的是减法运算器,所以调用的肯定是减法对象里的函数,不需要传任何参数
    cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
    delete abc;
    //乘法运算
    abc = new MulCalculator;
    abc->m_Num1 = 100;
    abc->m_Num2 = 100;
    //由于创建的是乘法运算器,所以调用的肯定是乘法对象里的函数,不需要传任何参数
    cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
    delete abc;
    //除法运算
    abc = new ChuCalculator;
    abc->m_Num1 = 100;
    abc->m_Num2 = 100;
    //由于创建的是除法运算器,所以调用的肯定是乘法对象里的函数,不需要传任何参数
    cout << abc->m_Num1 << " / " << abc->m_Num2 << " = " << abc->getResult() << endl;
    delete abc;
}
int main()
{
    // test01();
    test02();
    return 0;
}

纯虚函数

//纯虚函数
//写纯虚函数的目的就是让子类必须重写父类的虚函数,否则就不允许创建对象
#include <iostream>
using namespace std;
//这个抽象类的意义,类似于开发接口,每个人写的子类都必须遵从父类纯虚函数的定义进行实现
class Base
{
public:
    //纯虚函数
    //只要有一个纯虚函数,这个类就称为抽象类
    //特点:无法实例化对象
    //抽象类中的子类,必须要重写父类中的纯虚函数,否则也属于抽象类
    virtual void func() = 0;
};
class Son : public Base
{
public:
    //重写虚函数
    virtual void func()
    {
        cout << "func" << endl;
    };
};
void test01()
{
    //这里会报错,因为抽象类是无法实例化对象的
    // Base b;
    // new Base;
    //这里也会报错,因为子类如果不重写抽象类的纯虚函数则也无法创建实例化对象
    // Son s;
    Son s2;
    //拥有纯虚函数的类只能是父类(无法被实例化),且子类必须实现这个函数,否则还是抽象类
    //创建一个父类的指针指向子类的对象
    Base *base = new Son;
    //调用子类的函数
    base->func();
    //使用多态后想要调用不同对象的函数就new一个再调用
}
int main()
{
    test01();
    return 0;
}

多态案例-制作饮品

//多态案例-制作饮品
#include <iostream>
using namespace std;
//制作类
//父类主要用来存放虚函数模板,让子类去重写这些虚函数
class AbstractDrinking
{
public:
    //煮水
    //当一个函数不需要写具体实现时可以让其等于0
    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 << "Boil water" << endl;
    }
    //冲泡
    virtual void Brew()
    {
        cout << "Brew coffee" << endl;
    }
    //倒入杯中
    virtual void PourInCup()
    {
        cout << "PourInCup" << endl;
    }
    //加入辅料
    virtual void PutSomething()
    {
        cout << "with sugin and with milk" << endl;
    }
};
//制作茶叶类
class Tea : public AbstractDrinking
{
public:
    //重写父类的虚函数
    //煮水
    virtual void Boil()
    {
        cout << "Boil hot water" << endl;
    }
    //冲泡
    virtual void Brew()
    {
        cout << "Brew tea" << endl;
    }
    //倒入杯中
    virtual void PourInCup()
    {
        cout << "PourInCup" << endl;
    }
    //加入辅料
    virtual void PutSomething()
    {
        cout << "with sugin" << endl;
    }
};
//制作函数,传入的参数是父类的指针
void doWork(AbstractDrinking *abs)
{
    //通过父类的指针去调用里面的接口(自己写的函数)
    ///当父类通过指针去调用里面的接口时,会根据函数名去子类里找对应的函数(因为子类重写了父类的函数)
    //又因为传入的是子类的实参,所以会根据不同的子类的参数去调用不同的子类的函数
    abs->makeDrink();
    //因为子类是从堆区开辟的实参传入,所以在用完了要手动释放掉
    //因为是在函数里调用,形参是不同的,所以不管传入的子类是什么都能根据指针释放
    delete abs;
}
void test01()
{
    //制作咖啡
    //这里传入的参数是子类的实参,而接受的参数是父类的形参
    //等价于:AbstractDrinking *abs = new Coffee
    //用父类的指针来指向子类的对象
    doWork(new Coffee);
    cout << "-----------------" << endl;
    //制作茶叶
    //这就是多态的好处,一个函数可以接受不同的对象,不同的对象操作也是不同的
    doWork(new Tea);
}
int main()
{
    test01();
    return 0;
}

虚析构和纯虚析构

//虚析构和纯虚析构
#include <iostream>
#include <string>
using namespace std;
//动物类 父类,用来存放纯虚函数模板
class Animal
{
public:
    //当子类创建对象时会先调用父类构造函数
    Animal()
    {
        cout << "Animal open" << endl;
    }
    virtual void speak() = 0;
    //当子类对象销毁前会先调用子类的析构函数,后再调用父类的析构函数
    //在前面加上virtual就变成了虚析构函数
    //父类虚析构函数就可以解决释放父类指针时只调用父类的析构函数的问题
    // virtual ~Animal()
    // {
    //     cout << "~Animal end" << endl;
    // }
    //纯虚析构,也能解决释放父类指针带来的子类对象释放不干净问题
    //注意:如果子类中没有堆区的数据,可以不写虚析构或纯虚析构
    virtual ~Animal() = 0;
};
//注意,如果父类里写了纯虚析构函数,那么类外就必须要声明,也可以写具体实现
//只要有纯虚析构函数那么这个类就是抽象类
Animal::~Animal()
{
    cout << "~Animal end" << endl;
}
//继承父类的纯虚函数
class Cat : public Animal
{
public:
    //猫的构造函数
    Cat(string name)
    {
        cout << "Cat open" << endl;
        //从堆区创建一块内存用来存放传入的名字
        m_Name = new string(name);
    }
    //当继承了父类之后,父类中的纯虚函数必须要重写
    virtual void speak()
    {
        //从通过指针从堆区拿到存放名字的那块内存
        cout << *m_Name << " Cat at speak" << endl;
    }
    //猫的虚析构函数
    //也就是重写了父类的虚析构函数,这样的话在释放父类指针时就会先调用子类的虚析构函数
    virtual ~Cat()
    {
        //因为每次创建对象都从堆区拿了一块内存去存放名字,所以在析构函数里要释放掉那块内存
        if (m_Name != NULL)
        {
            cout << "~Cat end" << endl;
            delete m_Name;
            //并且指针也要置为空
            m_Name = NULL;
        }
    }
    //创建一个字符串指针用来指向存放“名字”的堆区的那块内存
    string *m_Name;
};
void test01()
{
    //用父类的指针指向子类对象
    //如果类里有构造函数可以在创建的时候就传入构造函数所需要的数据
    Animal *animal = new Cat("Tom");
    //父类调用自己的纯虚函数,其实也是在调用子类重写后的同名虚函数
    animal->speak();
    //注意:释放父类指针时父类指针在析构的时候,不会调用子类中析构函数
    //导致子类无法释放堆区的内存,如果有堆区的数据或堆区的属性会出现内存泄漏
    delete animal;
}
int main()
{
    test01();
    return 0;
}

多态案例-电脑组装

//多态案例-电脑组装
#include <iostream>
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()
    {
        //用cpu做计算的操作,其实也就是调用自己原本对象里内置的函数
        //因为m_cpu是通过其他类创建出来的,所以可以调用不属于电脑类的函数
        m_cpu->calculate();
        //用m_vc做显示操作
        m_vc->display();
        //用m_mem做存储操作
        m_mem->storage();
    }
    // delete对象会自动调用类的析构函数
    //提供析构函数,释放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的零件指针
    CPU *m_cpu;
    //显卡的零件指针
    VideoCard *m_vc;
    //内存条的零件指针
    Memory *m_mem;
};
//具体厂商
// Intel类,继承CPU类
class IntelCPU : public CPU
{
public:
    //重写父类中的纯虚函数
    virtual void calculate()
    {
        cout << "this is Intel CPU" << endl;
    }
};
// Intel类,继承VideCard(显卡)类
class IntelVideCard : public VideoCard
{
public:
    //重写父类中的纯虚函数
    virtual void display()
    {
        cout << "this is Intel VideCard display" << endl;
    }
};
// Intel类,继承Memory(内存条)类
class IntelMemory : public Memory
{
public:
    //重写父类中的纯虚函数
    virtual void storage()
    {
        cout << "this is Intel storage" << endl;
    }
};
// Lenovo(联想)厂商
//继承CPU
class LenovoCPU : public CPU
{
public:
    //重写父类中的纯虚函数
    virtual void calculate()
    {
        cout << "this is Lenovo CPU" << endl;
    }
};
// 继承VideCard(显卡)类
class LenovoVideCard : public VideoCard
{
public:
    //重写父类中的纯虚函数
    virtual void display()
    {
        cout << "this is Lenovo VideCard display" << endl;
    }
};
// 继承Memory(内存条)类
class LenovoMemory : public Memory
{
public:
    //重写父类中的纯虚函数
    virtual void storage()
    {
        cout << "this is Lenovo storage" << endl;
    }
};
void test01()
{
    //第一台电脑零件,父类指针(CPU)指向子类对象(IntelCPU)
    CPU *intelCpu = new IntelCPU;
    VideoCard *intelCard = new IntelVideCard;
    Memory *intelMem = new IntelMemory;
    cout << "one Computer open" << endl;
    //创建第一台电脑,传入三个零件(三个指针)
    Computer *Computerl = new Computer(intelCpu, intelCard, intelMem);
    //调用电脑类下的工作函数,其实也是通过这个函数去调用其他类下的函数
    Computerl->work();
    //工作完后释放电脑的指针指向的那块内存
    delete Computerl;
    //因为零件是纯虚函数不能直接创建对象 必须用指针创建 而它们又放在电脑类的构造中
    //所以要在电脑类中析构内存释放堆区数据

    cout << "--------------------" << endl;
    cout << "two Computer open" << endl;
    //第二台电脑的组装,直接new新的零件出来传入参数,不使用原来创建的零件
    Computer *Computer2 = new Computer(new LenovoCPU, new LenovoVideCard, new LenovoMemory);
    //调用电脑类下的工作函数,其实也是通过这个函数去调用其他类下的函数
    Computer2->work();
    //工作完后释放电脑的指针指向的那块内存
    delete Computer2;

    cout << "--------------------" << endl;
    cout << "three Computer open" << endl;
    //第三台电脑的组装,直接new新的零件出来传入参数,不使用原来创建的零件,使用不同的零件组合
    //用联想的CUP配Inter的显卡
    Computer *Computer3 = new Computer(new LenovoCPU, new IntelVideCard, new LenovoMemory);
    //调用电脑类下的工作函数,其实也是通过这个函数去调用其他类下的函数
    Computer3->work();
    //工作完后释放电脑的指针指向的那块内存
    delete Computer3;
    //注意:因为IntelCPU是子类,子类中没有开辟堆区数据,所以不需要写虚析构函数
}
int main()
{
    test01();
    return 0;
}

文件操作

C++文件操作-写文件

// C++文件操作-写文件
#include <iostream>
// C++操作文件需要包含的头文件
#include <fstream>
using namespace std;
void test01()
{
    //操作文件有三种方式
    // 1.ofstream 写文件
    // 2.ifstream 读文件
    // 3.fstream 读写文件
    //创建写文件的流对象(ofstream)
    ofstream ofs;
    //打开文件,第一个参数是文件的路径,第二个参数是打开的方式
    //如果不指定路径那么就会创建在与C++文件同级的文件下
    // ios::out是写文件,用写文件的方式打开文件
    //有的时候在路径前面要加反斜杠
    ofs.open("class_02/txt/test.txt", ios::out);
    //往文件中写内容,endl在文件当中也可以代表换行
    // ofs<<后面跟着的就是要写的内容
    ofs << "name:zs" << endl;
    ofs << "sex:man" << endl;
    ofs << "age:18" << endl;
    //写入完成后要关闭文件
    ofs.close();
}
int main()
{
    //文件类型分为文本文件和二进制文件两种
    test01();
    return 0;
}

C++文件操作-读文件

// C++文件操作-读文件
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
void test01()
{
    //创建读文件的流对象
    ifstream ifs;
    //读文件,第一个参数是路径,第二个参数是打开方式
    // ios::in 是进行读
    ifs.open("test.txt", ios::in);
    // is_open会判断文件是否打开成功,返回的是一个布尔值
    //将它取反后判断
    if (!ifs.is_open())
    {
        //文件打开失败
        //有时候文件名写错或者路径写错都有可能打开失败
        cout << "No" << endl;
        return;
    }

    //读数据
    //第一种方式:将文件的数据全部放入一个字符数组中
    // char buf[1024] = {0};
    //通过循环将文件中的数据全部都放入数组中,当文件读到头了会返回一个空值终止循环
    // while (ifs >> buf)
    // {
    //     //输出看看是否读入到了数据
    //     // ifs是按行读取,所以每次循环都会输出一行
    //     cout << buf << endl;
    // }

    //第二种方式:
    // char buf[1024] = {0};
    // //在ifs里有一个函数叫getline,是获取行的意思
    // //第一个参数是要读入文件的数组名,第二个参数是数组的大小
    // while (ifs.getline(buf, sizeof(buf)))
    // {
    //     cout << buf << endl;
    // }

    //第三种方式,将文件的数据放入string类型的字符串中
    // string buf;
    // // getline是一个全局函数,用来读取文件到string中
    // //第一个参数是基础的输入流,也就是通过读文件的流对象(ifstream)创建出来的对象ifs
    // //第二个参数是string类型的字符串名
    // while (getline(ifs, buf))
    // {
    //     //也是通过读取一行到数组中
    //     cout << buf << endl;
    // }

    //第四种方式:通过char类型的字符一个一个的读
    char c;
    // ifs里面有一个叫做get的函数,每次只读一个字符,通过!=EOF判断是否读到了文件尾结束循环
    while ((c = ifs.get()) != EOF)
    {
        //注意:第四种读取方式不能在cout后面加上endl
        cout << c;
    }
    //读取完成后要关闭文件
    ifs.close();
}
int main()
{
    test01();
    return 0;
}

C++二进制读文件操作-写文件

// C++二进制读文件操作-写文件
#include <iostream>
#include <fstream>
using namespace std;
// C++二进制的文件操作可以操作像class这样的类对象自定义数据类型到文件中
class Person
{
public:
    //姓名
    char m_Name[64];
    //年龄
    int m_Age;
};
void test01()
{
    //创建写文件的流对象
    // ofstream ofs;
    //打开文件,ios::out是写文件操作方式,ios::binary是通过二进制的方式写文件
    //中间还要加一个|操作符把他们关联起来
    // ofs.open("person.txt", ios::out | ios::binary);

    //可以将上面两步并做一步,在创建对象时就指定好写入的方式,因为ofs这个对象里有一个构造函数
    ofstream ofs("class_02/txt/person.txt", ios::out | ios::binary);

    //写文件
    //这是初始化列表的方式,只有公有属性才可以用
    Person p = {"zs", 18};
    // write就是写文件的函数,第一个参数是对象数组的地址,第二个参数是要写入数据的长度
    //但是因为直接对对象数组取地址返回的是*Person类型
    //而write函数要求的是char类型,所以要强转成char*类型
    ofs.write((const char *)&p, sizeof(Person));
    //其实是write函数要求的const char * _Str
    //那个Str是传入文件的首指针,也就是这里为什么要取地址的原因,因为返回的是Person的首指针

    //关闭文件
    ofs.close();
}
int main()
{
    test01();
    return 0;
}

C++二进制读文件操作-读文件

// C++二进制读文件操作-读文件
#include <iostream>
#include <fstream>
using namespace std;
class Person
{
public:
    //姓名
    char m_Name[64];
    //年龄
    int m_age;
};
void test01()
{
    //创建读文件的流对象
    ifstream ifs;
    // open就是打开的操作,第一个参数是文件路径,第二个参数是读文件的操作
    //要加上|符号来使读取的是二进制文件
    ifs.open("person.txt", ios::in | ios::binary);
    //通过is_open函数来判断是否打开成功
    if (!ifs.is_open())
    {
        cout << "No" << endl;
        return;
    }
    //将打开的文件的数据放入到Person对象中
    Person p;
    // read函数就是读取文件的操作,也就是将文件的数据放入到对象中
    //第一个参数是要放入数据的对象,返回类型是char*,所以要强转
    //第二个参数是要读取的长度,也就是占用的空间
    ifs.read((char *)&p, sizeof(Person));
    //输出读取到的数据
    cout << "name:" << p.m_Name << "\nage" << p.m_age << endl;
    //关闭读文件
    ifs.close();
}
int main()
{
    test01();
    return 0;
}