main 执行之前和之后

执行之前:

  • 设置栈指针
  • 初始化static 和global变量
  • 将未初始化的global变量赋初始值short/int/long=0,bool=false,pointer=null
  • 全局对象初始化,调用构造函数
  • 将main的argc/argv传给main
  • attribute((constructor))

main执行之后:

  • 全局对象的析构函数
  • atexit函数
  • attribute((destructor))

指针 Pointer和引用 Reference

  • 指针可以在声明时不初始化,可以稍后赋值,并且可以重新赋值,指向不同的对象。引用必须在声明时初始化,一旦初始化后就不能改变引用的对象。

  • 指针可以为空(nullptr),表示不指向任何对象。引用不能为空,必须始终引用一个有效对象。

  • 指针可以有多级,如指向指针的指针。引用只能有一级。

  • 指针可以进行算术运算,如++或—,引用不能进行算术运算。

  • 指针本身占用一定的内存空间(通常是4bytes(32 bit)或8bytes(64 bit));引用不占用额外的内存空间。

  • 指针是一个变量,存储的地址,而引用等同于原变量,可以看作是原变量的别名,

指针和引用使用时机

引用:

  • 希望在函数内修改原始对象时
  • 在重载操作符时使用引用
    1
    bool operator==(const MyClass& other) const;
  • 对于大型对象,使用const引用可以避免不必要的复制。
  • 对栈空间敏感(递归)时

指针:

  • 当对象的生命周期需要超出创建它的函数作用域时,返回函数内部动态分配的内存(使用指针)
    1
    2
    3
    4
    std::vector<int>* createAndFillVector(int size) {
    auto* vec = new std::vector<int>(size);
    return vec;
    }
  • 大对象创建,不想在栈上分配内存
  • 使用多态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Animal { public: virtual void speak() = 0; };
    class Dog : public Animal { public: void speak() override { /* ... */ } };
    class Cat : public Animal { public: void speak() override { /* ... */ } };

    Animal* createAnimal(std::string type) {
    if (type == "dog") return new Dog();
    if (type == "cat") return new Cat();
    return nullptr;
    }

堆和栈的区别

堆(Heap)和栈(Stack)是C++中两种主要的内存分配区域

  1. 内存分配方式:

    • 栈:自动分配和释放。当进入一个函数时,栈上会自动分配内存;当函数退出时,这些内存会自动释放。
    • 堆:需要手动分配和释放。使用 new 运算符分配内存,使用 delete 运算符释放内存。
  2. 内存大小:

    • 栈:大小通常较小,且固定。在大多数系统中,栈的大小在编译时就确定了。
    • 堆:大小通常较大,且可以动态增长(受限于可用的物理内存)。
  3. 分配速度:

    • 栈:分配非常快,只需要移动栈顶指针。
    • 堆:分配相对较慢,需要在空闲列表中查找合适的内存块。
  4. 内存布局和生长:

    • 栈:内存连续,高地址向低地址增长。不容易有碎片
    • 堆:内存可能不连续,低地址向高地址增长。容易有碎片
  5. 生命周期:

    • 栈:局部变量的生命周期与函数调用周期一致。
    • 堆:对象的生命周期由程序员控制,可以持续到整个程序结束。
  6. 管理方式:

    • 栈:由编译器自动管理。
    • 堆:由程序员手动管理,或使用智能指针等工具辅助管理。(容易memory leak)
  7. 数据访问速度:

    • 栈:访问速度较快,因为内存连续且使用相对寻址。
    • 堆:访问速度相对较慢,因为可能需要解引用指针。
  8. 常见用途:

    • 栈:存储局部变量、函数参数、返回地址等。
    • 堆:存储动态分配的对象,如需要在函数调用之间持续存在的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

class MyClass {
int data;
public:
MyClass(int d) : data(d) { std::cout << "Constructing MyClass " << data << std::endl; }
~MyClass() { std::cout << "Destroying MyClass " << data << std::endl; }
};

void stackAllocation() {
MyClass obj1(1); // 栈分配
MyClass obj2(2); // 栈分配
// 函数结束时,obj2和obj1会自动销毁(按照相反的顺序)
}

void heapAllocation() {
MyClass* ptr1 = new MyClass(3); // 堆分配
MyClass* ptr2 = new MyClass(4); // 堆分配

delete ptr2; // 必须手动释放
delete ptr1; // 必须手动释放
}

