Zhyx's Blog

Back

基础#

i++ ++i的性能差异:一般来说++i更高效,因为不需要生成一个临时值来保存自增前的值

内存分区#

代码区:程序的机器指令

全局区:全局变量、静态变量(包括静态成员变量)、常量

堆区:程序员通过new、malloc等手动分配的内存

栈区:函数参数、局部变量、返回地址等,自动回收

malloc/free:C的标准库,分配指定大小字节数,返回指向该块的指针。只分配和释放内存,不会调用构造或析构

new/delete:C++的运算符,为某个对象分配内存,返回该对象的指针,会调用构造和析构函数

内存泄漏:动态分配的堆内存在使用完毕后没有得到适当的回收和释放,最主要的原因就是malloc之后没有free,new之后没有delete 避免:使用智能指针、内存检测工具、记录日志等等

define typedef#

define只做简单替换,没有类型检查,只在编译预处理阶段起作用

typedef有类型检查,在编译运行时起作用

类型限定符#

const:常量限定符

  • 修饰变量:常量,不可被修改
  • 常量指针与指针常量
  • 修饰成员函数:不可修改成员变量(除mutable外),只能调用其他const成员函数
  • 修饰对象:只能调用类的const函数
int main()
{
    int a = 10;
    int b = 12;

    const int *p = &a; // 常量指针,不可通过指针改变对象的值。
    int *const q = &a; // 指针常量,指针的值,也就是指向的地址无法在改变。

    *p = 100; // 编译器报错
    a = 9;    // 但可以通过别的方式修改
    p = &b;   // 修改指向同样可以

    q = &p; // 编译器报错
}
c

volatile

  • 告诉编译器该变量可能会被其他线程或外部因素修改,因此不要对其进行缓存等优化,而是每次都从真正的内存中读取

restrict

  • 告诉编译器该指针没有别名,也就是说该指针指向的内存地址不会被其他指针指向或变量引用。让编译器可以采取更激进的优化

存储类型#

static

  • 修饰局部变量:只初始化一次,生命周期从函数扩展到整个程序运行期间
  • 修饰全局变量:限制作用域在当前文件
  • 类的静态成员:成员属于类,而非具体对象

extern

  • 用于引用其他文件中的全局变量或函数,实现跨文件访问

mutable

  • 只能修饰类的成员变量。表示可以通过const成员函数来修改

函数重载#

C++靠函数签名来唯一确定一个函数,签名包括函数名 + 参数类型(包括参数顺序和是否为引用)

默认参数#

参数列表从左往右,一旦开始设置默认参数,后续所有参数都必须有默认值。

数组#

数组名在大多数时候会被隐式转换为指向首元素的指针

int main()
{
    // 初始化时,带{}是全0,不带{}是随机
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr;

    cout << sizeof(arr); // 数组大小,5*4=20B
    cout << sizeof(p);   // 指针大小,64位系统中为8B

    cout << *p;     // 指向数组第一个元素
    cout << p[2];   // 等价于 *(p + 2)
    *(p + 3) = 100; // 可通过指针偏移修改元素
    return 0;
}
plaintext

结构体#

结构体是一张蓝图,声明结构体不占内存,使用结构体定义对象时会占内存

结构体对象的大小是其结构体中所有成员变量大小总和(需要内存对齐,比如int + char 为8B而非5B)

使用空结构体定义的对象也占1B,以保证每个对象都有唯一地址

类和对象#

访问说明符#

public:成员对所有代码可见

protected:对当前类和子类可见

private:仅当前类可见

构造和析构#

如果什么都不写,编译器会自动生成无参构造、拷贝构造,以及析构函数

如果手写了有参构造,相当于重载了构造函数,编译器就不会在生成默认的无参构造,但仍会有默认拷贝构造;如果手写了拷贝构造,编译器不会再默认生成其他构造

编译器自动生成的拷贝构造函数是浅拷贝,即简单的复制对象所有成员变量的值,包括指针的地址内容。这会导致复制对象和原对象的指针指向同一块内存,修改一个值会影响另一个。

深拷贝:为每个指针成员分配新的独立的内存,是对象互相不影响

class A {
public:
    int* ptr;
    A(int val) {
        ptr = new int(val);
    }

    // 深拷贝构造
    A(const A& other) {
        ptr = new int(*(other.ptr));
    }
}
plaintext

智能指针:用于管理动态分配的内存 unique:独占指针,无法复制,只能移动;同一时间只有一个独占指针拥有对象的所有权,超出作用域时自动释放内存 shared:共享指针,内部使用引用计数,当最后一个共享指针被销毁时,指向的那块内存才被回收 weak:弱指针,用来解决共享指针可能导致的循环引用问题,弱指针不会增加共享指针的引用计数,可以在不影响指向对象的情况下安全的观察对象,但也不能直接访问,只用使用lock方法获取对应的共享指针后才能访问

继承#

构造:先父后子

析构:先子后父

public继承时,子类继承下来的成员访问权限和父类保持一致 protected继承时,父类中的public成员在子类中被设置为protected private继承时,子类所有成员全部private

友元:

  • 声明函数:类中声明类外定义,该函数不属于类,但可以访问类的所有级别成员
  • 声明类:同样类中声明类外定义,该类可以访问本类中的所有级别成员
  • 不可继承,父类的友元不是子类的友元

对象的内存布局#

