本篇笔记,记录剑指offer
中的问题(不只是面试题)。
对某些面试题,同时也指出leetcode
对应题目的网址
:dagger:需要对文档进行补充,代码解释等
注意点:
基本素质:基础知识、高质量代码、清晰的思路、优化效率的能力、综合能力
面试问题摘记:
C++中四个与类型转换相关的关键字
背景:在C/C++语言中用 (type) value(在C++还可以采用type(value))来进行显式类型转换(explicit type conversion),常常又被称为强制转换(cast投射/铸模)。这种转换的正确性完全掌握在程序员手中,传统上强制转换往往被过度使用,成为C++程序犯错的一个主要根源。
为了减少强制转换的副作用,并且在查错时使程序员能够快速定位(总是最值得怀疑的)强制转换,在标准C++中新增加了4个关键字*_cast,用来提倡一种全新的C++显式转换语法:
*_cast
问:C++中,有哪四个与类型转换相关的关键字?
答:static_cast, const_cast, dynamic_cast, reinterpret_cast
static_cast, 运算符完成相关类型之间类型的转换
静态转换,在编译处理期间
C++内置基本数据类型之间的转换。但是没有运行时类型的检测来保证转换的安全性。
用法:
- 子类指针转换为基类是安全的,基类转换为字类就是不安全的。
- 基本数据类型之间的转换,int->char。
- 空指针转换为目标类型的空指针。
- 任何类型的表达式转换成void类型。
1
2l = static_cast<long>(i);//i是整型,l是长整型。
f = static_cast<float>(i);//在传统方法下,可以进行隐式转换。1
2
3
4i = static_cast<int>(l);//i是整型,l是长整型,f是浮点型,c是char。
i = static_cast<int>(f);// i = l,i = f会告警,且会丢失数据。
c = static_cast<char>(i);//i = (int)l,不会告警,但是程序debug困难。const_cast
去常转换,编译时执行
const_cast操作不能在不同的种类间转换。相反,它仅仅把它作用的表达式转换成常量。它可以使一个本来不是const类型的数据转换成const类型的,或者把const属性去掉。
用法:
- 常量指针被转化成非常量的指针,并且仍然指向原来的对象
- 常量引用被转换成非常量的引用,并且仍然指向原来的对象
1
2
3
4
5
6
7
8const int i = 0;
int *pi;
pi = &i; // 错误
pi = (int *)&i; // 被反对
pi = const_cast<int *>(&i); // 完美,将常量指针转换成非常量指针
long *pl = const_cast<long *>(&i); // 错误,要求是同数据类型
volatile int k = 0;
int *pk = const_cast<int *>(&k); // 正确
dynamic_cast => dynamic_cast
(expression) 基类必须有虚函数,即为多态时,可以转换
将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理.
如果 type-id 是类指针类型,那么expression也必须是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。
- dynamic_cast运算符可以在执行期决定真正的类型。如果 downcast 是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果 downcast 不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
dynamic_cast(动态转换):一种安全的向下类型转换(downcast)操作,用于在一个类继承层次上向下移动。
1
2
3
4
5
6
7
8class Pet {……};
class Dog : public Pet {……};
class Cat : public Pet {……};
……
Pet *pPet = new Cat; // 向上的类型转换
Dog *pDog = dynamic_cast<Dog *>(pPet); // 类型错误,返回0(NULL)
Cat *pCat = dynamic_cast<Cat *>(pPet); // 类型正确,返回指针
Cat *pCat = static_cast<Cat *>(pPet); // 正确,减少运行时的开销
reinterpret_cast
(expression) 使用reinterpret_cast通常是一种不明智且不方便的编程方式。但是在必须使用时,它也是非常有用的。
type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。
1 | const int sz = 100; // 定义数组大小,标准C++提倡用常型变量(而不是常数或 |
参考资料:
C++中,有哪四个与类型转换相关的关键字
C++中,有哪4个与类型转换相关的关键字?
空类型的大小
1问:一个空类型里面没有定义任何成员变量、成员函数,对该类型的求sizeof,得到的结果是什么?
答:为1,但是也根据编译器改变,vs当中是1。
解释:按理应该求得sizeof为0,但是在声明该类型的时候,他在内存中必须有一定的空间,否则无法使用这些实例。
2问:如果添加构造函数和析构函数呢?
答:还是1,调用这两个函数只需要两个函数的地址,而他们只和类型相关,和实例无关。
3问:如果上面的析构函数是虚函数呢?
答:如果是虚析构函数,那么会为该类型生成虚函数表,并在该类型当中添加一个指向虚函数表的指针。32位系统中一个指针占4字节,sizeof=4。64位系统中sizeof的得到的值位8。
拷贝构造函数
拷贝构造函数的传入参数必须是A(const&A a), 参数必须当成常量引用
赋值运算符函数
题目:为类型CMyString
的声明添加赋值运算符函数。
1 | class CMyString |
单例 C++ 几种实现
实现一:线程不安全
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Singleton{
public:
static Singleton* getInstance(){
if(m_instance == nullptr){
m_instance = new Singleton();
}
return m_instance;
}
private:
Singleton();
Singleton(const Singleton& other);
static Singleton* m_instance;
};
Singleton* Singleton::m_instance=nullptr;
/*
正常情况下,如果线程A调用getInstance()时,将m_instance 初始化了,那么线程B再调用getInstance()时,就不会再执行new了,直接返回之前构造好的对象。然而存在这种情况,线程A执行m_instance = new Singleton()还没完成,这个时候m_instance仍然为nullptr,线程B也正在执行m_instance = new Singleton(),这是就会产生两个对象,线程A和B可能使用的是同一个对象,也可能是两个对象,这样就可能导致程序错误,同时,还会发生内存泄漏。
*/实现二:线程安全,锁代价高
1
2
3
4
5
6
7Singleton* Singleton:getInstance(){
Lock(); //每次都会lock锁代价高
if(m_instance == nullptr);
m_instance = new Singleton;
Unlock();
return m_instance;
}实现三:线程安全,低锁代价
1
2
3
4
5
6
7
8
9Singleton* Singleton:getInstance(){
if(m_instance == nullptr){
Lock();
if(m_instance == nullptr);
m_instance = new Singleton;
Unlock();
}
return m_instance;
}实现四:解决方案三的bug,java/C#提出volatile,也就是把m_instanced用volatile声明,避免reorder。
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/*
这个方法没有进行深入研究
*/
//C++ 11版本之后的跨平台实现
// atomic c++11中提供的原子操作
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
/*
* std::atomic_thread_fence(std::memory_order_acquire);
* std::atomic_thread_fence(std::memory_order_release);
* 这两句话可以保证他们之间的语句不会发生乱序执行。
*/
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}实现五:pthread_once函数
在linux中,
pthread_once()
函数可以保证某个函数只执行一次。声明: int pthread_once(pthread_once_t once_control, void (init_routine) (void));
功能: 本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Singleton{
public:
static Singleton* getInstance(){
// init函数只会执行一次
pthread_once(&ponce_, &Singleton::init);
return m_instance;
}
private:
Singleton(); //私有构造函数,不允许使用者自己生成对象
Singleton(const Singleton& other);
//要写成静态方法的原因:类成员函数隐含传递this指针(第一个参数)
static void init() {
m_instance = new Singleton();
}
static pthread_once_t ponce_;
static Singleton* m_instance; //静态成员变量
};
pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT;
Singleton* Singleton::m_instance=nullptr; //静态成员需要先初始化
实现六:C++11版最简洁跨平台方案
使用局部静态变量
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
29class Singleton{
public:
// 注意返回的是引用
static Singleton& getInstance(){
static Singleton m_instance; //局部静态变量
return m_instance;
}
private:
Singleton();
Singleton(const Singleton& other);
};
//模板类
template<typename T>
class Singleton
{
public:
static T& getInstance() {
static T value_; //静态局部变量
return value_;
}
private:
Singleton();
~Singleton();
Singleton(const Singleton&); //拷贝构造函数
Singleton& operator=(const Singleton&); // =运算符重载
};sizeof获取数组大小
1 | int GetSize(arr[] a){ |
面试题3:数组中重复的数字
1 | class Solution { |
面试题4:二维数组中查找
解决该问题的关键是,从右上角开始找,因为右上角的数据!=目标数据的时候,大于target和小于target可以分成不同情况进行排除某行某列,从而不断进一步找到目标数,但是从左上角就无法分成两种情况。
1 |
|
面试题5:替换空格
题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
知识:从前往后替换的话,时间效率低(n2),需要重复遍历后面的字符。所以可以考虑从后面向前替换新增字符。
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Solution {
public:
void replaceSpace(char *str,int length) {
if(str == nullptr || length < 0) // 错误输入处理
return;
size_t count_space = 0;
for (size_t i = 0; i < length; i++)
{
/* code */
if(*(str+i) == ' ')
count_space++;
}
// info :printf("%d\n",count_space);
char* retstr = new char[length + count_space * 2];
retstr[length + count_space * 2] = '\0';
/**
* 执行从后替换逻辑
*
*/
// debug :length = 14
unsigned ptr = length + count_space * 2 - 1;
for (size_t i = length -1 ; i <= ptr; i--)
{
// debug :printf("%s \n",str+i);
/* code */
if( *(str + i) == ' ' ){
*(retstr + ptr) = '0'; ptr--;
*(retstr + ptr) = '2'; ptr--;
*(retstr + ptr) = '%'; ptr--;
}
else
{
/* code */
*(retstr + ptr) = *(str +i);
ptr--;
}
}
printf(" retstr is :%s \n",retstr);
}
};
面试题6、7、8、9、10
快速排序
O(n)的排序算法(有条件)
1 | void SortAges(int ages[],int lengh){ |
面试题11:旋转数组的最小数字
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组
[0,1,2,4,5,6,7]
可能变为[4,5,6,7,0,1,2]
)。请找出其中最小的元素。
你可以假设数组中不存在重复元素。
1 |
|
面试题12:回溯法-矩阵中的路径
Leetcode
中对应:单词搜索
1 | /* |
面试题13:回溯法-机器人的运动范围
1 | //#define ROBOTMOVERANGE |
面试题14:贪婪-剪绳子
1 |
|
面试题15: 一个数字中某一位为1的个数
1 | /* |
面试题16:一个数的整数次方
1 | /* |
面试题17:打印1-n位数的最大整数
1 |
|
面试题18:删除链表的节点
补充学习
链表
hash表实现
二叉树
红黑树
B+ tree