C++ 面试知识点总结

static 关键字的作用

1 全局静态变量

在全局变量前加上关键字static,全局变量就定义成一个全局静态变量。

静态存储区,在整个程序运行期间一直存在。

初始化:未经初始化的全局变量会被自动初始化为0;

作用域:全局静态变量在声明他的文件之外是不可见的,准确的说是从定义之处开始到结尾;

2 局部静态变量

在局部变量之前加上关键字static 就变成了局部静态变量

3 静态函数

在函数返回类型前加static,函数定义就为静态函数,函数的定义和声明都是extrm,但是静态函数在声明他的文件夹下可以见,其他地方不能使用

C 和 C++ 的区别

C++是面向对象的语言,而C是面向过程的结构化编程语言

语法上:

C++具有重载,继承和多态

C++比C多了很多类型的安全功能,比如强制类型转换

C++支持范式编程,比如模板类,函数模板之类

C++四种cast转换

C++的四种转换是:static_cast dynamic_cast const_cast reinterpret_cast

const_cast 

用于将const变量转换为非const

static_cast 

用于各种隐式转换,比如非const转换为非const  void*指针转换 static_const能用于多态向上转换,如果向下转能成功但是不安全。

dynamic_cast 

用于动态类型转换,只能用于含有虚函数的类,用于类层次的向上和向下转换,只能转指针或者引用。如向下转化时,如果是非法的对于指针返回NULL,对于引用抛出异常

reinterpret_cast

什么都能转 比如int转指针

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

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

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

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

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

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

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

7 指针可以有多级指针(**p)  而引用只能是一级的

8 指针和引用使用++符号意义不一样

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

请你说下你理解的C++中的smart pointer 四个智能指针:

为什么要使用智能指针:

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束的时候忘记释放掉,造成内存泄露。

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

unique_ptr (替换auto_ptr)

unique_ptr 实现独占式拥有或者严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象,它可以避免资源泄露

采用所有权模式,还是上面那个例子

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

unique_ptr<string> p4;                       //#5

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

编译器会认为p4=p3非法,避免了p3不再指向有效数据的问题。因此unique_ptr币auto_ptr更加安全

另外unique_ptr还有更加聪明的地方,当程序试图将一个unique_ptr赋值给另一个时 ,如果源unique_ptr 是个临时右值,编译器允许这么做;如果unique_ptr将存在一段时间,编译器将禁止那样做

unique_ptr<string> pu1(new string ("hello world"));

unique_ptr<string> pu2;

pu2 = pu1;                                      // #1 not allowed

unique_ptr<string> pu3;

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


shared_ptr 

shared_ptr 实现了共享式拥有概念。多个智能指针可以指向相同对象,该对象和其他相关资源会在最后一个引用时被销毁时释放。

从名字share就可以看出资源可以被多个指针共享,他使用记数机制来表名资源被几个指针共享。可以通过成员函数use_count()来查看资源使用者个数,除了可以通过new来构造,还可以通过auto_ptr ,unique_ptr来构造,当我们来调用release()时,当前指针会释放所有权,计数减1 当计数等于0的时候,资源会被释放。

share_ptr是为了解决auto_ptr 在对象所有权上的局限性,在使用引用计数的机制上提供了可以共享所有权的智能指针。

成员函数

use_count 返回引用计数个数

unique返回是否是独占所有权

swap交换两个shared_ptr对象

reset放弃内部对象的所有权或拥有对象的变更,会引起原有对象的引用计数的减少

get返回内部对象指针,由于已经重载()方法,因此和直接使用对象是一样的,如shared_ptr<int> sp(new int(1)); sp与sp.get()等价的

weak_ptr 

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

● 怎么判断一个数是二的倍数,怎么求一个数中有几个1,说一下你的思路并手写代码

1 判断一个数是不是二的倍数,即判断该数二位进制末位是不是0

a % 2 == 0 或者a & 0x0001 == 0。

2、求一个数中1的位数,可以直接逐位除十取余判断:

