应该使用inline内联函数,即编译器将inline内联函数内的代码替换到函数被调用的地方,我来为大家讲解一下关于常见c语言面试题及答案?跟着小编一起来看一看吧!

常见c语言面试题及答案(CC面试题目)

常见c语言面试题及答案

第一节 语法基础 C和C 有什么区别?#01 C 11有哪些新特性?#02 struct和class有什么区别?#03 对于一个频繁使用的短小函数,应该使用什么来实现?有什么优缺点?

应该使用inline内联函数,即编译器将inline内联函数内的代码替换到函数被调用的地方。

优点:

缺点:

#04 #define和inline有什么区别?#05 const关键字有什么作用?#06 #define和const有什么区别?#07 explicit关键字有什么作用?

首先,可以用单个实参来调用的构造函数都定义了从形参类型到实参类型的隐式转换,这种转换往往都是非预期的,所以使用explicit关键字对构造函数进行修饰,从而避免由构造函数定义的隐式转换。

#08 extern关键字有什么作用?#09 static关键字有什么作用?#10 volatile关键字有什么作用?可以同时是const和volatile吗?指针可以是volatile吗?

访问寄存器比访问内存要快,所以CPU会优先访问该数据在寄存器中的存储结果,但内存中的数据可能已经被意想不到地改变,而寄存器中还保留着原来的结果,所以为了避免这种情况,可以将变量声明为volatile,不进行编译优化,使得CPU每次都从内存中读取数据。

例子是并行设备的硬件寄存器、中断服务子程序会访问到的非自动变量、多线程应用中被几个任务共享的变量。

一个变量可以同时是const和volatile,一个例子是只读状态寄存器,是const表示程序不应该试图修改变量的值,是volatile表示变量的值可能被操作系统、硬件、其他线程等改变。

指针可以是volatile,一个例子是中断服务子程序修改一个指向buffer的指针。

补充:下面这段代码有什么错误?

int square(volatile int *ptr) { return (*ptr * *ptr); }

因为*ptr的值可能被意想不到地改变,所以两次解引用得到的值不一定相同,因此应该如下:

int square(volatile int *ptr) { int a = *ptr; return a * a; }

#11 sizeof和strlen之间有什么区别?#12 assert有什么用处?

assert是一种仅在debug版本中使用的宏函数,用于检查不该发生的情况,可以看作是一种在任何系统状态下都可以安全使用的无害测试手段。

另外,可以通过#define NDEBUG来关闭assert(需要在<cassert>头文件之前)。

#13 变量的声明和定义有什么区别?#14 指针和引用有什么区别?#15 [识别指针、函数和数组] 以下声明语句分别代表什么意思?

void *(*(*fp1)(int))[10];

fp1是一个指针,指向一个函数,这个函数的参数为整型,返回一个指针,这个指针指向一个数组,数组中有10个元素,每个元素都是一个void *指针。

float (*(*fp2)(int, int, int))(int);

fp2是一个指针,指向一个函数,这个函数的参数是三个整型,返回一个指针,这个指针指向一个函数,这个函数的参数是整型,返回是浮点型;

int (*(*fp3)())[10]();

fp3是一个指针,指向一个函数,这个函数返回一个指针,这个指针指向一个数组,数组中有10个元素,每个元素都是一个指针,指向一个函数,这个函数的参数为空,返回整型。

第二节 内存管理#00 常用的数据类型各自占用多大的内存空间?

数据类型

32位编译器

64位编译器

bool

1

1

char

1

1

short (int)

2

2

int

4

4

unsigned int

4

4

long

4

8

long long

8

8

float

4

4

double

8

8

pointer

4

8

#01 new/delete和malloc/free之间有什么关系?#02 delete与delete []有什么区别?

int *a = new int(1); delete a; int *b = new int(2); delete [] b; int *c = new int[11]; delete c; int *d = new int[12]; delete [] d;

#03 如果在申请动态内存时找不到足够大的内存块,即malloc和new返回空指针,那么应该如何处理这种情况?

try { int *ptr = new int[10000000]; } catch(bad_alloc &memExp) { cerr << memExp.what() << endl; }

void no_more_memory() { cerr << "Unable to satisfy request for memory" << endl; abort(); } int main() { set_new_handler(no_more_memory); int *ptr = new int[10000000]; }

