c++基本语言

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++基本语言相关的知识,希望对你有一定的参考价值。

C++一些常用的命令的意思,请列举出来

C++的命令有很多不知道楼主指的是那方面的
关于一些基本设置
|:set| :se[t] 显示所有被改动的选项。
参数
'autoindent' 'ai' 根据上一行决定新行的缩进
'cindent' 'cin' 实现 C 程序的缩进
'cinkeys' 'cink' 设置 'cindent' 时启动缩进的键
'cinoptions' 'cino' 设置 'cindent' 时如何缩进
'cinwords' 'cinw' 'si' 和 'cin' 在这些词后加入额外的缩进
'shiftwidth' 'sw' (自动) 缩进使用的步进单位,以空白数目计
'smarttab' 'sta' 插入 <Tab> 时使用 'shiftwidth'

sample
:set cin 实现 C 程序的缩进
:set sw=4 设计(自动) 缩进使用4个空格
:set sta 插入<tab>时使用'shiftwidth'
:set backspace=2 指明在插入模式下可以使用<BS>删除光标前面的字符
:set filetype=cpp 指定文件类型为C++,以便使用高亮关键字显示
:syntax enable 设置高亮关键字显示

怎么让vim在启动时自动设置?
启动vim的时候使用vim -u /root/vimrc.vim
Vim 会在启动的时候执行这个文件里的命令
---------------------
vimrc.vim
---------------------
set cin
set sw=4
set sta
set backspace=2
set filetype=cpp
syntax enable
---------------------
另外,使用:vertion可以查看到系统vimrc配置文件,用户vimrc配置文件等的路径

怎样让vim自动缩排代码?
将光标放在处,按ESC到Normal模式,然后按v再按%就可以把大括号中内容选定,然后按=就可以按照C语

言格式缩排
%命令的作用是跳到匹配处,比如括号之间,C的宏指令#if #else #endif之间。

关于代码打印
如下命令把当前文件转换成 html 格式(带有彩色高亮关键字):
:source $VIMRUNTIME/syntax/2html.vim
Vim 会打开一个新的窗口并显示 HTML 代码。可以将HTML保存以便打印或者放到WEB服务器上
:write main.c.html

关于复制粘贴
使用VIM的内置寄存器进行复制的命令 y
使用VIM的内置寄存器进行粘贴的命令 p
使用剪贴板进行复制的命令 "+y
使用剪贴板进行粘贴的命令 "+p

gdb常用命令
在Linux的c编程中大家经常用gdb调试程序,以下是一些常用的指令
1.break FUNCTION
在某个函数上设置断点。函数重载时,有可能同时在几个重载的函数上设置了断点

break +OFFSET
break -OFFSET
在当前程序运行到的前几行或后几行设置断点

break LINENUM
在行号为LINENUM的行上设置断点

break FILENAME:LINENUM
在文件名为FILENAME的原文件的第LINENUM行设置断点

break FILENAME:FUNCTION
在文件名为FILENAME的FUNCTION函数上设置断点
当你的多个文件中可能含有相同的函数名时必须给出文件名。

break *ADDRESS
在地址ADDRESS上设置断点,这个命令答应你在没有调试信息的程序中设置断点

break
当break命令不包含任何参数时,break命令在当前执行到的程序运行栈中的
下一条指令上设置一个断点。除了栈底以外,这个命令使程序在一旦从当前
函数返回时停止。相似的命令是finish,但finish并不设置断点。这一点在
循环语句中很有用。gdb在恢复执行时,至少执行一条指令。

break ... if COND
这个命令设置一个条件断点,条件由COND指定;在gdb每次执行到此断点时
COND都被计算当COND的值为非零时,程序在断点处停止

ignore BNUM COUNT'
设置第BNUM号断点的被忽略的次数为'COUNT',即断点BNUM再执行到第COUNT+1
次时程序停止

tbreak ARGS 或者简写为 tb
设置断点为只有效一次。ARGS的使用同break中的参量的使用

