C++基础知识重点复习

注:本文适用于面试临时抱佛脚。

基础

编译与链接有四个过程:

(1)预处理

(2)编译

(3)汇编

(4)链接
在这里插入图片描述

new、delete、malloc、free关系

delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数。malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

指针和引用的区别

本质上的区别是,指针是一个新的变量,只是这个变量存储的是另一个变量的地址,我们通过访问这个地址来修改变量。

而引用只是一个别名,还是变量本身。对引用进行的任何操作就是对变量本身进行操作,因此以达到修改变量的目的。

指针:是一个变量类型;指针可以不进行初始化;指针初始化后可以改变,在写代码时需要大量的检测
引用:是一个别名;引用必须要初始化;引用初始化后不可改变,无需检测

指针

指针数组与数组指针

int *p1[10];
与int (*p1)[10];
[]优先级高于 *
第一个先构成一个数组的定义,所以是指针数组,数组里存放的是指向int类型的指针。
第二个是一个指针,指针名是p1,int修饰数组,所以是数组指针,指向的是一个数组。

struct和class的区别

首先在C++里,struct和class都是类,但是如果不特殊声明,struct、的成员均是public成员,class的成员均为private成员。struct默认public继承,class默认private继承。
在C++中,struct也可以被继承们也可以包含成员函数实现多态。当struct定义了构造函数时就不能使用大括号对其初始化。

各种关键字

Static

  1. 用于全局变量:存储于静态存储区,整个程序期间一直存在。
  2. 用于局部变量:存储于静态存储区,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变。
  3. 用于函数:静态函数。函数默认情况下是extern修饰的,但被static修饰后只能在声明它的文件中可见,不能被其他文件所用。
  4. 用于类变量:静态成员可被多个对象之间数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即八正了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用。
  5. 用于类函数:同类变量。静态函数不能直接引用非静态类成员。

C++的内存分区

栈区(stack):主要存放函数参数以及局部变量,由系统自动分配释放。
堆区(heap):由用户通过 malloc/new 手动申请,手动释放。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
全局/静态区:存放全局变量、静态变量;程序结束后由系统释放。
字符串常量区:字符串常量就放在这里,程序结束后由系统释放。
代码区:存放程序的二进制代码。

const

作用

修饰变量,说明该变量不可以被改变;
修饰指针,分为指向常量的指针(pointer to const)和自身是常量的指针(常量指针,const pointer);
修饰引用,指向常量的引用(reference to const),用于形参类型,即避免了拷贝,又避免了函数对值的修改;
修饰成员函数,说明该成员函数内不能修改成员变量。
const 的指针与引用
指针
指向常量的指针(pointer to const)
自身是常量的指针(常量指针,const pointer)
引用
指向常量的引用(reference to const)
没有 const reference,因为引用本身就是 const pointer

const char* p1= greeting;          // 指向的东西不可改变
char* const p2 = greeting;          // 指针不可改变
const char * const p3 = greeting     //指向的东西和指针都不能改变

inline

特征

相当于把内联函数里面的内容写在调用内联函数处;
相当于不用执行进入函数的步骤,直接执行函数体;
相当于宏,却比宏多了类型检查,真正具有函数特性;
编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数;
在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。

编译器对内联函数的处理

将 inline 函数体复制到 inline 函数调用点处;
为所用 inline 函数中的局部变量分配内存空间;
将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)。

优点

内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。
内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
内联函数在运行时可调试,而宏定义不可以。

缺点

代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。
是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。

虚函数(virtual)可以是内联函数(inline)吗?

虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。

C++ cast转换

函数原型

_cast <type-id>(expression)

const_cast

用于将const变量转换为非const

static_cast

用于各种隐式转换,比如非const转const
该运算符把exdivssion转换为type-id类型,但没有执行时类型检查来保证转换的安全性。它主要有例如以下几种使用方法:
①用于类层次结构中基类和子类之间指针或引用的转换。
  进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
  进行下行转换(把基类指针或引用转换成子类表示)时,因为没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这样的转换的安全性也要开发者来保证。
