常见知识点积累

发布于:2023-02-09 ⋅ 阅读:(517) ⋅ 点赞:(0)

一、C++

1.1、基础语法:

1.1.1、C++11有哪些新特新?

1.1.2、实现循环的几种方式

for语句、while语句、do while语句、goto语句

goto语句概述:

goto语句允许把控制无条件转移到同一函数内的被标记的语句。

注意:在任何编程语言中,都不建议使用goto语句。因为它使得程序的控制流难以跟踪,使程序难以理解和难以修改。任何使用goto语句的程序可以改成不需要使用goto语句的写法。

语法:

goto label;

...

label: statement;

在这里,label可以是任何除C/C++关键字以外的纯文本,它可以设置在goto语句的前面或者后面。

 流程图:

C goto 语句

举例:

#include <iostream>
using namespace std;
using std::endl;
int main(int argc, char *argv[])
{
    int i = 1;
    int sum = 0;
loop:
    if(i <= 100) {
        sum = sum + i;
        i++;
        goto loop;
    }
    cout << "sum = " << sum << endl;

    return 0;
}

1.1.3、C++强制类型转换

类继承关系图

由上图可知,派生类由两部分组成:一、自定义的属性和方法;二、从基类继承的属性和方法。当我们进行上行转换,即从派生类向基类转换时,不管是C语言的旧式转型,还是C++的新式转型都可以转换成功。但可怕的是向下转换类型,即从基类向派生类转换,当我们采用C语言的旧式转型和C++的新式转型进行转换,一旦去调用派生类的属性和方法时,就会出现意想不到的情况,这就是对继承关系和内存分配理解不清楚导致的。

旧式的强制类型转换:

type(expr);    // 函数形式的强制类型转换
(type)expr;    // C语言风格的强制类型转换

 主要用于普通数据类型、指针的强制转换,没有类型检查,转换不安全。

命名的强制类型转换(C++新式转换):

// 静态转换
static_cast<type>(expression);
// 动态转换
dynamic_cast<type>(expression);
// const转换
const_cast<type>(expression);
// 重解释转换
reinterpret_cast<type>(expression);

其中,type是转换的目标类型而expression是要转换的值。

(1)、static_cast(编译时检查)

任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。

主要用法如下:

  • 用于类层次结构中基类和派生类之间指针或引用的转换

       进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;

       进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。

  • 用于基本数据类型之间的转换,如把int转换成char。
  • 把空指针转换成目标类型的空指针。
  • 把任何类型的表达式转换成void类型。

类上行和下行转换:

// Base *bp

//下行转换是不安全的
if (Derived *dp = static_cast<Derived *>(bp)) {
  //使用dp指向的Derived对象  
} else {
  //使用bp指向的Base对象  
}

//上行转换是安全的
if (Base *bp = static_cast<Base *>(dp)) {
  //使用bp指向的Derived对象  
} else {
  //使用dp指向的Base对象  
}

基本数据类型转换:

char a = 'a';
int b = static_cast<int>(a);    //正确,将char型数据转换成int型数据

double *c = new double;
void *d = static_cast<void *>(c);    //正确,将double指针转换成void指针

c = static_cast<double *>(d);    // 正确,将void指针转换成double指针

int e = 10;
const int f = static_cast<const int>(e);    //正确,将int型数据转换成const int型数据

const int g = 20;
int *h = static_cast<int *>(&g);    //编译错误,static_cast不能转换掉g的const属性

(2)、dynamic_cast(运行时检查)

注意:dynamic_cast是运行时处理的,运行时要进行类型检查,其他三种都是编译时完成的。dynamic_cast运算符(dynamic_cast operator)的使用情况如下所示:

  • dynamic_cast<type *>(e)
  • dynamic_cast<type &>(e)
  • dynamic_cast<type &&>(e)

其中,type必须是一个类类型,并且通常情况下该类型应该含有虚函数。 在第一种形式中,e必须是一个有效的指针;第二种形式中,e必须是一个左值;第三种形式中,e不能是左值。

在上面的所有形式中,e的类型必须符合以下三个条件中的任意一个:

  • e的类型是目标type的公有派生类
  • e的类型是目标type的公有基类
  • e的类型就是目标type类型。

如果符合,则类型转换可以成功。否则,转换失败。如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0;如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个bad_cast异常

