C++ 引用

引用

int& ref_a {a},声明引用 ref_a

  • 引用作为一个变量的别名
  • 使用是无需解引用运算符
  • 不能为空
  • 避免值的复制,减少开销
  • 常量引用避免引用指向的变量被修改 const int& param
  • 主要用于函数参数传递和函数返回值,使得函数能够直接修改调用者提供的值
1
2
3
4
5
void ref() {
auto nn {123};
auto& ref_n {nn};
cout << "reference:" << ref_n << endl; // reference: 123
}

指针

int* pointer_a {1}, 声明指针 pointer_a

  • 指针直接存储变量的内存地址

  • 直接表现为 16 进制数字,进行四则运算表示地址的偏移

    • ```c++
      // 加法运算
      int arr[5] = {1, 2, 3, 4, 5};
      int* ptr = arr;
      ptr = ptr + 2; // 移动两个整数的位置,即 arr[2]
      1
      2
      3
      4
      5
      6

      - ```c++
      // 减法
      int arr[5] = {1, 2, 3, 4, 5};
      int* ptr = &arr[4];
      ptr = ptr - 2; // 移动两个整数的位置,即 arr[2]
  • 可以为空 nullptr

  • &操作符是取地址运算符,用于获取变量在内存中的地址

  • *是解引用运算符,用于找到指针所指向的内存的具体内容,*pointerA = *pointerA + 1;语句的作用就是修改了这块内存的内容

  • 可以指向栈上的地址

  • 可以指向堆上的地址

1
2
3
4
5
6
7
8
9
10
11
void pointer() {
int* pointerB { nullptr };
cout << pointerB << endl; // 0x0
int a = 1;
int* pointerA { &a };
cout << pointerA << endl; // ...
cout << *pointerA << endl; //1
*pointerA = *pointerA + 1;
cout << *pointerA << endl; //2
cout << a << endl; //2
}

C++ 变量声明与定义

1
2
auto a {12} // a 是 int 类型
auto pi {3.141592} // pi 是 double 类型
  • 使用 {} 初始变量,编译器会检查数据是否匹配类型,比如 int b {3.5} 会编译不通过,使用 = 号会通过编译,并将将 double 3.5 缩减为 int 3
  • auto 关键字自动推导类型, 代替那些冗长、复杂的变量类型声明。
1
2
3
// auto 不是银弹
auto str {"hello"}; //const char*
std::string str2 {"hello"}; //std::string

命名空间

  • 写法 std::string (std 是命名空间,string 是 std 中定义的类)
  • 避免变量名冲突
  • 封装复杂业务逻辑
1
2
3
4
std::string ss {"hello world"};
// 使用 using namespace 简化
using namespace std;
string s2 {"hello cpp"}

静态变量

  • 只会在程序开始运行时初始化一次
  • 在程序声明周期里一直存在
  • 存储在数据段
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;
void testStaticVar() {
static int a{1};
cout << a << endl;
a = a + 1;
}
int main() {
testStaticVar(); //输出:1
// 重复调用,a 不会重新初始化
testStaticVar(); //输出:2
return 0;
}

全局变量

  • 存储在数据段

局部变量

  • 局部块中的变量会隐藏外部块的同名变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
int a{ 1 };
int main() {
int a { 123 };
cout << "外部的a:" << a << endl; //外部的a:123
{
cout << "外部的a:" << a << endl; //外部的a:123
int a{ 456 };
cout << "内部的a:" << a << endl; //内部的a:456
a = a + 1;
cout << "内部的a:" << a << endl; //内部的a:457
//使用限定符访问全局变量
cout << "全局的a:" << ::a << endl; //全局的a:1
}
cout << "外部的a:" << a << endl; //外部的a:123
cout << "全局的a:" << ::a << endl; //全局的a:1
}

