C++基础学习笔记(上)

这篇博客详细介绍了C++的基础知识,包括常量、数据类型、程序流程结构、数组、函数、指针、结构体、内存模式、引用、函数提高、类和对象、运算符重载、继承和多态。特别强调了指针的使用、结构体的应用、内存四区的概念以及多态的实现原理和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++基础学习笔记(上)

一、常量

1.宏常量:通常在文件上方,使用#define 常量名 常量值 定义;

2.const修饰的变量:const 数据类型 常量名 = 常量值,不可修改。

二、数据类型:

1.整型:

​ short:短整型,2B,-32768~32767

​ int:整型,4B,-231~231-1

​ long:长整型,win 4B,Unix 4B(32b),8B(64b)

​ long long:8B

2.sizeof()关键字可以统计数据类型占的内存大小。

3.实型(浮点型):

​ float:4B

​ double:8B

​ 科学计数法:f = 3e-2

4.字符型:char 1B,字符型变量将字符对应的ASCII码放到存储单元。

5.字符串型:

​ char 变量名[] = ”xx“ ——C风格

​ string 变量名 = ”xx“ ——C++风格

6.bool型:1B

7.数据输入:cin >> 变量

8.前置递增: 先让变量递增,再进行表达式运算。后置递增: 先进行表达式运算,再让变量递增。

三、程序流程结构

1.三目运算符返回的是变量,可以继续赋值。

2.do-while循环:do{循环语句} while{循环条件},与while的区别在于先执性一次循环语句,再判断循环条件

do {
    cout << num << endl;
    num ++;
}
while(num < 10);

水仙花数:

do {
    int i = num % 10;
    int j = num / 10 % 10;
    int k = num / 100;
    if (i*i*i  + j*j*j + k*k*k == num){
        cout << num << endl;
    }
    num ++;
}
while(num < 1000);

3.for(起始表达式;条件表达式;末尾循环体){循环语句;} 等价于下:

int i = 0;
for (;;) {
    if(i > 10){break;}
    cout << i <<endl;
    i ++;
}

continue本次循环剩下的代码不执行。

4.goto标记:如果标记的名称存在,执行到goto语句时会跳转到标记的位置。

goto FLAG;
FLAG:
cout << "goto!!" << endl;

四、数组

1.数组放在一块连续的内存空间中,每个元素都是相同的数据类型。创建方式:

​ int arr[5];

​ int arr[] = {1, 2, 3, 4, 5};

​ int arr[];

2.冒泡排序:

for (int i = 0; i < size - 1; ++i) {//总轮数为元素个数-1
    for (int j = 0; j < size - i - 1 ; ++j) {//对比次数 = 元素个数 - 当前轮数 - 1
        if (arr[j] > arr[j+1]){
            int temp = arr[j];
            arr[j] = arr[j+1];
            arr[j+1] = temp;
        }
    }
}

五、函数

1.函数的声明:函数写在main函数之后会找不到函数,需要事先声明。声明函数只有函数类型、函数名和参数列表。声明可以写多次,但函数定义只能有一次。

2.函数的分文件编写:

(1)创建.h头文件;(2)创建.cpp源文件;(3)在头文件中写函数的声明;(4)在源文件中写函数的定义,包含头文件。

六、指针(int *类型)

1.指针变量保存一个地址。通过解引用的方式使用指针:*p

2.指针在32b系统下占4B,在64b系统占8B;

3.空指针:指针变量指向内存中编号为0的空间,一般用于初始化指针变量:int *p = NULL;空指针指向的内存不允许访问,0-255的内存编号是系统使用。

4.野指针:指针变量指向非法内存空间。空指针和野指针都市自己申请的空间,不允许访问。

5.const修饰指针(3种情况):

​ const修饰指针:常量指针:const int * p = &a;指针的指向可以修改,但指针指向的值不能修改。

​ const修饰常量:指针常量:int * const p = &a;指针的指向不可以修改,但指针指向的值可以修改。

​ const既修饰指针又修饰常量:const int * const p = &a;指针的指向和指向的值都不能修改。

6.利用指针访问数组中的元素:

int arr[] = {1,2,3,4,5,6,7,8,9};
int * p = arr;//指向数组首地址
for (int i = 0; i < 9; ++i) {
    cout << *p << endl;
    p++;//指针向后偏移4B
}

7.指针和函数:在函数中需要修改传入的实参,则需要传入地址。

8.指针、数组、函数实现冒泡排序:

void bubbleSort(int * arr, int len){
    cout << "arr:" << arr <<endl;//arr地址
    cout << "* arr:" << * arr <<endl;//数组首元素
    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;
            }
        }
    }
}
void printArr(int * arr, int len){
    for (int i = 0; i < len; ++i) {
        cout << arr[i] << endl;
    }
}
int main(){
    int arr[] = {12,2,5,4,1,6,17,28,9,3};
    int len = sizeof(arr) / sizeof(arr[0]);
    bubbleSort(arr, len);
    printArr(arr, len);
}

七、结构体

1.结构体是用户自定义的数据类型,允许存储不同的数据类型。语法:struct 结构体名 { 结构体成员列表 };通过结构体创建变量的方式有三种(常用一二种),创建时struct关键字可胜省略,定义时不可省略:

​ struct 结构体名 变量名

​ struct 结构体名 变量名 = {成员1值, 成员2值…}

​ 定义结构体时顺便创建变量

struct Student{
    string name;
    int age;
    int score;
} s3;//第三种
int main(){
    //第一种
    struct Student s1;
    s1.name = "a";
    s1.age = 13;
    s1.score = 100;
    //第二种
    struct Student s2 = {"b", 18, 99};
    //第三种
    s3.name = "c";
    s3.age = 22;
    s3.score = 66;
}

2.结构体数组:将自定义的结构体放入数组中方便维护。语法:struct 结构体名 数组名[元素个数] = { {}, {}, {}, …}

struct Student{
    string name;
    int age;
    int score;
};
int main(){
    Student stuArr[3] = {
            {"a", 16, 100},
            {"b", 18,99},
            {"c",22,66}
    };
    stuArr[0].name = "d";//修改结构体变量
    for (int i = 0; i < 3; ++i) {//遍历输出结构体变量
        cout << stuArr[i].name << stuArr[i].age << stuArr[i].score << endl;
    }
}

3.结构体指针:作用:通过指针访问结构体中的成员。利用操作符 -> 可以通过结构体指针访问结构体属性。

struct Student{
    string name;
    int age;
    int score;
};
int main(){
   Student s = {"a", 16, 100};
   Student * p = &s;//通过指针指向结构体变量
   cout << "姓名:" << p->name << endl;
}

4.结构体嵌套结构体:例:一个老师带多个学生。