int fun(long x)

{

int _count = 0;

while(x)

{

if(x % 10== 1)

++_count;

x /= 10;

}

return _count;

}

int main()

{

cout << fun(123321) << endl;

return 0;

}

请回答一下数组和指针的区别

参考答案:

指针:

保存数据地址

间接访问数据,首先获得指针的内容,然后将其作为地址,从该地址中提取数据

通常用于动态的数据结构

通过Malloc分配内存,free释放内存

通常指向匿名数据,操作匿名函数



数组:

保存数据

直接访问数据

通常用于固定的数目且数据类型相同的元素

隐式的分配和删除

自身就为数据名


请回答下什么是野指针

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

请你介绍下C++中的智能指针

智能指针主要管理在堆上分配的内存,他讲普通的的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄露。

C++中常用的智能指针类型为shared_ptr,他采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配,当新增一个时引用计数加1,当过期时引用计数减1。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针一个是类。



● 请你来说一下智能指针的内存泄漏如何解决

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

● 请你回答一下为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数 考点:虚函数 析构函数

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


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

● 请你来说一下函数指针

1 定义

函数指针是指向函数的指针变量

函数指针本身是一个指针变量,该指针变量指向一个具体的函数。这正如用指针变量可指向整型变量,字符型,这里是指向函数。

C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址,有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样

2 用途

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

3 示例

char * fun(char * p)  {…}       // 函数fun

char * (*pf)(char * p);             // 函数指针pf

pf = fun;                        // 函数指针pf指向函数fun

pf(p);                        // 通过函数指针pf调用函数fun


● 请你来说一下fork函数

fork:创建一个和当前进程映像一样的进程可以通过fork()系统调用:

#include <sys/types.h>

#include <unistd.h>

pid_t fork(void);

成功的调用fork 会创建一个新的进程,他几乎与调用fork()的进程一模一样,这两个进程都会继续进行,在子进程中,成功的fork()调用会返回0,在父进程中fork()返回子进程的pid,如果出现错误,fork() 返回一个负值

最常见的fork()用法是创建一个新的进程,然后使用exec()载入二进制映像,替换当前进程的映像,在这种情况下,派生(fork)了新的进程,而这个子进程会执行一个新的二进制可执行文件的映像,这种派生加执行的方式是很常见的。

在早期Unix系统中,创建进程比较原始。当调用fork()时,内核会把所有的内部数据结构赋值一份,复制进程的页表项,然后把父进程的地址空间中的内容逐页复制到子进程地址空间中,但从内核角度来看,逐页的复制方法十分耗时,


● 请你来说一下C++中析构函数的作用

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

析构函数名与类函数名相同,只是在函数名前加一个位取反符~  

例如~stud()以区别构造函数,他不能带任何参数,也没有返回值,(包括void类型)。

只能有一个析构函数不能重载。


如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即自定义了析构函数)编译器总是会为我们合成一个析构函数,并且自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数,他也不进行任何操作。所以许多简单的类中,没有用显示的析构函数。

如果一个类中有指针,并且在使用过程中动态的申请了内存,那么最好显示构造函数在销毁类之前,释放掉申请的内存空间,避免内存泄露。

类析构顺序:1)派生类本身的析构函数;2)对象成员析构函数;3)基类析构函数。

● 请你来说一下静态函数和虚函数的区别

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

● 请你来说一说重载和覆盖

重载:两个函数名相同,但是参数列表不同,(个数,类型)返回值类型没有要求,在同一作用域中;

重写: 子类继承父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写;

● 请你来说一说static关键字

1 加了static关键字的全局变量只能在本文件中使用,

2 static定义的静态局部变量分配在数据段上,普通的局部变量分配在栈上,会因为函数栈帧的释放而被释放掉

3 对一个类中成员变量和成员函数来书,加了static关键字后就必须通过类名才能访问;

● 请你说一说strcpy和strlen

strcpy是字符串拷贝函数,原型:

char *strcpy(char* dest, const char *src);

从src逐字拷贝到dest,直到遇到‘/0’结束,因为没有指定的长度,可能会导致拷贝越界,造成缓冲区溢出漏洞,安全版本是strncpy函数。

strlen是计算字符串长度的函数,返回从开始到‘/0’之间的字符串个数

● 请你说一说你理解的虚函数和多态

多态的感觉主要分为静态多态和动态多态,静态多态主要是重载,在编译时就已经确定了;动态多态是虚函数机制实现的,在运行期间动态绑定。举个列子,一个父类型的函数指向一个子类对象的时候,会调用子类重写过后的函数,在父类中声明为加了virtual关键字,在子类中不加virtual也是虚函数;

虚函数的实现,在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段中。当子类继承父类的时候也会继承虚函数表,当子类重写父类的虚函数的时候,会将继承到的虚函数表的地址替换为重写写的函数地址,使用虚函数会增加访问内存的开销,降低效率。

请你回答下++i和i++的区别

++i先自增1,再返回;

i++是先返回,再自增1

● 请你来写个函数在main函数执行前先运行

__attribute((constructor))void before()

{

    printf("before main\n");

}

● 有段代码写成了下边这样,如果在只修改一个字符的前提下,使代码输出20个hello? for(int i = 0; i < 20; i--) cout << "hello" << endl;

for(int i = 0; i + 20; i--)

cout << "hello"<< endl;

● 以下四行代码的区别是什么? const char * arr = "123"; char * brr = "123"; const char crr[] = "123"; char drr[] = "123";

const char * arr = "123";

字符串123 保存在常量区,const本来是修饰arr指向的值不能通过arr去修改,但是字符串在常量区,本来就不能改变,所以加不加const都一样,

char * brr = "123";

字符串123 保存在常量区,这个arr指针指向的是同一个位置,同样不能通过brr修改123的值

const char crr[] = "123";

这里123是在栈上,但是编译器可能会做某些优化,将其放在常量区;

char drr[] = "123";

字符串保存在栈区 但是可以通过drr来修改

● 请你来说一下C++里是怎么定义常量的?常量存放在内存的哪个位置?

常量在C++里的定义就是一个top-level const 加上对象类型,常量定义必须初始化。对于局部对象,常量存放在栈中,对于全局对象,常量存放在全局/静态存储区。对于字面值常量,常量存放在常量存储区;

● 请你来回答一下const修饰成员函数的目的是什么?

const修饰的成员函数表明函数调用不会对对象做出任何改变,事实上,如果对对象做出更改,就应该为函数加上const限定,这样无论是const对象还是普通对象都可以调用函数。

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

不会 相当于函数的重载

● 请你来说一说隐式类型转换

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

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

默认是1M 不过可以调整

● 请你来说一说extern“C”

首先 new/delete 是C++的关键字 而malloc/free 是C的库函数,后者使用必须指明申请的内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数。

● 请你说说你了解的RTTI

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

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

子类若重写父类的虚函数,虚函数在表中,该函数的地址会被替换,对于存在虚函数的类的对象,在VS中,对象的对象模型的头部存放指向虚函数表的指针,通过该机制实现多态


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

每个函数调用都会分配函数栈,在栈内进行函数还行过程。调用前,先把返回地址压栈,然后把当前函数的esp指针压栈;

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

从右到左

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

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

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

不能,如果是在这样的情况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝函数

如此循环无法完成拷贝,栈也会满

● 请你回答一下malloc与new区别

malloc 需要给定申请内存的大小,返回指针需要强转

new会调用构造函数,不用指定内存大小,返回的指针不用强转

● 请你说一说select

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

● 请你说说fork,wait,exec函数

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

● 请你回答一下静态函数和虚函数的区别

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

● 请你说一说重载和覆盖

重载:两个函数名相同,但是参数列表不同(个数,类型)返回值类型没有要求,在同一作用域中;

重写:子类继承父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容