写这篇博客的目的,主要是记录一下在电话面试/面试、笔试中,经常遇到的C/C++相关的出现频率较高的知识点。个人经验、能力有限,热烈欢迎大家提供自身经历过的真实原题及答案。(众人拾柴火焰高!)
【如果你发现有错误,请务必指证!!谢谢!!!】【陆续更新中……】
目录
面试中常见的C++面试题
一、简答题/填空题
1、C和C++的区别(至少说出三点)
2、如何动态申请和删除二维数组
3、const与#define比较,有哪些优点?
4、const的应用和作用有哪些?
5、引用与指针有什么区别?在什么时候需要使用“常引用”?
6、简述数组与指针的区别?
7、内存的分配方式有哪些?并说说delete与 delete [ ] 的区别。
8、extern "C"有什么作用? 为什么要用extern "c" ?
9、 #include 和 #include "filename.h" 有什么区别?
10、总结static的应用和作用?
11、什么是指针?谈谈你对指针的理解?
12、指针的几种典型应用情况?
13、请举例说明函数指针与指针函数的区别?
14、C++函数中值的传递方式有哪几种?
15、对于一个频繁使用的短小函数,在C语言中应用什么实现,在C++中应用什么实现?
16、请说出类中private,protect,public三种访问限制类型的区别?
17、全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
18、一个类的构造函数和析构函数什么时候被调用,是否需要手工调用?
19、虚拟函数与普通成员函数的区别?内联函数和构造函数能否为虚拟函数?
20、构造函数和析构函数的调用顺序? 析构函数为什么要虚拟?
21、类中成员变量怎么进行初始化?
22、堆栈溢出一般是由什么原因导致的?
23、 堆和栈有什么区别?
24、C++是不是类型安全的?
25、构造函数和析构函数是否可以被重载,为什么?
26. 类和对象之间的关系是什么?
27、对对象成员进行初始化的次序是什么?
28、对象间是怎样实现数据的共享的?
29、构造函数的调用顺序是什么?
30、面向对象的程序设计思想是什么?
31、C++特点是什么,如何实现多态?
32、对象都具有的两方面特征是什么?分别是什么含义?
33、什么是类?
34、在类的内部定义成员函数的函数体,这种函数会具备那种属性?
35、成员函数通过什么来区分不同对象的成员数据?为什么它能够区分?
36、 构造函数与普通函数相比在形式上有什么不同?(构造函数的作用,它的声明形式来分析)
37、在类外有什么办法可以访问类的非公有成员?
38、不允许重载的5个运算符是哪些?
39、函数重载是什么意思?它与虚函数的概念有什么区别?
40、什么函数不能声明为虚函数?为什么?
41、struct(结构) 和 union(联合)的区别?
42、class 和 struct 的区别?
43、局部变量和全局变量是否可以同名?
44、assert()的作用?
45、类成员函数的重载、覆盖和隐藏的区别:
46、 动态分配和释放内存的,有什么区别?
47、sizeof与strlen的区别?
48、const char * 、char const *、 char * const 三者的区别
49、C中函数名不相同,C++可以的原因
50、C++如何调用C,编译器角度,extern作用
51、介绍一下快速排序/堆排序原理
52、线程和进程的区别?
53、SendMessage、PostMessage的区别
54、GetMessage()和PeekMessage()有什么区别?
二、编程题/上机题
1、用C/C++实现字符串拷贝,不能直接调用strcpy函数。
2、用C/C++实现strcat的功能。
3、不使用第三个变量,实现两个数的值交换。
当你不去旅行,不去冒险,不去谈一场恋爱,不过没试过的生活,每天只是挂着QQ,刷着微博,逛着淘宝,干着六七十岁都能做的事情,那么你要青春有什么用。
答:(1)C++是面向对象语言,C是面向过程语言。(接下来可能会问:什么是面向对象?什么是面向过程?)
(2)结构:C以结构体struct为核心结构;C++以类class为核心结构。(接下来可能会问:struct与class的区别是什么?)
(3)头文件的调用:C++用< >代替” “代表系统头文件。
(4)输入输出:鉴于C++中以对象作为核心,输入和输出都是在流对象上的操作。
(5)常见风格:C中常用宏定义来进行文本替换,不具有类型安全性;C++中常建议采用常量定义,具有类型安全性。
(6)内存分配:C中使用malloc与free,它们是C标准库函数;而C++中建议使用new/delete进行内存分配,它们是C++运算符。
【面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待。】
【面向对象(Object Oriented)是一种 对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。与面向过程明显的不同就是 封装、继承、类。
面向过程 (Procedure Oriented) 是一种 以过程为中心 的编程思想。这些都是以什么正在发生为主要目标进行编程,不同于面向对象的是谁在受影响。】
关于如何动态申请二维的数组,首先申请一个指针数组,然后令指针数组中的每一个元素都指向一个一维数组,这样二维数组就申请成功了。
//输入row和col的数值
int row, col;
int **two_array = new int*[row];
for (int i = 0; i < row; i++)
{
two_array[i] = new int[col];
}
还有很重要的一点不要忘记,不然就很可能会引起内存泄漏。用new-delete组合动态申请内存,怎么new出来的,就要怎么delete掉。
//内存释放
for (int i = 0; i < row; i++)
{
delete[] two_array[i];
}
delete[]two_array;
答:(1) #define是C语法中定义符号变量的方法,符号常量只是用来表达一个值,在编译阶段符号就被值替换了,它没有类型;
(2) const是C++语法中定义常变量的方法,常变量具有变量特性,它具有类型,内存中存在以它命名的存储单元,可以用sizeof测出长度。
答:(1)定义const常量,要想一个变量在使用的过程中不被改变,可以使用const关键字。在定义该const变量时,必须要对它进行初始化,因为之后不允许再对它赋值了;
const int model = 90; //定义了一个整型常量,其值为90.
int const score = 100; //与上面一样,定义了一个整形常量,其值为100.
const double PI = 3.141592654; //定义了一个双精度型常量PI.
(2)对指针来说,可以指定指针本身为const,即常量指针;也可以指定指针所指的数据为const,即指针所指向的对象是一个常量;或二者同时指定为const,即指针的指向不允许改变,同时其所指向的对象也不允许被改变;【有没有觉得很好玩,扛扛地~】
(a)int *const ptr; 表示指针本身是一个常量,即常量指针,表明其指向不允许被改变。
(b)int const *ptr; 与 const int *ptr; 是一样的,表示ptr这个指针所指向的对象是一个常量,该对象是不允许被改变。
(c)const int *const ptr; 到const的const指针,
表示该指针ptr,所指向的对象是一个常量,该对象不允许被改变;
同时该指针ptr本身也是一个常量,即常量指针,其指向不允许被改变。
(3)在函数声明中,可以用const修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
例如:void add_num(int const x1, int const x2);
(4)在类中定义常量成员函数,防止该函数内部修改成员变量的值;
例如:void fun_name() const;在函数声明的(空)参数表后面出现的const,则表明它是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
例如: const char fun_name() {……}
答:引用与指针的区别:(1)引用必须被初始化,指针可以不用;
(2)引用初始化以后不能改变,指针(常量指针除外)可以改变其所指的对象;
(3)不存在指向空值的引用,但是存在指向空值的指针。
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
答:数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(1)修改内容上的区别;
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; //注意p,指向常量字符串
p[0] = ‘X’; //编译器不能发现该错误,运行时错误
(2)用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。
答:内存的分配方式有三种:
(1)在静态存储区上分配,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。
(2)在栈上分配,函数内的局部变量就是从这分配的,但分配的内存容量有限。
(3)在堆上分配,也称动态分配,如我们用new,malloc分配内存,用delete,free来释放的内存。
delete与 delete [ ]区别:delete只会调用一次析构函数,而delete[ ]会调用每一个成员的析构函数。
答:extern "C" 是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。可以用一句话概括extern "C" 这个声明的真实目的:实现C++与C及其它语言的混合编程。因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extren “c”后,C++就能直接调用C函数了。
答:对于#include
对于#include "filename.h",编译器从用户的工作路径开始搜索 filename.h。
答:(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
答:指针是一个变量,该变量专门存放内存地址;
指针变量的类型取决于其指向的数据类型,在所指数据类型前加*;
指针变量的特点是它可以访问所指向的内存。
答:int *p[n]; ——指针数组,每个元素均为指向整型数据的指针。
int (*)p[n]; ——p为指向一维数组的指针,这个一维数组有n个整型数据。
int *p();——函数带回指针,指针指向返回的值。
int (*)p();——p为指向函数的指针。
答:函数指针是指向一个函数入口的指针;void (*efct) (string); //指向函数的指针
指针函数是函数的返回值是一个指针类型。int* print_name(sting); //指针函数
答:有三种方式:值传递、指针传递、引用传递;
值传递,在传值前,会开辟另外的一个存储空间,将实参的值拷贝到该存储空间里,这就是形参空间,所以对形参进行修改操作不会影响到外面的实参变量的值。从被调用函数的角度来分析,值传递过程是一个单向的,只能从实参传向形参,不能由形参传给实参。
指针传递,是实参将其地址传递给形参,其实形参和实参共享同一块内存空间,修改形参当然会影响到实参,形参是多少,实参就是多少。当函数要实现功能,不仅要将实参值传给形参,形参改变与实参同步的情况,就要使用指针传递。
引用传递,原理和指针传递相同,实参传递给形参的也是实参的地址,两者使用相同的空间,改变一者就会改变另外一个。
答 :c用宏定义,c++用inline,即内联函数。
答:private是私有类型,只有本类中的成员函数和友元函数访问;protect是保护型的,本类和继承类可以访问;public是公有类型,任何类都可以访问。
答:全局变量储存在静态数据区,局部变量存在堆栈中。
答:构造函数在创建类对象的时候被自动调用,析构函数在类对象生命期结束时,由系统自动调用。都不需要手工调用。
答:区别:虚拟函数有virtual关键字,有虚拟指针和虚函数表,虚拟指针就是虚拟函数的接口,而普通成员函数没有。内联函数和构造函数不能为虚拟函数。
答:构造函数的调用顺序:基类构造函数—对象成员构造函数—派生类构造函数;析构函数的调用顺序与构造函数相反。析构函数虚拟是为了防止析构不彻底,造成内存的泄漏。
答:可以通过构造函数的初始化列表或构造函数的函数体实现。
答 :没有回收垃圾资源。
答:栈区(stack)是由编译器自动分配释放,存放函数的参数值,局部变量的值等。
堆(heap)一般由使用者分配释放, 若使用者不释放,程序结束时可能由OS回收 。
答:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。
答:构造函数可以被重载,析构函数不可以被重载。因为构造函数可以有多个且可以带参数,而析构函数只能有一个,且不能带参数。
答:类是对象的抽象,对象是类的实例。
答:它的次序完全不受它们在初始化表中次序的影响,只有成员对象在类中声明的次序来决定的。
答:通过类的静态成员变量来实现对象间的数据共享。静态成员变量占有自己独立的空间不为某个对象所私有。
答:(1)先调用基类构造函数;
(2)按声明顺序初始化数据成员;
(3)最后调用自己的构造函数。
答:把数据结构和对数据结构进行操作的方法封装形成一个个的对象。
答:C++的三大特性是封装、继承和多态,主要是通过继承和虚函数实现多态性。多态的基础是继承,需要虚函数的支持。
那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
答:对象都具有的特征是:静态特征和动态特征。
静态特征是指能描述对象的一些属性(成员变量),动态特征是指对象表现出来的行为(成员函数)。
答:把一些具有共性的对象归类后形成一个集合,也就是所谓的类。
答:这种函数会自动为内联函数,这种函数在函数调用的地方在编译阶段都会进行代码替换。
答:通过this指针来区分,因为它指向对象的首地址来区分的。
答:构造函数是类的一种特殊成员函数,一般情况下,它是专门用来初始化对象成员变量的。
构造函数的名字必须与类名相同,它不具有任何类型,不返回任何值。
答:友元,继承,公有成员函数。
答:
(1) *(成员指针访问运算符号)
(2)::域运算符
(3)sizeof 长度运算符号
(4)?:条件运算符号
(5) .(成员访问符)
答:函数重载是一个同名函数完成不同的功能,编译系统在编译阶段通过函数参数个数、参数类型不同、函数的返回值不同来区分该调用哪一个函数,即实现的是静态的多态性。但是记住:不能仅仅通过函数返回值不同来实现函数重载。而虚函数实现的是在基类中通过使用关键字 virtual 来申明一个函数为虚函数,含义就是该函数的功能可能在将来的派生类中定义或者在基类的基础之上进行扩展,系统只能在运行阶段才能动态决定该调用哪一个函数,所以实现的是动态的多态性。它体现的是一个纵向的概念,也即在基类和派生类间实现。
答:构造函数(constructor)不能声明为虚函数;因为在构造一个对象时,需要知道对象的完整信息,特别是,必须知道对象的实际类型,而虚函数是在运行期间确定实际类型的。
答:(1)结构体和联合体都是由多个不同的数据类型成员组成,但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
(2)对联合的不同成员赋值,将会对其它成员重写,原来成员的值就不存在了,而对于结构的不同成员赋值是互不影响的。
答:struct 的成员默认是公有的,而类的成员默认是私有的。 public,protected,private
答:能,局部变量会屏蔽全局变量。如果要使用全局变量,需要使用 :: 域运算符。
答:assert()是一个调试程序时经常使用的宏,在程序运行时它计算括号内的表达式,如果表达式为FALSE (0), 程序将报告错误,并终止执行。如果表达式不为0,则继续执行后面的语句。这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。
答案:成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
答: malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。在C++中,申请和释放堆中分配的存储空间,分别使用new 和 delete 的两个运算符来完成;c语言提供内存动态分配的函数有:malloc、calloc、realloc,释放的函数为free函数。
char str[20] ="0123456789";
int a=strlen(str); //a=10;strlen 计算字符串的长度,以\0'为字符串结束标记。
int b=sizeof(str); //b=20;sizeof 计算的则是分配的数组str[20] 所占的内存空间的大小,不受里面存储的内容影响。
一、sizeof
sizeof(...)是运算符,在头文件中typedef为unsigned int,其值在编译时就计算好了,参数可以是数组、指针、类型、对象、函数等。
它的功能是:用来计算变量或者对象、类型所占字节的多少。
由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。
sizeof是一个关键字不是函数,发生在编译时刻,所以可以用作常量表达式。
具体而言,当参数分别如下时,sizeof返回的值表示的含义如下:
数组——编译时分配的数组空间大小;
指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,在32位系统是4,在64系统是8);
类型——该类型所占的空间大小;
对象——对象的实际占用空间大小;
函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。
*******************************************************************************************
二、strlen
strlen(...)是函数,要在运行时才能计算,用来计算字符串的长度,遇到第一个NULL('\0')为止,不包括‘\0’。参数必须是字符型指针(char*)。当数组名作为参数传入时,实际上数组就退化成指针了。
它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL,但不包括结束字符(即不包括 null 字符)。
(1)const char* ptr;
定义一个指向字符常量的指针,ptr是一个指向 char* 类型的常量。
*ptr的值为const,不能修改。
ptr的值不为const,可以修改。
(2)char const* ptr
等价const char *ptr
(3)char* const ptr
定义一个指向字符的指针常数,即const指针。
ptr的值为const,不能修改。
*ptr的值不为const,可以修改。
【记住:const在*右边,才修饰指针,指针指向不可改变; const在*左边,修饰指向的对象,指向的对象内容不能改变。】
答:因为C语言不支持函数重载,而C++中函数可以重载。
(1)函数名称相同,返回类型不同,参数相同;
(2)函数名称相同,返回类型不同,参数类型相同个数不同;
(3)函数名称相同,返回类型相同,参数类型相同个数不同;
答:在头文件中用extern "C"声明;extern是C/C++语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
extern 作用1:声明外部变量。
现代编译器一般采用按文件编译的方式,因此在编译时,各个文件中定义的全局变量是互相透明的,也就是说,在编译时,全局变量的可见域限制在文件内部。
extern 作用2:在C++文件中调用C方式编译的函数
C方式编译和C++方式编译,相对于C,C++中新增了诸如重载等新特性。所以全局变量和函数名编译后的命名方式有很大区别。
见此处:数据结构与算法_yishuihanq的博客-CSDN博客 第10项
1、线程的开销比进程小;进程要分配一大部分的内存,而线程只需要分配一部分栈就可以了。
2、一个程序至少有一个进程,一个进程至少有一个线程;一个线程只可以属于一个进程,但一个进程可以有多个线程。
3、进程是资源分配的最小单位,线程是程序执行的最小单位。线程无地址空间,它包括在进程的地址空间里。
4、一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
答:SendMessage可以理解为,SendMessage函数发送消息,等待消息处理完成,SendMessage才返回。是等待窗口处理函数返回后,SendMessage就返回了。
PostMessage可以理解为,PostMessage函数发送消息,不等待消息处理完成,立刻返回。PostMessage只管发送消息,消息有没有被送到则并不关心,只要发送了消息,便立刻返回。
答:两个函数主要有以下两个区别:
(1)GetMessage将等到有合适的消息时才返回,而PeekMessage只是查看消息队列,如果有消息,返回真,没有返回假。
(2)GetMessage会将消息从队列中删除,而PeekMessage可以设置最后一个参数wRemoveMsg来决定是否将消息保留在队列中。
在Windows的内部,GetMessage和PeekMessage执行着相同的代码。而两者最大的不同之处则体现在没有任何消息返回到应用程序的情况下,PeekMessage会返回一个空值到应用程序,GetMessage会在此时让应用程序休眠。
char* My_strcpy( char *pDest, const char *pSrc ) //用C/C++实现字符串拷贝
{
if( nullptr == pDest || nullptr == pSrc )
{
return nullptr;
}
if( pDest == pSrc )
{
return pDest;
}
char *pIter = pDest;
while( ( *pIter++ = *pSrc++ ) !='\0' );
return pDest;
}
int getStrLen(const char* str)//求字符串长度
{
int len = 0;
while( *str ++ != '\0')
{
len ++;
}
return len;
}
int my_strlen(const char *str)//用递归实现
{
assert(str != NULL);
if (*str == '\0')
{
return 0;
}
else
{
return (1 + my_strlen(++str));
}
}
char* f_strcat( char *pDest, const char *pSrc )
{
if( nullptr == pDest || nullptr == pSrc )
{
return nullptr;
}
/*char *pIter = pDest + strlen( pDest );
while( ( *pIter++ = *pSrc++ ) != '\0' );
return pDest;*/
}