struct Student{
    string name;
    int age;
    int score;
};
struct Teacher{
    int id;
    string name;
    int age;
    struct Student stu;
};
int main(){
   Teacher t;
   t.id = 1;
   t.name = "王老师";
   t.age = 45;
   t.stu.name = "小明";
   t.stu.age = 20;
   t.stu.score = 100;
   cout << t.name << t.id << t.age << t.stu.name << t.stu.age << endl;
}

5.结构体做函数参数:作用:将结构体作为参数向函数中传递,传递方式有值传递和地址传递

struct Student{
    string name;
    int age;
    int score;
};
void printStu(Student * s){
    cout << s->name << s->age << s->score << endl;
}
int main(){
    Student s;
    s.name = "小明";
    s.age = 18;
    s.score = 100;
    printStu(&s);
}

6.结构体重const使用:用来防止误操作。

//将函数的形参改为指针可以减少占用内存空间,而且不会复制新的副本出来。
//但会有误修改结构体数据的风险。使用const可以避免,有修改的操作会报错
void printStu(const Student * s){
    cout << s->name << s->age << s->score <<endl;
}
int main(){
    Student s = {"小明", 18, 100};
    printStu(&s);
}

7.例:每个老师带五个学生

struct Student{
    string sName;
    int score;
};
struct Teacher{
    string tName;
    Student sArr[5];
};
void allocateSpace(Teacher tArr[], int len){
    string nameSeed = "ABCDE";
    for (int i = 0; i < len; ++i) {
        tArr[i].tName = "teacher_";
        tArr[i].tName += nameSeed[i];
        for (int j = 0; j < 5; ++j) {
            tArr[i].sArr[j].sName = "student_";
            tArr[i].sArr[j].sName += nameSeed[j];
            tArr[i].sArr[j].score = 66;
        }
    }
}
void printInfo(Teacher tArr[], int len){
    for (int i = 0; i < len; ++i) {
        cout << "老师姓名:\t" << tArr[i].tName << endl;
        for (int j = 0; j < 5; ++j) {
            cout << "学生姓名:\t" << tArr[i].sArr[j].sName << "学生分数:\t" << tArr[i].sArr[j].score << endl;
        }
    }
}
int main(){
    //创建三个老师的数组
    Teacher tArr[3];
    //给三老师和其学生赋值
    int len = sizeof(tArr) / sizeof(tArr[0]);
    allocateSpace(tArr, len);
    //输出老师和学生信息
    printInfo(tArr, len);
}

8.例:冒泡排序输出按结构体的某个值排序结果:

struct Hero{
    string name;
    int age;
    string sex;
};
void bubbleSort(Hero heroArr[], int len){
    for (int i = 0; i < len - 1; ++i) {
        for (int j = 0; j < len - i - 1; ++j) {
            if (heroArr[j].age > heroArr[j+1].age){
                Hero temp = heroArr[j];
                heroArr[j] = heroArr[j+1];
                heroArr[j+1] = temp;
            }
        }
    }
}
void printArr(Hero heroArr[],int len){
    for (int i = 0; i < len; ++i) {
        cout << "名字:" << heroArr[i].name <<"年龄:" << heroArr[i].age << "性别:" << heroArr[i].sex << endl;
    }
}
int main(){
    Hero heroArr[5] = {
            {"a", 23, "男"},
            {"b", 25,"男"},
            {"c", 44, "男"},
            {"d", 53, "女"},
            {"e", 12, "男"}
    };
    bubbleSort(heroArr, 5);
    printArr(heroArr, 5);
}

9.通讯录管理系统示例:

#include <iostream>
#include <string>
# define MAX 1000
using namespace std;
struct Person{
    string m_Name;
    int m_Sex;
    int m_Age;
    string m_Phone;
    string m_Addr;
};
struct AddrBooks{//通讯录结构体
    Person personArr[MAX];
    int m_Size;//当前记录的联系人个数
};
//显示菜单界面
void showMenu(){
    cout << "   输入要执行的功能数字!" << endl;
    cout << "***** 1.添加联系人 *****" << endl;
    cout << "***** 2.显示联系人 *****" << endl;
    cout << "***** 3.删除联系人 *****" << endl;
    cout << "***** 4.查找联系人 *****" << endl;
    cout << "***** 5.修改联系人 *****" << endl;
    cout << "***** 6.清空联系人 *****" << endl;
    cout << "***** 0.退出      *****" << endl;
}
void addPerson(AddrBooks * addrBooks){
    if (addrBooks->m_Size == MAX){
        cout << "通讯录已满!无法加入!" << endl;
        return;
    }
    else{
        string name;
        cout << "请输入名字:" << endl;
        cin >> name;
        addrBooks->personArr[addrBooks->m_Size].m_Name  = name;
        int sex;
        cout << "请输入性别:(1男,2女)" << endl;
        while (true){
            cin >> sex;
            if (sex == 1 || sex == 2) {
            addrBooks->personArr[addrBooks->m_Size].m_Sex = sex;
                break;
            }
            cout << "输入有误!重新输入!" << endl;
        }
        int age = 0;
        cout << "请输入年龄:" << endl;
        cin >> age;
        addrBooks->personArr[addrBooks->m_Size].m_Age = age;

        string phone;
        cout << "请输入电话:" << endl;
        cin >> phone;
        addrBooks->personArr[addrBooks->m_Size].m_Phone = phone;

        string addr;
        cout << "请输入地址:" << endl;
        cin >> addr;
        addrBooks->personArr[addrBooks->m_Size].m_Addr = addr;

        cout << "添加成功!" << endl;
        addrBooks->m_Size ++;
    }
}
void showPerson(AddrBooks * addrBooks){
    if (addrBooks->m_Size == 0){
        cout << "当前记录为空!" << endl;
    } else{
        cout << "姓名\t\t性别\t\t年龄\t\t电话\t\t\t地址\t\t\t" << endl;
        for (int i = 0; i < addrBooks->m_Size; ++i) {
            cout <<addrBooks->personArr[i].m_Name << "\t\t" << (addrBooks->personArr[i].m_Sex == 1?"男":"女")
            << "\t\t" << addrBooks->personArr[i].m_Age << "\t\t" << addrBooks->personArr[i].m_Phone
            << "\t\t\t" << addrBooks->personArr[i].m_Addr << "\t\t\t" << endl;
        }
    }
}
int isExist(AddrBooks * addrBooks, string name){
    for (int i = 0; i < addrBooks->m_Size; i++) {
        if (addrBooks->personArr[i].m_Name == name){
            return i;
        }
    }
    return -1;
}
void deletePerson(AddrBooks * addrBooks){
    cout << "输入要删除的联系人:" << endl;
    string name;
    cin >> name;
    int ret = isExist(addrBooks, name);
    if (ret != -1){
        for (int i = ret; i < addrBooks->m_Size; ++i) {
            addrBooks->personArr[i] = addrBooks->personArr[i+1];
            addrBooks-> m_Size --;//更新数量
            cout << "删除成功!" << endl;
        }
    }else{
        cout << "查无此人!" << endl;
    }
}
void findPerson(AddrBooks * addrBooks){
    cout << "输入要查找的联系人:" << endl;
    string name;
    cin >> name;
    int ret = isExist(addrBooks, name);
    if (ret != -1){
        cout<< "姓名:" << addrBooks->personArr[ret].m_Name << "\n性别:"<< addrBooks->personArr[ret].m_Sex
        << "\n年龄:" << addrBooks->personArr[ret].m_Age << "\n电话:" << addrBooks->personArr[ret].m_Phone
        << "\n住址:" << addrBooks->personArr[ret].m_Addr << endl;
    } else{
        cout << "查无此人!" << endl;
    }
}
void modifyPerson(AddrBooks * addrBooks){
    cout << "输入要修改的联系人:" << endl;
    string name;
    cin >> name;
    int ret = isExist(addrBooks, name);
    if (ret != -1){
        string name;
        cout << "请输入姓名:" << endl;
        cin >> name;
        addrBooks->personArr[ret].m_Name = name;
        int sex;
        cout << "请输入性别:" << endl;
        while (true) {
            cin >> sex;
            if (sex == 1 || sex == 2) {
                addrBooks->personArr[ret].m_Sex = sex;
                break;
            } else {
                cout << "输入有误,重新输入!" << endl;
            }
        }
        int age;
        cout << "请输入年龄:" << endl;
        cin >> age;
        addrBooks->personArr[ret].m_Age = age;
        int phone;
        cout << "请输入电话:" << endl;
        cin >> phone;
        addrBooks->personArr[ret].m_Phone = phone;
        string address;
        cout << "请输入地址:" << endl;
        cin >> address;
        addrBooks->personArr[ret].m_Addr = address;
    }
}
void cleanPerson(AddrBooks *addrBooks){
    addrBooks->m_Size = 0;
    cout << "清空成功!" << endl;
}
int main(){
    AddrBooks addrBooks;
    addrBooks.m_Size = 0;
    int select = 0;
    while (true) {
        showMenu();
        cin >> select;
        switch (select) {
            case 0://退出
                cout << "欢迎下次使用!" << endl;
                return 0;
            case 1://添加联系人
                addPerson(&addrBooks);
                break;
            case 2://显示
                showPerson(&addrBooks);
                break;
            case 3://删除
                deletePerson(&addrBooks);
                break;
            case 4://查找
                findPerson(&addrBooks);
                break;
            case 5://修改
                modifyPerson(&addrBooks);
                break;
            case 6://清空:将通讯录中数量置零做逻辑清空即可
                cleanPerson(&addrBooks);
                break;
        }
    }
}

