C++面试

gcc与g++的区别

GCC:GNU Compiler Collection(GUN 编译器集合)

gcc是GCC中的GUN C Compiler(C 编译器)

g++是GCC中的GUN C++ Compiler(C++编译器)

gcc无法进行库文件的连接,即无法编译完成步骤4;而g++则能完整编译出可执行文件。(实质上,g++从步骤1- 3均是调用gcc完成,步骤4连接则由自己完成)

1.对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(cpp的语法规则比c的更强一些)

2.对于 .c和.cpp文件,g++则统一当做cpp文件编译

3.链接可以用 g++ 或者 gcc-lstdc++。因为 gcc 命令不能自动和 C++ 库链接,所以通常使用 g++ 来完成链接。

4.gcc在编译.c文件时,可使用的预定义宏是比较少的,很多都是未定义的。 gcc在编译cpp文件时、g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器),会加入一些额外的宏。

如何打印出当前源文件的文件名以及源文件的当前行号?

cout<<__FILE__<<"\n";

cout<<__LINE__<<"\n";

__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。

变量声明和定义的区别?

为变量分配地址和存储空间称为定义,不分配地址称为声明。一个量变可以再多个地方声明,但只能在一个地方定义。加入extern修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。

strcpy、sprintf、memcpy的区别

strcpy:字符串进行操作,完成从源字符串到目的字符串的拷贝,当源字符串的大小大于目的字符串的最大存储空间后,执行该操作会出现段错误。

sprintf:源对象可以是任意基本类型的数据,目的操作对象是字符串

memcpy:两个对象是两个任意可操作的内存地址

执行效率不同,memcpy最高,strcpy次之,sprintf效率最低

c++ vector的底层实现原理

vector底层是基于动态数组实现

vector中v[i]和v.at(i)的区别

v[5]; //A v.at[5]; //B 如果v非空,A和B没有任何区别。如果v为空,B会抛出std::out_of_range异常。 c++标准不要求vecor<T>::operator[]进行下标越界检查,原因是为了提高效率。如果需要下标越界检查,使用at。但性能会受到影响,因为越界检查增加了性能开销。 

vector扩容原理 

新增元素:vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素;

对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了;

初始时刻vector的capacity为0,塞入第一个元素后capacity增加为1;

不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容

哪些函数不能成为虚函数? 

不能被继承的函数和不能被重写的函数。 

1.普通函数 

2.友元函数 

3.构造函数 

4.内联成员函数 

5.静态成员函数

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

构造对象的时候,必须知道对象的实际类型。而虚函数行为是在运行期间确定实际类型的,在构造对象的时,对象还没有构造成功,编译器无法知道对象的实际类型是该类本身还是其派生类。

虚函数的运行依赖于虚函数指针,而虚函数指针在构造函数中进程初始化,让它指向正确的虚函数表,而在对象构造期间,虚函数指针还未构造完成。

构造函数

构造函数是系统自动调用的(创建对象时,系统自动调用构造函数);转换构造函数是单参数的构造函数,它可以将一个指定类型的数据转换为类的对象 

溢出 

1.栈溢出(栈的大小通常是1M-2M)

栈溢出是指函数中的局部变量造成的溢出(注:函数中形参和函数中的局部变量存放在栈上)

栈溢出包含两种情况:1.分配的的大小超过栈的最大值,2.分配的大小没有超过最大值,但是分配的buff比接收buff小(buff:缓冲区, 它本质上就是一段存储数据的内存)

注意:调试时栈溢出的异常要在函数调用结束后才会检测到,因为栈是在函数结束时才会开始进行出栈操作

栈溢出的解决办法

如果是超过栈的大小时,那就直接换成用堆;如果是不超过栈大小但是分配值小的,就增大分配的大小 

堆栈溢出一般由什么导致?

没有回收垃圾资源

什么是平衡二叉树?

左右子树都是平衡二叉树,且左右子树的深度差的绝对值不大于1

c++ 11 新特性 

1.nullptr :引入了 nullptr 关键字,专门用来区分空指针、 0 。 nullptr 的类型为 nullptr_t ,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较

2.auto 类型推导:最为常见而且显著的例子就是迭代器, auto 不能用于函数传参

3.decltype :从表达式的类型推断出要定义的变量类型

4.Lambda 表达式

(1)

(2) 更重要的应用是其可以用于函数的参数,通过这种方式可以实现回调函数

5.std::array 保存在栈内存中,相比堆内存中的 std::vector ,我们能够灵活的访问这里面的元素,从而获得更高的性能。 std::array 会在编译时创建一个固定大小的数组

6. 智能指针

为什么要使用智能指针?

使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源。

unique_ptr 保证同一时间内只有一个智能指针可以指向该对象。

unique_ptr<string> p3 (new string ("auto")); //#4

unique_ptr<string> p4 ; //#5

p4 = p3;// 此时会报错!!

程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做 :

unique_ptr<string> pu3;

pu3 = unique_ptr<string>(new string ("You"));// #2 allowed

shared_ptr 多个智能指针可以指向相同对象,

use_count()来查看资源的所有者个数

调用 release()时,当前指针会释放资源所有权,计数减一

unique 返回是否是独占所有权( use_count 为 1)

get 返回内部对象 ( 指针 )

weak_ptr 是一种不控制对象生命周期的智能指针 , 它指向一个 shared_ptr 管理的对象 . 它只可以从一个shared_ptr 或另一个 weak_ptr 对象构造 ,weak_ptr 是用来解决 shared_ptr 相互引用时的死锁问题 .shared_ptr 可以直接赋值给它,它可以通过调用 lock 函数来转换成 shared_ptr 。

