IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 记录C/C++面试题 -> 正文阅读

[C++知识库]记录C/C++面试题

  1. 多态的实现
    在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。
    如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数,此为多态的表现;

  2. Cpp四种强制类型转换
    const_cast:从字面意思上就可以理解,去除变量的const属性。
    static_cast:静态类型转换,一般用于基本类型间的转换,如char->int
    dynamic_cast:动态转换,同于多态之间的类型转换
    reinterpret_cast:用于不同类型的指针类型的转换。

  3. 类的static成员的特点
    static成员只有一份拷贝,被该类的所有对象所共享;
    static成员只能在类外初始化,并存放在全局(静态)存储区,不计入类的大小中;
    static可以通过类名直接访问,也可以通过对象访问;
    static成员函数只能访问static成员变量,因为其他的数据成员与生成的对象是绑定的,static成员函数不属于任何对象,没有this指针;

  4. 指针和引用的区别
    引用是被引用对象的一个别名,其只能在定义的时候初始化,并且其值不能改变不能为空
    指针可以在任何时候给其赋值,并且其可以为nullptr
    sizeof引用为其引用对象的大小,sizeof指针为指针本身的大小
    对引用取地址为其引用对象的地址

  5. 谈谈对Cpp内存的理解
    1、栈区(stack)― 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
    2、堆区(heap)― 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
    3、全局区(静态区)(static)― 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
    4、文字常量区 ― 常量字符串就是放在这里的。 程序结束后由系统释放
    5、程序代码区 ― 存放函数体的二进制代码。

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

  7. const关键字
    1.const 修饰类的成员变量,表示成员常量,不能被修改。
    2.const修饰函数承诺在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数。
    3.如果 const 构成函数重载,const 对象只能调用 const 函数,非 const 对象优先调用非 const 函数。
    4.const 函数只能调用 const 函数。非 const 函数可以调用 const 函数。
    5.类体外定义的 const 成员函数,在定义和声明处都需要 const 修饰符。。
    int const *p / const int *p; //value是常数
    int * const p; //常指针
    int *const p const; //常指针、value值也是常数

  8. static关键字

    1.static修饰全局变量

    当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。

    未加static的全局变量,在符号表中是global符号,其他目标文件可见,这样的符号是要参与符号解析的。加了static之后,是local符号,其他目标文件不可见,只在当前文件中可见,不参与符号解析过程。所以多个源文件可以定义同名的static全局变量,不会产生重定义错误。

    修饰全局变量是改变变量的作用域,让它只能在本文件中使用。

    2. 修饰局部变量时,使它放在.data 或者.bss段,默认初始化为0,初始化不为0放在.data段,没有初始化或初始化为0放在.bss段。程序一运行起来就给他分配内存,并进行初始化,也是唯一一次初始化。它的生存期为整个源程序,程序结束,它的内存才释放。但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。

    修饰局部变量是改变它的生存期,变为和整个程序的生命周期一样。

    3.修饰普通函数时,和修饰全局变量一样。函数经过编译产生一个函数符号,被static修饰后,就变为local符号,不参与符号解析,只在本文件中可见。

    4、修饰类的成员变量时。就变成静态成员变量,不属于对象,而属于类。不能在类的内部初始化,类中只能声明,定义需要在类外。类外定义时,不用加static关键字,只需要表明类的作用域。

    5、修饰类的成员函数时。变成静态成员函数,也不属于对象,属于类。形参不会生成this指针,仅能访问类的静态数据和静态成员函数。调用不依赖对象,所以不能作为虚函数。用类的作用域调用。

    6、如果你希望在一个函数中对一个变量只执行一次初始化,以后不再初始化,使用上一次结果,就应该使用静态局部变量。

  9. 构造函数为什么不能是虚函数

    1.调用虚函数需要访问虚表,访问虚表需要虚指针,构造函数在调用前根本不存在虚指针,这带来了先有鸡还是先有蛋的问题;
    2.多态是根据指针指向的具体对象类型调用对应的方法,前提当然是这个对象是已经构建出来了;
    3.构造函数是定义一个对象时自动调用的,用户不能自己调用构造函数,所以没必要是虚函数;

    总结:使用虚函数的前提是对象已经构造出来了,对象还没构造,使用什么虚函数?

  10. select、poll、epoll

    select的调用过程如下所示:

    (1)使用copy_from_user从用户空间拷贝fd_set到内核空间

    (2)注册回调函数__pollwait

    (3)遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll)

    (4)poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。

    (5)如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。

    (6)把fd_set从内核空间拷贝到用户空间。

    select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

    1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。

    ? ? ? 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.

    2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:

    ? ? ? ?当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。

    (1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

    (2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

    3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

    poll:

    poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

    poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

    它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:

    1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。 ? ? ? ? ? ? ? ? ??

    2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

    epoll:

    epoll有EPOLL LT和EPOLL ET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

    epoll为什么要有EPOLL ET触发模式?

    如果采用EPOLL LT模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。而采用EPOLL ET这种边沿触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

    epoll的优点:

    1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);
    2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;
    即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

    3、 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。

    select、poll、epoll之间的区别:

    \selectpollepoll
    操作方式遍历遍历回调
    底层实现数组链表哈希表
    IO效率每次调用都进行线性遍历,时间复杂度为O(n)每次调用都进行线性遍历,时间复杂度为O(n)事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到rdllist里面。时间复杂度O(1)
    最大连接数1024(x86)或 2048(x64)无上限无上限
    fd拷贝每次调用select,都需要把fd集合从用户态拷贝到内核态每次调用poll,都需要把fd集合从用户态拷贝到内核态调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

  11. sql执行顺序?

    from—join—on—where—group by—avg,sum....—having—select—distinct—order by—limit

  12. 字符串的操作(C和C++都说一说)

    C语言中有很多字符串操作函数,常见的有:

    字符串复制:char *strcpy(char *s1, const char *s2);
    求字符串长度:size_t strlen(const char *s);
    字符串拼接:char *strcat(char *s1, const char *s2);
    字符串比较:int strcmp(const char *s1, const char *s2);

    C++中字符串保存在string类型的变量(对象)中,而与上述字符串操作对应的操作:

    字符串复制:string& assign (const string& str);
    求字符串长度:size_t length() const;
    字符串拼接:string& append (const string& str);
    字符串比较:int compare (const string& str) const;

  13. 知道STL吗,挑两个你最常用的容器说一说

    vector:动态扩容数组

    map:key-value数据,自动排序去重。有以下几种不同的map(map、multimap、unordered_map、unordered_multimap),其中map用的是红黑树,unordered_map用的是hash表。
  14. 怎么确定一个程序是C编译的还是C++编译的
    如果编译器在编译cpp文件,那么__cplusplus就会被定义,如果是一个C文件被编译,那么 _STDC_就会被定义,_STDC_是预定义宏,当它被定义后,编译器将按照ANSIC标准来编译C语言程序。

  15. 说一下什么是内存泄漏,如何避免
    ??内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。最终的结果就是导致OOM。
    ??内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。
    确保没有在访问空指针。
    每个内存分配函数都应该有一个 free 函数与之对应,alloca 函数除外。
    每次分配内存之后都应该及时进行初始化,可以结合 memset 函数进行初始化,calloc 函数除外。
    每当向指针写入值时,都要确保对可用字节数和所写入的字节数进行交叉核对。
    在对指针赋值前,一定要确保没有内存位置会变为孤立的。
    每当释放结构化的元素(而该元素又包含指向动态分配的内存位置的指针)时,都应先遍历子内存位置并从那里开始释放,然后再遍历回父节点。
    始终正确处理返回动态分配的内存引用的函数返回值。

  16. 一个文件从源码到可执行文件所经历的过程
    1.预处理,产生.ii文件
    2.编译,产生汇编文件(.s文件)
    3.汇编,产生目标文件(.o或.obj文件)
    4.链接,产生可执行文件(.out或.exe文件)

  17. 了解C++新特性吗
    1.关键字及新语法:auto、nullptr、for
    2.STL容器:std::array、std::forward_list、std::unordered_map、std::unordered_set
    3.多线程:std::thread、std::atomic、std::condition_variable
    4.智能指针内存管理:std::shared_ptr、std::weak_ptr
    5.其他:std::function、std::bind和lamda表达式

  18. C++构造函数和析构函数在父子类之间的调用顺序

    建立对象时,首先调用基类的构造函数,然后在调用下一个派生类的构造函数,依次类推;

    析构对象时,其顺序正好与构造相反;

  19. 什么是纯虚函数
    相当于一个函数接口,只声明不定义。在其派生类里会重写。有纯虚函数的类为抽象类,不能实例化出对象。

  20. 构造函数和析构函数可以为虚函数吗
    构造函数不可以,析构函数可以甚至有时候必须声明为虚函数。用基类类型指针绑定派生类实例,析构的时候,如果基类析构函数不是虚函数,则只会析构基类,不会析构派生类对象,从而造成内存泄漏。

  21. 栈和堆的区别,什么时候必须使用堆
    栈是由程序分配的,而堆是由程序员手动去分配释放的。当需要的空间特别大的时候,就必须使用堆,因为栈的大小是有限制的,一般为5MB左右,所以当需要一个大块空间是,必须在堆上开辟空间。

  22. 如何不用sizeof判断一个机器是16位还是32位//不用sizeof(), 判断系统是32位还是16位...
    #include <cstdio>
    #include <cstdlib>
    #include <iostream>
    using namespace std;
    int main()
    {
    ? ? unsigned int a = ~0;
    ? ? if( a>65536 )
    ? ? {
    ? ? ? ? cout<<"32 bit"<<endl;
    ? ? }
    ? ? else
    ? ? {
    ? ? ? ? cout<<"16 bit"<<endl;
    ? ? }
    ? ? system("PAUSE");
    ? ? return 0;
    }

  23. 用宏定义实现swap
    #define F(a, b) (a = a ^ b);(b = a ^ b);(a = a ^ b);

  24. 头文件<>和""的区别
    遇到#include<math.h>时,系统先从系统默认的头文件目录中查找头文件
    遇到#include"math.h"时,系统先从当前的目录中搜索,若没有找到,再从系统默认的头文件中找
    故包含系统提供的库函数用#include<math.h>更快
    当包含用户自定义的.h文件时,使用#include"math.h"更快

  25. 编写string的构造函数、拷贝构造函数、赋值操作符重载和析构函数

    #include<cstring>
    #include<iostream>
    
    using namespace std;
    
    class MyString {
    public:
        MyString(const char* pcData = nullptr) {
            if(pcData == nullptr) {
                m_pdata = new char[1];
                *m_pdata = '\0';
            }
            else {
                int len = strlen(pcData);
                m_pdata = new char[len+1];
                strcpy(m_pdata, pcData);
            }
        }
    
        MyString(const MyString& other) {
            int len = strlen(other.m_pdata);
            m_pdata = new char[len+1];
            strcpy(m_pdata, other.m_pdata);
        }
    	
        MyString& operator =(const MyString &str) {
    		if(this == &str)
    			return *this;
    		delete [] m_pdata;
    		m_pdata = nullptr;
    		m_pdata = new char[strlen(str.m_pdata)+1];
    		strcpy(m_pdata, str.m_pdata);
    		return *this;
        }
    
        void Print() {
            cout << this->m_pdata << endl;
        }
    
        ~MyString() {
            delete [] m_pdata;
        }
    
    private:
        char* m_pdata;
    };
    
    int main() {
        MyString mstr;
    	MyString mstr2("hello world!");
    	mstr = mstr2;
    	mstr.Print();
    	mstr2.Print();
    
    	return 0;
    }
    

26.?计算机内部如何存储负数和浮点数?

负数比较容易,就是通过一个标志位和补码来表示。

对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit,double数据占用64bit,我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?如果胡乱分配,那世界岂不是乱套了么,其实不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。更多可以参考浮点数表示。
无论是单精度还是双精度在存储中都分为三个部分:

  • 1). 符号位(Sign) : 0代表正,1代表为负
  • 2). 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
  • 3). 尾数部分(Mantissa):尾数部分
    其中float的存储方式如下图所示:

而双精度的存储方式如下图:
在这里插入图片描述

27.函数调用的过程?

?如下结构的代码:

int main(void)
{
  ...
  d = fun(a, b, c);
  cout<<d<<endl;
  ...
  return 0;
}

调用fun()的过程大致如下:

  • main()========
  • 参数拷贝(压栈),注意顺序是从右到左,即c-b-a;
  • 保存d = fun(a, b, c)的下一条指令,即cout<<d<<endl(实际上是这条语句对应的汇编指令的起始位置);
  • 跳转到fun()函数,注意,到目前为止,这些都是在main()中进行的;
  • fun()=====
  • 移动ebp、esp形成新的栈帧结构;
  • 压栈(push)形成临时变量并执行相关操作;
  • return一个值;
  • 出栈(pop);
  • 恢复main函数的栈帧结构;
  • 返回main函数;
  • main()=======

28.左值和右值

  • 可以取地址的,有名字的,非临时的就是左值
  • 不能取地址的,没有名字的,临时的,通常生命周期就在某个表达式之内的就是右值

29.C和C++的区别?

1). C++是C的超集;
2). C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制),而对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。

30.int fun() 和 int fun(void)的区别?
这里考察的是c 中的默认类型机制。

  • 在c中,int fun() 会解读为返回值为int(即使前面没有int,也是如此,但是在c++中如果没有返回类型将报错),输入类型和个数没有限制, 而int fun(void)则限制输入类型为一个void。
  • 在c++下,这两种情况都会解读为返回int类型,输入void类型。