八、内存模式——内存四区(代码区、全局区、栈区、堆区)

1.代码区存放函数体的二进制代码,由操作系统进行管理;全局区存放全局变量、静态变量、常量(字符串常量、const修饰的全局变量,但const修饰的局部变量不在全局区);栈区在函数执行完后由编译器自动分配释放,存放函数的形参局部变量等;堆区由程序员分配和释放,若不释放则程序结束时由OS回收。

意义:不同的区域存放不同的数据赋予不同的生命周期,更灵活编程。

2.在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域——代码区和全局区。

​ **代码区:**存放cpu执行的机器指令。**特点:**代码区是共享的,目的是对于频繁执行的程序只需要在内存中有一份代码;代码区是只读的,防止程序意外的修改了它的指令。

​ **全局区:**在程序结束后由OS释放。const修饰的局部变量和局部变量都不在此区。

​ **栈区:**注意事项:不能返回局部变量的地址,栈区开辟的数据由编译器自动释放。

int * func(){
    int a = 10;//局部变量,存放在栈区,栈区数据在函数执行完后释放
    return &a;//返回局部变量的地址
}
int main(){
    int *p = func();
    cout << *p << endl;//输出10,因为编译器做了一次保留
    cout << *p << endl;//输出32767,第二次数据不再保留
}

堆区:new关键字开辟内存,会返回该数据对应类型的指针;delete关键字释放内存。

int * func(){
  int *p = new int(10);//利用new关键字开辟堆区数据,指针在栈区,开辟的空间在堆区。
  int *arr = new int[10];
  for(int i=0;i<10;i++){
      arr[i] = i+1;
  }
  return p;
}
int main(){
    int *p = func();
    cout << *p << endl;
    delete p;//delete关键字释放内存空间
    delete[] arr;
}

九、引用

1.引用的作用:给变量起别名,不同于值传递,不会再复制一份数据。语法:数据类型 &别名 = 原名;创建引用时必须初始化(赋值),且初始化后不可再改变。别名和原名指向同一块内存,可以操作原内存。

2.**引用的本质:在C++内部实现是一个指针常量:*int& ref = a; 等价于int const ref = &a;所以引用不可更改。内部发现ref = 1;自动转换为*ref = 1;

3.函数传参是,可以利用引用让形参修饰实参,可以简化指针修改实参。

//引用方式传参,起了一个一样的别名
void swap(int &a, int &b){
    int temp = a;
    a = b;
    b = temp;
}
int main(){
    int a = 1;
    int b = 2;
    swap(a, b);
    cout << a << b << endl;
}

4.引用做函数的返回值:用法:如果函数的返回值是引用,函数调用可以作为左值,不可返回局部变量的引用,因为函数执行后栈区数据释放,返回的地址已经不是局部变量本来的地址。

int& func0(){//不要返回局部变量的引用
    int a = 1;
    return a;
}
int& func1(){
    static int a = 1;//静态变量在全局区
    return a;//返回a的引用,等价于返回a这个变量
}
int main(){
    int &ref0 = func0();
    cout << "ref0:" << ref0 << endl;//第一次结果正确,因为编译器做了保留(但我的结果是两次都不正确)
    cout << "ref0:" << ref0 << endl;//第二次结果错误,因为内存已经释放
    int &ref1 = func1();
    cout << "ref1:" << ref1 << endl;//每次都能正确输出
    cout << "ref1:" << ref1 << endl;
    
    //如果函数的返回值是引用,则函数调用可作为左值
    func1() = 100;//等价于 a = 100
    cout << "ref1:" << ref1 << endl;
}

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

//int& ref = 10;引用本身需要一个合法的内存空间,此句错误
const int& ref = 10;
//加入const后,变为int temp = 10;const int& ref = temp;
void printA(const int &val){
    val = 101;//不小心使用赋值操作,改变了a的值,使用const则不能再修改
    cout << "val=" << val << endl;
}
int main(){
    int a = 100;
    printA(a);
}

十、函数提高

1.函数缺省参数:形参可使用缺省参数。如果某个位置有了默认参数,则从这个位置往后的形参都必须要有缺省参数。

