一C++基础面试题

Posted

tags:

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


一、C++基础

(1)C和C++区别?

1. C是面向过程的;C++是面向对象的
2. C++引入new/delete运算符,取代了C中的malloc/free库函数
3. C++引入引用的概念;C中没有
4. C++引入类的概念;C中没有
5. C++引入函数重载的特性;C中没有

(2)a和&a的区别?

int a[10];
int(*p)[10] = &a;

a是数组名,是数组首元素地址,+1表示下一个元素的地址;
如果a的值是0x00000001,+1表示0x00000005.
*(a+1) = a[1];

&a表示取地址

(3)static关键字有什么作用?

1. 修饰局部变量时,使得该变量在静态存储区分配内存;只能在首次函数调用中进行首次初始化,之后的函数调用不再进行初始化;其生命周期与程序相同,但其作用域为局部作用域,并不能一直被访问。

2. 修饰全局变量时,使得该变量在静态存储区分配内存;在声明该变量的整个文件中都是可见的,而在文件外是不可见的。

3. 修饰函数时,在声明改函数的整个文件中都是可见的,而在文件外是不可见的,从而可以在多人协作时避免同名函数的冲突。

4. 修饰成员变量时,所有的对象都只维持一份拷贝,可以实现不同对象间的数据共享;不需要实例化对象即可访问;不能在类内部初始化,一般在类外部初始化,并且初始化时不加static;

5. 修饰成员函数时,该函数不接受this指针,只能访问类的静态成员;不需要实例化对象即可访问。
问:C语言的关键字static和C++的关键字static有什么区别?
答:在C中static用来修饰局部静态变量和外部静态变量、函数。而C++中除了上述功能外,还用来定义类的成员变量和函数。即静态成员变量和静态成员函数。
「注意」:编程时 static 的记忆性,和全局性的特点可以让在不同时期调用的函数进行通信,传递信息,而 C++的静态成员则可以在多个对象实例间进行通信,传递信息。

(4)#define和const区别?

1. 编译器处理方式不同
#define宏是在预处理阶段展开,不能对宏定义进行调试;而const常量是在编译阶段使用

2. 类型和安全检查不同
#define宏没有类型,不做任何类型检查,仅仅是代码展开,可能产生边际效应等错误;而const常量有具体类型,在编译阶段会执行类型检查

3. 存储方式不同
#define宏仅仅是代码展开,在多个地方进行字符串替换,不会分配内存,存储于程序的代码段中;而const常量会分配内存,但只维持一份拷贝,存储于程序的数据段中。

4. 定义域不同
#define宏不受定义域限制;而const常量只能在定义域内有效

(5)typedef和define的区别?

1. 用法不同
typedef用来定义一种数据类型的别名,增加程序的可读性;define主要用来定义常量,以及书写复杂使用频繁的宏

2. 执行时间不同
typedef有作用域限定;define不受作用域约束,只要是在define声明后的引用都是正确的

//3. 对指针的操作不同
//typedef和define定义指针时有很大的区别

注意:typedef定义是语句,因为句尾要加上分号;而define不是语句,不需要加分号。

(6)变量的声明和定义的区别?

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

说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才会初始化,分配内存空间,如外部变量。
int main() 

extern int A;
//这是个声明而不是定义,声明A是一个已经定义了的外部变量
//注意:声明外部变量时可以把变量类型去掉如:extern A;
dosth(); //执行函数

int A; //是定义,定义了A为整型的外部变量

(7)#indef、#else、#endif、#ifndef的作用?

利用#ifdef、#endif将某程序功能模块包括进去,以向特定用户提供该功能。在不需要时用户可轻易将其屏蔽。

#ifdef MATH
#include "math.c"
#endif
虽然不用条件编译命令而直接用if语句也能达到要求,但那样做目标程序长(因为所有语句都编译),运行时间长(因为在程序运行时间对if语句进行测试)。
而采用条件编译,可以减少被编译的语句,从而减少目标程序的长度,减少运行时间。

(8)结构体可以直接赋值吗?

声明时可以直接初始化,同一结构体的不同对象之间也可以直接赋值,但是当结构体中含有指针“成员”时一定要小心。

「注意」:当有多个指针指向同一段内存时,某个指针释放这段内存可能会导致其他指针的非法操作。因此在释放前一定要确保其他指针不再使用这段内存空间。

(9)sizeof和strlen的区别?

1. sizeof是一个操作符,strlen是库函数
2. sizeof的参数可以是数据的类型,也可以是变量;strlen只能以\\0结尾的字符串作参数。
3. 编译器在编译时就计算出了sizeof的结果;strlen函数必须在运行时才能计算出来。并且sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度。
4. 数组做sizeof的参数不退化,传递给strlen就退化为指针了。
int main() 
char arr[10] = "123456789";
cout << sizeof(arr) << endl; //10
cout << strlen(arr) << endl; //9
return 0;

