C++ 面试宝典——基础知识(一)
C++ 面试宝典——基础知识(一)
一、static关键字的作用
- 全局静态变量
在全局变量前加上关键字static,全局变量就定义成一个全局静态变量.
静态存储区,在整个程序运行期间一直存在。
初始化:未被预先赋值的全局静态变量会隐式地赋值为零(在没有显式指定的情况下,默认初始值为零)
作用域:全局静态变量位于其声明的文件之外是不可见的。具体来说,在这种作用域中自定义位置开始延伸至整个程序结束。
- 局部静态变量
在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。
内存中的位置:静态存储区
初始化:未进行显式初始化的局部静态变量对象会被系统默认地赋值为0(未被显式赋值的对象其初始值是任意的),除非他们被显式赋值。
范围:范围仍属于局部作用域范畴,在该函数或语句块完成定义后即告终止。然而,在局部静态变量退出该范围后并未被销毁而是持续存在于内存中直至再次被调用的那一刻,并且其值保持不变;
- 静态函数
为了使函数返回类型前加上static而成为静态函数,在C语言中可以通过添加关键词static来实现这一点。通常情况下,在C语言中,默认情况下,一个函数的定义和声明都是通过extern关键字来限定其可见范围的。然而,在这种情况下,
静态函数仅限于其声明所在的文件中可见,
无法被其他包含该声明的文件所访问或使用。
通过静态修饰符static来定义一个函数,则该函数仅限于当前cpp文件内部使用,在此范围内能够避免与其他具有相同名称的函数在同一cpp文件中产生的冲突。
warning信息:不再在头文件中声明带有static属性的全局函数;避免在.cpp源文件内直接声明非静态全局函数;如果需要在同一项目的多个.cpp文件中复用该函数,则建议将它的声明提升到头文件部分;否则,在.cpp源文件内部需添加static修饰词以确保代码的一致性和可维护性。
- 类的静态成员
在类中定义的静态字段能够支持不同实例之间数据的一致性与协调,在不违反封装原则的前提下确保系统运行的安全性由此可见所有实例共享的属性和方法并非每个实例独有的属性和方法从这个角度看对多个对象而言静态数据字段仅存于一处以便所有实例能够访问使用
- 类的静态函数
静态成员函数与静态数据成员类似,在类中均属于其静态属性;它们均不具备作为实例属性的特征。因此,在引用这些静态属性时无需使用实例化的对象名。
在实现一个类中的静态方法时无法直接访问该类中的非静止方法(non-static member),但可以显式地访问其静止方法(static member)(这一点至关重要)。当在一个静止方法内部想要访问非静止方法时,则需通过实例对象对其进行访问(access)。由此可知,在C++中调用静止方法时采用以下语法格式:ClassName::staticMethodName(ParamList);
二、C++和C的区别
设计思想上:
C++是面向对象的语言,而C是面向过程的结构化编程语言
语法上:
C++由封装、继承和多态三种特性组成
相较于C语言,C++新增了大量类型安全的功能,例如强制类型转换
C++采用了范式编程的方式,例如提供模板类和函数模板等
三、c++中四种cast转换
C++中四个类型转换分别是:static_cast, dynamic_cast, const_cast, reinterpret_cast
1、const_cast
用于将const变量转为非const
2、static_cast
用于多种隐式转换操作中,在特定条件下实现类型间的自动过渡。例如,在将non-const对象转换为const对象时(如将void*类型的指针强制赋值给const指针变量),static_cast运算符能够有效地执行多态向上转型操作(即从基类指向派生类对象的转变)。然而,在这种情况下(即向下转换),如果操作能够成功完成,则可能带来不安全的风险(结果不确定)。
3、dynamic_cast
基于动态类型转换的技术实现是一种高级功能特性。这种技术仅适用于包含纯虚函数的对象类型,并且可以在类层次之间进行向上和向下转换操作。该功能仅能转换指向器或引用对象,在执行下层转换时应特别注意:当指针无效时返回NULL值;而引用操作则会抛出异常信息以提示潜在的问题所在位置。为了确保系统的稳定性和正确性,请深入研究其内部机制。
向上转换:指的是子类向基类的转换
向下转换:指的是基类向子类的转换
该程序在处理某个语句时识别变量的运行时类型属性与目标数据类型的属性是否一致的基础上决定是否允许进行类型转换。
4、reinterpret_cast
几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;
5、为什么不使用C的强制转换?
C的强制转换表面看上去拥有显著的功能特性,并且能够实现任何类型的数据转换。然而其数据转换过程不够清晰无法对转换过程中的异常情况进行检测容易导致转换过程中出现错误。
四、C/C++ 中指针和引用的区别?
1.指针有自己的一块空间,而引用只是一个别名;
2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
- 指针可以被赋值为NULL;而引用则需先进行赋值,并且只能是已存在的对象的引用。这在编程中尤其重要:例如,在编写代码时应确保变量已被定义以避免运行时错误。
当以参数形式传递时,在操作对象之前必须先解除对应的引用。然而,在这种情况下直接修改引用来的内容会导致其指向对象的内容发生变化
5.可以有const指针,但是没有const引用;
当使用指针时可以指向其他对象;然而引用只能作为一个对象的引用,并不可以更改。
7.指针可以有多级指针(**p),而引用至于一级;
8.指针和引用使用++运算符的意义不一样;
函数返回由动态内存分配的内存块或系统资源中的可用空间时,必须使用相应的指针变量,并避免直接引用以防止资源泄漏。
五、希望阐述您对于C++中四种智能指针的理解?它们分别是shared_ptr、unique_ptr、weak_ptr和auto_ptr。
在C++中存在四种智能指针:auto_ptr、shared_ptr、weak_ptr和unique_ptr;其中后三者自C++11起被支持,并且第一个已被弃用。
为什么要使用智能指针:
智能指针的主要功能是负责管理一个指向器。由于存在如下情况:当申请的空间在函数退出后未被释放而导致内存泄漏的问题。通过使用智能指针可以在很大程度上减少这一问题的发生。当超出类的作用域时会自动触发析构函数执行该析构函数将负责释放相关资源。因此其工作原理在于能够自动在对象生命周期结束时完成内存清理任务无需人工进行手动内存管理操作。
- 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不会报错.
此时不会触发错误;在程序运行期间访问对象p1会导致错误;一旦程序运行时试图访问对象p1将导致错误;因此auto_ptr的一个主要缺陷在于其可能导致内存泄漏的问题。
- unique_ptr(替换auto_ptr)
unique_ptr遵循独占式持有机制或严格持有机制,并被设计成确保只有一个智能指针能访问该对象。它特别适用于防止资源泄漏的情况(如未及时释放动态内存)。
采用所有权模式,还是上面那个例子
unique_ptr<string> p3 (new string ("auto")); //#4
unique_ptr<string> p4; //#5
p4 = p3;//此时会报错!!
编译器将p4=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避免了这一问题的原因在于其通过构造函数创建的临时对象在其所有权转移至pu3后会自动销毁。这种基于具体情况的行为模式表明,在内存管理和资源释放方面,unique_ptr的表现优于支持两种赋值操作的auto_ptr。
如若希望重复#1的操作,则必须谨慎地重复使用该指针,并通过赋予其新值来实现这一目标。C++提供了一种高效的方法——通过使用std::move()函数——使得这一操作得以实现。例如:
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
- shared_ptr
shared_ptr 采用共享持有器模式。若干个智能指针可能指向同一个对象,在"最后一个引用被销毁"时刻会自动释放该对象及其所有相关联的资源。从名字'share'可以看出这种设计使得多个指针对同一个对象进行访问非常方便。它使用计数值机制以表示有多少个指针同时持有这个资源。通过成员函数use_count()可查询拥有该资源的实例数量。除了通过 new 操作符创建之外还允许传入 auto_ptr、unique_ptr 或 weak_ptr 来初始化实例。调用 release() 后会释放当前持有器的所有权并将计数值减一。
shared_ptr 旨在克服 auto_ptr 在对象所有权方面的限制(后者为独占性质),通过采用引用计数机制的方式实现了对所有权的有效共享功能。
成员函数:
use_count 返回引用计数的个数
unique 返回是否是独占所有权( use_count 为 1)
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 会丢弃其所有的权限或操作对其做出修改的能力, 导致原有对象被引用的数量减少
成员函数 get 返回内部对象(用作指针),因为实现了括号运算符运算符重载, 使用 get 和直接访问对象效果一致.例如 shared_ptr
- weak_ptr
weakptr 是一种不自主管理对象生命周期的智能指针 ,它所指向的对象由 strong reference 的 sharedptr 实现内存管理 。 weakptr 的主要目的是为了配合 strongptr 引入的一种智能指针用于辅助其工作 ,它只能从 strongptr 或 weakptr 对象中构造出来 ,并且其构造与析构过程不会影响引用计数的变化情况 。若两个 strongptr 相互引用 ,则它们各自的引用计数永远不可能降至零 ,导致资源无法被正确释放 。 weakptr 是一种弱引用方式且不影响对象引用计数 ,并可与 strongptr 之间实现转换关系 ,其中 strongptr 可直接赋值给 weakptr 对象 ,而 weakptr 则可通过调用 lock 函数来获取相应的 strongptr 。
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之间存在相互引用的关系,在程序执行过程中会动态维护这两个资源的引用计数。当程序正常运行时,在调用智能指针pa、pb的析构函数之前这两个资源的引用计数初始化为2值。然而,在程序退出或函数返回时(即跳出函数),智能指针pa、pb析构时会使得这两个资源的引用计数分别减1。尽管如此,在这种情况下两个资源的实际引用计数值仍然维持在1的状态(因为它们各自的析构操作并未被调用),导致最终程序无法完全释放这些资源(因为A类的对象B没有被弱指针修饰)。为此我们可以采取另一种方案:将类A中的shared_ptr pb_改为weak_ptr pb_ 。经过这样的修改后运行程序可以看到,在初始状态下由于B对象只有1个实例指向它因此它的引用计数值是1值。而一旦B对象实例被析构(即pb指针对应的对象消失)那么该对象所持有的唯一一个引用计数值就会被清零从而触发自动释放机制此时A对象所持有的唯一一个引用来连接B对象也会随之减少一个从而最终使得A对象也被成功释放
需要注意的是我们无法直接通过weak_ptr获取对象的方法。例如,在B对象中有一个方法print()我们可以尝试这样做就会导致错误。其中英文pb_是一个weak_ptr我们应该将其转换为shared_ptr的方式即pa -> pb_ -> lock()-> cast<shared_ptr>() -> print()。
六、怎么判断一个数是二的倍数,怎么求一个数中有几个1,说一下你的思路并手写代码
1、判断一个数是不是二的倍数,即判断该数二进制末位是不是0:
a % 2 == 0 或者a & 0x0001 == 0。
2、求一个数中1的位数,可以直接逐位除十取余判断:
int fun(long x)
{
int _count = 0;
while(x)
{
if(x % 10 == 1)
++_count;
x /= 10;
}
return _count;
}
int main()
{
cout << fun(123321) << endl;
return 0;
}
七、数组和指针的区别
指针和数组的主要区别如下:
| 指针 | 数组 |
|---|---|
| 保存数据的地址 | 保存数据 |
| 间接访问数据,首先获得指针的内容,然后将其作为地址,从该地址中提取数据 | 直接访问数据 |
| 通常用于动态的数据结构 | 通常用于固定数目且数据类型相同的元素 |
| 通过Malloc分配内存,free释放内存 | 隐式的分配和删除 |
| 通常指向匿名数据,操作匿名函数 | 自身即为数据名 |
八、野指针是什么
野指针是指指向被回收对象或不被限定内存区域的指针
九、智能指针有没有内存泄露的情况
当两个对象之间相互建立共享指针成员变量的引用关系时,在这种情况下会产生循环引用现象。这会导致使得引用计数机制失效,并最终引发内存泄漏问题。
十、智能指针的内存泄漏如何解决
为了消除循环引用引发的内存泄漏问题,在编程语言中我们引入了一个被称为 weak_ptr 的弱指针。该 weak_ptr 的构造函数并不会修改任何引用计数值这一事实意味着它不会负责任何对象的实际内存分配工作。然而它却能类似于普通的非共享指针并提供一种机制来检测所管理对象是否已被释放从而有效防止非法访问行为的发生。
