面试总结2

内联函数(inline)

inline

内联函数的目的是为了减少函数调用的时间, 它是把内联函数的函数体在编译器预处理的时候替换到函数调用处,这样代码运行到这里的时候不需要花时间去调用函数。但内联函数的缺点也在于此, 它会增加执行文件的大小。
需要注意:
1.头文件中不仅要包含inline函数的声明, 还要包含inline函数的定义
编译器需要把inline函数的函数体替换到函数调用处,所以编译器必须知道inline函数的函数体是啥, 定义和声明写在同一头文件中 便于编译器查找替换。
2.可以在同一个项目的不同源文件内定义函数名相同, 实现相同的inline函数
同一个inline函数可以多处声明和定义, 但是必须要完全相同
3.定义在class内的成员函数默认是inline函数(至于编译器是否处理为inline就不一定了); 类外定义的成员函数就不是内联的了

virtual函数

1.类的构造函数不能是虚函数
类的构造函数是为了构造对象的, 所以在调用构造函数时必然知道是哪个对象调用了构造函数。
2.类的静态成员函数不能是虚函数
类的静态成员函数与对象无关, 是该类共用的, 没有this指针

使用inline关键字的函数可能会被编译器忽略而不在调用处展开, 如虚函数

1.如果定义的inline函数过大,为了放置生成的obj文件太大,编译器会忽略这里的inline声明
2.inline是在编译期将函数体替换到调用处的; 而虚函数的调用是运行期才能决定的,编译期并不能确定调用的是哪个类的虚函数,所以编译器会忽略inline关键字。(虚函数可以声明为inline,但是毫无内联的意义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

using namespace std;

class Base {
public:
virtual void who() {
cout << "I'm in Base." << endl;
}
};
class Derived : public Base {
public:
virtual void who() {
cout << "I'm in Derived." << endl;
}
};

int main() {
Base b;
Derived d;
//此处的虚函数who,是通过类的具体对象调用的, 编译期就能确定了, 所以它可以是内联的。
b.who();
//此处的虚函数是通过基类的指针/引用调用的, 需要在运行期间才能确定,所以不能为内联。
Base* pb = &d;
Base& rb = d;
pb->who();
rb.who();

return 0;
}

语法上面来讲,在基类和子类的who函数前面加上inline关键字,是可以正常编译,运行的。(编译时编译器会忽略之)

基类采用虚函数是为了防止内存泄漏。

如果派生类中申请了内存空间, 并在其析构函数中对这些内存空间进行释放。 假设基类中采用的是非虚析构函数, 当删除基类指针指向的派生类对象时就不会触发动态绑定,只会调用基类的析构函数。于是, 派生类申请的空间就得不到释放从而产生内存泄漏。
(派生类的析构函数只负责销毁由派生类自己分配的资源)

构造函数不能是虚函数

虚函数通过 虚函数表调用, 而vbtl在构造函数调用后才建立, 因而构造函数不可能是虚函数。

C++ 只会析构已经完成的对象

1.对象只有在其构造函数执行完毕时才算是完全构造妥当, 当构造函数中发生异常, 析构函数不会调用, 可能会造成内存泄漏。
2.如果异常从析构函数抛出, 那个析构函数便是执行不完全的, 即 它没有完成应该执行的每一件事情…

抽象类的作用

  • 带有纯虚函数的类 称为抽象类, 抽象基类负责定义接口, 由其派生类覆盖该接口。
  • C++ 支持两种多态:

1.编译时多态: 通过重载函数来实现;
2.运行时多态: 通过虚函数实现

  • 虚函数是在基类中被声明为virtual, 并在派生类中重新定义的成员函数。

友元函数必须在类内部声明

向上类型转换与向下类型转换 + static_cast 和 dynamic_cast

static_cast (编译时类型检查)

static_cast <type-id> (expression) 该运算符把expression转换为type-id类型。用法:

  1. 用于基本数据类型之间的转换, 转换的安全性由来保证, 如 把int转换为char时, 如果char没有足够的位来存放int的值,那么static_cast所做的只是简单的截断,即简单地把int的低8位复制到char的8位中,直接抛弃高位。
  2. 把空指针转换成目标类型的指针
  3. 把任何类型的表达式转换成void类型
  4. 用于类层次结构中父类和子类之间指针和引用的转换
    对于4. 存在两种形式的转换:
  5. 向上转换(派生类到基类)
  6. 向下转换(基类到派生类)
    static_cast在向上转换时是安全的,向下转换时是不安全的。(之所以说static_cast在下行转换时不安全, 是因为即使转换失败,它也不返回NULL)

dynamic_cast(运行时类型检查)

主要用于类层次结构中父类和子类之间指针和引用的转换。由于具有运行时类型检查,因此可以保证下行转换的安全性(即,转换成功返回转换后的正确类型指针, 如果转换失败, 就返回NULL)。之所以说static_cast在下行转换时不安全, 是因为即使转换失败,它也不返回NULL。

转型失败时

  • 转型对象为引用时, 抛出异常bad_cast
  • 转型对象为指针时, 返回NULL

    使用dynamic_cast时, 该类型必须含有虚函数

    因为dynamic_cast使用了存储在虚函数表中的信息来判断实际的类型。

对于上行转换, static_cast 与 dynamic_cast 一样

下面通过程序说明:

1
2
3
4
5
6
7
8
9
class Base {

virtual void fun() {};

};

class Derived : public Base {

};

由于子类继承于父类, 父类指针可以指向父类对象, 也可以指向子类对象; 如果pb 指向的是子类对象, 则dynamic_cast 和 static_cast 都可以转换成功

1
2
3
Base* pb = new Derived();
Derived* pd1 = static_cast<Derived*> (pb);
Derived* pd2 = dynamic_cast<Derived*> (pb);

如果pb指向的是父类对象

1
2
3
Base* pb = new Base();
Derived* pd1 = static_cast<Derived*>(pb);
Derived* pd2 = dynamic_cast<Derived*>(pb);

转换就是把对象从一种类型转换到另一种类型, 这时,如果用pd1去访问子类中有而父类中没有的成员,就会出现访问越界的错误,导致程序溃。
static_cast在编译时不会报错, 可以返回一个子类对象的指针,但这是不安全的;
dynamic_cast 具有运行时类型检查的功能, 由于上述转换不合理, 所以它返回NULL。

总结

C++中层次类型转换中无非两种:上行转换和下行转换

对于上行转换,static_cast和dynamic_cast效果一样,都安全;

对于下行转换:你必须确定要转换的数据确实是目标类型的数据,即需要注意要转换的父类类型指针是否真的指向子类对象,如果是,static_cast和dynamic_cast都能成功;如果不是static_cast能返回,但是不安全,可能会出现访问越界错误,而dynamic_cast在运行时类型检查过程中,判定该过程不能转换,返回NULL。

内存对齐

参考

如何判断两个浮点数是否相等

lambda

const_cast 去除 指向常数对象的指针或引用的常量性, 其去除常量性的对象必须为指针或引用

隐式转换

是指不需要用户干预, 编译器私下进行的类型转换行为。

  • 基本数据类型: 隐式转换发生在从小 ——> 大的转换中, 比如从char转换为int。(保证精度不丢失)
  • 自定义对象 : 子类对象可以隐式地转换为父类对象。

构造函数声明的时候加上explicit关键字, 能够禁止隐式转换。

如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制。可以通过将构造函数声明为explicit加以制止隐式类型转换。
关键字explicit只对一个实参的构造函数有效,因为需要多个实参的构造函数不能用于执行隐式转换, 所以无需将其指定为explicit.

多重继承 虚继承

模板

在成员函数中调用delete this

类对象的内存空间中, 只有数据成员和虚函数表指针,并不包含代码内容, 类的成员函数单独放在代码段中。
在调用成员函数时,隐含传递一个this指针, 让成员函数知道当前是哪个对象在调用它。 当调用delete this时, 类对象的内存空间被释放。
在这之后进行的其他任何函数调用, 只要不涉及this指针的内容, 都能正常运行, 一旦涉及到this指针, 如操作数据成员、调用虚函数等, 就会出现不可预期的问题。
因为delete this 释放了类对象的内存空间, 但是内存空间却并不是马上被回收到系统中,此时这段内存是可以访问的,但其中的值确是不确定的。当访问数据成员,可能得到的是一长串随机数; 访问虚函数表,指针无效。

在类的析构函数中调用delete this

会导致堆栈溢出。
因为delete本质是 为将被释放的内存调用一个或多个析构函数, 然后释放内存。
显然,delete this 会去调用本对象用本对象的析构函数, 而析构函数中又调用 delete this, 形成无限递归, 造成堆栈溢出, 系统崩溃。

智能指针

union 与 大端小端

大端模式: 是指数据的高字节保存在内存的低地址中, 而数据的低字节保存在内存的高地址端。
小端模式: 是指数据的高字节保存在内存的高地址中, 而数据的低字节保存在内存的低地址端。
理解: 看低字节保存在内存的哪一端

  • 数据的低字节保存在内存的高地址端 : 大端模式
  • 数据的低字节保存至内存的低地址端 : 小端模式

检测方法

  1. 直接读取存放在内存中的十六进制数值, 取第位进行值判断 (存疑)
1
2
3
4
int a = 0x12345678;
int* c = &a;
c[0] == 0x12 //大端模式
c[0] == 0x78 //小端模式
  1. 用union来判断

union所有数据成员是共享一段内存的,后写入的数据成员将覆盖之前的数据成员, 数据成员都有相同的书地址。
union的大小为最大数据成员的大小。
union的数据成员共用内存,并且首地址都是低地址首字节。 int i = 1 时, 大端存储1放在最高位, 小端存储1放在最低位; 当读取char ch 时, 是最低地址首字节。

1
2
3
4
5
6
7
8
9
union w {
int i;
char ch;
};

w p;
p.i = 1;
if (p.ch == 1) cout << "小端模式" << endl;
else cout << "大端模式" << endl;

strcpy和memcpy的区别

  1. 复制的内容不同。 strcpy只能复制字符串, 而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
  2. 复制的方法不同。 strcpy不需要指定长度,它遇到被复制字符的串结束符 “\0”时才结束, 所以容易溢出。memcpy则是ge根据其第三个参数决定复制的长度。
  3. 用途不同。通常在复制字符串时用strcpy, 而需要复制其他类型数据时用memcpy

assert()断言 只在DEBUG下生效

assert是宏, 不是函数。
原型 void assert (int expression);
assert常用于在函数开始处检验传入参数的合法性。 先计算表达式expression然后判断:

  • 表达式为真: 继续运行后面的程序
  • 表达式为假: 先向stderr打印错误信息, 然后通过调用abort来终止程序运行

strcat strcpy strncpy strcmp memset memcpy的内部实现

字符串拷贝strcpy

1
2
3
4
5
6
7
8
9
10
#include <assert.h>
#include <stdio.h>

char* strcpy (char* des, const char* src) {
assert(des!=NULL && src != NULL);
char* address = des;
while ((*des++ = *src++) != '\0');
return address;

}

strlen

des 和 src 所指内存区域不可以重叠且 des 必须有足够的空间来容纳 src 的字符串。

1
2
3
4
5
6
7
8
int strlen (char* str) {
assert(str != NULL);
int i = 0;
while ((*str++) != '\0')
i++;
return i;

}

strcat

des 和 src 所指内存区域不可以重叠且 des 必须有足够的空间来容纳 src 的字符串。

1
2
3
4
5
6
7
char* strcat (char* dest, const char* src) {
assert(dest != NULL && src != NULL);
char ret = dest;
while ((*dest++) != '\0');
while ((*dest++ = *src++) != '\0');
return ret;
}

strcmp

  • 1.字符串1 小于 字符串2, strcmp函数返回一个负值
  • 2.字符串1 等于 字符串2, strcmp函数返回 0
  • 3.字符串1 大于 字符串2, strcmp函数返回一个正值

两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇\0为止。

1
2
3
4
5
6
7
8
int strcmp(const char* dest, cosnt char* src) {
assert(dest != NULL && src != NULL)
while (*dest && *src && *dest = *src ) {
dest++;
src++;
}
return (*dest - *src);
}

空类会默认添加的东西

1
2
3
4
- Empty(); //默认构造函数
- ~Empty(); //析构函数
- Empty(const Empty& );//复制构造函数
- Empty& operator = (const Empty& );//赋值运算符

C++标准库

分为两部分

  • 标准函数库 : 这个库是由通用的、独立的、不属于任何类的函数组成的。函数库继承自C语言。
  • 面向对象类库 :这个库是类及其相关函数的集合。
    C++标准库包含了所有的C标准库,为了支持类型安全做了一定的添加和修改。

    标准函数库

  • I/O
  • 字符串和字符处理
  • 数学
  • 时间、日期和本地化
  • 动态分配
  • 其他
  • 宽字符函数

    面向对象类库

  • 标准的C++ I/O类
  • string 类
  • STL容器类
  • STL算法
  • STL函数对象
  • STL迭代器
  • STL分配器
  • 本地化库
  • 异常处理类
  • 杂项支持库

const char* char* string 三者的相互转化

new operator

  1. new operator 可以分解为 operator new 和 placement new两个动作.
  • operator new 负责申请内存 (类比C的malloc)
  • placement new 用于在给定的内存中初始化对象
  1. delete operator 完成 析构对象和 释放内存的操作
    operator delete 只用于释放内存.(类比C的free)

为什么拷贝构造函数必须传引用不能传值

拷贝构造函数用来初始化一个非引用类类型对象, 如果用传值的方式传参, 那么构造实参需要调用拷贝构造函数, 而拷贝构造函数需要传递实参, 所以会一直递归。

this指针首先入栈, 然后成员函数的参数从右向左入栈, 最后函数返回地址入栈

C++阻止一个类被实例化

  1. 将类定义为抽象基类或者将构造函数声明为private;
  2. 不允许类外部创建类对象, 只能在类内部创建对象。

抽象类和接口

含有纯虚函数的类就叫抽象类, 抽象类只能被继承,不能实例化

1
2
3
4
5
6
7
class A {
private:
int a;
public:
virtual void print() = 0 ;
void fun1() {cout << "fun1";}
}

接口是一种特殊的抽象类

  • 1.类中没有定义任何成员变量
  • 2.类中所有成员函数都是公有且都是纯虚函数
1
2
3
4
5
6
class deviceOp {
public:
virtual bool OpenDev()=0;
virtual void CloseDev()=0;
virtual int ReadDev(char* buf, int len)=0;
}

华为

1.

作者:onceorange
链接:https://www.nowcoder.com/discuss/249864?type=post&order=time&pos=&page=1
来源:牛客网

岗位是上海无线部门的软开。

一面:问了下笔试的一个题的思路。然后问了一些项目的定西,然后写代码,用笔写在纸上,二分查找,写了个递归版的,然后又让写个循环版的。

二面:上来先写一个,求一个二叉树节点的最大值。我用先序遍历写了一个。

然后加强版,求一个二叉树第k大的节点值,我用小顶堆+先序遍历写了一个,面试官看了说了声“我去,可以啊”。

然后面试官说 再问你一个进阶的,给你一个排序二叉树,升序输入里面节点值的

我说 中序遍历不就行了吗 面试官露出满意笑容。

三面 技术主管面 面试官感觉是个大佬 气场非常强 搞得我有点紧张 不过他透露出前两面面试官都是他的人 对我评价很不错。聊了聊天就结束了。

评价:华为效率很高,我八点半到,11点半三轮面试就结束了。面试官都很有礼貌,来了和走的时候会和你握手,而且都面带笑容。

手撕代码难度不大,注意细节要写好,比如楼主就忘记引用&这个符号怎么写了,还跟面试官请教了一下。

感觉今年确实激烈一点,中间淘汰了很多人,到达终面的远没有刚来的人多。

2.

广联达

leetcode 11.
leetcode 983.
leetcode 130.

0%