c和c++的区别
1、C和C++的区别
1)C是⾯向过程的语⾔,是⼀个结构化的语⾔,考虑如何通过⼀个过程对输⼊进⾏处理得到输出;C++是⾯向对象的语⾔,主要特征是“封装、继承和多态”。封装隐藏了实现细节,使得代码模块化;派⽣类可以继承⽗类的数据和⽅法,扩展了已经存在的模块,实现了代码重⽤;多态则是“⼀个接⼝,多种实现”,通过派⽣类重写⽗类的虚函数,实现了接⼝的重⽤。
2)C和C++动态管理内存的⽅法不⼀样,C是使⽤malloc/free,⽽C++除此之外还有new/delete关键字。
3)C++⽀持函数重载,C不⽀持函数重载
4)C++中有引⽤,C中不存在引⽤的概念
2、C++中指针和引⽤的区别
1)指针是⼀个新的变量,存储了另⼀个变量的地址,我们可以通过访问这个地址来修改另⼀个变量;
引⽤只是⼀个别名,还是变量本⾝,对引⽤的任何操作就是对变量本⾝进⾏操作,以达到修改变量的⽬的
2)引⽤只有⼀级,⽽指针可以有多级
3)指针传参的时候,还是值传递,指针本⾝的值不可以修改,需要通过解引⽤才能对指向的对象进⾏操作
引⽤传参的时候,传进来的就是变量本⾝,因此变量可以被修改
3、结构体struct和共同体union(联合)的区别
结构体:将不同类型的数据组合成⼀个整体,是⾃定义类型
共同体:不同类型的⼏个变量共同占⽤⼀段内存
1)结构体中的每个成员都有⾃⼰独⽴的地址,它们是同时存在的;
共同体中的所有成员占⽤同⼀段内存,它们不能同时存在;
2)sizeof(struct)是内存对齐后所有成员长度的总和,sizeof(union)是内存对齐后最长数据成员的长度
4、#define和const的区别
1)#define定义的常量没有类型,所给出的是⼀个⽴即数;const定义的常量有类型名字,存放在静态区域
2)处理阶段不同,#define定义的宏变量在预处理时进⾏替换,可能有多个拷贝,const所定义的变量在编译时确定其值,只有⼀个拷贝。
3)#define定义的常量是不可以⽤指针去指向,const定义的常量可以⽤指针去指向该常量的地址
4)#define可以定义简单的函数,const不可以定义函数
5、重载overload,覆盖override,重写overwrite,这三者之间的区别
1)overload,将语义相近的⼏个函数⽤同⼀个名字表⽰,但是参数和返回值不同,这就是函数重载
特征:相同范围(同⼀个类中)、函数名字相同、参数不同、virtual关键字可有可⽆
2)override,派⽣类覆盖基类的虚函数,实现接⼝的重⽤
特征:不同范围(基类和派⽣类)、函数名字相同、参数相同、基类中必须有virtual关键字(必须是虚函数)
3)overwrite,派⽣类屏蔽了其同名的基类函数
特征:不同范围(基类和派⽣类)、函数名字相同、参数不同或者参数相同且⽆virtual关键字
7、delete和delete[]的区别
delete只会调⽤⼀次析构函数,⽽delete[]会调⽤每个成员的析构函数
⽤new分配的内存⽤delete释放,⽤new[]分配的内存⽤delete[]释放
8、STL库⽤过吗?常见的STL容器有哪些?算法⽤过⼏个?
STL包括两部分内容:容器和算法
容器即存放数据的地⽅,⽐如array, vector,分为两类,序列式容器和关联式容器
序列式容器,其中的元素不⼀定有序,但是都可以被排序,⽐如vector,list,queue,stack,heap, priority-queue, slist
关联式容器,内部结构是⼀个平衡⼆叉树,每个元素都有⼀个键值和⼀个实值,⽐如map, set, hashtable, hash_set
算法有排序,复制等,以及各个容器特定的算法
迭代器是STL的精髓,迭代器提供了⼀种⽅法,使得它能够按照顺序访问某个容器所含的各个元素,但⽆需暴露该容器的内部结构,它将容器和算法分开,让⼆者独⽴设计。
9、const知道吗?解释⼀下其作⽤
const修饰类的成员变量,表⽰常量不可能被修改
const修饰类的成员函数,表⽰该函数不会修改类中的数据成员,不会调⽤其他⾮const的成员函数
10、虚函数是怎么实现的
每⼀个含有虚函数的类都⾄少有有⼀个与之对应的虚函数表,其中存放着该类所有虚函数对应的函数指针(地址),
类的⽰例对象不包含虚函数表,只有虚指针;
派⽣类会⽣成⼀个兼容基类的虚函数表。
11、堆和栈的区别
1)栈 stack 存放函数的参数值、局部变量,由编译器⾃动分配释放
堆heap,是由new分配的内存块,由应⽤程序控制,需要程序员⼿动利⽤delete释放,如果没有,程序结束后,操作系统⾃动回收
2)因为堆的分配需要使⽤频繁的new/delete,造成内存空间的不连续,会有⼤量的碎⽚
3)堆的⽣长空间向上,地址越⼤,栈的⽣长空间向下,地址越⼩
12、关键字static的作⽤
1)函数体内: static 修饰的局部变量作⽤范围为该函数体,不同于auto变量,其内存只被分配⼀次,因此其值在下次调⽤的时候维持了上次的值
2)模块内:static修饰全局变量或全局函数,可以被模块内的所有函数访问,但是不能被模块外的其他函数访问,使⽤范围限制在声明它的模块内
3)类中:修饰成员变量,表⽰该变量属于整个类所有,对类的所有对象只有⼀份拷贝
4)类中:修饰成员函数,表⽰该函数属于整个类所有,不接受this指针,只能访问类中的static成员变量
红⿊树是⼀种特殊的⼆叉查树
1)每个节点或者是⿊⾊,或者是红⾊
2)根节点是⿊⾊
3)每个叶⼦节点(NIL)是⿊⾊。 [注意:这⾥叶⼦节点,是指为空(NIL或NULL)的叶⼦节点!]
4)如果⼀个节点是红⾊的,则它的⼦节点必须是⿊⾊的
5)从⼀个节点到该节点的⼦孙节点的所有路径上包含相同数⽬的⿊节点。
特性4)5)决定了没有⼀条路径会⽐其他路径长出2倍,因此红⿊树是接近平衡的⼆叉树。
15、什么是内存泄漏?⾯对内存泄漏和指针越界,你有哪些⽅法?
动态分配内存所开辟的空间,在使⽤完毕后未⼿动释放,导致⼀直占据该内存,即为内存泄漏。
⽅法:malloc/free要配套,对指针赋值的时候应该注意被赋值的指针是否需要释放;使⽤的时候记得指针的长度,防⽌越界
16、定义和声明的区别
声明是告诉编译器变量的类型和名字,不会为变量分配空间
定义需要分配空间,同⼀个变量可以被声明多次,但是只能被定义⼀次
17、C++⽂件编译与执⾏的四个阶段
1)预处理:根据⽂件中的预处理指令来修改源⽂件的内容
2)编译:编译成汇编代码
3)汇编:把汇编代码翻译成⽬标机器指令
4)链接:链接⽬标代码⽣成可执⾏程序
18、STL中的vector的实现,是怎么扩容的?
vector使⽤的注意点及其原因,频繁对vector调⽤push_back()对性能的影响和原因。
vector就是⼀个动态增长的数组,⾥⾯有⼀个指针指向⼀⽚连续的空间,当空间装不下的时候,会申请⼀⽚更⼤的空间,将原来的数据拷贝过去,并释放原来的旧空间。当删除的时候空间并不会被释放,只是清空了⾥⾯的数据。对⽐array是静态空间⼀旦配置了就不能改变⼤⼩。
vector的动态增加⼤⼩的时候,并不是在原有的空间上持续新的空间(⽆法保证原空间的后⾯还有可供配置的空间),⽽是以原⼤⼩的两倍另外配置⼀块较⼤的空间,然后将原内容拷贝过来,并释放原空间。在VS下是1.5倍扩容,在GCC下是2倍扩容。
在原来空间不够存储新值时,每次调⽤push_back⽅法都会重新分配新的空间以满⾜新数据的添加操作。如果在程序中频繁进⾏这种操作,还是⽐较消耗性能的。
19、STL中unordered_map和map的区别
map是STL中的⼀个关联容器,提供键值对的数据管理。底层通过红⿊树来实现,实际上是⼆叉排序树和⾮严格意义上的⼆叉平衡树。所以在map内部所有的数据都是有序的,且map的查询、插⼊、删除操作的时间复杂度都是O(logN)。unordered_map和map类似,都是存储key-value对,可以通过key快速索引到value,不同的是unordered_map不会根据key 进⾏排序。unordered_map底层是⼀个防冗余的哈希表,存储时根据key的hash值判断元素是否相同,即unoredered_map内部是⽆序的
20、C++的内存管理
在C++中,内存被分成五个区:栈、堆、⾃由存储区、静态存储区、常量区
栈:存放函数的参数和局部变量,编译器⾃动分配和释放
堆:new关键字动态分配的内存,由程序员⼿动进⾏释放,否则程序结束后,由操作系统⾃动进⾏回收
⾃由存储区:由malloc分配的内存,和堆⼗分相似,由对应的free进⾏释放
全局/静态存储区:存放全局变量和静态变量
常量区:存放常量,不允许被修改
21、构造函数为什么⼀般不定义为虚函数?⽽析构函数⼀般写成虚函数的原因?
1、构造函数不能声明为虚函数
1)因为创建⼀个对象时需要确定对象的类型,⽽虚函数是在运⾏时确定其类型的。⽽在构造⼀个对象时,由于对象还未创建成功,编译器⽆法知道对象的实际类型,是类本⾝还是类的派⽣类等等
2)虚函数的调⽤需要虚函数表指针,⽽该指针存放在对象的内存空间中;若构造函数声明为虚函数,
那么由于对象还未创建,还没有内存空间,更没有虚函数表地址⽤来调⽤虚函数即构造函数了
2、析构函数最好声明为虚函数
⾸先析构函数可以为虚函数,当析构⼀个指向派⽣类的基类指针时,最好将基类的析构函数声明为虚函数,否则可以存在内存泄露的问题。
如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除指向派⽣类的基类指针时,只会调⽤基类的析构函数⽽不调⽤派⽣类析构函数,这样就会造成派⽣类对象析构不完全。
22、静态绑定和动态绑定的介绍
静态绑定和动态绑定是C++多态性的⼀种特性
1)对象的静态类型和动态类型
静态类型:对象在声明时采⽤的类型,在编译时确定
动态类型:当前对象所指的类型,在运⾏期决定,对象的动态类型可变,静态类型⽆法更改
2)静态绑定和动态绑定
静态绑定:绑定的是对象的静态类型,函数依赖于对象的静态类型,在编译期确定
动态绑定:绑定的是对象的动态类型,函数依赖于对象的动态类型,在运⾏期确定
只有虚函数才使⽤的是动态绑定,其他的全部是静态绑定
23、引⽤是否能实现动态绑定,为什么引⽤可以实现
可以。因为引⽤(或指针)既可以指向基类对象也可以指向派⽣类对象,这⼀事实是动态绑定的关键。⽤引⽤(或指针)调⽤的虚函数在运⾏时确定,被调⽤的函数是引⽤(或指针)所指的对象的实际类型所定义的。
24、深拷贝和浅拷贝的区别
深拷贝和浅拷贝可以简单的理解为:如果⼀个类拥有资源,当这个类的对象发⽣复制过程的时候,如果资源重新分配了就是深拷贝;反之没有重新分配资源,就是浅拷贝。
25、什么情况下会调⽤拷贝构造函数(三种情况)
系统⾃动⽣成的构造函数:普通构造函数和拷贝构造函数(在没有定义对应的构造函数的时候)
⽣成⼀个实例化的对象会调⽤⼀次普通构造函数,⽽⽤⼀个对象去实例化⼀个新的对象所调⽤的就是拷贝构造函数
调⽤拷贝构造函数的情形:
1)⽤类的⼀个对象去初始化另⼀个对象的时候
2)当函数的参数是类的对象时,就是值传递的时候,如果是引⽤传递则不会调⽤
3)当函数的返回值是类的对象或者引⽤的时候
26、纯虚函数
纯虚函数是只有声明没有实现的虚函数,是对⼦类的约束,是接⼝继承
包含纯虚函数的类是抽象类,它不能被实例化,只有实现了这个纯虚函数的⼦类才能⽣成对象
普通函数是静态编译的,没有运⾏时多态
27、什么是野指针
野指针不是NULL指针,是未初始化或者未清零的指针,它指向的内存地址不是程序员所期望的,可能指向了受限的内存
成因:
1)指针变量没有被初始化
2)指针指向的内存被释放了,但是指针没有置NULL
指针变量本身有地址吗
3)指针超过了变量了的作⽤范围,⽐如b[10],指针b+11
28、线程安全和线程不安全
线程安全就是多线程访问时,采⽤了加锁机制,当⼀个线程访问该类的某个数据时,进⾏保护,其他线程不能进⾏访问直到该线程读取完,其他线程才可以使⽤,不会出现数据不⼀致或者数据污染。
线程不安全就是不提供数据访问保护,有可能多个线程先后更改数据所得到的数据就是脏数据。
29、C++中内存泄漏的⼏种情况
内存泄漏是指⼰动态分配的堆内存由于某种原因程序未释放或⽆法释放,造成系统内存的浪费,导致
程序运⾏速度减慢甚⾄系统崩溃等严重后果。
1)类的构造函数和析构函数中new和delete没有配套
2)在释放对象数组时没有使⽤delete[],使⽤了delete
3)没有将基类的析构函数定义为虚函数,当基类指针指向⼦类对象时,如果基类的析构函数不是virtual,那么⼦类的析构函数将不会被调⽤,⼦类的资源没有正确释放,因此造成内存泄露
4)没有正确的清楚嵌套的对象指针
30、栈溢出的原因以及解决⽅法
1)函数调⽤层次过深,每调⽤⼀次,函数的参数、局部变量等信息就压⼀次栈
2)局部变量体积太⼤。
解决办法⼤致说来也有两种:
1> 增加栈内存的数⽬;增加栈内存⽅法如下,在vc6种依次选择Project->Setting->Link,在Category中选择output,在Reserve中输⼊16进制的栈内存⼤⼩如:0x10000000
2> 使⽤堆内存;具体实现由很多种⽅法可以直接把数组定义改成指针,然后动态申请内存;也可以把局部变量变成全局变量,⼀个偷懒的办法是直接在定义前边加个static,呵呵,直接变成静态变量(实质就是全局变量)
31、C++标准库vector以及迭代器
每种容器类型都定义了⾃⼰的迭代器类型,每种容器都定义了⼀队命名为begin和end的函数,⽤于返回迭代器。
迭代器是容器的精髓,它提供了⼀种⽅法使得它能够按照顺序访问某个容器所含的各个元素,但⽆需暴露该容器的内部结构,它将容器和算法分开,让⼆者独⽴设计。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。