- c++的三大特性:封装,多态,继承
- 局部变量能否和全局变量重名?可以,局部变量会屏蔽全局变量。在使用全局变量时需要使用 ":: "。
- 拷贝构造函数:参数为同类型的对象的常量引用的构造函数
- 函数指针:int (*f)(int,int) = & max;
- 静态成员函数没有this指针。
- 静态成员不能是虚函数。虚函数在程序运行时,根据实际对象的类型来调用相应的函数。而静态成员函数是不依赖于任何对象的,它不需要通过对象来访问。 静态成员函数没有this指针,所以无法访问vptr. 这就是为何static函数不能为virtual。
- 静态成员不可以访问非静态成员。在类为实例化时,没有具体的对象,也就没有相应的非静态成员存在
- 静态成员访问非静态成员方法。通过参数传入所在的类的对象。
- static修饰的全局变量声明与定义同时进行,也就是说当你在头文件中使用static声明了全局变量后,它也同时被定义了;最后,static修饰全局变量的作用域只能是本身的编译单元,也就是说它的“全局”只对本编译单元有效,其他编译单元无法使用extern引用该变量。extern和static不能同时修饰一个变量。
- const成员函数:函数名前面使用const 表示返回值为const,后面加 const表示函数不可以修改class的成员。后面加 const,传入的this指针为常量指针,不可以通过指针改变其内容。
- 静态成员函数后不用const修饰:静态成员不访问成员变量,后加const修饰无意义。
- 动态多态:(虚函数)对于相关的对象类型,确定它们之间的一个共同功能集,然后在基类中,把这些共同的功能声明为多个公共的虚函数接口。各个子类重写这些虚函数,以完成具体的功能。编译期要执行的具体程序无法确定,要等运行时才能确定。
- 静态多态:模板。编译期执行的程序便被确定。
- 数组作为参数传递给函数或将其赋值给指针时,数组会退化成指针,此时 对其使用
sizeof
将返回指针的大小,而不是数组的大小。 - 字符串char[]的实际长度比其内容长度要+1,因为多一个'\0'。
- 类的声明,会调用默认构造函数。 类指针的声明,不会调用构造函数。
- FILE *fopen(const char *filename, const char *mode);mode:r+:如果文件不存在返回null。从文件头开始写,覆盖的文字最后不加eof,未覆盖的字符保留。w+:当文件不存在则创建文件。从文件头开始写,覆盖的文字最后加eof,原文件将会被清空。
- 重载(overload):是函数名相同,参数列表不同。重载只是在类的内部存在。但是不能靠返回类型来判断。
- 重写(override):也叫做覆盖。子类重新定义父类中有相同名称和参数的虚函数。函数特征相同。但是具体实现不同,主要是在继承关系中出现的 。被重写的函数不能是static。必须是virtual。重写函数必须有相同的类型,名称和参数列表。重写函数的访问修饰符(protect,public,private)可以不同。
- 重定义(redefining):也叫隐藏。子类重新定义父类有相同名称的非虚函数(参数列表可以不同)。
- 如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。
- 方法覆盖(重写)对返回值的要求是:小于等于父类的返回值。方法的覆盖对访问要求是:大于等于父类的访问权限。
- 如果类的一个成员函数,调用了其他的成员函数,会调用当前类中重写/重定义的函数。
- 编译器无法分辨仅有返回值类型不同的两个函数。返回值类型不能构成overload的条件。
- C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称
- 只有友元类才可以访问私有成员,别的类都不行(子类也不行)
- 当scanf的输入占用的内存大于参数所指向的内存时,会将地位存入内存。当scanf的输入占用的内存小鱼参数所指向的内存时无影响(高位补充0)。
- 当printf输出的占位符小于七参数所指向的内存时:???
- 32位/64位:CPU位宽(数据总线宽度)。
- 虚拟地址空间的大小由操作系统决定,32位的操作系统虚拟地址空间的大小为 2^32 字节,也就是 4G,64 系统的操作系统虚拟地址空间大小为 2^64 字节。
- int类型在32位和64位操作系统都是4个字节大小,指针在32位操作系统是4个字节,在64位操作系统是8个字节。
- 在虚拟地址模式下,一个程序可以使用的内存容量跟计算机的物理内存(也就是你的内存条)没有关系,它由虚拟地址的取值范围决定。
- 在32位操作系统中,程序能使用的最大内存是 4GB,也就是2的32次方。
- 在64位操作系统中,理论上能够访问的虚拟地址的范围是 2^64。这是一个很大的值,几乎是无限的,就目前的技术来讲,不但物理内存不可能做到这么大,CPU的寻址能力也没有这么大,实现64位长的虚拟地址只会增加系统的复杂度,带不来任何好处。Windows 和 Linux 都对虚拟地址进行了限制,仅使用虚拟地址的低48位(6个字节),总的虚拟地址大小为 2^48 = 256TB。
- 分段机制会把程序的虚拟地址分成 4 个段:段0:代码段。段1:数据段。段2:堆。段3:栈。
- 一个由C/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)(段3)— 栈(stack):又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变 量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以 栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
2、堆区(heap) (段2)— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 动态内存分配
3、全局(静态)区(static)(段1):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(BSS段)。程序结束后由系统释放 静态内存分配
4、常量区(段1) —常量字符串就是放在这里的。 程序结束后由系统释放 静态内存分配
5、程序代码区(段0)—存放函数体的二进制代码。静态内存分配 - 初始化变量已经提前赋予了某个特定的初值,这些值同时保存于目标文件和可执行文件中。当程序开始运行时,操作系统将这些值拷贝至内存中一块名为数据段(data segment)的区域。
- 对未初始化变量,操作系统假设其初值均为0, 因此没有必要对这些值进行拷贝,操作系统保留一部分全为0内存空间,我们称其为 bss 段(bss segment)。 这就意味着可执行文件可以节省这部分存储空间:初始化变量的初始值必须保存于文件中,但对于未初始化变量我们只需要计算出它们占用的空间大小即可。
- 函数和成员函数在代码区(代码段)。
- 全局数据区+堆+栈=数据段。
- 静态存储区=全局数据区+bss段+代码段。
- 动态存储区=堆+栈
- bss不占用可执行文件空间,其内容由操作系统初始化(清零),裸机程序需要自行手动清零。 而data段则需要占用可执行文件空间,其内容由程序初始化。
- 虚指针存储在全局数据区。虚函数表存储在常量区。
- 段页式内存管理:先将程序划分为多个有逻辑意义的段,接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页。地址结构=段号+段内页号+页内位移。每一个程序一张段表,每个段又建立一张页表,段表中的地址是页表的起始地址,而页表中的地址则为某页的物理页号,
- 段页式地址变换中要得到物理地址须经过三次内存访问: 第一次访问段表,得到页表起始地址; 第二次访问页表,得到物理页号; 第三次将物理页号与页内位移组合,得到物理地址。
- 虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了。所以页表一定要覆盖全部虚拟地址空间。
- 不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。
- TLB(Translation Lookaside Buffer):通常称为页表缓存、转址旁路缓存、快表CPU 芯片中专门存放程序最常访问的页表项的 Cache。
- 逻辑地址和线性地址: 程序所使用的地址,通常是没被段式内存管理映射的地址,称为逻辑地址; 通过段式内存管理映射的地址,称为线性地址,也叫虚拟地址;
- Linux 系统中的每个段都是从 0 地址开始的整个 4GB 虚拟空间(32 位环境下),也就是所有的段的起始地址都是一样的。这意味着,Linux 系统中的代码,包括操作系统本身的代码和应用程序代码,所面对的地址空间都是线性地址空间(虚拟地址),这种做法相当于屏蔽了处理器中的逻辑地址概念,段只被用于访问控制和内存保护。
- Linux每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。
- 用户空间内存,从低到高分别是 7 种不同的内存段: 程序文件段,包括二进制可执行代码; 已初始化数据段,包括静态常量; 未初始化数据段,包括未初始化的静态变量; 堆段,包括动态分配的内存,从低地址开始向上增长; 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关); 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;
- 每个进程都有自己的虚拟空间,而物理内存只有一个,所以当启用了大量的进程,物理内存必然会很紧张,于是操作系统会通过内存交换技术,把不常使用的内存暂时存放到硬盘(换出),在需要的时候再装载回物理内存(换入)。
- C++不是类型安全的,java和C#是类型安全的
- 如果你只是声明一个空类,不做任何事情的话,编译器会自动为你生成一个默认构造函数、一个拷贝默认构造函数、一个默认拷贝赋值操作符和一个默认析构函数。这些函数只有在第一次被调用时,才会别编译器创建。所有这些函数都是inline和public的。
- 类中没有任何成员变量,其对象的大小为1!因为没有地址就没法存储这个对象,c++为没有成员变量的函数都开辟了一个字节的地址。
- 先创建的变量先构造。先构造的后析构。因为局部变量存储于栈中,先进后出。
- inline 函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。
- inline函数的实现:在任何调用内联函数的地方都内联函数的函数体,以避免频繁调用函数对栈内存重复开辟所带来的消耗。内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。 如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
- 关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用。
- 析构函数不可以被重载,因为析构函数只能有一个,且不能带参数。
- 类的构造函数一般是共有的(public),但有时也把构造函数声明为私有的(private),其作用是限制其创建该类对象的范围,这时,只能在本类和友元中创建该类对象。
- 对于霍夫曼树来说,其叶结点权值越小,离根越远,叶结点权值越大,离根越近,此外其仅有叶结点的出度为0,其他结点出度均为2。
- 常量在C++中可分为6种,它们是整型常量、实型常量(浮点型常量)、字符型常量、字符串常量、符号常量、逻辑型常量.
- 实型常量:又称实数或浮点数。在C语言中可以用单精度型和双精度型两种形式表示实型常量。
- 符号常量:在C语言中,可以用一个标识符来表示一个常量。符号常量的定义的两种方式:#define 和const。宏定义: 由预处理处理,单纯的是纯文本替换。const常量: 由C++编译器处理,提供类型检查和作用域检查。
- 普通的整型、浮点型或字符型常量在使用的时候通过立即数来实现,不在内存中存储。只有字符串常量和被const修饰的全局变量才会存储在数据段的常量区。
- 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
- 友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
- 因为友元函数没有this指针,则参数要有三种情况: 要访问非static成员时,需要对象做参数; 要访问static成员或全局变量时,则不需要对象做参数; 如果做参数的对象是全局对象,则不需要对象做参数. 可以直接调用友元函数,不需要通过对象或指针
- 友元关系不会被继承。父类的友元不是子类的友元。子类的友元是父类的友元,可以访问父类的私有成员和保护成成员。
- 友元关系不会被继承。父类是某个类的友元。子类不是该类的友元。
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
- 子类就是派生类。
- 对象之间的赋值是成员变量的赋值,成员函数不存在赋值问题。对象之间的赋值不会影响成员函数,也不会影响 this 指针。
- 只能用派生类对象给基类对象赋值,而不能用基类对象给派生类对象赋值
- protected(受保护)成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的。
- 每个成员函数的第一个参数默认都有个指向对象的 this 指针。
- 对象/对象指针没有初始化,也可以成功调用成员函数。
- this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。 只能在“成员函数”的内部使用 this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递(存储在ecx寄存器中)(CPU寄存器,不在内存中),不需要用户传递。
- A* p = nullptr; p->Print();//会在编译器下变成 // print(p);变成一个传参的过程,并没有发生空指针的解引用。
- 如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
- 指向函数的指针变量没有 ++ 和 -- 运算。
- 对于指向类成员函数的函数指针,引用时必须传入一个类对象的this指针,所以必须由类实体调用。void (A::*ptr)(int) = &A::setA; A a; (a.*ptr)(10000);
- 指向静态成员函数的指针: 在使用时需要指定类的作用域。这种指针可以指向其他静态成员函数,或者与普通函数指针一样,指向非成员函数。但是不可以指向非静态成员函数
- 指向非静态成员函数的指针: 不可以指向静态成员函数和非成员函数。
- 数组名是地址常量,指针是地址变量。
- 赋值运算符" = "是从右至左结合, x = y = z = 2;会对xyz依次赋值。
- C++ 初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序,也就是说初始化列表的顺序没有意义。所以如果在initialization list中,某个成员变量a的参数(初值)为其他成员变量b,如果变量b的声明位于a之前,那么b将会先进行初始化,如果b已经初始化完毕,那么a可以顺利使用b 的值进行初始化,如果变量a的声明在b之前,那么将会先对a进行初始化,而此时b还未进行初始化,b的值是一个不确定的值,a将会使用这个不确定的值进行初始化。
- 左值:可寻址的变量,有持久性,有自己的地址;右值:不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的,不在内存中存储,立即数。int x = 6; x:左值。x*6:右值。右值引用(c++11):int &&z3 = x * 6; 不可将左值赋值给右值引用。
- 浅拷贝:使用原生的赋值运算符函数去赋值有指针成员变量的对象,就会使得两个对象的指针地址也是一样的,也就是两个对象的指针成员变量指向的地址是同一个地方。当一个对象释放该指针成员变量时,另一个对象对应的指针成员变量指向的内存也会被释放,内存不安全。
- 深拷贝:重载赋值运算符函数:申请一块新的内存空间并将自己的指针成员变量指向该内存空间,将目标变量的指针类型的成员变量指向的地址内的内容赋值到新申请的内存空间中。
- 参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝,将一块内存上的数据复制到另一块内存上。
- C/C++ 禁止在函数调用时直接传递数组的内容,而是强制传递数组指针。
- C++ 中的引用必须在定义时进行初始化,在初始化后不可以再引用其他数据。引用的赋值是地址传递。
- 当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边(作为左值)。
- 引用作为函数返回值时,被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
- 指针和引用的区别:(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已,引用的类型必须和所引用的类型严格匹配,。(2)指针可以有多级,但是引用只能是一级 (3)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化; (4)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。 (5)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小; (6)指针和引用的自增(++)运算意义不一样;
- 常量引用:(1)指向常量对象时,一定要使用“常量引用”,而不能是一般的引用。(2)“常量引用”可以指向一个非常量对象,但不允许用过该引用修改非常量对象的值。(3)常量引用可以引用左值,非常量引用不可以。(4)在函数参数中,使用常量引用非常重要。因为函数有可能接受临时对象,而且同时需要禁止对所引用对象的一切修改。
- 常量指针:只能够读取内存中数据,却不能够修改内存中数据的指针,称为指向常量的指针,简称常量指针。const int * p; int const * p;
- 指针常量:指针本身是一个常量,但是指针所指向的内容可以改变。int * const p=&a;
- 用指针传递参数,可以得到指针变量保存的地址,使用这个地址对地址内的内容进行修改。但是作为参数的指针变量仍然是形参,对指针变量的值(该指针变量保存的地址)的修改不会被保留。
- 引用是类型安全的,而指针不是 (引用比指针多了类型检查)
- 编译器会将合法的变量名放到一个叫“符号表”的一个表中。
- 不同的编译器实现的vector扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。
- vector.capacity():返回容器当前已为之分配空间的元素数。初始时刻vector的capacity为0,插入第一个元素后capacity增加为1。
- vector.reserve( size_type new_cap ):增加 vector 的容量到大于或等于 new_cap 的值。若 new_cap 大于当前的 capacity() ,则分配新存储,否则该方法不做任何事。 reserve() 不更改 vector 的 size 。
- C++编译过程:编译(预编译,编译,汇编),链接
- 一个.cpp文件就是一个编译单元
- 头文件中只能放声明:变量的声明,函数的声明,类的声明。
- 类只有声明,没有定义。函数后接大括号的是定义。变量的声明:必须使用 extern;不能为变量赋予初始值
- 头文件中不能放函数的以及变量定义,因为当同时编译多个编译单元并连接为一个可执行文件的时候,如果这些编译单元中有重复引用同一个头文件,, 如果头文件中定义的函数又没有定义成局部函数, 那么在连接时, 就会发现多个相同的函数, 就会报错。如果在 header file 中定义全局变量, 并且将此全局变量赋初值, 那么在多个引用此 header file 的 implementation file 中同样存在相同变量名的拷贝, 关键是此变量被赋了初值, 所以编译器就会将此变量放入 DATA 段, 最终在连接阶段, 会在 DATA 段 中存在多个相同的变量, 它无法将这些变量统一成一个变量, 也就是仅为此变量分配一个空间, 而不是多份空间, 假定这个变量在 header file 中没有赋初值, 编译器就会将之放入 BSS 段, 连接器会对 BSS 段 的多个同名变量仅分配一个存储空间。
- 系统调用dlopen :获取共享库的名称,并将其载入运行程序的地址空间。由于载入的共享库本身也可能存在未定义符号,因此,调用 dlopen 很可能同时触发多个其他共享库的载入。
- new:尝试分配和初始化指定类型或占位符类型的对象或对象数组,并返回指向对象(或指向数组初始对象)的适当类型化的非零指针。
- 使用 new 为 C++ 类对象分配内存时,将在分配内存后调用对象的构造函数。
- 使用 delete 运算符解除由 new 运算符分配的内存。 使用 delete[] 运算符删除由 new 运算符分配的数组。
- new和delete必须配对使用,new不可以和free配对使用。
- 使用 new 运算符分配数组时,无法对每个元素执行显式初始化;仅调用默认构造函数(如果存在)。
- 使用 new 运算符分配的对象在退出定义它们的范围(大括号)时不会被销毁。所以一定要主要在退出判断体/循环体时要对相关对象进行销毁。
- new 运算符主要执行三项操作:1. 定位并保留要分配的对象的存储。 此阶段完成后,将分配正确的存储量,但它还不是对象。2. 初始化对象。 初始化完成后,将为成为对象的已分配存储显示足够的信息。 3.返回指向对象的指针,该对象所属的指针类型派生自 new-type-id 或 type-id。 程序使用此指针来访问最近分配的对象。
- array 容器的大小是固定的,无法动态的扩展或收缩,这也就意味着,在使用该容器的过程无法借由增加或移除元素而改变其大小,它只允许访问或者替换存储的元素。
- 运算符重载不可以改变运算符的优先级。
- 默认参数只可在函数声明中设定一次只有在无函数声明时,才可以在函数定义中设定。
- 默认参数定义的顺序为自右到左。即如果一个参数设定了缺省值时,其右边的参数都要有缺省值。
- C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,
- vector::void reserve( size_type new_cap );增加 vector 的容量到大于或等于 new_cap 的值。若 new_cap 大于当前的 capacity() ,则分配新存储,所有迭代器(包含尾后迭代器)和所有到元素的引用都被非法化。否则该方法不做任何事。reserve() 不更改 vector 的 size 。
- vector::void resize( size_type count );重设容器大小以容纳 count 个元素。 若当前大小大于 count ,则减小容器为其首 count 个元素。 若当前大小小于 count , 1) 则后附额外的默认插入的元素 2) 则后附额外的 value 的副本
参考:
-
小林coding 链接:https://www.zhihu.com/question/290504400/answer/1964845950
-
「Bird鸟人」链接:https://blog.csdn.net/wcc27857285/article/details/84891952
-
菜鸟教程 链接:菜鸟教程 - 学的不仅是技术,更是梦想!
-
英文:Beginner's Guide to Linkers/中文:帮 C/C++ 程序员彻底了解链接器_oncealong的博客-CSDN博客