C/C++查缺补漏《5》_智能指针、C和C++中的数组、指针、函数对比、C和C++中内存分配概览

0、前言:

  • 这部分笔记梳理下智能指针和c与c++中内存分配的基础问题。
  • 学习了这么久c和c++的应用,比如qt也好,linux下的c程序开发也好,我发现很多问题其实就是在掌握好基础之后的技术拓展,关于查缺补漏这部分,后续更新,还是以基础部分的纠错,查缺补漏为主。
  • 把基础知识串起来,才能形成知识结构的概览。

1、智能指针:

  • 在 C++ 中,智能指针是一种封装了原始指针的类模板,C++ 智能指针的核心设计目标就是管理堆上动态分配的内存(即通过 new/new[] 开辟的空间),避免内存泄漏(忘记释放内存)和悬垂指针(指针指向已释放的内存)等问题。
  • 但历史意义上一共有四种:std::auto_ptr(c++11废弃、c++17移除)、unique_ptr(C++11 至今,独占)、shared_ptr(C++11 至今,共享)、weak_ptr(C++11 至今,观测)
  • std::auto_ptr废除的原因:标准库容器/算法都假设"复制"后原对象保持不变(或至少等价),auto_ptr 违反了这一契约。如下所示:
cpp 复制代码
std::auto_ptr<int> p1(new int(10));
std::auto_ptr<int> p2 = p1; // 拷贝后,p1 失去所有权(变为空),p2 拥有所有权;
/*
此时若再通过 p1 访问对象(如 *p1),会导致未定义行为(如崩溃),
因为 p1 已不再指向有效内存。这种设计违背了用户对 "拷贝" 的
基本认知(通常认为拷贝后两个对象都有效),极易引发错误。
*/

1.1、std::unique_ptr

  • 特点:独占所有权,不能拷贝,只能移动,自动释放;
  • 模拟实现unique_pt:下面案例是为了体现c++中unique_ptr智能指针特点的"简易版本"
cpp 复制代码
#include <iostream>
using namespace std;

template<class T>
class my_unique_ptr {
    T* ptr;
public:
    explicit my_unique_ptr(T* p = nullptr) : ptr(p) {}
    ~my_unique_ptr() { delete ptr; }

    // 禁止拷贝
    my_unique_ptr(const my_unique_ptr&) = delete;
    my_unique_ptr& operator=(const my_unique_ptr&) = delete;

    // 允许移动
    // noexcept 就是用来控制异常抛出的,在这里起到编译器优化的作用;
    // 构造式移动
    my_unique_ptr(my_unique_ptr&& rhs) noexcept : ptr(rhs.ptr) { rhs.ptr = nullptr; cout << "这里调用了右值传参移动"<<endl; }
    // 赋值式移动
    my_unique_ptr& operator=(my_unique_ptr&& rhs) noexcept {
        if (this != &rhs) { delete ptr; ptr = rhs.ptr; rhs.ptr = nullptr; cout << "这里调用了右值赋值移动" << endl;}
        return *this;
    }

    T& operator*()  const { return *ptr; }
    T* operator->() const { return ptr; }
    T* get()        const { return ptr; }
};

int main() {
    my_unique_ptr<int> p2(new int(42));   // 接管裸指针
    my_unique_ptr<int> u(new int(5));     // 默认构造
    my_unique_ptr<int> u2(new int(8));     // 默认构造
    // auto u2 = u;                    // 错误,已删除拷贝
    auto u4(move(u2));                 // 移动,所有权转移
    auto u3 = move(u);                 // 移动,所有权转移
    if (!(u.get()))
    {
        cout << "u中已经没有资源了" << endl;
    }
    cout << *p2 << ' ' << *u3 << ' ' << *u4 << '\n'; // 42 5
    return 0;
}
/*
release模式调试
打印结果:

这里调用了右值传参移动
这里调用了右值传参移动
u中已经没有资源了
42 5 8
*/
  • 总结:unique_ptr的特点是 "独占 + 可移动 + 自动释放" ,独占的意思一份资源只能对应一个unique_ptr指针,这个资源可以从unique_ptr指针移动给另一个unique_ptr指针,但移动后,被移动的指针会置空;
  • 在std中调用自带的"独占智能指针的方式"