hbreak ARGS
设置一个由硬件支持的断点。这个命令的主要目的是用于对EPROM/ROM程序的调试
因为这条命令可以在不改变代码的情况下设置断点。这可以同SPARCLite DSU一起
使用。当程序访问某些变量和代码时,DSU将设置“陷井”。注重:你只能一次使用
一个断点,在新设置断点时,先删除原断点

thbreak ARGS'
设置只有一次作用的硬件支持断点

rbreak REGEX
在所有满足表达式REGEX的函数上设置断点。这个命令在所有相匹配的函数上设置无
条件断点,当这个命令完成时显示所有被设置的断点信息。这个命令设置的断点和
break命令设置的没有什么不同。当调试C++程序时这个命令在重载函数上设置断点时
非常有用。

info breakpoints [N]
info break [N]
info watchpoints [N]
显示所有的断点和观察点的设置表,有下列一些列
*Breakpoint Numbers*----断点号
*Type*----断点类型(断点或是观察点)
*Disposition*---显示断点的状态
*Enabled or Disabled*---使能或不使能。'y'表示使能,'n'表示不使能。
*Address*----地址,断点在你程序中的地址(内存地址)
*What*---地址,断点在你程序中的行号。
假如断点是条件断点,此命令还显示断点所需要的条件。
带参数N的'info break'命令只显示由N指定的断点的信息。
此命令还显示断点的运行信息(被执行过几次),这个功能在使用'ignore'
命令时很有用。你可以'ignore'一个断点许多次。使用这个命令可以查看断点
被执行了多少次。这样可以更快的找到错误。

maint info breakpoints
显示所有的断点,无论是你设置的还是gdb自动设置的。
断点的含义:
breakpoint:断点,普通断点
watchpoint:普通观察点
longjmp:内部断点,用于处理'longjmp'调用
longjmp resume:内部断点,设置在'longjmp'调用的目标上
until:'until'命令所使用的内部断点
finish:finish'命令所使用的内部断点

2.watch EXPR
为表达式(变量)expr设置一个观察点。一量表达式值有变化时,马上停住程序。
这个命令使用EXPR作为表达式设置一个观察点。GDB将把表达式加入到程序中并监
视程序的运行,当表达式的值被改变时GDB就使程序停止。这个也可以被用在SPARClite
DSU提供的新的自陷工具中。当程序存取某个地址或某条指令时(这个地址在调试寄
存器中指定),DSU将产生自陷。对于数据地址DSU支持'watch'命令,然而硬件断点寄
存器只能存储两个断点地址,而且断点的类型必须相同。就是两个'rwatch'型断点

或是两个'awatch'型断点。

rwatch EXPR'
设置一个观察点,当EXPR被程序读时,程序被暂停。

awatch EXPR'
设置一个观察点,当EXPR被读出然后被写入时程序被暂停。

info watchpoints
在多线程的程序中,观察点的作用很有限,GDB只能观察在一个线程中的表达式的值
假如你确信表达式只被当前线程所存取,那么使用观察点才有效。GDB不能注重一个
非当前线程对表达式值的改变。

rwatch <expr>
当表达式(变量)expr被读时,停住程序。

awatch <expr>
当表达式(变量)的值被读或被写时,停住程序。

info watchpoints
列出当前所设置了的所有观察点。

3.catch EXCEPTIONS
使用这个命令在一个被激活的异常处理句柄中设置断点。EXCEPTIONS是一个你要抓住
的异常。你一样可以使用'info catch'命令来列出活跃的异常处理句柄。

