c++常见问题

1 基本概念

1.1 变量的声明和定义有什么区别

变量的定义为变量分配地址和存储空间, 变量的声明不分配地址。一个变量可以在多个地方声明, 但是只在一个地方定义。
加入extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。

1.2 c++中内存分配情况

c++中内存一共分为五个区,堆区、栈区、全局/静态变量存储区、常量存储区、程序代码区。
栈区的特点:
由操作系统自动分配和释放,可分配内存小、速度快。
堆区的特点:
由程序员自行分配和释放,可分配内存大、速度慢。

1.3 全局变量和局部变量有什么区别?操作系统和编译器是怎么知道的?

(1)全局变量是整个程序都可访问的变量,生存期在整个程序从运行到结束,在程序结束时所占内存释放;
(2)局部变量存在于模块(子程序,函数)中,只有所在模块可以访问,其他模块不可直接访问,模块结束(函数调用完毕),局部变量消失,所占据的内存释放。
(3)操作系统和编译器通过内存分配的位置来知道的,全局变量分配在全局存储区,在程序开始运行的时候被加载,局部变量则分配在堆/栈里面。

1.4 形参与实参的区别

(1)形参只有被调用时才分配内存,调用结束时, 即刻释放所分配的内存,因此,形参只在函数内部有效。
(2)在函数调用时,实参必须有确定的值,以便把这些值传送给形参。
(3)实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。
(4)函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。

①. 值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象 或是大的结构体对象,将耗费一定的时间和空间。(传值)
②. 指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。(传值,传递的是地址值)
③. 引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。(传地址)
④. 效率上讲,指针传递和引用传递比值传递效率高。一般主张使用引用传递,代码逻辑上更加紧凑、清晰。

1.5 指针和引用的区别

(1)指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间;
(2)使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
(3)引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变;
(4)可以有多级指针(**p),没有多级引用;
(5)指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用是引用的变量值加1;
(6)引用访问一个变量是直接访问,而指针访问一个变量是间接访问;
(7)作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引用的实质是传地址,传递的是变量的地址;

1.6 C++中的指针参数传递和引用参数传递

(1) 指针参数传递本质上是值传递,它所传递的是指针的地址
值传递过程中,被调函数的形参作为局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从而形成了实参的一个副本。被调函数对形参的任何操作都是作为局部变量进行的,不会影响主调函数的实参的值(形参指针变了,实参指针不会变)。
(2) 引用参数传递本质是传地址,它所传递的是实参变量的地址。
引用参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。

1.7 深拷贝与浅拷贝

(1)浅拷贝:仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅拷贝出来的对象也会相应改变。
(2)深拷贝:在计算机中开辟了一块新的内存地址用于存放复制的对象。

2 关键字与运算符

2.1 #ifdef、#else、#endif和#ifndef的作用

利用#ifdef、#endif将某程序功能模块包括进去,以向特定用户提供该功能。在不需要时用户可轻易将其屏蔽。

2.2 define 和typedef的区别

define:

  1. 只是简单的字符串替换,没有类型检查
  2. 是在编译的预处理阶段起作用
  3. 不分配内存,有多少次使用就进行多少次替换

typedef:

  1. 有对应的数据类型,是要进行判断的
  2. 是在编译、运行的时候起作用
  3. 在静态存储区中分配空间,在程序运行过程中内存中只有一个拷贝

2.3 extern用法

(1)extern修饰的变量和函数可以在其他文件中调用。
(2)在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。

2.4 inline

inline修饰的函数为内联函数
使用场景:函数体很小,且被频繁调用。
使用方法:在函数定义前添加inline,使该函数变为内联函数。
使用注意:
(1)编译器在编译阶段将内联函数的调用动作替换为函数的本体,提高效率。
(2)inline只是开发者对编译器的一个建议,编译器进不进行操作取决于编译器的诊断功能,我们无法控制。
(3)内联函数的定义必须放在头文件(.h),这样用到该内联函数的cpp文件都可通过#include把内联函数的源代码包含进来。
(4)可能造成代码膨胀,所以内联函数体要尽量小。

2.5 sizeof 和strlen 的区别

(1)sizeof是一个标识符,strlen是库函数。
(2)sizeof的参数可以是数据的类型,也可以是变量,计算的是数据类型占内存的大小。而strlen的参数只能是结尾为‘\0’的字符串,计算的是字符串实际的长度。
(3)编译器在编译时就计算出了sizeof的结果,而strlen函数必须在运行时才能计算出来。

2.6 malloc/free 和new/delete 的区别