2.函数的声明有了默认参数,则函数的实现不能再有默认参数。声明和实现只能有一个有缺省参数。

3.函数占位参数:形参列表中可以有占位参数用来占位,调用函数时必须填补该位置。语法:返回值类型 函数名 (数据类型){}

4.函数重载:作用:函数名可以相同以提高复用性,根据函数中的参数不同调用不同的函数。

需满足:同一个作用域下;函数名相同;函数参数类型不同个数不同顺序不同(函数返回值不能作为重载条件)

注意事项:

(1)引用作为重载条件:形参是引用类型和const修饰的引用类型,使用函数重载时,如果实参是变量则调用前者,是常量则会调用后者。

(2)函数重载遇到函数有缺省参数:当函数有缺省参数时尽量不要使用重载。

十一、类和对象

1.封装:将属性和行为作为一个整体表现事物,将属性和行为加以权限控制。语法:class 类名{访问权限:属性/行为}

2.类中的属性和行为统称为成员,类中的属性称为成员属性或成员变量,行为称为成员函数或成员方法。

3.访问权限:

​ public:类内、类外都可访问

​ protected:类内可以访问,类外不可访问,继承时子可访问父内容。

​ private:类内可以访问,类外不可访问,继承时子不可访问父内容。

4.class和struct唯一区别:默认的访问权限不同:struct默认权限是公有,class默认权限是私有。

5.类中的成员是另一个类的对象,称该成员是对象成员

//Person中包含Phone类
class Phone{
public:
    string m_pName;
    Phone(string p_name){
        m_pName = p_name;
    }
};
class Person{
public:
    string m_name;
    Phone m_phone;
    //Phone m_phone = pName 隐式转换法创建对象
    Person(string name, string pName):m_name(name), m_phone(pName){
    }
};
void init(){
    //先构造Phone类,再构造Person类
    // 先执行Person类的析构函数,再执行Phone类的析构函数
    Person p("wzh", "iphone");
    cout << p.m_name << p.m_phone.m_pName << endl;
}
int main(){
    init();
}

6.构造函数和析构函数:被编译器自动调用,完成对象的初始化和清理工作。若不提供构造函数和析构函数,编译器会自动提供空实现的构造函数和析构函数。

(1)构造函数:语法:类名(){ };没有返回值也不写void;名称与类名相同,可以有参数,因此可以发生重载;调用对象时自动调用,且只调用一次。

(2)析构函数:语法:~类名(){ };没有返回值也不写void;名称与类名相同,前加~,不能有参数,因此不会发生重载;销毁对象时自动调用,且只调用一次。

(3)构造函数的分类:按参数分为有参构造无参构造;按类型分为普通构造拷贝构造:Person(const Person &p)。

(4)构造函数的三种调用方式:

​ **<1>括号法:**调用默认构造函数时不加括号(),如果加了括号编译器认为是函数声明。

​ **<2>显式法:**Person p = Person(10);

​ 调用拷贝构造:Person p = Person(p0); 单独的Person(10)是匿名对象,此行执行完后系统立即回收匿名对象。

​ 不能利用拷贝函数初始化匿名对象:Person(p2)——编译器认为:Person p2,即实例化一个对象。

​ **<3>隐式转换法:**Person p = 10;等价于Person p = Person(10)。拷贝构造:Person p = p1;

7.拷贝构造函数的调用时机:

​ 使用一个已经创建完毕的对象来初始化一个新对象;

​ 值传递的方式给函数参数传值;

​ 以值方式返回局部对象。

class Person{
public:
    int m_age;
    Person(){
        cout << "person默认构造函数调用" << endl;
    }
    Person(int age){
        m_age = age;
        cout << "person有参构造函数调用" << endl;
    }
    Person(const Person &p) {
        m_age = p.m_age;
        cout << "person拷贝构造函数调用" << endl;
    }
    ~Person(){
        cout << "person析构函数调用" << endl;
    }
};
void test(){
    Person p0(20);
    Person p1(p0);
    cout << "p0的年龄:" << p0.m_age <<endl;
    cout << "p1的年龄:" << p1.m_age <<endl;
}
int main(){
    test();
}

/**输出:
person有参构造函数调用
person拷贝构造函数调用
p0的年龄:20
p1的年龄:20
person析构函数调用
person析构函数调用*/

8.构造函数调用规则:默认情况下,c++编译器至少给一个类添加三个函数:

​ 默认构造函数(无参,函数体为空)

​ 默认析构函数(无参,函数体为空)

​ 默认构造拷贝函数,对属性进行值拷贝

调用规则:

​ 用户定义有参构造函数,则不再提供默认无参构造,但会提供默认拷贝构造函数

​ 用户定义拷贝构造函数,则不再提供其他构造函数

9.深拷贝和浅拷贝:

​ 浅拷贝:简单的赋值拷贝操作

​ 深拷贝:在堆区重新申请空间,进行拷贝操作

class Person{
public:
    int m_age;
    int * m_height;//身高(定义为指针,将数据开辟到堆区)
    Person(){
        cout << "默认构造函数" << endl;
    }

    Person(int age, int height){
        m_age = age;
        m_height = new int(height);//创建在堆区
        cout << "有参构造函数" << endl;
    }
    ~Person(){
        //手动将堆区的数据释放
        if (m_height != NULL){
            delete m_height;
            m_height =NULL;
        }
        cout << "默认析构函数" << endl;
    }
    //下面这是增加的深拷贝代码,自定义拷贝构造函数
    Person(const Person &p){
        cout << "拷贝构造函数调用" << endl;
        m_age = p.m_age;
        //m_height = p.m_height;编译器默认实现这行代码
        //深拷贝:重新开辟空间,让自己的指针指向此空间
        m_height = new int(* p.m_height);
    }
};
void test01(){
    Person p1(18, 170);
    cout << "p1的age:" << p1.m_age <<"p1的height:" << * p1.m_height << endl;
    Person p2(p1);
    /**若利用编译器的拷贝构造函数,则会做浅拷贝操作,给p2定义 m_age 变量并赋值18,
     * 定义 * m_height 变量并赋值为p1中 * m_height 的指针所在内存地址
     * p2调用析构函数释放内存后,p1又调用再次释放,带来的问题是重复释放
     * 应使用 深拷贝 进行,自定义拷贝构造函数,重新在堆区申请内存存放指针 m_height 的值*/
    cout << "p2的age:" << p2.m_age <<"p2的height:" << * p2.m_height << endl;
}
int main(){
    test01();
}

10.初始化列表:语法:构造函数():属性1(值1),属性2(值2)…{ }

class Person{
public:
    string m_name;
    int m_age;
    Person(string name, int age):m_name(name), m_age(age){
    }
};
int main(){
    Person("wzh", 18);
}

