C++八股总结(不间断更新)

数据类型和大小(32位和64位)
char:1字节 1字节
short:2字节 2字节
int:4字节 4字节
long:4字节 8字节
long long:8字节 8字节

new-delete malloc-free
new是C++中的关键字。new可以根据动态分配内存并创建对象。它会根据对象类型自动计算所需的空间并调用对象的构造函数进行初始化,使用new分配内存之后需要使用delete进行内存释放,
new[]和delete[]用于动态分配数组内存。
new和delete在实现的过程调用了底层的malloc和free,但是他们不是当初的malloc和free的封装,他们还调用了其他函数,例如构造函数和析构函数。
new的实现细节:new第一步调用内存分配函数,这里默认情况下使用malloc(当C++使用new的时候,new会根据要创建的对象的类型自动传递正确的大小给malloc),一旦内存分配成功就会调用对象的析构函数进行初始化对象。完成构造函数之后new返回指向该对象的指针。
delete进行销毁对象的时候,首先调用的是析构函数,一边释放对象持有的资源或执行其他清理工作,析构函数调用之后,delete会调用operator delete,通常这是一个底层的内存释放函数,默认情况下会使用free来释放。

int* arr = new int[10];  // 动态分配一个包含 10 个 int 的数组
delete[] arr;  // 释放动态数组内存

而malloc是C语言中的库函数。它在分配内存的时候需要知道所分配的内存块的大小,也就是说它会动态分配一块指定大小的内存块,使用malloc分配之后需要使用free释放内存。

calloc
来自于C语言标准库,定义在头文件中,动态内存分配函数,其功能是为数组或多个元素分配内存空间,并将所有分配的内存初始化为0.

void* calloc(size_t num, size_t size);
//num:要分配的个数;size:每个元素的字节大小

功能特点:
1.分类连续内存:它所分配的内存是连续的,大小为:numsize
2.初始化为零:calloc和malloc最大的不同在于,calloc会自动将分配的内存初始化为全0(每个字节都是0),而malloc不会进行初始化,对于需要清0的数组或数据结构,malloc比较有用。
3.返回指针:calloc返回一个指向分配内存区域的指针,类型为void
,所以C++中需要类型转换

#include <iostream>
#include <cstdlib>

int main() {
    // 分配一个存储10个int类型元素的内存
    int* arr = (int*)calloc(10, sizeof(int));

    // 检查是否成功分配内存
    if (arr == nullptr) {
        std::cerr << "内存分配失败" << std::endl;
        return 1;
    }

    // 打印分配的内存区域的内容,应该全是0
    for (int i = 0; i < 10; i++) {
        std::cout << arr[i] << " ";
    }

    // 释放分配的内存
    free(arr);

    return 0;
}

malloc、new、calloc
calloc分配内存之后会初始化,而且返回的是void *,需要强制类型转换(在C++),输入需要两个参数,效率没有malloc快

calloc分配失败,返回nullptr,new分配失败,会抛出std::bad_alloc异常。

realloc:用来调整已经分配的内存大小(通过malloc或者calloc),可以增加或减少内存大小。

运算符
三目运算符 “?:”以及其他运算符号
z = (z < y) ? z : x;

结构体的大小

#include<iostream>
using namespace std;
struct test
{
	int m1;
	char m2;
	float m3;
	union uu { char u1[5];
	int u2[2];
	}ua;
}myaa;
	cout << sizeof(struct  test) << endl;	

结构体的大小是20,因为:m1:4字节,m2:1字节,m3:4字节。联合体中:u1:5字节,u2:24=8字节
大多数系统使用内存对齐规则来优化性能,所以不同大小的成员会根据其大小进行对齐填充,4
3+8=20

结构体、联合体

智能指针
帮助C++程序员管理动态分配内存的工具,会自动释放new的内存,避免内存泄露。
作用:避免内存泄漏
c++98提供的是quto_ptr
c++11提供的是unique_ptr、shared_ptr、weak_ptr,位于中,提供了更强大和安全的内存管理功能。
1.auto_ptr是较早版本的智能指针,在进行指针拷贝和赋值的时候,新指针直接接管旧指针的资源并将旧指针指向为空,但是这种方式需要在访问旧指针的时候会出现问题。
2.unique_ptr:是auto_ptr的一个改良版,它确保一个对象同一时间只能有一个指针,一对一的关系。
3.shared_ptr:使得一个对象可以有多个智能指针,当这个对象所有的智能指针都被销毁的时候就进行自动回收(内部使用计数机制进行维护)。
4.weak_ptr:为了协助shared_ptr而出现的,他不能访问对象,只能观测shared_ptr的引用计数,防止出现死锁。

