五万字长文 C C++ 面试知识总结(上)

栏目: C++ · 发布时间: 5年前

内容简介:这是一篇五万字的C/C++面试知识点总结,包括答案:这是上篇,下篇今天也推送了,需要的同学记得去看看。本文花费了博主大量的时间进行收集、排版:如果你觉得文章对优点缺点

这是一篇五万字的C/C++面试知识点总结,包括答案:这是上篇,下篇今天也推送了,需要的同学记得去看看。本文花费了博主大量的时间进行收集、排版:如果你觉得文章对 你有帮助帮忙点赞 给博主一点鼓励~~

目录

  • C/C++

  • STL

  • 数据结构

  • 算法

  • Problems

  • 操作系统

  • 计算机网络

  • 网络编程

  • 数据库

  • 设计模式

  • 链接装载库

  • 海量数据处理

  • 音视频

  • 其他

  • 书籍

  • 复习刷题网站

  • 招聘时间岗位

  • 面试题目经验

C/C++

const

作用

  1. 修饰变量,说明该变量不可以被改变;

  2. 修饰指针,分为指向常量的指针和指针常量;

  3. 常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;

  4. 修饰成员函数,说明该成员函数内不能修改成员变量。

使用

// 类
class A
{
private:
    const int a;                // 常对象成员,只能在初始化列表赋值

public:
    // 构造函数
    A() { };
    A(int x) : a(x) { };        // 初始化列表

    // const可用于对重载函数的区分
    int getValue();             // 普通成员函数
    int getValue() const;       // 常成员函数,不得修改类中的任何数据成员的值
};

void function()
{
    // 对象
    A b;                        // 普通对象,可以调用全部成员函数
    const A a;                  // 常对象,只能调用常成员函数、更新常成员变量
    const A *p = &a;            // 常指针
    const A &q = a;             // 常引用

    // 指针
    char greeting[] = "Hello";
    char* p1 = greeting;                // 指针变量,指向字符数组变量
    const char* p2 = greeting;          // 指针变量,指向字符数组常量
    char* const p3 = greeting;          // 常指针,指向字符数组变量
    const char* const p4 = greeting;    // 常指针,指向字符数组常量
}

// 函数
void function1(const int Var);           // 传递过来的参数在函数内不可变
void function2(const char* Var);         // 参数指针所指内容为常量
void function3(char* const Var);         // 参数指针为常指针
void function4(const int& Var);          // 引用参数在函数内为常量

// 函数返回值
const int function5();      // 返回一个常数
const int* function6();     // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7();     // 返回一个指向变量的常指针,使用:int* const p = function7();
复制代码

static

作用

  1. 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。

  2. 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重名,可以将函数定位为 static。

  3. 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。

  4. 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。

this 指针

  1. this 指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向正在被该成员函数操作的那个对象。

  2. 当对一个对象调用成员函数时,编译程序先将对象的地址赋给 this 指针,然后调用成员函数,每次成员函数存取数据成员时,由隐含使用  this 指针。

  3. 当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向这个成员函数所在的对象的指针。

  4. this 指针被隐含地声明为:  ClassName *const this ,这意味着不能给 this 指针赋值;在 ClassName 类的 const 成员函数中, this 指针的类型为: const ClassName* const ,这说明不能对  this 指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作);

  5. this 并不是一个常规变量,而是个右值,所以不能取得  this 的地址(不能  &this )。

  6. 在以下场景中,经常需要显式引用 this 指针:

  7. 为实现对象的链式引用;

  8. 为避免对同一对象进行赋值操作;

  9. 在实现一些数据结构时,如 list

inline 内联函数

特征

  • 相当于把内联函数里面的内容写在调用内联函数处;

  • 相当于不用执行进入函数的步骤,直接执行函数体;

  • 相当于宏,却比宏多了类型检查,真正具有函数特性;

  • 不能包含循环、递归、switch 等复杂操作;

  • 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。

使用

// 声明1(加 inline,建议使用)
inline int functionName(int first, int secend,...);

// 声明2(不加 inline)
int functionName(int first, int secend,...);

// 定义
inline int functionName(int first, int secend,...) {/****/};

// 类内定义,隐式内联
class A {
    int doA() { return 0; }         // 隐式内联
}