(1)new 、delete 是标识符,可以重载,只能在C++ 中使用。
(2)malloc、free 是函数,可以覆盖,C、C++ 中都可以使用。
(3)new 可以调用对象的构造函数,对应的delete 调用相应的析构函数。
(4)malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数。
(5)new 、delete 返回的是某种数据类型指针,malloc、free 返回的是void 指针。
注意:malloc 申请的内存空间要用free 释放,而new 申请的内存空间要用delete 释放,不要混用。

2.7 static的用法和作用

2.7.1 修饰变量

(1)static修饰的变量被存储在静态存储区。
(2)局部静态变量(函数中static型变量),值维持上次调用函数时的值。
(3)全局静态变量,限制该全局变量只能在本文件中使用。(隐藏)

2.7.2 static 成员变量

(1)静态成员变量属于整个类。
(2)若在某个对象中修改了静态成员变量的值,则其它对象能直接看到修改结果。
(3)利用类名::成员变量名可以直接调用静态成员变量。

2.7.3 static 成员函数

(1)静态成员函数属于整个类。
(2)静态成员函数中不可修改和对象相关的成员变量,但是可以修改与类相关的成员变量(静态成员变量)。
(3)利用类名::成员函数名可以直接调用静态成员函数。

2.8 const用法

2.8.1 修饰指针或指针所指的数据

(1)char* const p,该指针无法指向别处。
(2)char const *p 或 const char *p,指针p指向的内存内容无法通过p修改。
(3)const char *const p,p指向的东西不可修改,同时p不可以指向其他东西

2.8.2 修饰引用

const int & a,a的内容不可以通过a修改

int main(){
    int i = 100;
    const int &a = i; //a的内容不可以通过a修改
    // a = 500; //错误
    i = 600; //正确
}
2.8.3 函数形参中的const

可以防止无意中修改了形参值导致实参指被修改。

void func1(const int &num)
{
    // num = 188; //错误
}
int main(){

    int n = 8;
    func1(n);  //形参值无法被修改,实参指也不会被误修改
}
2.8.4 成员函数末尾的const

该成员函数不会修改对象里任何成员变量的值,这种成员函数也被称为“常量成员函数”。

2.8.5 const 成员对象

(1)const类型的成员对象只能调用const类型的成员函数。
(2)const类型的成员函数既可被const对象调用也可被非const对象调用。

2.9 mutable用法

mutable(不稳定,易改变的意思),const的反义词。
用mutable修饰的成员变量永远处于可以被修改的状态,即便是在const结尾的成员函数中,也可以被修改。

2 类相关知识

2.1 C++三大特性

继承、封装、多态

2.1.1 继承

定义:
让某种类型对象获得另一个类型对象的属性和方法
功能:
可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展

2.1.2 封装

定义:
数据和代码捆绑在一起,避免外界干扰和不确定性访问
功能:
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏

2.1.3 多态

定义:
同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)
功能:
多态性是针对虚函数来说的,当父类指针指向子类对象,系统内部通过查虚函数表来确定执行哪个类的虚函数,这就是运行时期的多态性。

2.2 类成员初始化方式?为什么用成员初始化列表会快一些?

(1)声明时初始化
(2)赋值初始化:构造函数体内进行赋值初始化。
(3)列表初始化:构造函数冒号后使用初始化列表进行初始化。
区别:
赋值初始化,是在所有的数据成员被分配内存空间后才进行的。
列表初始化是给数据成员分配内存空间时就进行初始化。
由于C++的赋值操作会产生临时对象,因而降低程序的效率,故列表初始化更快。

2.3 构造函数

2.3.1 构造函数的定义

构造函数是一种特殊的成员函数,函数名与类名相同,在创建类的对象的时候,该成员函数会被系统自动调用。

2.3.2 默认构造函数

不带参数的构造函数被称为默认构造函数。
类定义中,在没有构造函数的情况下,系统会自动为我们隐式的定义一个默认构造函数。一旦我们自己定义了一个构造函数,不管该构造函数带几个参数,编译器都不会为我们创建合成的默认构造函数了。

2.3.3 拷贝构造函数

一个类的构造函数的第一个参数为所属类类型的引用。如果还有其他额外参数,且这些额外的参数都有默认值,则该构造函数就叫拷贝构造函数。

2.4 类的继承

2.4.1 继承时的调用顺序

当创建子类对象时,先调用父类的构造函数,再调用子类的构造函数。

2.4.2 访问权限与继承权限

访问权限和继承权限共有三种,从权限角度来看,public > protected > private, 访问权限和继承权限不同时,子类的访问权限取决于两者间较低的那个。
public:可以被任意实体所访问。
protected :只允许本类或子类的成员函数访问。
private:只允许本类的成员函数访问。

2.4.3 函数遮蔽

