C++知识点面经总结

写这篇文章是为了对面试遇到的知识点,和在牛客看到的一些知识点进行总结,以便自己以后好复习。会比较杂乱因为我是想到哪里写哪里,会进行持续的更新

1.static关键词的作用

C++内存分布为,代码段,只读数据段,数据段,堆  文件映射 共享区  栈  内核。

static修饰函数和全局变量的时候表明函数和全局变量是属于文件的,在编译的时候其他文件是不可见的,如果是修饰局部变量的话其和全局变量一样都是存放在数据段

中,只不过其作用域只限于函数,如果是修饰类的成员变量和成员函数的话,表明其不属于某个对象而属于类的。比如静态成员是存放在数据段的,而非静态的成员是存放在

对象内存分布中的,对象存放在哪里其就存放在哪里。

2.const关键词的作用

const修饰变量时表表明其不能修改的,首先说下内存分布:全局const对象存放在只读数据段,局部const对象存放在栈区。const有一个功能就是可以对类的成员函数进行重载

比如 void f();与void f() const是不同的函数。当我们知道不会修改成员变量的时候尽量将其声明为const。因为const对象只能调用const函数。

3指针和引用的区别

引用的底层实现也是指针,只不过二者在使用上会有一些的差别体现为:

1.指针可以不初始化,且可以更改,而引用必须初始化且不能更改

2.引用在使用方面就像对象的别名一样,对其所有的操作都会转到其引用的对象比如sizeof(a)是a的大小

而使用指针需要解引用,且指针本身是一个对象所以sizeof(p)在32位为4字节,在64位为8字节。

3对指针的++操作和对引用的++操作具有不同的意义。

4.C与C++的区别(面向对象的三大特征)(全是自己的写的欢迎讨论)

c是面向过程的编程语言,而C++是面向对象的编程语言。C++实现了封装继承和多态,

其中封装就是将数据和函数封装在一起,提供对外的接口,而隐藏内部的实现。

继承的主要目的是进行代码复用,就比如我们封装了人这个类,当想实现学生类的时候发现学生类包含人这个的许多特征,我们就可以进行继承,

分为三种继承pulbic继承,private继承,和protected继承,这三个继承的主要作用是改变基类成员对外的访问权限,比如public不会改变基类的访问权限

而privte继承会将基类成员的对外访问权限变为private,而protected继承会将基类对象的访问权限变为protected。对于基类的Private成员无论什么继承在派生类中都是不可见的

最重要的是多态,多态分为静态多态和动态多态,实现一个接口多种实现。

5.说说C++的多态

C++的多态分为静态多态和动态多态,静态多态是函数的重载和模板,在编译的时候进行确定。

函数重载发生在同一作用域中,比如同一个文件中和同一个类中,其是函数名字相同,但参数类型,数量不相同的函数(注意只是返回类型不同不是重载)

为什么需要在同一个类中呢:因为在基类中定义的函数,在派生类又写一个同名函数会对基类的函数进行掩藏。

动态多态,是在运行的时候进行确定,基类写一个虚函数,子类进行重写。当基类的指针或者引用指向派生类时调用虚函数会调用到子类的函数。

动态多态是通过虚指针和虚表来实现的,虚指针指向虚表,虚表存放着虚函数的地址,虚指针是在构造函数时候生成的,一般存放在对象的首部,而虚表是在编译的时候进行确定的

其只属于某个类而不是属于对象,存放在只读数据段。

6.构造函数可以为虚函数吗

不可以,因为调用虚函数需要虚指针,而虚指针是在构造函数期间进行生成的所有不能为虚函数。

7析构函数可以为虚函数吗

可以,而且当存在多态的时候我们就需要将其设置为虚函数,否则可能会发生内存泄露。

8.可以在构造函数或者析构函数中调用虚函数吗

可以的这么写但是最好不要,构造函数调用虚函数的时候,构造的顺序为父类->子类,先构造父对象再构造子对象

当构造到父类调用虚函数时会调用父类的函数而不是子类的函数,可以这么解释为此时的对象就是父类(参看effective C++)

析构函数也是类似的,先析构自己再析构父类。

