C++ 基础 学习笔记

主要基于 C++ 贺同学

一、 C++ 语言基础知识

1. 智能指针

在传统的 C++ 开发中,手动管理内存(new/delete)极易引发三个致命问题:

  • 内存泄漏:申请了空间但忘记释放。
  • 野指针/悬空指针:指针指向的内存已被释放,但指针还在被使用。
  • 重复释放:对同一块内存调用了两次 delete,导致程序崩溃。

智能指针的核心思想是 RAII(Resource Acquisition Is Initialization,资源获取即初始化)。

  • 封装成类:它将原始指针封装在一个类中。
  • 自动析构:利用 C++ 的局部对象生命周期管理特性。当智能指针对象超出其作用域(Scope)时,编译器会自动调用它的析构函数。
  • 释放资源:我们在析构函数内部编写了释放内存的逻辑,从而确保资源一定会被回收,无需程序员手动干预。

常用接口:

  • T* get():获取封装在内部的原生指针(裸指针)。
  • operator* / operator->:重载了指针操作符,让智能指针用起来和普通指针一模一样。
  • T* release()交出控制权。将内部指针置为 nullptr,并返回原来的裸指针。注意:它不会释放裸指针指向的内存!(需要手动 delete 返回的指针)。
  • void reset(T* ptr = nullptr)重置/替换。先释放当前管理的内存(如果有),然后接管传入的新指针 ptr

1.1. auto_ptr (C++98 引入,C++11 已被明确废弃)

  • 机制:采用所有权剥夺模式
  • 致命缺点:存在隐式的内存崩溃风险。
auto_ptr<string> p1(new string("hello"));
auto_ptr<string> p2 = p1; // 编译通过,不报错。
// 此时 p1 的控制权被 p2 剥夺,p1 变成了 nullptr。
// 如果之后代码继续访问 p1->size(),程序会直接崩溃 (Crash)!

1.2. unique_ptr (C++11 引入,用来替代 auto_ptr)

  • 机制独占式拥有。保证同一时间内只有一个智能指针可以指向该对象。
  • 安全性:直接在编译阶段扼杀隐患。
  • 优点:零额外开销(和裸指针一样快),极其安全,避免资源泄漏的首选。
unique_ptr<string> p3(new string("auto"));
unique_ptr<string> p4 = p3; // ❌ 编译报错!禁止直接拷贝赋值。

// 如果真的想转移所有权,必须显式使用 std::move
unique_ptr<string> p5 = std::move(p3); // ✅ 成功,p3 置空,p5 接管

1.3. shared_ptr (共享型,强引用)

  • 机制共享式拥有。利用引用计数 (Reference Counting) 机制,允许多个指针指向同一个对象。
  • 生命周期:每多一个 shared_ptr 指向该资源,计数 +1;每销毁一个,计数 -1。当最后一个引用被销毁(计数为 0)时,自动释放资源。
  • 关键函数use_count() 可以查看当前有多少个指针共享该资源。

1.4. weak_ptr (弱引用)

  • 机制不控制对象生命周期的观察者。它依附于 shared_ptr 存在。
  • 特点:它的构造和析构不会引起引用计数的增加或减少。它只提供对对象的访问手段,没有重载 *->
  • 用法:需要访问资源时,调用 lock() 函数。如果资源还没被释放,lock() 会返回一个临时的 shared_ptr 供你使用;如果资源已释放,返回空的 shared_ptr
  • 核心作用:解决循环引用(死锁)问题
    • 循环引用场景:对象 A 内部有一个 shared_ptr 指向对象 B;对象 B 内部也有一个 shared_ptr 指向对象 A。两者的引用计数永远降不到 0(最低是 1),导致内存永远泄漏。
    • 解决方案:将 A 或 B 其中一方内部的智能指针换成 weak_ptr 即可解决。
  • 注意:lock() 是一个原子操作(Atomic Operation)。它把“检查对象是否存活”和“增加引用计数防止被销毁”这两个动作绑定在了一起。要么一起成功,要么直接返回空指针。因此,永远使用 lock() 去访问 weak_ptr,而不是先查 expired()

2. 内存分配