cpp 复制代码
#include <iostream>
#include <memory> // 需包含此头文件

int main() {
    // 创建 unique_ptr 指向动态分配的 int(值为 10)
    std::unique_ptr<int> ptr1(new int(10));
    std::cout << "ptr1 指向的值:" << *ptr1 << std::endl; // 输出:10
     // 无需手动 delete,离开作用域时自动释放内存
    return 0;
}

1.2、std::shared_ptr

  • 特点:共享所有权,引用计数归零才释放;
  • 模拟实现shared_ptr:shared_ptr智能指针案例中重要的是实现这种共享的思路(多个同类对象指向同一内存地址);
cpp 复制代码
#include <iostream>
#include <utility>      // std::swap
#include <atomic>       // 原子引用计数

template <class T>
class my_shared_ptr {

    // 类的成员如果没有显式标明访问权限,默认访问权限是 private。
    /* 1. 控制块:保存对象指针 + 引用计数 + 删除器 */
    struct control_block {
        T* ptr;
        /*
        1、在多线程场景中,多个线程可能同时读写同一个变量。普通变量的读写操作可能被 CPU 拆分为多个指令(如读取、修改、写入),若多个线程交错执行这些指令,可能导致数据不一致(例如经典的 "计数器漏加" 问题)。
        2、而 std::atomic 变量的所有操作(如赋值、自增、比较交换等)都是原子操作------ 这些操作在 CPU 层面是不可分割的,要么完全执行,要么完全不执行,不会被其他线程打断。
        */
        std::atomic<size_t> ref;
        void (*deleter)(T*);   // 简易"函数指针"版删除器,deleter 是一个指针,指向 void(T*) 函数;

        control_block(T* p, void(*d)(T*) = [](T* t) { delete t; })  
            : ptr(p), ref(1), deleter(d) {
        }
        ~control_block() { if (ptr) deleter(ptr); }
    };

    control_block* blk;
public:
    /* 2. 构造:接管裸指针,创建控制块,非空就新建控制块并接管,空就保持空 */
    explicit my_shared_ptr(T* p = nullptr,
        void(*d)(T*) = [](T* t) { delete t; })
        : blk(p ? new control_block(p, d) : nullptr) {
    }

    /* 3. 析构:计数减一,归 0 时销毁控制块(连带对象),这里delete blk 会调用 ~control_block() { if (ptr) deleter(ptr); */
    ~my_shared_ptr() {
        if (blk && --blk->ref == 0) delete blk;
    }

    /* 4. 拷贝构造:共享,计数 +1 
   rhs.blk 里存的是地址,blk(rhs.blk)这实现的是把原对象的blk给到新对象,它两公用一个blk;
    */
    my_shared_ptr(const my_shared_ptr& rhs) noexcept
        : blk(rhs.blk) {
        if (blk) blk->ref.fetch_add(1); // fetch_add(1)操作之后,ref中的值增加1;
    }

    /* 5. 拷贝赋值:先自减,再共享新块 
    调用的时候,新对象=旧对象,则旧对象分享,计数加1,新对象之前的blk就没了,所以新对象计数减1;
    */
    my_shared_ptr& operator=(const my_shared_ptr& rhs) noexcept {
        if (this != &rhs) {
            /* 先减旧块引用 */
            if (blk && --blk->ref == 0) delete blk;
            /* 再共享新块 */
            blk = rhs.blk;
            if (blk) blk->ref.fetch_add(1);
        }
        return *this;
    }

    /* 6. 移动构造:直接偷控制块   【新对象还不存在】
    构造移动,原对象直接置空;
    */
    my_shared_ptr(my_shared_ptr&& rhs) noexcept
        : blk(rhs.blk) {
        rhs.blk = nullptr;
    }

    /* 7. 移动赋值:先自减,再偷块    【新对象已经存在】
    赋值移动,新对象清空,原对象先赋值给新对象再置空;
    */
    my_shared_ptr& operator=(my_shared_ptr&& rhs) noexcept {
        if (this != &rhs) {
            if (blk && --blk->ref == 0) delete blk;
            blk = rhs.blk;
            rhs.blk = nullptr;
        }
        return *this;
    }