GDB中对于异常处理由以下情况不能处理:
* 假如你使用一个交互的函数,当函数运行结束时,GDB将象普通情况一样把控制返
回给你。假如在调用中发生了异常,这个函数将继续运行直到碰到一个断点,一个信号
或是退出运行。
* 你不能手工产生一个异常( 即异常只能由程序运行中产生 )
* 你不能手工设置一个异常处理句柄。
有时'catch'命令不一定是调试异常处理的最好的方法。假如你需要知道异常产生的
确切位置,最好在异常处理句柄被调用以前设置一个断点,这样你可以检查栈的内容。
假如你在一个异常处理句柄上设置断点,那么你就不轻易知道异常发生的位置和原因。
要仅仅只在异常处理句柄被唤醒之前设置断点,你必须了解一些语言的实现细节。

3.cont N
第N次经过该断点时才停止程序运行

4.enable 断点编号
恢复暂时失活的断点,要恢复多个编号的断点,可用空格将编号分开

5.disable 断点编号
使断点失效,但是断点还在

6.delete 断点编号或者表达式
删除某断点

7.clear 断点所在行号
清除某断点

8.查看断点列表
info break

9.watch counter>15
当counter>15的时候程序终止

10.当程序崩溃的时候linux会生成一个core文件,可以用
gdb a.out core
where
查看导致崩溃的原因

11.continue
恢复程序运行,直到碰到下一个断点

12.run
程序开始运行,直到碰到断点

13.step
执行一行代码

14.next
和s不同的是他不跟踪到代码的内部,一步一步执行代码

15.直接回车为执行上一个命令

16.print 变量
打印某一变量的值

17.display 变量
每次运行到断点就显示变量的值,用于观察变量的变化

18.set 变量=
在程序执行中重新设置某变量的值

19.printf "%2.2s\n",(char*)0x120100fa0
结果打印出:He

20. 设置gdb的列宽,以下为将屏幕设置为70列
set width 70

21. info args 列出你程序所接受的命令行参数
info registers列出寄存器的状态
info breakpoint列出在程序中设的断点
要获得具体的关于info的信息用help info.

22. set
这个命令用来为你的程序设置一个运行环境(使用一个表达式)。
set prompt $把gdb的提示符设为$.
set args 可指定运行时参数。(如:set args 10 20 30 40 50)
show args 命令可以查看设置好的运行参数。

23. show
show命令用来显示gdb自身的状态。
使用'set'命令可以改变绝大多数由'show'显示的信息
使用show radix命令来显示基数
用不带任何参变量的'set'命令可以显示所有可以设置的变量的值
有三个变量是不可以用'set'命令来设置的:
show version显示gdb的版本号
show copying显示版权信息
show warranty显示担保信息

参考资料:http://www.hua126.com/

参考技术A 随着计算机语言的发展,我们现在编写一个程序越来越容易了。利用一些软件开发工具,往往只要通过鼠标的拖拖点点,计算机就会自动帮你生成许多代码。但在很多时候,计算机的这种能力被滥用了,我们往往只考虑把这个程序搭起来,而不去考虑程序的性能如何,程序是否足够的健壮。而此节课的目的主要是介绍一些编码的经验,让大家编写的程序更加健壮和高性能。

1、Prefer const and inline to #define

在C++编程中应该尽量使用const和inline来代替#define,尽量做到能不用#define就不用。#define常见的用途有“定义常量”以及“定义宏”,但其中存在诸多的弊病。

第一,查错不直观,不利于调试。Define的定义是由预处理程序处理的,作的是完全的文本替换,不做任何的类型检查。在编译器处理阶段,define定义的东西已经被完全替换了,这样在debug的时候就看不到任何的相关信息,即跟踪时不能step into宏。例如,把ASPECT_RATIO用define定义成1.653,编译器就看不到ASPECT_RATIO这个名字了。如果编译器报1.653错,那么就无从知道此1.653来自于何处。在真正编码的时候应该使用如下的语句来定义:

static const double ASPECT_RATIO = 1.653;

第二,没有任何类型信息,不是type safe.因为它是文本级别的替换,这样不利于程序的维护。

第三,define的使用很容易造成污染。比如,如果有两个头文件都定义了ASPECT_RATIO, 而一个CPP文件又同时包含了这两个头文件,那么就会造成冲突。更难查的是另外一种错误,比如有如下的代码:

// in header file def.h
#define Apple 1
#define Orange 2
#define Pineapple 3

// in some cpp file that includes the def.h
enum Colors White, Black, Purple, Orange;

在h文件中Orange被定义成水果的一种,而在。cpp文件中Orange又成为了一种颜色,那么编译器就会把此处的Orange替换成2,编译可能仍然可以通过,程序也能够运行,但是这就成了一个bug,表现出古怪的错误,且很难查错。再比如定义了一个求a与b哪个数大的宏,#define max(a,b) ((a) > (b) ? (a) : (b))

int a = 5, b = 0;
max(++ a, b);
max(++ a, b + 10);

在上面的操作中,max(++ a, b); 语句中a被++了两次,而max(++ a, b + 10); 语句中a只加了一次,这样在程序处理中就很有可能成为一个bug,且此bug也非常的难找。在实际编码时可以使用如下的语句来做:

template<class T>
inline const T&
max(const T& a, const T& b) return a > b ? a : b;

2、Prefer C++-style casts

在程序中经常会需要把一种类型转换成另外一种类型,在C++中应该使用static_cast、const_cast、dynamic_cast、reinterpret_cast关键字来做类型转换。因为这有以下好处,一是其本身就是一种注释,在代码中看到上面这些关键字就可马上知道此处是进行类型转换。二是C语言中类型转换通常是很难进行搜索的,而通过关键字cast则可以很容易的找到程序中出现类型转换的地方了。

3、Distinguish between prefix and postfix forms of increment and decrement operators

通常对于操作系统或编译器自身支持的类型,prefix(前缀,如++i)与postfix(后缀,如i++)的效果是一样的。因为现在的编译器都很聪明,它会自动做优化,这两者的汇编代码是一样的,性能不会有差别。但有时候也会有不同的,如一些重载了操作符的类型。下面是模拟prefix与postfix的操作过程,可以发现在postfix操作中会生成一个临时变量,而这一临时变量是会占用额外的时间和开销的。

// prefix form: increment and fetch
UPInt& UPInt::operator++()

*this += 1; // increment
return *this; // fetch

// postfix form: fetch and increment
const UPInt UPInt::operator++(int)

UPInt oldValue = *this; // fetch
++(*this); // increment
return oldValue; // return what was fetched


一般情况下不需要区分是先++,还是后++,但是我们在编写程序的时候最好能习惯性的将其写成++i的形式,如在使用STL中的iterator时,prefix与postfix会有相当大的性能差异。请不要小看这些细节,实际在编写程序的时候,若不注意具体细节,你会发现程序的性能会非常的低。但要注意,虽然在大多数情况下可以用prefix来代替postfix,但有一种情况例外,那就是有[]操作符时,比如gzArray [++index] 是不等于 gzArray[index++]的。
参考技术B if如果
int定义变量
cin输入
cout输出
参考技术C 。。那么多,你可以看书上的都有的啊

C++常见面试题之基本语言


title: C++常见面试题之基本语言

categories:

-  [C++]
tags:
-  [面试题]
date: 2021/05/11

一、基本语言

1、基本知识

1 说下C++和C的区别

C++是面向对象的语言(有重载、继承和多态三种特性,增加了类型安全功能,比如强制类型转换,支持范式编程,比如模板类、函数模板等)

C是面向过程的结构化编程语言

2 说一下C++中static关键字的作用

  1. 静态全局变量:
    • 全局数据区分配内存,程序运行期间一直存在
    • 未初始化 自动初始化为0
    • 声明它的整个文件可见,文件之外不可见
  2. 静态局部变量:
    • 该对象的声明处时 被首次初始化,即 以后的函数调用不再进行初始化
    • 作用域仍为 局部作用域,当定义它的函数或者语句块结束的时候,作用域结束
  3. 静态函数:
    • 只在声明的文件中可见,不可被其他文件调用
    • 其它文件中可以定义相同名字的函数,不会发生冲突
  4. 静态数据成员:
    • 静态数据成员在程序中只有一个拷贝,由该类型所有对象共享访问,只分配一次内存
    • 存储在全局数据区,静态数据成员定义时要分配空间,所以不能在类声明中定义
  5. 静态成员函数:
    • 它为 类的全部服务而不是为某一个类的具体对象服务
    • 不具有this指针, 无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数