静态函数和虚函数的区别

静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定(编译器在编译阶段不知道要调用哪个方法)。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销。

基类为什么需要虚析构函数?

防止内存泄漏。假如没有虚析构函数,释放一个由基类指针指向的派生类对象时,不会触发动态绑定,

则只会调用基类的析构函数,不会调用派生类的。派生类中申请的空间则得不到释放导致内存泄漏。C++ 默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。

C++类内可以定义引用数据成员吗?

可以, 1. 必须通过成员函数初始化列表初始化。 2. 初始化后的引用变量所占用的内存空间和普通变量相同

C++源文件从文本到可执行文件经历的过程?

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

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

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

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

Linux中如何创建进程以及创建进程后如何区分子进程?

   A:使用fork()调用创建子进程,fork()调用返回两个值,大于0的表示父进程,等于0的表示子进程。

 fork创建的子进程继承了父进程哪些内容

    A:子进程继承了父进程的地址空间,打开的文件描述符等。

fork创建的子进程继承了父进程打开的文件描述符,如何让这种继承不发生

    A:可以在打开文件的时候,设置FD_CLOSEXEC标志位,如果文件描述符中这个标志位置位,那么调用exec时会自动关闭对应的文件。

孤儿进程

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init 进程 ( 进程号为 1) 所收养,并由 init 进程对它们完成状态收集工作。

僵尸进程

一个进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。僵尸进程是一个进程必然会经过的过程:这是每个子进程在结束时都要经过的阶段。

危害:

如果进程不调用 wait / waitpid 的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。

brk 系统调用和 mmap 系统调用的作用

Malloc 在申请内存时,一般会通过 brk 或者 mmap 系统调用进行申请。其中当申请内存小于 128K 时,

会使用系统函数 brk 在堆区中分配;而当申请内存大于 128K 时,会使用系统函数 mmap 在映射区分配。

内存泄漏的分类

1. 堆内存泄漏 ( Heap leak )。对内存指的是程序运行中根据需要分配通过 malloc,realloc new 等从堆

中分配的一块内存,再是完成后必须通过调用对应的 free 或者 delete 删掉。如果程序的设计的错误导致

这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生 Heap Leak.

2. 系统资源泄露( Resource Leak )。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET

等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。

解决内存泄露的方法:智能指针和 vilgrind

32 位,64 位系统中,各种常用内置数据类型占用的字节数?

C++不能重载的操作符 

原码,反码,补码 

正数:原码、反码和补码都一样; 

负数: 

原码转换为反码:符号位不变,数值位分别“按位取反” 

原码转换为补码:符号位不变,数值位按位取反,末位再加1 

补码转换为原码:符号位不变,数值位按位取反,末位再加1 

派生类和基类

1.派生类对象赋给基类对象;2.派生类对象可以初始化基类引用;3.对生类对象的地址可以赋给基类指针 

c++多态的实现

A:多态分为两种,一种是运行时的多态,一种是编译时的多态。前者称为动态绑定,后者称为静态绑定。动态绑定时由虚函数来实现,静态绑定是由函数重载来实现。

深拷贝和浅拷贝

【浅拷贝】是增加了一个指针,指向原来已经存在的内存。

【深拷贝】是增加了一个指针,并新开辟了一块空间,让指针指向这块新开辟的空间。 

浅拷贝存在的问题:在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误 

编译系统在我们没有自己定义拷贝构造函数时,会在拷贝对象时调用默认拷贝构造函数,进行的是浅拷贝

在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。 

网络字节序是大端序还是小端序?

大端序

 

例1

struct fields{ 

unsigned short a:4; 

unsigned short b :5; 

unisgned short c:7;} test 

int i=*((short *)&test) 取从地址&test开始两字节(short占两字节)(一字节=8位)的内容转化为short型数据

变量后面加:然后加数字表示位域,也就是按位存放,这是计算机为节省空间的一种方式

例2

1.int *p1=new int[10],2.int *p1=new int[10]()  1申请的空间里是随机值,2申请的空间里面的值已初始化

例3

typedef struct list_t{ 

struct list_t *next; 

char data[0]; 

}list_t 

data[0]不占空间,方便管理内存缓冲区,减少内存碎片化

例4

char *p1="12345"; char *p2=(char *)malloc(10); 

p1,p2都存在栈中(指针本身存在于栈中),p1指向的对象存在于常量区,p2指向的对象存在于堆中,堆和栈在内存中的生长方向相反,堆的内存地址向上,栈的内存地址向下 

例5:怎么做到对象不再栈上创建

编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。因此,将析构函数设为私有,类对象就无法建立在栈上了 

例6

typedef 在编译的时候处理,define在预编译的时候处理

例7

typedef char *String_t 和 #define String_d char * 同时定义多个变量时: 

String_t a,b 

String_d c,d a,b,c是char* d是char

例8

结构体及结构体变量的初始化:传统的依次初始化每一个成员;默认初始化第一个成员,不能这样初始化,eg:p={,"age"}

例9

'\0':ASCII为0的字符,在内存中实际表示为0

例10

一般情况下:小循环放外面,大循环放里面,但是如果循环里面是数组,如果数组较长,跨页较多,会导致速度慢 

例11

空类:编译器会自动生成:默认构造函数、析构函数、拷贝构造函数、赋值函数

例12

char str[][10]={"123","456"},*p=str; p指向这个数组的首指针,p+10指向数组第二维的首指针

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值