11.静态成员:在成员变量和成员函数前加static。

​ 静态成员变量:

​ 所有对象共享同一份数据;

​ 在编译阶段分配内存;

在类内声明,类外初始化。

​ 静态成员函数:

​ 所有对象共享同一个函数;

​ 静态成员函数只能访问静态成员变量。

​ 类外访问不到私有静态成员函数。

class Person{
public:
    static void func() {
        m_a = 100;
//        m_b = 200;  静态成员函数不能访问非静态成员变量,无法区分是那个对象的变量
        cout << "static" << endl;
    }
    static int m_a;
    int m_b;
};

int Person::m_a = 0;
void tests(){
    //通过对象访问
    Person p;
    p.func();
    //所有对象共享一个函数,通过类名访问
    Person::func();
}
int main(){
    tests();
}

12.C++对象模型:

(1)成员变量和成员函数分开存储。只有非静态成员变量才属于类的对象上面。空对象占用1B的空间。在类内定义一个int数据则对象占用4B空间,再增加任意静态成员变量不增加对象占用的空间大小。

(2)this指针:每一个非静态成员函数只会存在一个函数实例,也就是多个同类型的对象共用一块代码,C++通过this指针指向被调用的成员函数所属的对象。this指针是隐含每一个非静态成员函数内的一种指针,直接使用即可。

​ 用途:<1>当形参和成员变量同名时,可用this来区分;<2>在类的非静态成员函数中返回对象本身,可使用return *this。

class Person{
public:
    Person(int age){
        this->age = age;
    }
//    void PersonAddAge(Person &p){
    Person & PersonAddAge(Person &p){
    //这里如果不使用引用,而是返回值,则下面链式每次调用返回的是新建的一个Person创建的对象复制的一份数据
        this->age += p.age;
        return * this;
    }
    int age;
};
void test1(){
    Person p1(18);
    cout << p1.age << endl;
}
void test2(){
    Person p1(5);
    Person p2(10);
    //链式编程,由于原PersonAddAge函数返回的是空,所以不能连续调用,需要改为返回Person的引用
    p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
    cout << "p2年龄:" << p2.age << endl;
}
int main(){
    test1();
    test2();
}

(3)空指针可以调用成员函数,但需注意有没有用到this指针,若用到this指针则需要加以判断保证代码健壮性。

class Person{
public:
    void printName(){
        cout << "名字!" << endl;
    }
    void printAge() {
        if(this == NULL){
            return;
        }
        cout << m_age << endl;//这里默认是this->m_age
    }
    int m_age;
};
int main(){
    Person * p = NULL;
    p->printName();
    p->printAge();//这里不对,空指针调用不到m_age
}

13.const修饰成员函数:

常函数:

​ 成员函数后加const后称这个函数为常函数;

​ 常函数内不能修改成员属性;

​ 成员属性声明时加关键字mutable后,在常函数中依然可以修改。

常对象:

​ 声明对象前加const称该对象为成对象;

​ 常对象只能调用常函数。

class Person{
public:
    //常函数:
    void printName() const{//内隐含着一个this指针,是指针常量,指向后不能更改指向,但可以修改指向的值;
        // 函数名后加一个const修饰this指向,变为:const Person * const this;则不能再修改this指向的值。
//        m_name = "wzh";//不可修改
        m_age = 10;//添加 mutable 关键字后,在常函数下可以修改此值,修改常函数中成员属性
        cout << "名字!" << endl;
    }
    //常对象如果不是只能调用常函数,会通过调用此函数间接修改成员变量,不可以!
    void func(){
        m_name = "123";
    }
    string m_name;
    mutable int m_age;
};
void test(){
    const Person p;//常对象
//    p.m_name = "";//不能修改成员变量的值
    p.m_age = 10;//在常对象中也可以修改mutable修饰的成员变量。
    p.printName();
}
int main(){
    test();
}

14.友元:有些私有属性需要让类外特殊的一些函数或者类进行访问,需要用到友元,目的是让一个函数或者类访问另一个类中的私有成员

三种实现:全局函数做友元;类做友元;成员函数做友元。

(1)全局函数做友元:

class Building{//房屋类
    friend void friends(Building * building);//此全局函数是Building的友元,可以访问Building的私有成员
public:
    Building(){
        m_sitRoom = "客厅";
        m_bedRoom = "卧室";
    }
public:
    string m_sitRoom;
private:
    string m_bedRoom;
};
void friends(Building * building){
    cout << "好朋友可以访问我的客厅!" << building->m_sitRoom<< endl;
    cout << "好朋友可以访问我的客厅!" << building->m_bedRoom <<endl;//此处可以通过友元声明来访问私有属性

}
void test(){
    Building building;
    friends(&building);
}

(2)友元类:

//——————以下使用了"类外实现成员函数和构造函数"——————//

class Home;//声明这个类,不然报错
//——————以下定义两个类,Home和Friends类——————//
class Friends{//朋友类
public:
    Friends();//构造函数
    void visit();//成员函数,访问Home中的公、私有属性
    Home * home;
};
class Home{//家类
    friend class Friends;//声明友元类Friends,可以访问本类中的私有成员
public:
    Home();
public:
    string m_sitRoom;//客厅
private:
    string m_bedRoom;//卧室
};
//——————类外实现三个构造函数和成员函数,声明是Home类/Friends类下的成员函数——————//
Home::Home(){
    m_sitRoom = "客厅";//可以添加this
    m_bedRoom = "卧室";
}
Friends::Friends(){
    home = new Home;
}
void Friends::visit() {
    cout << "朋友正在访问Home中的" << home->m_sitRoom << endl;
    cout << "朋友正在访问Home中的" << home->m_bedRoom << endl;
}
//————————————————以下函数调用——————————————————————//
void test(){
    Friends friends;
    friends.visit();
}
int main(){
    test();
}

(3)成员函数作友元:

//——————以下使用了"类外实现成员函数和构造函数"——————//
class Home;
class Friends{
public:
    Friends();
    void visit1();
    void visit2();
    Home * home;
};
class Home{
    //告诉编译器,Friends类下的的visit1()成员函数作为本类的友元
    friend void Friends::visit1();
public:
    Home();
public:
    string m_sitRoom;
private:
    string m_bedRoom;
};

Home::Home(){
    m_sitRoom = "客厅";
    m_bedRoom = "卧室";
}
Friends::Friends(){
    home = new Home;
}
void Friends::visit1(){
    cout << "visit:" << home->m_sitRoom << endl;
    cout << "visit:" << home->m_bedRoom << endl;//声明了友元,就可以访问到私有属性了
}
void Friends::visit2(){
    cout << "visit:" << home->m_sitRoom << endl;
}
void test(){
    Friends friends;
    friends.visit1();
    friends.visit2();
}
int main(){
    test();
}