    /* 8. 观察接口 */
    long use_count() const noexcept {
        return blk ? blk->ref.load() : 0;
    }
    T* get() const noexcept { return blk ? blk->ptr : nullptr; }
    T& operator*() const { return *blk->ptr; }
    T* operator->() const { return blk->ptr; }
    explicit operator bool() const noexcept { return blk != nullptr; }

    /* 9. reset:放弃当前对象,可选接管新对象 */
    void reset(T* p = nullptr,
        void(*d)(T*) = [](T* t) { delete t; }) {
        if (blk && --blk->ref == 0) delete blk;
        blk = p ? new control_block(p, d) : nullptr;
    }
};

/* 10. 简单测试 */
int main() {
    my_shared_ptr<int> a(new int(42));
    std::cout << "a.use_count() = " << a.use_count() << '\n'; // 1

    my_shared_ptr<int> b = a;          // 拷贝共享
    std::cout << "after copy: a=" << a.use_count()
        << " b=" << b.use_count() << '\n';              // 2 2

    auto c = std::move(b);             // 移动,b 变空
    std::cout << "after move: a=" << a.use_count()
        << " b=" << (b ? b.use_count() : 0)
        << " c=" << c.use_count() << '\n';              // 2 0 2

    std::cout << *c << '\n';           // 42
    return 0;   // 离开作用域,最后一次析构把 int 释放
}     
/*
release模式下打印结果:
a.use_count() = 1
after copy: a=2 b=2
after move: a=2 b=0 c=2
42
*/
  • 总结:
    ①、共享载体
    把"资源 + 元数据"封装成独立结构体(控制块),让多个类对象只保存它的指针 → 实现同类型不同实例共用一份内存。
    ②、共享安全
    对控制块里的引用计数(或任何并发读写变量)用 std::atomic 做原子加减,杜绝竞态条件,防止"漏加 / 多减"。
    ③、生命周期管理
    类对象的构造 / 拷贝 / 移动 / 析构都围绕"原子计数是否为 0"决定是否真正释放共享块,从而用轻量级指针复制替代昂贵数据深拷贝,同时保证最后一个持有者才清理资源。
  • 在std中调用自带的"共享智能指针的方式"
cpp 复制代码
#include <iostream>
#include <memory>

int main() {
    /* 1. 创建共享指针 */
    auto sp1 = std::make_shared<int>(10);
    std::cout << "sp1=" << *sp1 << "  count=" << sp1.use_count() << '\n'; // 1

    /* 2. 拷贝共享 */
    auto sp2 = sp1;
    std::cout << "sp2=" << *sp2 << "  count=" << sp1.use_count() << '\n'; // 2

    /* 3. 改值 */
    *sp2 = 42;
    std::cout << "sp1=" << *sp1 << "  sp2=" << *sp2 << '\n'; // 42 42

    /* 4. 移动:sp3 接管,sp2 变空 */
    auto sp3 = std::move(sp2);
    std::cout << "after move: sp2.empty=" << sp2.use_count() // 0
        << "  sp3=" << *sp3 << "  count=" << sp3.use_count() << '\n'; // 2

    return 0;
}
/*
release模式下
打印结果:
sp1=10  count=1
sp2=10  count=2
sp1=42  sp2=42
after move: sp2.empty=0  sp3=42  count=2
*/

1.3、std::weak_ptr

  • 特点:weak_ptr 就是专为"修" shared_ptr 的天然缺陷而设计的,如下就是使用shared_ptr陷入循环引用的例子;
cpp 复制代码
#include <memory>
#include <iostream>

class B; // 前置声明

class A {
public:
    std::shared_ptr<B> b_ptr; // A 持有 B 的 shared_ptr
    ~A() { std::cout << "A 被销毁" << std::endl; }
};

class B {
public:
    std::shared_ptr<A> a_ptr; // B 持有 A 的 shared_ptr
    ~B() { std::cout << "B 被销毁" << std::endl; }
};