若子类中的成员函数与父类中的成员函数相同,此时子类对象. 函数名,调用的是子类的成员函数,父类成员函数被遮蔽。
解决方法:
①. 在子类成员函数中利用父类::函数名来强制调用父类的同名成员函数。
②. 通过using关键字让父类同名函数在子类中可见(只能解决子类对象中调用父类成员函数的重载版本)

2.5 虚函数

2.5.1 父类指针指向子类对象

父类指针可以指向子类对象,而子类指针无法指向父类对象。
若此时,父类和子类中存在同名的成员函数,父类指针调用的是父类的成员函数。

2.5.2 虚函数作用

使父类指针可以调用子类的同名成员函数

2.5.3 虚函数的使用

在父类的成员函数声明前加上virtual关键字,则该成员函数被声明为虚函数。
(2)一旦某个函数在父类中被声明为虚函数,那么在所有子类中它都是虚函数。
(3)子类的虚函数声明前可以不用加virtual。
(4)调用哪个类的成员函数与你new的类对象有关。

2.5.4 override与final关键字
  1. override
    为了避免在子类中写错虚函数,在C++11中,可以在虚函数声明中加上override关键字,此时,编译器会在父类中寻找与子类同名的虚函数,并用子类的虚函数将其覆盖,若在父类中没有找到同名的虚函数则报错。
    (1)override主要是为了防止程序员在子类中写错虚函数名,此时会自动报错。
    (2)override关键字用在子类中,且虚函数专用。
  2. final
    final也是虚函数专用,但其用于父类,如果我们在父类的成员函数中增加了final,则任何尝试覆盖该函数的操作都将引发错误。
2.5.5 动态绑定

调用虚函数执行的是动态绑定,即只有程序运行时,才知道调用了哪个子类的虚函数,动态绑定到Dog还是Cat,取决于new的是Dog还是Cat。

2.5.6 纯虚函数

(1)纯虚函数是在基类中声明的虚函数,令声明的虚函数=0,但是其在基类中没有定义。
(2)纯虚函数要求任何子类都要定义该虚函数自己的实现方法。
(3)一旦一个类中有纯虚函数,则这个类变成了“抽象类”,不可以生成该类的对象。
(4)抽象类不用来生成对象,主要目的用于统一管理子类对象。

class Animal //抽象类
{
public:
    virtual void eat() = 0;   //纯虚函数
};
2.5.7 虚析构

虚析构的目的:
在存在虚函数时,delete父类指针只析构了父类的对象而子类对象没有被析构,会造成内存泄漏。为了在delete父类指针时能够同时调用父类的析构函数与子类的析构函数,需要把父类的析构函数声明为virtual。

2.6 友元

1)友元类不能被继承。
2)友元关系是单向的,比如类B是类A的友元类,但并不表示类A是类B的友元类。
3)友元关系没有传递性,比如类B是类A的友元类,类C是类B的友元类,但并不代表类C是类A的友元类。

优点:使得访问protected、private更加灵活。
缺点:打破了类的封装性,降低了类的可靠性与可维护性。

2.6.1 友元函数

1)友元函数是个函数,只要让普通函数func声明为类A的友元函数,那么func这个函数就能访问类A的所有成员(成员变量、成员函数),无论是public,private,protected
2)由于友元函数不属于类成员,所以友元函数的声明不受public、protected、private的影响

2.6.2 友元类

1)可以在类A中把类B声明为类A的友元类,此时,可以在类B的成员函数中,访问类A的所有成员(成员变量、成员函数),无论是public,private,protected。
2)由于友元类不属于类成员,所以友元类的声明不受public、protected、private的影响。

2.6.3 友元成员函数

可以在类A中声明类B的成员函数为A的友元成员函数,使其可以访问A的所有成员变量、成员函数,包括public, private,protected

3 智能指针

unique_ptr:独占式指针,同一时间内,只有一个指针能指向该对象,支持移动操作但不支持拷贝与赋值操作
shared_ptr:共享式指针,多个指针指向同一对象,最后一个指针被销毁时,这个对象被释放。
weak_ptr:辅助shared_ptr。

3.1 shared_ptr

3.1.1 工作原理

我们可以认为每个shared_ptr都有一个引用计数,无论何时我们拷贝一个shared_ptr,引用计数都会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁,引用计数就会递减,如果shared_ptr的引用计数变为0,它就会调用析构函数,自动释放自己所管理的对象。

3.1.2 shared_ptr指向的内存何时被释放?

(a)该shared_ptr被释放的时候。
(b)该shared_ptr指向其他对象。

3.2 unique_ptr

独占式指针,同一时间内,只有一个指针能指向该对象,当这个unique_ptr被销毁时,它指向的对象也被销毁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值