十二、运算符重载

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

2.加号运算符重载:实现两个自定义数据类型相加的运算。也可以通过函数重载进行两个不同数据类型相加。通过成员函数或全局函数重载 +:

class Person{
public:
    int m_a, m_b;
    //start————通过成员函数重载 + ——————//
    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;
    }
    //——————通过成员函数重载 + ————end//
};
//start————通过全局函数重载 + ——————//
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;
}
//——————通过全局函数重载 + ————end//
void test(){
    Person p1;
    p1.m_a = 10;
    p1.m_b = 10;
    Person p2;
    p2.m_a = 5;
    p2.m_b = 5;

    Person p3 = p1 + p2;
    //成员函数 本质是:Person p3 = p1.operator+(p2);
    //全局函数 本质是:Person p3 = operator+(p1,p2)
    cout << "p2.m_a:" << p3.m_a << endl <<"p3.m_b:" << p3.m_b << endl;
}
int main(){
    test();
}

3.左移运算符重载:配合友元可以实现输出自定义数据类型。

class Person{
    friend ostream & operator<<(ostream &out, Person &p);

public:
    Person(int a, int b){
        this->m_a = a;
        this->m_b = b;
    }
    //一般不用成员函数重载左移运算符,cout会在右边
private:
    int m_a;
    int m_b;
};
//————利用全局函数重载左移运算符——————//
ostream & operator<<(ostream &out, Person &p){//返回out,链式编程,cout后可以无限追加输出
    out << "m_a:" << p.m_a << endl << "m_b:" << p.m_b;
    return out;
}

void test(){
    Person p(10, 5);
    cout << p;
}
int main(){
    test();
}
//输出:
//m_a:10
//m_b:5

4.递增运算符重载:

引例:

int a = 10;
cout << ++a << endl; //输出11,先执行递增,再执行cout
cout << a << endl; //输出11
int b = 10;
cout << b++ << endl;//输出10,先输出b,再执行b++
cout << b << endl;//输出11
class MyInteger{
    friend ostream & operator<<(ostream & out,MyInteger myInteger);
public:
    MyInteger(){
        m_num = 0;
    }
    //返回引用,为了一直对一个数据递增
    MyInteger & operator++(){//前置
        m_num ++;//先++
        return *this;
    }
    //这里要返回MyInteger类型的值而不是引用,因为局部对象temp在当前函数
    //执行完后被释放,再返回当前对象的引用会报错(但我这里没有报错,原因未知)
    MyInteger& operator++(int){//后置++,用int占位符区分后置,避免重定义
        MyInteger temp = *this;//先记录当前值,再++,但返回的是递增前的值
        m_num ++;
        return temp;
    }
private:int m_num;
};

ostream & operator<<(ostream & out,MyInteger myInteger){
    out << myInteger.m_num;
    return out;
}

void test1(){
    MyInteger myInteger;
    cout << ++myInteger << endl;
    cout << myInteger << endl;
}
void test2(){
    MyInteger myInteger;
    cout << myInteger++ << endl;
    cout << myInteger << endl;
}

int main() {
    test1();
    test2();
}

5.赋值运算符重载:

编译器至少给一个类添加四个函数:

(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;
        }
    }
    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 test(){
    Person p1(10);
    Person p2(20);
    Person p3(30);
    //赋值操作 将p1的10赋值给p2,但会出现析构函数中的堆数据的重复释放问题:p2释放了一次,p1又释放,利用深拷贝解决问题
    p3 = p2 = p1;//要实现这样的操作,重写的函数要返回Person类的引用
    cout << "p1年龄:" << *p1.m_age << endl;
    cout << "p2年龄:" << *p2.m_age << endl;
    cout << "p3年龄:" << *p3.m_age << endl;
}
int main(){
    test();
}

6.重载关系运算符:可以让两个自定义类型对象进行对比操作。

class Person{
public:
    Person(string name, int age){
        m_name = name;
        m_age = age;
    }
    //重载 == 运算符或 != 运算符
    bool operator==(Person &p){
        if(this->m_name == p.m_name && this->m_age == p.m_age){
            return true;
        }
        return false;
    }
    string m_name;
    int m_age;
};
void test(){
    Person p1("wzh", 18);
    Person p2("wzh", 18);
    if (p1 == p2){
        cout << "相等!" << endl;
    } else{
        cout << "不等!" << endl;
    }
}
int main(){
    test();
}

7.函数调用运算符重载:()重载后非常像函数的调用,称为仿函数,没有固定写法。

class Print{
public:
    void operator()(string test){
        cout << test << endl;
    }
};
class Add{
public:
    int operator()(int num1, int num2){
        return num1 + num2;
    }
};
void test(){
    Print print;
    print("hello!");

    Add add;
    int result = add(10, 5);
    cout << "result:"<<result << endl;

    //start————创建匿名函数对象——————//匿名对象:Add()
    cout << "匿名函数result:" << Add()(10, 20) <<endl;//执行完后立即释放
    //——————创建匿名函数对象——————end//
}

int main(){
    test();
}

十三、继承

1.继承语法:class 子类:继承方式 父类

2.继承方式:不管以何种方式继承,子类中都不能继承父类的私有属性

​ 公有继承:除了私有权限,原样继承父类的属性;

​ 保护继承:除了私有权限,将父类的公有和保护属性继承后变为保护属性;

​ 私有继承:将父类的公有和保护属性继承后变为私有属性。

3.继承时子类会继承父类中所有属性(公有、保护、私有)的非静态成员,父类的私有成员是被编译器隐藏了,访问不到。

4.继承中的构造和析构顺序:父类构造函数->子类构造函数->子类析构函数->父类析构函数。

5.当子类和父类出现同名的成员,需要通过子类对象访问子类或父类中同名的成员属性成员函数时:

(1)访问子类同名成员属性/成员函数直接访问即可;

(2)访问父类同名成员属性/成员函数需要添加作用域:类名.作用域::属性名/类名.作用域::函数名。

注:如果子类出现和父类同名的成员函数,子类的同名成员函数会隐藏父类所有的同名成员函数(包括重载函数)。

6.继承中同名的静态成员在子类对象上的访问:与非静态成员处理方式一致。但有两种访问方式:

(1)访问同名静态成员属性:

​ <1>通过对象访问:s.m_a s.Base::m_a

​ <2>通过类名访问:Son::m_a Son::Base::m_a(第一个::代表通过类名方式访问,第二个::代表访问父类的作 用域下的属性)

(2)访问同名静态成员函数:

​ <1>通过对象访问:s.func() s.Base::func()

​ <2>通过类名访问:Son::func() Son::Base::func()

7.多继承语法:class 子类:继承方式 父类1, 继承方式 父类2…(不建议使用多继承)

8.多继承时,当父类中出现同名成员会报错,需要加作用域区分。