class与struct区别
他们两个都是用于定义自定义数据类型,他们的主要区别在于默认值的成员访问权限。在语法和功能上基本一致。
不同点:
1.
class默认是private
struct默认是public
2.继承的默认访问权限不一样
class的默认继承是private继承,而struct的继承默认是public继承。

struct BaseStruct {};
struct DerivedStruct : BaseStruct {};  // public 继承

class BaseClass {};
class DerivedClass : BaseClass {};  // private 继承

共同点:都可以包含成员函数和数据成员等;二者都支持构造函数和析构函数;二者都支持模板,都可以用作模板类的定义;二者都支持继承、多态等面向对象的特性。
适用情况:
struct:定义的数据类型主要是存储数据,不太关心封装,对访问权限要求不高。
class:更适合封装、隐匿细节的操作。

C++特性以及个特性的定义
C++三大特性:封装、继承、多态
**封装:**封装是面向对象的独立基本概念之一,他将数据和操作数据的方法绑定在一起,形成一个独立的单元,通过封装可以隐藏对象的内部实现细节,仅提供必要的接口给外界使用,保护数据的完整性和安全性
好处:防止外界代码可以直接访问和修改对象的内部状态
将相关数据和行为组织在一起,形成模块化,提高可读性和可维护性。
内部代码可以随时更改而不影响外部使用者,只需要保证接口的一致性。
封装权限的的关键词:public、private、protect
**继承:**运行一个类直接从另一个类中获取属性和方法,通过继承,子类可以实现复用父类代码,同事可以扩展或修改基类的功能。
好处:代码重用:避免编写重复代码,提高开发效率
层次结构:类的层次结构组织代码
可扩展性:子类可以在不修改基类的情况下就增加或者重写功能。
继承包括:单继承、多继承、层次继承。继承的访问控制符:public、private和protect决定了基类成员在子类中的访问权限。
在这里插入图片描述
当基类与子类都有构造函数,如果定义了一个子类对象,那么首先要调用基类的构造函数,然后再调用子类的构造函数;析构函数则与之相反,先调用子类的析构函数,在调用基类的析构函数。

**菱形继承:**通常发生在多重继承中,当类B和C继承类A,而类D又继承了类B和类C,就会发生菱形继承。
在这里插入图片描述
问题:这种情况下,类D会继承类A的属性和方法,但是类A的属性和方法可能已经在类B和C内进行了重新定义或修改,这样就会导致冲突和不确定性问题。
解决办法菱形继承问题的方法:虚继承、接口和抽象类、多重继承的限制。
虚继承:虚继承可以避免多次继承同一基类。通过将基类声明位虚继承,编译器确保只有一个基类实例存在。
如果没有虚继承,那么B和C各自有一份A的副本,他们各自对副本进行操作,D继承的就是BC对A的副本和A的原本,然后就可能会有冲突。
接口和抽象类:接口只包含方法和签名,而没有实现,可以避免冲突。
多重继承的限制:有些编程语言不支持多重继承。

**多态:**它允许不同类的对象通过统一的接口进行交互。多态是的统一操作可以作用于不同的对象,并根据对象的实际类型表现出不同的行为。
多态的类型:编译时多态和运行时多态
编译时多态(静态多态):通过函数重载和运算符重载实现,决策在编译阶段完成。
运行时多态(动态多态):通过虚函数和继承实现,决策在运行阶段完成。

重载和重写
重载使之在同一个作用域内,多个函数或运算符使用相同的名称,但是他们的参数列表(参数类型、数量和顺序)不同。重载的函数或运算符在编译时根据调用时传入的参数类型和数量来决定实际调用哪个版本。
c语言中没有重载,在编译过程中C++会保存不仅函数名,还有函数参数,这样才能够在后续调用中不造成混乱,而c语言只保存函数名称,所以没有办法重载。
重载可以用相同的名字定义多个具有不同功能的函数或运算符,从而简化代码,提高代码的可读性和可维护性。

#include <iostream>

// 函数重载:不同参数列表的同名函数
void print(int i) {
    std::cout << "Integer: " << i << std::endl;
}

void print(double d) {
    std::cout << "Double: " << d << std::endl;
}

void print(const std::string& s) {
    std::cout << "String: " << s << std::endl;
}

int main() {
    print(10);        // 调用 print(int)
    print(3.14);      // 调用 print(double)
    print("Hello");  // 调用 print(const std::string&)
    return 0;

    //运算符重载
    #include <iostream>

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r, double i) : real(r), imag(i) {}

    // 运算符重载:重载 + 运算符
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }

    void display() const {
        std::cout << real << " + " << imag << "i" << std::endl;
    }
};

int main() {
    Complex c1(1.2, 3.4);
    Complex c2(5.6, 7.8);
    Complex c3 = c1 + c2; // 使用重载的 + 运算符
    c3.display(); // 输出: 6.8 + 11.2i
    return 0;
}

}

注意:重载必须在同一作用域内;
函数的返回类型不能作为重载的依据;
运算符重载应保持操作符的直观意义,避免混淆。

重写:在派生类中重新定义基类中已经定义的虚函数。通过重写,派生类可以提供基类函数的具体实现,以实现不同的行为。函数的重写必须与基类中的虚函数具有相同的函数名(名称、参数类型、返回值)
目的:允许在继承关系中子类对基类的方法进行扩展或者修改,使得派生类可以根据自己的需要提供不同的实现,他是实现多态的重要机制之一。
注意:基类中的虚函数必须用virtual关键字声明,然后再派生类中重写
重写函数签名必须于基类中的虚函数完全一致。
可以使用override关键字显示标记一个函数是对基类虚函数的重写,增加代码的清晰性和安全性。

override关键字
用于显式声明派生类中虚函数重写基类中的虚函数。这是一个用于增加代码安全性和可读性的关键字。
主要作用:显示的标明该函数是对基类虚函数的重写,同时也让编译器进行更严格的类型检查。如果函数未正确的重写基类中的虚函数,编译器会报错。在没有使用override的情况下,如果派生类的函数没有完全匹配基类的函数签名,编译器不会提示错误,函数可能没有办法被正确重写,导致潜在逻辑错误。
好处:编译检查、代码可读性、与final关键字的结合

class Base {
public:
    virtual void print() const {  // 基类中的虚函数
        std::cout << "Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void print() const override {  // 正确的重写,使用 override 关键字
        std::cout << "Derived class" << std::endl;
    }
};

final关键字:与override一起使用,final关键字用于资质进一步重写,保证派生类中的虚函数不能在被继承类重写。

class Derived : public Base {
public:
    void print() const override final {  // 使用 final 防止进一步重写
        std::cout << "Derived class" << std::endl;
    }
};
class AnotherDerived : public Derived {
public:
    // void print() const override;  // 错误,Derived 类的 print 函数已被 final 限制
};

虚函数和纯虚函数
虚函数:virtual声明的成员函数,允许在派生类中被重写。虚函数的主要目的是支持运行时的多态性,即在运行时根据对象的实际类型调用相应的函数版本,而不是根据指针或者引用的类型调用其基类的函数。
虚函数的特性:动态绑定、虚函数表、可以提供默认实现
动态绑定:在程序运行的时候确定调用哪一个版本的虚函数,这取决于对象的实际类型而不是指针或引用的类型。
可以提供默认实现:基类的虚函数有默认实现,派生类可以选择重写也可以选择使用基类的实现
虚函数表:放通过指针或哟用调用虚函数时,会根据对象的实际类型调用对应的版本。
纯虚函数:纯虚函数通常用于定义抽象接口,基类提供一个统一的几口,但具体实现必须有派生类进行完成。

class Base {
public:
    virtual void show() = 0;  // 纯虚函数
};
class Derived : public Base {
public:
    void show() override {
        std::cout << "Derived class show function" << std::endl;
    }
};
int main() {
    // Base base;  // 错误!抽象类不能实例化
    Base* basePtr = new Derived();
    basePtr->show();  // 调用 Derived 类的 show 函数
    delete basePtr;
}

纯虚函数特性:没有实现、抽象类、继承要求
没有实现:基类中没有对纯虚函数的实现,具体的实现必须在派生类中完成
抽象类:包含虚函数的类是抽象类,不能被实例化。派生类通常也会变成抽象类,无法实例化。
抽象类的作用:主要用于接口类或基类,定义通用的行为接口,二聚体的实现留给派生类。
继承要求:如果派生类补充些基类的纯虚函数,派生类也会变成抽象类,无法实例化。

虚函数表
虚函数表是C++实现运行时多态的核心机制之一。它用于支持虚函数的动态绑定,使得程序在运行时能够根据对象的实际类型调用先用的函数。