// 类外定义,需要显式内联
class A {
    int doA();
}
inline int A::doA() { return 0; }   // 需要显式内联
复制代码

编译器对 inline 函数的处理步骤

  1. 将 inline 函数体复制到 inline 函数调用点处;

  2. 为所用 inline 函数中的局部变量分配内存空间;

  3. 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;

  4. 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)。

优缺点

优点

  1. 内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。

  2. 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。

  3. 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。

  4. 内联函数在运行时可调试,而宏定义不可以。

缺点

  1. 代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

  2. inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。

  3. 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。

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

Are "inline virtual" member functions ever actually "inlined"?

答案: www.cs.technion.ac.il/users/yechi…

  • 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。

  • 内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。

  • inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如  Base::who() ),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。

虚函数内联使用

#include <iostream>  
using namespace std;
class Base
{
public:
    inline virtual void who()
    {
        cout << "I am Base\n";
    }
    virtual ~Base() {}
};
class Derived : public Base
{
public:
    inline void who()  // 不写inline时隐式内联
    {
        cout << "I am Derived\n";
    }
};

int main()
{
    // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。 
    Base b;
    b.who();

    // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。  
    Base *ptr = new Derived();
    ptr->who();

    // 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
    delete ptr;
    ptr = nullptr;

    system("pause");
    return 0;
} 
复制代码

assert()