int main() {
    {
    //创建一个 std::shared_ptr 智能指针,该指针管理一个动态分配的 A 类对象,并将这个智能指针赋值给变量 a。
        auto a = std::make_shared<A>();
        auto b = std::make_shared<B>();
        a->b_ptr = b; // a 引用 b(b 的引用计数变为 2)
        b->a_ptr = a; // b 引用 a(a 的引用计数变为 2)
    } // 离开作用域,a 和 b 被销毁(引用计数各减 1,变为 1)

    // 此时 a 和 b 的引用计数仍为 1(互相引用),资源未释放,内存泄漏
    std::cout << "程序结束" << std::endl;
    return 0;
}
// 运行结果只有:程序结束;
  • 使用weak_ptr的案例
cpp 复制代码
#include <memory>
#include <iostream>

class B;

class A {
public:
    std::shared_ptr<B> b_ptr; // A 仍持有 B 的 shared_ptr
    ~A() { std::cout << "A 被销毁" << std::endl; }
};

class B {
public:
    std::weak_ptr<A> a_ptr; // B 持有 A 的 weak_ptr(当 B 类的 a_ptr(weak_ptr<A> 类型)接收 A 对象的 shared_ptr 赋值 时(如 b->a_ptr = a),A 的引用计数不会增加。)
    ~B() { std::cout << "B 被销毁" << std::endl; }
};

int main() {
    {
        auto a = std::make_shared<A>();
        auto b = std::make_shared<B>();
        a->b_ptr = b; // b 的引用计数变为 2
        b->a_ptr = a; // a 的引用计数仍为 1(weak_ptr 不增加计数)
    } // 离开作用域:
      // a 销毁,a 的引用计数减为 0 → A 被释放
      // b 的引用计数减为 1(因 a->b_ptr 被销毁),但此时 B 中 a_ptr 是 weak_ptr,无实际引用
      // b 销毁,引用计数减为 0 → B 被释放

    std::cout << "程序结束" << std::endl;
    return 0;
}
/*
运行结果:
A 被销毁
B 被销毁
程序结束

解释:后定义的先销毁,因此先销毁b,b中引用计数变成1,但还不调用b的析构,
再销毁a,a中引用计数变成0,因此a调用析构函数先被销毁,由于a指向b,
因此,b的引用计数减少,然后b的引用计数变成了0,然后b再调用;
*/

1.4、智能指针小结:

  • 智能指针,能够解决堆上开辟空间后,忘记释放空间的问题。智能指针的使用还是有不足的地方,例如unique_ptr不能拷贝,shared_ptr可能会出现两个类互相指向无法释放的问题,所以日常使用需要注意使用场景,挑选合适的智能指针;

2、C和C++中的数组

  • 定义:数组是一组相同类型元素的集合,所有元素在内存中连续存储。C和C++中的数组都遵循这一基本特性,但C++提供了更丰富的容器类型作为补充。
  • 数组分为静态数组和动态数组,静态数组是在编译器确定大小,内存的分配位置在栈或者全局区,由编译器确定大小管理内存,无需手动释放内存;动态数组在运行期确定大小,内存分配位置在堆区,大小可变,由程序运行时确定,需要手动释放内存。

2.1、静态数组定义:栈区或全局区

  • C语言中数组定义:
c 复制代码
int arr[5];           // 定义5个整数的数组
char str[10];         // 定义10个字符的数组
double scores[20];    // 定义20个双精度浮点数的数组
  • C++语言中数组定义:
cpp 复制代码
int arr[5];           // 与C语言相同
std::array<int, 5> arr; // C++11引入的标准数组容器
  • 不论C还是C++定义,只要先定义了数组,没有初始化,接下来初始化,不能给数组名整体赋值,因为定义之后,数据名就被看作了一个地址常量(不是变量),是编译器分配内存后固定的地址符号,它不占用独立内存,也不能被当作 "左值"(赋值操作的接收方)。 需要单个元素逐个初始化,或者通过memcpy 批量拷贝。注意如果是C++的静态数组用了容器array先定义,是可以接着整体赋值的
    • 附1:为什么可以通过memcpy批量拷贝:void* memcpy(void* dest, const void* src, size_t count); 看memcpy的参数就明白了,第一个参数:void* dest 含义:目标内存的首地址(即你要把数据拷贝到哪里),可以接收任意类型的内存地址。第二个参数const void* src,源内存的首地址,加 const 表示函数内部不会修改源内存的内容,同时允许接收常量 / 变量地址。第三个参数size_t count,要拷贝的字节数(不是元素个数!)。
    • 附2:C++的静态数组用了容器array,本质还是封装了静态数组,但是内部的类重载了赋值运算符,遇到整体赋值,本质还是会挨个赋值,所以可以定义之后,再整体初始化。