内存管理中栈的先进后出

压栈(Push)操作:

  • 当有新数据需要压入栈时,处理器会先将栈指针减小(因为栈向低地址增长)。
    然后将数据存储在新的栈顶位置。

出栈(Pop)操作:

  • 当需要弹出数据时,处理器会读取栈指针指向的数据。
    然后增加栈指针,使其指向下一个元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

void funcB(int x) {
int y = x + 5; // y 在栈上分配
std::cout << "funcB: " << y << std::endl;
// 函数返回时,y 自动从栈上移除
}

void funcA() {
int a = 10; // a 在栈上分配
funcB(a); // a 的值被压入栈作为参数
std::cout << "funcA: " << a << std::endl;
// 函数返回时,a 自动从栈上移除
}

int main() {
funcA();
return 0;
}
  1. main() 调用 funcA() 时:

    • funcA 的返回地址被压入栈。
    • a 分配栈空间。
  2. funcA() 调用 funcB(a) 时:

    • funcB 的返回地址被压入栈。
    • 参数 a 的值被压入栈。
    • y 分配栈空间。
  3. funcB() 返回时:

    • y 的栈空间被释放。
    • 参数 a 的值从栈中移除。
    • 处理器使用返回地址跳回 funcA
  4. funcA() 返回时:

    • a 的栈空间被释放。
    • 处理器使用返回地址跳回 main

int p[10] & int (p)[10]

  1. int *p[10]
  • 它是一个数组(p是一个数组名)
  • 数组的大小是10
  • 数组的每个元素是一个指向int的指针
  1. int (*p)[10]
  • 它是一个指针(p是一个指针)
  • 这个指针指向一个数组
  • 被指向的数组包含10个int元素

new/delete 和 malloc/free

  1. 类型安全:

    • new/delete 是类型安全的,malloc/free 不是。
    • new 返回类型化的指针,malloc 返回 void*。
  2. 构造和析构:

    • new 会调用构造函数,delete 会调用析构函数。
    • malloc/free 不涉及构造和析构。
  3. 重载:
    • 可以重载 new 和 delete 操作符。
    • 可以覆盖 malloc 和 free。

对于非基本类型的对象时,创建要使用构造constructor函数,销毁使用析构destructor函数

Variable Declaration & Definition 声明和定义

  1. 变量声明:
  • 声明告诉编译器变量的名称和类型。
  • 不分配内存空间。
  • 通常用于头文件中,告诉其他文件该变量的存在。
  • 可以多次声明同一个变量。
  1. 变量定义:
  • 定义不仅声明了变量,还为其分配了内存空间。
  • 初始化变量(可选)。
  • 只能定义一次。
  • 通常在源文件(.cpp)中进行。

struct和class区别

  1. 默认访问权限

    • struct: 默认为public
    • class: 默认为private
  2. 默认继承方式

    • struct: 默认public继承
    • class: 默认private继承

宏定义macro definition

  1. 基本概念:

    • 宏定义使用 #define 指令
    • 在编译之前由预处理器处理
    • 本质上是简单的文本替换
  2. 语法:

    1
    #define 标识符 替换文本
  3. 宏定义的类型:

    a. 对象式宏(Object-like Macros):

    1
    #define PI 3.14159

    b. 函数式宏(Function-like Macros):

    1
    #define MAX(a, b) ((a) > (b) ? (a) : (b))
  1. 宏定义的特点:
    • 不进行类型检查
    • 可以用于条件编译(如 DEBUG)
    • 在整个文件中有效,除非被 #undef 取消定义
    • 便于维护常量值

      宏定义和函数区别

  • 函数有类型检查
  • 函数有返回值

    宏定义和typedef/const区别

  • 宏定义在预处理阶段处理, typedef/const在编译阶段处理
  • 宏定义没有类型检查,typedef/const有类型检查
  • 宏定义是简单的文本替换,而 typedef 和 using 创建了真正的类型别名。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 使用宏定义
    #define INT_VECTOR std::vector<int>

    // 使用typedef
    typedef std::vector<int> IntVector;

    // 使用 C++11 的 using (类似于 typedef)
    using IntVec = std::vector<int>;

    // 使用 const
    const double pi = 3.14159;

const 和static