31.在C中用const 能定义真正意义上的常量吗?C++中的const呢?
不能。c中的const仅仅是从编译层来限定,不允许对const 变量进行赋值操作,在运行期是无效的,所以并非是真正的常量(比如通过指针对const变量是可以修改值的),但是c++中是有区别的,c++在编译时会把const常量加入符号表,以后(仍然在编译期)遇到这个变量会从符号表中查找,所以在C++中是不可能修改到const变量的。

补充:

  • c中的局部const常量存储在栈空间,全局const常量存在只读存储区,所以全局const常量也是无法修改的,它是一个只读变量。
  • 这里需要说明的是,常量并非仅仅是不可修改,而是相对于变量,它的值在编译期已经决定,而不是在运行时决定。
  • c++中的const 和宏定义是有区别的,宏是在预编译期直接进行文本替换,而const发生在编译期,是可以进行类型检查和作用域检查的。
  • c语言中只有enum可以实现真正的常量。
  • c++中只有用字面量初始化的const常量会被加入符号表,而变量初始化的const常量依然只是只读变量。
  • c++中const成员为只读变量,可以通过指针修改const成员的值,另外const成员变量只能在初始化列表中进行初始化。

32.宏和内联(inline)函数的比较?

  • 首先宏是C中引入的一种预处理功能;
  • 内联(inline)函数是C++中引入的一个新的关键字;C++中推荐使用内联函数来替代宏代码片段;
  • 内联函数将函数体直接扩展到调用内联函数的地方,这样减少了参数压栈,跳转,返回等过程;
  • 由于内联发生在编译阶段,所以内联相较宏,是有参数检查和返回值检查的,因此使用起来更为安全;
  • 需要注意的是, inline会向编译期提出内联请求,但是是否内联由编译器决定(当然可以通过设置编译器,强制使用内联);
  • 由于内联是一种优化方式,在某些情况下,即使没有显示的声明内联,比如定义在class内部的方法,编译器也可能将其作为内联函数。
  • 内联函数不能过于复杂,最初C++限定不能有任何形式的循环,不能有过多的条件判断,不能对函数进行取地址操作等,但是现在的编译器几乎没有什么限制,基本都可以实现内联。