9.菱形继承(钻石继承):两个子类继承同一个父类,又有某个类同时继承这两个子类。假设父类的一个属性已经被两个子类继承,子子类会又继承了两份此属性。当菱形继承时,子子类有2个子类有相同的数据,需加作用域区分。但子子类只需要一份此数据, 造成资源浪费,解决方式:虚继承——两个子类继承方式前加关键字virtual,此时基类称为虚基类。到此,此数据只有一份,不论在哪个作用域(子类1、子类2、子子类)中操作此值否只有一份。因为在两个子类中各建立了一个虚基类指针vbptr,指向子子类的此属性值。

十四、多态

1.多态分为静态多态和动态多态(主要)。

​ 静态多态:函数重载和运算符重载;

​ 动态多态:派生类和虚函数实现运行时的动态多态。

2.区别:

​ 静态多态的函数地址早确定:在编译阶段确定函数地址;

​ 动态多态的函数地址晚确定:在运行阶段确定函数地址。

3.(1)动态多态代码示例:

//虚函数处理前:输出:动物叫  因为地址在编译阶段已确定地址。
//因为doSpeak()函数接收的是Animal对象,调用speak函数会调用Animal类中的speak()函数
//若需要执行 猫叫,需要晚绑定:则此函数地址不能提前绑定,需要在运行时绑定
//在动物类的speak函数前加virtual关键字,则不提前确定地址
class Animal{
public:
    virtual void speak(){//在此处添加virtual关键字变为虚函数
        cout << "动物叫" << endl;
    }
};
class Cat:public Animal{
public:
    void speak(){
        cout << "猫叫" << endl;
    }
};
void doSpeak(Animal &animal){//Animal & animal = cat;父类的指针可以直接指向子类
    animal.speak();
}
int main(){
    Cat cat;
    doSpeak(cat);
}

(2)动态多态满足条件:<1>有继承关系;<2>子类重写父类的虚函数,子类加与不加virtual关键字无妨。

(3)动态多态使用条件:父类指针或者引用指向子类对象,即上述中doSpeak()函数使用时。

4.多态的原理:

(1)上述代码示例中,如果不使用virtual关键字,Animal只占用1B,是一个空类;

(2)增加了virtual关键字后,占4B,是因为增加了一个指针——vfptr(虚函数指针),指向一个虚函数表(vftable),里面存放的是虚函数表,记录虚函数的地址。

(3)此时Animal类的表内记录了:&Animal::speak

(4)而Cat类也继承了vfptr,其虚函数表内也记录了:&Animal::speak

(5)当重写父类的虚函数后,子类的虚函数表内部会:将继承来的父类虚函数表的内容替换成子类的虚函数地址:&Cat::speak

(6)当父类的指针或引用指向子类对象时,发生多态,编译器会去传入的子类的虚函数表中调用函数的入口地址。

5.多态实现计算器的案例:

//实现计算器抽象类,下面的子类会重写其成员函数
class AbsCalculator{
public:
    virtual int getResult(){
        return 0;
    }
    int m_num1;
    int m_num2;
};
//加减乘的子类,继承父类,重写getResult()函数
class AddCalculator:public AbsCalculator{
public:
    int getResult(){
        return m_num1 + m_num2;
    }
};
class SubCalculator:public AbsCalculator{
public:
    int getResult(){
        return m_num1 - m_num2;
    }
};
class MulCalculator:public AbsCalculator{
public:
    int getResult(){
        return m_num1 * m_num2;
    }
};
//使用多态计算加法,将父类指针指向子类
void polymAdd(){
    AbsCalculator * abc = new AddCalculator;
    abc->m_num1 = 1;
    abc->m_num2 = 3;
    cout << abc->getResult() << endl;
    delete abc;
}
void polymSub(){
    AbsCalculator * abc = new SubCalculator;
    abc->m_num1 = 1;
    abc->m_num2 = 3;
    cout << abc->getResult() << endl;
    delete abc;
}
int main(){
    polymAdd();
    polymSub();
}

6.纯虚函数和抽象类:

(1)纯虚函数:在多态中,父类中的虚函数实现基本是无意义的,主要都是调用子类重写的内容。因此可将虚函数改为纯虚函数:virtual 返回值类型 函数名 (参数列表) = 0;

(2)抽象类:当类中只要有了一个纯虚函数,此类称为抽象类。

(3)抽象类的特点:无法实例化对象;子类必须重写抽象类中的纯虚函数,否则子类也属于抽象类,无法实例化对象。

7.多态实现制作饮品案例:

描述:过程:煮水->冲泡->倒入杯中->加入辅料,提供抽象类:制作饮品基类,提供子类制作咖啡和茶叶。

class AbsDrink{
public:
    virtual void Boil() = 0;//纯虚函数,煮水
    virtual void Brew() = 0;//冲泡
    virtual void Pour() = 0;//倒水
    virtual void Put() = 0;//加辅料
    void make(){//开始制作
        Boil();
        Brew();
        Pour();
        Put();
    }
};
class Coffee:public AbsDrink{
public:
    virtual void Boil(){
        cout << "加入纯净水" << endl;
    }
    virtual void Brew(){
        cout << "冲泡咖啡" << endl;
    }
    virtual void Pour(){
        cout << "倒入杯中" << endl;
    }
    virtual void Put(){
        cout << "加入方糖" << endl;
    }
};

class Tea:public AbsDrink{
public:
    virtual void Boil(){
        cout << "加入山泉水" << endl;
    }
    virtual void Brew(){
        cout << "冲泡茶叶" << endl;
    }
    virtual void Pour(){
        cout << "倒入茶具中" << endl;
    }
    virtual void Put(){
        cout << "加入柠檬" << endl;
    }
};
void doWork(AbsDrink * abs){
    abs->make();
    delete abs;
}
void makeCoffee(){
    doWork(new Coffee);
}
void makeTea(){
    doWork(new Tea);
}
int main(){
    makeCoffee();
    makeTea();
}

8.虚析构和纯虚析构:

​ 使用多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。

​ 解决方案:将父类中的析构函数改为虚析构或纯虚析构。

​ 虚析构和纯虚析构共性:

​ 可以解决父类指针释放子类对象问题;

​ 且都需要有具体的函数实现。

​ 区别:有了纯虚析构则该类属于抽象类,无法实例化对象。

​ 示例:父类指针在析构时不会调用子类中的析构函数,导致子类如果有堆区属性出现内存泄漏。

class Animal{
public:
    Animal(){
        cout << "Animal构造函数调用!" << endl;
    }
//    virtual ~Animal(){//变为虚析构
//        cout << "Animal析构函数调用!" << endl;
//    }
    virtual ~Animal() = 0;//纯虚析构:需要声明也需要实现
    virtual void say() = 0;
};
//纯虚析构函数的实现:
Animal::~Animal(){
    cout << "Animal纯虚析构函数调用!" << endl;
}

