C++ 针对于 C 语言的增强

1. C++ 概述

  • 起源:C++ 由丹麦计算机科学家比雅尼·斯特劳斯特鲁普(Bjarne Stroustrup)于 20 世纪 80 年代初在贝尔实验室开发。最初,它被称为“C with Classes”,是在 C 语言的基础上增加了面向对象编程的特性,目的是为了提高软件开发的效率和可维护性。
  • 标准化:1998 年,国际标准化组织(ISO)发布了第一个 C++ 标准(ISO/IEC 14882:1998),此后经过多次修订和更新,如 C++03、C++11、C++14、C++17、C++20 和 C++23 等版本,不断引入新的特性和改进,以适应不断变化的编程需求。

2. C++ 针对于 C 语言的增强内容

2.1 补充 bool 类型

bool 类型是 C++ 补充数据类型,占用内存字节数为 1,数据对应的常量为 true false,依然遵循非 0 即真原则。默认 true(1) , false(0)

/*
这是 CPP 标准输入输出库,可以认为是 stdio.h,比 C
语言stdio.h 功能强大。
*/
#include <iostream>

/*
namespace 命名空间,可以用于切换开发模式,限制数据,函数,类型范围。
*/
using namespace std;

int main(int argc, char const *argv[])
{
    bool ret1 = true;
    bool ret2 = false;

    /*
    cout 是 C++ 中的输出操作对应的输出流 ostream
    << cout 控制台输出特征运算符,后续可以【重载 reload <<】运算符完成
    自定义类型控制展示,或者字符串数据转换
    endl 表示当前 cout 打印展示结束,同时可以进行换行操作。
    */
    cout << "ret1 : " << ret1 << endl; // true 展示效果为 1
    cout << "ret2 : " << ret2 << endl; // false 展示效果为 0

    // sizeof(bool) ==> 1 C++ bool 占用内存字节数为 1 字节。
    // 后续开发中极限操作下,可以使用一个字节对应 8 个 bool 类型。
    cout << "sizeof(bool) : " << sizeof(bool) << endl;
  

    return 0;
}
2.2 结构体增强

CPP 相较于 C 语言结构体增强内容

  • 简化类型名称,CPP 结构体 struct 关键字之后的名称就是当前结构体数据类型名称
  • CPP 结构体中可以定义函数,当前函数为成员函数,可以使用结构体内部的其他组成部分,包括成员变量和成员函数,同时当前成员函数的调用方式需要通过结构体变量或者结构体指针操作实现。
#include <iostream>

using namespace std;

/*
CPP 结构体
    1. 当前结构体的数据类型名称为
        Student
        CPP 中简化操作,省略 struct 结构体数据类型标记
        同时可以省略 typedef 对当前数据类型进行重命名操作
    2. 【重要区别】
        CPP 结构体中支持函数定义和实现。并且函数可以直接使用
        结构体中成员变量。
*/
struct Student
{
    char name[32];
    int id;
    short age;
    char gender;

    void show()
    {
        /*
        当前函数中使用的变量,都是当前结构体定义声明的
        成员变量内容,光标放置提示
            name ==> char Student::name[32]
            :: 作用域运算符/四饼/的,告知调用者当前变量或者函数
            属于哪一个数据类型,同时数据类型都是复合数据类型,例如
            struct 结构体和 class 类

        这里隐含一个 【this】
        */
        cout << "Name : " << name
             << ", ID : " << id
             << ", Age : " << age
             << ", Gender : " << gender
             << endl;
    }
};

int main(int argc, char const *argv[])
{
    Student stu1 = {"James", 1, 40, 'M'};

    /*
    【重点】
        当前 show 函数是一个成员函数,要求通过当前 Student 结构体
        对应的变量进行调用,操作类似于结构体变量使用成员变量方式,

        分为两种情况
            1. 结构体变量数据使用 .
            2. 结构体指针数据使用 ->
    */
    stu1.show();

    Student *p_stu = &stu1;
    p_stu->show();

    return 0;
}
2.3 new 和 delete 动态内存管理

C 语言中动态内存申请

void *malloc(size_t size);
void *calloc(size_t n, size_t size);
void *realloc(void *p, size_t size);

内存释放

void free(void *p);