33.求下列结果

#include <iostream>

using namespace std;
class B
{
public:
    int m_iNum;
    virtual void foo(){cout<<"B"<<endl;};
};

class D : public B
{
public:
    char *m_szName[100];
    void foo(){cout<<"D"<<endl;};
};

void func(B *pb)
{
    D *pd1 = static_cast<D*>(pb);
    D *pd2 = dynamic_cast<D*>(pb);
    pd1->foo();
    pd2->foo();
}
int main()
{
    B *b=new B();
    func(b);
    return 0;
}

B

pd1为D*类型,pd2为空指针

34.在C++程序中调用被C编译器编译后的函数,为什么要加extern“C”?

C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同,假设某个函数原型为:

          void foo(int x, int y);

该函数被C编译器编译后在库中的名字为 _foo, 而C++编译器则会产生像: _foo_int_int 之类的名字。为了解决此类名字匹配的问题,C++提供了C链接交换指定符号 extern “C”。

35.?头文件中的 ifndef/define/endif 是干什么用的? 该用法和 program once 的区别?
相同点:

  • 它们的作用是防止头文件被重复包含。

不同点

  • ifndef 由语言本身提供支持,但是 program once 一般由编译器提供支持,也就是说,有可能出现编译器不支持的情况(主要是比较老的编译器)。
  • 通常运行速度上 ifndef 一般慢于 program once,特别是在大型项目上, 区别会比较明显,所以越来越多的编译器开始支持 program once。
  • ifndef 作用于某一段被包含(define 和 endif 之间)的代码, 而 program once 则是针对包含该语句的文件, 这也是为什么 program once 速度更快的原因。
  • 如果用 ifndef 包含某一段宏定义,当这个宏名字出现“撞车”时,可能会出现这个宏在程序中提示宏未定义的情况(在编写大型程序时特别需要注意,因为有很多程序员在同时写代码)。相反由于program once 针对整个文件, 因此它不存在宏名字“撞车”的情况, 但是如果某个头文件被多次拷贝,program once 无法保证不被多次包含,因为program once 是从物理上判断是不是同一个头文件,而不是从内容上。

