现代C++学习——基本语法外的一点补充
本文最后更新于 315 天前,其中的信息可能已经有所发展或是发生改变。

C++ 基本内容

C++ 进程内存结构

进程即执行中的程序,包含 代码区与常量区 栈区 堆区 静态变量区

代码区与常量区(只读区)

存放代码本身和常量,这里的常量就是立即量,不是那种带 const 的变量

数字直接和代码存一块,而字符串存在常量区

栈区

函数执行用的内存,在函数结束后相应地销毁

堆区

用来分配的内存,需要手动销毁

  • 堆区特点是大和灵活,存放大的对象并用指针管理

  • 而程序的各种逻辑跑在栈中,如果栈可以只操作指针而不是把对象本体搬来搬去,可以显著减少负担

  • 栈和堆的关系:堆用来存对象,栈进行对象的管理

静态变量区(可读写区)

存放静态和全局变量,静态和全局都能长期留在内存中,在编译时就创建了

  • 区别仅为声明位置不同,导致静态作用范围比全局小,而且操作过程依然保持封装性

C++ 动态内存分配

单个分配

pointer = new SomeTypepointer = new SomeType(param)

进行省略空括号的默认构造,或带参构造(比如经常使用的拷贝构造)

多个分配 [ ]

pointer = new SomeType[n]pointer = new SomeType[n]()

注意:基本类型的分配,带空括号初始化为 0,不带括号则为随机值

总结:一般建议不省略空括号

释放

delete pointer / delete[] pointer

避免内存泄漏:及时delete,或使用智能指针 / 异常处理

const 关键字

const 变量在编译期视为常量,但其本质,包括在进程内存的位置以及汇编层面体现都是变量

const 默认修饰的是左边的标识,如果左边没有才是修饰右边的标识,只要能修饰那就可以有多个 const

例1:const int* const pi 第一个 const 表示值固定,第二个 const 表示指针固定(等同引用)

例2:int const*const int* 是一回事

auto 关键字

auto 关键字的意义,在于:

  1. 一定程度上减少了程序员检查类型的工作,在能确定类型的安全前提下,所以不要滥用
  2. 同理,在赋值时可以让编译器直接确定类型,不用检查左值类型的正确性,反而高效一点
  3. 在面向对象和模板代码中发光发热(比如名字很长但是很确定的类型,如迭代器),而简单的代码就比较随意
auto 的注意点
  1. auto 不能推断引用,要加 &,而且推断引用实际上是推断引用指向的对象

  2. auto 推断带 const 的目标时

    如果是 auto ,则不保留 const

    如果是 auto& ,则会带上 const,变为 const 引用

    image-20231008105447443

    普通变量情况

    image-20231009031609673

    指向常整数的指针常量,类型是指针,不加 & 时忽略了指针的 const,但保留指向对象的 const

    • 从安全角度出发,非引用推断只保留数值;引用推断与目标本身等价,所以也必须 const
    • 由于指针是变量,不涉及 & 相关特性,于是在 auto 的使用中指针和引用就有了区别
  3. 当然如果确定为常量的话,直接手动 const auto,这样既省心又直观

boost 库
  • 更好的类型推断

    #include 
    using boost::typeindex::type_id_with_cvr;
    
    auto a = 100;
    std::cout << type_id_with_cvr().pretty_name() << std::endl;
    // 输出: int
  • 万能引用、完美转发等

Boost C++ Libraries

下载压缩包,解压,运行目录下脚本生成 b2.exe

然后搜索 VS2022 Native Tools 命令行,执行编译命令

其中指定工具集版本,项目右键 - 属性 - 常规 - 平台工具集

静态变量,指针和引用

关于静态变量,有一个说法:在编译时就为其分配了内存,早于程序的运行

没错,但说法有一定迷惑性,因为静态变量区本就是是进程的一部分

所以这里的提前分配和早于程序,可以认为是:一旦进程内存确定,静态变量内存就确定,提前分配的是相对空间

所以程序如果只编译的话,就是保留静态变量的分配方案。等什么时候运行,才进入内存

而代码执行过程中,再遇到静态变量的声明行,就会直接跳过了

关于指针,指针涉及的动作无外乎这几个:

  1. 取地址时对变量使用 &
  2. 按地址取值时对自己使用 *
  3. 通过加减,来按类型移动相应的地址

关于引用:除了一些特性外,int& ref = i 等效 int* const ptr = &i

左值和右值

C++任何一个对象要么是左值,要么是右值