3 说一说C++中四种cast转换

  1. const_cast

    用于将const变量转为非const

  2. static_cast

    用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;

  3. dynamic_cast

    用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。

    向上转换:指的是子类向基类的转换

    向下转换:指的是基类向子类的转换

    它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。

  4. reinterpret_cast

    几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

4 说一下C/C++中指针和引用的区别

1.指针有自己的一块空间,而引用只是一个别名;

2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;

3.指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;

4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;

5.可以有const指针,但是没有const引用;

6.指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;

7.指针可以有多级指针(**p),而引用只有一级;

8.指针和引用使用++运算符的意义不一样;

9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

5 说一下指针和数组的主要区别

指针和数组的主要区别如下:

指针 数组
保存数据的地址 保存数据
间接访问数据,首先获得指针的内容,然后将其作为地址,从该地址中提取数据 直接访问数据,
通常用于动态的数据结构 通常用于固定数目且数据类型相同的元素
通过Malloc分配内存,free释放内存 隐式的分配和删除
通常指向匿名数据,操作匿名函数 自身即为数据名

6 说一下野指针是什么?

野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针。

7 说一下C++中的智能指针

share_ptr:申请堆内存初始化为1,使用时+1,释放时-1,为0时堆内存释放

unique_ptr:指向的堆内存空间的引用计数,都只能为 1,放弃所指空间,堆内存空间释放回收

weak_ptr:需要搭配share_ptr使用,指针被释放所指堆内存的引用计数不会-1

8 说一下智能指针有没有内存泄露的情况

当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏

9 说一下智能指针内存泄露如何解决

为了解决循环引用导致的内存泄漏,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。

10 说一下为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数

1、将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。

2、C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

11 说一下函数指针

定义:函数指针是指向函数的指针变量。

用途:调用函数和做函数的参数,比如回调函数。

12 说一下fork函数

fork( )会创建一个新的进程,它几乎与调用fork( )的进程一模一样

13 说一下C++析构函数的作用

1、析构函数与构造函数对应,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数

2、使用的过程中动态的申请了内存,那么最好显示构造析构函数在销毁类之前,释放掉申请的内存空间,避免内存泄漏

14 说一下静态函数和虚函数的区别

静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销

15 写个函数在main函数执行前先运行

__attribute((constructor)) void before()

{ printf("before main\n");}

16 C++怎么定义常量?常量 存放在内存中的那个位置?

1、常量在C++里的定义就是一个top-level const加上对象类型,常量定义必须初始化。

2、对于局部对象,常量存放在栈区,对于全局对象,常量存放在全局/静态存储区。对于字面值常量,常量存放在常量存储区。

17 const修饰成员函数的目的?

const修饰的成员函数表明函数调用不会对对象做出任何更改

18 如果同时定义了两个函数,一个带const,一个不带,会有问题吗?

不会,这相当于函数的重载。

19 请你来说一说C++中的隐式类型转换?

首先,对于内置类型,低精度的变量给高精度变量赋值会发生隐式类型转换,其次,对于只存在单个参数的构造函数的对象构造来说,函数调用可以直接使用该参数传入,编译器会自动调用其构造函数生成临时对象

20 请你来说一说C++函数栈空间的最大值?

默认是1M,不过可以调整

21 请你来说一说extern“C” ?

C++调用C函数需要extern C,因为C语言没有函数重载。

22 请你说说你了解的RTTI?