36.指针和引用的区别?
相同点:

  • 1). 都是地址的概念;
  • 2). 都是“指向”一块内存。指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名;
  • 3). 引用在内部实现其实是借助指针来实现的,一些场合下引用可以替代指针,比如作为函数形参。

不同点:

  • 指针是一个实体,而引用(看起来,这点很重要)仅是个别名;
  • 引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
  • 引用不能为空,指针可以为空;
  • “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
  • 指针和引用的自增(++)运算意义不一样;
  • 引用是类型安全的,而指针不是 (引用比指针多了类型检查)
  • 引用具有更好的可读性和实用性。

37.引用占用内存空间吗?

对引用取地址,其实是取的引用所对应的内存空间的地址。这个现象让人觉得引用好像并非一个实体。但是引用是占用内存空间的,而且其占用的内存和指针一样,因为引用的内部实现就是通过指针来完成的。

38.三目运算符

在C中三目运算符(? :)的结果仅仅可以作为右值,比如如下的做法在C编译器下是会报错的,但是C++中却是可以是通过的。这个进步就是通过引用来实现的,因为下面的三目运算符的返回结果是一个引用,然后对引用进行赋值是允许的。

int main(void)
{
        int a = 8;
        int b = 6;
        (a>b ? a : b) = 88;
        cout<<a; //output 88
    return 0;
}

39.指针数组和数组指针的区别

数组指针,是指向数组的指针,而指针数组则是指该数组的元素均为指针。

数组指针,是指向数组的指针,其本质为指针,形式如下。如 int (*p)[n],p即为指向数组的指针,()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。数组指针是指向数组首元素的地址的指针,其本质为指针,可以看成是二级指针。