左和右的说法是 C 语言的历史遗留,右值一定在 = 右边,但左值也可以在 = 右边

左值:拥有地址属性的对象(比如一个变量,我们可以对它赋值,也就是把值存储到它的地址处,所以有地址属性)

右值:不满足左值条件的对象,也是不能放在等号左边的对象。临时对象就是右值

int i = 10; // 右值
int i1 = i; // 左值
int i2 = i + 1; // 右值
++i;    // 左值, 因为i已经是自增后的值, 所以是直接取i自身地址
i++;    // 右值, 因为取的是i自增前的值, 实际上是临时对象

引用的分类

提前看一眼,后面边学边涉及

  1. 普通左值引用:就是一个对象的别名,只能绑定左值,无法绑定常量对象

    (如果不绑定一个确定的变量,那依然会有指针的内存不可控问题)

    int i = 10;
    int& ri = i;
  2. const左值引用:可以对常量起别名,可以绑定左值和右值

    (这里没有不可控的危险了,所以可以当成常变量名)

    const int& ri = 100;
  3. 右值引用:只能绑定右值的引用(涉及的概念已经脱离了传统指针,看后面内容)

    int i = 20;
    int&& rri = i++;
  4. 万能引用:很重要,但需要模板等基础的概念,后面再说

move 函数

  1. 右值看重对象的值而不考虑地址,move函数可以对一个左值使用,使操作系统不再在意其地址属性,将其完全视作一个右值
int i = 50;
int&& rri = std::move(i);
  1. move函数让操作的对象失去了地址属性

    所以我们有义务保证以后不再使用该变量的地址属性,简单来说就是不再使用该变量,因为左值对象的地址是其使用时无法绕过的属性

    相当于变量把自己的地址交了出来,仅带着值寻找下一个落脚点,至于有什么意义,后面会讨论

临时对象

右值引用主要处理的就是临时对象。所有临时对象都是右值,因为产生后很快就可能销毁,使用的是它的值属性

程序执行时生成的中间对象就是临时对象,如函数返回值、表达式等

可调用对象

函数

除了一般的函数外,C++ 提供了通过指针调用函数的功能

返回值(*)(参数) 定义了某个函数指针类型

返回值(*指针名)(参数) 声明一个该类的指针变量,指针名必须写在中间,作为左值很迷惑

  • 但 C++11 引入了 using 语法糖来解决这个问题 ↓(usingtypedef 的上位替代)
using pf = void(*)(int);    // pf可以完全替代右边的类型来声明变量

void t(int i){}
int main(){
    int i = 50;
    pf ptrT = t;    // 有人说函数名本身就是指针,是错误的。这里其实是隐式执行 pf ptrT = &t
    ptrT(i);
}
  • 冷知识:关于 typedef 为何被替代的问题,简单解释一下:

typedef 原名 新名,本该如此,但实际上该语法遵从的是声明变量的语法。何来此言?

假设要为函数指针 void(*)(int, int) 起别名 func_t,代码是 typedef void(*func_t)(int, int)

——给大伙整沉默了

仿函数

对类实现 ( ) 运算符,( ) 又称为函数调用运算符,可以包含参数,参数形式与函数相同

实现之后,可以对该类对象使用 (参数) 表现为调用(但目前还不知道有什么用)

class Test{
public:
    int operator()(int t){
        return t + 1;
    }
};

Test test;
test(1);    // 结果为2的右值
  • 冷知识:类中定义了运算符,可以在对象后直接使用运算符,但也能直接调用运算符函数
class Test{
public:
  int operator+(int t)
  {
      return t + 2;
  }
};
Test test;
// 下面两个等价
cout << test + 2;
cout << test.operator+(2);
lambda 表达式

[捕获列表](参数) -> 返回值 {函数体}

捕获

用捕获列表指定该 lambda 表达式可以访问的前文变量(因为 lambda 对象不遵从传统的作用范围,要手动指定)

[] 不捕获任何变量

[i] 捕获变量 i(在函数中仅取其值)

[&i] 以引用捕获变量 i(在函数中使用 i 的地址)

[=] 取值捕获所有变量

[&] 取引用捕获所有变量

可以混用捕获,例如:

[=, &i] 表示变量 i 用引用传递,此外所有变量用值传递

[&, i] 表示变量 i 用值传递,此外所有变量用引用传递

参数

形式与函数相同

返回值

可以正常设定类型,或指定为 autoauto 也可以推断 void),或直接省略,编译器依然会自动判断返回值

文末附加内容
暂无评论

发送评论 编辑评论


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