构造函数和析构函数可以是虚函数吗?可是是纯虚函数吗?
构造函数不可以是虚函数也不可以是纯虚函数,因为虚函数、纯虚函数是为了多态的动态调度,这个功能在对象创建阶段是不适用的。
析构函数可以是虚函数也可以是纯虚函数。

char和int之间的转换
注意大小、符号扩展和溢出方面的问题。有符号的char范围是:-128~ 127;无符号的char范围是:0~255,C++没有明确规定char有无符号,所以具体根据编译器来决定。int通常是4字节,范围是:-2147483648 到 2147483647。int转换char时只保留int的低8位,如果超出了可表示范围,结果会发生溢出或者阶段。char换int会自动扩展大小,保持数值不变。

野指针与悬挂指针
野指针指的是没有经过初始化的指针,它指向的地址是未知的、不确定的、随机的。
产生原因:指针未初始化或者一些错误操作之后,被赋值为非法地址
防止措施:初始化或者置空
悬挂指针:指向一块内存已经被释放(或销毁)的区域的指针。此时指针仍然持有原来的地址,但是该地址对应的内存已经失效,不能在被安全访问,通过悬挂指针在访问这片内存,会有不可预测的行为甚至程序崩溃。
产生的原因:1.对象被删除时,并没有将指针置空。
2. 局部变量作用域结束,函数返回后,函数内部的局部指针变量可能只想了已经被回收的栈空间。
尽量使用智能指针来避免这些内存管理的问题

关键字inline、define、const、static、consterpr、typedef、using、mutable、volatitle、enum
inline(内联函数):建议编译器将函数的代码直接插入到调用函数的地方,以避免函数调用的开销(如栈的销毁。)

inline int add(int a, int b) {
    return a + b;
}