CPP 中补充了 new 和 delete 动态内存管理,更为方便简单。

  • new :动态申请内存【堆区】空间,存储目标类型。【注意】new 申请的内存空间不会进行任何的数据擦除操作,存在【野值】问题,必要时可以使用 memset 函数进行内存擦除操作,或者保证在使用 new 申请内存空间之前,进行必要的初始化赋值操作。
  • delete:释放通过 new 关键字申请的内存空间。

new + delete 操作 CPP 结构体

#include <iostream>

/*
CPP 导入相关 C 语言头文件,通常情况下会采用 cxxx 方式
保证代码中的格式一致化。

#include <cstring> <====> #include <string.h>
*/
#include <cstring>

using namespace std;

/*
new 和 delete 操作
*/

struct Student
{
    char name[32];
    int id;
    short age;
    char gender;

    void show();
};

/*
明确告知编译器,当前实现的 show 函数是 Student 结构体中的 show 函数内容
*/
void Student::show()
{
    cout << "Name : " << name
         << ", ID : " << id
         << ", Age : " << age
         << ", Gender : " << gender
         << endl;
}

int main(int argc, char const *argv[])
{
    /*
    void *malloc(size_t size);
        Student *stu1 = (Student *)malloc(sizeof(Student));

    new 关键字申请结构体内存空间
        new 是告知编译器,当前申请内存堆区空间
        Student 数据类型是告知编译器,申请的内存空间存储数据类型
            为 Student 类型,需要计算对应内存大小,同时返回值对应
            地址类型为 Student *,不需要进行强制转换。
    【注意】
        new 申请的内存空间,不会进行任何的数据擦除操作,依然按照基本
        内存申请进行赋值清除之后才可以正常使用的原则。
        【先申请,后使用,先赋值,后使用】
    */
    Student *p_stu = new Student;

    strcpy(p_stu->name, "James");
    p_stu->age = 40;
    p_stu->id = 1;
    p_stu->gender = 'M';

    p_stu->show();

    /*
    delete 操作用于将当前 new 申请的内存空间进行释放操作,
    要求提供给 delete 关键字的信息是对应内存空间首地址
    */
    delete p_stu;

    Student *p_stu2 = new Student;

    p_stu2->show();

    delete p_stu2;

    return 0;
}

new + delete 操作数组

#include <iostream>

#include <cstring>

using namespace std;

int main(int argc, char const *argv[])
{
    /*
    利用 new 关键字申请连续内存空间,要求存储的数据内容
    是 10 个 int 类型数据

    new int[10];  告知 CPU 申请内存堆区空间,按照当前计算机
    情况,存储 10 个 int 类型内存。
    */
    int *arr = new int[10];

    /*
    new 关键字申请的内存空间,如果需要保证内存数据安全,可以进行擦除操作
    利用 memset 函数
        void memset(void *p, int ch, size_t size);
    */
    memset(arr, 0, sizeof(int) * 10);
  

    for (size_t i = 0; i < 10; i++)
    {
        cout << "arr[" << i << "] = " << arr[i] << endl;
    }

    /*
    【重点】
        new 申请内存时使用了 [], 后续进行内存释放,delete 释放内存操作
        必须同样加上 []
    */
    delete[] arr;

    return 0;
}

3. namespace 命名空间

3.1 C++ 命名空间作用

可以在大型项目中,利用命名空间区分不同开发者,不同开发模块的,相同标识符(数据类型名称,函数名,变量名)

例如:车辆驾驶模式控制,要求提供给车辆 ECU 的模块数据名是一致的,但是提供的数据具体数值不同,从而满足车辆模式切换。

使用的关键字是 namespace

3.2 C++ 中常用 std 命名空间
#include <iostream>

// #define MODE_1
#define MODE_2
// #define MODE_3

/*
C++ 常用命名空间,主要包括
    C++ 常用类型,C++ 基础输入输出操作。

    如果没有导入 std 基础命名空间,会导致代码中的
    特定类型或者操作无法使用

*/
#ifdef MODE_1
using namespace std;
#endif

#ifdef MODE_3
// 单独引入 std 命名空间中的 cout 和 endl 操作
using std::cout;
using std::endl;
#endif