指针类型的dynamic_cast

        假设Bae类至少含有一个虚函数,Derived是Base的公有派生类。如果有一个指向Base的指针bp,则我们可以在运行时将它转换成指向Derived的指针,具体代码如下:

if (Derived *dp = dynamic_cast<Derived *>(bp))
{
    // 使用dp指向Derived对象
} else {  // bp指向一个Base对象
    // 使用bp指向的Base对象
}

        如果bp指向Derived对象,则上述的类型转换初始化dp并令其指向bp所指的Derived对象。此时,if语句内部使用Derived操作的代码是安全的。否则,类型转换的结果为0,dp为0意味着if语句的条件失败,此时else子句执行相应的Base操作。

Note:我们可以对一个空指针执行dynamic_cast,结果是所需类型的空指针。 

        值得注意的一点是,我们在条件部分定义了dp,这样做的好处是可以在一个操作中同时完成类型转换条件检查两项任务。而且,指针dp在if语句外部是不可访问的。一旦转换失败,即使后续的代码忘了做相应判断,也不会接触到这个未绑定的指针,从而确保程序是安全的。

Note:在条件部分执行dynamic_cast操作可以确保类型转换和结果检查在同一条表达式中完成。 

引用类型的dynamic_cast

        引用类型的dynamic_cast与指针类型的dynamic_cast在表示错误发生的方式上略有不同。因为不存在所谓的空引用,所以对于引用类型来说无法使用与指针类型安全相同的错误报告策略,当对引用的类型转换失败时,程序抛出一个std::bad_cast的异常,该异常定义在typeinfo标准库头文件中。具体代码如下:

void f(const Base &b)
{
    try {
        const Derived &d = dynamic_cast<const Derived&>(b);
        // 使用b引用的Derived对象
    } catch (bad_cast) {
        // 处理类型转换失败的情况
    }
}

(3)、const_cast

const_cast<type>(expression);

用于修改类型的const属性(唯一有此能力的C++类型转换操作符),不能对非指针或非引用的变量添加或移除const,type和expression的类型是一样的。

  • 常量指针被转化成非常量的指针 ,并且仍然指向原来的对象。
  • 常量引用被转化成非常量的引用,并且仍然指向原来的对象。
  • const_cast一般用于修改底层const。如const char *p形式。
// 去掉const指针的const属性
const char *p = "hello";
char *q = const_cast<char *>(p);
// 去掉const引用的const属性
const int v = 20;
const char &r1 = v;
char &r2 = const_cast<char &>(r1);

(4)、reinterpret_cast

reinterpret_cast<type>(expression)

        type必须是一个指针引用算术类型函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。

注意:reinterpret_cast强制转换过程仅仅只是比特位的拷贝,因此在使用的过程中需要特别谨慎!

IBM的C++指南,C++之父Bjarne Stroustrup的FAQ网页和MSDN的Visual C++也都指出:错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。

MSDN中也提到了,实际中可将reinterpret_cast应用到哈希函数中,如下(64位系统中需将unsigned int修改为unsigned long):

// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>

// Returns a hash code based on an address
unsigned short Hash( void *p ) {
   unsigned int val = reinterpret_cast<unsigned int>( p );
   return ( unsigned short )( val ^ (val >> 16));
}

using namespace std;
int main() {
   int a[20];
   for ( int i = 0; i < 20; i++ )
      cout << Hash( a + i ) << endl;
}

1.1.4、指针与引用 相同点与差异点

1.1.5、const限定符 作用于变量、函数、类成员方法

底层const与顶层const

(1)、int *p;

(2)、const int *p;

(3)、int * const p;

(4)、const int * const p;

1.1.6、static限定符 作用于变量、函数、类成员方法

1.1.7、lambda表达式

1.1.8、大小端

大小端字节序的来历(摘自《深入理解计算机系统 第三版》) - 撒欢 - 博客园

你真的懂CPU大小端模式吗?_字节

1.1.9 new/delete以及malloc/free的使用方法

https://blog.csdn.net/frame_c/article/details/125059262https://blog.csdn.net/frame_c/article/details/125059262

1.1.10 C++异常处理机制 

        异常是指存在于运行时的反常行为,这些行为超出了函数正常功能的范围。当程序的某部分检测到一个它无法处理的问题,需要用到异常处理。在C++语言中,异常相关内容的详细介绍如下:
        C++异常处理机制 -- frame_main