2.1. 栈区 (Stack)

  • 特性:由编译器自动分配和释放,操作方式类似于数据结构中的栈(先进后出 LIFO)。分配效率极高
  • 存储内容:局部变量、函数的参数值、函数返回地址等。
  • 生长方向:通常是向下生长(从高地址向低地址扩展)。
  • 致命风险(Stack Overflow):栈的空间非常有限(在 Linux 系统下默认通常是 8MB)。如果你开了一个超大的局部数组(比如 int arr[1000000];),或者递归调用层数太深(没有退出条件),就会把栈撑爆,导致栈溢出崩溃

2.2. 堆区 (Heap)

  • 特性:动态内存分配区域,必须由程序员手动申请(new/malloc)和释放(delete/free)。空间非常大,几乎受限于机器的物理内存。
  • 生长方向:通常是向上生长(从低地址向高地址扩展)。
  • 致命风险
    • 内存泄漏 (Memory Leak)new 了之后忘记 delete
    • 内存碎片 (Memory Fragmentation):频繁地分配和释放不同大小的小块内存,会导致内存空间变得七零八碎,虽然总空间够,但申请不出一整块连续的内存。

2.3. 全局/静态存储区 (Global/Static Segment)

这里存放的是生命周期贯穿整个程序运行期的变量(全局变量和 static 静态变量)。在底层,它被细分为两个相邻的区域:

  • .data 区(已初始化数据区):存放已经明确初始化且不为 0 的全局变量和静态变量。
  • .bss 区(未初始化数据区):存放未初始化或初始化为 0 的全局变量和静态变量。操作系统在加载程序时,会自动把这块区域的数据清零。

2.4. 常量存储区 (Constant / Read-Only Segment)

  • 特性:也被称为 .rodata (Read-Only Data) 区。这里的内存受到操作系统的严格保护,绝对只读
  • 存储内容:字符串字面量(比如 char* p = "Hello World"; 里的 "Hello World")以及部分 const 修饰的变量。
  • 致命风险:如果你尝试强行修改这块区域的数据(比如 p[0] = 'h';),操作系统会立刻抛出 段错误 (Segmentation Fault) 并终止程序。

2.5. 代码区 (Code / Text Segment)

  • 特性:存放程序的机器指令(也就是你写的代码编译后的二进制文件)。
  • 特点
    • 只读:防止程序在运行中意外修改自己的指令。
    • 共享:如果你同时打开了 5 个记事本程序,内存中其实只有一份记事本的代码区,它们共享这部分内存以节约资源。

3. 指针参数传递和引用参数传递

3.1. 指针传递:本质是“值传递”(传的是地址的副本)

当你把一个指针传给函数时,其实是发生了一次“拷贝”。

  • 栈区开辟:编译器会在被调用函数的栈帧里,开辟一块新的内存,专门用来存放传进来的地址值。这个新指针(形参)和外面的旧指针(实参)是两个独立的变量,只是它们碰巧装了同一个地址。
    • 改指针的指向(形参变,实参不变):如果你在函数里写 p = nullptr;,这只是把形参那个临时变量清空了,外面的实参指针完全不受影响。
    • 改指针指向的内容(形参变,实参也变):如果你写 *p = 10;,因为形参和实参拿着同一把钥匙(地址),这会切实地改变外部数据。

3.2. 引用传递:底层是“间接寻址”(安全的高级马甲)

引用传递在底层实现上,其实也占用了栈空间的内存(通常被编译器实现为一个常量指针 Type* const)。

  • 操作透明化:最大的区别在于,编译器负责了所有的“解引用(*)”工作。当你在函数里操作引用变量时,编译器自动将其翻译为间接寻址,直接去操作主调函数里的那个本体。
  • 修改本体:你在函数内对引用做的任何赋值,都会 100% 作用于外部的实参。

3.3. 为什么想改外部指针的指向,必须用 ***&

既然指针传递是拷贝了指针本身,如果想在函数里改变外部指针自身的指向(比如让外部指针指向一块新 new 出来的内存):

  • 传“指针的指针”(二级指针 int** p)。
  • 或者传“指针的引用”(int* &p,推荐写法,更直观)。

