C语言与C++之常见问题分享

简介: C语言与C++之常见问题分享

1 变量的声明和定义有什么区别

变 量 的 定 义 为 变 量 分 配 地 址 和 存 储 空 间 , 变 量 的 声 明 不 分 配 地 址 。 一 个 变 量 可 以 在 多 个 地 方 声 明 , 但 是 只 在 一 个 地 方 定 义 。 加 入 e x t e r n 修 饰 的 是 变 量 的 声 明 , 说 明 此 变 量 将 在 文 件 以 外 或 在 文 件 后 面 部 分 定 义 。

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

int main()
{
extern int A;

//这是个声明而不是定义,声明A是一个已经定义了的外部变量

//注意:声明外部变量时可以把变量类型去掉如:extern A;

dosth(); //执行函数
}
int A; 

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


2 简述#ifdef、#else、#endif和#ifndef的作用

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

#ifdef MATH
#include "math.c"
#endif

在子程序前加上标记,以便于追踪和调试。

#ifdef DEBUG
printf ("Indebugging......!");
#endif

应对硬件的限制。由于一些具体应用环境的硬件不一样,限于条件,本地缺乏这种设备,只能绕过硬件,直

接写出预期结果。

注 意 : 虽 然 不 用 条 件 编 译 命 令 而 直 接 用 i f 语 句 也 能 达 到 要 求 , 但 那 样 做 目 标 程 序 长 ( 因 为 所 有 语 句 都 编 译 ) , 运 行 时 间 长 ( 因 为 在 程 序 运 行 时 间 对 i f 语 句 进 行 测 试 ) 。而 采 用 条 件 编 译 , 可 以 减 少 被 编 译 的 语 句 , 从 而 减 少 目 标 程 序 的 长 度 , 减 少 运 行 时 间 。


3 写出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)

4 结构体可以直接赋值吗

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

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


5 sizeof 和strlen 的区别

sizeof是一个操作符,strlen是库函数。

sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为‘\0’的字符串作参数。

编译器在编译时就计算出了sizeof的结果,而strlen函数必须在运行时才能计算出来。并且sizeof计算的是数 据类型占内存的大小,而strlen计算的是字符串实际的长度。 数组做sizeof的参数不退化,传递给strlen就退化为指针了


6 C 语言的关键字 static 和 C++ 的关键字 static 有什么区别2020/8/3

在 C 中 s t a t i c 用 来 修 饰 局 部 静 态 变 量 和 外 部 静 态 变 量 、 函 数 。 而 C + + 中 除 了 上 述 功 能 外 , 还 用 来 定 义 类 的 成 员 变 量 和 函 数 。 即 静 态 成 员 和 静 态 成 员 函 数 。 注 意 : 编 程 时 s t a t i c 的 记 忆 性 , 和 全 局 性 的 特 点 可 以 让 在 不 同 时 期 调 用 的 函 数 进 行 通 信 , 传 递 信 息 , 而 C + + 的 静 态 成 员 则 可 以 在 多 个 对 象 实 例 间 进 行 通 信 , 传 递 信 息 。


7 C 语言的 malloc 和 C++ 中的 new 有什么区别

new 、delete 是操作符,可以重载,只能在C++ 中使用。

malloc、free 是函数,可以覆盖,C、C++ 中都可以使用。

new 可以调用对象的构造函数,对应的delete 调用相应的析构函数。

malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数

new 、delete 返回的是某种数据类型指针,malloc、free 返回的是void 指针。

注 意 : m a l l o c 申 请 的 内 存 空 间 要 用 f r e e 释 放 , 而 n e w 申 请 的 内 存 空 间 要 用

d e l e t e 释 放 , 不 要 混 用 。


8 写一个 “标准”宏MIN

# d e f i n e m i n ( a , b ) ( ( a ) < = ( b ) ? ( a ) : ( b ) )


9 ++i和i++的区别

+ + i 先 自 增 1 , 再 返 回 , i + + 先 返 回 i , 再 自 增 1


10 volatile有什么作用

状态寄存器一类的并行设备硬件寄存器。

一个中断服务子程序会访问到的非自动变量。

多线程间被几个任务共享的变量。

注 意 : 虽 然 v o l a t i l e 在 嵌 入 式 方 面 应 用 比 较 多 , 但 是 在 P C 软 件 的 多 线 程 中 ,

v o l a t i l e 修 饰 的 临 界 变 量 也 是 非 常 实 用 的 。


11 一个参数可以既是const又是volatile吗

可 以 , 用 c o n s t 和 v o l a t i l e 同 时 修 饰 变 量 , 表 示 这 个 变 量 在 程 序 内 部 是 只 读 的 , 不 能 改 变 的 , 只 在 程 序 外 部 条 件 变 化 下 改 变 , 并 且 编 译 器 不 会 优 化 这 个 变 量 。 每 次 使 用 这 个 变 量 时 , 都 要 小 心 地 去 内 存 读 取 这 个 变 量 的 值 , 而 不 是 去 寄 存 器 读 取 它 的 备 份 。

注 意 : 在 此 一 定 要 注 意 c o n s t 的 意 思 , c o n s t 只 是 不 允 许 程 序 中 的 代 码 改 变 某 一 变 量 , 其 在 编 译 期 发 挥 作 用 , 它 并 没 有 实 际 地 禁 止 某 段 内 存 的 读 写 特 性 。


12 a 和&a 有什么区别

& a : 其 含 义 就 是 “ 变 量 a 的 地 址 ” 。

* a : 用 在 不 同 的 地 方 , 含 义 也 不 一 样 。

在声明语句中,*a只说明a是一个指针变量,如int *a;

在其他语句中,*a前面没有操作数且a是一个指针时,*a代表指针a指向的地址内存放的数据,如b=*a;

*a前面有操作数且a是一个普通变量时,a 代 表 乘 以 a , 如 c = b a。


13 用C 编写一个死循环程序

while(1)

{ }

注 意 : 很 多 种 途 径 都 可 实 现 同 一 种 功 能 , 但 是 不 同 的 方 法 时 间 和 空 间 占 用 度 不 同 , 特

别 是 对 于 嵌 入 式 软 件 , 处 理 器 速 度 比 较 慢 , 存 储 空 间 较 小 , 所 以 时 间 和 空 间 优 势 是

选 择 各 种 方 法 的 首 要 考 虑 条 件 。


14 结构体内存对齐问题

请 写 出 以 下 代 码 的 输 出 结 果 :