宏定义(#define):用于定义宏(类似于文本替换),可以定义常量或简化复杂的代码段。
注意:宏没有类型检查,容易导致难以调试的错误。而且宏中使用多行代码时,要小心避免逻辑错误或优先级问题,建议用括号保证正确的执行顺序。

#define PI 3.14159
#define SQUARE(x) ((x) * (x))

const:声明的变量、对象、指针或类成员的值在初始化后不能被修改,它可以用于定义常量、修饰指针、函数、类成员等。const具有类型检查。
1.修饰变量:const修饰变量时,该变量必须在定义时初始化,且其值不能被改变。

const int MAX_SIZE = 100;
// MAX_SIZE 不能被修改

2.修饰常量引用:使用const修饰的函数参数,可以确保传递给函数的对象不会再函数中被修改,通常用于传递较大的对象避免拷贝开销(比如类对象。)

void print(const std::string& str) {
    // str 是常量引用,不能被修改
    std::cout << str;
}

3.传递常量指针:确保函数不能修改指针所指向的内容

void process(const int* p) {
    // 不能通过 p 修改其所指向的值
}

4.修饰返回类型
返回常量引用可以避免返回值的拷贝,同时保护返回值(类的成员变量,它存储在类的实例中,实例销毁的时候才会被回收)不被修改。

const std::string& getName() const {
    return name;  // 返回成员变量的常量引用
}

5.修饰类成员函数:
const成员函数承诺不会修改类的成员变量,也不会调用非const成员函数。
但是再const成员函数中,只有mutable修饰的变量可以被修改。

class MyClass {
public:
    void increment() const {
        counter++;  // 因为 counter 是 mutable,可以在 const 函数中修改
    }
private:
    mutable int counter;
};

常量指针、指针常量

static:在函数内部:定义静态局部变量,该变量在整个程序运行期间保持其值,且只初始化一次。
在类成员中:定义里的静态成员变量或静态成员函数,这些成员属于类,而不是类的实例。
在全局作用域中:将变量或函数的作用域限制在定义它的文件中,防止其他文件访问它。
注意事项:static变量的生命周期和作用域是关键所在,特别是在类中使用时,他们可以在不需要实例化类的情况下访问。

宏定义(#define)与inline的对比:
1.宏定义只是简单的文本替换,不进行类型检查,而inline就是类型安全的,可以避免宏定义中的错误。
2.inline函数在某些编译器中可能不会被完整内联,但其代码更清晰且安全。它会进行类型安全检查、语句是否正确等编译功能,inline是函数

constexpr:定义在编译时可以求值的常量。它可以用于常量表达式和函数,在编译时计算器值可以提高运行时性能。

constexpr int square(int x) {
    return x * x;
}

typedef和using:
作用:用于定义类型别名,typedef是旧的C风格,using是C++11引入的新方式,语法更直观。

typedef int MyInt;   // 旧方式
using MyInt = int;   // 新方式

mutable:允许在const对象中修改mutable修饰的成员变量

class MyClass {
    mutable int counter;
};

volatile:用于提示编译器,不要对volatile修饰的变量进行优化。它常用于多线程编程或硬件编程,防止编译器对变量的读取或写入进行优化,从而保持变量的实时性。

volatile int flag = 1;

enum:枚举类型,定义一组命名常量,通常用于表示状态或选项。

enum Color { Red, Green, Blue };

模板
C++模板是泛型编程的核心机制。它允许编写与类型无关的代码,从而在编译时生成与具体类型相关的代码实例。模板不仅提高了代码的重用性,还提供了编译时的类型安全检查。C++中模板主要分为函数模板和类模板,此外还有模板特化、模板参数等机制,可以为模板编写提供额外的灵活性。
1.基本概念:模板主要目的是为开发者提供一种方式编写通用代码,而无需显式指定类型。C++编译器在编译时会根据用户提供的具体类型生成相应的代码实例化。
关键点:泛型编程、实例化、类型安全
泛化编程:通过模板实现编写类型无关的代码。
实例化:编译器根据模板参数生成特定类型的代码
类型安全:模板仍然保证类型安全,模板实例化时会进行类型检查。
2.函数模板:用于定义可以处理多种不同类型的函数。示例代码:

template<typename T>
T add(T a, T b) {
    return a + b;
}
int result = add(3, 4);  // T 自动推导为 int
double result2 = add(3.5, 2.5);  // T 自动推导为 double
//或者
int result = add<int>(3, 4);
double result2 = add<double>(3.5, 2.5);

3.类模板
类模板用于定义可以使用不同类型的类结构,类似于函数模板,类模板也不需要在编写时指定类型。
用法代码:

template<typename T>
class Box {
private:
    T value;
public:
    Box(T val) : value(val) {}
    T getValue() const { return value; }
};
//使用方法:
Box<int> intBox(123);
std::cout << intBox.getValue() << std::endl;  // 输出 123

Box<double> doubleBox(45.67);
std::cout << doubleBox.getValue() << std::endl;  // 输出 45.67
//只定义一个Box类。然后通过不同类型进行实例化,生成多个不同的类
  1. 非类型模板参数
    除了类模板之外还有非类型参数,即:可以接受一个具体的值作为模板参数。
    代码示例:
template<int N>
class Array {
private:
    int arr[N];  // 数组大小为模板参数
public:
    void set(int index, int value) { arr[index] = value; }
    int get(int index) const { return arr[index]; }
};
Array<10> arr;
arr.set(0, 100);
std::cout << arr.get(0) << std::endl;  // 输出 100

5.模板特化:允许开发者位特定类型提供特殊的实现方式。当需要对特定类型进行特殊处理时,可以使用模板特化。
全特化实例:全特化是指为某个具体类型提供模板的完全不同的实现。

template<>
class Box<char> {
private:
    char value;
public:
    Box(char val) : value(val) {}
    char getValue() const { return value; }
    void print() const { std::cout << "Specialized for char: " << value << std::endl; }
};
Box<char> charBox('A');
charBox.print();  // 输出 "Specialized for char: A"

偏特化:是指为部分参数进行特化,而不是所有模板参数。

template<typename T>
class Box<T*> {  // 偏特化用于指针类型
private:
    T* value;
public:
    Box(T* val) : value(val) {}
    T* getValue() const { return value; }
};

6.模板编译机制:模板的编译机制与普通的函数和类有不同,主要表现为:模板定义和使用通常在一个翻译单元中,而不是像普通类一样放在.cpp文件中。模板实例化:编译器在遇到模板的具体使用时,会根据传入的类型参数进行模板实例化,生成对应的代码。
模板编译的两个阶段:
1.模板定义阶段:编译器对模板进行语法检查,但不会生成实际代码。此时,编译器只会检查模板的基本语法是否正确,而不考虑具体的模板参数。
2.模板实例化阶段:单模板被实际使用时(即实例化时),编译器会根据传入的模板参数生成具体的代码。
7.模板局限性:1.编译时间长(模板实例化是由编译器生成具体代码,因此使用模板可能导致编译时间的显著增加)、复杂的错误信息(模板编译错误往往较为复杂,错误信息也更难理解,尤其是模板嵌套较深时)、代码膨胀(模板实例会为每个具体类型生成一份代码,这可能导致编译后的可执行文件体积膨胀。)
8. 模板元编程
模板不仅可以用于类和函数,还可以用于编译时计算,这种技术被称为模板元编程。模板元编程利用模板的递归特性,在编译期间执行一些计算,而不是运行时计算。
示例代码:

template<int N>
struct Factorial {
    static const int value = N * Factorial<N - 1>::value;
};

template<>
struct Factorial<0> {
    static const int value = 1;
};

9.Variadic Templates(可变参数模板)
C++11引入了可变参数模板,允许模板接受不定数量的参数。这使得编写更灵活的模板代码可以成为可能。
示例代码

template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    if constexpr(sizeof...(args) > 0) {
        print(args...);
    }
}

10 模板的应用
在C++中应用官方,STL(标准模板库)、智能指针

泛型编程
一种编程范式,旨在编写通用的代码,使得代码可以适用于多种类型,而不局限于某一个特定的数据类型。通俗的说,泛型编程就是让我们编写的代码更灵活、可重复、适应性强。不管处理的是整数、浮点数、字符串还是其他类型,都能使用统一套代码来处理。
核心:通用性、类型参数化
优点:代码复用性高、类型安全、可维护性强、灵活性高
缺点:编译时间增加、代码膨胀、复杂的错误信息

堆和栈的速度
栈更快一点。操作系统在底层对栈提供支持,会分配专门的寄存器存放栈的地址,长得进栈出栈操作也很简单,所以栈的效率更快。
堆的操作是C/C++库函数提供的,再分配内存的时候需要算法寻找合适的大小,并且获取堆的内容需要两次访问,第一次访问指针,第二次访问指针所指的的内存。

已经有了malloc和free,为什么还要new和delete
对于非内部数据对象(如类对象)只用malloc和free无法满足要求。该对象在创建的时候需要自动执行构造函数,在消亡的时候要自动执行析构函数,犹豫malloc/free是库函数而不是运算符,不在编译器的权利控制之下,也就不能自动执行构造函数和析构函数,因此需要new和delete

static
static的意思是静态的,用来修饰变量,函数和类成员。

变量:被static修饰的变量就是静态变量,它会在程序运行过程中一直存在,会被放在静态存储区。局部静态变量的作用域在函数体内,全局静态变量的作用域在这个文件内。
函数:被static修饰过的函数就是静态函数,静态函数只能在本文件中使用,不能被其他文件调用,也不会和其他文件中的同名函数冲突。
类:在类中,被static修饰的成员变量是类静态成员,这个静态成员会被类的多个对象共用。被static修饰的成员函数也属于静态成员,不是属于某个对象的,访问这个静态函数不需要引用对象名,而是通过引用类名来访问。
补充:

全局静态变量:
定义:.在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。
说明:
1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)
2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非它被显示初始化)
3)作用域:全局静态变量在声明它的文件之外是不可见的。
全局静态变量的好处:
1)不会被其他文件所访问,修改;
2)其他文件中可以使用相同名字的变量,不会发生冲突。