9.说一下智能指针

shared_ptr weaked_ptr unique_ptr

其中shared_ptr的底层实现是一根指针和一个引用计数指针。可以有多个指针共享一个资源,当析构时会将引用计数减一,引用计数为0时就会对资源进行释放。

shared_ptr在循环引用的时候可能会发生内存泄露,这时就引出了weaked_ptr,其是弱引用不增加引用计数,但随时可以上升为shared_ptr;

unique_ptr是独享的,也就是禁止拷贝和赋值的,但允许移动构造和移动赋值,也就是说我们可以在函数中返回unique_ptr。或者调用std::move。

10.nullptr和NULL的区别

NULL是宏在C语言中定义为

#define NULL void*(0);

但是因为C++中不允许空指针隐式的转化为其他指针所以在C++中

#define NULL 0

也就是在C++中NULL就是0,因为可能会出现问题

void f(void *);

void f(int);

当传参为NULL时就可能发生编译错误,或者会调用第二个函数。所以C++11引入了nullptr,nullptr是一个关键字,可以随时的转换为其他类型的指针

11 map和unordered_map

map的底层实现是红黑树,unordered_map的底层实现是哈希表

红黑树的查找效率一般为O(longn),哈希表的查找效率为O(1).

但是哈希表会产生冲突,使用开链法来解决冲突

12.两次delete会发生什么

不同编译器有不同的做法,在vs2019中两次delete会产生异常。

13给定一个随机数生成器生成0的概率为p,生成1的概率为1-p求生成0和生成1的概率相等的随机数生成器 代码如下

int myrand()
{
    int i=rand();
    int j=rand();
    if(i==0&&j==1)//i==0为p j为1为1-p 则此概率为p*(1-p)
    return 0;
    else if(i==1&&j==0)//i==1为1-p j==0为p 则此概率为(1-p)*p
    return 1;
}

 14 malloc和new的区别

new是关键词需要编译器支持 malloc是库函数需要引入头文件

new返回对象的指针,无需强制转换,而malloc是返回(void*) 需要进行强制转换

new失败抛出异常,malloc失败返回NULL

new在自由存储区分配内存,而malloc在堆上分配内存

new会调用operator new  我们可以重载operator new来实现内存分配 而operator new 一般是用malloc来实现的,所以一般自由存储区也就是用堆来实现的 我们也可以重载operator new 所以自由存储区也可以是静态存储区。

有了malloc还引用new的原因是对象的引用后malloc不能满足对象的内存分配而new会调用对象的构造函数

15静态成员函数可以是虚函数吗

不能,静态函数只是一个作用域局限于类的普通函数,不与任何实例对象绑定。

16类的空指针可以调用成员函数吗

可以,只要在类中不运用this指针(也就是不使用成员函数和成员变量)。

class A
{
    int a;
    public:
    void f(){}
    void f1(){a=1;}
};
int main()
{
A*ptr=nullptr;
ptr->f();//没有问题
ptr->f1();//有问题
return 0;
}

17 shared_ptr的简单实现

template<typename T>
class myshared_ptr
{
private:
    T* ptr_;
    int* count_;
public:
    //空构造函数
    myshared_ptr() :ptr_(nullptr), count_(nullptr)
    {
    }
    //构造函数构造出count_
    myshared_ptr(T* p)
        :ptr_(p), count_(new int(1))
    {
    }
    //只有不是空指针才能对count_进行加操作
    myshared_ptr(const myshared_ptr& rhs)
    {
        ptr_ = rhs.ptr_;
        count_ = rhs.count_;
        if (ptr_ != nullptr)
            (*count_)++;
    }
    //一切前提都是不是空指针
    //当指针引用为0进行析构
    ~myshared_ptr()
    {
        if (ptr_ != nullptr)
        {
            (*count_)--;
            if (*count_ == 0)
            {
                delete ptr_;
                delete count_;
            }  
        }
    }
    //对这个进行减一如果引用为0进行析构。
    myshared_ptr& operator=(const myshared_ptr& rhs)
    {
        if (&rhs == this)
            return *this;
        if (ptr_ != nullptr)
        {
            (*count_)--;
            if (*count_ == 0)
            {
                delete ptr_;
                delete count_;
            }
        }  
        ptr_ = rhs.ptr_;
        count_ = rhs.count_;
        (*count_)++;
        return *this;
    }
    int count()const
    {
        return *count_;
    }
    //返回裸指针
    T* get()const 
    {
        return ptr_;
    }
    //重载让其像指针一样
    T& operator *()
    {
        return *ptr_;
    }
    T *operator ->()
    {
        return ptr_;
    }
};