类型名 (*数组标识符)[数组长度]

指针数组,在C语言和C++中,数组元素全为指针的数组称为指针数组,其中一维指针数组的定义形式如下。指针数组中每一个元素均为指针,其本质为数组。如 int *p[n], []优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。

类型名 *数组标识符[数组长度]

40.左值引用与右值引用

左值引用就是我们通常所说的引用,如下所示。左值引用通常可以看作是变量的别名。

type-id & cast-expression 

// demo
int a = 10
int &b = a

int &c = 10	// 错误,无法对一个立即数做引用

const int &d = 10	// 正确, 常引用引用常数量是ok的,其等价于 const int temp = 10; const int &d = temp	

右值引用是 C++11 新增的特性,其形式如下所示。右值引用用来绑定到右值,绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期。

type-id && cast-expression  

// demo
int &&var = 10;	// ok

int a = 10
int &&b = a	// 错误, a 为左值

int &&c = var	// 错误,var 为左值

int &&d = move(a)	// ok, 通过move得到左值的右值引用

在汇编层面右值引用做的事情和常引用是相同的,即产生临时量来存储常量。但是,唯一 一点的区别是,右值引用可以进行读写操作,而常引用只能进行读操作。

41.右值引用的意义

  • 右值引用支持移动语义的实现,可以减少拷贝,提升程序的执行效率

    // move example
    #include <utility>      // std::move
    #include <iostream>     // std::cout
    #include <vector>       // std::vector
    #include <string>       // std::string
     
    int main () {
      std::string foo = "foo-string";
      std::string bar = "bar-string";
      std::vector<std::string> myvector;
     
      myvector.push_back (foo);                    // copies
      myvector.push_back (std::move(bar));         // moves
     
      std::cout << "myvector contains:";
      for (std::string& x:myvector) std::cout << ' ' << x;
      std::cout << '\n';
      
      std::cout << "foo:"<<foo<<'\n';
      std::cout << "bar:"<<bar<<std::endl;
     
      return 0;
    }

    输出结果如下:

    myvector contains: foo-string bar-string
    foo:foo-string
    bar:

  • 右值引用可以使重载函数变得更加简洁。右值引用可以适用 const T& 和 T& 形式的参数。

42.什么是面向对象(OOP)?面向对象的意义?

Object Oriented Programming, 面向对象是一种对现实世界理解和抽象的方法、思想,通过将需求要素转化为对象进行问题处理的一种思想。其核心思想是数据抽象、继承和动态绑定(多态)。
面向对象的意义在于:将日常生活中习惯的思维方式引入程序设计中;将需求中的概念直观的映射到解决方案中;以模块为中心构建可复用的软件系统;提高软件产品的可维护性和可扩展性。

43.解释下封装、继承和多态?

1). 封装
封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。
封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
从封装的角度看,public, private 和 protected 属性的特点如下。

不管哪种属性,内类都是可以访问的
public 是一种暴露的手段,比如暴露接口,类的对象可以访问
private 是一种隐藏的手段,类的对象不能访问
protected 成员:
和 public 一样可以被子类继承
和 private 一样不能在类外被直接调用
特例:在衍生类中可以通过衍生类对象访问

2). 继承
继承主要实现重用代码,节省开发时间。
子类可以继承父类的一些东西。
a.公有继承(public) 公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态(基类的私有成员仍然是私有的,不能被这个派生类的子类所访问)。
b.私有继承(private) 私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员(并且不能被这个派生类的子类所访问)。
c.保护继承(protected) 保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员(并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的)。
这里特别提一下虚继承。虚继承是解决C++多重继承问题(其一,浪费存储空间;第二,存在二义性问题)的一种手段。比如菱形继承,典型的应用就是 iostream, 其继承于 istream 和 ostream,而 istream 和 ostream 又继承于 ios。

3).多态
多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。与之相对应的编译时绑定函数称为静态绑定。多态是设计模式的基础,多态是框架的基础。