断言,是宏,而非函数。assert 宏的原型定义在 <assert.h> (C)、 <cassert> (C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义  NDEBUG 来关闭 assert,但是需要在源代码的开头, include <assert.h> 之前。

使用

#define NDEBUG          // 加上这行,则 assert 不可用
#include <assert.h>

assert( p != NULL );    // assert 不可用
复制代码

sizeof()

  • sizeof 对数组,得到整个数组所占空间大小。

  • sizeof 对指针,得到指针本身所占空间大小。

#pragma pack(n)

设定结构体、联合以及类成员变量以 n 字节方式对齐

使用

#pragma pack(push)  // 保存对齐状态
#pragma pack(4)     // 设定为 4 字节对齐

struct test
{
    char m1;
    double m4;
    int m3;
};

#pragma pack(pop)   // 恢复对齐状态
复制代码

位域

Bit mode: 2;    // mode 占 2 位
复制代码

类可以将其(非静态)数据成员定义为位域(bit-field),在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。

  • 位域在内存中的布局是与机器有关的

  • 位域的类型必须是整型或枚举类型,带符号类型中的位域的行为将因具体实现而定

  • 取地址运算符(&)不能作用于位域,任何指针都无法指向类的位域

volatile

volatile int i = 10; 
复制代码
  • volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以使用 volatile 告诉编译器不应对这样的对象进行优化。

  • volatile 关键字声明的变量,每次访问时都必须从内存中取出值(没有被 volatile 修饰的变量,可能由于编译器的优化,从 CPU 寄存器中取值)

  • const 可以是 volatile (如只读的状态寄存器)

  • 指针可以是 volatile

extern "C"

  • 被 extern 限定的函数或变量是 extern 类型的

  • extern "C" 修饰的变量和函数是按照 C 语言方式编译和连接的

extern "C" 的作用是让 C++ 编译器将  extern "C" 声明的代码当作 C 语言代码处理,可以避免 C++ 因符号修饰导致代码不能和 C语言 库中的符号进行链接的问题。

"C" 使用

#ifdef __cplusplus
extern "C" {
#endif

void *memset(void *, int, size_t);

#ifdef __cplusplus
}
#endif
复制代码

struct 和 typedef struct

C 中

// c
typedef struct Student {
    int age; 
} S;
复制代码

等价于

// c
struct Student { 
    int age; 
};

typedef struct Student S;
复制代码

此时 S 等价于  struct Student ,但两个标识符名称空间不相同。

另外还可以定义与 struct Student 不冲突的  void Student() {}

C++ 中

由于编译器定位符号的规则(搜索规则)改变,导致不同于C语言。

一、如果在类标识符空间定义了 struct Student {...}; ,使用  Student me; 时,编译器将搜索全局标识符表, Student 未找到,则在类标识符内搜索。

即表现为可以使用 Student 也可以使用  struct Student ,如下:

// cpp
struct Student { 
    int age; 
};

void f( Student me );       // 正确,"struct" 关键字可省略
复制代码

二、若定义了与 Student 同名函数之后,则  Student 只代表函数,不代表结构体,如下:

typedef struct Student { 
    int age; 
} S;

void Student() {}           // 正确,定义后 "Student" 只代表此函数

//void S() {}               // 错误,符号 "S" 已经被定义为一个 "struct Student" 的别名

int main() {
    Student(); 
    struct Student me;      // 或者 "S me";
    return 0;
}
复制代码

C++ 中 struct 和 class

总的来说,struct 更适合看成是一个数据结构的实现体,class 更适合看成是一个对象的实现体。

区别

  • 最本质的一个区别就是默认的访问控制
  1. 默认的继承访问权限。struct 是 public 的,class 是 private 的。

  2. struct 作为数据结构的实现体,它默认的数据访问控制是 public 的,而 class 作为对象的实现体,它默认的成员变量访问控制是 private 的。

union 联合

联合(union)是一种节省空间的特殊的类,一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。当某个成员被赋值后其他成员变为未定义状态。联合有如下特点:

  • 默认访问控制符为 public

  • 可以含有构造函数、析构函数

  • 不能含有引用类型的成员

  • 不能继承自其他类,不能作为基类

  • 不能含有虚函数

  • 匿名 union 在定义所在作用域可直接访问 union 成员

  • 匿名 union 不能包含 protected 成员或 private 成员

  • 全局匿名联合必须是静态(static)的

    使用

#include<iostream>

union UnionTest {
    UnionTest() : i(10) {};
    int i;
    double d;
};

static union {
    int i;
    double d;
};

int main() {
    UnionTest u;

    union {
        int i;
        double d;
    };

    std::cout << u.i << std::endl;  // 输出 UnionTest 联合的 10

    ::i = 20;
    std::cout << ::i << std::endl;  // 输出全局静态匿名联合的 20

    i = 30;
    std::cout << i << std::endl;    // 输出局部匿名联合的 30

    return 0;
}
复制代码

C 实现 C++ 类

C 语言实现封装、继承和多态:

dongxicheng.org/cpp/ooc/

explicit(显式)构造函数

explicit 修饰的构造函数可用来防止隐式转换

explicit 使用

class Test1
{
public:
    Test1(int n)            // 普通构造函数
    {
        num=n;
    }
private:
    int num;
};

class Test2
{
public:
    explicit Test2(int n)   // explicit(显式)构造函数
    {
        num=n;
    }
private:
    int num;
};

int main()
{
    Test1 t1=12;            // 隐式调用其构造函数,成功
    Test2 t2=12;            // 编译错误,不能隐式调用其构造函数
    Test2 t2(12);           // 显式调用成功
    return 0;
}
复制代码

friend 友元类和友元函数

  • 能访问私有成员

  • 破坏封装性

  • 友元关系不可传递

  • 友元关系的单向性

  • 友元声明的形式及数量不受限制

using

using 声明

一条 using 声明 语句一次只引入命名空间的一个成员。它使得我们可以清楚知道程序中所引用的到底是哪个名字。如:

using namespace_name::name;
复制代码

构造函数的 using 声明【C++11】

在 C++11 中,派生类能够重用其直接基类定义的构造函数。

class Derived : Base {
public:
    using Base::Base;
    /* ... */
};
复制代码

如上 using 声明,对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数。生成如下类型构造函数:

derived(parms) : base(args) { }
复制代码

using 指示

using 指示 使得某个特定命名空间中所有名字都可见,这样我们就无需再为它们添加任何前缀限定符了。如:

using namespace_name name;
复制代码

尽量少使用 using 指示 污染命名空间

一般说来,使用 using 命令比使用 using 编译命令更安全,这是由于它 只导入了制定的名称 。如果该名称与局部名称发生冲突,编译器将 发出指示 。using编译命令导入所有的名称,包括可能并不需要的名称。如果与局部名称发生冲突,则 局部名称将覆盖名称空间版本 ,而编译器 并不会发出警告 。另外,名称空间的开放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。

using 使用

尽量少使用 using 指示

using namespace std;
复制代码

应该多使用 using 声明

int x;
std::cin >> x ;
std::cout << x << std::endl;
复制代码

或者

using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
cout << x << endl;
复制代码

:: 范围解析运算符

分类

::name
class::name
namespace::name

:: 使用

int count = 0;        // 全局(::)的 count

class A {
public:
    static int count; // 类 A 的 count(A::count)
};

int main() {
    ::count = 1;      // 设置全局的 count 的值为 1

    A::count = 2;     // 设置类 A 的 count 为 2

    int count = 0;    // 局部的 count
    count = 3;        // 设置局部的 count 的值为 3

    return 0;
}
复制代码

enum 枚举类型

限定作用域的枚举类型

enum class open_modes { input, output, append };
复制代码

不限定作用域的枚举类型

enum color { red, yellow, green };
enum { floatPrec = 6, doublePrec = 10 };
复制代码

decltype

decltype 关键字用于检查实体的声明类型或表达式的类型及值分类。语法:

decltype ( expression )
复制代码

使用

// 尾置返回允许我们在参数列表之后声明返回类型
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
    // 处理序列
    return *beg;    // 返回序列中一个元素的引用
}
// 为了使用模板参数成员,必须用 typename
template <typename It>
auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
{
    // 处理序列
    return *beg;    // 返回序列中一个元素的拷贝
}
复制代码