③把空指针转换成目标类型的空指针。
④把不论什么类型的表达式转换成void类型。

注意:static_cast 不能转换掉exdivssion的const、volitale、或者__unaligned属性。

dynamic_cast

用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转换。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。
向上转换:指的是子类向基类的转换
向下转换:指的是基类向子类的转换
dynamic_cast主要用于类层次间的上行转换和下行转换,还能够用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast 的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast 更安全。

reinterpret_cast

几乎什么都可以转,比如将int转指针。

类与对象

面向对象四大特征

抽象
封装
继承
多态

为什么析构函数要为虚函数?

防止内存泄漏。
原因:如果在派生类中申请了内存空间,在析构函数对这块内存进行释放。假设基类中采用非虚析构函数,当删除基类指针指向派生类的对象时,就不会触发动态绑定,也不会调用派生类的析构函数。那么,派生类的内存空间得不到释放就会发生内存泄漏。

类析构顺序:①派生类本身的析构函数
②对象成员析构函数
③基类析构函数

静态函数和虚函数的区别
静态函数在编译的时候已经确定了运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销。

多态:
静态多态:重载,编译时已经确定。
动态多态:虚函数机制,在运行期间动态绑定。
虚函数机制:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段中(.text)。当子类继承了父类的时候也会继承其函数表,当子类重写父类中虚函数的时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址,使用了虚函数,会增加访问内存开销,降低效率。

1、重载多态(Ad-hoc Polymorphism,编译期):函数重载、运算符重载
2、子类型多态(Subtype Polymorphism,运行期):虚函数
3、参数多态性(Parametric Polymorphism,编译期):类模板、函数模板
4、强制多态(Coercion Polymorphism,编译期/运行期):基本类型转换、自定义类型转换

容器和算法

map和set的区别
都是红黑树实现的
map是键值对,set是集合
map不允许修改键值
map可以用键值索引

vector:连续存储的容器,动态数组,在堆上分配空间,底层实现:数组。
两倍容量增长:

  1. vector增加(插入)新元素时,如果未超过当时的容量,则还有剩余空间,那么直接添加到最后(插入指定位置),然后调成迭代器。
  2. 如果没有剩余空间了,则会重新配置原有元素个数的两倍空间,然后将原空间元素通过复制的方式初始化空间,再向新空间增加元素,最后析构并释放原空间,之前的迭代器会失效。

List:动态链表,在堆上分配空间,每插入一个元素都会分配空间,每删除一个元素都会释放空间,底层实现:双向链表。
适用场景:经常插入删除大量数据

STL中迭代器如何删除元素?

  1. 对于序列容器vector,deque来说,使用erase(iterator)后,后边的每个元素的迭代器都会失效,但是后边每个元素都会往前移动一个位置,但是erase会返回下一个有效的迭代器。
  2. 对于关联容器map,set来说,使用了erase(iterator)后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素的,不会影响到下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。
  3. 对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的iterator,因此上面两种正确的方法都可以使用。

resize()和reserve()的区别:
resize()会生成元素,reserve只分配空间不会生成元素

泛型编程

函数模板

<template typename T>
int func(T a);

类模板

<template typename T>
class newClass
{
};

内存

在这里插入图片描述

1.栈 - 由编译器自动分配释放

2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收

3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束释放

4.另外还有一个专门放常量的地方。- 程序结束释放

5 程序代码区,存放2进制代码。

在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的"adgfdf"这样的字符串存放在常量区。

C++ 11

auto关键字

C++中auto关键字是一个类型说明符,通过变量的初始值或者表达式中参与运算的数据类型来推断变量的类型。编程时通常性需要把表达式付给变量,这就要求再声明变量时清除地知道表达式的类新型,C++新标准引入了auto类型说明符,让编译器去分析表达式的类型。由于,需要编译器推断变量或表达式的类型,所以,auto定义的变量必须初始化。

decltype关键字

