精华内容
下载资源
问答
  • C/C++面试试题2021

    2021-06-17 14:21:26
    C++试题
  • 程序员C/C++面试题汇总 Intel面试题 微软面试题 Cisco思科面试题 华为面试题C/C++笔试题 慧通面试题
  • 史上最全C/C++面试题汇总

    万次阅读 多人点赞 2020-04-20 23:29:22
    总结一些常见面试题,包括准备面试查阅的和自己实际面试中遇到的。不建议全篇的去背面试题,要学会将面试的知识点进行分类总结,聚合成一块块的知识点,然后去学习串联,推荐《王道程序员面试宝典》这本求职复习书籍...

    总结一些常见面试题,包括准备面试查阅的和自己实际面试中遇到的。不建议全篇的去背面试题,要学会将面试的知识点进行分类总结,聚合成一块块的知识点,然后去学习串联,推荐《王道程序员面试宝典》这本求职复习书籍,应付一般小厂的面试绰绰有余,内容很全面,建议反复阅读记忆。

    一、C/C++部分

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

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

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

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

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

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

    • 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值也是常数

    • static关键字

    • 构造函数为什么不能是虚函数

    • select、poll、epoll

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

    • 知道STL吗,挑两个你最常用的容器说一说
      vector:动态扩容数组
      map:key-value数据,自动排序去重。有以下几种不同的map(map、multimap、unordered_map、unordered_multimap),其中map用的是红黑树,unordered_map用的是hash表。

    • 怎么确定一个程序是C编译的还是C++编译的
      如果编译器在编译cpp文件,那么__cplusplus就会被定义,如果是一个C文件被编译,那么 _STDC_就会被定义,_STDC_是预定义宏,当它被定义后,编译器将按照ANSIC标准来编译C语言程序。

    • 说一下什么是内存泄漏,如何避免

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

    • 了解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表达式

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

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

    • 构造函数和析构函数可以为虚函数吗
      构造函数不可以,析构函数可以甚至有时候必须声明为虚函数。

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

    • 如何不用sizeof判断一个机器是16位还是32位

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

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

    • 编写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;
    }
    

    二、操作系统部分

    • 进程和线程间的通信方式
      Linux进程:管道、有名管道、信号、信号量、共享内存、消息队列、套接字
      Linux线程:互斥体、信号量、条件变量
      Windows进程:管道、共享内存、消息队列、信号量、套接字
      Windows线程:临界区、互斥量、信号量、事件

    • 死锁产生的原因和死锁的条件
      原因:系统资源的竞争、进程推进顺序非法
      条件:互斥条件、不剥夺条件、请求和保持条件、循环等待条件

    • 如何采用单线程处理高并发
      采取I/O复用来提高单线程处理多请求的能力(epoll和select)
      采用事件驱动模型,基于异步回调来处理事件

    • 线程的状态
      新建(NEW)、可运行(RUNNABLE)、运行(RUNNING)、阻塞(BLOCKED)、死亡(DEAD)

    • 进程的状态
      运行状态:进程正在处理器上运行,在单处理器环境下,每一时刻最多只有一个进程处于运行状态。
      就绪状态:进程已处于准备运行的状态,即进程获得了除处理器之外的一切所需要的资源,一旦得到处理器即可运行。
      阻塞状态:又称为等待状态,进程正在等待某一事件而暂停运行。如等待某资源为可用(不包括处理器)或等待输入/输出完成。即使处理器空闲,该进程也不能运行。
      创建状态:进程正在被创建,尚未转到就绪状态。
      结束状态:进程正从系统中消失。可能是进程正常结束或其它原因中断退出运行。

    • 系统调用brk和mmap
      从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。
      1、brk是将数据段(.data)的最高地址指针_edata往高地址推;
      2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。
      这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
      在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。

    • 进程和线程的区别

    • 常用的Linux命令

    • Makefile

    • gcc/g++

    • gdb

    • shell

    • 什么是虚拟内存

    • 说说三种内存管理机制
      页式管理:
      段式管理:
      分段分页管理:

    • 大端和小端,用C++代码怎么确定
      大端低地址存放高位,高地址存放地位。小端相反。

    union un {
    	char a;
    	int b;
    }
    un tmp.b = 1;
    if(tmp.a) {
    	cout << "小端" << endl;
    }
    else {
    	cout << "大端" << endl;
    }
    

    三、计算机网络部分

    • TCP和UDP的区别
      用户数据报协议 UDP(User Datagram Protocol) 是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加UDP首部),支持一对一、一对多、多对一和多对多的交互通信。
      传输控制协议 TCP(Transmission Control Protocol) 是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。

      1.TCP面向连接, UDP面向无连接的
      2.TCP有保障的,UDP传输无保障的
      3.TCP是效率低的,UDP效率高的
      4.TCP是基于流的,UDP基于数据报文
      5.TCP传输重要数据,UDP传输不重要的数据

    • TCP三次握手
      首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。
      B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。
      A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。
      B 收到 A 的确认后,连接建立。

    • 三次握手的原因
      第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
      客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。

    • TCP四次挥手
      以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。
      A 发送连接释放报文,FIN=1。
      B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。
      当 B 不再需要连接时,发送连接释放报文,FIN=1。
      A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。
      B 收到 A 的确认后释放连接。

    • 四次挥手的原因
      客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。

    • TIME_WAIT
      客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:
      确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。
      等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。

    • 一个URL从输入到浏览器地址栏开始都发生了什么

    • 编写socket套接字的步骤

    • Http协议
      http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式,HTTP1.1版本中给出一种持续连接的机制,绝大多数的Web开发,都是构建在HTTP协议之上的Web应用。

    四、数据结构算法部分

    • 几种常见的排序算法
      快排、插入、选择,这是最基本的三个,建议背下来,最有可能让你手撕的。

    • 链表的一些性质和操作
      链表是最常用也是比较简单的一个基本数据结构,几乎是构成所有高级结构的基础,面试常拿链表做文章。建议了解链表的一些常见问题:

      • 如何判断链表有环
        使用快慢指针,当两个指针重合证明有环
      • 转置链表O(n)时间、O(1)空间
        三个指针,改变next指针指向
    • 常见的查找算法

      • 二分法
    • 动态规划

      • 最长公共子序列

    五、个人面试经验总结

    其实就应届生来说,面试官考察的时候还是很仁慈的,不会问得很深,而且专挑你会的问。基本上你简历上写什么他就问什么。
    建议可以去实习的同学一定要去实习,实习经历和实习项目将会是一个重要的加分项和谈资,有一次面试,我和人家技术全程就在谈我实习期间的工作和项目。大概进行了半个多小时,直接通过。而且每次面试,针对项目和实习经历这一块,都会聊至少半个小时。所以,有条件一定要去实习,有基础一定要做一个有得说的项目。
    就C/C++而言,一般你只需要把语言和Linux(shell、gcc/g++、makefile、gdb这些是你应该了解的,并且以后工作中一定会用到的)学好,基本上就没什么大问题,数据结构和算法尽量学,但是基本的一定要会,然后就是操作系统和计算机网络,这是做后端开发必备的技能点,能学多好学多好,不过对应届生而言,差点也没关系。至于数据库,能学就学,反正我是没有系统的去学,会写SQL语句一般问题就不大。然后有兴趣和时间,推荐学习一些开源组件,类似于Nginx、各大MQ、docker。。。反正多学点指定没有坏处。

    展开全文
  • 2018秋招C/C++面试题总结

    万次阅读 多人点赞 2018-09-22 18:57:43
    博主从8月中旬开始大大小小面试了十几家公司,至今也许是告一段落吧,希望后面会有好结果,因此总结记录一些C/C++方向常见的问题。和大家一起学习! 参考了互联网的各种资源,自己尝试归类整理,谢谢~ 一、C和...

    博主从8月中旬开始大大小小面试了十几家公司,至今也许是告一段落吧,希望后面会有好结果,因此总结记录一些C/C++方向常见的问题。和大家一起学习!
    参考了互联网的各种资源,自己尝试归类整理,谢谢~

    一、C和C++的区别是什么?

    C是面向过程的语言,C++是在C语言的基础上开发的一种面向对象编程语言,应用广泛。
    C中函数不能进行重载,C++函数可以重载
    C++在C的基础上增添类,C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制),而对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。

    C++中struct和class除了默认访问权限外,别的功能几乎都相同。

    二、关键字static、const、extern作用

    static和const的作用在描述时主要从类内和类外两个方面去讲:

    static关键字的作用:

    (1)函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
    (2)在模块内的static全局变量和函数可以被模块内的函数访问,但不能被模块外其它函数访问;
    (3)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
    (4)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

    const关键字的作用:

    (1)阻止一个变量被改变
    (2)声明常量指针和指针常量
    (3)const修饰形参,表明它是一个输入参数,在函数内部不能改变其值
    (4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量(const成员一般在成员初始化列表处初始化)
    (5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为”左值”。

    extern关键字的作用:

    (1)extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
    (2)extern "C"的作用是让 C++ 编译器将extern "C"声明的代码当作 C 语言代码处理,可以避免 C++ 因符号修饰导致代码不能和C语言库中的符号进行链接。

    三、sizeof和strlen的区别

    (1)sizeof是运算符,而strlen是函数;
    (2)sizeof的用法是sizeof(参数),这个参数可以是数组,指针,类型,对象,甚至是函数,其值在编译的时候就计算好了,而strlen的参数必须是字符型指针(char*),其值必须在函数运行的时候才能计算出来;
    (3) sizeof的功能是获得保证能容纳实现的建立的最大对象的字节的大小,而strlen的功能是返回字符串的长度,切记这里的字符串的长度是不包括结束符的;
    (4)当数组作为参数传递给函数的时候,传的是指针,而不是数组,传递数组的首地址;

    char str[20] = "0123456789";
    int a = strlen(str);  //10
    int b = sizeof(str);//20
    

    四、指针和引用的区别

    (1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
    (2)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
    (3)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化
    (4)指针的值在初始化后可以改变,即指向其它的存储单元,而引用初始化后就不会再改变。
    (5)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小。
    (6)作为参数传递时,二者有本质不同:指针传参本质是值传递,被调函数的形参作为局部变量在栈中开辟内存以存放由主调函数放进来的实参值,从而形成实参的一个副本。而引用传递时,被调函数对形参的任何操作都会通过一个间接寻址的方式影响主调函数中的实参变量。
    如果想通过指针参数传递来改变主调函数中的相关变量,可以使用指针的指针或者指针引用。

    五 、指针数组、数组指针、函数指针

    指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在32 位系统下任何类型的指针永远是占4 个字节。它是“储存指针的数组”的简称。
    数组指针:首先它是一个指针,它指向一个数组。在32 位系统下任何类型的指针永远是占4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。它是“指向数组的指针”的简称。
    一个小栗子:

        int arr[] ={1,2,3,4,5};
        int *ptr =(int *)(&arr+1);  //2  5
        int *ptr =(int *)(arr+1);   //2  1
        cout<<*(arr+1)<<" "<<*(ptr-1)<<endl;
    //数组名arr可以作为数组的首地址,而&a是数组的指针。
    //arr和&arr指向的是同一块地址,但他们+1后的效果不同,arr+1是一个元素的内存大小(增加4)
    //而&arr+1增加的是整个数组的内存
    

    数组指针(行指针)

        int a[2][3] = {{1,2,3},{4,5,6}};
        int (*p)[3];
        p = a;
        p++;
        cout<<**p<<endl;  //4  the second rank
    

    六、C++内存布局

    C/C++程序编译时内存分为5大存储区

    (1)栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量值等,其操作方法类似数据结构中的栈。
    (2)堆区(heap):一般由程序员分配释放,与数据结构中的堆毫无关系,分配方式类似于链表。
    (3)全局/静态区(static):全局变量和静态变量的存储是放在一起的,在程序编译时分配。
    (4)文字常量区:存放常量字符串。
    (5)程序代码区:存放函数体(类的成员函数、全局函数)的二进制代码

    int a=0; //全局初始化区
    char *p1; //全局未初始化区
    void main()
    {
    	int b; //栈
    	char s[]="bb"; //栈
    	char *p2; //栈
    	char *p3="123"; //其中,“123\0”常量区,p3在栈区
    	static int c=0; //全局区
    	p1=(char*)malloc(10); //10个字节区域在堆区
    	strcpy(p1,"123"); //"123\0"在常量区,编译器 可能 会优化为和p3的指向同一块区域
    }
    
    C/C++内存分配有三种方式:

    (1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
    (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
    栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
    (3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。
    动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏。
    另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。

    七、堆和栈的区别

    (1)申请方式
    stack:
    由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
    heap:
    需要程序员自己申请,并指明大小,在c中malloc函数

    如p1 = (char *)malloc(10); 
    在C++中用new运算符 
    如p2 = (char *)malloc(10); 
    但是注意p1、p2本身是在栈中的。 
    

    (2)申请后系统的响应
    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
    堆: 首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲 结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
    (3)申请大小的限制及生长方向
    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也可能是1M,它是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小 。
    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
    (4)申请效率的比较:
    栈由系统自动分配,速度较快。但程序员是无法控制的。
    堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
    另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
    (5)堆和栈中的存储内容
    栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

    八、malloc/free 、new/delete区别

    (1)malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
    (2)对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
         由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
    (3)C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
    (4)new可以认为是malloc加构造函数的执行。new出来的指针是直接带类型信息的。而malloc返回的都是void指针。

    九 、常见的内存错误及对策

    (1)内存尚未分配成功,却使用了它;

    解决办法:在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口使用assert(p != NULL) 进行检查,如果是用malloc或者new来申请的,应该用
    if (p == NULL)或者 if (p != NULL)来进行防错处理。
    (2)内存分配虽然成功,但是尚未初始化就引用它;
    错误原因:一是没有初始化的观念,二是误以为内存的缺省初值全为零,导致引用初值错误(如数组)。
    解决办法:内存的缺省初值是什么并没有统一的标准,尽管有些时候为零值,但是宁可信其有,不可信其无,无论以何种方式创建数组,都要赋初值。

    (3)内存分配成功并初始化,但是超过了内存的边界;
    这种问题常出现在数组越界,写程序是要仔细。
    (4)忘记释放内存,造成内存泄露;
    含有这种错误的函数每次被调用都会丢失一块内存,开始时内存充足,看不到错误,但终有一次程序死掉,报告内存耗尽。
    (5)释放了内存却继续使用它
    产生原因:1.程序中的对象调用关系过于复杂,难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
    2.函数return语句写错了,注意不要返回指向“栈内存”的指针或者引用,因为该内存在函数体结束时理论上被自动销毁。
    3.使用free或者delete释放了内存后,没有将指针设置为null,导致产生野指针。
    解决办法:小心仔细。

    内存管理需要遵循的规则

    (1)用malloc 或者 new 申请内存之后,应该立即检查指针值是否为 NULL ,防止使用指针值为NULL的内存;
    (2)不要忘记数组和动态内存赋初值,防止未被初始化的内存作为右值使用;
    (3)避免数组或者指针下标越界,特别要当心“多1”或者“少1”的操作;
    (4)动态内存的申请与释放必须配对,防止内存泄露;
    (5)用free或者delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”;

    十、字节对齐问题

    为什么要使用字节对齐?

    字节对齐是C/C++编译器的一种技术手段,主要是在可接受空间浪费的前提下,尽可能地提高对相同元素过程的快速处理。(比如32位系统,4字节对齐能使CPU访问速度提高)
    需要字节对齐的根本原因在于CPU访问数据的效率问题。

    字节对齐的原则

    (1)结构体中每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会填充字节
    (2)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会填充字节。
    当然这里还要考虑#pragma pack(n)伪指令的影响,如果有取较小值。

    // 用于测试的结构体
    typedef struct MemAlign
    {
    	char a[18];	// 18 bytes
    	double b;	// 08 bytes	
    	char c;		// 01 bytes
    	int d;		// 04 bytes
    	short e;	// 02 bytes
    }MemAlign;
    
    //大小为:48字节
    

    对于union:sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。

    十一、0的比较判断,浮点数存储

    1. int型变量
    if ( n == 0 )
    if ( n != 0 )
    2. bool型
     if (value == 0)
    if (value != 0)
    3. char*if(p == NULL) / if(p != NULL)
    5. 浮点型
    const float EPSINON = 0.0000001;
    if ((x >= - EPSINON) && (x <= EPSINON)
    

    十二、内联函数有什么优点?内联函数和宏定义的区别

    优点:函数会在它所调用的位置上展开。这么做可以消除函数调用和返回所带来的开销(寄存器存储和恢复),而且,由于编译器会把调用函数的代码和函数本身放在一起优化,所以也有进一步优化代码的可能。
    内联函数使用的场合:对于简短的函数并且调用次数比较多的情况,适合使用内联函数。

    内联函数和宏定义区别:

    1)内联函数在编译时展开,而宏在预编译时展开
    2)在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。
    3)内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能。
    4)宏不是函数,而inline是函数

    十三、调用惯例及printf变参实现

    函数在被调用时,函数的调用方和被调用方对于函数时如何调用的必须有一个明确的规定。只有双方同时遵循同样的规定,函数才能够被正确调用。这样的规定被称为:调用惯例。

    函数的调用惯例包含两个方面:

    1.函数参数的传递顺序和方式

    函数的传递有很多种方式,最常见的是通过栈传递。函数的调用方将参数压入栈中,函数自己再从栈中将参数取出。对于有多个参数的函数,调用惯例要规定函数调用方将参数压栈的顺序,是从左往右压栈,还是从右往左压栈。

    2.栈的维护方式

    在函数将参数压入栈中之后,函数体会被调用,此后需要将被压入的参数全部弹出,使得栈在函数调用前后保持一致。这个弹出的工作可以由函数调用方来完成,也可以由函数本身来完成。在不指定调用惯例的情况下,默认采用cdecl惯例。

    在这里插入图片描述

    十四 、覆盖、重载、隐藏的区别

    (1)重载:重载翻译自overload,是指同一可访问区内被声明的几个具有不同参数列表(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
    (2)重写:重写翻译自override,是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致,只有函数体不同。

    1.成员函数被重载的特征:

    (1)相同的范围(在同一个类中);

    (2)函数名字相同;

    (3)参数不同;

    (4)virtual 关键字可有可无。

    2.覆盖是指派生类函数覆盖基类函数,特征是:

    (1)不同的范围(分别位于派生类与基类);

    (2)函数名字相同;

    (3)参数相同;

    (4)基类函数必须有virtual 关键字。

    3.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

    (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。

    (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

    十五、四种强制类型转换

    static_cast( expression )
    1. 用于数值类型之间的转换,也可以用于指针之间的转换,编译时已经确定好,效率高,但需要保证其安全性。
      a) 指针要先转换成void才能继续往下转换。
      b) 在基类和派生类之间进行转换(必须有继承关系的两个类)
    2. 子类对象可以转为基类对象(安全),基类对象不能转为子类对象(可以转换,但不安全,dynamic_cast可以实现安全的向下转换)。
    3. static_cast不能转换掉expression的const、volatile、或者__unaligned属性
    dynamic_cast < type-id> ( expression )
    1. 只能用于对象的指针和引用之间的转换,需要虚函数。
    2. dynamic_cast会检查转换是否会返回一个被请求的有效的完整对象,否则返回NULL;
    3. Type-id必须是类的指针、类的引用或者void *,用于将基类的指针或引用安全地转换成派生类的指针或引用。
    const_cast < type-id> ( expression )

    这个转换类型操纵传递对象的const属性,或者是设置或者是移除。

    reinterpret_cast < type-id> ( expression )

    用在任意指针类型之间的转换;以及指针与足够大的整数类型之间的转换,从整数到指针,无视大小。

    隐式类型转换
    1. 两种常用的实现隐式类类型转换的方式:
      a、使用单参数的构造函数或N个参数中有N-1个是默认参数的构造函数。
      b、使用operator目标类型() const
    2. 避免隐式转换:前面加explicit。
    展开全文
  • 1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL 我在这想看到几件事情: 1) #define 语法的基本知识(例如:不能以分号结束,...
  • linux c/c++ 面试题
  • C/C++面试题

    2012-11-03 21:26:33
    C/C++,计算机网络,通信,电子面试题集锦
  • 大家好,我是 K 哥!最近群里有小伙伴想跳槽,问我有没有常见的 C/C++ 面试题。这不正好,K 哥之前整理了一份 PDF,里面包含了各种经典的 C/C++ 题目,当然更重要的是还附带了非...

    大家好,我是 K 哥!

    最近群里有小伙伴想跳槽,问我有没有常见的 C/C++ 面试题。这不正好,K 哥之前整理了一份 PDF,里面含了各种经典的 C/C++ 题目,当然更重要的是还附带了非常详细的答案。

    K 哥不仅面试之前会反复阅读,平时没事的时候偶尔也会翻看,它帮助我拿到了不少心仪的 offer。也算是压箱底的宝贝儿了,现在免费分享给大伙儿,希望对大家能有所帮助。

    面试前必看必背,有需要的小伙伴们可以自取哈,具体获取方式见文末。

    随便截几张图,一起来看看吧:

    正所谓:“基础不牢,地动山摇”,尤其是常用的知识点,一定要熟练掌握,更要做到知其然知其所以然。

    需要完整 PDF 文档的小伙伴,关注下方公众号并回复「0005」即可自取哈!

    最后,希望这份资料能给大家带来好运,如果拿到大大大 offer,也欢迎回访 K 哥!

    展开全文
  • 大华 华为 诺西 华三 IMB 微软等大公司 面试必考的经典面c/c++面试题 经典面c/c++面试题 应届毕业生 求职宝典
  • 嵌入式C/C++面试题

    千次阅读 2018-11-16 17:14:33
    引用(c++具备)跟原变量实质是同一个东西,只不过是原变量的一个别名,在内存中占同一个存储单元。 指针可以有多级,但引用只能有一级 指针可以为空,但引用值不能为NULL,并且引用在定义的时候必须初始化; ...
    • typedef的使用:

    为复杂的声明定义简单的别名;

    如结构体的定义:

    typedef struct _node
    {
        void *data;
        struct _node *prior;
        struct _node *next; 
    }Node,*PNode;

    这里的PNode表示什么意思?
    PNode a;  相当于  Node *a;  又相当于:struct _node *a;


    • 指针和引用的区别
    1. 指针是一个变量,其存储的是一个地址,指向内存的一个存储单元。引用(c++具备)跟原变量实质是同一个东西,只不过是原变量的一个别名,在内存中占同一个存储单元。
    2. 指针可以有多级,但引用只能有一级
    3. 指针可以为空,但引用值不能为NULL,并且引用在定义的时候必须初始化;
    4. 指针在初始化后可以改变,引用在初始化后不能再改变

    • 指针的理解和作为参数传递时的注意

    指针作为形参时,也是值传递的过程,是将指针存储的地址传递给形参指针,源指针和形参指针是两个指针,不是同一个指针,只是他们指向的地址相同。


    • ->的使用

    为了使用方便和直观,c语言中结构体指针带成员(*p).num可以用p->num来代替。


    • static的作用
    1. 在函数体内声明一个静态变量,在这一函数被调用时维持其值不变。
    2. 在模块内,但在函数体外,一个被声明static的变量可以被模块内的所有函数访问,但不能被模块外其他函数访问。它是一个本地的全局变量。
    3. 在模块内声明的静态的函数只可被这一模块内的其它函数调用。这个函数被限制在声明它的模块的本地范围有效。(也可用于不同模块中同名函数隐藏,排除干扰)

    • .h头文件中

     

    #ifnef	_TEST_H_
    #define  _TEST_H_
    …
    #endif
    

    的作用是:

    避免重复编译,条件编译相关知识。防止该.h文件被重复引用。

    #ifndef x //先测试x是否被宏定义过

    #define x

    程序段1 //如果x没有被宏定义过,定义x,并编译程序 1

    #else

    程序段2 //如果x已经定义过了则编译程序段2的语句,忽视程序段 1

    #endif//终止if


    • include<>和” ”的区别:

    <  >在编译器类库路径下查找文件;

    “  ”先到程序目录中查找该头文件,找不到再到类库路径下查找该文件


    • 实时系统的基本特性:

    在特定的时间内完成特定的任务,可靠,高效。

    高精度计时系统,多级中断机制,实时调度机制。


    • 堆,栈溢出一般是由什么原因导致的
    1. 没有回收垃圾资源
    2. 层次太深的递归调用

    • 不能做switch的参数类型:

    switch的参数不能为实型。(if也一样)


    • 什么函数不能声明为虚函数:

    constructor构造函数


    • 全局变量可不可以被定义在被多个.c文件引用的头文件中?

    可以,在不同的c文件中以static形式来声明同名全局变量。可以在不同的c文件中声明同名的全局变量,但前提条件是只有一个c文件中对次全局变量赋初值


    • 什么是预编译,何时需要预编译:

    预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等就是为编译做的预备工作的阶段,主要处理#开头的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。

             c编译系统提供的预处理功能主要有以下三种:

             宏定义,预处理,条件编译


    • 关键字const

    const意味着只读;

             const  int a;                               //a是一个常型整数

             int    const a;                                //a是一个常型整数

             const int *a;                                 //a是一个指向常型整数的指针(整数不可修改,指针可修改)

             int    *       const a;                      //a是一个指向整数的常型指针(整数可修改,指针不可修改)

             int const *a const;                     //a是一个指向常型整数的常型指针(都不可修改)

             合理的使用关键字const可以使编译器和自然的保护那些不希望被改变的参数,防止其被无意的代码修改


    • 关键字volatile

    volatile提醒编译器它后面所定义的变量随时都可能改变,因此编译后的程序每次需要存取这个变量的时候,都会直接从变量的地址中存取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能会暂时使用寄存器中的值。如果这个变量被别的程序更新了的话,将出现不一致的现象。

    一般来说,volatile用在如下几个地方:

    1. 中断服务程序中修改的供其他程序检测的变量;
    2. 多任务环境下,各任务间共享的标志
    3. 存储器映射的硬件寄存器,因为每次对它的读写都可能由不同意义

    Memory 
         
    有了上面的知识就不难理解Memory修改描述符了,Memory描述符告知GCC

    1. 不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码之前,它前面的指令都执行完毕 
    2. 不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变量值写回内存,如果后面又访问这些变量,需要重新访问内存。

    如果汇编指令修改了内存,但是GCC 本身却察觉不到,因为在输出部分没有描述,此时就需要在修改描述部分增加“memory”,告诉GCC 内存已经被修改,GCC 得知这个信息后,就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内存,如果以后又要使用这些变量再重新读取。 
         
    使用“volatile”也可以达到这个目的,但是我们在每个变量前增加该关键字,不如使用“memory”方便。、


    • 以下三条输出语句分别输出什么?

    char str1[] = "abc"; 
    char str2[] = "abc"; 
    const char str3[] = "abc"; 
    const char str4[] = "abc"; 
    const char* str5 = "abc"; 
    const char* str6 = "abc"; 
    cout << ( str1==str2 ) << endl; //
    输出什么? 
    cout << ( str3==str4 ) << endl; // 输出什么? 
    cout << ( str5==str6 ) << endl; // 输出什么?

    0,0,1

    str1和str2是字符串数组,每个都有自己的存储器,数组名的值则是各存储器的首地址,所以不等,为0

    str3和str4只是按const语义,他们所指向的数据区不能修改。

    str5和str6不是数组而是字符指针,并没有分别存储器,"abc"以常量形式存于静态数据区,指针则指向该区的首地址,所以相等。


    • 以下代码什么问题?

    cout << (true?1:"1") << endl;

    三木运算操作类型不一致


    • 结构体和共同体的区别

    结构体struct:把不同类型的数据组合成一个整体,自定义类型。

    共同体union:使几个不同类型的变量共同占用一段内存。

    structunion都有内存对齐,结构体的内存布局依赖于CPU、操作系统、编译器及编译时的对齐选项。

     

    展开全文
  • 本文主要介绍了C/C++面试题:编写类String的构造、析构和赋值函数,解析构造函数、析构函数和赋值函数的编写方法
  • 腾讯c/c++面试题

    2013-04-17 14:04:34
    腾讯C/C++笔试,对C/C++方向应聘的人笔试很有帮助
  • Google,华为,百度,腾讯,中兴最新C/C++试题 + 一份研发工程师简历模板
  • 华为c/c++面试题

    2010-07-05 19:52:48
    华为c/c++面试题 华为c面试题 华为c++面试题
  • c/c++面试题

    2015-08-04 21:44:33
    面试必备c/c++题型;其中包含50余个c/c++常见的知识要点。对于面试有很大帮助。
  • c/c++面试习题

    2014-05-30 17:25:36
    包含c/c++章节习题(含答案),世界500强面试题(含答案与源代码),
  • 华为c/c++面试题3

    2010-07-05 19:53:46
    华为c/c++面试题 华为c++面试题 华为c面试题
  • 引用是C++中的特性,它的作用也是为了共享内存,某些方面上,它要优于指针,因为引用不存在空值,定义时必须初始化,且无法更改的缘故,使用引用更加安全,且引用不占用内存。 3、指针与引用的区别有哪些? 类型 ...
  • C/C++面试题大全

    2010-05-09 09:44:12
    C-C++面试题大全,一百多道常见面试题,全部有详细解答,无论是对面试者还是初学者都绝对有用。
  • 第二篇 C/C++面试题 第3章 C/C++程序基础 3.1 基本概念 面试题1:什么是C语言语句 面试题2:变量的声明和定义有什么区别 面试题3:下列字符中,哪些不是C语言关键字 面试题4:下列变量定义中,哪些是合法的 面试题5...
  • 多年收集的cc++经典书籍汇总 华为笔试面试题面试宝典 嵌入式 深入理解指针
  • 阿里C/C++面试题

    2019-10-06 00:11:26
    C/C++ 问得比较难得话通常会涉及操作系统,计算机网络底层特别深得知识。 一面(105分钟) 1 自我介绍,工作地选择 2 针对简历,实验室的项目和实习的项目分别进行具体介绍,详细介绍使用了什么技术,介绍完后...
  • c/c++/嵌入式面试题

    2018-03-27 08:03:20
    c/c++/嵌入式面试题 面了两个岗位,一个嵌入式linux_qt,另一个是c++_音视频编解码方面的。 在这里呢,我想说的是,后面我们的同学出去搞开发,要想拿高薪,除了嵌入式linux c/c++ and 单片机 基础知识,得有点自己...
  • C/C++常见面试知识点总结附面试真题----20210529更新

    万次阅读 多人点赞 2018-09-19 22:47:57
    以下内容部分整理自网络,部分为自己面试的真题。 第一部分:计算机基础 1. C/C++内存有哪几种类型? C中,内存分为5个区:堆(malloc)、栈(如局部变量、函数参数)、程序代码区(存放二进制代码)、全局/静态存储区...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 66,821
精华内容 26,728
关键字:

c/c++面试题

c++ 订阅