(9)volatile有什么作用?

易变关键字,告诉编译器不要对这个变量进行优化,也就是说,每次都要去绝对地址上取值,而不能从寄存器上取值。
(1)并行设备的硬件寄存器(如状态寄存器)。
(2)一个中断服务子程序中访问到的变量
(3)多线程应用中被多个任务共享的变量。


问:一个参数可以即是const又是volatile吗?
答:可以,用const和volatile同时修饰变量,表示这个变量在程序内部是只读的,不能改变的,只在程序外部条件变化下改变,并且编译器不会优化这个变量。每次使用这个变量时,都要小心地去内存读取这个变量的值,而不是去寄存器读取它的备份。
注意:在此一定要注意const的意思,const只是不允许程序中的代码改变某一变量,其在编译期发挥作用,它并没有实际地禁止某段内存的读写特性。

(10)全局变量和局部变量有什么区别?操作系统和编译器是怎么知道的?

1. 全局变量是整个程序都可访问的变量,生存期在整个程序从运行到结束;
局部变量存在于模块(子程序,函数)中,只有在其局部作用域内可以访问。局部变量消失,所占内存释放。

操作系统和编译器是通过内存分配的位置来知道的,全局变量分配在全局区,局部变量分配值栈区。

(13)简述strcpy、sprintf 与memcpy 的区别?

1. 操作对象不同。strcpy的两个操作对象均为字符串;sprintf的源操作对象可以是多种数据类型,目的操作对象是字符串;memcpy的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
2. 执行效率不同。memory效率最高,strcpy次之,sprintf最低
3. 实现功能不同。strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型格式到字符串的转化,memcpy主要是内存块间的拷贝。

「注意」:strcpy、sprintf 与memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来 选择合适的函数实现拷贝功能。
char *strcpy(char *dest, const char *src);  //src赋值给dest

int sprintf( char *buffer, const char *format [, argument,...] );

void* memcpy(void* destination, const void* source, size_t num);
内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。


(14)内联函数inline

问:对于一个频繁使用的短小函数,应该使用什么来实现?有什么优缺点?
应该使用inline内联函数,即编译器将inline内联函数内的代码替换到函数被调用的地方。
优点:
* 在内联函数被调用的地方进行代码展开,省去了函数调用的时间,从而提高程序运行效率
* 相比于宏函数,内联函数在代码展开时,编译器会进行语法安全检查或数据类型转换,使用更加安全
缺点:
* 代码膨胀,产生更多的开销
* 如果内联函数内代码块的执行时间比调用时间长很多,那么效率的提升并没有那么大
* 如果修改内联函数,那么所有调用该函数的代码文件都需要重新编译

注意:内联声明知识建议,是否内联展开有编译器自己决定

(16)右值引用有什么作用?

右值引用的主要目的是为了实现转移语义和完美转发,消除两个对象交互时不必要的对象拷贝,也能够更加简洁明确地定义泛型函数。

(19)class和struct的区别?

1. 结构体不能有函数成员;类可以有
2. 结构体访问权限默认为public,类的访问权限默认为private
3. 结构体没有继承关系,类有丰富的继承关系。
4. class可以定义类模板,struct没有

(20)说一说extern “C”

extern "C"的主要作用是为了能够正确实现C++代码调用其他C语言代码。实现C与C++的兼容。

加上extern "C"后,会指示编译器这部分代码按C语言的方式进行编译。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

(21)拷贝初始化、直接初始化区别?初始化和赋值区别?

拷贝初始化和直接初始化调用的构造函数是不一样的。

赋值操作是在两个已经存在的对象间进行的;而初始化是要创建一个新的对象,并且其初值来源于另一个已存在的对象。
编译器会区别这两种情况,赋值的时候调用重载的赋值运算符,初始化的时候调用拷贝构造函数。如果类中没有拷贝构造函数,则编译器会提供一个默认的。

(22)设置地址为0x67a9 的整型变量的值为0xaa66

int *ptr; 
ptr = (int *)0x67a9;
*ptr = 0xaa66;

「注意」:这道题就是强制类型转换的典型例子,绝大部份情况下,地址长度和整型数据的长度是一样的(此时的整型指的是 long), 即一个整型数据可以强制转换成地址指针类型,只要有意义即可。

(23)请解析((void ()( ) )0)( )的含义

一、C++基础面试题_编译器

2. 指针

(1)智能指针

智能指针的作用是:能够处理内存泄漏问题和空悬指针问题。

