C++八股大杂烩
C++
1 语法&特性
返回值-NRV
有一条语句,A a = f()
;其中 f()是一个函数,函数里边申请了一个 A 的对象 b,然后把对象 b 返回。在对象返回的时候,一般情况下要调用拷贝函数,把函数 f()里边的局部对象 b 拷贝到函数外部的对象 a。但
NRV:具名返回值优化(Named Return Value)简单的说就是,如果用了 NRV 优化,那就不必要调用拷贝构造函数,编译器可以这样做,把 a 的地址传递进函数 f(),然后不让 f()申请要返回的对象 b 的空间,用 a 的地址来代替 b 的地址,这样当要返回对象 b 的时候,就不必要拷贝了,因为 b 就是 a,省去了局部变量 b,省去了拷贝的过程。
指针传参
指针作为参数传递,也是传值,即拷贝一份指针。这同样意味着你在函数中对指针的操作,并不会真正改变原指针。这里要注意理解:不改变的,是指针指向哪里,但是你却可以通过这个指针,真实的改变原来指针指向的内容。
如果你想修改一个值,你可以传递指向它的指针(或引用)。因此如果你想修改指针的值(即指针的指向),那么你就要传递指向指针的指针(或指针的引用)。
但是要注意,如果你修改了指针的指向,那么原来指针的指向也会变,这就意味着,原来指针指向的那一块内存区域可能无法引用了,那就会造成内存泄漏。
类
(1) 语法
- 一些“关键字”:
inline
(外部函数变为内联)、const
(不修改)、default
()、explict
(不隐式类型转换)、virtual
(虚函数、虚继承)、static
(类成员/静态成员)、using
(可以在基类中隐藏父类的 public 继承来的东西) - 初始化列表:构造函数后以
:
开始,进行初始化。- 静态成员只能在类内定义,类外初始化,不能出现在初始化列表中(因为一个类只含有一份?)
- 基类的构造函数,也在初始化列表中调用
D(int a, int b, int c) : B(a, b), _c(c)
(如果基类没有默认构造函数,那子类必须显示调用) - 初始化顺序只看声明顺序(以及构造函数调用顺序),与初始化列表出现顺序无关。(注意区别,跟出现次序有关的是多继承时,构造函数的调用顺序)
- 子类只关心父类的初始化,不管再往上几辈的事,爷爷类自有父类负责(虚基类除外:子类还要调用虚基类的构造函数来初始化虚基类子对象,每个低层次的类都要写,但是实际上只有最派生类执行)【最派生类】:建立对象时所指定的类
- 构造与析构函数
- 构造函数调用顺序:虚基类 \(\Rarr\) 父类(按声明左到右) \(\Rarr\)客人(类类型数据,按定义前到后)\(\Rarr\) 自己。析构顺序与之相反
- 不管显示还是隐式,基类总是要调用父类的构造函数,以及析构函数的。因此可以用子类的指针来析构父类,但父类的指针不会析构子类
- 类型适应:当且仅当
- 继承不会继承基类的构造和析构函数;
- 虚继承:虚继承即让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类。目的是解决菱形继承问题\(\Rarr\)同名虚基类,在对象中只会产生一份虚基类子对象(全部父类加上子类,共享一个虚基类子对象)
(2) 内存布局
- 成员函数在代码区,不占有对象的内存。成员函数中定义的局部变量是“共享的”
- 有虚函数的,对象首块内存会存放一个指向虚表的指针。可以
(void **)(*(void**)&obj)
- 有继承的,子类中先出现的是父类中的数据成员(基类子对象),然后紧跟着是自己的数据成员
- 如果父类中有虚函数,子类也会有一个虚表,如果没有重写父类的虚函数,那虚表中项目就指向父类的虚函数,如果重写了,那就指向子类的虚函数
(3) 常见问题
- 拷贝构造函数参数:
- 深浅拷贝:
- 虚构造函数与虚析构函数:
关键字和上下文关键字
// delete
void func(int)=delete;
// override, final
void f() override final;
类模板与友元函数
// 模板类中声明友元函数共有四种:
template <typename T>
class A{
public:
friend void f1(); // 1.不需要模板参数的,非模板函数
friend void f2(A<T>& a); // 2.需要模板参数的,非模板函数
friend void f3<T>(A<T>& a); // 3.需要模板参数的,模板函数
template <typename U> // 4.需要模板参数且自带参数的模板函数
friend void f4(A<U>& a);
private:
T v;
};
不要在类内声明的是第二种,在类外定义的却是第三种了。
智能指针
Lambda 表达式
auto foo = [capture](parameters)->return_type {func_body}
-
[capture] 用来捕获表达式外部的变量:
-
[ = ] :以值(拷贝)的方式捕获所有外部变量,函数体内可以访问,但是不能修改。
-
[ & ] :以引用的方式捕获所有外部变量,函数体内可以访问并修改(需要当心无效的引用);
-
[ var ] :以值(拷贝)的方式捕获某个外部变量,函数体可以访问但不能修改。
-
[ &var ] :以引用的方式获取某个外部变量,函数体可以访问并修改
-
[ this ] :捕获 this 指针,可以访问类的成员变量和函数
-
[ =,&var ] :引用捕获变量 var,其他外部变量使用值捕获。
-
[ &,var ] :只捕获变量 var,其他外部变量使用引用捕获。
-
(parameters):即自定义函数里面的参数,只能在表达式的封闭范围内用
-
返回值可以自动推断
2 库与函数速查
⭐getline()
-
有两个 getline()函数,一个在<istream>里面,通过(输入流)成员调用的方式指出输入流,一个在<string>里面,为顶层函数,在参数列表中指出输入流
-
getline()实际读取的为(n - 1)个字符,且会把终止符从流中删除
-
读到文件尾返回eofbit
// # include<istream> // 注意是char*,同时要指出读入的size
istream& getline (char* s, streamsize n, char delim );
// # include<string>
istream& getline (istream& is, string& str, char delim);
⭐get()
-
与 getline()区别在于,读到 delim 字符后,会中止并将其保留在流中,这就会被下一个用户读到
-
只有<istream>版本
// 依次为:single character (1),c-string (2),stream buffer (3)
int get(); istream& get (char& c);
istream& get (char* s, streamsize n, char delim);
istream& get (streambuf& sb, char delim);
正则表达式
<regex>库是 C11 中新增的特性,下面给出一些使用的例子
regex
:定义一个正则表达式类,如regex rx("^[0-9]");
match_result
:
3 标准库 STL
数组
(1) vector
- vector 的大小。里面似乎存放的只是几个指针而已,start、fin 等。在使用
umap
时候发现的
string 类
栈与队列
(1) priority_queue
template<
typename T, // 类型
typename Container = std::vector<T>,
typename Compare = std::less<typename Container::value_type>
> class priority_queue;
优先队列的底层实现是二叉堆,插入时,让使 Compare 返回 true 的元素下沉(sink)。
使用时容易模糊的就在于,Compare
怎么定义(讨论的是自定义类型,而非 POD)。一般的策略有:在类型中重载 <
运算符,则即可缺省 compare,或定义仿函数
//(1) Comp缺省,自定义类型中,重载或友元重载小于 < 运算符
struct ListNode {
bool operator< (const ListNode&){}
friend operator< (const ListNode&, const ListNode&){}
}
//(2) Comp为仿函数(即定义一个类Comp,然后重载它的括号运算符)
Class Comp{
bool operator()(const T&, const T&){}}
常用的操作有push(), pop(), top(), empty()
集合 与 映射
(1) set
⭐cpp-reference ⭐[set 博客总结](c++ stl 库中的 set - 然终酒肆 - 博客园 (cnblogs.com))
template<
typename Key,
typename Compare = std::less<Key>,
typename Allocator = std::allocator<Key>
> class multiset;
注意四个版本:set
, multiset
, unordered_set
, unordered_multiset
默认有序是升序,(只需使用 rbegin()
和rend()
就能的到逆序的)
常用操作:insert、find、count、clear、empty
(2) map
默认按关键字升序,STL 中的有序均是升序,因此需要重载的只是小于号 \(<\)
链表
(1) list
template<
typename T,
typename Allocator = std::allocator<T>
> class list;
双向链表
front、back、push_back、pop_front、insert、erase、remove_if、sort、unique、reverse
(2) forward_list
单项链表