#04 内存泄漏的场景有哪些?如何判断内存泄漏?如何定位内存泄漏?

内存泄漏的场景:

char* getMemory() { char *p = (char *)malloc(30); return p; } int main() { char *p = getMemory(); return 0; }

在构造函数中动态分配内存,但未在析构函数中正确释放内存;

判断和定位内存泄漏的方法:

在Linux系统下,可以使用valgrind、mtrace等内存泄漏检测工具。

#05 内存的分配方式有几种?#06 堆和栈有什么区别?#07 静态内存分配和动态内存分配有什么区别?#08 如何构造一个类,使得只能在堆上或只能在栈上分配内存?#09 浅拷贝和深拷贝有什么区别?

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存;而深拷贝会创造一个相同的对象,新对象与原对象不共享内存,修改新对象不会影响原对象。

#10 字节对齐的原则是什么?第三节 面向对象#00 面向对象的三大特征是哪些?各自有什么样的特点?#01 多态的实现有哪几种?

多态分为静态多态和动态多态。其中,静态多态是通过重载和模板技术实现的,在编译期间确定;动态多态是通过虚函数和继承关系实现的,执行动态绑定,在运行期间确定。

#02 动态多态有什么作用?有哪些必要条件?

动态多态的作用:

动态多态的必要条件:

#03 动态绑定是如何实现的?

当编译器发现类中有虚函数时,会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且在对象中增加一个指针vptr,用于指向类的虚函数表。当派生类覆盖基类的虚函数时,会将虚函数表中对应的指针进行替换,从而调用派生类中覆盖后的虚函数,从而实现动态绑定。

#04 纯虚函数有什么作用?如何实现?

定义纯虚函数是为了实现一个接口,起到规范的作用,想要继承这个类就必须覆盖该函数。

实现方式是在虚函数声明的结尾加上= 0即可。

#05 虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的?

虚函数表是针对类的,类的所有对象共享这个类的虚函数表,因为每个对象内部都保存一个指向该类虚函数表的指针vptr,每个对象的vptr的存放地址都不同,但都指向同一虚函数表。

#06 为什么基类的构造函数不能定义为虚函数?

虚函数的调用依赖于虚函数表,而指向虚函数表的指针vptr需要在构造函数中进行初始化,所以无法调用定义为虚函数的构造函数。

#07 为什么基类的析构函数需要定义为虚函数?

为了实现动态绑定,基类指针指向派生类对象,如果析构函数不是虚函数,那么在对象销毁时,就会调用基类的析构函数,只能销毁派生类对象中的部分数据,所以必须将析构函数定义为虚函数,从而在对象销毁时,调用派生类的析构函数,从而销毁派生类对象中的所有数据。

#08 构造函数和析构函数能抛出异常吗?#09 如何让一个类不能实例化?

将类定义为抽象类(也就是存在纯虚函数)或者将构造函数声明为private。

#10 多继承存在什么问题?如何消除多继承中的二义性?
  1. 增加程序的复杂度,使得程序的编写和维护比较困难,容易出错;
  2. 在继承时,基类之间或基类与派生类之间发生成员同名时,将出现对成员访问的不确定性,即同名二义性;
  1. 当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类的成员时,将产生另一种不确定性,即路径二义性;
#11 如果类A是一个空类,那么sizeof(A)的值为多少?为什么?

sizeof(A)的值为1,因为编译器需要区分这个空类的不同实例,分配一个字节,可以使这个空类的不同实例拥有独一无二的地址。

第四节 高级特性#00 什么是智能指针?智能指针有什么作用?分为哪几种?各自有什么样的特点?

智能指针是一个RAII类模型,用于动态分配内存,其设计思想是将基本类型指针封装为(模板)类对象指针,并在离开作用域时调用析构函数,使用delete删除指针所指向的内存空间。

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

分为auto_ptr、unique_ptr、shared_ptr和weak_ptr四种,各自的特点:

#01 shared_ptr是如何实现的?
  1. 构造函数中计数初始化为1;
  2. 拷贝构造函数中计数值加1;
  3. 赋值运算符中,左边的对象引用计数减1,右边的对象引用计数加1;
  4. 析构函数中引用计数减1;
  5. 在赋值运算符和析构函数中,如果减1后为0,则调用delete释放对象。