int main(int argc, char const *argv[])
{
#ifdef MODE_1
    cout << "123" << endl;
#endif

#ifdef MODE_2
    /*
    如果代码中没有导入 std ,可以明确告知当前编译器
    使用的 cout 和 endl 所属命名空间关系
    */
    std::cout << "123" << std::endl;
#endif

#ifdef MODE_3
    cout << "123" << endl;
#endif
    return 0;
}
3.3 C++ 自定义命名空间
#include <iostream>

using namespace std;

#define MODE_1
// #define MODE_2

/*
自定义命名空间
*/
namespace NS_A
{
    /*
    命名空间中可以完成
        1. 数据类型声明
        2. 变量声明
        3. 函数声明
    */

    // 自定义命名空间中定义数据类型
    struct Student
    {
        char name[32];
        int id;
        short age;
        char gender;

        void show();
    };

    // 自定义命名空间中完成变量定义和赋值
    int num = 10;

    // 自定义命名空间中声明函数
    void test_namespace();
};

// 利用作用域运算符和命名空间名称,明确告知编译器,当前实现的函数是
// 命名空间 NS_A 中 test_namespace
void NS_A::test_namespace()
{
    cout << "命名空间中 NS_A 中的函数" << endl;
}

/*
利用作用域运算符,多重层级关系,明确告知当前函数 show 是
命名空间 NS_A 其中声明的结构体类型 Student 中的成员函数
*/
void NS_A::Student::show()
{
    cout << "Name : " << name
         << ", ID : " << id
         << ", Age : " << age
         << ", Gender : " << gender
         << endl;
}

#ifdef MODE_2
using namespace NS_A;
#endif

int main(int argc, char const *argv[])
{
#ifdef MODE_1
    /*
    如果没有明确的引入使用自定义命名空间,可以利用作用域运算符
    告知编译器,当前 Student 类型是命名空间 NS_A 中的数据类型
    */
    NS_A::Student stu = {"James", 1, 40, 'M'};

    stu.show();

    NS_A::test_namespace();

    cout << "NS_A::num : " << NS_A::num << endl;
#endif

#ifdef MODE_2
    Student stu = {"James", 1, 40, 'M'};

    stu.show();

    test_namespace();

    cout << "num : " << num << endl;
#endif

    return 0;
}
3.4 命名空间冲突问题

在项目中引入了多个命名空间,但是在不同的命名空间中,存在相同内容,例如同名函数,同名变量,同名类型。需要利用【作用域运算符解决】

#include <iostream>

using namespace std;

namespace NS_A
{
    int num = 20;
};
namespace NS_C
{
    int num = 100;
};

using namespace NS_A;
using namespace NS_C;

int main(int argc, char const *argv[])
{
#if 0
    // 因为 main 中定义了局部变量 num,和命名空间中冲突
    // 计算机考虑就近原则问题,直接使用 main 函数中的 num
    // 打印结果为: num : 2000
    int num = 2000;
    cout << "num : " << num << endl;

#else
    /*
    同时引入 NS_A NS_C 两个命名空间,同时存在同名变量 num
    对于当前程序而言,两个命名空间优先级一致,无法明确的约束
    告知 num 属于哪一个命名空间,提示【语法错误】
    */
    // cout << "num : " << num << endl;

    /*
    【解决方案】
        利用命名空间名称和作用域运算符,明确告知当前 num 属于哪一个命名空间
    */
    cout << "NS_A::num : " << NS_A::num << endl;
    cout << "NS_C::num : " << NS_C::num << endl;

#endif
    return 0;
}
3.5 命名空间延续性
#include <iostream>

using namespace std;

/*
当前代码中看似声明了两个命名空间 NS_A 
但是实际上第一个是 NS_A 命名空间声明,第二个
是针对于当前已存在的命名空间 NS_A 进行补充内容·
*/
namespace NS_A
{
    int n = 10;
};

namespace NS_A
{
    void test() { cout << "测试函数" << endl; }
};

/*
利用 using 关键字明确使用指定命名空间 NS_A
可以认为当前 NS_A 中有两个内容
    1. 成员变量 n
    2. 成员函数 test
*/
using namespace NS_A;

int main(int argc, char const *argv[])
{
    cout << "n : " << n << endl;
    test();

    return 0;
}
3.6 命名空间小案例

使用命名空间,设置当前软件执行的两种模式,分别为测试 DEBUG 模式,已经正常模式

#include <iostream>

using namespace std;

// #define _DEBUG_