3.4. 编译器视角:符号表 (The Symbol Table)

  • **符号表(Symbol Table)**是编译器在编译期间维护的一张表,记录了变量名和对应的内存地址。
  • 指针的符号表:记录的是【指针变量名 —> 指针自己的内存地址】。指针自己的地址是固定的,但指针里面存的值(指向哪里)随时可以改。
  • 引用的符号表:记录的是【引用变量名 —> 本体对象的内存地址】。
  • 符号表一旦生成,变量名和地址的映射关系就彻底定死了。所以指针可以随意改变其保存的地址值,而引用一旦绑定了某个对象,终生不能换绑

4. static 和 const 关键字

4.1. static 关键字

static 的核心逻辑在于改变变量的生命周期(使其贯穿整个程序)或限制标识符的链接属性(使其仅在特定范围内可见)。

4.1.1. 修饰局部变量(静态局部变量)
  • 存储位置:由栈区改为静态数据区
  • 生命周期:从程序运行到结束,不会随函数退出而销毁。
  • 作用域:保持不变,仍限制在定义它的语句块内。
  • 特性:只在第一次执行时初始化一次,后续调用保留上次运行的值。
4.1.2. 修饰全局变量(静态全局变量)
  • 可见性变化:由“整个工程可见”变为**“仅本源文件可见”**(内部链接)。
  • 用途:有效防止多个文件中出现同名变量导致的链接冲突。
4.1.3. 修饰函数(静态函数)
  • 作用:与修饰全局变量类似,限制函数仅在声明它的**模块(文件)**内可见,隐藏了函数接口。
4.1.4. 修饰类成员(静态成员变量/函数)

静态成员变量

  • 归属:属于而非特定对象,所有对象共享同一个副本。
  • 初始化:必须在类外进行定义和初始化(因为它们独立于对象存在)。

静态成员函数

  • this 指针:因此不能访问非静态成员,只能访问静态成员。
  • 调用方式:可以通过类名直接调用 Class::Func(),也可以通过对象调用。
  • 禁止 virtual:虚函数依赖 vptr 指针,而 vptr 存放在对象内存中并通过 this 访问,static 函数无 this,故不能为虚函数。虚函数的调⽤关系:this -> vptr -> ctable -> virtual function

4.2. const 关键字

const 的核心逻辑是只读保护,防止数据被意外修改。

4.2.1. 修饰基本数据类型
  • const int a = 10;int const a = 10; 等价。
  • 变量值被锁定,尝试修改会导致编译错误。
4.2.2. 修饰指针与引用(关键区分)
  • 常量指针(Pointer to Constant):const int* pint const* pconst* 左侧,修饰指向的内容。内容不可变,指针指向可变。
  • 指针常量(Constant Pointer):int* const pconst* 右侧,修饰指针本身。内容可变,指针指向不可变。
  • 双重限定const int* const p。指向的内容和指针本身都不可变。
4.2.3. 函数中的 const 应用
  • 做参数:保护输入数据不被函数内部修改,常用于 const T&(引用传递)以提高效率并确保安全。
  • 做返回值:防止返回值被作为左值修改(如返回 const ref)。
4.2.4. 类中的 const 用法
  • const 成员变量
    • 初始化:只能在构造函数的初始化列表中进行。
    • 特性:在对象生命周期内是常量,不同对象的该常量值可以不同。
  • const 成员函数
    • 目的:承诺不修改对象的任何非静态成员。
    • 互斥性:不能与 static 同时修饰同一个成员函数(因为 staticthis 指针,而 const 修饰的是隐式的 this)。
    • 例外:被 mutable 修饰的变量可以在 const 函数中被修改。
  • 常量对象
    • 限制:一旦定义为 const ClassObj,该对象只能调用 const 成员函数
4.2.5. 常量成员函数深度解析

成员函数在调用时会隐式传递 this 指针:

  • 普通成员函数:隐式形参为 T* const this(指针地址不可变,指向内容可变)。
  • 常量成员函数:隐式形参变为 const T* const this(指向的内容也不可变)。
对象类型可调用函数类型说明
非常量对象const非const 成员函数权限可以缩小(从可读写到只读)
常量对象 const 成员函数权限不能放大(必须保证只读)

5. C 和 C++ 区别

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇C++ Primer 笔记