静态局部变量和全局变量一样,数据都存放在全局区域,所以在主程序之前,编译器已经为其分配好了内存。在 C++ 中,初始化是在执行相关代码时才会进行初始化。
不可以 虚函数用于实现运行时的多态,或者称为晚绑定或动态绑定。而内联函数用于提高效率。内联函数的原理是,在编译期间,对调用内联函数的地方的代码替换成函数代码。内联函数对于程序中需要频繁使用和调用的小函数非常有用。 虚函数要求在运行时进行类型确定,而内联函数要求在编译期完成相关的函数替换
static 修饰成员变量,在数据段分配内存。 static 修饰成员函数,在代码区分配内存。
C++中基类采用 virtual 虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。
在构造函数和析构函数中最好不要调用虚函数 构造函数或者析构函数调用虚函数并不会发挥虚函数动态绑定的特性,跟普通函数没区别 即使构造函数或者析构函数如果能成功调用虚函数, 程序的运行结果也是不可控的
A(A&& b){ *** } // a = std::move(b)
A& operator=(A&& b){ *** return *this; }
前者是把 new、delete 运算符重载为 private 属性。后者是把构造、析构函数设为 protected 属性,再用子类来动态创建 建立类的对象有两种方式:
带有默认构造函数的类成员对象 带有默认构造函数的基类 带有一个虚函数的类 带有一个虚基类的类 合成的默认构造函数中,只有基类子对象和成员类对象会被初始化。所有其他的非静态数据成员都不会被初始化
C++中提供了 explicit 关键字,在构造函数声明的时候加上 explicit 关键字,能够禁止隐式转换 如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制。可以通过将构造函数声明为 explicit 加以制止隐式类型转换,关键字 explicit 只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit。
将一个派生类的指针转换成某一个基类指针,编译器会将指针的值偏移到该基类在对象内存中的起始位置
源代码-->预处理-->编译-->优化-->汇编-->链接-->可执行文件
通过下标访问 vector 中的元素时不会做边界检查,即便下标越界。也就是说,下标与 first 迭代器相加的结果超过了 finish 迭代器的位置,程序也不会报错,而是返回这个地址中存储的值。如果想在访问 vector 中的元素时首先进行边界检查,可以使用 vector 中的 at 函数。通过使用 at 函数不但可以通过下标访问 vector 中的元素,而且在 at 函数内部会对下标进行边界检查 map 的下标运算符[]的作用是:将 key 作为下标去执行查找,并返回相应的值;如果不存在这个 key,就将一个具有该 key 和 value 的默认值插入这个 map erase()函数,只能删除内容,不能改变容量大小; erase 成员函数,它删除了 itVect 迭代器指向的元素,并且返回要被删除的 itVect 之后的迭代器,迭代器相当于一个智能指针,之后迭代器将失效。;clear()函数,只能清空内容,不能改变容量大小;如果要想在删除内容的同时释放内存,那么你可以选择 deque 容器 int main(){ vector<int> vec(10, 0); int arr[10] = {0,0,0,0,0,0,0,0,0,0}; cout << vec[11] << endl; // 输出值 cout << *(vec.begin()+11) << endl; // 输出值 cout << vec.at(11); // 报错,越界 cout << arr[11]; // 输出值 }
vector 通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,再插入新增的元素 初始时刻 vector 的 capacity 为 0,塞入第一个元素后 capacity 增加为 1 不同的编译器实现的扩容方式不一样,VS2015 中以 1.5 倍扩容,GCC 以 2 倍扩容 对比可以发现采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到 O(n)的时间复杂度,因此,使用成倍的方式扩容 以 2 倍的方式扩容,导致下一次申请的内存必然大于之前分配内存的总和,导致之前分配的内存不能再被使用,所以最好倍增长因子设置为(1,2)之间 向量容器 vector 的成员函数 pop_back()可以删除最后一个元素 而函数 erase()可以删除由一个 iterator 指出的元素,也可以删除一个指定范围的元素 还可以采用通用算法 remove()来删除 vector 容器中的元素 采用 remove 一般情况下不会改变容器的大小,而 pop_back()与 erase()等成员函数会改变容器的大小,使得之后所有迭代器、引用和指针都失效
函数指针指向的是特殊的数据类型,函数的类型是由其返回的数据类型和其参数列表共同决定的,而函数的名称则不是其类型的一部分 函数指针声明 int (*pf)(const int&, const int&); 上面的 pf 就是一个函数指针,指向所有返回类型为 int,并带有两个 const int & 参数的函数。应该注意的是 *pf 两边的括号是必须的否则就是声明了一个返回int *类型的函数 函数指针赋值 指针名 = 函数名; 指针名 = &函数名;
代码段 只读,可共享; 代码段(code segment/text segment )通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等 数据段 储存已被初始化了的静态数据。数据段(data segment )通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。 BSS 段 未初始化的数据段。BSS 段(bss segment )通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS 是英文 Block Started by Symbol 的简称。BSS 段属于静态内存分配(BSS 段 和 data 段的区别是 ,如果一个全局变量没有被初始化(或被初始化为 0),那么他就存放在 bss 段;如果一个全局变量被初始化为非 0,那么他就被存放在 data 段) 堆(heap ) 堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减) 栈(stack) 栈又称堆栈,是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{} ”中定义的变量(但不包括 static 声明的变量,static 意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/ 恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。 共享内存映射区域 栈和堆之间,有一个共享内存的映射的区域。这个就是共享内存存放的地方。一般共享内存的默认大小是 32M
综上: 栈区(stack) — 由编译器自动分配释放,存放函数的参数值,局部变量的值等其操作方式类似于数据结构中的栈 堆区(heap) — 一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS(操作系统)回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表 全局区(静态区)(static) — 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放 文字常量区 — 常量字符串就是放在这里的。程序结束后由系统释放 程序代码区 — 存放函数体的二进制代码
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生 memory leak 空间大小:一般来讲在 32 位系统下,堆内存可以达到 4G 的空间,但是对于栈来讲,一般都是有一定的空间大小的 碎片问题:对于堆来讲,频繁的 new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出 生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。 分配方式:堆都是动态分配的,没有静态分配的堆。栈有 2 种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 alloca 函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高,堆则是 C/C++函数库提供的
野指针:指向内存被释放的内存或者没有访问权限的内存的指针。它的成因有三个:1. 指针变量没有被初始化。2. 指针 p 被 free 或者 delete 之后,没有置为 NULL。3.指针操作超越了变量的作用范围 (觉得存在错误)
野指针:野指针指,访问一个已删除或访问受限的内存区域的指针,野指针不能判断是否为 NULL 来避免。指针没有初始化,释放后没有置空,越界 悬空指针:一个指针的指向对象已被删除,那么就成了悬空指针。野指针是那些未初始化的指针
内存泄漏 是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制 (内存泄露的排查诊断与解决)
new 简单类型直接调用 operator new 分配内存;而对于复杂结构,先调用 operator new 分配内存,然后在分配的内存上调用构造函数;对于简单类型,new[]计算好大小后调用 operator new;对于复杂数据结构,new[] 先调用 operator new[]分配内存,然后在 p 的前四个字节写入数组大小 n,然后调用 n 次构造函数,针对复杂类型,new[]会额外存储数组大小 delete 简单数据类型默认只是调用 free 函数;复杂数据类型先调用析构函数再调用 operator delete;针对简单类型,delete 和 delete[]等同。假设指针 p 指向 new[]分配的内存。因为要 4 字节存储数组大小,实际分配的内存地址为[p-4],系统记录的也是这个地址。delete[]实际释放的就是 p-4 指向的内存。而 delete 会直接释放 p 指向的内存,这个内存根本没有被系统记录,所以会崩溃 需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了
RAII 全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为 C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在 RAII 的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定 智能指针(std::shared_ptr 和 std::unique_ptr)即 RAII 最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记 delete 造成的内存泄漏。毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现 delete 了
class A{ int a; double b; }; class B{ int a, b; double c; }; class C{ int a; double b; int c; }; class D{ int a; double b; int c,d; }; int main(){ cout << sizeof(int) << " " << sizeof(double) << endl; cout << sizeof(A) << " " << sizeof(B) << " " << sizeof(C) << " " << sizeof(D) << endl; } // out /* 4 8 16 16 24 24 */
平台原因(移植原因)
性能原因:
#define MAX(x,y) (x > y ? x:y)
define 是关键字,inline 是函数 宏定义在预处理阶段进行文本替换,inline 函数在编译阶段进行替换 inline 函数有类型检查,相比宏定义比较安全
在 C/C++中,对函数参数的扫描是从后向前的。C/C++的函数参数是通过压入堆栈的方式来给函数传参数的,所以最后压入的参数总是能够被函数找到,因为它就在堆栈指针的上方。printf 的第一个被找到的参数就是那个字符指针,就是被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数来判断参数个数及数据类型,通过这些就可算出数据需要的堆栈指针的偏移量了。
函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。即函数模板允许隐式调用和显式调用而类模板只能显示调用。在使用时类模板必须加
<T>
,而函数模板不必
全局变量(外部变量)的说明之前再冠以 static 就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个原文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。static 全局变量与普通的全局变量的区别是 static 全局变量只初始化一次,防止在其他文件单元被引用。 static 函数与普通的函数作用域不同。只在当前源文件中使用的函数应该声明为内部函数(static),内部函数应该在当前源文件 中说明和定义。对于可在当前源文件以外使用的函数应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。static 函数与普通函数最主要区别是 static 函数在内存中只有一份,普通静态函数在每个被调用中维持一份拷贝,程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)中
++it
, it++
哪个好前置返回一个引用,后置返回一个对象 前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低 ++i实现 int& operator++() { *this += 1; return *this; } i++实现 int operator++(int) { int temp = *this; ++*this; return temp; }
因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的 CPP 文件时才会去找对应的模板声明和实现,在这种情况下编译器是不知道实现模板类或函数的 CPP 文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接程序找地址。但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。 模板定义很特殊。由
template<…>
处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。
参数的含义是程序在命令行下运行的时候,需要输入 argc 个参数,每个参数是以 char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针char * 指向的内存中,数组的中元素的个数为 argc 个,第一个参数为程序的名称。
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址端。 小端模式,是指数据的高字节保存在内存的高地址中,低位字节保存在在内存的低地址端。 检测1直接读取存放在内存中的十六进制数值,取低位进行值判断 int a = 0x12345678; int *c = &a; c[0] == 0x12 大端模式 c[0] == 0x78 小端模式
对于类类型的对象而言,用 malloc/free 无法满足要求的。对象在创建的时候要自动执行构造函数,消亡之前要调用析构函数。由于 malloc/free 是库函数而不是运算符,不在编译器控制之内,不能把执行构造函数和析构函数的任务强加给它,因此,C++还需要 new/delete。
拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象)。拷贝构造函数用来初始化一个非引用类类型对象,如果用传值的方式进行传参数,那么构造实参需要调用拷贝构造函数,而拷贝构造函数需要传递实参,所以会一直递归。
当在类的非静态成员函数访问类的非静态成员时,编译器会自动将对象的地址传给作为隐含参数传递给函数,这个隐含参数就是 this 指针。即使你并没有写 this 指针,编译器在链接时也会加上 this 的,对各成员的访问都是通过 this 的。例如你建立了类的多个对象时,在调用类的成员函数时,你并不知道具体是哪个对象在调用,此时你可以通过查看 this 指针来查看具体是哪个对象在调用。This 指针首先入栈,然后成员函数的参数从右向左进行入栈,最后函数返回地址入栈。
shared_ptr<int> p = make_shared<int>(42);
通常用 auto 更方便,
auto p = …;shared_ptr<int> p2(new int(2));
每个 shared_ptr 都有一个关联的计数器,通常称为引用计数,一旦一个 shared_ptr 的计数器变为 0,它就会自动释放自己所管理的对象; shared_ptr 的析构函数就会递减它所指的对象的引用计数。如果引用计数变为 0,shared_ptr 的析构函数就会销毁对象,并释放它占用的内存。