namespace Debug_Model
{
    void test() 
    {
        cout << "LOG : test 函数执行开始!" << endl;
        cout << "代码功能实现" << endl;
        cout << "LOG : test 函数执行完毕!" << endl;
    }
};
namespace Normal_Model
{
    void test() 
    {
        cout << "代码功能实现" << endl;
    }
};

#ifdef _DEBUG_
using namespace Debug_Model;
#else
using namespace Normal_Model;
#endif

int main(int argc, char const *argv[])
{
    test();
    return 0;
}

4. Reference 引用【重点】

4.1 引用概述

引用是 C++ 补充内容,可以直接认为是当前变量本身!!!

涉及到的运算标识是 &

4.2 引用的基本语法
#include <iostream>

using namespace std;

int main(int argc, char const *argv[])
{
    int n = 10;
  
    /*
    定义一个引用,当前引用对应的变量为 n
    & 之前是一个数据类型 int ,之后使用一个引用名称 r1
    利用赋值运算符,明确告知编译器,当前 r1 是 n 的【引用】

    【引用可以直接认为是当前变量本身】【别名】
    */
    int &r1 = n;

    cout << "n : " << n << endl;
    cout << "r1 : " << r1 << endl;

    /*
    通过当前 int 类型变量 n 的引用 r1 进行赋值操作
    修改的内容就是 n 本身
    */
    r1 = 1000;

    cout << "n : " << n << endl;
    cout << "r1 : " << r1 << endl;

    /*
    直接重新赋值变量 n,无论后续是通过 n 访问其中的变量
    还是利用别名 r1 访问其中的变量,都是同一个内存区域
    */
    n = 20000;

    cout << "n : " << n << endl;
    cout << "r1 : " << r1 << endl;

    /*
    r2 是 r1 的引用,r1 是 n 的引用
    可以认为 r2 就是 n 的引用
    */
    int &r2 = r1;

    cout << "n : " << n << endl;
    cout << "r1 : " << r1 << endl;
    cout << "r2 : " << r2 << endl;

    r2 = 50;

    cout << "n : " << n << endl;
    cout << "r1 : " << r1 << endl;
    cout << "r2 : " << r2 << endl;
    return 0;
}
4.3 引用使用案例

基本数据类型

#include <iostream>

using namespace std;

void assign(int n);
void assign_pointer(int *p);
void assign_reference(int &ref); // 【重点】

int main(int argc, char const *argv[])
{
    int n = 0;

    /*
    【值传递】
        仅提供当前变量中存储的数据给予 assign 函数使用,并不是
        提供变量 n 本身交给  assign。函数执行之后,main 函数 n 
        存储数据不变
    */
    assign(n);
    cout << "n : " << n << endl; // n = 0;

    /*
    【地址传递】
        当前函数所需参数是一个目标数据类型为 int 类型的地址,
        提供给函数的实际参数是变量 n 的内存空间首地址。
    */
    assign_pointer(&n);
    cout << "n : " << n << endl; // n = 100;

    /*
    void assign_reference(int &ref);
    【引用传递】
        当前函数声明所需参数是一个 int &ref,是一个 int 类型变量的引用
        要求提供给当前函数的实际参数必须是一个 int 类型变量
        函数调用提供的实际参数是 n ,可以认为内部代码执行效果是
            int &ref = n;
            ref 是当前 main 函数中的 n 的引用
            引用可以直接认为是变量本身,所以在内部函数进行 ref 赋值操作
            操作的实际内容是当前 main 函数定义的变量 n  
    */
    assign_reference(n);
    cout << "n : " << n << endl; // n = 20000;

    return 0;
}
void assign_reference(int &ref)
{
    ref = 20000;
}

void assign_pointer(int *p)
{
    *p = 100;
}

void assign(int n)
{
    /*
    当前赋值操作仅是完成了对于当前 assign 函数内部局部变量
    n 的赋值操作,和 main 函数中的 n 没有任何的关系。
    */
    n = 100;
}

结构体类型

#include <iostream>

#include <cstring>

using namespace std;

struct Student
{
    char name[32];
    int id;
    short age;
    char gender;

    void show()
    {
        cout << "Name : " << name
             << ", ID : " << id
             << ", Age : " << age
             << ", Gender : " << gender
             << endl;
    }
};