运行时类型检查,在C++层面主要体现在dynamic_cast和typeid,VS中虚函数表的-1位置存放了指向type_info的指针。对于存在虚函数的类型,typeid和dynamic_cast都会去查询type_info

23 请你说说虚函数表具体是怎样实现运行时多态的?

24 请你说说C语言是怎么进行函数调用的?

25 请你说说C语言参数压栈顺序?

从右到左

26 请你说说C++如何处理返回值?

生成一个临时变量,把它的引用作为函数参数传入函数内

27 请你回答一下C++中拷贝赋值函数的形参能否进行值传递?

不能。如果是这种情况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝构造函数。。如此循环,无法完成拷贝,栈也会满。

28 请你回答一下malloc与new区别?

1、new分配内存按照数据类型进行分配,malloc分配内存按照指定的大小分配;

2、new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化。

3、new不仅分配一段内存,而且会调用构造函数,malloc不会。

4、new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会。

5、new是一个操作符可以重载,malloc是一个库函数。

6、malloc分配的内存不够的时候,可以用realloc扩容。扩容的原理?new没用这样操作。

7、new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。

8、申请数组时:new[]一次分配所有内存,多次调用构造函数,搭配使用delete[],delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n。

29 请你说一说select  ?

select在使用前,先将需要监控的描述符对应的bit位置1,然后将其传给select,当有任何一个事件发生时,select将会返回所有的描述符,需要在应用程序自己遍历去检查哪个描述符上有事件发生,效率很低,并且其不断在内核态和用户态进行描述符的拷贝,开销很大

30 请你说说fork,wait,exec函数 ?

父进程产生子进程使用fork拷贝出来一个父进程的副本,此时只拷贝了父进程的页表,两个进程都读同一块内存,当有进程写的时候使用写实拷贝机制分配内存,exec函数可以加载一个elf文件去替换父进程,从此父进程和子进程就可以运行不同的程序了。fork从父进程返回子进程的pid,从子进程返回0.调用了wait的父进程将会发生阻塞,直到有子进程状态改变,执行成功返回0,错误返回-1。exec执行成功则子进程从新的程序开始运行,无返回值,执行失败返回-1

2、容器和算法

1 请你来说一下map和set有什么区别,分别又是怎么实现的?

map和set都是C++的关联容器,其底层实现都是红黑树(RB-Tree)

map和set区别在于:

(1)map中的元素是key-value(关键字—值)对:关键字起到索引的作用,值则表示与索引相关联的数据;Set与之相对就是关键字的简单集合,set中每个元素只包含一个关键字。

(2)set的迭代器是const的,不允许修改元素的值;map允许修改value,但不允许修改key。其原因是因为map和set是根据关键字排序来保证其有序性的,如果允许修改key的话,那么首先需要删除该键,然后调节平衡,再插入修改后的键值,调节平衡,如此一来,严重破坏了map和set的结构,导致iterator失效,不知道应该指向改变前的位置,还是指向改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值;而map的迭代器则不允许修改key值,允许修改value值。

(3)map支持下标操作,set不支持下标操作。map可以用key做下标,map的下标运算符[ ]将关键码作为下标去执行查找,如果关键码不存在,则插入一个具有该关键码和mapped_type类型默认值的元素至map中,因此下标运算符[ ]在map应用中需要慎用,const_map不能用,只希望确定某一个关键值是否存在而不希望插入元素时也不应该使用,mapped_type类型没有默认值也不应该使用。如果find能解决需要,尽可能用find。

2 请你来说一说STL迭代器删除元素?

1.对于序列容器vector,deque来说,使用erase(itertor)后,后边的每个元素的迭代器都会失效,但是后边每个元素都会往前移动一个位置,但是erase会返回下一个有效的迭代器;

2.对于关联容器map set来说,使用了erase(iterator)后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素的,不会影响到下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。

3.对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的iterator,因此上面两种正确的方法都可以使用

3 请你说一说STL中map数据存放形式?

红黑树。unordered map底层结构是哈希表

