C++随心记(三)

1. vector clear函数详解:释放元素、释放内存、内部调度过程

        直接声明的vector容器是一个普通变量,相当于一个未知大小的动态数组,不需要手动释放,超出作用于范围时会自动回收。

        如果vector内存储的是元素而不是指针,这些元素会随vector的析构而自动释放。

        如果vector内存储的是指针,在vector析构时,指针指向的由程序员new() / malloc() 分配的nei s

1.1 clean() 部分源码  + vector的影响:

        vector.clear()实际作用是将vector容器的size重置为0(size是vector容器当前存储的元素数目),但不会改变vector的容量(capacity表示当前vector在内存中申请的这片区域所能容纳的元素个数),并不会释放程序员自己动态分配的内存。

        真正释放内存的地方在vector的析构函数,在跳出某个vector的作用域后,析构函数自动启动,依次调用内部各个元素的析构函数,然后调用allocator中的deallocate释放容器本身的内存。  

// clear函数最终调用部分
for ( ; first < last; ++first) //遍历元素进行析构 
destroy(&*first); 

// destroy() 单指针版本  
template <class T>  
inline void destroy(T* pointer) {  
    pointer->~T();   // 唤起 dtor ~T()  
} 

// 其中first是迭代器
// *iterator 表示元素本身,&*iterator 是一个指针
// 之后唤起每个指针的析构函数

1.2 vector元素是对象(int、float等object)

        调用 clear 函数后,自动为当前存储的每个元素调用各自的析构函数,并将size置为0。

void clear() { erase(begin(), end()); }  

1.3 vector元素是普通指针

        如果存储的是指向对象的指针,则并不会调用相应的析构函数,也就是这些指针指向的对象并不会销毁。

        这种情况下如果直接调用clear()函数 或者 vector 销毁,内部元素(指向对象的指针)并不会自动释放,会造成内存泄漏。解释一下,如果需要删除的内部元素——指针,其指向的对象没有其他引用,在调用clear()之后将会失去这部分对象的访问能力,并且这部分对象也不会自动销毁,内存就不会释放!

        所以想要释放容器元素指向的对象,需要手动调用循环删除。

for(i= 0; i < vItem.size();i++)
      delete vItem[i];

这个链接内,有详细代码描述:vector的clear()的内部过程与析构函数调用_码农小张的博客-CSDN博客

2 常量表达式 

        值不会改变,并且在编译过程中就能得到计算结果的表达式,即能在编译时求值的表达式。字面值属于常量表达式,常量表达式初始化的 const 对象也是。

        其中字面值包括但不限于:算术类型、引用、指针等,在c++中除了void之外的内置类型都是字面值类型。

const int a = 24;   // a是常量表达式 && 编译期常量

const int b = a + 1; // b也是常量表达式  && 编译期常量表达式

int c = 8;          // c是一个变量

const int d = c;    // d属于运行期常量,也是属于常量的。
// 但d不是常量表达式,因为c在程序执行到达其所在的声明处时才初始化
// 所以变量d的值程序运行时才知道,这种写法能够正常编译。

const int len = get_size();  // len不是常量表达式,因为要具体值运行时才能获知

int s[a]; // 正确编译

这也说明了,由关键字const声明的不一定就是常量表达式!

3. constexpr

        在实际项目中,是否是常量表达式的判断很难,因此在c++11中引入的新概念 constexpr ,新标准规定,允许将变量声明为 constexpr 类型以便由编译器来验证变量的值是否是常量表达式。

        constexpr 只能定义编译期常量表达式,否则编译报错;同时初始化也必须使用常量表达式

        由 constexpr 指定符声明的变量在编译时计算得到函数或者变量的值,该变量一定是一个常量,并且必须使用常量表达式进行初始化

        constexpr 同样可以修饰函数:函数需要满足5个条件:

        1. 函数在使用前,必须定义;

        2. 函数必须有返回值;

        3. 只能存在一条  return 返回语句

        4. 返回的必须是常量表达式

        5. 参数和返回值必须是字面值类型

        使用constexpr修饰的函数,在执行初始化任务时,编译器对 constexpr 函数调用替换成结果值,为了能在编译中随时展开,constexpr函数被隐式地指定为内联函数(inline)

int func_A(int x) {
    return 2 * x;
}

// 需要注意的是函数B返回值不是编译期常量,传入的参数是字面值类型的,但在编译期尚未知晓
// 此时constexpr修饰的函数和普通函数一样
constexpr int func_B(int x) {
    return 5 * x;
}

constexpr int func_B() {
    return 10;
}

int main() {
    constexpr int x = 123;          // 编译通过,变量初始化
    constexpr int y1 = func_A(123); // 编译报错
    constexpr int y2 = func_B(123); // 编译通过,函数初始化,右侧常量表达式
}

错误使用示例:

int main() {
    int n;
    cin >> n;
    const int a = n + 10;      // 编译通过,运行期获得结果
    constexpr int b = n + 10;  // 编译错误
    
    return 0;
}

4. const

        const 修饰变量的语义要求编译器阻止所有对该变量的赋值行为,所以必须在 const 变量初始化时就提供初始值。

        但是,这个初值可以是编译期确定的值,也可以是运行期确定的值,换句话说const 变量的初始化可以延迟到运行时期。

int main() {
    int n;
    cin >> n;
    const int a = n + 10;      // 编译通过,运行期获得结果
    
    int x[n];  // 编译通过
    int y[a];  // 编译通过 在作为数组长度时候,是直接替换的

    return 0;
}

5. const和constexpr区别

        constexpr 的主要功能是使运算可以在编译期完成,并且保证表达式在语义上是类型安全的。因此规定,constexpr 定义的对象必须是编译期常量表达式,其初始化的值也必须是常量表达式。

        const 修饰一个对象表示它是常量,这仅仅表达了该对象在初始化后就不再改变!,并未区分出编译期常量 或者 运行期常量,所以为其初始化的值不一定是常量表达式。进一步讲,const 更像是一种 “写权限控制”,即对于那些被 const 修饰的变量,不能通过该变量对所指对象作任何修改(包括常量引用、指向常量的指针、一般常量)。

猜你喜欢

转载自blog.csdn.net/xiao_ling_yun/article/details/126057881