局部静态变量:
定义:在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。
说明:
1)内存中的位置:静态存储区
2)初始化:未经初始化的局部静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。

注意:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对它进行访问。
当static用来修饰全局变量的时候,它就改变了全局变量的作用域(在声明它的文件之外是不可见的),但是没有改变它的存放位置,还是在静态存储区中。

定义和声明
声明是告诉编译器变量的类型和名称,不会为变量分配空间
定义是对这个变量和函数进行内存分配和初始化,需要分配空间,同一个变量可以被声明多次,但只能被定义一次。

被free的内存是立刻还给操作系统嘛?
不是,向北ptmalloc使用双链表保存起来,当用户下一次申请内存的时候会尝试从这些内存中寻找合适的返回,避免系统频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。

volatitle
标明他修饰的变量十分容易被改编,所以编译器就不会对这个变量进行优化(CPU的优化是将改变量放到CPU寄存器而不是内存),进而提供稳定的访问。每次读取volatitle的变量,系统总是会从内存中读取这个变量,并且立刻将他的值保存。

STL中sort和stable_sort实现方法
STL中sort使用快排和插入排序实现,stable_sort使用归并排序

C++为什么没有垃圾回收?
1.首先,实现一个垃圾回收器会带来额外的时间和空间开销。需要开辟一定的空间保存指针的引用和对他们的标记mask。然后需要单独开辟一个线程在空闲的时候进行free操作。
2.垃圾回收会使得C++不适合进行很多底层操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值