#02 类型转换分为哪几种?各自有什么样的特点?#03 RTTI是什么?其原理是什么?

RTTI即运行时类型识别,其功能由两个运算符实现:

#04 右值引用有什么作用?

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

#05 标准库中有哪些容器?分别有什么特点?

标准库中的容器主要分为三类:顺序容器、关联容器、容器适配器。

顺序容器包括五种类型:

关联容器包含两种类型:

容器适配器包含三种类型:

#06 vector的reserve()和resize()方法之间有什么区别?

首先,vector的容量capacity()是指在不分配更多内存的情况下可以保存的最多元素个数,而vector的大小size()是指实际包含的元素个数;

其次,vector的reserve(n)方法只改变vector的容量,如果当前容量小于n,则重新分配内存空间,调整容量为n;如果当前容量大于等于n,则无操作;

最后,vector的resize(n)方法改变vector的大小,如果当前容量小于n,则调整容量为n,同时将其全部元素填充为初始值;如果当前容量大于等于n,则不调整容量,只将其前n个元素填充为初始值。

#07 静态链接和动态链接有什么区别?#08 悬挂指针与野指针有什么区别?#09 拷贝构造函数和赋值运算符重载之间有什么区别?

Student s; Student s1 = s; // 隐式调用拷贝构造函数 Student s2(s); // 显式调用拷贝构造函数

Student s; Student s1; s1 = s; // 使用赋值运算符

#10 覆盖和重载之间有什么区别?第五节 代码实现#00 对于float类型的变量,如何比较零值?

if ((val >= -0.000001) && (val <= 0.000001))

#01 如何判断一段程序是C编译程序还是C 编译程序?

#ifdef __cplusplus cout << "C " << endl; #else cout << "C" << endl; #endif

#02 如何使用宏定义求两个元素中的最小值?

#define MIN(A, B) ((A) <= (B) ? (A) : (B))

但此处应注意防范宏的副作用,比如对于MIN(*p , b)来说:

((*p ) <= (b) ? (*p ) : (b))

因此,对于指针p来说,会产生两次自增操作。

#03 如何实现strcpy函数?

char *strcpy(char *dst, char *src) { assert(dst != nullptr && src != nullptr); char *res = dst; while ((*dst = *src ) != '\0'); return res; }

#04 如何实现strcat函数?

char *strcat(char *dst, char *src) { assert(dst != nullptr && src != nullptr); char *res = dst; while (*dst != '\0') dst; while ((*dst = *src ) != '\0'); return res; }

#05 如何实现strcmp函数?

int strcmp(const char *str1, const char *str2) { assert(str1 != nullptr && str2 != nullptr); while (*str1 == *str2 && *str1 != '\0' && *str2 != '\0') { str1; str2; } return *str1 - *str2;

#06 下面这段代码有什么问题?

void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = nullptr; GetMemory(str); strcpy(str, "hello world"); printf(str); }

#07 下面这段代码有什么问题?

char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = nullptr; str = GetMemory(); printf(str); }

GetMemory中的数组p[]是局部变量,所开辟的内存将在函数调用结束后被回收,虽然返回指向该内存的指针,但是内存中的数据可能已经发生了改变,从而形成悬挂指针。

可以使用如下两种方法进行改进:

char *p = "hello world"; static char *p = "hello world";

#08 如何实现一个string类?

class String { public: String(const char *str = nullptr); String(const String &other); ~ String(void); String & operator =(const String &other); private: char *m_data; }; String::String(const char *str) { if (str == nullptr) { m_data = new char[1]{'\0'}; } else { int length = strlen(str); m_data = new char[length 1]; strcpy(m_data, str); } } String::String(const String &other) { int length = strlen(other.m_data); m_data = new char[length 1]; // 此处可以访问private成员,是因为此处还算是在类内,只要是在类内就可以访问类成员 strcpy(m_data, other.m_data); } String::~String(void) { delete [] m_data; } String & String::operator =(const String &other) { if (this == &other) return *this; // 检查自赋值 delete [] m_data; // 释放原来的内存 int length = strlen(other.m_data); m_data = new char[length 1]; strcpy(m_data, other.m_data); return *this; }

,