#include<stdio.h>
struct S1
{
int i:8;
char j:4;
int a:4;
double b;
};
struct S2
{
int i:8;
char j:4;
double b;
int a:4;
};
struct S3
{
int i;
char j;
double b;
int a;
};
int main()
{
printf("%d\n",sizeof(S1)); // 输出8
printf("%d\n",sizeof(S1); // 输出12
printf("%d\n",sizeof(Test3)); // 输出8
return 0;
}
sizeof(S1)=16
sizeof(S2)=24
sizeof(S3)=32

说 明 : 结 构 体 作 为 一 种 复 合 数 据 类 型 , 其 构 成 元 素 既 可 以 是 基 本 数 据 类 型 的 变 量 , 也 可 以 是 一 些 复 合 型 类 型 数 据 。 对 此 , 编 译 器 会 自 动 进 行 成 员 变 量 的 对 齐 以 提 高 运 算 效 率 。 默 认 情 况 下 , 按 自 然 对 齐 条 件 分 配 空 间 。 各 个 成 员 按 照 它 们 被 声 明 的 顺 序 在 内 存 中 顺 序 存 储 , 第 一 个 成 员 的 地 址 和 整 个 结 构 的 地 址 相 同 , 向 结 构 体 成 员 中 s i z e 最 大 的 成 员 对 齐 。 许 多 实 际 的 计 算 机 系 统 对 基 本 类 型 数 据 在 内 存 中 存 放 的 位 置 有 限 制 , 它 们 会 要 求 这 些 数 据 的 首 地 址 的 值 是 某 个 数 k ( 通 常 它 为 4 或 8 ) 的 倍 数 , 而 这 个 k 则 被 称 为 该 数 据 类 型 的 对 齐 模 数 。


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

全局变量是整个程序都可访问的变量,谁都可以访问,生存期在整个程序从运行到结束(在程序结束时所占内存释放); 而局部变量存在于模块(子程序,函数)中,只有所在模块可以访问,其他模块不可直接访问,模块结束 (函数调用完毕),局部变量消失,所占据的内存释放。 操作系统和编译器,可能是通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行 的时候被加载.局部变量则分配在堆栈里面。


16 简述C、C++程序编译的内存分配情况2020/8/3

从静态存储区域分配:

内 存 在 程 序 编 译 时 就 已 经 分 配 好 , 这 块 内 存 在 程 序 的 整 个 运 行 期 间 都 存 在 。 速 度 快 、 不 容 易 出 错 , 因 为 有 系 统 会 善 后 。 例 如 全 局 变 量 , s t a t i c 变 量 , 常 量 字 符 串 等 。

在栈上分配:

在 执 行 函 数 时 , 函 数 内 局 部 变 量 的 存 储 单 元 都 在 栈 上 创 建 , 函 数 执 行 结 束 时 这 些 存 储 单 元 自 动 被 释 放 。 栈 内 存 分 配 运 算 内 置 于 处 理 器 的 指 令 集 中 , 效 率 很 高 , 但 是 分 配

的 内 存 容 量 有 限 。 大 小 为 2 M 。

从堆上分配:

即 动 态 内 存 分 配 。 程 序 在 运 行 的 时 候 用 m a l l o c 或 n e w 申 请 任 意 大 小 的 内 存 , 程 序 员 自 己 负 责 在 何 时 用 f r e e 或 d e l e t e 释 放 内 存 。 动 态 内 存 的 生 存 期 由 程 序 员 决 定 , 使 用 非 常 灵 活 。 如 果 在 堆 上 分 配 了 空 间 , 就 有 责 任 回 收 它 , 否 则 运 行 的 程 序 会 出 现 内 存 泄 漏 , 另 外 频 繁 地 分 配 和 释 放 不 同 大 小 的 堆 空 间 将 会 产 生 堆 内 碎 块 。

一 个 C 、 C + + 程 序 编 译 时 内 存 分 为 5 大 存 储 区 : 堆 区 、 栈 区 、 全 局 区 、 文 字 常 量 区 、 程 序 代 码 区 。


17 简述strcpy、sprintf 与memcpy 的区别

操作对象不同,strcpy 的两个操作对象均为字符串,sprintf 的操作源对象可以是多种数据类型, 目的操作 对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。 执行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。

实现功能不同,strcpy 主要实现字符串变量间的拷贝,sprintf 主要实现其他数据类型格式到字 符串的转 化,memcpy 主要是内存块间的拷贝。

注 意 : s t r c p y 、 s p r i n t f 与 m e m c p y 都 可 以 实 现 拷 贝 的 功 能 , 但 是 针 对 的 对 象 不 同 , 根 据 实 际 需 求 , 来 选 择 合 适 的 函 数 实 现 拷 贝 功 能 。


18 请解析(*(void (*)( ) )0)( )的含义

void (*0)( ) : 是一个返回值为void,参数为空的函数指针0。

(void (*)( ))0: 把0转变成一个返回值为void,参数为空的函数指针。

*(void (*)( ))0: 在上句的基础上加*表示整个是一个返回值为void,无参数,并且起始地址为0的函

数的名字。

(*(void (*)( ))0)( ): 这就是上句的函数名所对应的函数的调用。


19 C语言的指针和引用和c++的有什么区别?

指针有自己的一块空间,而引用只是一个别名;

使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;

作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对 象;

可以有const指针,但是没有const引用;

指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;

指针可以有多级指针(**p),而引用止于一级;

指针和引用使用++运算符的意义不一样;

如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。


20 typedef 和define 有什么区别

用法不同:typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义 常量,以及

书写复杂使用频繁的宏。

执行时间不同:typedef 是编译过程的一部分,有类型检查的功能。define 是宏定义,是预编译的部分,其

发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。

作用域不同:typedef 有作用域限定。define 不受作用域约束,只要是在define 声明后的引用 都是正确

的。

对指针的操作不同:typedef 和define 定义的指针时有很大的区别。

注 意 : t y p e d e f 定 义 是 语 句 , 因 为 句 尾 要 加 上 分 号 。 而 d e f i n e 不 是 语 句 , 千 万 不

能 在 句 尾 加 分 号 。


21 指针常量与常量指针区别

指 针 常 量 是 指 定 义 了 一 个 指 针 , 这 个 指 针 的 值 只 能 在 定 义 时 初 始 化 , 其 他 地 方 不 能 改

变 。 常 量 指 针 是 指 定 义 了 一 个 指 针 , 这 个 指 针 指 向 一 个 只 读 的 对 象 , 不 能 通 过 常 量

指 针 来 改 变 这 个 对 象 的 值 。 指 针 常 量 强 调 的 是 指 针 的 不 可 改 变 性 , 而 常 量 指 针 强 调

的 是 指 针 对 其 所 指 对 象 的 不 可 改 变 性 。

注 意 : 无 论 是 指 针 常 量 还 是 常 量 指 针 , 其 最 大 的 用 途 就 是 作 为 函 数 的 形 式 参 数 , 保 证

实 参 在 被 调 用 函 数 中 的 不 可 改 变 特 性 。


22 简述队列和栈的异同

队 列 和 栈 都 是 线 性 存 储 结 构 , 但 是 两 者 的 插 入 和 删 除 数 据 的 操 作 不 同 , 队 列 是 “ 先 进

先 出 ” , 栈 是 “ 后 进 先 出 ” 。

注 意 : 区 别 栈 区 和 堆 区 。 堆 区 的 存 取 是 “ 顺 序 随 意 ” , 而 栈 区 是 “ 后 进 先 出 ” 。 栈 由

编 译 器 自 动 分 配 释 放 , 存 放 函 数 的 参 数 值 , 局 部 变 量 的 值 等 。 其 操 作 方 式 类 似 于 数

据 结 构 中 的 栈 。 堆 一 般 由 程 序 员 分 配 释 放 , 若 程 序 员 不 释 放 , 程 序 结 束 时 可 能 由O S 回 收 。 分 配 方 式 类 似 于 链 表 。 它 与 本 题 中 的 堆 和 栈 是 两 回 事 。 堆 栈 只 是 一 种 数 据 结 构 , 而 堆 区 和 栈 区 是 程 序 的 不 同 内 存 存 储 区 域 。


23 设置地址为0x67a9 的整型变量的值为0xaa66

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

注 意 : 这 道 题 就 是 强 制 类 型 转 换 的 典 型 例 子 , 无 论 在 什 么 平 台 地 址 长 度 和 整 型 数 据 的

长 度 是 一 样 的 , 即 一 个 整 型 数 据 可 以 强 制 转 换 成 地 址 指 针 类 型 , 只 要 有 意 义 即 可 。


24 编码实现字符串转化为数字

编 码 实 现 函 数 a t o i ( ) , 设 计 一 个 程 序 , 把 一 个 字 符 串 转 化 为 一 个 整 型 数 值 。 例 如 数

字 : “ 5 4 8 6 3 2 1 ” , 转 化 成 字 符 : 5 4 8 6 3 2 1 。

int myAtoi(const char * str)
{
int num = 0; //保存转换后的数值
int isNegative = 0; //记录字符串中是否有负号
int n =0;
char *p = str;
if(p == NULL) //判断指针的合法性
{
return -1;
}
while(*p++ != '\0') //计算数字符串度
{
n++;
}
p = str;
if(p[0] == '-') //判断数组是否有负号
{
isNegative = 1;
}
char temp = '0';
for(int i = 0 ; i < n; i++)
{
char temp = *p++;
if(temp > '9' ||temp < '0') //滤除非数字字符
{
continue;
}
if(num !=0 || temp != '0') //滤除字符串开始的0 字符
{
temp -= 0x30; //将数字字符转换为数值
num += temp *int( pow(10 , n - 1 -i) );
}
}
if(isNegative) //如果字符串中有负号,将数值取反
{
return (0 - num);
}
else
{
return num; //返回转换后的数值
}
}


25 C语言的结构体和C++的有什么区别

C语言的结构体是不能有函数成员的,而C++的类可以有。

C语言的结构体中数据成员是没有private、public和protected访问限定的。而C++的类的成员有这些访问

限定。

C语言的结构体是没有继承关系的,而C++的类却有丰富的继承关系。

注 意 : 虽 然 C 的 结 构 体 和 C + + 的 类 有 很 大 的 相 似 度 , 但 是 类 是 实 现 面 向 对 象 的 基 础 。

而 结 构 体 只 可 以 简 单 地 理 解 为 类 的 前 身 。


26 简述指针常量与常量指针的区别

指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其他地方不能改变。常量指针是指定义

了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。

指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。

注 意 : 无 论 是 指 针 常 量 还 是 常 量 指 针 , 其 最 大 的 用 途 就 是 作 为 函 数 的 形 式 参 数 , 保 证

实 参 在 被 调 用 函 数 中 的 不 可 改 变 特 性 。


27 如何避免“野指针”

指针变量声明时没有被初始化。解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向 NULL。

指针p被free或者delete之后,没有置为NULL。解决办法:指针指向的内存空间被释放后指针应该指向 NULL。

指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向 NULL。


28 句柄和指针的区别和联系是什么?

句 柄 和 指 针 其 实 是 两 个 截 然 不 同 的 概 念 。 W i n d o w s 系 统 用 句 柄 标 记 系 统 资 源 , 隐 藏

系 统 的 信 息 。 你 只 要 知 道 有 这 个 东 西 , 然 后 去 调 用 就 行 了 , 它 是 个 3 2 i t 的 u i n t 。 指

针 则 标 记 某 个 物 理 内 存 地 址 , 两 者 是 不 同 的 概 念 。


29 new/delete与malloc/free的区别是什么

new能自动计算需要分配的内存空间,而malloc需要手工计算字节数。

int *p = new int[2];
int *q = (int *)malloc(2*sizeof(int));

new与delete直接带具体类型的指针,malloc和free返回void类型的指针。

new类型是安全的,而malloc不是。例如int *p = new float[2];就会报错;而int p =

m a l l o c ( 2 sizeof(int))编译时编译器就无法指出错误来。

new一般分为两步:new操作和构造。new操作对应与malloc,但new操作可以重载,可以自定义内存分配

策略,不做内存分配,甚至分配到非内存设备上,而malloc不行。

new调用构造函数,malloc不能;delete调用析构函数,而free不能。

malloc/free需要库文件stdlib.h的支持,new/delete则不需要!

注 意 : d e l e t e 和 f r e e 被 调 用 后 , 内 存 不 会 立 即 回 收 , 指 针 也 不 会 指 向 空 , d e l e t e

或 f r e e 仅 仅 是 告 诉 操 作 系 统 , 这 一 块 内 存 被 释 放 了 , 可 以 用 作 其 他 用 途 。 但 是 由 于

没 有 重 新 对 这 块 内 存 进 行 写 操 作 , 所 以 内 存 中 的 变 量 数 值 并 没 有 发 生 变 化 , 出 现 野 指

针 的 情 况 。 因 此 , 释 放 完 内 存 后 , 应 该 讲 该 指 针 指 向 N U L L 。


30 说一说extern“C”

e x t e r n " C " 的 主 要 作 用 就 是 为 了 能 够 正 确 实 现 C + + 代 码 调 用 其 他 C 语 言 代 码 。 加 上

e x t e r n " C " 后 , 会 指 示 编 译 器 这 部 分 代 码 按 C 语 言 ( 而 不 是 C + + ) 的 方 式 进 行 编

译 。 由 于 C + + 支 持 函 数 重 载 , 因 此 编 译 器 编 译 函 数 的 过 程 中 会 将 函 数 的 参 数 类 型 也

加 到 编 译 后 的 代 码 中 , 而 不 仅 仅 是 函 数 名 ; 而 C 语 言 并 不 支 持 函 数 重 载 , 因 此 编 译 C

语 言 代 码 的 函 数 时 不 会 带 上 函 数 的 参 数 类 型 , 一 般 只 包 括 函 数 名 。

这 个 功 能 十 分 有 用 处 , 因 为 在 C + + 出 现 以 前 , 很 多 代 码 都 是 C 语 言 写 的 , 而 且 很 底 层

的 库 也 是 C 语 言 写 的 , 为 了 更 好 的 支 持 原 来 的 C 代 码 和 已 经 写 好 的 C 语 言 库 , 需 要 在

C + + 中 尽 可 能 的 支 持 C , 而 e x t e r n " C " 就 是 其 中 的 一 个 策 略 。

C++代码调用C语言代码

在C++的头文件中使用

在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到


31 请你来说一下C++中struct和class的区别

在 C + + 中 , c l a s s 和 s t r u c t 做 类 型 定 义 是 只 有 两 点 区 别 :

默 认 继 承 权 限 不 同 , c l a s s 继 承 默 认 是 p r i v a t e 继 承 , 而 s t r u c t 默 认 是 p u b l i c 继

c l a s s 还 可 用 于 定 义 模 板 参 数 , 像 t y p e n a m e , 但 是 关 键 字 s t r u c t 不 能 同 于 定 义

模 板 参 数 C + + 保 留 s t r u c t 关 键 字 , 原 因

保 证 与 C 语 言 的 向 下 兼 容 性 , C + + 必 须 提 供 一 个 s t r u c t

C + + 中 的 s t r u c t 定 义 必 须 百 分 百 地 保 证 与 C 语 言 中 的 s t r u c t 的 向 下 兼 容 性 , 把

C + + 中 的 最 基 本 的 对 象 单 元 规 定 为 c l a s s 而 不 是 s t r u c t , 就 是 为 了 避 免 各 种 兼 容

性 要 求 的 限 制

对 s t r u c t 定 义 的 扩 展 使 C 语 言 的 代 码 能 够 更 容 易 的 被 移 植 到 C + + 中


32 C++类内可以定义引用数据成员吗?

可以,必须通过成员函数初始化列表初始化。


33 C++中类成员的访问权限

C + + 通 过 p u b l i c 、 p r o t e c t e d 、 p r i v a t e 三 个 关 键 字 来 控 制 成 员 变 量 和 成 员 函

数 的 访 问 权 限 , 它 们 分 别 表 示 公 有 的 、 受 保 护 的 、 私 有 的 , 被 称 为 成 员 访 问 限 定 符 。

在 类 的 内 部 ( 定 义 类 的 代 码 内 部 ) , 无 论 成 员 被 声 明 为 p u b l i c 、 p r o t e c t e d 还 是

p r i v a t e , 都 是 可 以 互 相 访 问 的 , 没 有 访 问 权 限 的 限 制 。 在 类 的 外 部 ( 定 义 类 的 代 码

之 外 ) , 只 能 通 过 对 象 访 问 成 员 , 并 且 通 过 对 象 只 能 访 问 p u b l i c 属 性 的 成 员 , 不

能 访 问 p r i v a t e 、 p r o t e c t e d 属 性 的 成 员


34 什么是右值引用,跟左值又有什么区别?

左 值 和 右 值 的 概 念 :

左值:能取地址,或者具名对象,表达式结束后依然存在的持久对象;

右值:不能取地址,匿名对象,表达式结束后就不再存在的临时对象; 区别:

左值能寻址,右值不能;

左值能赋值,右值不能;

左值可变,右值不能(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变);


35 面向对象的三大特征

封装性:将客观事物抽象成类,每个类对自身的数据和方法实行 protection (private , protected ,

public )。

继承性:广义的继承有三种实现形式:实现继承(使用基类的属性和方法而无需额外编码的能力)、可 视继

承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。

多态性:是将父类对象设置成为和一个或更多它的子对象相等的技术。用子类对象给父类对象赋值 之后,父

类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。


36 说一说c++中四种cast转换

C + + 中 四 种 类 型 转 换 是 : s t a t i c _ c a s t , d y n a m i c _ c a s t , c o n s t _ c a s t ,

r e i n t e r p r e t _ c a s t

1 、 c o n s t _ c a s t

用于将const变量转为非const

2 、 s t a t i c _ c a s t

用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能

成功但是不安全,结果未知;

3 、 d y n a m i c _ c a s t

用 于 动 态 类 型 转 换 。 只 能 用 于 含 有 虚 函 数 的 类 , 用 于 类 层 次 间 的 向 上 和 向 下 转 化 。 只

能 转 指 针 或 引 用 。 向 下 转 化 时 , 如 果 是 非 法 的 * * * 对 于 指 针 返 回 N U L L , 对 于 引 用 抛

异 常 * * * 。 要 深 入 了 解 内 部 转 换 的 原 理 。

向上转换:指的是子类向基类的转换

向下转换:指的是基类向子类的转换

它 通 过 判 断 在 执 行 到 该 语 句 的 时 候 变 量 的 运 行 时 类 型 和 要 转 换 的 类 型 是 否 相 同 来 判 断

是 否 能 够 进 行 向 下 转 换 。

4 、 r e i n t e r p r e t _ c a s t

几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

5 、 为 什 么 不 使 用 C 的 强 制 转 换 ?

C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。


37 C++的空类有哪些成员函数

缺省构造函数。

缺省拷贝构造函数。

缺省析构函数。

缺省赋值运算符。

缺省取址运算符。

缺省取址运算符 const 。

注 意 : 有 些 书 上 只 是 简 单 的 介 绍 了 前 四 个 函 数 。 没 有 提 及 后 面 这 两 个 函 数 。 但 后 面 这

两 个 函 数 也 是 空 类 的 默 认 函 数 。 另 外 需 要 注 意 的 是 , 只 有 当 实 际 使 用 这 些 函 数 的 时

候 , 编 译 器 才 会 去 定 义 它 们 。


38 对c++中的smart pointer四个智能指针:

shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解

C + + 里 面 的 四 个 智 能 指 针 : a u t o _ p t r , s h a r e d _ p t r , w e a k _ p t r , u n i q u e _ p t r

其 中 后 三 个 是 c + + 1 1 支 持 , 并 且 第 一 个 已 经 被 1 1 弃 用 。

智 能 指 针 的 作 用 是 管 理 一 个 指 针 , 因 为 存 在 以 下 这 种 情 况 : 申 请 的 空 间 在 函 数 结 束 时

忘 记 释 放 , 造 成 内 存 泄 漏 。 使 用 智 能 指 针 可 以 很 大 程 度 上 的 避 免 这 个 问 题 , 因 为 智 能

指 针 就 是 一 个 类 , 当 超 出 了 类 的 作 用 域 是 , 类 会 自 动 调 用 析 构 函 数 , 析 构 函 数 会 自 动

释 放 资 源 。 所 以 智 能 指 针 的 作 用 原 理 就 是 在 函 数 结 束 时 自 动 释 放 内 存 空 间 , 不 需 要 手

动 释 放 内 存 空 间 。

auto_ptr(c++98的方案,cpp11已经抛弃)

采 用 所 有 权 模 式 。

auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr<string> p2;

p2 = p1; //auto_ptr不会报错.

此 时 不 会 报 错 , p 2 剥 夺 了 p 1 的 所 有 权 , 但 是 当 程 序 运 行 时 访 问 p 1 将 会 报 错 。 所 以

a u t o _ p t r 的 缺 点 是 : 存 在 潜 在 的 内 存 崩 溃 问 题 !

unique_ptr(替换auto_ptr)

u n i q u e _ p t r 实 现 独 占 式 拥 有 或 严 格 拥 有 概 念 , 保 证 同 一 时 间 内 只 有 一 个 智 能 指 针 可

以 指 向 该 对 象 。 它 对 于 避 免 资 源 泄 露 ( 例 如 “ 以 n e w 创 建 对 象 后 因 为 发 生 异 常 而 忘 记

调 用 d e l e t e ” ) 特 别 有 用 。

采 用 所 有 权 模 式 。

unique_ptr<string> p3 (new string ("auto")); //#4
unique_ptr<string> p4; //#5

p4 = p3;//此时会报错!!

编 译 器 认 为 p 4 = p 3 非 法 , 避 免 了 p 3 不 再 指 向 有 效 数 据 的 问 题 。 因 此 , u n i q u e _ p t r

比 a u t o _ p t r 更 安 全 。

另 外 u n i q u e _ p t r 还 有 更 聪 明 的 地 方 : 当 程 序 试 图 将 一 个 u n i q u e _ p t r 赋 值 给 另 一

个 时 , 如 果 源 u n i q u e _ p t r 是 个 临 时 右 值 , 编 译 器 允 许 这 么 做 ; 如 果 源

u n i q u e _ p t r 将 存 在 一 段 时 间 , 编 译 器 将 禁 止 这 么 做 , 比 如 :

unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 allowed

其 中 # 1 留 下 悬 挂 的 u n i q u e _ p t r ( p u 1 ) , 这 可 能 导 致 危 害 。 而 # 2 不 会 留 下 悬 挂 的

u n i q u e _ p t r , 因 为 它 调 用 u n i q u e _ p t r 的 构 造 函 数 , 该 构 造 函 数 创 建 的 临 时 对 象

在 其 所 有 权 让 给 p u 3 后 就 会 被 销 毁 。 这 种 随 情 况 而 已 的 行 为 表 明 , u n i q u e _ p t r

优 于 允 许 两 种 赋 值 的 a u t o _ p t r 。

注 : 如 果 确 实 想 执 行 类 似 与 # 1 的 操 作 , 要 安 全 的 重 用 这 种 指 针 , 可 给 它 赋 新 值 。

C + + 有 一 个 标 准 库 函 数 s t d : : m o v e ( ) , 让 你 能 够 将 一 个 u n i q u e _ p t r 赋 给 另 一 个 。

例 如 :

unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
shared_ptr

shared_ptr

s h a r e d _ p t r 实 现 共 享 式 拥 有 概 念 。 多 个 智 能 指 针 可 以 指 向 相 同 对 象 , 该 对 象 和 其 相关 资 源 会 在 “ 最 后 一 个 引 用 被 销 毁 ” 时 候 释 放 。 从 名 字 s h a r e 就 可 以 看 出 了 资 源 可 以被 多 个 指 针 共 享 , 它 使 用 计 数 机 制 来 表 明 资 源 被 几 个 指 针 共 享 。 可 以 通 过 成 员 函 数u s e _ c o u n t ( ) 来 查 看 资 源 的 所 有 者 个 数 。 除 了 可 以 通 过 n e w 来 构 造 , 还 可 以 通 过 传

入 a u t o _ p t r , u n i q u e _ p t r , w e a k _ p t r 来 构 造 。 当 我 们 调 用 r e l e a s e ( ) 时 , 当 前 指针 会 释 放 资 源 所 有 权 , 计 数 减 一 。 当 计 数 等 于 0 时 , 资 源 会 被 释 放 。

s h a r e d _ p t r 是 为 了 解 决 a u t o _ p t r 在 对 象 所 有 权 上 的 局 限 性 ( a u t o _ p t r 是 独 占的 ) , 在 使 用 引 用 计 数 的 机 制 上 提 供 了 可 以 共 享 所 有 权 的 智 能 指 针 。


成 员 函 数 :


u s e _ c o u n t 返 回 引 用 计 数 的 个 数

u n i q u e 返 回 是 否 是 独 占 所 有 权 ( u s e _ c o u n t 为 1 )

s w a p 交 换 两 个 s h a r e d _ p t r 对 象 ( 即 交 换 所 拥 有 的 对 象 )

r e s e t 放 弃 内 部 对 象 的 所 有 权 或 拥 有 对 象 的 变 更 , 会 引 起 原 有 对 象 的 引 用 计 数 的 减少

g e t 返 回 内 部 对 象 ( 指 针 ) , 由 于 已 经 重 载 了 ( ) 方 法 , 因 此 和 直 接 使 用 对 象 是 一 样 的 .

如 s h a r e d _ p t r s p ( n e w i n t ( 1 ) ) ; s p 与 s p . g e t ( ) 是 等 价 的

weak_ptr

w e a k _ p t r 是 一 种 不 控 制 对 象 生 命 周 期 的 智 能 指 针 , 它 指 向 一 个 s h a r e d _ p t r 管理 的 对 象 . 进 行 该 对 象 的 内 存 管 理 的 是 那 个 强 引 用 的 s h a r e d _ p t r . w e a k _ p t r 只 是提 供 了 对 管 理 对 象 的 一 个 访 问 手 段 。 w e a k _ p t r 设 计 的 目 的 是 为 配 合 s h a r e d _ p t r而 引 入 的 一 种 智 能 指 针 来 协 助 s h a r e d _ p t r 工 作 , 它 只 可 以 从 一 个 s h a r e d _ p t r或 另 一 个 w e a k _ p t r 对 象 构 造 , 它 的 构 造 和 析 构 不 会 引 起 引 用 记 数 的 增 加 或 减 少 。w e a k _ p t r 是 用 来 解 决 s h a r e d _ p t r 相 互 引 用 时 的 死 锁 问 题 , 如 果 说 两 个

s h a r e d _ p t r 相 互 引 用 , 那 么 这 两 个 指 针 的 引 用 计 数 永 远 不 可 能 下 降 为 0 , 资 源 永 远 不会 释 放 。 它 是 对 对 象 的 一 种 弱 引 用 , 不 会 增 加 对 象 的 引 用 计 数 , 和 s h a r e d _ p t r 之 间可 以 相 互 转 化 , s h a r e d _ p t r 可 以 直 接 赋 值 给 它 , 它 可 以 通 过 调 用 l o c k 函 数 来 获 得

s h a r e d _ p t r 。
class B;
class A
{
public:
shared_ptr<B> pb_;
~A()
{
cout<<"A delete
";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout<<"B delete
";
}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->pa_ = pa;
pa->pb_ = pb;
cout<<pb.use_count()<<endl;
cout<<pa.use_count()<<endl;
}
int main()
{
fun();
return 0;
}

可 以 看 到 f u n 函 数 中 p a , p b 之 间 互 相 引 用 , 两 个 资 源 的 引 用 计 数 为 2 , 当 要 跳 出 函

数 时 , 智 能 指 针 p a , p b 析 构 时 两 个 资 源 引 用 计 数 会 减 一 , 但 是 两 者 引 用 计 数 还 是 为

1 , 导 致 跳 出 函 数 时 资 源 没 有 被 释 放 ( A B 的 析 构 函 数 没 有 被 调 用 ) , 如 果 把 其 中 一

个 改 为 w e a k _ p t r 就 可 以 了 , 我 们 把 类 A 里 面 的 s h a r e d _ p t r p b _ ; 改 为 w e a k _ p t r

p b _ ; 运 行 结 果 如 下 , 这 样 的 话 , 资 源 B 的 引 用 开 始 就 只 有 1 , 当 p b 析 构 时 , B 的 计

数 变 为 0 , B 得 到 释 放 , B 释 放 的 同 时 也 会 使 A 的 计 数 减 一 , 同 时 p a 析 构 时 使 A 的 计 数

减 一 , 那 么 A 的 计 数 为 0 , A 得 到 释 放 。

注 意 : 不 能 通 过 w e a k _ p t r 直 接 访 问 对 象 的 方 法 , 比 如 B 对 象 中 有 一 个 方 法 p r i n t ( ) ,

我 们 不 能 这 样 访 问 , p a - > p b _ - > p r i n t ( ) ; 英 文 p b _ 是 一 个 w e a k _ p t r , 应 该 先 把

它 转 化 为 s h a r e d _ p t r , 如 : s h a r e d _ p t r p = p a - > p b _ . l o c k ( ) ; p - > p r i n t ( ) ;


39 说说强制类型转换运算符

static_cast

用于非多态类型的转换

不执行运行时类型检查(转换安全性不如 dynamic_cast)

通常用于转换数值数据类型(如 float -> int)

可以在整个类层次结构中移动指针,子类转化为父类安全(向上转换),父类转化为子类不安全(因为子类可能有不在父类的字段或方法)

dynamic_cast用于多态类型的转换执行行运行时类型检查只适用于指针或引用

对不明确的指针的转换将失败(返回 nullptr),但不引发异常可以在整个类层次结构中移动指针,包括向上转换、向下转换

const_cast

用于删除 const、volatile 和 __unaligned 特性(如将 const int 类型转换为 int 类型 ) reinterpret_cast用于位的简单重新解释

滥用 reinterpret_cast 运算符可能很容易带来风险。除非所需转换本身是低级别的,否则应- 使用其他强制转换运算符之一。

允许将任何指针转换为任何其他指针类型(如 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换,但其本身并不安全)

也允许将任何整数类型转换为任何指针类型以及反向转换。

reinterpret_cast 运算符不能丢掉 const、volatile 或 __unaligned 特性。

reinterpret_cast 的一个实际用途是在哈希函数中,即,通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。

bad_cast

由于强制转换为引用类型失败,dynamic_cast 运算符引发 bad_cast 异常。

b a d _ c a s t 使 用

try {
Circle& ref_circle = dynamic_cast<Circle&>(ref_shape);
}
catch (bad_cast b) {
cout << "Caught: " << b.what();
}

40 谈谈你对拷贝构造函数和赋值运算符的认识


拷 贝 构 造 函 数 和 赋 值 运 算 符 重 载 有 以 下 两 个 不 同 之 处 :

拷贝构造函数生成新的类对象,而赋值运算符不能。

由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象 是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉。

注 意 : 当 有 类 中 有 指 针 类 型 的 成 员 变 量 时 , 一 定 要 重 写 拷 贝 构 造 函 数 和 赋 值 运 算 符 ,不 要 使 用 默 认 的 。

41 在C++中,使用malloc申请的内存能否通过delete释放?使用new申请的

内存能否用free?

不 能 , m a l l o c / f r e e 主 要 为 了 兼 容 C , n e w 和 d e l e t e 完 全 可 以 取 代 m a l l o c/ f r e e 的 。 m a l l o c / f r e e 的 操 作 对 象 都 是 必 须 明 确 大 小 的 。 而 且 不 能 用 在 动 态 类上 。 n e w 和 d e l e t e 会 自 动 进 行 类 型 检 查 和 大 小 , m a l l o c / f r e e 不 能 执 行 构 造 函 数与 析 构 函 数 , 所 以 动 态 对 象 它 是 不 行 的 。 当 然 从 理 论 上 说 使 用 m a l l o c 申 请 的 内 存 是可 以 通 过 d e l e t e 释 放 的 。 不 过 一 般 不 这 样 写 的 。 而 且 也 不 能 保 证 每 个 C + + 的 运 行 时都 能 正 常 。


42 用C++设计一个不能被继承的类

template <typename T> class A
{
friend T;
private:
A() {}
~A() {}
};
class B : virtual public A<B>
{
public:
B() {}
~B() {}
};
class C : virtual public B
{
public:
C() {}
~C() {}
};
void main( void )
{
B b;
//C c;
return;
}

注 意 : 构 造 函 数 是 继 承 实 现 的 关 键 , 每 次 子 类 对 象 构 造 时 , 首 先 调 用 的 是 父 类 的 构 造

函 数 , 然 后 才 是 自 己 的 。


43 C++自己实现一个String类

#include <iostream>
#include <cstring>
using namespace std;
class String{
public:

// 默认构造函数

String(const char *str = nullptr);

// 拷贝构造函数

String(const String &str);

// 析构函数

~String();

// 字符串赋值函数

String& operator=(const String &str);
private:
char *m_data;
int m_size;
};

// 构造函数

String::String(const char *str)
{
if(str == nullptr) // 加分点:对m_data加NULL 判断
{
m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志'\0'的
m_data[0] = '\0';
m_size = 0;
}
else
{
m_size = strlen(str);
m_data = new char[m_size + 1];
strcpy(m_data, str);
}
}

// 拷贝构造函数

// 拷贝构造函数
String::String(const String &str) // 得分点:输入参数为const型
{
m_size = str.m_size;
m_data = new char[m_size + 1]; //加分点:对m_data加NULL 判断
strcpy(m_data, str.m_data);
}

// 析构函数

String::~String()
{
delete[] m_data;
}

// 字符串赋值函数

String& String::operator=(const String &str) // 得分点:输入参数为const
{
if(this == &str) //得分点:检查自赋值
return *this;
delete[] m_data; //得分点:释放原有的内存资源
m_size = strlen(str.m_data);
m_data = new char[m_size + 1]; //加分点:对m_data加NULL 判断
strcpy(m_data, str.m_data);
return *this; //得分点:返回本对象的引用
}

44 访问基类的私有虚函数

写 出 以 下 程 序 的 输 出 结 果

#include <iostream.h>
class A
{
virtual void g()
{
cout << "A::g" << endl;
}
private:
virtual void f()
{
cout << "A::f" << endl;
}
};
class B : public A
{
void g()
{
cout << "B::g" << endl;
}
virtual void h()
{
cout << "B::h" << endl;
}
};
typedef void( *Fun )( void );
void main()
{
B b;
Fun pFun;
for(int i = 0 ; i < 3; i++)
{
pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i );
pFun();
}
}