new申请的空间在函数结束时忘记释放,会造成内存泄漏。使用智能指针可以避免这个问题,因为智能指针是一个类,当超出了类的作用域时,类会自动调用析构函数释放内存,不需要手动释放内存空间。

四种智能指针:auto_ptr、unique_ptr、shared_ptr、weak_ptr

1. auto_ptr
实现独占式拥有的概念,同一时间只能有一个智能指针可以指向该对象,但auto_ptr在C++11中被摒弃,其问题在于:
* 对象所有权的转移。比如在函数传参过程中,对象所有权不会返还,从而存在潜在的内存奔溃问题。
* 不能指向数组,也不能作为STL容器的成员。
auto_ptr<string> p1 (new string ("aaaaaa"));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错
此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr存在潜在的内存奔溃问题。

2. unique_ptr
实现独占式拥有的概念,同一时间只能有一个智能指针可以指向该对象,因为无法进行拷贝构造和拷贝赋值,但是可以进行移动构造和移动赋值。
unique_ptr<string> p1 (new string ("aaa"));
unique_ptr<string> p2;
p2 = p1; //报错


3. shared_ptr
实现共享式拥有的概念,即多个智能指针可以在指向相同的对象,该对象及相关资源会在其所指对象不再使用之后,自动释放与对象相关的资源。

4.weak_ptr
weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象。weak_ptr只是提供了对管理对象的一个访问手段,weak_ptr设计的目的是为了配合shared_ptr而引入的一种智能指针来协助shared_ptr工作。
解决shared_ptr相互引用时,两个指针的引用计数永远不会下降为0,从而导致死锁的问题。
weak_ptr是对对象的一种弱引用,可以绑定到shared_ptr,但不会增加对象的引用计数。
问:shared_ptr是如何实现的?
1. 构造函数中计数初始化为1
2. 拷贝构造函数中计数值加1
3. 赋值运算符中,左边的对象引用计数减1,右边的对象引用计数加1
4. 析构函数中引用计数减1
5. 在赋值运算符和析构函数中,如果减1后为0,则调用delete释放对象。

(2)空悬指针、野指针的区别?

空悬指针:
在delete之后,指针就变成了空悬指针。即指向一块曾经保存数据对象但现在已经无效的内存的指针。
可以在delete之后将nullptr赋予指针,这样就清楚地指出指针不指向任何对象。

野指针:未初始化的指针称为野指针

(3)如何避免“野指针”?

1. 指针声明式记得初始化,//可以是具体的地址值,也可让它指向NULL
2. 指针指向的内存空间被释放后,指针应该指向NULL
3. 指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间让指针指向NULL。

(4)句柄和指针的区别和联系?

句柄和指针其实是两个截然不同的概念。
Windows系统用句柄标记系统资源,隐藏系统的信息。你只要知道有这个东西,然后去调用就行了,它是32bit的unit。
指针则标记某个物理内存地址,两者是不同的概念。

(5)指针和引用的区别?

1. 指针本身是个对象,有自己的一块空间;引用只是一个别名
2. 使用sizeof看指针的大小是4;引用的大小看引用对象的大小
3. 有const指针;没有const引用
4. 引用必须初始化
5. 有多级指针(指针的指针);而没有多级引用
6. 指针和引用++的意义不一样
7. 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏

3. const

  • const对象一旦创建后其值就不能再改变,所以const对象必须初始化。
  • 默认状态下,const对象仅在文件内有效。要多个文件共享用extern const4

(1)指针常量与常量指针的区别?顶层const和底层const区别?

面经说法:

1. 在指针常量中,指针自身的值是一个常量,不可改变,始终指向同一个地址。在定义的同时必须初始化。
在常量指针中,指针指向的内容是不可改变的,指针看起来好像指向了一个常量。
2. 指针常量强调的是指针的不可改变性;常量指针强调的是指针指向的对象的不可改变性。

指针常量:指针类型的常量(int *const p)
常量指针:指向常量的指针(const int * p, int const *p)

书上说法:C++ primer5第56页

指向常量的指针(pointer to const):不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。

常量指针(const pointer):必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的地址)就不能再改变了。

​const int * P​​:指向常量的指针,底层const

​int * const p​​:常量指针,顶层const,不能修改p的值,即不能修改地址

​const int * const p​​:指向常量的常量指针

用​​顶层const​​表示指针本身是个常量

用​​底层const​​表示指针所指的对象是一个常量


以上是关于一C++基础面试题的主要内容,如果未能解决你的问题,请参考以下文章

C++面试题

C++面试题(算法基础-排序算法)

秋招已过,各大厂的面试题分享一波 附C++实现

C++面试题(基础)

C++基础知识点集合

C++基础知识点集合