class Cat:public Animal{
public:
    Cat(string name){
        cout << "Cat构造函数调用!" << endl;
        m_name = new string(name);
    }
    ~Cat(){
        if(m_name != NULL){
            cout << "Cat析构函数调用!" << endl;
            delete m_name;
            m_name = NULL;
        }
    }
    virtual void say(){
        cout << *m_name << "_猫叫!" << endl;
    }
    string * m_name;
};

void testcat(){
    Animal * animal = new Cat("tom");
    animal->say();
    //父类指针在析构时不会调用子类中的析构函数,导致子类如果有堆区属性出现内存泄漏
    //解决方式:在父类析构函数前加virtual关键字变为虚析构
    delete animal;
}
int main(){
    testcat();
}

9.电脑组装案例:

(1)将电脑的每个零件封装出抽象基类;

(2)并提供不同的厂商生产的不同的零件;

(3)创建电脑类提供让电脑工作的函数,并调用每个零件工作的接口;

(4)测试时组装三台不同的电脑进行工作。

class Cpu{
public:
    virtual void calculator() = 0;
};
class VideoCard{
public:
    virtual void print() = 0;
};
class Memory{
public:
    virtual void store() = 0;
};
class Computer{
public:
    Computer(Cpu * cpu, VideoCard * vc, Memory * memory){
        m_cpu = cpu;
        m_vc = vc;
        m_memory = memory;
    }
    ~Computer(){
        if (m_cpu != NULL){//释放cpu的内存
            delete m_cpu;
            m_cpu =NULL;
        }if (m_vc != NULL){//释放显卡的内存
            delete m_vc;
            m_cpu =NULL;
        }if (m_memory != NULL){//释放内存的内存
            delete m_memory;
            m_cpu =NULL;
        }
    }
    void boot(){//让电脑运行
        m_cpu->calculator();
        m_vc->print();
        m_memory->store();
        cout << "启动完成!" << endl;
    }
    Cpu * m_cpu;
    VideoCard * m_vc;
    Memory * m_memory;
};
class IntelCpu:public Cpu{
    virtual void calculator(){
        cout << "Intel的CPU开始计算..." << endl;
    };
};
class IntelVideoCard:public VideoCard{
    virtual void print() {
        cout << "Intel的显卡开始显示..." << endl;
    }
};
class IntelMemory:public Memory{
    virtual void store() {
        cout << "Intel的内存条开始存储..." << endl;
    }
};

class DellCpu:public Cpu{
    virtual void calculator(){
        cout << "Dell的CPU开始计算..." << endl;
    };
};
class DellVideoCard:public VideoCard{
    virtual void print() {
        cout << "Dell的显卡开始显示..." << endl;
    }
};
class DellMemory:public Memory{
    virtual void store() {
        cout << "Dell的内存条开始存储..." << endl;
    }
};

void package0(){//组装第1台电脑:多态
    //第1台电脑的零件,在堆区开辟内存,在电脑类的析构函数中销毁
    Cpu * intelCpu = new IntelCpu;
    VideoCard * intelVc = new IntelVideoCard;
    Memory *intelMemory = new IntelMemory;
    //组装第一台电脑
    Computer * computer = new Computer(intelCpu, intelVc, intelMemory);
    computer->boot();
    delete computer;
}
void package1(){//组装第2台电脑
    //第2台电脑的零件,在堆区开辟内存,在电脑类的析构函数中销毁
    Cpu * dellCpu = new DellCpu;
    VideoCard * dellVc = new DellVideoCard;
    Memory *dellMemory = new DellMemory;
    //组装第一台电脑
    Computer * computer = new Computer(dellCpu, dellVc, dellMemory);
    computer->boot();
    delete computer;
}
void package2(){//组装第3台电脑
    //第3台电脑的零件,在堆区开辟内存,在电脑类的析构函数中销毁
    Cpu * intelCpu = new IntelCpu;
    VideoCard * dellVc = new DellVideoCard;
    Memory *intelMemory = new IntelMemory;
    //组装第一台电脑
    Computer * computer = new Computer(intelCpu, dellVc, intelMemory);
    computer->boot();
    delete computer;
}
int main(){
    package0();
    package1();
    package2();
}

十五、文件

1.C++中对文件操作要包含头文件。文件类型分为两种:

​ 文本文件:文件以文本的ASCII码形式存储在计算机中;

​ 二进制文件:文明以文本的二进制形式存储在计算机中

2.操作文件的三大类:

​ ofstream:写操作;

​ ifstream:读操作;

​ fstream:读写操作。

3.文本文件:

(1)写文件步骤:

​ <1>包含头文件;

​ <2>创建流对象:ofstream ofs;

​ <3>打开文件:ofs.open("./xxx", 打开方式);

​ <4>写数据:ofs << “xxxxx”;

​ <5>关闭文件:ofs.close();

(2)打开方式:可以配合使用,使用 | 操作符。

ios::in读方式
ios::out写方式
ios::ate初始位置:文件尾
ios::app追加方式写文件
ios::trunc若文件存在则先删除,再创建
ios::binary二进制方式

(3)写文件示例:

void write(){
    ofstream ofs;
    ofs.open("test.txt", ios::out);
    ofs << "姓名:" << endl;
    ofs << "性别:" << endl;
    ofs << "年龄:" << endl;
    ofs.close();
}

(4)读文件的四种方式:

void read(){
    ifstream ifs;
    ifs.open("test.txt", ios::in);
    if(!ifs.is_open()){
        cout << "打开失败!" << endl;
        return;
    }
    //第1种读取方式
    char buff0[1024] = {0};
    while (ifs >> buff0){
        cout << buff0 <<endl;
    }
    //第2种读取方式
    char buff1[1024] = {0};
    while (ifs.getline(buff1, 1024)){
        cout << buff1 <<endl;
    }
    //第3种读取方式
    string buff2;
    while (getline(ifs, buff2)){
        cout << buff2 <<endl;
    }
    //第4种读取方式
    char c;
    while ((c = ifs.get()) != EOF){
        cout << c;
    }
    ifs.close();
}

4.二进制读写文件:

class Person{
public:
    char m_name[64];
    int m_age;
};
void write2file(){
    ofstream ofs("person.txt", ios::out | ios::binary);
    Person p = {"wzh", 20};
    ofs.write((const char *)&p, sizeof(Person));
}
void readFile(){
    ifstream ifs("person.txt", ios::in | ios::binary);
    if (!ifs.is_open()){
        cout << "打开文件失败!" << endl;
        return;
    }
    Person p;
    ifs.read((char *)&p, sizeof(Person));
    cout << "姓名:" << p.m_name << "年龄:" << p.m_age << endl;
}
int main(){
    write2file();
    readFile();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值