44.什么时候生成默认构造函数(无参构造函数)?什么时候生成默认拷贝构造函数?什么是深拷贝?什么是浅拷贝?默认拷贝构造函数是哪种拷贝?什么时候用深拷贝?
1). 没有任何构造函数时,编译器会自动生成默认构造函数,也就是无参构造函数;当类没有拷贝构造函数时,会生成默认拷贝构造函数。
2). 深拷贝是指拷贝后对象的逻辑状态相同,而浅拷贝是指拷贝后对象的物理状态相同;默认拷贝构造函数属于浅拷贝。
3). 当系统中有成员指代了系统中的资源时,需要深拷贝。比如指向了动态内存空间,打开了外存中的文件或者使用了系统中的网络接口等。如果不进行深拷贝,比如动态内存空间,可能会出现多次被释放的问题。是否需要定义拷贝构造函数的原则是,类是否有成员调用了系统资源,如果定义拷贝构造函数,一定是定义深拷贝,否则没有意义。

45.虚析构函数的作用?

基类采用虚析构函数可以防止内存泄漏。比如下面的代码中,如果基类 A 中不是虚析构函数,则 B 的析构函数不会被调用,因此会造成内存泄漏。

class A{
public:
  A(){}
  //~A(){}
  virtual ~A(){cout << "A disconstruct" << endl;}  // 虚析构
//   ~A(){cout << "A disconstruct" << endl;}  // 析构

};

class B : public A{
public:
  B(){
    // new memory
    // ...
    cout << "B construct" << endl;
  }
  ~B(){
    // delete memory
    // ...
    cout << "B disconstruct" << endl;
  }
};

int main(int argc, char **argv)
{
  A *p = new B;
  
  // some operations
  // ...
  
  delete p;  // 由于基类中是虚析构,这里会先调用B的析构函数,然后调用A的析构函数
  
  return 0;
}

但并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。?

46.细看拷贝构造函数

对于 class A,它的拷贝构造函数如下:

 A::A(const A &a){}

1) 为什么必须是当前类的引用呢?
循环调用。如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。

只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 C++ 语法的要求。

2) 为什么是 const 引用呢?
拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 const 限制后,这个含义更加明确了。

另外一个原因是,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型。如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能直接转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了。

47.C++的编译环境
如下图所示,C++的编译环境由如下几部分构成:C++标准库、C语言兼容库、编译器扩展库及编译模块。

在这里插入图片描述

#include<iostream>  //C++标准库,不带".h"
#include<string.h>  //C语言兼容库,由编译器厂商提供

值得注意的是,C语言兼容库功能上跟C++标准库中的C语言子库相同,它的存中主要为了兼容C语言编译器,也就是说如果一个文件只包含C语言兼容库(不包含C++标准库),那么它在C语言编译器中依然可以编译通过。

48.Most vexing parse

自己编写一个类

假设自己写了这么一个类,我们想调用 copy 构造:

class String {
public:
    String() {
        cout << "dctor" << endl;
    }

    String(const string &name) {
        cout << name << endl;
    }
};

调用实现:

int main() {
    char *t = "helloworld";
    String s(string(t));        // no result
    return 0;
}

这种没有任何结果输出,理想情况下,我们会认为它会调用所谓的拷贝构造,可事实呢,这行被编译器认为是函数声明!

上述传递的是一个匿名对象,被解析成了函数名为 s,带了一个参数(函数指针指向参数 t 并返回 string 对象的函数),返回一个 String 对象的函数声明。

像这种问题被称为:"Most Vexing Parse"

解决方案:

Scott Meyers 在 Effective C++中提到有如下解决方案:

String s((string(t)));        // ok

在外部再次添加一个括号!

另外在 C++11 中也可以使用 Uniform initialization(统一初始化)来处理这种歧义:

String ss{string(t)};        // ok

49.-------------------待更新(操作系统和计网的面试题也有总结,可以去康康o3o)?

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-22 20:23:18  更:2022-02-22 20:23:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:45:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码