引用

左值引用

常规引用,一般表示对象的身份。

右值引用

右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。

右值引用可实现转移语义(Move Sementics)和精确传递(Perfect Forwarding),它的主要目的有两个方面:

  • 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。

  • 能够更简洁明确地定义泛型函数。

引用折叠

  • X& &、X& &&、X&& & 可折叠成 X&

  • X&& && 可折叠成 X&&

  • 宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对 “参数” 进行的是一对一的替换。

成员初始化列表

好处

  • 更高效:少了一次调用默认构造函数的过程。

  • 有些场合必须要用初始化列表:

  1. 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面

  2. 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面

  3. 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。

initializer_list 列表初始化【C++11】

用花括号初始化器列表列表初始化一个对象,其中对应构造函数接受一个 std::initializer_list 参数.

initializer_list 使用

#include <iostream>
#include <vector>
#include <initializer_list>

template <class T>
struct S {
    std::vector<T> v;
    S(std::initializer_list<T> l) : v(l) {
         std::cout << "constructed with a " << l.size() << "-element list\n";
    }
    void append(std::initializer_list<T> l) {
        v.insert(v.end(), l.begin(), l.end());
    }
    std::pair<const T*, std::size_t> c_arr() const {
        return {&v[0], v.size()};  // 在 return 语句中复制列表初始化
                                   // 这不使用 std::initializer_list
    }
};

template <typename T>
void templated_fn(T) {}

int main()
{
    S<int> s = {1, 2, 3, 4, 5}; // 复制初始化
    s.append({6, 7, 8});      // 函数调用中的列表初始化

    std::cout << "The vector size is now " << s.c_arr().second << " ints:\n";

    for (auto n : s.v)
        std::cout << n << ' ';
    std::cout << '\n';

    std::cout << "Range-for over brace-init-list: \n";

    for (int x : {-1, -2, -3}) // auto 的规则令此带范围 for 工作
        std::cout << x << ' ';
    std::cout << '\n';

    auto al = {10, 11, 12};   // auto 的特殊规则

    std::cout << "The list bound to auto has size() = " << al.size() << '\n';

//    templated_fn({1, 2, 3}); // 编译错误!“ {1, 2, 3} ”不是表达式,
                             // 它无类型,故 T 无法推导
    templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK
    templated_fn<std::vector<int>>({1, 2, 3});           // 也 OK
}
复制代码

面向对象

