C++面试八股文快问快答の基础篇
文章目录
- 基础篇
- 变量的声明和定义有什么区别
- 简述#ifdef、#else、#endif和#ifndef的作用
- 写出int 、bool、 float 、指针变量与 “零值”比较的if 语句
- 结构体可以直接赋值吗
- sizeof 和strlen 的区别
- sizeof求类型大小
- C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
- C 语言的 malloc 和 C++ 中的 new 有什么区别
- 写一个 “标准”宏MIN
- ++i和i++的区别
- volatile有什么作用
- C++中volatile的作用
- 一个参数可以既是const又是volatile吗
- a 和&a 有什么区别
- 用C 编写一个死循环程序
- 结构体内存对齐问题
- 全局变量和局部变量有什么区别?实怎么实现的?操作系统和编译器是怎么知道的?
- 简述C、C++程序编译的内存分配情况
- 简述strcpy、sprintf 与memcpy 的区别
- 请解析((void ()( ) )0)( )的含义
- typedef 和define 有什么区别
- 指针常量与常量指针区别
- 简述队列和栈的异同
- 设置地址为0x67a9 的整型变量的值为0xaa66
- 编码实现字符串转化为数字
- C语言的结构体和C++的有什么区别
- 简述指针常量与常量指针的区别
- 如何避免“野指针”
- 句柄和指针的区别和联系是什么?
- new/delete与malloc/free的区别是什么
- 说一说extern“C”
- 请你来说一下C++中struct和class的区别
- C++类内可以定义引用数据成员吗?
- C++中类成员的访问权限
- 什么是右值引用,跟左值又有什么区别?
- 面向对象的三大特征
- 说一说c++中四种cast转换
- C++的空类有哪些成员函数
- 对c++中的smart pointer四个智能指针:shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解
- 说说强制类型转换运算符
- 谈谈你对拷贝构造函数和赋值运算符的认识
- 在C++中,使用malloc申请的内存能否通过delete释放?使用new申请的内存能否用free?
- 用C++设计一个不能被继承的类
- C++自己实现一个String类
- 访问基类的私有虚函数
- 对虚函数和多态的理解
- 简述类成员函数的重写、重载和隐藏的区别
- 链表和数组有什么区别
- 用两个栈实现一个队列的功能
- 共享数据的保护
- 程序内存分配方式以及它们的区别
- 堆区(heap)
- explicit
- mutable关键字
- 用const修饰函数的返回值
- 宏、const和enum
- static的生存期
- 全局变量和static变量的区别
- 为什么栈要比堆速度要快
- c++ 析构函数调用时间
- 静态绑定 动态绑定 (也叫动态连编,静态连编)
- C语言的指针和c++的引用有什么区别?
- 请你说说C语言是怎么进行函数调用的
- C++中拷贝赋值函数的形参能否进行值传递?
- include头文件的顺序以及双引号””和尖括号<>的区别
- 一个C++源文件从文本到可执行文件经历的过程
- 内存泄漏原因和判断方法
- 段错误的产生原因
- C++ 函数调用过程
- 如何调试c++ 多线程程序?
- 面向对象和面向过程的区别
- 关于引用赋值的多态:
- 模板的声明和实现不能分开的原因
- C++类中引用成员和常量成员的初始化(初始化列表)
- memset为int型数组初始化问题
- 编译器对 inline 函数的处理步骤
- 虚函数(virtual)可以是内联函数(inline)吗?
- 静态库和动态库比较
- 虚函数、虚函数表,虚指针
- C/C++如何判断两个小数是否相等
- C++空类的大小
- c++ 空类,含有虚函数的类的大小(此问题都是在32位机器上而言)
- 32位机与64位机指针占用空间不同
- 引经据典
基础篇
变量的声明和定义有什么区别
变量的定义为变量分配地址和存储空间, 变量的声明不分配地址。一个变量可以在多个地方声明, 但是只在一个地方定义。 加入extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。
说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间, 如外部变量。
int main()
{
extern int A;
//这是个声明而不是定义,声明A是一个已经定义了的外部变量
//注意:声明外部变量时可以把变量类型去掉如:extern A;
dosth(); //执行函数
}
int A; //是定义,定义了A为整型的外部变量
简述#ifdef、#else、#endif和#ifndef的作用
利用#ifdef、#endif将某程序功能模块包括进去,以向特定用户提供该功能。在不需要时用户可轻易将其屏蔽。
#ifdef MATH
#include “math.c”
#endif
//在子程序前加上标记,以便于追踪和调试。
#ifdef DEBUG
printf (“Indebugging…!”);
#endif
应对硬件的限制。由于一些具体应用环境的硬件不一样,限于条件,本地缺乏这种设备,只能绕过硬件,直接写出预期结果。
注意:虽然不用条件编译命令而直接用if语句也能达到要求,但那样做目标程序长(因为所有语句都编译),运行时间长(因为在程序运行时间对if语句进行测试)。而采用条件编译,可以减少被编译的语句,从而减少目标程序的长度,减少运行时间。
写出int 、bool、 float 、指针变量与 “零值”比较的if 语句
//int与零值比较
if ( n == 0 )
if ( n != 0 )
//bool与零值比较
if (flag) // 表示flag为真
if (!flag) // 表示flag为假
//float与零值比较
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON) //其中EPSINON是允许的误差(即精度)。
//指针变量与零值比较
if (p == NULL)
if (p != NULL)
结构体可以直接赋值吗
声明时可以直接初始化,同一结构体的不同对象之间也可以直接赋值,但是当结构体中含有指针“成员”时一定要小心。
注意:当有多个指针指向同一段内存时,某个指针释放这段内存可能会导致其他指针的非法操作。因此在释放前一定要确保其他指针不再使用这段内存空间。
sizeof 和strlen 的区别
sizeof是一个操作符,strlen是库函数。
sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为‘ ’的字符串作参数。
编译器在编译时就计算出了sizeof的结果,而strlen函数必须在运行时才能计算出来。并且sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度。
数组做sizeof的参数不退化,传递给strlen就退化为指针了
sizeof求类型大小
类的大小为类的非静态成员数据的类型大小之和,也就是说静态成员数据不作考虑。
普通成员函数与sizeof无关。
虚函数由于要维护在虚函数表,所以要占据一个指针大小,也就是4字节。
类的总大小也遵守类似class字节对齐的,调整规则。
例如有如下结构体:
struct Stu
{
int id;
char sex;
float hight;
};
那么一个这样的结构体变量占多大内存呢?也就是
cout<<sizeof(Stu)<<endl; 会输出什么?
在了解字节对齐方式之前想当然的会以为:sizeof(Stu) = sizeof(int)+sizeof(char)+sizeof(float) = 9.
然而事实并非如此!
字节对齐原则:在系统默认的对齐方式下:每个成员相对于这个结构体变量地址的偏移量正好是该成员类型所占字节的整数倍,且最终占用字节数为成员类型中最大占用字节数的整数倍。
在这个例子中,id的偏移量为0(0=40),sex的偏移量为4(4=14),hight的偏移量为8(8=24),此时占用12字节,也同时满足12=34.所以sizeof(Stu)=12.
struct A {
char y;
char z;
long long x;
}; 16字节
struct A {
char y;
char z;
int x;
}; 8字节
struct A {
char y;
char* z;
int x;
};12字节
struct A {
char y;
}; 1字节
我的总结:
最终大小一定是最大数据类型的整数倍;
静态变量不占空间
每种类型的偏移量为自身的n倍;
详细请查阅:struct/class等内存字节对齐问题详解
C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在 C 中 static 用来修饰局部静态变量和外部静态变量、函数。而 C++中除了上述功能外,还用来定义类的成员变量和函数。即静态成员和静态成员函数。
注意:编程时 static 的记忆性,和全局性的特点可以让在不同时期调用的函数进行通信,传递信息,而 C++的静态成员则可以在多个对象实例间进行通信,传递信息。
C 语言的 malloc 和 C++ 中的 new 有什么区别
new 、delete 是操作符,可以重载,只能在C++ 中使用。
malloc、free 是函数,可以覆盖,C、C++ 中都可以使用。
new 可以调用对象的构造函数,对应的delete 调用相应的析构函数。
malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
new 、delete 返回的是某种数据类型指针,malloc、free 返回的是void 指针。
注意:malloc 申请的内存空间要用free 释放,而new 申请的内存空间要用delete 释放,不要混用。
写一个 “标准”宏MIN
#define min(a,b) ((a)<=(b)?(a):(b))
++i和i++的区别
++i先自增1,再返回,i++先返回i,再自增1
volatile有什么作用
状态寄存器一类的并行设备硬件寄存器。
一个中断服务子程序会访问到的非自动变量。
多线程间被几个任务共享的变量。
注意:虽然volatile在嵌入式方面应用比较多,但是在PC软件的多线程中,volatile修饰的临界变量也是非常实用的。
C++中volatile的作用
总结:建议编译器不要对该变量进行优化
volatile是“易变的”、“不稳定”的意思。volatile是C的一个较为少用的关键字,它用来解决变量在“共享”环境下容易出现读取错误的问题。
定义为volatile的变量是说这变量可能会被意想不到地改变,即在你程序运行过程中一直会变,你希望这个值被正确的处理,每次从内存中去读这个值,而不是因编译器优化从缓存的地方读取,比如读取缓存在寄存器中的数值,从而保证volatile变量被正确的读取。
在单任务的环境中,一个函数体内部,如果在两次读取变量的值之间的语句没有对变量的值进行修改,那么编译器就会设法对可执行代码进行优化。由于访问寄存器的速度要快过RAM(从RAM中读取变量的值到寄存器),以后只要变量的值没有改变,就一直从寄存器中读取变量的值,而不对RAM进行访问。
而在多任务环境中,虽然在一个函数体内部,在两次读取变量之间没有对变量的值进行修改,但是该变量仍然有可能被其他的程序(如中断程序、另外的线程等)所修改。如果这时还是从寄存器而不是从RAM中读取,就会出现被修改了的变量值不能得到及时反应的问题。如下程序对这一现象进行了模拟。
#include <iostream>
using namespace std;
int main(int argc,char* argv[])
{
int i=10;
int a=i;
cout<<a<<endl;
_asm
{
mov dword ptr [ebp-4],80
}
int b=i;
cout<<b<<endl;
}
/*
程序在VS2012环境下生成Release版本,输出结果是:
10
10
*/
阅读以上程序,注意以下几个要点:
以上代码必须在Release模式下考查,因为只有Release模式下才会对程序代码进行优化,而这种优化在变量共享的环境下容易引发问题。
在语句b=i;之前,已经通 过内联汇编代码修改了i的值,但是i的变化却没有反映到b中,如果i是一个被多个任务共享的变量,这种优化带来的错误很可能是致命的。
汇编代码[ebp-4]表示变量i的存储单元,因为ebp是扩展基址指针寄存器,存放函数所属栈的栈底地址,先入栈,占用4个字节。随着函数内申明的局部变量的增多,esp(栈顶指针寄存器)就会相应的减小,因为栈的生长方向由高地址向低地址生长。i为第一个变量,栈空间已被ebp入栈占用了4个字节,所以i的地址为ebp-i,[ebp-i]则表示变量i的存储单元。
一个参数可以既是const又是volatile吗
可以,用const和volatile同时修饰变量,表示这个变量在程序内部是只读的,不能改变的,只在程序外部条件变化下改变,并且编译器不会优化这个变量。每次使用这个变量时,都要小心地去内存读取这个变量的值,而不是去寄存器读取它的备份。
注意:在此一定要注意const的意思,const只是不允许程序中的代码改变某一变量,其在编译期发挥作用,它并没有实际地禁止某段内存的读写特性。
a 和&a 有什么区别
&a:其含义就是“变量a的地址”。
*a:用在不同的地方,含义也不一样。
在声明语句中,*a只说明a是一个指针变量,如int *a;
在其他语句中,*a前面没有操作数且a是一个指针时,*a代表指针a指向的地址内存放的数据,如b=*a;
*a前面有操作数且a是一个普通变量时,a代表乘以a,如c=ba。
用C 编写一个死循环程序
while(1)
{
}
注意:很多种途径都可实现同一种功能,但是不同的方法时间和空间占用度不同,特别是对于嵌入 式软件,处理器速度比较慢,存储空间较小,所以时间和空间优势是选择各种方法的首要考虑条件。
结构体内存对齐问题
请写出以下代码的输出结果:
#include <stdio.h>
using namespace std;
/**************************************************************
* 结构体内存对⻬问题
* 从偏移为0的位置开始存储;
* 如果没有定义 #pragma pack(n)
* sizeof 的最终结果必然是结构内部最⼤成员的整数倍,不够补⻬;
* 结构内部各个成员的⾸地址必然是⾃身⼤⼩的整数倍;
*
***************************************************************/
struct S1
{
int i ; //起始偏移0,sizeof(i)=4; 地址0、1、2、3分配给成员i
char j ; //起始偏移4,sizeof(j)=1;
int a ; //sizeof(a)=4,内存对齐到8个字节,从偏移量为8处存放a;
double b;//sizeof(b)=8,内存对齐到16个字节,再存放b,结构体总大小24;
};
//结构体成员的首地址必须是自身大小的整数倍
struct S3
{
char j;//起始偏移0,sizeof(j)=1;
float i;//sizeof(i)=4,内存对齐到4,起始偏移量为4,再存放i
double b;//当前地址为8,是b大小的整数倍,无需对齐,直接存放成员b 8个字节
int a;//sizeof(a)=4,内存对齐到20,再存放a,总大小24字节;
};
int main()
{
printf("%d
", sizeof(S1));
printf("%d
", sizeof(S3));
return 0;
}
24
24
说明:结构体作为一种复合数据类型,其构成元素既可以是基本数据类型的变量,也可以是一些复合型类型数据。对此,编译器会自动进行成员变量的对齐以提高运算效率。默认情况下,按自然对齐条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同,向结构体成员中size最大的成员对齐。
许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,而这个k则被称为该数据类型的对齐模数。
全局变量和局部变量有什么区别?实怎么实现的?操作系统和编译器是怎么知道的?
全局变量是整个程序都可访问的变量,谁都可以访问,生存期在整个程序从运行到结束(在程序结束时所占内存释放);
而局部变量存在于模块(子程序,函数)中,只有所在模块可以访问,其他模块不可直接访问,模块结束(函数调用完毕),局部变量消失,所占据的内存释放。
操作系统和编译器,可能是通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载.局部变量则分配在堆栈里面。
简述C、C++程序编译的内存分配情况
从静态存储区域分配:
内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错, 因为有系统会善后。例如全局变量,static 变量,常量字符串等。
在栈上分配:
在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释 放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。大小为2M。
从堆上分配:
即动态内存分配。程序在运行的时候用 malloc 或new 申请任意大小的内存,程序员自己负责在何 时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生 堆内碎块。
一个C、C++程序编译时内存分为5 大存储区:堆区、栈区、全局区、文字常量区、程序代码区。
简述strcpy、sprintf 与memcpy 的区别
操作对象不同,strcpy 的两个操作对象均为字符串,sprintf 的操作源对象可以是多种数据类型, 目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
执行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。
实现功能不同,strcpy 主要实现字符串变量间的拷贝,sprintf 主要实现其他数据类型格式到字 符串的转化,memcpy 主要是内存块间的拷贝。
注意:strcpy、sprintf 与memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来 选择合适的函数实现拷贝功能。
请解析((void ()( ) )0)( )的含义
void (0)( ) :是一个返回值为void,参数为空的函数指针0。
(void ()( ))0:把0转变成一个返回值为void,参数为空的函数指针。
(void ()( ))0:在上句的基础上加表示整个是一个返回值为void,无参数,并且起始地址为0的函数的名字。
((void (*)( ))0)( ):这就是上句的函数名所对应的函数的调用。
typedef 和define 有什么区别
用法不同:
typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义 常量,以及书写复杂使用频繁的宏。
执行时间不同:
typedef 是编译过程的一部分,有类型检查的功能。define 是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
作用域不同:
typedef 有作用域限定。define 不受作用域约束,只要是在define 声明后的引用 都是正确的。
对指针的操作不同:
typedef 和define 定义的指针时有很大的区别。
注意:typedef 定义是语句,因为句尾要加上分号。而define 不是语句,千万不能在句尾加分号。
指针常量与常量指针区别
指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其他地方不能改变。常量指针 是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。 指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。
注意:无论是指针常量还是常量指针,其最大的用途就是作为函数的形式参数,保证实参在被调用 函数中的不可改变特性。
简述队列和栈的异同
队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是“先进先出”,栈是 “后进先出”。
注意:区别栈区和堆区。堆区的存取是“顺序随意”,而栈区是“后进先出”。栈由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS 回收。分配方式类似于链表。 它与本题中的堆和栈是两回事。堆栈只是一种数据结构,而堆区和栈区是程序的不同内存存储区域。
设置地址为0x67a9 的整型变量的值为0xaa66
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa66;
注意:这道题就是强制类型转换的典型例子,无论在什么平台地址长度和整型数据的长度是一样的, 即一个整型数据可以强制转换成地址指针类型,只要有意义即可。
编码实现字符串转化为数字
编码实现函数atoi(),设计一个程序,把一个字符串转化为一个整型数值。例如数字:“5486321 ”, 转化成字符:5486321。
int myAtoi(const char * str)
{
int num = 0; //保存转换后的数值
int isNegative = 0; //记录字符串中是否有负号
int n =0;
char *p = str;
if(p == NULL) //判断指针的合法性
{
return -1;
}
while(*p++ != '