对象在内存中包含:

  • 虚函数表指针:若类有虚函数,会包含一个指向虚函数表的指针(VPTR) 64位下8B
  • 父类非静态成员变量
  • 自身非静态成员变量

对象本身并不包含成员函数本体。成员函数是属于类本身的,在编译时就确定了地址,所有对象共享同一份成员函数代码。成员函数在调用时会隐式接收一个 this 指针。

多态#

静态多态:成员函数的重载、构造函数的重载等等,编译期发生

动态多态:父类指针或引用指向子类对象,运行时发生,需要使用虚函数

子类对象在内存中包含完整的父类部分和自身的扩展部分,因此编译器允许这种向上转型

当没有使用virtual修饰父类的speak时,指向子类对象的父类指针仍然会调用父类的speak,这是因为编译器已经静态绑定:在编译时,就已经根据a的静态类型(Animal *)决定调用Animal::speak,并不会检查运行时的实际对象类型

class Animal{
public:
    /* virtual */void speak() const{
        cout << "Animal speaks" << endl;
    }
};

class Dog : public Animal{
public:
    void speak() const/* override */{
        cout << "Dog barks" << endl;
    }
};

int main(){
    Dog d;
    Animal *a = &d;
    a->speak(); //Animal speaks

    return 0;
}
plaintext

虚函数#

当某个类中包含虚函数时,这个类会维护一个虚函数表(存在代码区),存放自身的虚函数地址。

子类继承父类后,同样会维护一个虚函数表,包含

  • 父类的虚函数(如果子类没有重写,则存储父类的实现地址)
  • 子类重写的虚函数(替换父类虚表中的对应条目)
  • 子类新增的虚函数(追加到虚表末尾)

每个包含虚函数的类实例化一个对象时,该对象都会包含一个虚表指针(VPTR),VPTR指向该对象所属类的虚函数表。

如上,当使用动态多态时,Animal和Dog类都将维护一个虚表,表中是虚函数以及重写后的虚函数地址

Animal::vtable: +-------------------+ | &Animal::speak | // 指向 Animal::speak() +-------------------+

Dog::vtable: +-------------------+ | &Dog::speak | // 替换 Animal::speak +-------------------+

实例化一个Dog对象d后,该对象的内存结构: +-------------------+ <— &d (Dog 对象的起始地址) | vptr (8 bytes) | —> 指向 Dog::vtable +-------------------+ | Animal 的数据成员 | // 如果有的话 +-------------------+ | Dog 的数据成员 | // 如果有的话 +-------------------+

使用父类指针*a指向d时,由于 speak() 是虚函数,调用 a->speak() 会通过 vptr 查找 Dog::vtable,并调用 Dog::speak()

没有虚构造函数:vptr在构造期间才被初始化

虚析构:非常必要,确保指向子类对象的父类指针可以调用到子类的析构函数,保证资源正确释放

抽象类 纯虚函数#

在虚函数声明后加 = 0 来定义纯虚函数。表示该函数只声明不实现,交给子类重写。

包含纯虚函数的类称为抽象类,无法实例化,必须被继承以重写纯虚函数

STL#

vector#

初始化:vector v(5, 10); // 创建 5 个元素,每个元素值为 10

底层是动态数组,维护三个信息

  • 容量(capacity):当前分配的内存能容纳多少元素。
  • 大小(size):当前实际存储的元素数量。
  • 指向数据的指针。

当 size == capacity,需要扩容。通常是申请一块更大的内存,把原来的元素拷贝或移动到新内存

list#

list lst(5, 10)

底层是双向链表,维护头节点和尾结点指针

set && map#

set s = {1, 3, 5, 7}; map<string, int> m2 = {{“a”,1}, {“b”,2}};

set 有序集合,存储唯一元素,自动按大小排序 map 有序映射集,自动按key排序,key唯一

底层是红黑树

unordered_set && unordered_map#

底层是哈希表,无需存储

通用方法#

size() // 返回容器中元素数量 empty() // 判断容器是否为空,空返回1 max_size() // 返回容器可能包含的最大元素数 front() // 返回第一个元素(vector, list, deque等) back() // 返回最后一个元素(vector, list, deque等) at(index) // 返回指定位置的元素,带边界检查(vector, deque, array) operator[] // 返回指定位置的元素(vector, deque, map等) push_back() // 在末尾添加元素(vector, list, deque等) pop_back() // 删除末尾元素(vector, list, deque等) push_front() // 在开头添加元素(list, deque等) pop_front() // 删除开头元素(list, deque等) insert() // 在指定位置插入元素 erase() // 删除指定位置或范围的元素 clear() // 清空容器

迭代器#

本质是一种智能指针,用于遍历、访问容器中的元素,链接算法等

C++ 多线程 python多线程

C++ 11 14 17

常见ACM输入输出处理#

vector<vector<int>> res;
string line;

// getline 从输入流中读取一行输入,直到遇到换行符 比如1 2 3 4 则line = "1 2 3 4"
while (getline(cin, line)) {
    stringstream ss(line);  //将line转成流对象
    vector<int> row;
    int num;
    while (ss >> num) {     //每当ss中提取到数字时
        row.push_back(num);
    }
    result.push_back(row);
}
plaintext
C++复习
https://astro-pure.js.org/blog/16_c%E5%A4%8D%E4%B9%A0
Author Zhang Yix
Published at June 13, 2025
Comment seems to stuck. Try to refresh?✨