4 请你说说STL中map与unordered_map?

1、Map映射,map 的所有元素都是 pair,同时拥有实值(value)和键值(key)。pair 的第一元素被视为键值,第二元素被视为实值。所有元素都会根据元素的键值自动被排序。不允许键值重复。

底层实现:红黑树

适用场景:有序键值对不重复映射

2、Multimap

多重映射。multimap 的所有元素都是 pair,同时拥有实值(value)和键值(key)。pair 的第一元素被视为键值,第二元素被视为实值。所有元素都会根据元素的键值自动被排序。允许键值重复。

底层实现:红黑树

适用场景:有序键值对可重复映射

5 请你说一说vector和list的区别,应用,越详细越好 ?

1、概念:

1)Vector

连续存储的容器,动态数组,在堆上分配空间

底层实现:数组

两倍容量增长:

vector 增加(插入)新元素时,如果未超过当时的容量,则还有剩余空间,那么直接添加到最后(插入指定位置),然后调整迭代器。

如果没有剩余空间了,则会重新配置原有元素个数的两倍空间,然后将原空间元素通过复制的方式初始化新空间,再向新空间增加元素,最后析构并释放原空间,之前的迭代器会失效。

性能:

访问:O(1)

插入:在最后插入(空间够):很快

在最后插入(空间不够):需要内存申请和释放,以及对之前数据进行拷贝。

在中间插入(空间够):内存拷贝

在中间插入(空间不够):需要内存申请和释放,以及对之前数据进行拷贝。

删除:在最后删除:很快

在中间删除:内存拷贝

适用场景:经常随机访问,且不经常对非尾节点进行插入删除。

2、List

动态链表,在堆上分配空间,每插入一个元数都会分配空间,每删除一个元素都会释放空间。

底层:双向链表

性能:

访问:随机访问性能很差,只能快速访问头尾节点。

插入:很快,一般是常数开销

删除:很快,一般是常数开销

适用场景:经常插入删除大量数据

2、区别:

1)vector底层实现是数组;list是双向链表。

2)vector支持随机访问,list不支持。

3)vector是顺序内存,list不是。

4)vector在中间节点进行插入删除会导致内存拷贝,list不会。

5)vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。

6)vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。

3、应用

vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。

list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。

6 请你来说一下STL中迭代器的作用,有指针为何还要迭代器?

Iterator(迭代器)提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示

Iterator类的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果

7 请你说一说epoll原理?

调用顺序:

int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

首先创建一个epoll对象,然后使用epoll_ctl对这个对象进行操作,把需要监控的描述添加进去,这些描述如将会以epoll_event结构体的形式组成一颗红黑树,接着阻塞在epoll_wait,进入大循环,当某个fd上有事件发生时,内核将会把其对应的结构体放入到一个链表中,返回有事件发生的链表

8 n个整数的无序数组,找到每个元素后面比它大的第一个数,要求时间复杂度为O(N) ?

vector<intfindMax(vector<int>num)
{
    if(num.size()==0)
        return num;
    vector<int>res(num.size());
    int i=0;
    stack<int>s;
    while(i<num.size())
    {
        if(s.empty()||num[s.top()]>=num[i])
        {
         s.push(i++);
        }
        else
        {
            res[s.top()]=num[i];
            s.pop();
     }
    }
    while(!s.empty())
    {
        res[s.top()]=INT_MAX;
        s.pop();
    }
    for(int i=0; i<res.size(); i++)
        cout<<res[i]<<endl;
    return res;
}

9 请你回答一下STL里resize和reserve的区别?

resize():改变当前容器内含有元素的数量(size())

reserve():只是为元素预留出空间而已

3、类和数据抽象

1 请你来说一下C++中类成员的访问权限?

public:共有的

protected:受保护的

private:私有的

类内三者都可以访问,类外只能访问共有public的

2 请你来说一下C++中struct和class的区别?

C++中,可以用struct和class定义类,都可以继承