2.2、静态数组初始化:

  • C 有的初始化写法,C++ 几乎全兼容;C++ 在此基础上又多出几条自己的语法和容器,C 用不了。
  • C和C++都可以使用的基础的初始化方式:
c 复制代码
// 一维数组初始化:分为完全初始化、不完全初始化、清零极简写法,原则就是数组大小明确。
int a[4] = {1,2,3,4}; // 完全显式
int b[] = {1,2,3};  // 省略长度
int c[5] = {1, 2};  // 不完全初始化 → 剩余置 0
int d[10] = {0};  //清零极简写法

// 二维数组初始化:分为分行初始化,也就是每行元素都用花括号括起来、整体初始化,也就是所有元素写在一个花括号中。原则就是数组中每行有多少元素要明确,也就是"列定行省"原则。
int g[][3] = { {1,2,3}, {4,5,6} }; // 二维数组(行优先,列的大小必须是常量且明确给出,行可省略,因为二维数组是按照列划分数组的)
int h[][3] = { 1,2,3, 4,5,6 };  // 这样定义二维数组也可以【列定行省】
int m[3][4] = {0}; // 仅给第一个元素赋值 0,所有元素都会置 0

// 二维数组的调用
cout << sizeof(g)/sizeof(g[0])<<endl;
for(int i= 0; i<sizeof(g)/sizeof(g[0]); i++)
{
    cout<< *(g[i]) << *(g[i]+1) << *(g[i]+2) <<" other show : "<< g[i][0] << g[i][1] << g[i][2] << endl;
} // 打印结果一样
  • C++独有的初始化方式:
cpp 复制代码
int a[]{1,2}; // 列表初始化,省略等号
std::array<int, 3> a{1, 2, 3};    // STL中的静态数组,列表初始化
constexpr int arr[]{1,2}; // constexpr是强制表达式 / 变量 / 函数在「编译期」完成计算,而非运行期,是实现「编译期编程」的基础。
/*
注意:constexpr要用于函数,就要在函数声明时在函数类型前面加该关键字,然后在调用的时候,就能编译期给constexpr修饰的变量返回对应的值。

constexpr int square(int x) {
    return x * x; // 函数体必须简单(C++11 要求只有一条 return,C++14 放宽)
}
constexpr int res = square(4);
*/
  • 对于C而言,上面总结只是C89的特性,如果是C99的话,还有比较有意思的初始化,C99还有新的方法,但是目前开发比较通用的C版本都是C89。

2.3、动态数组定义:堆区

  • C语言中定义,必须检查 malloc/calloc/realloc 返回值(内存不足时返回 NULL),用 free(arr) 释放。
c 复制代码
// n 是运行期变量
// malloc,仅分配内存,未初始化(内存值为随机垃圾值),失败返回null
int* arr = (int*)malloc(n * sizeof(int));

// n 是运行期变量
// calloc,自动将所有元素初始化为 0(等价于 malloc + memset),失败返回null
int* arr = (int*)calloc(n, sizeof(int));

// n 是运行期变量
// realloc,把原来那块堆内存"变大"或"变小",失败返回null
arr = (int*)realloc(arr, new_n*sizeof(int));
  • C++中定义:malloc/calloc/realloc 在 C++ 中可以正常使用,编译器完全支持,语法和 C 语言一致;但 C++ 有更贴合自身特性的 new/delete,因此在纯 C++ 开发中,除非是兼容 C 代码、手动精细控制内存等特殊场景,否则更推荐用 new/delete 或 std::vector。