面向对象程序设计(Object-oriented programming,OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。

五万字长文 C C++ 面试知识总结(上)
面向对象特征

面向对象三大特征 —— 封装、继承、多态

封装

  • 把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

  • 关键字:public, protected, friendly, private。不写默认为 friendly。

关键字 当前类 包内 子孙类 包外
public
protected ×
friendly × ×
private × × ×

继承

  • 基类(父类)——> 派生类(子类)

多态

  • 多态,即多种状态,在面向对象语言中,接口的多种不同的实现方式即为多态。

  • C++ 多态有两种:静态多态(早绑定)、动态多态(晚绑定)。静态多态是通过函数重载实现的;动态多态是通过虚函数实现的。

  • 多态是以封装和继承为基础的。

静态多态(早绑定)

函数重载

class A
{
public:
    void do(int a);
    void do(int a, int b);
};
复制代码

动态多态(晚绑定)

  • 虚函数:用 virtual 修饰成员函数,使其成为虚函数

注意:

  • 普通函数(非类成员函数)不能是虚函数

  • 静态函数(static)不能是虚函数

  • 构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针)

  • 内联函数不能是表现多态性时的虚函数,解释见:虚函数(virtual)可以是内联函数(inline)吗?:t.cn/E4WVXSP

动态多态使用

class Shape                     // 形状类
{
public:
    virtual double calcArea()
    {
        ...
    }
    virtual ~Shape();
};
class Circle : public Shape     // 圆形类
{
public:
    virtual double calcArea();
    ...
};
class Rect : public Shape       // 矩形类
{
public:
    virtual double calcArea();
    ...
};
int main()
{
    Shape * shape1 = new Circle(4.0);
    Shape * shape2 = new Rect(5.0, 6.0);
    shape1->calcArea();         // 调用圆形类里面的方法
    shape2->calcArea();         // 调用矩形类里面的方法
    delete shape1;
    shape1 = nullptr;
    delete shape2;
    shape2 = nullptr;
    return 0;
}
复制代码

虚析构函数

虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。

虚析构函数使用

class Shape
{
public:
    Shape();                    // 构造函数不能是虚函数
    virtual double calcArea();
    virtual ~Shape();           // 虚析构函数
};
class Circle : public Shape     // 圆形类
{
public:
    virtual double calcArea();
    ...
};
int main()
{
    Shape * shape1 = new Circle(4.0);
    shape1->calcArea();    
    delete shape1;  // 因为Shape有虚析构函数,所以delete释放内存时,先调用子类析构函数,再调用基类析构函数,防止内存泄漏。
    shape1 = NULL;
    return 0;
}
复制代码

纯虚函数

纯虚函数是一种特殊的虚函数,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。

virtual int A() = 0;
复制代码

虚函数、纯虚函数

CSDN . C++ 中的虚函数、纯虚函数区别和联系:t.cn/E4WVQBI

  • 类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样的话,这样编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。

  • 虚函数在子类里面也可以不重载的;但纯虚函数必须在子类去实现。

  • 虚函数的类用于 “实作继承”,继承接口的同时也继承了父类的实现。当然大家也可以完成自己的实现。纯虚函数关注的是接口的统一性,实现由子类完成。

  • 带纯虚函数的类叫抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。抽象类和大家口头常说的虚基类还是有区别的,在 C# 中用 abstract 定义抽象类,而在 C++ 中有抽象类的概念,但是没有这个关键字。抽象类被继承后,子类可以继续是抽象类,也可以是普通类,而虚基类,是含有纯虚函数的类,它如果被继承,那么子类就必须实现虚基类里面的所有纯虚函数,其子类不能是抽象类。

虚函数指针、虚函数表

  • 虚函数指针:在含有虚函数类的对象中,指向虚函数表,在运行时确定。

  • 虚函数表:在程序只读数据段( .rodata section ,见:目标文件存储结构: t.cn/E4WVBeF),存放…

虚继承

虚继承用于解决多继承条件下的菱形继承问题(浪费存储空间、存在二义性)。

底层实现原理与编译器相关,一般通过 虚基类指针虚基类表 实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

实际上,vbptr 指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

虚继承、虚函数

  • 相同之处:都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)

  • 不同之处:

  • 虚函数不占用存储空间

  • 虚函数表存储的是虚函数地址

  • 虚基类依旧存在继承类中,只占用存储空间

  • 虚基类表存储的是虚基类相对直接继承类的偏移

  • 虚继承

  • 虚函数

模板类、成员模板、虚函数

  • 模板类中可以使用虚函数

  • 一个类(无论是普通类还是类模板)的成员模板(本身是模板的成员函数)不能是虚函数