struct的默认继承权限和默认访问权限是public

class 的默认继承权限和默认访问权限是private

class还可以定义模板类形参,比如template <class T, int i>

3 请你回答一下C++类内可以定义引用数据成员吗?

可以,必须通过成员函数初始化列表初始化。

4 请你回答一下什么是右值引用,跟左值又有什么区别?

右值引用是C++11中引入的新特性 , 它实现了转移语义和精确传递。它的主要目的有两个方面:

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

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

左值和右值的概念:

右值引用和左值引用的区别:

  1. 左值可以寻址,而右值不可以。

  2. 左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。

  3. 左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。

4、编译与底层

1 请你来说一下一个C++源文件从文本到可执行文件经历的过程?

对于C++源文件,从文本到可执行文件一般需要四个过程:

预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件。

编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件

汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件

链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件

2 请你回答一下malloc的原理,另外brk系统调用和mmap系统调用的作用分别是什么?

Malloc函数用于动态分配内存。

malloc其采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。

Malloc采用隐式链表结构将堆区分成连续的、大小不一的块,包含已分配块和未分配块;

Malloc在申请内存时 < 128K  -- 使用系统函数brk在堆区中分配

Malloc在申请内存时 > 128K  -- 使用系统函数mmap在映射区分配

3 请你说一说C++的内存管理是怎样的?

在C++中,虚拟内存分为代码段、数据段、BSS段、堆区、文件映射区以及栈区六部分。

代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。

数据段:存储程序中已初始化的全局变量和静态变量

bss 段:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量。

堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。

映射区:存储动态链接库以及调用mmap函数进行的文件映射

4 请你回答一下如何判断内存泄漏?

内存泄漏通常是由于调用了malloc/new等内存申请的操作,但是缺少了对应的free/delete

1、内存泄漏检查工具Valgrind,mtrace

2、写代码时添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,来判断内存是否泄露

5 请你来说一下什么时候会发生段错误?

1、使用野指针

2、试图修改字符串常量的内容

6 请你来说一下reactor模型组成 ?

reactor模型要求主线程只负责监听文件描述上是否有事件发生,有的话就立即将该事件通知工作线程,除此之外,主线程不做任何其他实质性的工作,读写数据、接受新的连接以及处理客户请求均在工作线程中完成。

7 请自己设计一下如何采用单线程的方式处理高并发?

在单线程模型中,可以采用I/O复用来提高单线程处理多个请求的能力,然后再采用事件驱动模型,基于异步回调来处理事件来

5、C++11

1 请问C++11有哪些新特性?

C++11 最常用的新特性如下:

auto关键字:编译器可以根据初始值自动推导出类型。但是不能用于函数传参以及数组类型的推导

nullptr关键字:nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;而NULL一般被宏定义为0,在遇到重载时可能会出现问题。

智能指针:C++11新增了std::shared_ptr、std::weak_ptr等类型的智能指针,用于解决内存管理的问题。

初始化列表:使用初始化列表来对类进行初始化

右值引用:基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率

atomic原子操作用于多线程资源互斥操作

新增STL容器array以及tuple

利用Lambda表达式,可以方便的定义和创建匿名函数

2 请你详细介绍一下C++11中的可变参数模板、右值引用和lambda这几个新特性

可变参数模板:

C++11的可变参数模板,对参数进行了高度泛化,可以表示任意数目、任意类型的参数,其语法为:在class或typename后面带上省略号”。

右值引用:

利用Lambda表达式,可以方便的定义和创建匿名函数


如果你觉得文章还不错,记得"点赞关注"


以上是关于c++基本语言的主要内容,如果未能解决你的问题,请参考以下文章

C++语言学习基本路线图!

语言学习c++——内置基本类型

C++ 炼气期之基本结构语法中的底层逻辑

C++作为面向对象语言的三个基本特征:封装,继承和————

怎么提高C++编程的基本功?这才是硬实力

Java 基本概念