输 出 结 果 :

B::g
A::f
B::h

注 意 : 考 察 了 面 试 者 对 虚 函 数 的 理 解 程 度 。 一 个 对 虚 函 数 不 了 解 的 人很 难 正 确 的 做 出本 题 。 在 学 习 面 向 对 象 的 多 态 性 时 一 定 要 深 刻 理 解 虚 函 数 表 的 工 作 原 理 。


45 对虚函数和多态的理解

多 态 的 实 现 主 要 分 为 静 态 多 态 和 动 态 多 态 , 静 态 多 态 主 要 是 重 载 , 在 编 译 的 时 候 就 已经 确 定 ; 动 态 多 态 是 用 虚 函 数 机 制 实 现 的 , 在 运 行 期 间 动 态 绑 定 。 举 个 例 子 : 一 个 父类 类 型 的 指 针 指 向 一 个 子 类 对 象 时 候 , 使 用 父 类 的 指 针 去 调 用 子 类 中 重 写 了 的 父 类 中的 虚 函 数 的 时 候 , 会 调 用 子 类 重 写 过 后 的 函 数 , 在 父 类 中 声 明 为 加 了 v i r t u a l 关 键 字的 函 数 , 在 子 类 中 重 写 时 候 不 需 要 加 v i r t u a l 也 是 虚 函 数 。

虚 函 数 的 实 现 : 在 有 虚 函 数 的 类 中 , 类 的 最 开 始 部 分 是 一 个 虚 函 数 表 的 指 针 , 这 个 指 针 指 向 一 个 虚 函 数 表 , 表 中 放 了 虚 函 数 的 地 址 , 实 际 的 虚 函 数 在 代 码 段 ( . t e x t ) 中 。 当 子 类 继 承 了 父 类 的 时 候 也 会 继 承 其 虚 函 数 表 , 当 子 类 重 写 父 类 中 虚 函 数 时 候 , 会 将 其 继 承 到 的 虚 函 数 表 中 的 地 址 替 换 为 重 新 写 的 函 数 地 址 。 使 用 了 虚 函 数 , 会 增 加 访 问 内 存 开 销 , 降 低 效 率 。


46 简述类成员函数的重写、重载和隐藏的区别

( 1 ) 重 写 和 重 载 主 要 有 以 下 几 点 不 同 。

范围的区别:被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中。

参数的区别:被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数的参数列表一 定不同。

virtual 的区别:重写的基类中被重写的函数必须要有virtual 修饰,而重载函数和被重载函数可以被 virtual修饰,也可以没有。

( 2 ) 隐 藏 和 重 写 、 重 载 有 以 下 几 点 不 同 。

与重载的范围不同:和重写一样,隐藏函数和被隐藏函数不在同一个类中。

参数的区别:隐藏函数和被隐藏的函数的参数列表可以相同,也可不同,但是函数名肯定要相同。 当参数不相同时,无论基类中的参数是否被virtual 修饰,基类的函数都是被隐藏,而不是被重写。

注 意 : 虽 然 重 载 和 覆 盖 都 是 实 现 多 态 的 基 础 , 但 是 两 者 实 现 的 技 术 完 全 不 相 同 , 达 到的 目 的 也 是 完 全 不 同 的 , 覆 盖 是 动 态 态 绑 定 的 多 态 , 而 重 载 是 静 态 绑 定 的 多 态 。


47 链表和数组有什么区别

存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间, 长度可变,每个结点要保存相邻结点指针。

数据查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点, 效率低。

数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动。

越界问题:链表不存在越界问题,数组有越界问题。

注 意 : 在 选 择 数 组 或 链 表 数 据 结 构 时 , 一 定 要 根 据 实 际 需 要 进 行 选 择 。 数 组 便 于 查询 , 链 表 便 于 插 入 删 除 。 数 组 节 省 空 间 但 是 长 度 固 定 , 链 表 虽 然 变 长 但 是 占 了 更 多的 存 储 空 间 。


48 用两个栈实现一个队列的功能

typedef struct node
{
int data;
node *next;
}node,*LinkStack;
//创建空栈:
LinkStack CreateNULLStack( LinkStack &S)
{
S = (LinkStack)malloc( sizeof( node ) ); // 申请新结点
if( NULL == S)
{
printf("Fail to malloc a new node.\n");
return NULL;
}
S->data = 0; //初始化新结点
S->next = NULL;
return S;
}
//栈的插入函数:
LinkStack Push( LinkStack &S, int data)
{
if( NULL == S) //检验栈
{
printf("There no node in stack!");
return NULL;
}
LinkStack p = NULL;
p = (LinkStack)malloc( sizeof( node ) ); // 申请新结点
if( NULL == p)
{
printf("Fail to malloc a new node.\n");
return S;
}
if( NULL == S->next)
{
p->next = NULL;
}
else
{
p->next = S->next;
}
p->data = data; //初始化新结点
S->next = p; //插入新结点
return S;
}
//出栈函数:
node Pop( LinkStack &S)
{
node temp;
temp.data = 0;
temp.next = NULL;
if( NULL == S) //检验栈
{
printf("There no node in stack!");
return temp;
}
temp = *S;
if( S->next == NULL )
{
printf("The stack is NULL,can't pop!\n");
return temp;
}
LinkStack p = S ->next; //节点出栈
S->next = S->next->next;
temp = *p;
free( p );
p = NULL;
return temp;
}
//双栈实现队列的入队函数:
LinkStack StackToQueuPush( LinkStack &S, int data)
{
node n;
LinkStack S1 = NULL;
CreateNULLStack( S1 ); //创建空栈
while( NULL != S->next ) //S 出栈入S1
{
n = Pop( S );
Push( S1, n.data );
}
Push( S1, data ); //新结点入栈
while( NULL != S1->next ) //S1 出栈入S
{
n = Pop( S1 );
Push( S, n.data );
}
return S;
}

注 意 : 用 两 个 栈 能 够 实 现 一 个 队 列 的 功 能 , 那 用 两 个 队 列 能 否 实 现 一 个 队 列 的 功 能呢 ? 结 果 是 否 定 的 , 因 为 栈 是 先 进 后 出 , 将 两 个 栈 连 在 一 起 , 就 是 先 进 先 出 。 而 队列 是 现 先 进 先 出 , 无 论 多 少 个 连 在 一 起 都 是 先 进 先 出 , 而 无 法 实 现 先 进 后 出 。


49 vector的底层原理

v e c t o r 底 层 是 一 个 动 态 数 组 , 包 含 三 个 迭 代 器 , s t a r t 和 f i n i s h 之 间 是 已 经 被 使 用的 空 间 范 围 , e n d _ o f _ s t o r a g e 是 整 块 连 续 空 间 包 括 备 用 空 间 的 尾 部 。

当 空 间 不 够 装 下 数 据 ( v e c . p u s h _ b a c k ( v a l ) ) 时 , 会 自 动 申 请 另 一 片 更 大 的 空 间( 1 . 5 倍 或 者 2 倍 ) , 然 后 把 原 来 的 数 据 拷 贝 到 新 的 内 存 空 间 , 接 着 释 放 原 来 的 那 片空 间 [ v e c t o r 内 存 增 长 机 制 ] 。


当 释 放 或 者 删 除 ( v e c . c l e a r ( ) ) 里 面 的 数 据 时 , 其 存 储 空 间 不 释 放 , 仅 仅 是 清 空 了里 面 的 数 据 。 因 此 , 对 v e c t o r 的 任 何 操 作 一 旦 引 起 了 空 间 的 重 新 配 置 , 指 向 原v e c t o r 的 所 有 迭 代 器 会 都 失 效 了 。


50 vector中的reserve和resize的区别


reserve是直接扩充到已经确定的大小,可以减少多次开辟、释放空间的问题(优化push_back),就可以提高效率,其次还可以减少多次要拷贝数据的问题。reserve只是保证vector中的空间大小(capacity)最少达到参数所指定的大小n。reserve()只有一个参数。

resize()可以改变有效空间的大小,也有改变默认值的功能。capacity的大小也会随着改变。resize()可以有多个参数。


51 vector中的size和capacity的区别


size表示当前vector中有多少个元素(finish - start);

capacity函数则表示它已经分配的内存中可以容纳多少元素(end_of_storage - start);


52 vector中erase方法与algorithn中的remove方法区别


vector中erase方法真正删除了元素,迭代器不能访问了

remove只是简单地将元素移到了容器的最后面,迭代器还是可以访问到。因为algorithm通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。


53 vector迭代器失效的情况


当插入一个元素到vector中,由于引起了内存重新分配,所以指向原内存的迭代器全部失效。

当删除容器中一个元素后,该迭代器所指向的元素已经被删除,那么也造成迭代器失效。erase方法会返回下一个有效的迭代器,所以当我们要删除某个元素时,it=vec.erase(it);


54 正确释放vector的内存(clear(), swap(), shrink_to_fit())


vec.clear():清空内容,但是不释放内存。

vector().swap(vec):清空内容,且释放内存,想得到一个全新的vector。

vec.shrink_to_fit():请求容器降低其capacity和size匹配。

vec.clear();vec.shrink_to_fit();:清空内容,且释放内存。


55 list的底层原理


ist的底层是一个双向链表,使用链表存储数据,并不会将它们存储到一整块连续的内存空间中。恰恰相反,各元素占用的存储空间(又称为节点)是独立的、分散的,它们之间的线性关系通过指针来维持,每次插入或删除一个元素,就配置或释放一个元素空间。

list不支持随机存取,如果需要大量的插入和删除,而不关心随即存取


56 什么情况下用vector,什么情况下用list,什么情况下用deque


vector可以随机存储元素(即可以通过公式直接计算出元素地址,而不需要挨个查找),但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。除非必要,我们尽可能选择使用vector而非deque,因为deque的迭代器比vector迭代器复杂很多。

list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁,比如写多读少的场景。

需要从首尾两端进行插入或删除操作的时候需要选择deque。


57 priority_queue的底层原理


priority_queue:优先队列,其底层是用堆来实现的。在优先队列中,队首元素一定是当前队列中优先级最高的那一个。


58 map 、set、multiset、multimap的底层原理

m a p 、 s e t 、 m u l t i s e t 、 m u l t i m a p 的 底 层 实 现 都 是 红 黑 树 , e p o l l 模 型 的 底 层 数据 结 构 也 是 红 黑 树 , l i n u x 系 统 中 C F S 进 程 调 度 算 法 , 也 用 到 红 黑 树 。


红 黑 树 的 特 性 :


每个结点或是红色或是黑色;

根结点是黑色;

每个叶结点是黑的;

如果一个结点是红的,则它的两个儿子均是黑色;

每个结点到其子孙结点的所有路径上包含相同数目的黑色结点。


59 为何map和set的插入删除效率比其他序列容器高

因为不需要内存拷贝和内存移动


60 为何map和set每次Insert之后,以前保存的iterator不会失效?


因为插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变。


61 当数据元素增多时(从10000到20000),map的set的查找速度会怎样

变化?


RB-TREE用二分查找法,时间复杂度为logn,所以从10000增到20000时,查找次数从log10000=14次到

log20000=15次,多了1次而已。


62 map 、set、multiset、multimap的特点


set和multiset会根据特定的排序准则自动将元素排序,set中元素不允许重复,multiset可以重复。

map和multimap将key和value组成的pair作为元素,根据key的排序准则自动将元素排序(因为红黑树也是二叉搜索树,所以map默认是按key排序的),map中元素的key不允许重复,multimap可以重复。

map和set的增删改查速度为都是logn,是比较高效的。


63 为何map和set的插入删除效率比其他序列容器高,而且每次insert之后,

以前保存的iterator不会失效?


存储的是结点,不需要内存拷贝和内存移动。

插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变。


64 为何map和set不能像vector一样有个reserve函数来预分配数据?


在map和set内部存储的已经不是元素本身了,而是包含元素的结点。也就是说map内部使用的Alloc并不是map声明的时候从参数中传入的Alloc。


65 set的底层实现实现为什么不用哈希表而使用红黑树?


set中元素是经过排序的,红黑树也是有序的,哈希是无序的

如果只是单纯的查找元素的话,那么肯定要选哈希表了,因为哈希表在的最好查找时间复杂度为O(1),并且如果用到set中那么查找时间复杂度的一直是O(1),因为set中是不允许有元素重复的。而红黑树的查找时间复杂度为O(lgn)


66 hash_map与map的区别?什么时候用hash_map,什么时候用map?


构造函数:hash_map需要hash function和等于函数,而map需要比较函数(大于或小于)。

存储结构:hash_map以hashtable为底层,而map以RB-TREE为底层

总的说来,hash_map查找速度比map快,而且查找速度基本和数据量大小无关,属于常数级别。而map的查找速度是logn级别。但不一定常数就比log小,而且hash_map还有hash function耗时。

如果考虑效率,特别当元素达到一定数量级时,用hash_map。

考虑内存,或者元素数量较少时,用map。


67 迭代器失效的问题


插 入 操 作 :

对于vector和string,如果容器内存被重新分配,iterators,pointers,references失效;如果没有重新分配,

那么插入点之前的iterator有效,插入点之后的iterator失效;

对于deque,如果插入点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入

元素到front和back时,deque的迭代器失效,但reference和pointers有效;

对于list和forward_list,所有的iterator,pointer和refercnce有效。 删除操作:

对于vector和string,删除点之前的iterators,pointers,references有效;off-the-end迭代器总是失效的;

对于deque,如果删除点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入

元素到front和back时,off-the-end失效,其他的iterators,pointers,references有效;

对于list和forward_list,所有的iterator,pointer和refercnce有效。

对于关联容器map来说,如果某一个元素已经被删除,那么其对应的迭代器就失效了,不应该再被使用,否

则会导致程序无定义的行为。


68 STL线程不安全的情况


在对同一个容器进行多线程的读写、写操作时;

在每次调用容器的成员函数期间都要锁定该容器;

在每个容器返回的迭代器(例如通过调用begin或end)的生存期之内都要锁定该容器;

在每个在容器上调用的算法执行期间锁定该容器。


目录
相关文章
|
28天前
|
API 数据库 C语言
【C/C++ 数据库 sqlite3】SQLite C语言API返回值深入解析
【C/C++ 数据库 sqlite3】SQLite C语言API返回值深入解析
167 0
|
28天前
|
安全 算法 程序员
【C/C++ 文件操作】深入理解C语言中的文件锁定机制
【C/C++ 文件操作】深入理解C语言中的文件锁定机制
31 0
|
1月前
|
算法 程序员 编译器
C++与C语言的差异:编程语言之间的奥秘探索
C++与C语言的差异:编程语言之间的奥秘探索
49 0
|
29天前
|
JSON JavaScript 前端开发
C++ 智能指针与 JSON 处理:高级编程技巧与常见问题解析
C++ 智能指针与 JSON 处理:高级编程技巧与常见问题解析
264 0
|
12天前
|
C语言 C++ 数据格式
【C++对于C语言的扩充】C++与C语言的联系,命名空间、C++中的输入输出以及缺省参数
【C++对于C语言的扩充】C++与C语言的联系,命名空间、C++中的输入输出以及缺省参数
|
16天前
|
开发框架 .NET 编译器
【C++】C++对C语言的关系,拓展及命名空间的使用
【C++】C++对C语言的关系,拓展及命名空间的使用
|
21天前
|
人工智能 机器人 测试技术
【C/C++】C语言 21点桌牌游戏 (源码) 【独一无二】
【C/C++】C语言 21点桌牌游戏 (源码) 【独一无二】
|
29天前
|
存储 安全 编译器
【C/C++ 基本数据类型】C++ 基本数据类型深度解析与C语言对比
【C/C++ 基本数据类型】C++ 基本数据类型深度解析与C语言对比
58 0
|
1月前
|
Unix Linux C语言
【C/C++ 跳转函数】setjmp 和 longjmp 函数的巧妙运用: C 语言错误处理实践
【C/C++ 跳转函数】setjmp 和 longjmp 函数的巧妙运用: C 语言错误处理实践
19 0
|
1月前
|
安全 Unix Linux
【C/C++ 字符串】探索C语言之字符串分割函数:strtok和strsep的区别
【C/C++ 字符串】探索C语言之字符串分割函数:strtok和strsep的区别
15 0

热门文章

最新文章