void assign_student(Student stu);
void assign_student_reference(Student &ref);

int main(int argc, char const *argv[])
{
    Student stu;

    /*
    操作之后,当前 main 函数中的结构体变量 stu 中
    存储的数据无法预估,存在【野值】。因为当前参数
    提供给函数的内容并不是 main 函数结构体变量本身
    */
    assign_student(stu);
    stu.show();

    /*
    引用就是当前变量本身,就可以认为函数调用操作,实际参数
    提供给当前引用参数的形式为
        Student &ref = stu;
        ref 就是当前 main 函数内部的结构体变量 stu
    */
    assign_student_reference(stu);
    stu.show();

    return 0;
}

void assign_student(Student stu)
{
    strcpy(stu.name, "James");
    stu.id = 1;
    stu.age = 40;
    stu.gender = 'M';
}
void assign_student_reference(Student &ref)
{
    strcpy(ref.name, "James");
    ref.id = 1;
    ref.age = 40;
    ref.gender = 'M';
}
4.4数组的引用

Snipaste_2025-03-11_09-18-03.png

5. const 关键字

const修饰的常变量必须在定义时初始化!

5.1 const 修饰内容无法修改
#include <iostream>

using namespace std;

int main(int argc, char const *argv[])
{
    /*
    两种写法都是满足当前代码需求
    */
    // int const n = 10;
    const int n = 10;

    /*
    表达式必须是可修改的左值C/C++(137)

    赋值号: 将赋值号右侧的数据,赋值给左侧的变量
    当前情况下,n 使用后 const 修饰之后,按照 C/C++ 语法
    要求 const 修饰的内容无法修改,当前 n 可以认为是带有
    名字的常量。n 无法被重新赋值。
    */
    n = 200; // 【ERROR】
    cout << "n : " << n << endl;

    /*
    const 修饰的变量内容,必须在定义时进行初始化操作。否则语法报错
    */
    const int num; // 【ERROR】

    num = 1000; // 【ERROR】

    return 0;
}
5.2 const 和指针的爱恨情仇
#include <iostream>

using namespace std;

/*
指向不可变: 当前指针存储地址不可变
指向空间不可变 :当前指针指向内存中的数据不可变
*/

int main(int argc, char const *argv[])
{
    int n1 = 10;
    int n2 = 20;

    /*
    const 在 * 号之前!
        const int * p1 = &n1;
        int const * p1 = &n1;
    以上写法没有区别!!!
    */
    const int *p1 = &n1;

    p1 = &n2;   // 指向发生改变!
    *p1 = 2000; // 指向内存空间改变! 【ERROR】
    /*
    当前 const 修饰的内容实际上 *p1
        可以认为 const 限制内容是当前指针指向内存空间不可以修改。
        但是当前指针存储的地址可以修改。
    【指向可变,指向内存空间数据不可变】
    */

    int * const p2 = &n1;

    p2 = &n2;   // 指向发生改变! 【ERROR】
    *p2 = 2000; // 指向内存空间改变!
    /*
    当前 const 修饰的内容实际上 p2
        可以认为 p2 指针变量中存储的地址无法修改
        但是通过 p2 指针存储地址,访问对应的内存空间可以修改数据
    【指向不可变,指向内存空间数据可变】
    */

  
    const int * const p3 = &n1;

    p3 = &n2;   // 指向发生改变! 【ERROR】
    *p3 = 2000; // 指向内存空间改变! 【ERROR】
    /*
    当前 p3 指针变量,采用两个 const 修饰内容,
        修饰之后
        【指向不可变,指向内存空间数据不可变】
    */

    return 0;
}
5.2 经典面试题案例
#include <iostream>

#include <cstring>

using namespace std;

struct Student
{
    char name[32];
    int id;
    short age;
    char gender;

    void show()
    {
        cout << "Name : " << name
             << ", ID : " << id
             << ", Age : " << age
             << ", Gender : " << gender
             << endl;
    }
};

int main(int argc, char const *argv[])
{
    Student stu1 = {"James", 1, 40, 'M'};

    const Student * const p1 = &stu1;
    /*
    【问】以下哪些正确
        A. p1 = new Student;
        B. p1->age = 10;
        C. strcpy(p1->name, "Curry");
        D. *p1.id = 10;
    */
    return 0;
}