美文网首页
C++ 基础

C++ 基础

作者: cb_guo | 来源:发表于2020-04-18 21:36 被阅读0次

    C 和 C++ 区别

    • C语言是面向过程的编程,C++是面向对象的编程
    • C中变量声明和代码是分开的,必须在函数开始处声明;
      C++中,变量可以在任意处声明,只要保证先声名后使用的原则就可以。
    • C++中有bool(或boolean类型);C中可没有这样的bool类型,均为数值类型
    • C语言中函数没有参数默认值,在C++中函数有参数默认值的概念
    • C++ 引用概念 int& a =b

    cpp 分配内存的几种方式

    cpp 内存
    c/c++ 内存分配

    cpp 类的大小

    对象是类类型的一个变量,类则是对象的模板
    类是抽象的,不占用存储空间;而对象是具体的,占用存储空间

    c++ 对象内存分布

    最权威结论是:非静态成员变量总和加上编译器为了CPU计算做出的数据对齐处理和支持虚函数所产生的负担的总和
    C++ 中每个空类型的实例(类为空的对象)占1Byte空间
    静态数据成员是不占对象的内存空间的
    成员函数不占空间
    构造函数和析构函数不占空间
    编译器为了支持虚函数,会产生额外的负担,这正是指向虚函数表的指针的大小
    函数代码段是公用的,即如果一个类定义了10个对象,这些对象的成员函数所对应的是同一个函数代码段,而不是10个不同的函数代码段

    引用和指针区别

    本质上的区别是,指针是一个新的变量,只是这个变量存储的是另一个变量的地址,我们通过访问这个地址来修改变量。
    引用只是一个别名,还是变量本身。对引用进行的任何操作就是对变量本身进行操作,因此以达到修改变量的目的

    (1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;
    而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:
    int a=1;int *p=&a;
    int a=1;int &b=a;
    上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。
    而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。
    (2)可以有const指针,但是没有const引用(const引用可读不可改,与绑定对象是否为const无关)
    (3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
    (4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
    (5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
    (6)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
    (7)指针和引用的自增(++)运算意义不一样;
    
    • 面向对象三大特性(封装,继承,多态);什么时候会用到多态
      C++多态性主要是通过虚函数实现的,虚函数允许子类重写
      应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承
      派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用
    • cpp 虚函数,纯虚函数,虚函数表
      虚函数与虚继承寻踪
      虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数
      纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。同时含有纯虚函数的类称为抽象类,它不能生成对象
    • 一个复杂的类继承+虚函数+成员变量,问类的内存分布
    • 虚表指针指的是什么,那个复杂类里面的虚表,他的结构是什么样的
    • stl 用过吗;vector和list的优缺点(优缺点都要说),什么时候用vector,什么时候用list
      如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
      如果你需要大量的插入和删除,而不关心随即存取,则应使用list
      如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque
    • vector, list, map, hashmap 底层原理和各种功能复杂度
      c++ list, vector, map, set 区别与用法比较
    • 创建一个 vector<int> ,里面存了 5 个元素,1 2 3 4 5 把迭代器指向5,然后在vector最前面插入一个0,问刚才那个迭代器指向几 4
    #include<iostream>
    #include<vector>
    using namespace std;
    int main(){
        vector<int> aa;
        aa.push_back(1);
        aa.push_back(2);
        aa.push_back(3);
        aa.push_back(4);
        aa.push_back(5);
        vector<int>::iterator it = aa.end()-1;
        cout<<*it<<endl;        // 5
        aa.insert(aa.begin(), 1, 0);  // 在最前面插入一个0
        
        vector<int>::iterator tt;
        for(tt = aa.begin(); tt != aa.end(); tt++){
            cout<<*tt<<" ";    //  0 1 2 3 4 5
        }
        cout<<*it<<endl;       // 4
    }
    
    • 创建一个 vector<int> ,里面存了 5 个元素,1 2 3 4 5 把迭代器指向3,然后在vector中删除 3 ,问刚才那个迭代器指向几 4
    #include<iostream>
    #include<vector>
    using namespace std;
    int main(){
        vector<int> aa;
        aa.push_back(1);
        aa.push_back(2);
        aa.push_back(3);
        aa.push_back(4);
        aa.push_back(5);
        vector<int>::iterator it = aa.end()-3;
        cout<<*it<<endl;  // 3       
        
        aa.erase(it);
        cout<<*it<<endl;  // 4  
        vector<int>::iterator tt;
        for(tt = aa.begin(); tt != aa.end(); tt++){
            cout<<*tt<<" ";   // 1 2 4 5 
        }
        cout<<endl;
    }
    
    • 为什么vector的插入操作可能会导致迭代器失效?
      vector动态增加大小时,并不是在原空间后增加新的空间,而是以原大小的两倍在另外配置一片较大的新空间,然后将内容拷贝过来,并释放原来的空间。由于操作改变了空间,所以迭代器失效。

    • vector的内存分配了解吗?a是vector的一个元素,&a传参可以吗?为什么?
      最好不要,vector动态分配内存,如果vector扩容原来的地址失效

    • 什么是迭代器失效
      C++ 迭代器失效
      容器元素整体“迁移”导致存放原容器元素的空间不再有效,从而使得指向原空间的迭代器失效。
      删除元素使得某些元素次序发生变化使得原本指向某元素的迭代器不再指向希望指向的元素。

    • 智能指针 shared_ptr 和 weak_ptr 使用方式,为什么这么用;auto_ptr为什么被废了,和 shared_ptr 区别
      STL一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr和weak_ptr,其中后三个是c++11支持,并且第一个已经被c++11弃用。
      第一种智能指针是shared_ptr,它有一个叫做共享所有权(sharedownership)的概念。shared_ptr的目标非常简单:多个指针可以同时指向一个对象,当最后一个shared_ptr离开作用域时,内存才会自动释放。
      如果需要共享资源使用shared_ptr,如果独占使用资源就使用unique_ptr.
      c++ 智能指针用法详解
      C++ 智能指针
      C++11 智能指针
      为什么要使用智能指针:我们知道c++的内存管理是让很多人头疼的事,当我们写一个new语句时,一般就会立即把delete语句直接也写了,但是我们不能避免程序还未执行到delete时就跳转了或者在函数中没有执行到最后的delete语句就返回了,如果我们不在每一个可能跳转或者返回的语句前释放资源,就会造成内存泄露。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源

    • write/read 和 mmap 区别,零拷贝之类,mmap如何实现
      read()是系统调用,其中进行了数据拷贝,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,然后再将这些数据拷贝到用户空间,在这个过程中,实际上完成了两次数据拷贝
      而mmap()也是系统调用,如前所述,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了 一次数据拷贝。因此,内存映射的效率要比read/write效率高。
      内存映射文件原理探索
      mmap与read/write的区别

    • cpu 大端、小端存储
      大端模式:高位字节排放在内存的低地址端,低位字节排放在内存的高地址端
      小端模式:低位字节排放在内存的低地址端,高位字节排放在内存的高地址端

    比如数字 0x12345678 在两种不同字节序 CPU 中的存储顺序如下所示
    1)大端模式:
        低地址 -----------------> 高地址
        0x12  |  0x34  |  0x56  |  0x78
    2)小端模式:
        低地址 ------------------> 高地址
        0x78  |  0x56  |  0x34  |  0x12
    

    为什么要注意字节序的问题呢?
    如果程序只在单机环境下运行,并且不和其他程序打交道,那么完全可以忽略字节序的存在;
    但是,如果程序要跟其他程序产生交互呢,此时就一定需要注意写字节序的问题。C/C++ 语言编写的程序里数据存储顺序是跟编译平台所在 CPU 相关的,而 JAVA 编写的程序则唯一采用 大端模式 来存储数据
    试想,如果你用 C/C++ 语言在 x86 平台下编写的程序跟别人的 JAVA 程序互通时会产生什么结果? 就拿上面的 0x12345678 来说,将指向 0x12345678 的指针指向 JAVA 程序,由于 JAVA 采用大端方式存储数据,很自然会将你的数据翻译成 0x78563412 。因此,在你的 C 程序传给 JAVA 程序之前有必要进行字节序的转换工作
    无独有偶,所有网络协议也都是采用大端模式来传输数据。所以有时也会把大端方式称之为网络字节序当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输

    • 内存对齐,怎么修改对齐基数
      内存对齐到底是怎么回事?
      pragma pack 的主要作用就是改变编译器的内存对齐方式,这个指令在网络报文的处理中有着重要的作用,#pragma pack(n) 是他最基本的用法,其作用是改变编译器的对齐方式, 不使用这条指令的情况下,编译器默认采取#pragma pack(8)也就是8字节的默认对齐方式,n值可以取(1, 2, 4, 8, 16) 中任意一值。

    • struct 的字节对齐问题
      1、类型的长度和数据成员对齐:在第一个成员之后,每个成员距离struct 首地址的距离 offset ,都是 struct 内成员自身(sizeof) 与 #pragma pack(n) 中的 n 的最小值的整数倍,如果未经对其时不满足这个规则,在对齐时就会在这个成员前填充空字节以使其达到数据成员对齐
      2、整体对齐:编译器在进行数据成员对齐之后,还要进行整体对齐。与数据对齐相似但不是完全相同,如果数据对齐完成时 struct 的大小不是 struct 内成员自身长度最大值(sizeof) 与 #pragma pack(n) 中的 n 的最小值的整数倍。(注意这里是成员中长度最大的那个与n比较,而不是特定的一个成员。)就要在struct的最后添加空字节直到对齐。

    • class 和 struct 区别,class 怎么防止继承,
      C++ 中保留了C语言的 struct 关键字,并且加以扩充。在C语言中,struct 只能包含成员变量,不能包含成员函数。而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。
      总的来说,struct 更适合看成是一个数据结构的实现体,class 更适合看成是一个对象的实现体。最本质的一个区别就是默认的访问控制

      1. 默认的继承访问权限。struct 是 public 的,class 是 private 的。
      2. struct 作为数据结构的实现体,它默认的数据访问控制是 public 的,而 class 作为对象的实现体,它默认的成员变量访问控制是 private 的。
        在编写C++代码时,我强烈建议使用 class 来定义类,而使用 struct 来定义结构体,这样做语义更加明确。
    • 预编译

    预处理器的主要作用就是把通过预处理的内建功能对一个资源进行等价替换,
    最常见的预处理有:文件包含,条件编译、布局控制和宏替换4种。
    
    文件包含:
    #include 是一种最为常见的预处理,主要是做为文件的引用组合源程序正文。
    
    条件编译:
    #if,#ifndef,#ifdef,#endif,#undef等也是比较常见的预处理,主要是进行编译时进行有选择的挑选,
    注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。
    
    布局控制:
    #progma,这也是我们应用预处理的一个重要方面,主要功能是为编译程序提供非常规的控制流信息。
    
    宏替换:
    #define,这是最常见的用法,它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。
    
    • c++ 11 新特性
      十大必掌握C++11新特性
      C++11新特性梳理
      新增基于范围的for循环
      自动类型推断 auto
      匿名函数 Lambda
      显示重写(覆盖)override和final
      空指针常量 nullptr
      long long int类型
      线程支持
      智能指针

    • “浅拷贝”与“深拷贝”

    • 拷贝构造函数和拷贝赋值函数的区别
      调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。
      拷贝构造函数必须以引用的方式传递参数

    • 对于一个空类,编译器默认生成四个成员函数:默认构造函数、析构函数、拷贝构造函数、赋值函数

    • 析构函数为何要是虚函数?
      基类,派生类。基类析构是非虚函数,则派生类销毁时,只会调用基类析构函数,不会调用派生类构造函数,会造成资源泄露,损坏数据结构。然而,是虚函数可以避免此问题。

    • C++构造函数不能为虚函数
      从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。

    • define 和 const 区别
      在C++ 中,可以用const来定义常量,也可以用 #define来定义常量。但是前者比后者有更多的优点:
      const常量有数据类型,而宏常量没有数据类型。
      编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。

    • 构造函数能否抛异常?最好不要,否则容易内存泄露

    • 协程的概念
      协程的概念总结
      协程就是类函数一样的程序组件,你可以在一个线程里面轻松创建数十万个协程,就像数十万次函数调用一样

    • 常用的调试方法和工具:GDB
      gdb 是 gcc 的调试工具,主要用于C与C++ 这两种语言编写的程序。它的功能很强大,主要体现在以下4点。1)启动程序时,可以按照用户自定义的要求随心所欲地运行程序;2)可让被调试的程序在指定的断点处停止;3)当程序被停住时,可以检查此时程序中的运行状态;4)动态地改变程序的执行环境

    • new和malloc区别
      1、 申请的内存所在位置
      new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存
      2、返回类型安全性
      new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
      3、内存分配失败时的返回值
      new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
      4、是否需要指定内存大小
      使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸
      5、是否调用构造函数/析构函数
      new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。而malloc则不会
      6、对数组的处理
      C++提供了new[]与delete[]来专门处理数组类型:
      至于malloc,它并知道你在这块内存上要放的数组还是啥别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小

    • 函数指针与指针函数辨别
      函数指针是指向函数的指针变量,即本质是一个指针变量。
      指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针

    • static
      参考1 参考2

    • uint16_t和uint32_t的强转相关

    • 引用是否能实现动态绑定,为什么引用可以实现
      因为对象的类型是确定的,在编译期就确定了
      指针或引用是在运行期根据他们绑定的具体对象确定

    • 动态绑定
      参考1 参考2

    • 成员初始化列表的概念,为什么用成员初始化列表会快一些
      参考1 参考2

    • STL 中 vector 中 reserve() 和 resize() 区别
      resize()函数的作用是改变vector元素个数
      reverse() 函数的作用是改变容量

    • 指针常量 vs 常量指针
      指针常量 int *const p
      常量指针 const int *p / int const *p

    • 类是抽象的,不占用存储空间;而对象是具体的,占用存储空间

    • 内存泄漏:在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
      最难捉摸也最难检测到的错误之一是内存泄漏,即未能正确释放以前分配的内存的 bug。 只发生一次的小的内存泄漏可能不会被注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种征兆:从性能不良(并且逐渐降低)到内存完全用尽。 更糟的是,泄漏的程序可能会用掉太多内存,以致另一个程序失败,而使用户无从查找问题的真正根源。 此外,即使无害的内存泄漏也可能是其他问题的征兆。
      内存泄漏会因为减少可用内存的数量从而降低计算机的性能。最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃。内存泄漏可能不严重,甚至能够被常规的手段检测出来。在现代操作系统中,一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂运行的应用程序中的内存泄漏不会导致严重后果。

    相关文章

      网友评论

          本文标题:C++ 基础

          本文链接:https://www.haomeiwen.com/subject/jasrvqtx.html