1.1.11 命名空间

        

1.2、编译和内存相关

1.2.1 、C++程序的编译过程

详细讲解如下:

        C/C++编译过程浅析

1.2.2、C++内存管理

C++ 内存分区:栈、堆、全局/静态存储区、常量存储区、代码区

:存放函数的局部变量、函数参数、返回地址等,由编译器自动分配和释放。
:动态申请的内存空间,就是由 malloc 分配的内存块,由程序员控制它的分配和释放,如果程序执行结束还没有释放,操作系统会自动回收。
全局区/静态存储区(.bss 段和 .data 段):存放全局变量和静态变量,程序运行结束操作系统自动释放,在 C 语言中,未初始化的放在 .bss 段中,初始化的放在 .data 段中,C++ 中不再区分了。
常量存储区(.data 段):存放的是常量,不允许修改,程序运行结束自动释放。
代码区(.text 段):存放代码,不允许修改,但可以执行。编译后的二进制文件存放在这里。

力扣https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/e4vkxv/

1.2.3、内存对齐 

1.2.4、静态库与动态库的区别

1.2.5 C++多文件编程

        为了方便项目的开发与后期维护,最好将同一业务方向的代码放在同一源文件中,这就必然使得项目由分散在不同路径下的多个源文件组成。但涉及到不同文件定义的变量,数据类型和函数相互引用时会导致一系列的问题,具体的介绍如下:

        C++多文件编程

1.3、面向对象:

1.3.1、在资源受限的情况下,C++的编程技巧

1.3.2、重载与重写的区别

1.3.3、何为多态

1.3.4、构造函数与析构函数

构造函数与析构函数的用法

继承中的构造函数与析构函数

#include <iostream>
using std::cout;
using std::endl;
class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
    ~Base() {
        cout << "Base destructor" << endl;
    }
private:
    int val1;
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived constructor" << endl;
    }
    ~Derived() {
        cout << "Derived destructor" << endl;
    }
private:
    int val2;
};

int main(int argc, char *argv[])
{
    Derived derived;
}

输出结果为:

 

(1)、(继承)派生类中构造函数的调用顺序

Base constructor

Derived constructor

(2)、(继承)派生类中析构函数的调用顺序

Derived destructor

Base destructor

    基类-->派生类   基类指针指向派生类对象-->(1)、调用基类中的成员变量与成员方法
                                                                          (2)、调用派生类中定义的成员变量与成员方法
                                                                          (3)、调用虚函数

类中名字的查找顺序

1.3.5、虚函数

(1)、有无虚函数的类的区别

(2)、虚函数与纯虚函数

(3)、虚函数的实现原理

(4)、是否存在虚构造函数与虚析构函数

1.3.6、new和malloc以及delete和free的区别

1.4、STL标准库:

1.4.1、C++中 vector与数组的区别

1.4.2、list、map和vector的区别,以及在删除元素时的注意事项,返回值是什么?

1.5、模板:

1.6、泛型编程

二、LINUX

2.1、LINUX基础

2.2、LINUX系统编程

进程和线程的区别

如何防止多个线程同时操作同一资源

2.2.1、多线程 如何将线程与多核CPU绑定,同时跑多个线程?

2.2.2、线程池

2.2.3、静态库与动态库

2.2.4、进程间的通信方式

三、数据结构与算法

概念以及术语

3.1

线性表

3.2.1 如何最快的获取单链表的中间结点的位置?给定一个单链表,不知道结点总个数,怎么只遍历一次就知道中间结点?

方法一:

最容易想到的一个方法是:首先遍历一遍获得结点总个数,然后取一半作计数器再次遍历。这个方法遍历了两次,是最慢的方法。

方法二:

使用双指针的方法,这个方法是面试题的正解。一个指针(P1)每次步进一个结点,另一个指针(P2)每次步进两个结点。当P2遍历到链表末尾时,P1正好遍历到中间结点。

栈和队列

3.3

串、数组和广义表

3.4

树和二叉树

3.5

3.6

查找

3.7.1叙述常用的查找算法,并描述其时间复杂度

排序

3.8.1 叙述常用的排序算法,并描述其时间复杂度

四、计算机网络 TCP/IP

五、数据库

六、操作系统

七、编译原理

八、makefile

1、makefile 如何添加一个新的编译文件

本文含有隐藏内容,请 开通VIP 后查看