抽象类、接口类、聚合类

  • 抽象类:含有纯虚函数的类

  • 接口类:仅含有纯虚函数的抽象类

  • 聚合类:用户可以直接访问其成员,并且具有特殊的初始化语法形式。满足如下特点:

  • 所有成员都是 public

  • 没有有定于任何构造函数

  • 没有类内初始化

  • 没有基类,也没有 virtual 函数

内存分配和管理

malloc、calloc、realloc、alloca

  1. malloc:申请指定字节数的内存。申请到的内存中的初始值不确定。

  2. calloc:为指定长度的对象,分配能容纳其指定个数的内存。申请到的内存的每一位(bit)都初始化为 0。

  3. realloc:更改以前分配的内存长度(增加或减少)。当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,而新增区域内的初始值则不确定。

  4. alloca:在栈上申请内存。程序在出栈的时候,会自动释放内存。但是需要注意的是,alloca 不具可移植性, 而且在没有传统堆栈的机器上很难实现。alloca 不宜使用在必须广泛移植的程序中。C99 中支持变长数组 (VLA),可以用来替代 alloca。

malloc、free

用于分配、释放内存

malloc、free 使用

申请内存,确认是否申请成功

char *str = (char*) malloc(100);
assert(str != nullptr);
复制代码

释放内存后指针置空

free(p); 
p = nullptr;
复制代码

new、delete

  1. new / new[]:完成两件事,先底层调用 malloc 分了配内存,然后调用构造函数(创建对象)。

  2. delete/delete[]:也完成两件事,先调用析构函数(清理资源),然后底层调用 free 释放空间。

  3. new 在申请内存时会自动计算所需字节数,而 malloc 则需我们自己输入申请内存空间的字节数。

new、delete 使用

申请内存,确认是否申请成功

int main()
{
    T* t = new T();     // 先内存分配 ,再构造函数
    delete t;           // 先析构函数,再内存释放
    return 0;
}
复制代码

定位 new

定位 new(placement new)允许我们向 new 传递额外的参数。

new (palce_address) type
new (palce_address) type (initializers)
new (palce_address) type [size]
new (palce_address) type [size] { braced initializer list }
复制代码
  • palce_address 是个指针

  • initializers 提供一个(可能为空的)以逗号分隔的初始值列表

delete this 合法吗?

Is it legal (and moral) for a member function to say delete this? 答案:t.cn/E4Wfcfl

合法,但:

  1. 必须保证 this 对象是通过 new (不是  new[] 、不是 placement new、不是栈上、不是全局、不是其他对象成员)分配的

  2. 必须保证调用 delete this 的成员函数是最后一个调用 this 的成员函数

  3. 必须保证成员函数的 delete this 后面没有调用 this 了

  4. 必须保证 delete this 后没有人使用了

如何定义一个只能在堆上(栈上)生成对象的类?

如何定义一个只能在堆上(栈上)生成对象的类?

答案:t.cn/E4WfDhP

只能在堆上

方法:将析构函数设置为私有

原因:C++ 是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。

只能在栈上

方法:将 new 和 delete 重载为私有

原因:在堆上生成对象,使用 new 关键词操作,其过程分为两阶段:第一阶段,使用 new 在堆上寻找可用内存,分配给对象;第二阶段,调用构造函数生成对象。将 new 操作设置为私有,那么第一阶段就无法完成,就不能够在堆上生成对象。

智能指针

C++ 标准库(STL)中

头文件: #include <memory>

C++ 98

std::auto_ptr<std::string> ps (new std::string(str));
复制代码

受限于文章字数限制,后续部分请看【 中篇 】。

扫描下方二维码, 及时 获取更多 互联网求职面经javapython爬虫大数据 等技术,和 海量资料分享 :公众号后台回复“ csdn ”即可免费领取【csdn】和【百度文库】下载服务;公众号后台回复“ 资料 ”:即可领取 5T精品学习资料java面试考点java面经总结 ,以及 几十个 java 、大数据项目资料很全,你想找的几乎都有

五万字长文 C C++ 面试知识总结(上)

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to Probability Theory and Its Applications

An Introduction to Probability Theory and Its Applications

William Feller / Wiley / 1991-1-1 / USD 120.00

Major changes in this edition include the substitution of probabilistic arguments for combinatorial artifices, and the addition of new sections on branching processes, Markov chains, and the De Moivre......一起来看看 《An Introduction to Probability Theory and Its Applications》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具