No description
AI Reading Assistant
Whole-book reading guide from stratified index samples; jump to passages in the text
Tags
AI categories
Shown after the reading guide is generated (max 3)
Text Preview (First 20 pages)
Registered users can read the full content for free
Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.
Page
1
代码随想录知识星球精华(最强⼋股⽂) 这份PDF总结了 代码随想录知识星球 的全部精华内容,覆盖了⼏乎程序员学习必备的内容。 知识星球⾥很多录友拿到了⼤⼚offer,包括科班 和 ⾮科班的,⽽他们的每⽇学习总结都是每⼀位准备求职的程序 员必备的内容,也是⾼频考点,⽽这些内容⼜经过了我(程序员Carl)精⼼挑选,所以都是精华中的精华。 录友们在星球⾥的每⽇总结,很多都相当精彩,如果没有⼀个整理,或者系统性的沉底,那么也是⽯沉⼤海,但这 些内容对后来星球的录友是⾮常有价值的,⽽且星球⽼录友们,也未必对⾃⼰曾经的精彩内容作出整理。 所以我打算将知识星球⾥的内容整理出PDF,⽅便新来的录友快速学习,⽅便星球⽼录友们快速复习。 也有很多录友都问我,去哪找靠谱的⼋股⽂来看。 ⽽这份PDF就是最强⼋股⽂!是经过⽆数拿过⼤⼚offer录友们 的千锤百炼的精华! 筹划了⼏个⽉,现在这份 代码随想录知识星球-学习⼿册 PDF 终于问世了。 这份PDF 很⼲货,总共有 21w字,将近300张导图,表格,分析图 。覆盖如下内容: C++,Java,Go,数据结构和算法 操作系统,数据库,计算机⽹络,设计模式,Linux等等⾼频考点 将近30份⾯经 各个岗位学习路线 offer对⽐,精彩问答 录友们程序⼈⽣的各种真实故事 资料分享 还有很多很多,可以打卡PDF看看⽬录。 这份PDF第⼀时间开放在知识星球⾥,这是星球⾥录友们的福⾳! 作者清单 同时PDF的作者也有很多,这⾥都将其⼀⼀列出,不落下⼀位,⼤家可以追随星球⽼录友们的脚步,⼀起学习! 以下排名不分先后。 程序员Carl Charon 对该PDF整理做了很多⼯作,整理的很有调理 weikunkun
Page
2
Goaway posper ironartisa* 壮 Jingle 懵d Nx Powerstot Louis ⾼欧叶尼兹 。超。zpc 逍遥游 33 ⼩⼀ 梅⻄ 本⼈ 习惯过了头 ⼩⼀ Mona ⽩夜 追⻛少年 洋洋 仰望 ! Jeremy ⼩阿哥 Silas shell0108 爱读书的⼆丙⼦ 安之若素 ⻢它不是驼 ⻘ Mi Manchi 燕留痕 贰柒伍 溯⻛ !"#$%&' 正⽂ [C++] C++ 基础
Page
3
关键字与运算符 指针与引⽤ 1. 指针存放某个对象的地址,其本身就是变量(命了名的对象),本身就有地址,所以可以有指向指针的指针; 可变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变 2. 引⽤就是变量的别名,从⼀⽽终,不可变,必须初始化 3. 不存在指向空值的引⽤,但是存在指向空值的指针 define 和 typedef 的区别 define: 1. 只是简单的字符串替换,没有类型检查 2. 是在编译的预处理阶段起作⽤ 3. 可以⽤来防⽌头⽂件重复引⽤ 4. 不分配内存,给出的是⽴即数,有多少次使⽤就进⾏多少次替换 typedef: 1. 有对应的数据类型,是要进⾏判断的 2. 是在编译、运⾏的时候起作⽤ 3. 在静态存储区中分配空间,在程序运⾏过程中内存中只有⼀个拷⻉ define 和 inline 的区别 1、define: 定义预编译时处理的宏,只是简单的字符串替换,⽆类型检查,不安全。 2、inline: inline是先将内联函数编译完成⽣成了函数体直接插⼊被调⽤的地⽅,减少了压栈,跳转和返回的操作。没有普通 函数调⽤时的额外开销; 内联函数是⼀种特殊的函数,会进⾏类型检查; 对编译器的⼀种请求,编译器有可能拒绝这种请求; C++中inline编译限制: 1. 不能存在任何形式的循环语句 2. 不能存在过多的条件判断语句 3. 函数体不能过于庞⼤ 4. 内联函数声明必须在调⽤语句之前 override 和 overload 1、override是重写(覆盖)了⼀个⽅法 以实现不同的功能,⼀般是⽤于⼦类在继承⽗类时,重写⽗类⽅法。 规则: 1. 重写⽅法的参数列表,返回值,所抛出的异常与被重写⽅法⼀致
Page
4
2. 被重写的⽅法不能为private 3. 静态⽅法不能被重写为⾮静态的⽅法 4. 重写⽅法的访问修饰符⼀定要⼤于被重写⽅法的访问修饰符(public>protected>default>private) 2、overload是重载,这些⽅法的名称相同⽽参数形式不同 ⼀个⽅法有不同的版本,存在于⼀个类中。 规则: 1. 不能通过访问权限、返回类型、抛出的异常进⾏重载 2. 不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不⼀样) 3. ⽅法的异常类型和数⽬不会对重载造成影响 使⽤多态是为了避免在⽗类⾥⼤量重载引起代码臃肿且难于维护。 重写与重载的本质区别是,加⼊了override的修饰符的⽅法,此⽅法始终只有⼀个被你使⽤的⽅法。 new 和 malloc 1、new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。 2、使⽤new操作符申请内存分配时⽆须指定内存块的⼤⼩,⽽malloc则需要显式地指出所需内存的尺⼨。 3、opeartor new /operator delete可以被重载,⽽malloc/free并不允许重载。 4、new/delete会调⽤对象的构造函数/析构函数以完成对象的构造/析构。⽽malloc则不会 5、malloc与free是C++/C语⾔的标准库函数,new/delete是C++的运算符 6、new操作符从⾃由存储区上为对象动态分配内存空间,⽽malloc函数从堆上动态分配内存。 表格
Page
5
constexpr 和 const const 表示“只读”的语义,constexpr 表示“常量”的语义 constexpr 只能定义编译期常量,⽽ const 可以定义编译期常量,也可以定义运⾏期常量。 你将⼀个成员函数标记为constexpr,则顺带也将它标记为了const。如果你将⼀个变量标记为constexpr,则同样 它是const的。但相反并不成⽴,⼀个const的变量或函数,并不是constexpr的。 constexpr变量 复杂系统中很难分辨⼀个初始值是不是常量表达式,可以将变量声明为constexpr类型,由编译器来验证变量的值 是否是⼀个常量表达式。 必须使⽤常量初始化: 如果constexpr声明中定义了⼀个指针,constexpr仅对指针有效,和所指对象⽆关。 constexpr int n = 20; constexpr int m = n + 1; static constexpr int MOD = 1000000007;
Page
6
constexpr函数 constexpr函数是指能⽤于常量表达式的函数。 函数的返回类型和所有形参类型都是字⾯值类型,函数体有且只有⼀条return语句。 为了可以在编译过程展开,constexpr函数被隐式转换成了内联函数。 constexpr和内联函数可以在程序中多次定义,⼀般定义在头⽂件。 constexpr 构造函数 构造函数不能说const,但字⾯值常量类的构造函数可以是constexpr。 constexpr构造函数必须有⼀个空的函数体,即所有成员变量的初始化都放到初始化列表中。对象调⽤的成员函数 必须使⽤ constexpr 修饰 const 指针常量: const int* d = new int(2); 常量指针: int *const e = new int(2); 区别⽅法: 左定值,右定向:指的是const在*的左还是右边 拓展: 顶层const:指针本身是常量; 底层const:指针所指的对象是常量; 若要修改const修饰的变量的值,需要加上关键字volatile; 若想要修改const成员函数中某些与类状态⽆关的数据成员,可以使⽤mutable关键字来修饰这个数据成员; 『const和static的区别』 constexpr int *p = nullptr; //常量指针 顶层const const int *q = nullptr; //指向常量的指针, 底层const int *const q = nullptr; //顶层const constexpr int new() {return 42;}
Page
7
constexpr的好处: 1. 为⼀些不能修改数据提供保障,写成变量则就有被意外修改的⻛险。 2. 有些场景,编译器可以在编译期对constexpr的代码进⾏优化,提⾼效率。 3. 相⽐宏来说,没有额外的开销,但更安全可靠。 volatile 定义: [与const绝对对⽴的,是类型修饰符]影响编译器编译的结果,⽤该关键字声明的变量表示该变量随时可能发⽣变 化,与该变量有关的运算,不要进⾏编译优化;会从内存中重新装载内容,⽽不是直接从寄存器拷⻉内容。 作⽤: 指令关键字,确保本条指令不会因编译器的优化⽽省略,且要求每次直接读值,保证对特殊地址的稳定访问 使⽤场合: 在中断服务程序和cpu相关寄存器的定义 举例说明: 空循环:
Page
8
extern 定义:声明外部变量【在函数或者⽂件外部定义的全局变量】 static 作⽤:实现多个对象之间的数据共享 + 隐藏,并且使⽤静态成员还不会破坏隐藏原则;默认初始化为0 前置++与后置++ 为了区分前后置,重载函数是以参数类型来区分,在调⽤的时候,编译器默默给int指定为⼀个0 1、为什么后置返回对象,⽽不是引⽤ 因为后置为了返回旧值创建了⼀个临时对象,在函数结束的时候这个对象就会被销毁,如果返回引⽤,那么我请问 你?你的对象对象都被销毁了,你引⽤啥呢? 2、为什么后置前⾯也要加const 其实也可以不加,但是为了防⽌你使⽤i++++,连续两次的调⽤后置++重载符,为什么呢? 原因: 它与内置类型⾏为不⼀致;你⽆法活得你所期望的结果,因为第⼀次返回的是旧值,⽽不是原对象,你调⽤两次后 置++,结果只累加了⼀次,所以我们必须⼿动禁⽌其合法化,就要在前⾯加上const。 3、处理⽤户的⾃定义类型 最好使⽤前置++,因为他不会创建临时对象,进⽽不会带来构造和析构⽽造成的格外开销。 std::atomic 问题:a++ 和 int a = b 在C++中是否是线程安全的? 答案:不是 例1: a++:从C/C++语法的级别来看,这是⼀条语句,应该是原⼦的;但从编译器得到的汇编指令来看,其实不是原⼦ 的。 for(volatile int i=0; i<100000; i++); // 它会执⾏,不会被优化掉 self &operator++() { node = (linktype)((node).next); return *this; } const self operator++(int) { self tmp = *this; ++*this; return tmp; }
Page
9
其⼀般对应三条指令,⾸先将变量a对应的内存值搬运到某个寄存器(如eax)中,然后将该寄存器中的值⾃增1, 再将该寄存器中的值搬运回a代表的内存中 现在假设i的值是0,有两个线程,每个线程对变量a的值都递增1,预想⼀下,其结果应该是2,可实际运⾏结构可 能是1!是不是很奇怪? 分析如下: 我们预想的结果是线程1和线程2的三条指令各⾃执⾏,最终a的值变为2,但是由于操作系统线程调度的不确定性, 线程1执⾏完指令(1)和(2)后,eax寄存器中的值变为1,此时操作系统切换到线程2执⾏,执⾏指令(3)(4)(5),此时 eax的值变为1;接着操作系统切回线程1继续执⾏,执⾏指令(6),得到a的最终结果1。 例2: int a = b; 从C/C++语法的级别来看,这是条语句应该是原⼦的;但从编译器得到的汇编指令来看,由于现在计 算机CPU架构体系的限制,数据不能直接从内存某处搬运到内存另外⼀处,必须借助寄存器中转,因此这条语句⼀ 般对应两条计算机指令,即将变量b的值搬运到某个寄存器(如eax)中,再从该寄存器搬运到变量a的内存地址 中: 既然是两条指令,那么多个线程在执⾏这两条指令时,某个线程可能会在第⼀条指令执⾏完毕后被剥夺CPU时间 ⽚,切换到另⼀个线程⽽出现不确定的情况。 解决办法: C++11新标准发布后改变了这种困境,新标准提供了对整形变量原⼦操作的相关库,即std::atomic, 这是⼀个模板类型: 我们可以传⼊具体的整型类型对模板进⾏实例化,实际上stl库也提供了这些实例化的模板类型 mov eax, dword ptr [a] # (1) inc eax # (2) mov dword ptr [a], eax # (3) int a = 0; // 线程1(执⾏过程对应上⽂汇编指令(1)(2)(3)) void thread_func1() { a++; } // 线程2(执⾏过程对应上⽂汇编指令(4)(5)(6)) void thread_func2() { a++; } mov eax, dword ptr [b] mov dword prt [a], eax template<class T> struct atomic:
Page
10
C++ 三⼤特性 访问权限 C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、 受保护的、私有的,被称为成员访问限定符。 在类的内部(定义类的代码内部),⽆论成员被声明为 public、protected 还是 private,都是可以互相访问的, 没有访问权限的限制。 在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访 问 private、protected 属性的成员。 ⽆论共有继承、私有和保护继承,私有成员不能被“派⽣类”访问,基类中的共有和保护成员能被“派⽣类”访问。 对于共有继承,只有基类中的共有成员能被“派⽣类对象”访问,保护和私有成员不能被“派⽣类对象”访问。对于私 有和保护继承,基类中的所有成员不能被“派⽣类对象”访问。 1. 继承 定义: 让某种类型对象获得另⼀个类型对象的属性和⽅法 功能: 它可以使⽤现有类的所有功能,并在⽆需重新编写原来的类的情况下对这些功能进⾏扩展 常⻅的继承有三种⽅式: 1、实现继承: 指使⽤基类的属性和⽅法⽽⽆需额外编码的能⼒ 2、接⼝继承: 指仅使⽤属性和⽅法的名称、但是⼦类必须提供实现的能⼒ 3、可视继承: 指⼦窗体(类)使⽤基窗体(类)的外观和实现代码的能⼒ 例如: 将⼈定义为⼀个抽象类,拥有姓名、性别、年龄等公共属性,吃饭、睡觉等公共⽅法,在定义⼀个具体的⼈时,就 可以继承这个抽象类,既保留了公共属性和⽅法,也可以在此基础上扩展跳舞、唱歌等特有⽅法。 // 初始化1 std::atomic<int> value; value = 99; // 初始化2 // 下⾯代码在Linux平台上⽆法编译通过(指在gcc编译器) std::atomic<int> value = 99; // 出错的原因是这⾏代码调⽤的是std::atomic的拷⻉构造函数 // ⽽根据C++11语⾔规范,std::atomic的拷⻉构造函数使⽤=delete标记禁⽌编译器⾃动⽣成 // g++在这条规则上遵循了C++11语⾔规范。
Page
11
2. 封装 定义: 数据和代码捆绑在⼀起,避免外界⼲扰和不确定性访问; 功能: 把客观事物封装成抽象的类,并且类可以把⾃⼰的数据和⽅法只让可信的类或者对象操作,对不可信的进⾏信息隐 藏,例如:将公共的数据或⽅法使⽤public修饰,⽽不希望被访问的数据或⽅法采⽤private修饰。 3. 多态 定义: 同⼀事物表现出不同事物的能⼒,即向不同对象发送同⼀消息,不同的对象在接收时会产⽣不同的⾏为(重载实现 编译时多态,虚函数实现运⾏时多态) 功能: 多态性是允许你将⽗对象设置成为和⼀个或更多的他的⼦对象相等的技术,赋值之后,⽗对象就可以根据当前赋值 给它的⼦对象的特性以不同的⽅式运作; 简单⼀句话:允许将⼦类类型的指针赋值给⽗类类型的指针。 实现多态有两种⽅式 1. 覆盖(override): 是指⼦类重新定义⽗类的虚函数的做法 2. 重载(overload): 是指允许存在多个同名函数,⽽这些函数的参数表不同(或许参数个数不同,或许参数 类型不同,或许两者都不同) 例如: 基类是⼀个抽象对象——⼈,那学⽣、运动员也是⼈,⽽使⽤这个抽象对象既可以表示学⽣、也可以表示运动员。 虚函数 当基类希望派⽣类定义适合⾃⼰的版本,就将这些函数声明成虚函数(virtual) 虚函数依赖虚函数表⼯作,表来保存虚函数地址,当我们⽤基类指针指向派⽣类时,虚表指针指向派⽣类的虚函数 表 这个机制可以保证派⽣类中的虚函数被调⽤到 1、虚函数是动态绑定的 也就是说,使⽤虚函数的指针和引⽤能够正确找到实际类的对应函数,⽽不是执⾏定义类的函数,这是虚函数的基 本功能。 2、多态(不同继承关系的类对象,调⽤同⼀函数产⽣不同⾏为) 1. 调⽤函数的对象必须是指针或者引⽤ 2. 被调⽤的函数必须是虚函数(virtual),且完成了虚函数的重写(派⽣类中有⼀个跟基类的完全相同虚函数) 3、动态绑定绑定的是动态类型 所对应的函数或属性依赖于对象的动态类型,发⽣在运⾏期。
Page
12
4、构造函数不能是虚函数 ⽽且,在构造函数中调⽤虚函数,实际执⾏的是⽗类的对应函数,因为⾃⼰还没有构造好, 多态是被disable的。 5、虚函数的⼯作⽅式 依赖虚函数表⼯作的,表来保存虚函数地址,当我们⽤基类指针指向派⽣类时,虚表指针vptr指向派⽣类的虚函数 表。 这个机制可以保证派⽣类中的虚函数被调⽤到。 6、析构函数可以是虚函数,⽽且,在⼀个复杂类结构中,这往往是必须的。 7、将⼀个函数定义为纯虚函数。 实际上是将这个类定义为抽象类,不能实例化对象;纯虚函数通常没有定义体,但也完全可以拥有。 8、inline, static, constructor三种函数都不能带有virtual关键字。 (1)inline是在编译时展开,必须要有实体。 内联函数是指在编译期间⽤被调⽤函数体本身来代替函数的调⽤指令,但虚函数的多态特性需要在运⾏时根据对象 类型才知道调⽤哪个虚函数,所以没法在编译时进⾏内联函数展开。 (2)static属于class⾃⼰的类相关,必须有实体; static成员没有this指针。virtual函数⼀定要通过对象来调⽤,有隐藏的this指针,实例相关。 9、析构函数可以是纯虚的 但纯虚析构函数必须有定义体,因为析构函数的调⽤是在⼦类中隐含的。 10、派⽣类的override虚函数定义必须和⽗类完全⼀致。 除了⼀个特例,如果⽗类中返回值是⼀个指针或引⽤,⼦类override时可以返回这个指针(或引⽤)的派⽣。 为什么需要虚继承 1、为了解决多继承时的命名冲突和冗余数据问题 C++ 提出了虚继承,使得在派⽣类中只保留⼀份间接基类的成员。其中多继承(Multiple Inheritance)是指从多 个直接基类中产⽣派⽣类的能⼒,多继承的派⽣类继承了所有⽗类的成员。 2、虚继承的⽬的是让某个类做出声明,承诺愿意共享它的基类 其中,这个被共享的基类就称为虚基类(Virtual Base Class),其中A 就是⼀个虚基类。在这种机制下,不论虚基 类在继承体系中出现了多少次,在派⽣类中都只包含⼀份虚基类的成员。 类 A 有⼀个成员变量 a,不使⽤虚继承,那么在类 D 中直接访问 a 就会产⽣歧义。 编译器不知道它究竟来⾃ A -->B-->D 这条路径,还是来⾃ A-->C-->D 这条路径。
Page
13
C++标准库中的 iostream 类就是⼀个虚继承的实际应⽤案例。 iostream 从 istream 和 ostream 直接继承⽽来,⽽ istream 和 ostream ⼜都继承⾃⼀个共同的名为 baseios 的 类,是典型的菱形继承。 此时 istream 和 ostream 必须采⽤虚继承,否则将导致 iostream 类中保留两份 baseios 类的成员。 使⽤多继承经常出现⼆义性,必须⼗分⼩⼼; ⼀般只有在⽐较简单和不易出现⼆义性或者实在必要情况下才使⽤多继承,能⽤单⼀继承解决问题就不要⽤多继 承。 空类 1、为何空类的⼤⼩不是0 为了确保两个不同对象的地址不同,必须如此。 类的实例化是在内存中分配⼀块地址,每个实例在内存中都有独⼀⽆⼆的⼆地址。 同样,空类也会实例化,所以编译器会给空类隐含的添加⼀个字节,这样空类实例化后就有独⼀⽆⼆的地址了。 所以,空类的sizeof为1,⽽不是0。
Page
14
此时,类A和类B都不是空类,其sizeof都是4,因为它们都具有虚函数表的地址。 此时,A是空类,其⼤⼩为1;B不是空类,其⼤⼩为4.因为含有指向虚基类的指针。 多重继承的空类的⼤⼩也是1。 它们的sizeof都是1. 何时共享虚函数地址表: 如果派⽣类继承的第⼀个是基类,且该基类定义了虚函数地址表,则派⽣类就共享该表⾸址占⽤的存储单元。 对于除前述情形以外的其他任何情形,派⽣类在处理完所有基类或虚基类后,根据派⽣类是否建⽴了虚函数地址 表,确定是否为该表⾸址分配存储单元。 抽象类与接⼝的实现 接⼝描述了类的⾏为和功能,⽽不需要完成类的特定实现;C++ 接⼝是使⽤抽象类来实现的 1、类中⾄少有⼀个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使⽤ "= 0" 来指定的。 2、设计抽象类(通常称为 ABC)的⽬的,是为了给其他类提供⼀个可以继承的适当的基类。抽象类不能被⽤于实 例化对象,它只能作为接⼝使⽤。 class A{ virtual void f(){} }; class B:public A{} class A{}; class B:public virtual A{}; class Father1{}; class Father2{}; class Child:Father1, Father2{}; class X{}; //sizeof(X):1 class Y : public virtual X {}; //sizeof(Y):4 class Z : public virtual X {}; //sizeof(Z):4 class A : public virtual Y {}; //sizeof(A):8 class B : public Y, public Z{}; //sizeof(B):8 class C : public virtual Y, public virtual Z {}; //sizeof(C):12 class D : public virtual C{}; //sizeof(D):16 class Shape { public: // 提供接⼝框架的纯虚函数 virtual int getArea() = 0; void setWidth(int w) { width = w;
Page
15
主函数: 智能指针 } void setHeight(int h) { height = h; } protected: int width; int height; }; // 派⽣类 class Rectangle: public Shape { public: int getArea() { return (width * height); } }; class Triangle: public Shape { public: int getArea() { return (width * height)/2; } }; Rectangle Rect; Triangle Tri; Rect.setWidth(5); Rect.setHeight(7); Rect.getArea(); //35 Tri.setWidth(5); Tri.setHeight(7); Tri.getArea(); //17
Page
16
1. shared_ptr 1、shared_ptr的实现机制是在拷⻉构造时使⽤同⼀份引⽤计数 (1)⼀个模板指针T* ptr 指向实际的对象 (2)⼀个引⽤次数 必须new出来的,不然会多个shared_ptr⾥⾯会有不同的引⽤次数⽽导致多次delete (3)重载operator*和operator-> 使得能像指针⼀样使⽤shared_ptr (4)重载copy constructor 使其引⽤次数加⼀(拷⻉构造函数) (5)重载operator=(赋值运算符) 如果原来的shared_ptr已经有对象,则让其引⽤次数减⼀并判断引⽤是否为零(是否调⽤delete),然后将新的对象 引⽤次数加⼀ (6)重载析构函数 使引⽤次数减⼀并判断引⽤是否为零; (是否调⽤delete) 2、线程安全问题 (1)同⼀个shared_ptr被多个线程“读”是安全的; (2)同⼀个shared_ptr被多个线程“写”是不安全的; 证明:在多个线程中同时对⼀个shared_ptr循环执⾏两遍swap。 shared_ptr的swap函数的作⽤就是和另外⼀个 shared_ptr交换引⽤对象和引⽤计数,是写操作。执⾏两遍swap之后, shared_ptr引⽤的对象的值应该不变) (3)共享引⽤计数的不同的shared_ptr被多个线程”写“ 是安全的。 2. unique_ptr 1、unique_ptr”唯⼀”拥有其所指对象 同⼀时刻只能有⼀个unique_ptr指向给定对象,离开作⽤域时,若其指向对象,则将其所指对象销毁(默认 delete)。 2、定义unique_ptr时 需要将其绑定到⼀个new返回的指针上。 3、unique_ptr不⽀持普通的拷⻉和赋值(因为拥有指向的对象) 但是可以拷⻉和赋值⼀个将要被销毁的unique_ptr;可以通过release或者reset将指针所有权从⼀个(⾮const) unique_ptr转移到另⼀个unique。
Page
17
3. weak_ptr 1、weak_ptr是为了配合shared_ptr⽽引⼊的⼀种智能指针 它的最⼤作⽤在于协助shared_ptr⼯作,像旁观者那样观测资源的使⽤情况,但weak_ptr没有共享资源,它的构造 不会引起指针引⽤计数的增加。 2、和shared_ptr指向相同内存 shared_ptr析构之后内存释放,在使⽤之前使⽤函数lock()检查weak_ptr是否为空指针。 C++强制类型转换 关键字:static_cast、dynamic_cast、reinterpret_cast和 const_cast 1. static_cast 没有运⾏时类型检查来保证转换的安全性 进⾏上⾏转换(把派⽣类的指针或引⽤转换成基类表示)是安全的 进⾏下⾏转换(把基类的指针或引⽤转换为派⽣类表示),由于没有动态类型检查,所以是不安全的。 使⽤: 1. ⽤于基本数据类型之间的转换,如把int转换成char。 2. 把任何类型的表达式转换成void类型。 2. dynamic_cast 在进⾏下⾏转换时,dynamic_cast具有类型检查(信息在虚函数中)的功能,⽐static_cast更安全。 转换后必须是类的指针、引⽤或者void*,基类要有虚函数,可以交叉转换。 dynamic本身只能⽤于存在虚函数的⽗⼦关系的强制类型转换;对于指针,转换失败则返回nullptr,对于引⽤,转 换失败会抛出异常。 3. reinterpret_cast 可以将整型转换为指针,也可以把指针转换为数组;可以在指针和引⽤⾥进⾏肆⽆忌惮的转换,平台移植性⽐价 差。 4. const_cast 常量指针转换为⾮常量指针,并且仍然指向原来的对象。常量引⽤被转换为⾮常量引⽤,并且仍然指向原来的对 象。去掉类型的const或volatile属性。 C++内存模型
Page
18
字符串操作函数 常⻅的字符串函数实现 1、strcpy() 把从strsrc地址开始且含有'\0'结束符的字符串复制到以strdest开始的地址空间,返回值的类型为char* 2、strlen() 计算给定字符串的⻓度。 3、strcat() 作⽤是把src所指字符串添加到dest结尾处。
Page
19
4、strcmp() ⽐较两个字符串设这两个字符串为str1,str2, 若str1 == str2,则返回零 若str1 < str2,则返回负数 若str1 > str2,则返回正数 内存泄漏 1、什么是内存泄露? 内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使⽤的内存的情况。内存泄漏并⾮指内存 在物理上的消失,⽽是应⽤程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因⽽造成了内存的浪 费。 可以使⽤Valgrind, mtrace进⾏内存泄漏检查。 2、内存泄漏的分类
Page
20
(1)堆内存泄漏 (Heap leak) 对内存指的是程序运⾏中根据需要分配通过malloc,realloc new等从堆中分配的⼀块内存,再是完成后必须通过调 ⽤对应的 free或者 delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被 使⽤,就会产⽣ Heap Leak. (2)系统资源泄露(Resource Leak) 主要指程序使⽤系统分配的资源⽐如 Bitmap,handle ,SOCKET 等没有使⽤相应的函数释放掉,导致系统资源的浪 费,严重可导致系统效能降低,系统运⾏不稳定。 (3)没有将基类的析构函数定义为虚函数 当基类指针指向⼦类对象时,如果基类的析构函数不是 virtual,那么⼦类的析构函数将不会被调⽤,⼦类的资源没 有正确是释放,因此造成内存泄露。 3、什么操作会导致内存泄露? 指针指向改变,未释放动态分配内存。 4、如何防⽌内存泄露? 将内存的分配封装在类中,构造函数分配内存,析构函数释放内存;使⽤智能指针 5、智能指针有了解哪些? 智能指针是为了解决动态分配内存导致内存泄露和多次释放同⼀内存所提出的,C11标准中放在< memory>头⽂ 件。包括:共享指针,独占指针,弱指针 6、构造函数,析构函数要设为虚函数吗,为什么? (1)析构函数 析构函数需要。当派⽣类对象中有内存需要回收时,如果析构函数不是虚函数,不会触发动态绑定,只会调⽤基类 析构函数,导致派⽣类资源⽆法释放,造成内存泄漏。 (2)构造函数 构造函数不需要,没有意义。虚函数调⽤是在部分信息下完成⼯作的机制,允许我们只知道接⼝⽽不知道对象的确 切类型。 要创建⼀个对象,你需要知道对象的完整信息。 特别是,你需要知道你想要创建的确切类型。 因此,构 造函数不应该被定义为虚函数。 测试题⽬ 1、以下为WindowsNT 32位C++程序,请计算下⾯sizeof的值 char str[] = "hello"; char* p = str; int n = 10; // 请计算 sizeof(str) = ? sizeof(p) = ? sizeof(n) = ?
Loading comments...
Reply to Comment
Edit Comment