数字

  • int, float, double
  • 可以使用 lone, short, unsigned, signed 等修饰符去改变 int 等类型的位数
  • C++ 在不同平台上同一类型可能是不同的位数,比如 int 可能是 2 或 4 位
  • math.h 标准库提供了一系列操作数字的方法,比如 floor, round 等

字符串

  • char 定义字符串
  • 字符\0 标志字符串终止
  • 标准库提供 string 类
  • R"()"原始字符串(类似 js 里的 模版字符串)

C++ 编译流程

编译流程

CompileAndLink.png

  • 预处理环节完成 #include 等宏和常量的替换
  • 编译过程,生成的机器指令放一堆 .obj 文件中
  • 链接过程将编译产生的 obj 文件和引用的库链接到一起,生成可执行文件或动态链接库

预处理指令 #pragma once

告诉预处理器这个文件只会被处理一次,避免多次引用同一文件后存在内容

应用程序的内存布局

  • 内核空间:用于存储操作系统和驱动程序为进程提供的临时机器指令和中间变量。
  • 映射段:用于装载或映射动态链接库,也常用于将文件内容映射到内存中。
  • 代码段:用于存放应用程序的机器指令,为了防止指令被其他程序修改,代码段是只读的。
  • 数据段:用于存储全局变量、静态变量(static)和常量数据(const)。
  • :用于存储应用程序运行过程中申请的内存空间,比如使用 malloc 方法或 new 关键字申请的内存。
  • :用于存储函数的局部变量、参数、返回值及调用者的上下文信息。

ProcessMemory.png

2023-11-07.md 栈与栈帧

stackMemory.png

  • 空间中分配内存的变量不需要程序员手动销毁,栈帧销毁时栈帧上的变量会被自动销毁。
  • 栈的总内存大小是固定的,而且非常小,递归操作容易爆栈
  • 操作栈上的内存效率很高

  • 使用 new 操作符返回堆空间地址(指针)
  • 需要开发者手动释放,使用 delete 关键字
  • 堆内存容量大,适合放大对象或数组

C++ 环境配置

使用 CLion 开发,

目录与依赖项

C++ 程序通常包含两种文件

  • 头文件 .h ,负责定义类型、方法和变量
  • 源码文件 .cpp, 负责实现头文件中的定义

源码文件需要引入头文件

1
2
// a.cpp 引入 .h
#include "a.h"

第三方也通过 include 引入,要注明相对路径(标准库或系统库不用,IDE 通常会自动配置)

1
2
3
// 双引号会同时搜索依赖库和用户文件,只是优先搜索用户文件;尖括号则仅搜索依赖库
#include "ohterlib/xxx.h"
#include <iostream>

字符集与预处理器定义

  • 字符编码在 IDE 里设置(CLion 默认是 UTF-8)

  • 自定义预处理器宏可以在源文件、头文件、CMakeLists.txt 文件中定义

    • ```c++
      // 源文件定义
      #define MY_MACRO 1
      int main() {
      if (MY_MACRO) {
      // 宏 MY_MACRO 的值为 1
      
      }
      return 0;
      }
      1
      2
      3
      4
      5
      6
      7

      - ```c++
      // CMakeLists.txt 中定义
      // 语法 add_definitions(<宏名> [<值>])
      add_definitions(-DMY_MACRO=1) // MY_MACRO 前面的 -D 表示预处理宏
      // 在CMake中,-D 选项用于定义预处理宏(Preprocessor Macros)。
      // -D 后面跟着宏的名称和可选的值。宏定义后,编译器会在编译源代码之前将其传递给预处理器

切换 debug 和 release

更改路径:设置 -> CMake -> 在 Build Type 下拉列表中,选择 DebugRelease

Debug 模式

  • 编译器会生成调试信息,以便调试器使用。
  • 程序会启动调试器。
  • 程序会在调试器的控制下运行。

Release 模式

  • 编译器会生成最优化的代码。
  • 程序不会启动调试器。
  • 程序将以最快的速度运行。