cpp 复制代码
// 仅分配内存,未初始化(随机值),n就是动态数组的大小;
int* arr = new int[n];   

// 使用vector是定义加初始化
std::vector<int> vec(n);//大小 n,默认初始化为 0
std::vector<int> vec(n, 10);//大小 n,全部初始化为 10
std::vector<int> vec{1,2,3,4,5};//列表初始化,推导大小
vec.push_back(6); / vec.resize(10);
/*
- 若新大小 > 当前大小:新增元素默认值初始化(int 为 0,string 为空串),也可指定初始值(vec.resize(10, 5):新增元素填 5);
- 若新大小 < 当前大小:删除末尾多余元素,容器大小缩小,但容量(capacity)不会自动缩减(需手动调用shrink_to_fit()释放多余容量)。
*/

2.4、动态数组初始化:

  • C语言,
c 复制代码
//方式 1:循环赋值
for(int i=0; i<n; i++) arr[i] = i;
//方式 2:memset 清零
memset(arr, 0, n * sizeof(int));
  • C++:在C++中对于有复杂结构的堆空间,不能使用memset填充。
cpp 复制代码
//方式 1:循环赋值
for(int i=0; i<n; i++) arr[i] = i;
//方式 2:std::fill
std::fill(arr, arr+n, 0);

// 释放
delete[] arr; 
arr = nullptr;

3、C和C++中的指针

3.1、先概览指针的作用

  • 直接操作内存: 不论是C还是C++中,指针的作用就是直接操作内存,提升程序效率,在传递大结构体、数组给函数时,直接传递值会拷贝整个数据,传指针的话只要传递内存地址,就可以定位数据,可以大幅提升效率。在嵌入式开发或者驱动编程中,指针可以直接访问硬件寄存器的内存地址,这是高级语言python无法做到的。
  • 通过函数修改外部值: C/C++ 中函数参数默认是 "值传递"(函数内修改的是拷贝,不影响外部),指针能打破这个限制,让函数直接修改外部变量的值。
  • 指向分配的动态内存: 程序运行时需要灵活申请 / 释放内存(比如不确定数组大小),必须通过指针接收动态内存的地址。
  • 实现复杂的数据结构: 链表的节点需要通过指针指向 "下一个节点",树的节点需要指针指向 "左孩子""右孩子"------ 没有指针就无法构建这些非连续存储的结构。
  • 对于C++还可以实现多态和智能指针: C++ 中,指向基类的指针可以指向派生类对象,是实现多态的基础;同时 C++11 后的智能指针(unique_ptr、shared_ptr)本质也是对原生指针的封装,解决内存泄漏问题。

3.2、C和C++中指针的区别

  • 空指针: C 只有NULL/0,C++11 + 推荐nullptr(解决NULL的二义性);
  • 类型安全: C 允许void*隐式转换,C++ 必须显式转换,类型检查更严格;
c 复制代码
// void* 隐式转换为int*,C语言允许,C++不允许
int* p = malloc(sizeof(int)); 
// 显式类型转换,C和C++都允许
int* p1 = (int*)malloc(sizeof(int));
  • 初始化能力: C 需 "分配 + 手动初始化" 分离,C++ 的new支持一步到位,且独有智能指针(自动管理内存)。
c 复制代码
// 在c当中先申请空间再初始化:
int* p = (int*)malloc(sizeof(int));  // 1. 分配内存(申请一个装int的盒子)
*p = 10; // 2. 初始化 

// 在C++中可以申请空间和初始化一起完成
int* p = new int(10); // 分配内存 + 初始化值10,一步完成!
  • 写 C 代码指针可以先用NULL初始化,因为在C中,用NULL初始化指针是标准、安全的做法(NULL在 C 中本质是(void*)0,天然关联指针语义)。写 C++ 代码优先用nullptr和new,在C++中会认为NULL是0,而非指针,所以要使用nullptr初始化指针,避免混用 C/C++ 初始化方式导致的兼容性问题。

4、C和C++中的函数