有时我们希望从表达式的类型推断出要定义的变量类型,但是不想用该表达式的值初始化变量(初始化可以用auto)。为了满足这一需求,C++11新标准引入了decltype类型说明符,它的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
    decktype(type) argument;

nullptr

nullptr关键字:nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;而NULL一般被宏定义为0,在遇到重载时可能会出现问题。

智能指针

C++中有四个智能指针:auto_ptr, shared_ptr, weak_ptr, unique_ptr其中后三个是C++11支持,并且第一个已经被11弃用。

为什么要使用智能指针?

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄露。使用智能指针可以很大程度上避免这个问题,因为智能指针就是一个类,当超出一个类的作用域时,类会自动调用析构函数,析构函数自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

auto_ptr

C++11已弃用,不够安全。

unique_ptr

实现独占式拥有或严格拥有概念,保证同一时间只有一个智能指针可以指向该对象,它对于避免资源泄露(例如,以“new”创建对象后因为发生异常而忘记调用“delete”)特别有用。

shared_ptr

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出来资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr,unique_ptr, weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
shared_ptr是为了解决auto_ptr在独享所有权上的局限性(auto_ptr是独占的),在引用计数的机制上提供了可以共享所有权的智能指针。

成员函数

use_count():返回引用计数的个数
unique():返回是否独占所有权
swap():交换两个shared_ptr对象(即交换所拥有的对象)
reset():放弃内部对象的所有权或拥有对象的变更,会引起原有对象的引用计数的减少
get():返回内部对象(指针),由于已经重载了()方法,因此和直接使用对象是一样的,如shared_ptr sp(new int(1)); sp与sp.get()是等价的。

weak_ptr

weak_ptr是一种不控制对象声明周期的智能指针,它指向一个shared_ptr管理的对象,进行该对象的内存管理的是那个强引用的shared_ptr,weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr设计的目的是为了配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题。
如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用次数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

lambda函数(匿名函数)

[函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体}
[capture] (parameters)->return-type {body}
[]叫做捕获说明符,表示一个lambda表达式的开始。接下来是参数列表,即这个匿名的lambda函数的参数。
parameters,普通参数列表
->return-type表示返回类型,如果没有返回类型,则可以省略这部分。这涉及到c++11的另一特性,参见自动类型推导,最后就是函数体部分。

Lambda 语法分析
[函数对象参数]

标识一个 Lambda 表达式的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造
函数的。函数对象参数只能使用那些到定义 Lambda 为止时 Lambda 所在作用范围内可见的局部变量(包括 Lambda 所在类
的 this)。函数对象参数有以下形式:

空。没有任何函数对象参数。
=。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相
当于编译器自动为我们按值传递了所有局部变量)。
&。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式
(相当于是编译器自动为我们按引用传递了所有局部变量)。
this。函数体内可以使用 Lambda 所在类中的成员变量。
a。将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的,要
修改传递进来的拷贝,可以添加 mutable 修饰符。
&a。将 a 按引用进行传递。
a,&b。将 a 按值传递,b 按引用进行传递。
=,&a,&b。除 a 和 b 按引用进行传递外,其他参数都按值进行传递。
&,a,b。除 a 和 b 按值进行传递外,其他参数都按引用进行传递。

(操作符重载函数参数)

标识重载的 () 操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种
方式进行传递。

mutable 或 exception 声明

这部分可以省略。按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是值本身)。exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。

-> 返回值类型

标识函数返回值的类型,当返回值为 void,或者函数体中只有一处 return 的地方(此时编译器可以自动推断出返回值类型)
时,这部分可以省略。

{函数体}

标识函数的实现,这部分不能省略,但函数体可以为空。
注:lambda函数转载于链接

初始化列表

使用初始化列表来对类进行初始化

右值引用

基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
右值引用

atomic原子操作

用于多线程资源互斥操作
atomic原子操作
未完待续

原创文章 39 获赞 5 访问量 4925

猜你喜欢

转载自blog.csdn.net/q1072118803/article/details/105161865