本文已参与「新人创作礼」活动,一起开启掘金创作之路。
1.请你介绍一下C++/C的内存分配。
32bit CPU可寻址4G线性空间,每个进程都有各自独立的4G逻辑地址,其中0—3G是用户态空间,3—4G是内核空间,不同进程相同的逻辑地址会映射到不同的物理地址中。 其逻辑地址其划分如下: 3G用户空间和1G内核空间 ①静态区域: text segment(代码段):包括只读存储区和文本区,其中==只读存储区存储字符串常量==,文本区存储程序的机器代码。 data segment(数据段):存储程序中已初始化的全局变量和静态变量。 BSS segment:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量,对于未初始化的全局变量和静态变量,程序运行main之前时会统一清零。 ②动态区域: heap(堆): 当进程未调用malloc时是没有堆段的,只有调用malloc时采用分配一个堆,并且在程序运行过程中可以动态增加堆大小(移动break指针),从低地址向高地址增长。分配小内存时使用该区域。 堆的起始地址由mm_struct结构体中的start_brk标识,结束地址由brk标识。 memory mapping segment(映射区):存储动态链接库等文件映射、申请大内存(malloc时调用mmap函数) stack(栈):使用栈空间存储函数的返回地址、参数、局部变量、返回值,从高地址向低地址增长。在创建进程时会有一个最大栈大小,Linux可以通过ulimit命令指定。
2.请你谈谈smart pointer:
shared_ptr,unique_ptr,weak_ptr,auto_ptr
C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr. 其中后三个是c++11支持,并且第一个已经被11弃用。
为什么要使用智能指针: 智能指针的作用是更安全的管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
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不会报错. 此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题! 2. unique_ptr(替换auto_ptr) unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。 采用所有权模式,还是上面那个例子
unique_ptr<string> p3 (new string ("auto")); //#4 unique_ptr<string> p4; //#5 p4 = p3;//此时会报错!! 编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。 另外unique_ptr还有更聪明的地方:当程序试图将一个unique_ptr赋值给另一个时,如果源unique_ptr是个临时右值,编译器允许这么做;如果源unique_ptr将存在一段时间,编译器将禁止这么做,比如: 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留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权让给pu3后就会被销毁。这种随情况而已的行为表明,unique_ptr优于允许两种赋值的auto_ptr。 注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如: unique_ptr<string> ps1, ps2; ps1 = demo("hello"); ps2 = move(ps1); ps1 = demo("alexia"); cout << *ps2 << *ps1 << endl; 3. shared_ptr shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。 shared_ptr是为了解决auto_ptr在对象所有权上的局限性(auto_ptr是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针。 成员函数: use_count返回引用计数的个数 unique返回是否是独占所有权( use_count为1) swap交换两个shared_ptr对象(即交换所拥有的对象) reset放弃内部对象的所有权或拥有对象的变更,会引起原有对象的引用计数的减少 get返回内部对象(指针),由于已经重载了()方法,因此和直接使用对象是一样的.如shared_ptr<int> sp(new int(1)); sp与sp.get()是等价的 4. weak_ptr weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象.进行该对象的内存管理的是那个强引用的shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr设计的目的是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它只可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。 它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化, shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr.
class B; class A { public: shared_ptr<B> pb_; ~A() { cout<<"A delete\n"; } }; class B { public: shared_ptr<A> pa_; ~B() {cout<<"B delete\n"; } }; 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; } 可以看到fun函数中pa,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_;改为weak_ptr pb_;运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。 注意的是我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print();英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();
输入一个链表,输出该链表中倒数第k个结点。
思路:双指针法,先让第一个指针走k步,如果还没走到k步,就走完了,返回空。不然,等第一个指针走到k步之后,让第二个指针开始一起走,当第一个指针走到最后一位时,第一个指针的位置就是倒数第k个。
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {}
};
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode * first=pListHead;
ListNode * second = pListHead;
int i=1;
for(i=1;i<k;i++){
if(first!=NULL){
first = first->next;
}
else{
break;
}
} //让第一个指针走到第k个结点
if(i<=k){
return NULL; //如果第一个指针没走到第k个结点,返回空。
}
while(first!=NULL){
first=first->next;
second = second->next;
}
return second;
}
};
不敢相信,AI,牛客网都做的这么好。