4.1、函数的分类

  • C和C++中文件级独立函数 (不隶属于任何类的函数),按照作用域/链接属性分,可分为:
    • 全局函数:跨文件可见(外部链接),是最基础的普通函数;
    • 静态函数:仅当前文件可见(内部链接),是作用域被限制的普通函数。
  • 补充:C++中独有的类成员函数(隶属于类,非文件级独立函数),不纳入上述分类,单独总结在C++类的知识部分。

4.2、概览C和C++中函数的区别:

  • C 的函数追求简洁、贴近底层,而 C++ 在兼容 C 函数的基础上,扩展了面向对象、类型安全、代码复用等高级特性。
  • 基本语法: C语言的函数不支持重载(同名函数会编译报错),没有默认参数(函数的形参不支持默认参数),没有内联函数的说法,C++的函数支持重载,有默认参数,还有内联函数。
  • 面向对象的特性: C语言没有是面向过程的,C++在兼容C语言的基础上,又有了面向对象的特性,因此有了各种成员函数,虚函数,友元函数。
  • 函数作用域: C语言函数作用域是全局、C++函数作用域有全局、类内、命名空间当中。
  • 拓展方面: C++中的函数还可以写成函数模板,这样就可以给不同类型的数据适配同一套逻辑了。C++还支持匿名函数,适合临时、简短的函数逻辑(如算法回调)。

4.3、函数的写法:

  • C++ 完全兼容 C 的基础函数语法,包括函数声明、定义、调用、返回值、参数传递(值传递、指针传递)等核心写法,二者完全一致。
  • 函数定义的基础语法:
cpp 复制代码
// C/C++都支持:全局函数定义
int add(int a, int b) {
    return a + b;
}

// C/C++都支持:静态函数定义(仅当前文件可见)
static void print(int val) {
    printf("%d\n", val); // C用printf,C++可混用printf/cout
}
// C 和 C++ 中,static修饰的文件级函数都仅当前文件可见,跨文件用extern声明也无法调用,链接时会报错 ------ 规则完全一致。
  • 函数声明的基础语法:
cpp 复制代码
// C/C++都支持:函数声明(跨文件调用全局函数时用)
extern int add(int a, int b); 
// 省略extern也可,C/C++默认函数声明为extern
int add(int a, int b);

5、C和C++中的内存分配:

5.1、概览:

  • C++ 在兼容 C 的内存分区基础上,新增了两个具有专属语义的内存区域;
  • 核心分区(C 和 C++ 都有):代码区、全局 / 静态区、栈区、堆区、常量区;
  • C++ 新增:自由存储区(逻辑上属于堆区,是 new/delete 的专属语义区域)、TLS 区(线程本地存储区,C++11 新增);
  • 说明:C++ 完全兼容 C 在堆区通过 malloc/calloc/realloc/free 开辟 / 释放动态内存的方式;同时 C++ 通过 new/delete 在自由存储区分配内存(区别于 C 的 "裸内存分配",new 会调用构造函数、delete 会调用析构函数),适配面向对象的内存管理。
相关推荐
幽络源小助理2 小时前
SpringBoot+Vue大学城水电管理系统源码 | 后勤设备管理 | 幽络源
java·开发语言
闻林禹2 小时前
c++并发编程
开发语言·c++
CTO Plus技术服务中2 小时前
一栈式、系统性的C、C++、Go、网络安全、Linux运维开发笔记和面试笔记
c++·web安全·golang
zc.ovo2 小时前
线段树优化建图
数据结构·c++·算法·图论
WaWaJie_Ngen2 小时前
C++实现一笔画游戏
c++·算法·游戏·游戏程序·课程设计
小尧嵌入式2 小时前
【Linux开发一】类间相互使用|继承类和构造写法|虚函数实现多态|五子棋游戏|整数相除混合小数|括号使用|最长问题
开发语言·c++·算法·游戏
黎雁·泠崖2 小时前
Java数组进阶:内存图解+二维数组全解析(底层原理+Java&C差异对比)
java·c语言·开发语言
Remember_9932 小时前
【JavaSE】一站式掌握Java面向对象编程:从类与对象到继承、多态、抽象与接口
java·开发语言·数据结构·ide·git·leetcode·eclipse
你的冰西瓜2 小时前
C++中的map容器详解
开发语言·c++·stl