18 .sizeof和strlen的区别

1.sizeof是运算符在编译的时候就进行计算,strlen是函数在运行的时候进行计算

2.strlen传入的是字符串计算字符串的大小遇见‘/0'就停止了,sizeof可以传入指针、数组、类、对象。还可以计算函数的返回值但不能是void

19.实现一个Memecpy

void* memcpy(void* des, void* src, size_t len)
{
    char* p1 = static_cast<char*>(des);
    char* p2 = static_cast<char*>(src);
    if (p1 == nullptr || p2 == nullptr)
        return nullptr;
    //会发生覆盖从后往前赋值
    if (p2<p1&&p2 + len > p1)
    {
        p1 = p1 + len - 1;
        p2 = p2 + len - 1;
        while (len--)
        {
            *p1-- = *p2--;
        }
    }
    else
    {
        while (len --)
        {
            *p1++ = *p2++;
        }
    }
    return des;
}

20 extern"C" 和volatile的作用

volatile的作用是防止编译器过度优化,每次存取都必须要经过内存(不会消除线程安全问题)

extern "C"的作用是所以语句像C一样编译,因为C++支持重载,所以在编译的时候会进行符号修饰,也就是对符号有命名规则

而extern "C"的作用就是不进行符号修饰像C一样编译。

21内敛函数和宏定义的区别

内敛函数是函数可以进行调试,而宏定义只是简单的字符串替换

二者都是进行展开来减少函数调用时的开销,不过内敛函数是在编译时展开而宏定义是在预处理里时展开,所以编译时候的类型检查,安全性检查这些内敛函数都有

内敛函数更加安全,而且内敛函数书写比宏定义更加方便

22#Include<>和#include""的区别

#include""先检查程序目录后检查编译指定的目录

#include<>只检查编译器指定的目录

23结构体对齐

第一个成员在与结构体变量偏移量(offset)为0的地址处。
其他成员变量要对齐到对齐数的整数倍的地址处。
对齐数 = 对齐系数 与 该成员大小的较小值。
#pragma pack(n);中的n就是对齐系数。
VS中默认的值为8;linux中的默认值为4。
结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。
 

class s1
{
    char a;//1
    double b;//8
    int c;//4
};
//1+(7)+8+4=20  需要对齐到8的整数倍 为24
#pragma pack(4)
class s2
{
    char a;
    double b;//地址为min(4,8)的整数倍
    int c;
};
//1+(3)+8+4=16  16为4的整数倍
int main()
{
    int* p;
    cout << sizeof(s1) << endl;;
    cout << sizeof(s2);
}

如果嵌套结构体那么就将结构体看作一个整体,并且这个整体的对齐数就是结构体的最大对齐数

#pragma pack(4)
class s2
{
    char a;
    double b;//地址为min(4,8)的整数倍
    int c;
};
//1+(3)+8+4=16  16为4的整数倍
#pragma pack(push)
#pragma pack(8)
class s1
{
    char a;//1
    s2 b;//对齐数为4
    int c;//4
};
//1+(3)+16+4=24

int main()
{
    int* p;
    cout << sizeof(s1) << endl;;
}
#pragma pack(4)
class s2
{
    char a;
    double b;//地址为min(4,8)的整数倍
    int c;
};
//1+(3)+8+4=16  16为4的整数倍
#pragma pack(push)
#pragma pack(8)
class s1
{
    char a;//1
    s2 b;//对齐数为4
    double c;//4
};
//1+(3)+16(4)+8=32
int main()
{
    int* p;
    cout << sizeof(s1) << endl;;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值