编译过程分为四个过程:编译(编译预处理、编译、优化),汇编,链接。
编译预处理:处理以 # 开头的指令;
编译、优化:将源码 .cpp 文件翻译成 .s 汇编代码;
汇编:将汇编代码 .s 翻译成机器指令 .o 文件;
链接:汇编程序生成的目标文件,即 .o 文件,并不会立即执行,因为可能会出现:.cpp 文件中的函数引用了另一个 .cpp文件中定义的符号或者调用了某个库文件中的函数。那链接的目的就是将这些文件对应的目标文件连接成一个整体,从而生成可执行的程序 .exe文件。
链接分为两种:
静态链接:代码从其所在的静态链接库中拷贝到最终的可执行程序中,在该程序被执行时,这些代码会被装入到该进程的虚拟地址空间中。
动态链接:代码被放到动态链接库或共享对象的某个目标文件中,链接程序只是在最终的可执行程序中记录了共享对象的名字等一些信息。在程序执行时,动态链接库的全部内容会被映射到运行时相应进行的虚拟地址的空间。
二者的优缺点:
静态链接:浪费空间,每个可执行程序都会有目标文件的一个副本,这样如果目标文件进行了更新操作,就需要重新进行编译链接生成可执行程序(更新困难);优点就是执行的时候运行速度快,因为可执行程序具备了程序运行的所有内容。
动态链接:节省内存、更新方便,但是动态链接是在程序运行时,每次执行都需要链接,相比静态链接会有一定的性能损失。
C++ 内存分区:栈、堆、全局/静态存储区、常量存储区、代码区。
全局变量、局部变量、静态全局变量、静态局部变量的区别:
C++ 变量根据定义的位置的不同的生命周期,具有不同的作用域,作用域可分为 6 种:全局作用域,局部作用域,语句作用域,类作用域,命名空间作用域和文件作用域。
从作用域看:
从分配内存空间看:
如果在头文件中定义全局变量,当该头文件被多个文件 include 时,该头文件中的全局变量就会被定义多次,导致重复定义,因此不能再头文件中定义全局变量。
什么是内存对齐?内存对齐的原则?为什么要进行内存对齐,有什么优点?
内存对齐:编译器将程序中的每个“数据单元”安排在字的整数倍的地址指向的内存之中
内存对齐的原则:
进行内存对齐的原因:(主要是硬件设备方面的问题)
内存对齐的优点:
内存泄漏:由于疏忽或错误导致的程序未能释放已经不再使用的内存。
进一步解释:
char *p = (char *)malloc(10); char *p1 = (char *)malloc(10); p = np; 123
开始时,指针 p 和 p1 分别指向一块内存空间,但指针 p 被重新赋值,导致 p 初始时指向的那块内存空间无法找到,从而发生了内存泄漏。
防止内存泄漏的方法:
内存泄漏检测工具的实现原理:
内存检测工具有很多,这里重点介绍下 valgrind 。
智能指针是为了解决动态内存分配时带来的内存泄漏以及多次释放同一块内存空间而提出的。C++11 中封装在了 < memory > 头文件中。
C++11 中智能指针包括以下三种:
共享指针(shared_ptr):资源可以被多个指针共享,使用计数机制表明资源被几个指针共享。通过 use_count() 查看资源的所有者的个数,可以通过 unique_ptr、weak_ptr 来构造,调用 release() 释放资源的所有权,计数减一,当计数减为 0 时,会自动释放内存空间,从而避免了内存泄漏。
独占指针(unique_ptr):独享所有权的智能指针,资源只能被一个指针占有,该指针不能拷贝构造和赋值。但可以进行移动构造和移动赋值构造(调用move() 函数),即一个 unique_ptr 对象赋值给另一个 unique_ptr 对象,可以通过该方法进行赋值。
弱指针(weak_ptr):指向 shared_ptr 指向的对象,能够解决由shared_ptr带来的循环引用问题。
智能指针的实现原理: 计数原理。
#include <iostream> #include <memory> template <typename T> class SmartPtr { private : T _ptr; size_t _count; public: SmartPtr(T *ptr = nullptr) : _ptr(ptr) { if (_ptr) { _count = new size_t(1); } else { _count = new size_t(0); } } <span class="token operator">~</span><span class="token function">SmartPtr</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token operator">-></span>_count<span class="token punctuation">)</span><span class="token operator">--</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token operator">-></span>_count <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">delete</span> <span class="token keyword">this</span><span class="token operator">-></span>_ptr<span class="token punctuation">;</span> <span class="token keyword">delete</span> <span class="token keyword">this</span><span class="token operator">-></span>_count<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token function">SmartPtr</span><span class="token punctuation">(</span><span class="token keyword">const</span> SmartPtr <span class="token operator">&</span>ptr<span class="token punctuation">)</span> <span class="token comment">// 拷贝构造:计数 +1</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token operator">!=</span> <span class="token operator">&</span>ptr<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">this</span><span class="token operator">-></span>_ptr <span class="token operator">=</span> ptr<span class="token punctuation">.</span>_ptr<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token operator">-></span>_count <span class="token operator">=</span> ptr<span class="token punctuation">.</span>_count<span class="token punctuation">;</span> <span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token operator">-></span>_count<span class="token punctuation">)</span><span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> SmartPtr <span class="token operator">&</span><span class="token keyword">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword">const</span> SmartPtr <span class="token operator">&</span>ptr<span class="token punctuation">)</span> <span class="token comment">// 赋值运算符重载 </span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token operator">-></span>_ptr <span class="token operator">==</span> ptr<span class="token punctuation">.</span>_ptr<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">return</span> <span class="token operator">*</span><span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token operator">-></span>_ptr<span class="token punctuation">)</span> <span class="token comment">// 将当前的 ptr 指向的原来的空间的计数 -1</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token operator">-></span>_count<span class="token punctuation">)</span><span class="token operator">--</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token operator">-></span>_count <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">delete</span> <span class="token keyword">this</span><span class="token operator">-></span>_ptr<span class="token punctuation">;</span> <span class="token keyword">delete</span> <span class="token keyword">this</span><span class="token operator">-></span>_count<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token operator">-></span>_ptr <span class="token operator">=</span> ptr<span class="token punctuation">.</span>_ptr<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token operator">-></span>_count <span class="token operator">=</span> ptr<span class="token punctuation">.</span>_count<span class="token punctuation">;</span> <span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token operator">-></span>_count<span class="token punctuation">)</span><span class="token operator">++</span><span class="token punctuation">;</span> <span class="token comment">// 此时 ptr 指向了新赋值的空间,该空间的计数 +1</span> <span class="token keyword">return</span> <span class="token operator">*</span><span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> T <span class="token operator">&</span><span class="token keyword">operator</span><span class="token operator">*</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token function">assert</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token operator">-></span>_ptr <span class="token operator">==</span> <span class="token keyword">nullptr</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token operator">*</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token operator">-></span>_ptr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> T <span class="token operator">*</span><span class="token keyword">operator</span><span class="token operator">-></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token function">assert</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token operator">-></span>_ptr <span class="token operator">==</span> <span class="token keyword">nullptr</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token operator">-></span>_ptr<span class="token punctuation">;</span> <span class="token punctuation">}</span> size_t <span class="token function">use_count</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">return</span> <span class="token operator">*</span><span class="token keyword">this</span><span class="token operator">-></span>count<span class="token punctuation">;</span> <span class="token punctuation">}</span> }; 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
借助 std::move() 可以实现将一个 unique_ptr 对象赋值给另一个 unique_ptr 对象,其目的是实现所有权的转移。
// A 作为一个类 std::unique_ptr<A> ptr1(new A()); std::unique_ptr<A> ptr2 = std::move(ptr1); 123
智能指针可能出现的问题:循环引用
在如下例子中定义了两个类 Parent、Child,在两个类中分别定义另一个类的对象的共享指针,由于在程序结束后,两个指针相互指向对方的内存空间,导致内存无法释放。
#include <iostream> #include <memory> using namespace std; class Child; class Parent; class Parent { private: shared_ptr<Child> ChildPtr; public: void setChild(shared_ptr<Child> child) { this->ChildPtr = child; } <span class="token keyword">void</span> <span class="token function">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token operator">-></span>ChildPtr<span class="token punctuation">.</span><span class="token function">use_count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator">~</span><span class="token function">Parent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">}</span> }; class Child { private: shared_ptr<Parent> ParentPtr; public: void setPartent(shared_ptr<Parent> parent) { this->ParentPtr = parent; } void doSomething() { if (this->ParentPtr.use_count()) { <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator">~</span><span class="token function">Child</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">}</span> }; int main() { weak_ptr<Parent> wpp; weak_ptr<Child> wpc; { shared_ptr<Parent> p(new Parent); shared_ptr<Child> c(new Child); p->setChild(c); c->setPartent(p); wpp = p; wpc = c; cout << p.use_count() << endl; // 2 cout << c.use_count() << endl; // 2 } cout << wpp.use_count() << endl; // 1 cout << wpc.use_count() << endl; // 1 return 0; } 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
循环引用的解决方法: weak_ptr
循环引用:该被调用的析构函数没有被调用,从而出现了内存泄漏。
#include <iostream> #include <memory> using namespace std; class Child; class Parent; class Parent { private: //shared_ptr<Child> ChildPtr; weak_ptr<Child> ChildPtr; public: void setChild(shared_ptr<Child> child) { this->ChildPtr = child; } <span class="token keyword">void</span> <span class="token function">doSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">//new shared_ptr</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token operator">-></span>ChildPtr<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator">~</span><span class="token function">Parent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">}</span> }; class Child { private: shared_ptr<Parent> ParentPtr; public: void setPartent(shared_ptr<Parent> parent) { this->ParentPtr = parent; } void doSomething() { if (this->ParentPtr.use_count()) { <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator">~</span><span class="token function">Child</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token punctuation">}</span> }; int main() { weak_ptr<Parent> wpp; weak_ptr<Child> wpc; { shared_ptr<Parent> p(new Parent); shared_ptr<Child> c(new Child); p->setChild(c); c->setPartent(p); wpp = p; wpc = c; cout << p.use_count() << endl; // 2 cout << c.use_count() << endl; // 1 } cout << wpp.use_count() << endl; // 0 cout << wpc.use_count() << endl; // 0 return 0; } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
1. auto 类型推导
auto 关键字:自动类型推导,编译器会在 编译期间 通过初始值推导出变量的类型,通过 auto 定义的变量必须有初始值。
auto 关键字基本的使用语法如下:
2. decltype 类型推导
decltype 关键字:decltype 是“declare type”的缩写,译为“声明类型”。和 auto 的功能一样,都用来在编译时期进行自动类型推导。如果希望从表达式中推断出要定义的变量的类型,但是不想用该表达式的值初始化变量,这时就不能再用 auto。decltype 作用是选择并返回操作数的数据类型。
区别:
auto var = val1 + val2; decltype(val1 + val2) var1 = 0; 12
3. lambda 表达式
lambda 表达式,又被称为 lambda 函数或者 lambda 匿名函数。
lambda匿名函数的定义:
[capture list] (parameter list) -> return type { function body; }; 1234
其中:
#include <iostream> #include <algorithm> using namespace std; int main() { int arr[4] = {4, 2, 3, 1}; //对 a 数组中的元素进行升序排序 sort(arr, arr+4, [=](int x, int y) -> bool{ return x < y; } ); for(int n : arr){ cout << n << " "; } return 0; } 123456789101112131415
4. 范围 for 语句
for (declaration : expression){ statement } 123
参数的含义:
5. 右值引用
右值引用:绑定到右值的引用,用 && 来获得右值引用,右值引用只能绑定到要销毁的对象。为了和右值引用区分开,常规的引用称为左值引用。
#include <iostream> #include <vector> using namespace std; int main() { int var = 42; int &l_var = var; int &&r_var = var; // error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int' 错误:不能将右值引用绑定到左值上 <span class="token keyword">int</span> <span class="token operator">&&</span>r_var2 <span class="token operator">=</span> var <span class="token operator">+</span> <span class="token number">40</span><span class="token punctuation">;</span> <span class="token comment">// 正确:将 r_var2 绑定到求和结果上</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> } 12345678910111213
6. 标准库 move() 函数
move() 函数:通过该函数可获得绑定到左值上的右值引用,该函数包括在 utility 头文件中。该知识点会在后续的章节中做详细的说明。
7. 智能指针
相关知识已在第一章中进行了详细的说明,这里不再重复。
8. delete 函数和 default 函数
#include <iostream> using namespace std; class A { public: A() = default; // 表示使用默认的构造函数 ~A() = default; // 表示使用默认的析构函数 A(const A &) = delete; // 表示类的对象禁止拷贝构造 A &operator=(const A &) = delete; // 表示类的对象禁止拷贝赋值 }; int main() { A ex1; A ex2 = ex1; // error: use of deleted function 'A::A(const A&)' A ex3; ex3 = ex1; // error: use of deleted function 'A& A::operator=(const A&)' return 0; } 12345678910111213141516171819
首先说一下面向对象和面向过程:
区别和联系:
区别:
面向对象:对象是指具体的某一个事物,这些事物的抽象就是类,类中包含数据(成员变量)和动作(成员方法)。
面向对象的三大特性:
class A { public: void fun(int tmp); void fun(float tmp); // 重载 参数类型不同(相对于上一个函数) void fun(int tmp, float tmp1); // 重载 参数个数不同(相对于上一个函数) void fun(float tmp, int tmp1); // 重载 参数顺序不同(相对于上一个函数) int fun(int tmp); // error: 'int A::fun(int)' cannot be overloaded 错误:注意重载不关心函数返回类型 }; 123456789
#include <iostream> using namespace std; class Base { public: void fun(int tmp, float tmp1) { cout << "Base::fun(int tmp, float tmp1)" << endl; } }; class Derive : public Base { public: void fun(int tmp) { cout << "Derive::fun(int tmp)" << endl; } // 隐藏基类中的同名函数 }; int main() { Derive ex; ex.fun(1); // Derive::fun(int tmp) ex.fun(1, 0.01); // error: candidate expects 1 argument, 2 provided return 0; } 12345678910111213141516171819202122
说明:上述代码中 ex.fun(1, 0.01); 出现错误,说明派生类中将基类的同名函数隐藏了。若是想调用基类中的同名函数,可以加上类型名指明 ex.Base::fun(1, 0.01);,这样就可以调用基类中的同名函数。
#include <iostream> using namespace std; class Base { public: virtual void fun(int tmp) { cout << "Base::fun(int tmp) : " << tmp << endl; } }; class Derived : public Base { public: virtual void fun(int tmp) { cout << "Derived::fun(int tmp) : " << tmp << endl; } // 重写基类中的 fun 函数 }; int main() { Base *p = new Derived(); p->fun(3); // Derived::fun(int) : 3 return 0; } 1234567891011121314151617181920
重写和重载的区别:
隐藏和重写,重载的区别:
说明:该问题最好结合自己的项目经历进行展开解释,或举一些恰当的例子,同时对比下面向过程编程。
面向对象编程进一步说明:
面向对象编程将数据成员和成员函数封装到一个类中,并声明数据成员和成员函数的访问级别(public、private、protected),以便控制类对象对数据成员和函数的访问,对数据成员起到一定的保护作用。而且在类的对象调用成员函数时,只需知道成员函数的名、参数列表以及返回值类型即可,无需了解其函数的实现原理。当类内部的数据成员或者成员函数发生改变时,不影响类外部的代码。
多态:多态就是不同继承类的对象,对同一消息做出不同的响应,基类的指针指向或绑定到派生类的对象,使得基类指针呈现不同的表现方式。在基类的函数前加上 virtual 关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
实现方法:多态是通过虚函数实现的,虚函数的地址保存在虚函数表中,虚函数表的地址保存在含有虚函数的类的实例对象的内存空间中。
实现过程:
#include <iostream> using namespace std; class Base { public: virtual void fun() { cout << "Base::fun()" << endl; } <span class="token keyword">virtual</span> <span class="token keyword">void</span> <span class="token function">fun1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> cout <span class="token operator"><<</span> <span class="token string">"Base::fun1()"</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">virtual</span> <span class="token keyword">void</span> <span class="token function">fun2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> cout <span class="token operator"><<</span> <span class="token string">"Base::fun2()"</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span> }; class Derive : public Base { public: void fun() { cout << "Derive::fun()" << endl; } <span class="token keyword">virtual</span> <span class="token keyword">void</span> <span class="token function">D_fun1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> cout <span class="token operator"><<</span> <span class="token string">"Derive::D_fun1()"</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">virtual</span> <span class="token keyword">void</span> <span class="token function">D_fun2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> cout <span class="token operator"><<</span> <span class="token string">"Derive::D_fun2()"</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span> }; int main() { Base *p = new Derive(); p->fun(); // Derive::fun() 调用派生类中的虚函数 return 0; } 123456789101112131415161718192021222324252627
基类的虚函数表如下:
派生类的对象虚函数表如下:
简单解释:当基类的指针指向派生类的对象时,通过派生类的对象的虚表指针找到虚函数表(派生类的对象虚函数表),进而找到相应的虚函数 Derive::f() 进行调用。
strlen 源代码: size_t strlen(const char *str) { size_t length = 0; while (*str++) ++length; return length; } 1234567
#include <iostream> #include <cstring> using namespace std; int main() { char arr[10] = "hello"; cout << strlen(arr) << endl; // 5 cout << sizeof(arr) << endl; // 10 return 0; } 123456789101112
#include <iostream> #include <cstring> using namespace std; void size_of(char arr[]) { cout << sizeof(arr) << endl; // warning: 'sizeof' on array function parameter 'arr' will return size of 'char*' . cout << strlen(arr) << endl; } int main() { char arr[20] = "hello"; size_of(arr); return 0; } /* 输出结果: */ 12345678910111213141516171819202122
strlen 本身是库函数,因此在程序运行过程中,计算长度;而 sizeof 在编译时,计算长度;
sizeof 的参数可以是类型,也可以是变量;strlen 的参数必须是 char* 类型的变量。
lambda 表达式的定义形式如下:
[capture list] (parameter list) -> reurn type { function body } 1234
其中:
举例:
lambda 表达式常搭配排序算法使用。
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> arr = {3, 4, 76, 12, 54, 90, 34}; sort(arr.begin(), arr.end(), [](int a, int b) { return a > b; }); // 降序排序 for (auto a : arr) { cout << a << " "; } return 0; } /* 运行结果:90 76 54 34 12 4 3 */ 123456789101112131415161718
作用:用来声明类构造函数是显示调用的,而非隐式调用,可以阻止调用构造函数时进行隐式转换。只可用于修饰单参构造函数,因为无参构造函数和多参构造函数本身就是显示调用的,再加上 explicit 关键字也没有什么意义。
隐式转换:
#include <iostream> #include <cstring> using namespace std; class A { public: int var; A(int tmp) { var = tmp; } }; int main() { A ex = 10; // 发生了隐式转换 return 0; } 123456789101112131415161718
上述代码中,A ex = 10; 在编译时,进行了隐式转换,将 10 转换成 A 类型的对象,然后将该对象赋值给 ex,等同于如下操作:
为了避免隐式转换,可用 explicit 关键字进行声明:
#include <iostream> #include <cstring> using namespace std; class A { public: int var; explicit A(int tmp) { var = tmp; cout << var << endl; } }; int main() { A ex(100); A ex1 = 10; // error: conversion from 'int' to non-scalar type 'A' requested return 0; } 1234567891011121314151617181920
作用:
static 定义静态变量,静态函数。
#include <iostream> using namespace std; int fun(){ static int var = 1; // var 只在第一次进入这个函数的时初始化 var += 1; return var; } int main() { for(int i = 0; i < 10; ++i) cout << fun() << " "; // 2 3 4 5 6 7 8 9 10 11 return 0; } 123456789101112131415
#include<iostream> using namespace std; class A { private: int var; static int s_var; // 静态成员变量 public: void show() { cout << s_var++ << endl; } static void s_show() { cout << s_var << endl; // cout << var << endl; // error: invalid use of member 'A::a' in static member function. 静态成员函数不能调用非静态成员变量。无法使用 this.var // show(); // error: cannot call member function 'void A::show()' without object. 静态成员函数不能调用非静态成员函数。无法使用 this.show() } }; int A::s_var = 1; // 静态成员变量在类外进行初始化赋值,默认初始化为 0 int main() { <span class="token comment">// cout << A::sa << endl; // error: 'int A::sa' is private within this context</span> A ex<span class="token punctuation">;</span> ex<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">A</span><span class="token operator">::</span><span class="token function">s_show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> } 123456789101112131415161718192021222324252627282930
static 静态成员变量:
#include <iostream> using namespace std; class A { public: static int s_var; int var; void fun1(int i = s_var); // 正确,静态成员变量可以作为成员函数的参数 void fun2(int i = var); // error: invalid use of non-static data member 'A::var' }; int main() { return 0; } 123456789101112131415
#include <iostream> using namespace std; class A { public: static A s_var; // 正确,静态数据成员 A var; // error: field 'var' has incomplete type 'A' A *p; // 正确,指针 A &var1; // 正确,引用 }; int main() { return 0; } 12345678910111213141516
static 静态成员函数:
相同点:
不同点:
作用:
在类中的用法:
const 成员变量:
const 成员函数:
区别:
const 的优点:
#include <iostream> #define INTPTR1 int * typedef int * INTPTR2; using namespace std; int main() { INTPTR1 p1, p2; // p1: int *; p2: int INTPTR2 p3, p4; // p3: int *; p4: int * <span class="token keyword">int</span> var <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">const</span> INTPTR1 p5 <span class="token operator">=</span> <span class="token operator">&</span>var<span class="token punctuation">;</span> <span class="token comment">// 相当于 const int * p5; 常量指针,即不可以通过 p5 去修改 p5 指向的内容,但是 p5 可以指向其他内容。</span> <span class="token keyword">const</span> INTPTR2 p6 <span class="token operator">=</span> <span class="token operator">&</span>var<span class="token punctuation">;</span> <span class="token comment">// 相当于 int * const p6; 指针常量,不可使 p6 再指向其他内容。</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> } 1234567891011121314151617
#include <iostream> #define MAX(X, Y) ((X)>(Y)?(X):(Y)) #define MIN(X, Y) ((X)<(Y)?(X):(Y)) using namespace std; int main () { int var1 = 10, var2 = 100; cout << MAX(var1, var2) << endl; cout << MIN(var1, var2) << endl; return 0; } /* 程序运行结果: */ 1234567891011121314151617
作用:
inline 是一个关键字,可以用于定义内联函数。内联函数,像普通函数一样被调用,但是在调用时并不通过函数调用的机制而是直接在调用点处展开,这样可以大大减少由函数调用带来的开销,从而提高程序的运行效率。
使用方法:
#include <iostream> using namespace std; class A{ public: int var; A(int tmp){ var = tmp; } void fun(){ cout << var << endl; } }; int main() { return 0; } 123456789101112131415161718
#include <iostream> using namespace std; class A{ public: int var; A(int tmp){ var = tmp; } void fun(); }; inline void A::fun(){ cout << var << endl; } int main() { return 0; } 1234567891011121314151617181920
另外,可以在声明函数和定义函数的同时加上 inline;也可以只在函数声明时加 inline,而定义函数时不加 inline。只要确保在调用该函数之前把 inline 的信息告知编译器即可。
#include <iostream> #define MAX(a, b) ((a) > (b) ? (a) : (b)) using namespace std; inline int fun_max(int a, int b) { return a > b ? a : b; } int main() { int var = 1; cout << MAX(var, 5) << endl; cout << fun_max(var, 0) << endl; return 0; } /* 程序运行结果: */ 123456789101112131415161718192021222324
new 是 C++ 中的关键字,用来动态分配内存空间,实现方式如下:
int *p = new int[5]; 1
delete 的实现原理:
delete 和 delete [] 的区别:
在使用的时候 new、delete 搭配使用,malloc、free 搭配使用。
malloc 的原理:
malloc 的底层实现:
brk() 函数实现原理:向高地址的方向移动指向数据段的高地址的指针 _enddata。
mmap 内存映射原理:
1.进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域;
2.调用内核空间的系统调用函数 mmap(),实现文件物理地址和进程虚拟地址的一一映射关系;
3.进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝。
说明:union 是联合体,struct 是结构体。
区别:
#include <iostream> using namespace std; typedef union { char c[10]; char cc1; // char 1 字节,按该类型的倍数分配大小 } u11; typedef union { char c[10]; int i; // int 4 字节,按该类型的倍数分配大小 } u22; typedef union { char c[10]; double d; // double 8 字节,按该类型的倍数分配大小 } u33; typedef struct s1 { char c; // 1 字节 double d; // 1(char)+ 7(内存对齐)+ 8(double)= 16 字节 } s11; typedef struct s2 { char c; // 1 字节 char cc; // 1(char)+ 1(char)= 2 字节 double d; // 2 + 6(内存对齐)+ 8(double)= 16 字节 } s22; typedef struct s3 { char c; // 1 字节 double d; // 1(char)+ 7(内存对齐)+ 8(double)= 16 字节 char cc; // 16 + 1(char)+ 7(内存对齐)= 24 字节 } s33; int main() { cout << sizeof(u11) << endl; // 10 cout << sizeof(u22) << endl; // 12 cout << sizeof(u33) << endl; // 16 cout << sizeof(s11) << endl; // 16 cout << sizeof(s22) << endl; // 16 cout << sizeof(s33) << endl; // 24 cout <span class="token operator"><<</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> <span class="token comment">// 4</span> cout <span class="token operator"><<</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">double</span><span class="token punctuation">)</span> <span class="token operator"><<</span> endl<span class="token punctuation">;</span> <span class="token comment">// 8</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
struct A{}; class B : A{}; // private 继承 struct C : B{}; // public 继承 123
举例:
#include<iostream> using namespace std; class A{ public: void funA(){ cout << "class A" << endl; } }; struct B: A{ // 由于 B 是 struct,A 的默认继承级别为 public public: void funB(){ cout << "class B" << endl; } }; class C: B{ // 由于 C 是 class,B 的默认继承级别为 private,所以无法访问基类 B 中的 printB 函数 }; int main(){ A ex1; ex1.funA(); // class A B ex2<span class="token punctuation">;</span> ex2<span class="token punctuation">.</span><span class="token function">funA</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// class A</span> ex2<span class="token punctuation">.</span><span class="token function">funB</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// class B</span> C ex3<span class="token punctuation">;</span> ex3<span class="token punctuation">.</span><span class="token function">funB</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// error: 'B' is not an accessible base of 'C'.</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> } 12345678910111213141516171819202122232425262728293031323334
volatile 的作用:当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为 violatile,告知编译器不应对这样的对象进行优化。
volatile不具有原子性。
volatile 对编译器的影响:使用该关键字后,编译器不会对相应的对象进行优化,即不会将变量从内存缓存到寄存器中,防止多个线程有可能使用内存中的变量,有可能使用寄存器中的变量,从而导致程序错误。
使用 volatile 关键字的场景:
volatile 关键字和 const 关键字可以同时使用,某种类型可以既是 volatile 又是 const ,同时具有二者的属性。
#include <iostream> using namespace std; int fun(int tmp){ static int var = 10; var = tmp; return &var; } int main() { cout << *fun(5) << endl; return 0; } /* 运行结果: */ 123456789101112131415161718
说明:上述代码中在函数 fun 中定义了静态局部变量 var,使得离开该函数的作用域后,该变量不会销毁,返回到主函数中,该变量依然存在,从而使程序得到正确的运行结果。但是,该静态局部变量直到程序运行结束后才销毁,浪费内存空间。
当 C++ 程序 需要调用 C 语言编写的函数,C++ 使用链接指示,即 extern “C” 指出任意非 C++ 函数所用的语言。
举例:
// 可能出现在 C++ 头文件<cstring>中的链接指示 extern "C"{ int strcmp(const char*, const char*); } 1234
C 语言代码:
#include<stdio.h> void main(){ printf("%d\n", sizeof(1==1)); } /* 运行结果: */ 12345678910
C++ 代码:
#include <iostream> using namespace std; int main() { cout << sizeof(1==1) << endl; return 0; } /* */ 1234567891011
void *memcpy(void *dst, const void *src, size_t size) { char *psrc; char *pdst; <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">NULL</span> <span class="token operator">==</span> dst <span class="token operator">||</span> <span class="token constant">NULL</span> <span class="token operator">==</span> src<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">return</span> <span class="token constant">NULL</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>src <span class="token operator"><</span> dst<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>src <span class="token operator">+</span> size <span class="token operator">></span> <span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>dst<span class="token punctuation">)</span> <span class="token comment">// 出现地址重叠的情况,自后向前拷贝</span> <span class="token punctuation">{<!-- --></span> psrc <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>src <span class="token operator">+</span> size <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> pdst <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>dst <span class="token operator">+</span> size <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>size<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token operator">*</span>pdst<span class="token operator">--</span> <span class="token operator">=</span> <span class="token operator">*</span>psrc<span class="token operator">--</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span> psrc <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>src<span class="token punctuation">;</span> pdst <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>dst<span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>size<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token operator">*</span>pdst<span class="token operator">++</span> <span class="token operator">=</span> <span class="token operator">*</span>psrc<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> dst<span class="token punctuation">;</span> } 12345678910111213141516171819202122232425262728293031
strcpy 函数的缺陷:strcpy 函数不检查目的缓冲区的大小边界,而是将源字符串逐一的全部赋值给目的字符串地址起始的一块连续的内存空间,同时加上字符串终止符,会导致其他变量被覆盖。
#include <iostream> #include <cstring> using namespace std; int main() { int var = 0x11112222; char arr[10]; cout << "Address : var " << &var << endl; cout << "Address : arr " << &arr << endl; strcpy(arr, "hello world!"); cout << "var:" << hex << var << endl; // 将变量 var 以 16 进制输出 cout << "arr:" << arr << endl; return 0; } /* Address : var 0x23fe4c Address : arr 0x23fe42 var:11002164 arr:hello world! */ 12345678910111213141516171819202122
说明:从上述代码中可以看出,变量 var 的后六位被字符串 “hello world!” 的 “d!\0” 这三个字符改变,这三个字符对应的 ascii 码的十六进制为:\0(0x00),!(0x21),d(0x64)。
原因:变量 arr 只分配的 10 个内存空间,通过上述程序中的地址可以看出 arr 和 var 在内存中是连续存放的,但是在调用 strcpy 函数进行拷贝时,源字符串 “hello world!” 所占的内存空间为 13,因此在拷贝的过程中会占用 var 的内存空间,导致 var的后六位被覆盖。
auto 类型推导的原理:
编译器根据初始值来推算变量的类型,要求用 auto 定义变量时必须有初始值。编译器推断出来的 auto 类型有时和初始值类型并不完全一样,编译器会适当改变结果类型使其更符合初始化规则。
虚函数:被 virtual 关键字修饰的成员函数,就是虚函数。
#include <iostream> using namespace std; class A { public: virtual void v_fun() // 虚函数 { cout << "A::v_fun()" << endl; } }; class B : public A { public: void v_fun() { cout << "B::v_fun()" << endl; } }; int main() { A *p = new B(); p->v_fun(); // B::v_fun() return 0; } 12345678910111213141516171819202122232425
纯虚函数:
说明:
实现机制:虚函数通过虚函数表来实现。虚函数的地址保存在虚函数表中,在类的对象所在的内存空间中,保存了指向虚函数表的指针(称为“虚表指针”),通过虚表指针可以找到类对应的虚函数表。虚函数表解决了基类和派生类的继承问题和类中成员函数的覆盖问题,当用基类的指针来操作一个派生类的时候,这张虚函数表就指明了实际应该调用的函数
虚函数表相关知识点:
注:虚函数表和类绑定,虚表指针和对象绑定。即类的不同的对象的虚函数表是一样的,但是每个对象都有自己的虚表指针,来指向类的虚函数表。
实例:
无虚函数覆盖的情况:
#include <iostream> using namespace std; class Base { public: virtual void B_fun1() { cout << "Base::B_fun1()" << endl; } virtual void B_fun2() { cout << "Base::B_fun2()" << endl; } virtual void B_fun3() { cout << "Base::B_fun3()" << endl; } }; class Derive : public Base { public: virtual void D_fun1() { cout << "Derive: