C++程序员面试题库(一)——基本语法

1.C++语言

  1、什么是内存溢出

  内存溢出(Out Of Memory,简称OOM)是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。此时程序就运行不了,系统会提示内存溢出。
造成这种现象的原因通常有两种:
  第一种是由于长期保持某些资源的引用,垃圾回收器无法回收它,从而使该资源不能够及时释放,也称为内存泄露
  另外一种是当需要保存多个耗用内存过大或当加载单个超大的对象时,该对象的大小超过了当前剩余的可用内存空间。

  2、c++中内存的五大区

  在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
  栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除变量的存储区。里面的变量通常是局部变量、函数参数等。
  堆,就是那些由malloc等分配的内存块,用free来结束自己的生命的。
  自由存储区,就是那些由new分配的内存块,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
  全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
  常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改 。

堆区和自由存储区的区别与联系
(1)malloc申请的内存在堆上,使用free释放。new申请的内存在自由存储区,用delete释放
(2)堆(heap)是c语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,
当程序运行时调用malloc()时就会从中分配,调用free可把内存交换。而自由存储区是C++中通过new和delete
动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器
默认用堆来实现自由存储区,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来实现,
这时由new运算符分配的对象,说它在堆上也对,说它在自由存储区也对。
记住:
(1)堆是c语言和操作系统的术语,是操作系统维护的一块内存。自由存储是C++中通过new和delete动态
分配和释放对象的抽象概念。
(2)new所申请的内存区域在C++中称为自由存储区,编译器用malloc和free实现new和delete操作符时,
new申请的内存可以说是在堆上。
(3)堆和自由内存区有相同之处,但并不等价。

  3、智能指针

  C++中有四个智能指针:auto_ptr,shared_ptr,unique_ptr,weak_ptr。其中后三个是C++11支持,并且第一个已经被11弃用了。
  为什么使用智能指针:
  智能指针的作用就是管理一个指针,因为存在以下情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上避免这个问题,因为智能指针是一个类,当超出了类的作用域时,会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

  1. auto_ptr(C++98方案,C++11被抛弃)
  采用所有权模式。

如:auto_ptr< string > p1(new string ("Hello"));
auto_ptr< string> p2;
p2=p1;

  此时不会报错,p2剥夺了P1的所有权,并且当访问p1的时候会报错。所以auto_ptr的缺点:存在潜在的内存崩溃问题。

  2、 unique_ptr (替代auto_ptr)
  也是采用所有权模式(单一所有权,所以无法被拷贝)。但是它实现独站式拥有或严格拥有的概念,保证同一时间内只有一个智能指针可以指向该对象。

如:unique_ptr < string > p1(new string ("Hello"));
unique_ptr < string> p2;
p2=p1;//此时会报错

编译器认为p2=p1非法,也就避免了p1指向无效数据的问题。比auto_ptr更安全。
  另外,当unique_ptr是临时右值时,编译器允许赋值。比如:

#1 unique_ptr < string > p1(new string ("Hello"));
unique_ptr < string> p2;
p2=p1;//此时会报错
#2 unique_ptr < string> p3;
p3=unique_ptr <string> (new string ("Hello"));//此时是允许的

在#2 中创建的临时对象在所有权让给p3后就会被销毁。
  如果非要执行类似#1 的操作那么需要借助库函数std::move。

unique ptr< string > p1,p2;
p1=demo("hello");
p2=move(p1);
p1=demo("you");//此时所有权转让给p2后可以继续赋值使用。

  3、 shared_ptr
  shared_ptr实现共享是拥有的概念。多个指针可以指向相同的对象,该对象及其相关资源会在“最后一个引用被销毁”时候释放。它采用计数机制来表明资源被几个指针共享。 可以通过use_count()来查看资源的所有者的个数。
  除了使用new来构造shared_ptr,还可以通过传入auto_ptr,unique_ptr,weak_ptr来构造。
  shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

  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。

  考官问道,大多是考察shared_ptr的,可以这样答:
  智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11 中最常用的 智能指针类型为 shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。 该引用计数的内存在堆上分配。当新增一个时引用计数加 1,当过期时引用计数减一。只有引用 计数为 0 时,智能指针才会自动释放引用的内存资源。对 shared_ptr 进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过 make_shared 函数或者通 过构造函数传入普通指针。并可以通过 get 函数获得普通指针。

  4、野指针

  野指针的定义:野指针指向一个已删除的对象或未申请访问受限内存区域的指针。
  与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。需对指针进行初始化。
  为什么会出现野指针:
  1.指针变量未初始化
任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存(int *p = &a, 这就是指向合法的内存了。)。如果没有初始化,编译器会报错“ ‘point’ may be uninitializedin the function ”。
  2.指针释放后未置空
有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。
看到2我明白了,上面说的:对C++来说,内存泄漏就是new出来的对象没有delete,俗称野指针。我觉得不太准确,内存泄漏说的是为对象申请的内存未被释放,野指针说的是指向已被删除的或者未申请受限访问内存的指针。怎么能说内存泄漏俗称野指针呢?这就像说保险柜没清理(内存泄漏)和指向特殊保险柜的指示牌(野指针)是一回事一样。
  3.指针操作超越变量作用域
就是在你超过作用域后,内存已经释放,这时指向那个被释放的内存的指针就是野指针,你却还调用,这样不行。

  5、static关键字

  1、全局静态变量
  在全局变量前加上关键字static,全局变量就定义成一个全局静态变量。
  存放在静态存储区,在整个程序运行期间一直存在。
  初始化:未经初始化的全局静态变量会自动初数化为0。
  作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义开始,到文件结尾。(只需在一个源文件中定义,就可以作用于所有的源文件。)
  2、局部静态变量
  在局部变量前加上关键字static,局部变量就定义成一个局部静态变量。
  内存中的位置:静态存储区。
  初始化:未经初始化的局部静态变量会自动初数化为0。
  作用域:作用域是局部作用域。当定义它的函数或者语句块结束的时候,作用域结束。但当局部变量离开作用域后,并没有被销毁,而是驻留在内存中,直到该函数再次被调用,并且值不变。
  3、静态函数
  在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都
是 extern 的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。并且该函数只能在本cpp内使用,不会同其他cpp中的同名函数冲突。
   warning: 不要再头文件中声明 static 的全局函数(头文件声明的可以被其他cpp复用,如果是static就没必要声明在头文件,在该cpp文件声明就行)。
  不要在 cpp 内声明非 static 的全局函数(在头文件声明),如果你要在多个 cpp 中复用该函数,就把它的声明提到头文件里去,否则 cpp 内部声明需加 上static 修饰。
  4、类的静态成员
  在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态成员还不会破坏隐藏的原则,保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用。
  5、类的静态函数
  静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
  在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);  (需要加上类名)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值